可执行一个简单的工作流。

This commit is contained in:
戚辰先生 2024-12-09 00:01:46 +08:00
parent 4b4d35fccd
commit f5fb0a2dae
21 changed files with 176 additions and 175 deletions

View File

@ -124,7 +124,7 @@ public class DefaultWorkflowEngine implements WorkflowEngine {
startNode.setNodeType(startNodeConfig.getType()); startNode.setNodeType(startNodeConfig.getType());
startNode.setName(startNodeConfig.getName()); startNode.setName(startNodeConfig.getName());
startNode.setConfig(objectMapper.writeValueAsString(startNodeConfig.getConfig())); startNode.setConfig(objectMapper.writeValueAsString(startNodeConfig.getConfig()));
startNode.setStatus(NodeStatusEnum.PENDING); startNode.setStatus(NodeStatusEnum.RUNNING);
startNode.setCreateTime(LocalDateTime.now()); startNode.setCreateTime(LocalDateTime.now());
nodeInstanceRepository.save(startNode); nodeInstanceRepository.save(startNode);
@ -203,7 +203,6 @@ public class DefaultWorkflowEngine implements WorkflowEngine {
.findByWorkflowInstanceAndStatusNot(instance, NodeStatusEnum.COMPLETED); .findByWorkflowInstanceAndStatusNot(instance, NodeStatusEnum.COMPLETED);
if (uncompletedNodes.isEmpty()) { if (uncompletedNodes.isEmpty()) {
instance.complete();
workflowInstanceRepository.save(instance); workflowInstanceRepository.save(instance);
} }
} catch (Exception e) { } catch (Exception e) {
@ -223,21 +222,18 @@ public class DefaultWorkflowEngine implements WorkflowEngine {
} }
private void createAndExecuteNextNode(WorkflowInstance instance, String nextNodeId, NodeConfig nodeConfig) { private void createAndExecuteNextNode(WorkflowInstance instance, String nextNodeId, NodeConfig nodeConfig) {
try {
NodeInstance nextNode = new NodeInstance(); NodeInstance nextNode = new NodeInstance();
nextNode.setNodeId(nextNodeId); nextNode.setNodeId(nextNodeId);
nextNode.setWorkflowInstance(instance); nextNode.setWorkflowInstance(instance);
nextNode.setNodeType(nodeConfig.getType()); nextNode.setNodeType(nodeConfig.getType());
nextNode.setName(nodeConfig.getName()); nextNode.setName(nodeConfig.getName());
nextNode.setConfig(objectMapper.writeValueAsString(nodeConfig.getConfig())); // nextNode.setConfigObject(nodeExecuteConfigConverter.toNodeExecutorConfig(nodeConfig));
// nextNode.setConfigObject(nodeConfig);
nextNode.setStatus(NodeStatusEnum.PENDING); nextNode.setStatus(NodeStatusEnum.PENDING);
nodeInstanceRepository.save(nextNode); nodeInstanceRepository.save(nextNode);
// 递归执行后续节点 // 递归执行后续节点
executeNode(nextNode.getId()); executeNode(nextNode.getId());
} catch (JsonProcessingException e) {
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_INVALID, e);
}
} }
@Override @Override

View File

@ -15,6 +15,5 @@ public class WorkflowEngineException extends BusinessException {
public WorkflowEngineException(ResponseCode code, Throwable cause) { public WorkflowEngineException(ResponseCode code, Throwable cause) {
super(code, new Object[]{cause.getMessage()}); super(code, new Object[]{cause.getMessage()});
initCause(cause);
} }
} }

View File

@ -5,9 +5,10 @@ import com.qqchen.deploy.backend.system.enums.LogLevelEnum;
import com.qqchen.deploy.backend.framework.enums.ResponseCode; import com.qqchen.deploy.backend.framework.enums.ResponseCode;
import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException; import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException;
import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContextOperations; import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContextOperations;
import com.qqchen.deploy.backend.workflow.engine.executor.node.config.ScriptNodeConfig; import com.qqchen.deploy.backend.workflow.engine.executor.node.config.ScriptNodeExecutorConfig;
import com.qqchen.deploy.backend.workflow.entity.NodeInstance; import com.qqchen.deploy.backend.workflow.entity.NodeInstance;
import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum; import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum;
import com.qqchen.deploy.backend.workflow.enums.ScriptLanguageEnum;
import com.qqchen.deploy.backend.workflow.service.WorkflowVariableOperations; import com.qqchen.deploy.backend.workflow.service.WorkflowVariableOperations;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -51,7 +52,7 @@ public class ScriptNodeExecutor extends AbstractNodeExecutor {
@Override @Override
public void validate(String config) { public void validate(String config) {
try { try {
ScriptNodeConfig scriptConfig = objectMapper.readValue(config, ScriptNodeConfig.class); ScriptNodeExecutorConfig scriptConfig = objectMapper.readValue(config, ScriptNodeExecutorConfig.class);
// 验证脚本内容 // 验证脚本内容
if (scriptConfig.getScript() == null || scriptConfig.getScript().trim().isEmpty()) { if (scriptConfig.getScript() == null || scriptConfig.getScript().trim().isEmpty()) {
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR, "Script content cannot be empty"); throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR, "Script content cannot be empty");
@ -78,40 +79,44 @@ public class ScriptNodeExecutor extends AbstractNodeExecutor {
@Override @Override
protected void doExecute(NodeInstance nodeInstance, WorkflowContextOperations context) { protected void doExecute(NodeInstance nodeInstance, WorkflowContextOperations context) {
try { try {
String configJson = nodeInstance.getConfig(); // String configJson = nodeInstance.getConfig();
ScriptNodeConfig config = objectMapper.readValue(configJson, ScriptNodeConfig.class); // ScriptNodeExecutorConfig config = objectMapper.readValue(configJson, ScriptNodeExecutorConfig.class);
//
// 设置重试次数和间隔 // // 设置重试次数和间隔
int maxAttempts = config.getRetryTimes() != null ? config.getRetryTimes() : 1; // int maxAttempts = config.getRetryTimes() != null ? config.getRetryTimes() : 1;
long retryInterval = config.getRetryInterval() != null ? config.getRetryInterval() : 0; // long retryInterval = config.getRetryInterval() != null ? config.getRetryInterval() : 0;
//
Exception lastException = null; // Exception lastException = null;
for (int attempt = 1; attempt <= maxAttempts; attempt++) { // for (int attempt = 1; attempt <= maxAttempts; attempt++) {
try { // try {
// 执行脚本 // // 执行脚本
ScriptNodeExecutorConfig config = new ScriptNodeExecutorConfig();
config.setLanguage(ScriptLanguageEnum.SHELL);
config.setInterpreter("/bin/bash");
config.setScript("ls -a");
executeScript(config, nodeInstance, context); executeScript(config, nodeInstance, context);
return; // 执行成功直接返回 // return; // 执行成功直接返回
} catch (Exception e) { // } catch (Exception e) {
lastException = e; // lastException = e;
if (attempt < maxAttempts) { // if (attempt < maxAttempts) {
context.log(String.format("Script execution failed (attempt %d/%d), retrying in %d seconds", // context.log(String.format("Script execution failed (attempt %d/%d), retrying in %d seconds",
attempt, maxAttempts, retryInterval), LogLevelEnum.WARN); // attempt, maxAttempts, retryInterval), LogLevelEnum.WARN);
Thread.sleep(retryInterval * 1000L); // Thread.sleep(retryInterval * 1000L);
} // }
} // }
} // }
//
// 如果所有重试都失败抛出最后一个异常 // // 如果所有重试都失败抛出最后一个异常
if (lastException != null) { // if (lastException != null) {
throw lastException; // throw lastException;
} // }
} catch (Exception e) { } catch (Exception e) {
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED, e); throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED, e);
} }
} }
private void executeScript(ScriptNodeConfig config, NodeInstance nodeInstance, WorkflowContextOperations context) throws Exception { private void executeScript(ScriptNodeExecutorConfig config, NodeInstance nodeInstance, WorkflowContextOperations context) throws Exception {
ProcessBuilder processBuilder = new ProcessBuilder(); ProcessBuilder processBuilder = new ProcessBuilder();
// 获取命令实现并构建命令 // 获取命令实现并构建命令

View File

@ -5,7 +5,7 @@ import com.qqchen.deploy.backend.system.enums.LogLevelEnum;
import com.qqchen.deploy.backend.framework.enums.ResponseCode; import com.qqchen.deploy.backend.framework.enums.ResponseCode;
import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException; import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException;
import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContextOperations; import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContextOperations;
import com.qqchen.deploy.backend.workflow.engine.executor.node.config.ShellNodeConfig; import com.qqchen.deploy.backend.workflow.engine.executor.node.config.ShellNodeExecutorConfig;
import com.qqchen.deploy.backend.workflow.entity.NodeInstance; import com.qqchen.deploy.backend.workflow.entity.NodeInstance;
import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum; import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum;
import com.qqchen.deploy.backend.workflow.service.WorkflowVariableOperations; import com.qqchen.deploy.backend.workflow.service.WorkflowVariableOperations;
@ -46,7 +46,7 @@ public class ShellNodeExecutor extends AbstractNodeExecutor {
@Override @Override
public void validate(String config) { public void validate(String config) {
try { try {
ShellNodeConfig shellConfig = objectMapper.readValue(config, ShellNodeConfig.class); ShellNodeExecutorConfig shellConfig = objectMapper.readValue(config, ShellNodeExecutorConfig.class);
// 验证执行器类型 // 验证执行器类型
if (!"SHELL".equals(shellConfig.getExecutor())) { if (!"SHELL".equals(shellConfig.getExecutor())) {
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR); throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR);
@ -74,7 +74,7 @@ public class ShellNodeExecutor extends AbstractNodeExecutor {
protected void doExecute(NodeInstance nodeInstance, WorkflowContextOperations context) { protected void doExecute(NodeInstance nodeInstance, WorkflowContextOperations context) {
try { try {
String configJson = nodeInstance.getConfig(); String configJson = nodeInstance.getConfig();
ShellNodeConfig config = objectMapper.readValue(configJson, ShellNodeConfig.class); ShellNodeExecutorConfig config = objectMapper.readValue(configJson, ShellNodeExecutorConfig.class);
// 验证执行器类型 // 验证执行器类型
if (!"SHELL".equals(config.getExecutor())) { if (!"SHELL".equals(config.getExecutor())) {
@ -110,7 +110,7 @@ public class ShellNodeExecutor extends AbstractNodeExecutor {
} }
} }
private void executeShellCommand(ShellNodeConfig config, NodeInstance nodeInstance, WorkflowContextOperations context) throws Exception { private void executeShellCommand(ShellNodeExecutorConfig config, NodeInstance nodeInstance, WorkflowContextOperations context) throws Exception {
ProcessBuilder processBuilder = new ProcessBuilder(); ProcessBuilder processBuilder = new ProcessBuilder();
processBuilder.command("sh", "-c", config.getScript()); processBuilder.command("sh", "-c", config.getScript());

View File

@ -10,7 +10,7 @@ import java.util.List;
*/ */
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class ApprovalNodeConfig extends NodeConfig { public class ApprovalNodeExecutorConfig extends NodeExecutorConfig {
/** /**
* 审批人类型USER-指定用户ROLE-指定角色LEADER-直属领导 * 审批人类型USER-指定用户ROLE-指定角色LEADER-直属领导

View File

@ -9,7 +9,7 @@ import java.util.List;
*/ */
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class ConditionNodeConfig extends NodeConfig { public class ConditionNodeExecutorConfig extends NodeExecutorConfig {
/** /**
* 条件表达式使用SpEL表达式 * 条件表达式使用SpEL表达式

View File

@ -8,7 +8,7 @@ import lombok.EqualsAndHashCode;
*/ */
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class GitNodeConfig extends NodeConfig { public class GitNodeExecutorConfig extends NodeExecutorConfig {
/** /**
* Git仓库URL * Git仓库URL

View File

@ -9,7 +9,7 @@ import java.util.Map;
*/ */
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class HttpNodeConfig extends NodeConfig { public class HttpNodeExecutorConfig extends NodeExecutorConfig {
/** /**
* 请求URL * 请求URL

View File

@ -9,7 +9,7 @@ import java.util.Map;
*/ */
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class JenkinsNodeConfig extends NodeConfig { public class JenkinsNodeExecutorConfig extends NodeExecutorConfig {
/** /**
* Jenkins服务器ID * Jenkins服务器ID

View File

@ -9,7 +9,7 @@ import java.util.Map;
*/ */
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class NacosNodeConfig extends NodeConfig { public class NacosNodeExecutorConfig extends NodeExecutorConfig {
/** /**
* Nacos服务器ID * Nacos服务器ID

View File

@ -1,66 +0,0 @@
package com.qqchen.deploy.backend.workflow.engine.executor.node.config;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum;
import lombok.Data;
/**
* 节点配置基类
*/
@Data
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = ApprovalNodeConfig.class, name = "APPROVAL"),
@JsonSubTypes.Type(value = ScriptNodeConfig.class, name = "SCRIPT"),
@JsonSubTypes.Type(value = ShellNodeConfig.class, name = "SHELL"),
@JsonSubTypes.Type(value = JenkinsNodeConfig.class, name = "JENKINS"),
@JsonSubTypes.Type(value = GitNodeConfig.class, name = "GIT"),
@JsonSubTypes.Type(value = ConditionNodeConfig.class, name = "CONDITION"),
@JsonSubTypes.Type(value = ParallelNodeConfig.class, name = "PARALLEL"),
@JsonSubTypes.Type(value = NacosNodeConfig.class, name = "NACOS"),
@JsonSubTypes.Type(value = HttpNodeConfig.class, name = "HTTP"),
@JsonSubTypes.Type(value = NotifyNodeConfig.class, name = "NOTIFY")
})
public abstract class NodeConfig {
/**
* 节点ID
*/
private String id;
/**
* 节点名称
*/
private String name;
/**
* 节点类型
*/
private NodeTypeEnum type;
/**
* 超时时间分钟
*/
private Integer timeout;
/**
* 重试次数
*/
private Integer retryCount;
/**
* 重试间隔
*/
private Integer retryInterval;
/**
* 失败是否继续
*/
private Boolean continueOnFailed;
/**
* 描述
*/
private String description;
}

View File

@ -0,0 +1,48 @@
package com.qqchen.deploy.backend.workflow.engine.executor.node.config;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum;
import lombok.Data;
/**
* 节点配置基类
*/
@Data
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
@JsonIgnoreProperties(ignoreUnknown = true) // 忽略未知字段
@JsonSubTypes({
@JsonSubTypes.Type(value = ApprovalNodeExecutorConfig.class, name = "APPROVAL"),
@JsonSubTypes.Type(value = ScriptNodeExecutorConfig.class, name = "SCRIPT"),
@JsonSubTypes.Type(value = ShellNodeExecutorConfig.class, name = "SHELL"),
@JsonSubTypes.Type(value = JenkinsNodeExecutorConfig.class, name = "JENKINS"),
@JsonSubTypes.Type(value = GitNodeExecutorConfig.class, name = "GIT"),
@JsonSubTypes.Type(value = ConditionNodeExecutorConfig.class, name = "CONDITION"),
@JsonSubTypes.Type(value = ParallelNodeExecutorConfig.class, name = "PARALLEL"),
@JsonSubTypes.Type(value = NacosNodeExecutorConfig.class, name = "NACOS"),
@JsonSubTypes.Type(value = HttpNodeExecutorConfig.class, name = "HTTP"),
@JsonSubTypes.Type(value = NotifyNodeExecutorConfig.class, name = "NOTIFY")
})
public class NodeExecutorConfig {
/**
* 节点ID
*/
private String id;
/**
* 节点名称
*/
private String name;
/**
* 节点类型
*/
private NodeTypeEnum type;
/**
* 描述
*/
private String description;
}

View File

@ -10,7 +10,7 @@ import java.util.Map;
*/ */
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class NotifyNodeConfig extends NodeConfig { public class NotifyNodeExecutorConfig extends NodeExecutorConfig {
/** /**
* 通知类型EMAIL-邮件SMS-短信DINGTALK-钉钉WEIXIN-企业微信 * 通知类型EMAIL-邮件SMS-短信DINGTALK-钉钉WEIXIN-企业微信

View File

@ -9,7 +9,7 @@ import java.util.List;
*/ */
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class ParallelNodeConfig extends NodeConfig { public class ParallelNodeExecutorConfig extends NodeExecutorConfig {
/** /**
* 并行分支列表 * 并行分支列表

View File

@ -12,7 +12,7 @@ import java.util.Map;
*/ */
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class ScriptNodeConfig extends NodeConfig { public class ScriptNodeExecutorConfig extends NodeExecutorConfig {
/** /**
* 脚本内容 * 脚本内容
*/ */
@ -39,16 +39,6 @@ public class ScriptNodeConfig extends NodeConfig {
*/ */
private Integer timeout; private Integer timeout;
/**
* 环境变量
*/
private Map<String, String> environment;
/**
* 成功退出码
*/
private Integer successExitCode = 0;
/** /**
* 重试次数 * 重试次数
*/ */
@ -60,15 +50,17 @@ public class ScriptNodeConfig extends NodeConfig {
private Integer retryInterval; private Integer retryInterval;
/** /**
* 脚本参数 * 环境变量
*/ */
private Map<String, String> arguments; private Map<String, String> environment;
public String getScript() { /**
return script; * 成功退出码
} */
private Integer successExitCode = 0;
public Map<String, String> getEnvironment() { /**
return environment; * 失败是否继续
} */
private Boolean continueOnFailed;
} }

View File

@ -7,12 +7,12 @@ import java.util.Map;
/** /**
* Shell节点配置 * Shell节点配置
* @deprecated 请使用 {@link ScriptNodeConfig} 替代设置 language="shell" * @deprecated 请使用 {@link ScriptNodeExecutorConfig} 替代设置 language="shell"
*/ */
@Deprecated(since = "1.0", forRemoval = true) @Deprecated(since = "1.0", forRemoval = true)
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class ShellNodeConfig extends NodeConfig { public class ShellNodeExecutorConfig extends NodeExecutorConfig {
/** /**
* 执行器类型固定为 SHELL * 执行器类型固定为 SHELL
*/ */

View File

@ -1,6 +1,6 @@
package com.qqchen.deploy.backend.workflow.engine.executor.node.script.command; package com.qqchen.deploy.backend.workflow.engine.executor.node.script.command;
import com.qqchen.deploy.backend.workflow.engine.executor.node.config.ScriptNodeConfig; import com.qqchen.deploy.backend.workflow.engine.executor.node.config.ScriptNodeExecutorConfig;
import java.util.List; import java.util.List;
/** /**
@ -12,5 +12,5 @@ public interface ScriptCommand {
* @param config 脚本配置 * @param config 脚本配置
* @return 命令行参数列表 * @return 命令行参数列表
*/ */
List<String> buildCommand(ScriptNodeConfig config); List<String> buildCommand(ScriptNodeExecutorConfig config);
} }

View File

@ -2,7 +2,7 @@ package com.qqchen.deploy.backend.workflow.engine.executor.node.script.command.i
import com.qqchen.deploy.backend.framework.enums.ResponseCode; import com.qqchen.deploy.backend.framework.enums.ResponseCode;
import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException; import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException;
import com.qqchen.deploy.backend.workflow.engine.executor.node.config.ScriptNodeConfig; import com.qqchen.deploy.backend.workflow.engine.executor.node.config.ScriptNodeExecutorConfig;
import com.qqchen.deploy.backend.workflow.engine.executor.node.script.command.ScriptCommand; import com.qqchen.deploy.backend.workflow.engine.executor.node.script.command.ScriptCommand;
import com.qqchen.deploy.backend.workflow.engine.executor.node.script.command.ScriptLanguageSupport; import com.qqchen.deploy.backend.workflow.engine.executor.node.script.command.ScriptLanguageSupport;
import com.qqchen.deploy.backend.workflow.enums.ScriptLanguageEnum; import com.qqchen.deploy.backend.workflow.enums.ScriptLanguageEnum;
@ -17,7 +17,7 @@ import java.util.List;
@ScriptLanguageSupport(ScriptLanguageEnum.PYTHON) @ScriptLanguageSupport(ScriptLanguageEnum.PYTHON)
public class PythonScriptCommand implements ScriptCommand { public class PythonScriptCommand implements ScriptCommand {
@Override @Override
public List<String> buildCommand(ScriptNodeConfig config) { public List<String> buildCommand(ScriptNodeExecutorConfig config) {
if (config.getInterpreter() == null || config.getInterpreter().trim().isEmpty()) { if (config.getInterpreter() == null || config.getInterpreter().trim().isEmpty()) {
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR, throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR,
"Python interpreter path must be specified"); "Python interpreter path must be specified");

View File

@ -2,7 +2,7 @@ package com.qqchen.deploy.backend.workflow.engine.executor.node.script.command.i
import com.qqchen.deploy.backend.framework.enums.ResponseCode; import com.qqchen.deploy.backend.framework.enums.ResponseCode;
import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException; import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException;
import com.qqchen.deploy.backend.workflow.engine.executor.node.config.ScriptNodeConfig; import com.qqchen.deploy.backend.workflow.engine.executor.node.config.ScriptNodeExecutorConfig;
import com.qqchen.deploy.backend.workflow.engine.executor.node.script.command.ScriptCommand; import com.qqchen.deploy.backend.workflow.engine.executor.node.script.command.ScriptCommand;
import com.qqchen.deploy.backend.workflow.engine.executor.node.script.command.ScriptLanguageSupport; import com.qqchen.deploy.backend.workflow.engine.executor.node.script.command.ScriptLanguageSupport;
import com.qqchen.deploy.backend.workflow.enums.ScriptLanguageEnum; import com.qqchen.deploy.backend.workflow.enums.ScriptLanguageEnum;
@ -17,7 +17,7 @@ import java.util.List;
@ScriptLanguageSupport(ScriptLanguageEnum.SHELL) @ScriptLanguageSupport(ScriptLanguageEnum.SHELL)
public class ShellScriptCommand implements ScriptCommand { public class ShellScriptCommand implements ScriptCommand {
@Override @Override
public List<String> buildCommand(ScriptNodeConfig config) { public List<String> buildCommand(ScriptNodeExecutorConfig config) {
if (config.getInterpreter() == null || config.getInterpreter().trim().isEmpty()) { if (config.getInterpreter() == null || config.getInterpreter().trim().isEmpty()) {
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR, throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR,
"Shell interpreter path must be specified"); "Shell interpreter path must be specified");

View File

@ -1,7 +1,12 @@
package com.qqchen.deploy.backend.workflow.entity; package com.qqchen.deploy.backend.workflow.entity;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.qqchen.deploy.backend.framework.annotation.LogicDelete; import com.qqchen.deploy.backend.framework.annotation.LogicDelete;
import com.qqchen.deploy.backend.framework.domain.Entity; import com.qqchen.deploy.backend.framework.domain.Entity;
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
import com.qqchen.deploy.backend.framework.utils.SpringUtils;
import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException;
import com.qqchen.deploy.backend.workflow.engine.executor.node.config.NodeExecutorConfig;
import com.qqchen.deploy.backend.workflow.enums.NodeStatusEnum; import com.qqchen.deploy.backend.workflow.enums.NodeStatusEnum;
import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum; import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum;
import jakarta.persistence.*; import jakarta.persistence.*;
@ -10,6 +15,9 @@ import lombok.EqualsAndHashCode;
import java.time.LocalDateTime; import java.time.LocalDateTime;
/**
*
*/
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@jakarta.persistence.Entity @jakarta.persistence.Entity
@ -18,77 +26,84 @@ import java.time.LocalDateTime;
public class NodeInstance extends Entity<Long> { public class NodeInstance extends Entity<Long> {
/** /**
* 工作流实例 *
*/ */
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "workflow_instance_id", nullable = false) @JoinColumn(name = "workflow_instance_id", nullable = false)
private WorkflowInstance workflowInstance; private WorkflowInstance workflowInstance;
/** /**
* 节点ID * ID
*/ */
@Column(nullable = false) @Column(nullable = false)
private String nodeId; private String nodeId;
/** /**
* 节点类型 *
*/ */
@Enumerated(EnumType.ORDINAL) @Enumerated(EnumType.ORDINAL)
@Column(nullable = false) @Column(nullable = false)
private NodeTypeEnum nodeType; private NodeTypeEnum nodeType;
/** /**
* 节点名称 *
*/ */
@Column(nullable = false) @Column(nullable = false)
private String name; private String name;
/** /**
* 节点状态 *
*/ */
@Enumerated(EnumType.ORDINAL) @Enumerated(EnumType.ORDINAL)
@Column(nullable = false) @Column(nullable = false)
private NodeStatusEnum status; private NodeStatusEnum status;
/** /**
* 开始时间 *
*/ */
private LocalDateTime startTime; private LocalDateTime startTime;
/** /**
* 结束时间 *
*/ */
private LocalDateTime endTime; private LocalDateTime endTime;
/** /**
* 节点配置(JSON) * (JSON)
*/ */
@Column(columnDefinition = "TEXT") @Column(columnDefinition = "TEXT")
private String config; private String config;
/** /**
* 输入参数(JSON) * (JSON)
*/ */
@Column(columnDefinition = "TEXT") @Column(columnDefinition = "TEXT")
private String input; private String input;
/** /**
* 输出结果(JSON) * (JSON)
*/ */
@Column(columnDefinition = "TEXT") @Column(columnDefinition = "TEXT")
private String output; private String output;
/** /**
* 错误信息 *
*/ */
@Column(columnDefinition = "TEXT") @Column(columnDefinition = "TEXT")
private String error; private String error;
/** /**
* 前置节点ID * ID
*/ */
private String preNodeId; private String preNodeId;
@Transient
private NodeExecutorConfig configObject;
private static ObjectMapper getObjectMapper() {
return SpringUtils.getBean(ObjectMapper.class);
}
public String getConfig() { public String getConfig() {
return config; return config;
} }
@ -96,4 +111,26 @@ public class NodeInstance extends Entity<Long> {
public String getPreNodeId() { public String getPreNodeId() {
return preNodeId; return preNodeId;
} }
public <T extends NodeExecutorConfig> T getConfigObject(Class<T> configClass) {
if (configObject == null && config != null) {
try {
configObject = getObjectMapper().readValue(this.config, configClass);
} catch (Exception e) {
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR,
String.format("Failed to parse config for node %s: %s", name, e.getMessage()));
}
}
return (T) configObject;
}
public void setConfigObject(NodeExecutorConfig config) {
this.configObject = config;
try {
this.config = getObjectMapper().writeValueAsString(config);
} catch (Exception e) {
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR,
String.format("Failed to serialize config for node %s: %s", name, e.getMessage()));
}
}
} }

View File

@ -161,16 +161,6 @@ public class WorkflowInstance extends Entity<Long> {
this.error = reason; this.error = reason;
} }
/**
* 完成工作流实例
*/
public void complete() {
if (!canComplete()) {
throw new IllegalStateException("Cannot complete workflow instance in " + status + " status");
}
this.status = WorkflowInstanceStatusEnum.COMPLETED;
this.endTime = LocalDateTime.now();
}
/** /**
* 标记工作流实例为失败 * 标记工作流实例为失败