可正常启动

This commit is contained in:
dengqichen 2024-12-04 18:22:15 +08:00
parent 9fce2fc900
commit c486a6edaa
7 changed files with 273 additions and 15 deletions

View File

@ -120,7 +120,10 @@ public enum ResponseCode {
WORKFLOW_DEPENDENCY_NOT_SATISFIED(2750, "workflow.dependency.not.satisfied"),
WORKFLOW_CIRCULAR_DEPENDENCY(2751, "workflow.circular.dependency"),
WORKFLOW_SCHEDULE_INVALID(2752, "workflow.schedule.invalid"),
WORKFLOW_CONCURRENT_LIMIT_EXCEEDED(2753, "workflow.concurrent.limit.exceeded");
WORKFLOW_CONCURRENT_LIMIT_EXCEEDED(2753, "workflow.concurrent.limit.exceeded"),
WORKFLOW_CONFIG_INVALID(2754, "workflow.config.invalid"),
WORKFLOW_TRANSITION_INVALID(2755, "workflow.transition.invalid"),
WORKFLOW_CONDITION_INVALID(2756, "workflow.condition.invalid");
private final int code;
private final String messageKey; // 国际化消息key

View File

@ -1,11 +1,14 @@
package com.qqchen.deploy.backend.workflow.engine.parser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException;
import com.qqchen.deploy.backend.workflow.engine.node.NodeConfig;
import com.qqchen.deploy.backend.workflow.entity.NodeConfig;
import com.qqchen.deploy.backend.workflow.entity.TransitionConfig;
import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@ -42,7 +45,7 @@ public class WorkflowDefinitionParser {
for (JsonNode node : nodesNode) {
NodeConfig config = objectMapper.treeToValue(node, NodeConfig.class);
nodes.add(config);
log.debug("Parsed node: id={}, type={}", config.getId(), config.getType());
log.debug("Parsed node: id={}, type={}", config.getNodeId(), config.getType());
}
return nodes;
} catch (JsonProcessingException e) {
@ -75,7 +78,7 @@ public class WorkflowDefinitionParser {
return transitions;
} catch (JsonProcessingException e) {
log.error("Failed to parse transition config: {}", e.getMessage(), e);
throw new WorkflowEngineException(ResponseCode.WORKFLOW_TRANSITION_CONFIG_INVALID, e);
throw new WorkflowEngineException(ResponseCode.WORKFLOW_CONFIG_INVALID, e);
}
}
@ -97,7 +100,7 @@ public class WorkflowDefinitionParser {
}
if (!hasStart || !hasEnd) {
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_INVALID,
throw new WorkflowEngineException(ResponseCode.WORKFLOW_CONFIG_INVALID,
"Workflow must have both START and END nodes");
}
@ -107,16 +110,16 @@ public class WorkflowDefinitionParser {
boolean targetExists = false;
for (NodeConfig node : nodes) {
if (node.getId().equals(transition.getSourceNodeId())) {
if (node.getNodeId().equals(transition.getSourceNodeId())) {
sourceExists = true;
}
if (node.getId().equals(transition.getTargetNodeId())) {
if (node.getNodeId().equals(transition.getTargetNodeId())) {
targetExists = true;
}
}
if (!sourceExists || !targetExists) {
throw new WorkflowEngineException(ResponseCode.WORKFLOW_TRANSITION_CONFIG_INVALID,
throw new WorkflowEngineException(ResponseCode.WORKFLOW_CONFIG_INVALID,
String.format("Invalid node reference in transition: %s -> %s",
transition.getSourceNodeId(), transition.getTargetNodeId()));
}
@ -132,17 +135,17 @@ public class WorkflowDefinitionParser {
boolean hasOutgoing = false;
for (TransitionConfig transition : transitions) {
if (transition.getTargetNodeId().equals(node.getId())) {
if (transition.getTargetNodeId().equals(node.getNodeId())) {
hasIncoming = true;
}
if (transition.getSourceNodeId().equals(node.getId())) {
if (transition.getSourceNodeId().equals(node.getNodeId())) {
hasOutgoing = true;
}
}
if (!hasIncoming || !hasOutgoing) {
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_INVALID,
String.format("Isolated node found: %s", node.getId()));
throw new WorkflowEngineException(ResponseCode.WORKFLOW_CONFIG_INVALID,
String.format("Isolated node found: %s", node.getNodeId()));
}
}

View File

@ -0,0 +1,119 @@
package com.qqchen.deploy.backend.workflow.entity;
import com.qqchen.deploy.backend.framework.domain.Entity;
import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum;
import jakarta.persistence.Column;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.type.SqlTypes;
import java.util.Map;
/**
* 节点配置
* 用于定义工作流中的节点包括节点的基本信息和特定配置
*/
@Data
@EqualsAndHashCode(callSuper = true)
@jakarta.persistence.Entity
@Table(name = "wf_node_config")
public class NodeConfig extends Entity<Long> {
/**
* 节点ID在同一个工作流中必须唯一
*/
@NotBlank(message = "节点ID不能为空")
@Column(nullable = false)
private String nodeId;
/**
* 节点名称用于显示
*/
@NotBlank(message = "节点名称不能为空")
@Column(nullable = false)
private String name;
/**
* 节点类型决定了节点的行为
* START: 开始节点每个工作流必须有且只有一个
* END: 结束节点每个工作流必须至少有一个
* TASK: 任务节点执行具体的任务
* GATEWAY: 网关节点控制流程的分支和合并
*/
@NotNull(message = "节点类型不能为空")
@Column(nullable = false)
private NodeTypeEnum type;
/**
* 节点配置不同类型的节点有不同的配置
* TASK节点
* - type: SHELL/HTTP/JAVA
* - config: 具体的任务配置
* GATEWAY节点
* - type: EXCLUSIVE/PARALLEL/INCLUSIVE
* - conditions: 分支条件配置
*/
@JdbcTypeCode(SqlTypes.JSON)
@Column(columnDefinition = "json")
private Map<String, Object> config;
/**
* 工作流定义ID
*/
@Column(name = "workflow_definition_id", nullable = false)
private Long workflowDefinitionId;
/**
* 检查节点配置是否有效
*
* @return true if valid, false otherwise
*/
public boolean isValid() {
if (nodeId == null || nodeId.trim().isEmpty()) {
return false;
}
if (name == null || name.trim().isEmpty()) {
return false;
}
if (type == null) {
return false;
}
// 检查特定类型节点的配置
if (type == NodeTypeEnum.TASK && (config == null || !config.containsKey("type"))) {
return false;
}
if (type == NodeTypeEnum.GATEWAY && (config == null || !config.containsKey("type"))) {
return false;
}
return true;
}
/**
* 获取任务类型仅对TASK类型节点有效
*
* @return 任务类型
*/
public String getTaskType() {
if (type != NodeTypeEnum.TASK || config == null) {
return null;
}
return (String) config.get("type");
}
/**
* 获取网关类型仅对GATEWAY类型节点有效
*
* @return 网关类型
*/
public String getGatewayType() {
if (type != NodeTypeEnum.GATEWAY || config == null) {
return null;
}
return (String) config.get("type");
}
}

View File

@ -0,0 +1,100 @@
package com.qqchen.deploy.backend.workflow.entity;
import com.qqchen.deploy.backend.framework.domain.Entity;
import jakarta.persistence.Column;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 流转配置
* 用于定义工作流中节点之间的连接关系和流转条件
*/
@Data
@EqualsAndHashCode(callSuper = true)
@jakarta.persistence.Entity
@Table(name = "wf_transition_config")
public class TransitionConfig extends Entity<Long> {
/**
* 源节点ID
* 流转的起始节点
*/
@NotBlank(message = "源节点ID不能为空")
@Column(name = "source_node_id", nullable = false)
private String sourceNodeId;
/**
* 目标节点ID
* 流转的目标节点
*/
@NotBlank(message = "目标节点ID不能为空")
@Column(name = "target_node_id", nullable = false)
private String targetNodeId;
/**
* 流转条件使用SpEL表达式
* 为空表示无条件流转
* 示例
* - "${status == 'SUCCESS'}"
* - "${amount > 1000}"
* - "${result.code == 200 && result.data != null}"
*/
@Column(columnDefinition = "text")
private String condition;
/**
* 优先级数字越小优先级越高
* 用于控制多个出向流转的执行顺序
* 默认为0
*/
@NotNull(message = "优先级不能为空")
@Column(nullable = false)
private Integer priority = 0;
/**
* 工作流定义ID
*/
@Column(name = "workflow_definition_id", nullable = false)
private Long workflowDefinitionId;
/**
* 检查流转配置是否有效
*
* @return true if valid, false otherwise
*/
public boolean isValid() {
if (sourceNodeId == null || sourceNodeId.trim().isEmpty()) {
return false;
}
if (targetNodeId == null || targetNodeId.trim().isEmpty()) {
return false;
}
if (sourceNodeId.equals(targetNodeId)) {
return false; // 不允许自循环
}
if (workflowDefinitionId == null) {
return false;
}
return true;
}
/**
* 是否是条件流转
*
* @return true if conditional, false otherwise
*/
public boolean isConditional() {
return condition != null && !condition.trim().isEmpty();
}
/**
* 获取优先级如果未设置则返回默认值0
*
* @return 优先级值
*/
public int getPriorityValue() {
return priority != null ? priority : 0;
}
}

View File

@ -148,4 +148,39 @@ workflow.node.config.error=工作流节点配置错误
workflow.execution.error=工作流执行错误
workflow.not.draft=只有草稿状态的工作流定义可以发布
workflow.not.published=只有已发布状态的工作流定义可以禁用
workflow.not.disabled=只有已禁用状态的工作流定义可以启用
workflow.not.disabled=只有已禁用状态的工作流定义可以启用
# System level messages (1xxx)
success=操作成功
system.error=系统错误
param.error=参数错误
unauthorized=未授权
forbidden=禁止访问
not.found=资源不存在
method.not.allowed=方法不允许
conflict=资源冲突
too.many.requests=请求过于频繁
internal.server.error=内部服务器错误
# Business level messages (2xxx)
# Common business messages (2000-2099)
business.error=业务错误
data.not.found=数据不存在
data.already.exists=数据已存在
data.validation.failed=数据验证失败
operation.not.allowed=操作不允许
# Workflow related messages (2100-2199)
workflow.not.found=工作流不存在
workflow.already.exists=工作流已存在
workflow.not.published=工作流未发布
workflow.config.invalid=工作流配置无效
workflow.node.not.found=工作流节点不存在
workflow.node.execution.failed=工作流节点执行失败
workflow.instance.not.found=工作流实例不存在
workflow.instance.not.running=工作流实例未运行
workflow.variable.not.found=工作流变量不存在
workflow.log.not.found=工作流日志不存在
workflow.transition.invalid=工作流流转配置无效
workflow.node.type.not.supported=不支持的节点类型
workflow.condition.invalid=工作流条件配置无效