可正常启动
This commit is contained in:
parent
9fce2fc900
commit
c486a6edaa
@ -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
|
||||
|
||||
@ -1 +0,0 @@
|
||||
|
||||
@ -1 +0,0 @@
|
||||
|
||||
@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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=工作流条件配置无效
|
||||
Loading…
Reference in New Issue
Block a user