增加脚本适配工厂
This commit is contained in:
parent
79edbbcb82
commit
048d53855c
@ -113,6 +113,7 @@ public enum ResponseCode {
|
|||||||
WORKFLOW_NODE_TIMEOUT(2724, "workflow.node.timeout"),
|
WORKFLOW_NODE_TIMEOUT(2724, "workflow.node.timeout"),
|
||||||
WORKFLOW_NODE_CONFIG_ERROR(2725, "workflow.node.config.error"),
|
WORKFLOW_NODE_CONFIG_ERROR(2725, "workflow.node.config.error"),
|
||||||
WORKFLOW_EXECUTION_ERROR(2726, "workflow.execution.error"),
|
WORKFLOW_EXECUTION_ERROR(2726, "workflow.execution.error"),
|
||||||
|
WORKFLOW_CONFIG_ERROR(2727, "workflow.config.error"),
|
||||||
WORKFLOW_VARIABLE_NOT_FOUND(2730, "workflow.variable.not.found"),
|
WORKFLOW_VARIABLE_NOT_FOUND(2730, "workflow.variable.not.found"),
|
||||||
WORKFLOW_VARIABLE_TYPE_INVALID(2731, "workflow.variable.type.invalid"),
|
WORKFLOW_VARIABLE_TYPE_INVALID(2731, "workflow.variable.type.invalid"),
|
||||||
WORKFLOW_PERMISSION_DENIED(2740, "workflow.permission.denied"),
|
WORKFLOW_PERMISSION_DENIED(2740, "workflow.permission.denied"),
|
||||||
|
|||||||
@ -0,0 +1,38 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.dto;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点配置DTO
|
||||||
|
* 用于前端传递节点配置信息
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class NodeConfigDTO {
|
||||||
|
/**
|
||||||
|
* 节点ID
|
||||||
|
*/
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点类型
|
||||||
|
*/
|
||||||
|
private NodeTypeEnum type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点名称
|
||||||
|
*/
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点配置
|
||||||
|
*/
|
||||||
|
private Map<String, Object> config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点描述
|
||||||
|
*/
|
||||||
|
private String description;
|
||||||
|
}
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.dto;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点定义DTO,用于解析工作流定义中的节点配置
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class NodeDefinition {
|
||||||
|
/**
|
||||||
|
* 节点ID
|
||||||
|
*/
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点类型
|
||||||
|
*/
|
||||||
|
private NodeTypeEnum type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点名称
|
||||||
|
*/
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下一个节点的ID列表
|
||||||
|
*/
|
||||||
|
private List<String> next;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点配置,不同类型的节点有不同的配置
|
||||||
|
*/
|
||||||
|
private Map<String, Object> config;
|
||||||
|
}
|
||||||
@ -10,16 +10,6 @@ import lombok.EqualsAndHashCode;
|
|||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
|
|
||||||
import com.qqchen.deploy.backend.workflow.enums.NodeStatusEnum;
|
|
||||||
import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum;
|
|
||||||
import jakarta.validation.constraints.NotBlank;
|
|
||||||
import jakarta.validation.constraints.NotNull;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 节点实例DTO
|
* 节点实例DTO
|
||||||
*/
|
*/
|
||||||
@ -72,6 +62,11 @@ public class NodeInstanceDTO extends BaseDTO {
|
|||||||
*/
|
*/
|
||||||
private String config;
|
private String config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点描述
|
||||||
|
*/
|
||||||
|
private String description;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 输入参数(JSON)
|
* 输入参数(JSON)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -17,7 +17,9 @@ 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 com.qqchen.deploy.backend.workflow.enums.WorkflowDefinitionStatusEnum;
|
import com.qqchen.deploy.backend.workflow.enums.WorkflowDefinitionStatusEnum;
|
||||||
import com.qqchen.deploy.backend.workflow.enums.WorkflowInstanceStatusEnum;
|
import com.qqchen.deploy.backend.workflow.enums.WorkflowInstanceStatusEnum;
|
||||||
|
import com.qqchen.deploy.backend.workflow.repository.INodeConfigRepository;
|
||||||
import com.qqchen.deploy.backend.workflow.repository.INodeInstanceRepository;
|
import com.qqchen.deploy.backend.workflow.repository.INodeInstanceRepository;
|
||||||
|
import com.qqchen.deploy.backend.workflow.repository.ITransitionConfigRepository;
|
||||||
import com.qqchen.deploy.backend.workflow.repository.IWorkflowDefinitionRepository;
|
import com.qqchen.deploy.backend.workflow.repository.IWorkflowDefinitionRepository;
|
||||||
import com.qqchen.deploy.backend.workflow.repository.IWorkflowInstanceRepository;
|
import com.qqchen.deploy.backend.workflow.repository.IWorkflowInstanceRepository;
|
||||||
import com.qqchen.deploy.backend.workflow.service.WorkflowVariableOperations;
|
import com.qqchen.deploy.backend.workflow.service.WorkflowVariableOperations;
|
||||||
@ -43,6 +45,12 @@ public class DefaultWorkflowEngine implements WorkflowEngine {
|
|||||||
@Resource
|
@Resource
|
||||||
private INodeInstanceRepository nodeInstanceRepository;
|
private INodeInstanceRepository nodeInstanceRepository;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private INodeConfigRepository nodeConfigRepository;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ITransitionConfigRepository transitionConfigRepository;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private Map<NodeTypeEnum, NodeExecutor> nodeExecutors;
|
private Map<NodeTypeEnum, NodeExecutor> nodeExecutors;
|
||||||
|
|
||||||
@ -82,19 +90,51 @@ public class DefaultWorkflowEngine implements WorkflowEngine {
|
|||||||
variableOperations.setVariables(instance.getId(), variables);
|
variableOperations.setVariables(instance.getId(), variables);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. 创建并执行开始节点
|
// 5. 解析并保存节点和流转配置
|
||||||
|
try {
|
||||||
|
// 清除旧的配置(如果存在)
|
||||||
|
nodeConfigRepository.deleteByWorkflowDefinitionId(definition.getId());
|
||||||
|
transitionConfigRepository.deleteByWorkflowDefinitionId(definition.getId());
|
||||||
|
|
||||||
|
// 解析并保存新的配置
|
||||||
|
List<NodeConfig> nodeConfigs = workflowDefinitionParser.parseNodeConfig(definition.getNodeConfig());
|
||||||
|
List<TransitionConfig> transitions = workflowDefinitionParser.parseTransitionConfig(definition.getTransitionConfig());
|
||||||
|
|
||||||
|
// 保存节点配置
|
||||||
|
for (NodeConfig config : nodeConfigs) {
|
||||||
|
config.setWorkflowDefinitionId(definition.getId());
|
||||||
|
nodeConfigRepository.save(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存流转配置
|
||||||
|
for (TransitionConfig config : transitions) {
|
||||||
|
config.setWorkflowDefinitionId(definition.getId());
|
||||||
|
transitionConfigRepository.save(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. 查找并创建开始节点
|
||||||
|
NodeConfig startNodeConfig = nodeConfigs.stream()
|
||||||
|
.filter(n -> n.getType() == NodeTypeEnum.START)
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow(() -> new WorkflowEngineException(ResponseCode.WORKFLOW_CONFIG_INVALID, "Start node not found"));
|
||||||
|
|
||||||
NodeInstance startNode = new NodeInstance();
|
NodeInstance startNode = new NodeInstance();
|
||||||
startNode.setWorkflowInstance(instance);
|
startNode.setWorkflowInstance(instance);
|
||||||
startNode.setNodeId("start");
|
startNode.setNodeId(startNodeConfig.getNodeId());
|
||||||
startNode.setNodeType(NodeTypeEnum.START);
|
startNode.setNodeType(startNodeConfig.getType());
|
||||||
startNode.setName("开始节点");
|
startNode.setName(startNodeConfig.getName());
|
||||||
|
startNode.setConfig(objectMapper.writeValueAsString(startNodeConfig.getConfig()));
|
||||||
startNode.setStatus(NodeStatusEnum.PENDING);
|
startNode.setStatus(NodeStatusEnum.PENDING);
|
||||||
startNode.setCreateTime(LocalDateTime.now());
|
startNode.setCreateTime(LocalDateTime.now());
|
||||||
nodeInstanceRepository.save(startNode);
|
nodeInstanceRepository.save(startNode);
|
||||||
|
|
||||||
// 6. 执行开始节点
|
// 7. 执行开始节点
|
||||||
executeNode(startNode.getId());
|
executeNode(startNode.getId());
|
||||||
|
|
||||||
return instance;
|
return instance;
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_CONFIG_ERROR, e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -120,25 +160,29 @@ public class DefaultWorkflowEngine implements WorkflowEngine {
|
|||||||
.variableOperations(variableOperations)
|
.variableOperations(variableOperations)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
try {
|
||||||
// 执行节点
|
// 执行节点
|
||||||
executor.execute(nodeInstance, context);
|
executor.execute(nodeInstance, context);
|
||||||
|
|
||||||
// 3. 更新节点状态
|
// 更新节点状态
|
||||||
nodeInstance.setStatus(NodeStatusEnum.COMPLETED);
|
nodeInstance.setStatus(NodeStatusEnum.COMPLETED);
|
||||||
nodeInstance.setEndTime(LocalDateTime.now());
|
nodeInstance.setEndTime(LocalDateTime.now());
|
||||||
nodeInstanceRepository.save(nodeInstance);
|
nodeInstanceRepository.save(nodeInstance);
|
||||||
|
|
||||||
// 4. 获取并执行后续节点
|
// 从数据库获取流转配置
|
||||||
WorkflowDefinition definition = instance.getWorkflowDefinition();
|
List<TransitionConfig> transitions = transitionConfigRepository
|
||||||
List<TransitionConfig> transitions = workflowDefinitionParser.parseTransitionConfig(definition.getTransitionConfig());
|
.findByWorkflowDefinitionId(instance.getWorkflowDefinition().getId());
|
||||||
List<NodeConfig> nodeConfigs = workflowDefinitionParser.parseNodeConfig(definition.getNodeConfig());
|
|
||||||
|
|
||||||
// 获取当前节点的后续节点
|
// 获取当前节点的后续节点
|
||||||
List<String> nextNodeIds = transitions.stream()
|
List<String> nextNodeIds = transitions.stream()
|
||||||
.filter(t -> t.getSourceNodeId().equals(nodeInstance.getNodeId()))
|
.filter(t -> t.getFrom().equals(nodeInstance.getNodeId()))
|
||||||
.map(TransitionConfig::getTargetNodeId)
|
.map(TransitionConfig::getTo)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
|
// 获取节点配置
|
||||||
|
List<NodeConfig> nodeConfigs = nodeConfigRepository
|
||||||
|
.findByWorkflowDefinitionId(instance.getWorkflowDefinition().getId());
|
||||||
|
|
||||||
// 创建并执行后续节点
|
// 创建并执行后续节点
|
||||||
for (String nextNodeId : nextNodeIds) {
|
for (String nextNodeId : nextNodeIds) {
|
||||||
NodeConfig nodeConfig = nodeConfigs.stream()
|
NodeConfig nodeConfig = nodeConfigs.stream()
|
||||||
@ -154,14 +198,28 @@ public class DefaultWorkflowEngine implements WorkflowEngine {
|
|||||||
createAndExecuteNextNode(instance, nextNodeId, nodeConfig);
|
createAndExecuteNextNode(instance, nextNodeId, nodeConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. 检查是否所有节点都已完成
|
// 检查是否所有节点都已完成
|
||||||
List<NodeInstance> uncompletedNodes = nodeInstanceRepository.findByWorkflowInstanceAndStatusNot(
|
List<NodeInstance> uncompletedNodes = nodeInstanceRepository
|
||||||
instance, NodeStatusEnum.COMPLETED);
|
.findByWorkflowInstanceAndStatusNot(instance, NodeStatusEnum.COMPLETED);
|
||||||
|
|
||||||
if (uncompletedNodes.isEmpty()) {
|
if (uncompletedNodes.isEmpty()) {
|
||||||
instance.complete();
|
instance.complete();
|
||||||
workflowInstanceRepository.save(instance);
|
workflowInstanceRepository.save(instance);
|
||||||
}
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 更新节点状态为失败
|
||||||
|
nodeInstance.setStatus(NodeStatusEnum.FAILED);
|
||||||
|
nodeInstance.setError(e.getMessage());
|
||||||
|
nodeInstance.setEndTime(LocalDateTime.now());
|
||||||
|
nodeInstanceRepository.save(nodeInstance);
|
||||||
|
|
||||||
|
// 更新工作流实例状态为失败
|
||||||
|
instance.setStatus(WorkflowInstanceStatusEnum.FAILED);
|
||||||
|
instance.setError(e.getMessage());
|
||||||
|
workflowInstanceRepository.save(instance);
|
||||||
|
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED, e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createAndExecuteNextNode(WorkflowInstance instance, String nextNodeId, NodeConfig nodeConfig) {
|
private void createAndExecuteNextNode(WorkflowInstance instance, String nextNodeId, NodeConfig nodeConfig) {
|
||||||
@ -178,7 +236,7 @@ public class DefaultWorkflowEngine implements WorkflowEngine {
|
|||||||
// 递归执行后续节点
|
// 递归执行后续节点
|
||||||
executeNode(nextNode.getId());
|
executeNode(nextNode.getId());
|
||||||
} catch (JsonProcessingException e) {
|
} catch (JsonProcessingException e) {
|
||||||
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR, e);
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_INVALID, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -36,7 +36,7 @@ public abstract class ConditionalGatewayConfig extends GatewayConfig {
|
|||||||
/**
|
/**
|
||||||
* 目标节点ID
|
* 目标节点ID
|
||||||
*/
|
*/
|
||||||
private String targetNodeId;
|
private String to;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分支描述
|
* 分支描述
|
||||||
|
|||||||
@ -23,7 +23,7 @@ public class ExclusiveGatewayConfig extends ConditionalGatewayConfig {
|
|||||||
// 返回第一个满足条件的分支的targetNodeId
|
// 返回第一个满足条件的分支的targetNodeId
|
||||||
for (ConditionalBranch branch : getBranches()) {
|
for (ConditionalBranch branch : getBranches()) {
|
||||||
if (evaluateCondition(branch.getCondition(), context)) {
|
if (evaluateCondition(branch.getCondition(), context)) {
|
||||||
return Collections.singletonList(branch.getTargetNodeId());
|
return Collections.singletonList(branch.getTo());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Collections.singletonList(getDefaultNodeId());
|
return Collections.singletonList(getDefaultNodeId());
|
||||||
|
|||||||
@ -28,7 +28,7 @@ public class InclusiveGatewayConfig extends ConditionalGatewayConfig {
|
|||||||
// 返回所有满足条件的分支的targetNodeId
|
// 返回所有满足条件的分支的targetNodeId
|
||||||
List<String> nextNodeIds = getBranches().stream()
|
List<String> nextNodeIds = getBranches().stream()
|
||||||
.filter(branch -> evaluateCondition(branch.getCondition(), context))
|
.filter(branch -> evaluateCondition(branch.getCondition(), context))
|
||||||
.map(ConditionalBranch::getTargetNodeId)
|
.map(ConditionalBranch::getTo)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
// 如果没有满足条件的分支,使用默认分支
|
// 如果没有满足条件的分支,使用默认分支
|
||||||
|
|||||||
@ -33,7 +33,7 @@ public class ParallelGatewayConfig extends GatewayConfig {
|
|||||||
/**
|
/**
|
||||||
* 目标节点ID
|
* 目标节点ID
|
||||||
*/
|
*/
|
||||||
private String targetNodeId;
|
private String to;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分支描述
|
* 分支描述
|
||||||
@ -45,7 +45,7 @@ public class ParallelGatewayConfig extends GatewayConfig {
|
|||||||
public List<String> getNextNodeIds(WorkflowContextOperations context) {
|
public List<String> getNextNodeIds(WorkflowContextOperations context) {
|
||||||
// 返回所有分支的targetNodeId
|
// 返回所有分支的targetNodeId
|
||||||
return branches.stream()
|
return branches.stream()
|
||||||
.map(ParallelBranch::getTargetNodeId)
|
.map(ParallelBranch::getTo)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,6 +21,9 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.workflow.engine.executor.node.script.command.ScriptCommand;
|
||||||
|
import com.qqchen.deploy.backend.workflow.engine.executor.node.script.registry.ScriptCommandRegistry;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 脚本节点执行器
|
* 脚本节点执行器
|
||||||
* 支持多种脚本语言(Python, Shell, JavaScript等)
|
* 支持多种脚本语言(Python, Shell, JavaScript等)
|
||||||
@ -35,6 +38,9 @@ public class ScriptNodeExecutor extends AbstractNodeExecutor {
|
|||||||
@Resource
|
@Resource
|
||||||
private WorkflowVariableOperations variableOperations;
|
private WorkflowVariableOperations variableOperations;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ScriptCommandRegistry commandRegistry;
|
||||||
|
|
||||||
private final ExecutorService executorService = Executors.newCachedThreadPool();
|
private final ExecutorService executorService = Executors.newCachedThreadPool();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -51,7 +57,7 @@ public class ScriptNodeExecutor extends AbstractNodeExecutor {
|
|||||||
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");
|
||||||
}
|
}
|
||||||
// 验证脚本语言
|
// 验证脚本语言
|
||||||
if (scriptConfig.getLanguage() == null || scriptConfig.getLanguage().trim().isEmpty()) {
|
if (scriptConfig.getLanguage() == null) {
|
||||||
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR, "Script language must be specified");
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR, "Script language must be specified");
|
||||||
}
|
}
|
||||||
// 验证其他参数
|
// 验证其他参数
|
||||||
@ -108,26 +114,11 @@ public class ScriptNodeExecutor extends AbstractNodeExecutor {
|
|||||||
private void executeScript(ScriptNodeConfig config, NodeInstance nodeInstance, WorkflowContextOperations context) throws Exception {
|
private void executeScript(ScriptNodeConfig config, NodeInstance nodeInstance, WorkflowContextOperations context) throws Exception {
|
||||||
ProcessBuilder processBuilder = new ProcessBuilder();
|
ProcessBuilder processBuilder = new ProcessBuilder();
|
||||||
|
|
||||||
// 根据脚本语言设置命令
|
// 获取命令实现并构建命令
|
||||||
List<String> command = new ArrayList<>();
|
ScriptCommand command = commandRegistry.getCommand(config.getLanguage());
|
||||||
switch (config.getLanguage().toLowerCase()) {
|
List<String> commandList = command.buildCommand(config);
|
||||||
case "python":
|
|
||||||
command.add(config.getInterpreter() != null ? config.getInterpreter() : "python");
|
|
||||||
command.add("-c");
|
|
||||||
command.add(config.getScript());
|
|
||||||
break;
|
|
||||||
case "shell":
|
|
||||||
command.add("sh");
|
|
||||||
command.add("-c");
|
|
||||||
command.add(config.getScript());
|
|
||||||
break;
|
|
||||||
// TODO: 添加其他语言支持
|
|
||||||
default:
|
|
||||||
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR,
|
|
||||||
"Unsupported script language: " + config.getLanguage());
|
|
||||||
}
|
|
||||||
|
|
||||||
processBuilder.command(command);
|
processBuilder.command(commandList);
|
||||||
|
|
||||||
// 设置工作目录
|
// 设置工作目录
|
||||||
if (config.getWorkingDirectory() != null && !config.getWorkingDirectory().trim().isEmpty()) {
|
if (config.getWorkingDirectory() != null && !config.getWorkingDirectory().trim().isEmpty()) {
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package com.qqchen.deploy.backend.workflow.engine.executor.node.config;
|
package com.qqchen.deploy.backend.workflow.engine.executor.node.config;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.workflow.enums.ScriptLanguageEnum;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
@ -20,7 +21,7 @@ public class ScriptNodeConfig extends NodeConfig {
|
|||||||
/**
|
/**
|
||||||
* 脚本语言:python/shell/javascript
|
* 脚本语言:python/shell/javascript
|
||||||
*/
|
*/
|
||||||
private String language;
|
private ScriptLanguageEnum language;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解释器路径(可选)
|
* 解释器路径(可选)
|
||||||
|
|||||||
@ -0,0 +1,16 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.engine.executor.node.script.command;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.workflow.engine.executor.node.config.ScriptNodeConfig;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 脚本命令构建接口
|
||||||
|
*/
|
||||||
|
public interface ScriptCommand {
|
||||||
|
/**
|
||||||
|
* 构建脚本执行命令
|
||||||
|
* @param config 脚本配置
|
||||||
|
* @return 命令行参数列表
|
||||||
|
*/
|
||||||
|
List<String> buildCommand(ScriptNodeConfig config);
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.engine.executor.node.script.command;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.workflow.enums.ScriptLanguageEnum;
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 脚本语言支持注解
|
||||||
|
*/
|
||||||
|
@Target(ElementType.TYPE)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Documented
|
||||||
|
public @interface ScriptLanguageSupport {
|
||||||
|
/**
|
||||||
|
* 支持的脚本语言类型
|
||||||
|
*/
|
||||||
|
ScriptLanguageEnum value();
|
||||||
|
}
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.engine.executor.node.script.command.impl;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
|
||||||
|
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.script.command.ScriptCommand;
|
||||||
|
import com.qqchen.deploy.backend.workflow.engine.executor.node.script.command.ScriptLanguageSupport;
|
||||||
|
import com.qqchen.deploy.backend.workflow.enums.ScriptLanguageEnum;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Python脚本命令构建实现
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@ScriptLanguageSupport(ScriptLanguageEnum.PYTHON)
|
||||||
|
public class PythonScriptCommand implements ScriptCommand {
|
||||||
|
@Override
|
||||||
|
public List<String> buildCommand(ScriptNodeConfig config) {
|
||||||
|
if (config.getInterpreter() == null || config.getInterpreter().trim().isEmpty()) {
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR,
|
||||||
|
"Python interpreter path must be specified");
|
||||||
|
}
|
||||||
|
return Arrays.asList(
|
||||||
|
config.getInterpreter(),
|
||||||
|
"-c",
|
||||||
|
config.getScript()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.engine.executor.node.script.command.impl;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
|
||||||
|
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.script.command.ScriptCommand;
|
||||||
|
import com.qqchen.deploy.backend.workflow.engine.executor.node.script.command.ScriptLanguageSupport;
|
||||||
|
import com.qqchen.deploy.backend.workflow.enums.ScriptLanguageEnum;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shell脚本命令构建实现
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@ScriptLanguageSupport(ScriptLanguageEnum.SHELL)
|
||||||
|
public class ShellScriptCommand implements ScriptCommand {
|
||||||
|
@Override
|
||||||
|
public List<String> buildCommand(ScriptNodeConfig config) {
|
||||||
|
if (config.getInterpreter() == null || config.getInterpreter().trim().isEmpty()) {
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR,
|
||||||
|
"Shell interpreter path must be specified");
|
||||||
|
}
|
||||||
|
return Arrays.asList(
|
||||||
|
config.getInterpreter(),
|
||||||
|
"-c",
|
||||||
|
config.getScript()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,57 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.engine.executor.node.script.registry;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
|
||||||
|
import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException;
|
||||||
|
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.enums.ScriptLanguageEnum;
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 脚本命令注册表
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class ScriptCommandRegistry {
|
||||||
|
private final Map<ScriptLanguageEnum, ScriptCommand> commands = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private List<ScriptCommand> scriptCommands;
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
scriptCommands.forEach(command -> {
|
||||||
|
ScriptLanguageSupport support = command.getClass().getAnnotation(ScriptLanguageSupport.class);
|
||||||
|
if (support != null) {
|
||||||
|
registerCommand(support.value(), command);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册脚本命令实现
|
||||||
|
* @param language 脚本语言
|
||||||
|
* @param command 命令实现
|
||||||
|
*/
|
||||||
|
public void registerCommand(ScriptLanguageEnum language, ScriptCommand command) {
|
||||||
|
commands.put(language, command);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取脚本命令实现
|
||||||
|
* @param language 脚本语言
|
||||||
|
* @return 命令实现
|
||||||
|
*/
|
||||||
|
public ScriptCommand getCommand(ScriptLanguageEnum language) {
|
||||||
|
ScriptCommand command = commands.get(language);
|
||||||
|
if (command == null) {
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR,
|
||||||
|
"No command implementation found for language: " + language.getDescription());
|
||||||
|
}
|
||||||
|
return command;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -15,6 +15,7 @@ import org.springframework.stereotype.Component;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 工作流定义解析器
|
* 工作流定义解析器
|
||||||
@ -36,14 +37,30 @@ public class WorkflowDefinitionParser {
|
|||||||
try {
|
try {
|
||||||
log.debug("Parsing node config: {}", nodeConfig);
|
log.debug("Parsing node config: {}", nodeConfig);
|
||||||
JsonNode rootNode = objectMapper.readTree(nodeConfig);
|
JsonNode rootNode = objectMapper.readTree(nodeConfig);
|
||||||
JsonNode nodesNode = rootNode.get("nodeConfig");
|
JsonNode nodesNode = rootNode.get("nodes");
|
||||||
if (nodesNode == null || !nodesNode.isArray()) {
|
if (nodesNode == null || !nodesNode.isArray()) {
|
||||||
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_INVALID);
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_INVALID);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<NodeConfig> nodes = new ArrayList<>();
|
List<NodeConfig> nodes = new ArrayList<>();
|
||||||
for (JsonNode node : nodesNode) {
|
for (JsonNode node : nodesNode) {
|
||||||
NodeConfig config = objectMapper.treeToValue(node, NodeConfig.class);
|
NodeConfig config = new NodeConfig();
|
||||||
|
// 将前端的id映射到nodeId
|
||||||
|
config.setNodeId(node.get("id").asText());
|
||||||
|
config.setName(node.get("name").asText());
|
||||||
|
config.setType(NodeTypeEnum.valueOf(node.get("type").asText()));
|
||||||
|
|
||||||
|
// 解析节点配置
|
||||||
|
if (node.has("config")) {
|
||||||
|
config.setConfig(objectMapper.convertValue(node.get("config"),
|
||||||
|
new TypeReference<Map<String, Object>>() {}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 可选字段
|
||||||
|
if (node.has("description")) {
|
||||||
|
config.setDescription(node.get("description").asText());
|
||||||
|
}
|
||||||
|
|
||||||
nodes.add(config);
|
nodes.add(config);
|
||||||
log.debug("Parsed node: id={}, type={}", config.getNodeId(), config.getType());
|
log.debug("Parsed node: id={}, type={}", config.getNodeId(), config.getType());
|
||||||
}
|
}
|
||||||
@ -64,18 +81,32 @@ public class WorkflowDefinitionParser {
|
|||||||
try {
|
try {
|
||||||
log.debug("Parsing transition config: {}", transitionConfig);
|
log.debug("Parsing transition config: {}", transitionConfig);
|
||||||
JsonNode rootNode = objectMapper.readTree(transitionConfig);
|
JsonNode rootNode = objectMapper.readTree(transitionConfig);
|
||||||
JsonNode transitionsNode = rootNode.get("transitionConfig");
|
JsonNode transitionsNode = rootNode.get("transitions");
|
||||||
if (transitionsNode == null || !transitionsNode.isArray()) {
|
if (transitionsNode == null || !transitionsNode.isArray()) {
|
||||||
throw new WorkflowEngineException(ResponseCode.WORKFLOW_CONFIG_INVALID);
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_CONFIG_INVALID);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<TransitionConfig> transitions = new ArrayList<>();
|
List<TransitionConfig> transitions = new ArrayList<>();
|
||||||
for (JsonNode node : transitionsNode) {
|
for (JsonNode node : transitionsNode) {
|
||||||
TransitionConfig config = objectMapper.treeToValue(node, TransitionConfig.class);
|
TransitionConfig config = new TransitionConfig();
|
||||||
|
config.setFrom(node.get("from").asText());
|
||||||
|
config.setTo(node.get("to").asText());
|
||||||
|
|
||||||
|
// 可选字段
|
||||||
|
if (node.has("condition")) {
|
||||||
|
config.setCondition(node.get("condition").asText());
|
||||||
|
}
|
||||||
|
if (node.has("description")) {
|
||||||
|
config.setDescription(node.get("description").asText());
|
||||||
|
}
|
||||||
|
if (node.has("priority")) {
|
||||||
|
config.setPriority(node.get("priority").asInt());
|
||||||
|
}
|
||||||
|
|
||||||
transitions.add(config);
|
transitions.add(config);
|
||||||
log.debug("Parsed transition: {} -> {}, priority={}",
|
log.debug("Parsed transition: {} -> {}, priority={}",
|
||||||
config.getSourceNodeId(),
|
config.getFrom(),
|
||||||
config.getTargetNodeId(),
|
config.getTo(),
|
||||||
config.getPriority());
|
config.getPriority());
|
||||||
}
|
}
|
||||||
return transitions;
|
return transitions;
|
||||||
@ -97,6 +128,10 @@ public class WorkflowDefinitionParser {
|
|||||||
boolean hasEnd = false;
|
boolean hasEnd = false;
|
||||||
for (NodeConfig node : nodes) {
|
for (NodeConfig node : nodes) {
|
||||||
if (node.getType() == NodeTypeEnum.START) {
|
if (node.getType() == NodeTypeEnum.START) {
|
||||||
|
if (hasStart) {
|
||||||
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_CONFIG_INVALID,
|
||||||
|
"工作流只能有一个开始节点");
|
||||||
|
}
|
||||||
hasStart = true;
|
hasStart = true;
|
||||||
} else if (node.getType() == NodeTypeEnum.END) {
|
} else if (node.getType() == NodeTypeEnum.END) {
|
||||||
hasEnd = true;
|
hasEnd = true;
|
||||||
@ -104,7 +139,8 @@ public class WorkflowDefinitionParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!hasStart || !hasEnd) {
|
if (!hasStart || !hasEnd) {
|
||||||
throw new WorkflowEngineException(ResponseCode.WORKFLOW_CONFIG_INVALID, "工作流必须包含开始节点和结束节点");
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_CONFIG_INVALID,
|
||||||
|
"工作流必须包含开始节点和结束节点");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 检查流转配置的完整性
|
// 2. 检查流转配置的完整性
|
||||||
@ -113,10 +149,10 @@ public class WorkflowDefinitionParser {
|
|||||||
boolean targetExists = false;
|
boolean targetExists = false;
|
||||||
|
|
||||||
for (NodeConfig node : nodes) {
|
for (NodeConfig node : nodes) {
|
||||||
if (node.getNodeId().equals(transition.getSourceNodeId())) {
|
if (node.getNodeId().equals(transition.getFrom())) {
|
||||||
sourceExists = true;
|
sourceExists = true;
|
||||||
}
|
}
|
||||||
if (node.getNodeId().equals(transition.getTargetNodeId())) {
|
if (node.getNodeId().equals(transition.getTo())) {
|
||||||
targetExists = true;
|
targetExists = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -124,8 +160,8 @@ public class WorkflowDefinitionParser {
|
|||||||
if (!sourceExists || !targetExists) {
|
if (!sourceExists || !targetExists) {
|
||||||
throw new WorkflowEngineException(ResponseCode.WORKFLOW_CONFIG_INVALID,
|
throw new WorkflowEngineException(ResponseCode.WORKFLOW_CONFIG_INVALID,
|
||||||
String.format("流转配置中的节点不存在: %s -> %s",
|
String.format("流转配置中的节点不存在: %s -> %s",
|
||||||
transition.getSourceNodeId(),
|
transition.getFrom(),
|
||||||
transition.getTargetNodeId()));
|
transition.getTo()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,12 +9,12 @@ public class TransitionRule {
|
|||||||
/**
|
/**
|
||||||
* 源节点ID
|
* 源节点ID
|
||||||
*/
|
*/
|
||||||
private String sourceNodeId;
|
private String from;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 目标节点ID
|
* 目标节点ID
|
||||||
*/
|
*/
|
||||||
private String targetNodeId;
|
private String to;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 条件表达式
|
* 条件表达式
|
||||||
|
|||||||
@ -35,7 +35,7 @@ public class TransitionRuleEngine {
|
|||||||
|
|
||||||
// 过滤当前节点的规则并按优先级排序
|
// 过滤当前节点的规则并按优先级排序
|
||||||
List<TransitionRule> nodeRules = rules.stream()
|
List<TransitionRule> nodeRules = rules.stream()
|
||||||
.filter(rule -> rule.getSourceNodeId().equals(currentNode.getNodeId()))
|
.filter(rule -> rule.getFrom().equals(currentNode.getNodeId()))
|
||||||
.sorted(Comparator.comparing(TransitionRule::getPriority))
|
.sorted(Comparator.comparing(TransitionRule::getPriority))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
@ -43,7 +43,7 @@ public class TransitionRuleEngine {
|
|||||||
List<String> nextNodeIds = new ArrayList<>();
|
List<String> nextNodeIds = new ArrayList<>();
|
||||||
for (TransitionRule rule : nodeRules) {
|
for (TransitionRule rule : nodeRules) {
|
||||||
if (evaluateCondition(rule.getCondition(), context)) {
|
if (evaluateCondition(rule.getCondition(), context)) {
|
||||||
nextNodeIds.add(rule.getTargetNodeId());
|
nextNodeIds.add(rule.getTo());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -47,6 +47,13 @@ public class NodeConfig extends Entity<Long> {
|
|||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
private NodeTypeEnum type;
|
private NodeTypeEnum type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所属工作流定义ID
|
||||||
|
*/
|
||||||
|
@NotNull(message = "工作流定义ID不能为空")
|
||||||
|
@Column(name = "workflow_definition_id", nullable = false)
|
||||||
|
private Long workflowDefinitionId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 节点配置,不同类型的节点有不同的配置
|
* 节点配置,不同类型的节点有不同的配置
|
||||||
* TASK节点:
|
* TASK节点:
|
||||||
@ -61,10 +68,10 @@ public class NodeConfig extends Entity<Long> {
|
|||||||
private Map<String, Object> config;
|
private Map<String, Object> config;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 工作流定义ID
|
* 节点描述,用于说明节点的用途
|
||||||
*/
|
*/
|
||||||
@Column(name = "workflow_definition_id", nullable = false)
|
@Column(columnDefinition = "text")
|
||||||
private Long workflowDefinitionId;
|
private String description;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查节点配置是否有效
|
* 检查节点配置是否有效
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package com.qqchen.deploy.backend.workflow.entity;
|
package com.qqchen.deploy.backend.workflow.entity;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import com.qqchen.deploy.backend.framework.domain.Entity;
|
import com.qqchen.deploy.backend.framework.domain.Entity;
|
||||||
import jakarta.persistence.Column;
|
import jakarta.persistence.Column;
|
||||||
import jakarta.persistence.Table;
|
import jakarta.persistence.Table;
|
||||||
@ -19,19 +20,17 @@ import lombok.EqualsAndHashCode;
|
|||||||
public class TransitionConfig extends Entity<Long> {
|
public class TransitionConfig extends Entity<Long> {
|
||||||
/**
|
/**
|
||||||
* 源节点ID
|
* 源节点ID
|
||||||
* 流转的起始节点
|
|
||||||
*/
|
*/
|
||||||
@NotBlank(message = "源节点ID不能为空")
|
@NotBlank(message = "源节点ID不能为空")
|
||||||
@Column(name = "source_node_id", nullable = false)
|
@Column(name = "`from`", nullable = false)
|
||||||
private String sourceNodeId;
|
private String from;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 目标节点ID
|
* 目标节点ID
|
||||||
* 流转的目标节点
|
|
||||||
*/
|
*/
|
||||||
@NotBlank(message = "目标节点ID不能为空")
|
@NotBlank(message = "目标节点ID不能为空")
|
||||||
@Column(name = "target_node_id", nullable = false)
|
@Column(name = "`to`", nullable = false)
|
||||||
private String targetNodeId;
|
private String to;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 流转条件,使用SpEL表达式
|
* 流转条件,使用SpEL表达式
|
||||||
@ -41,8 +40,8 @@ public class TransitionConfig extends Entity<Long> {
|
|||||||
* - "${amount > 1000}"
|
* - "${amount > 1000}"
|
||||||
* - "${result.code == 200 && result.data != null}"
|
* - "${result.code == 200 && result.data != null}"
|
||||||
*/
|
*/
|
||||||
@Column(name = "transition_condition", columnDefinition = "text")
|
@Column(name = "`condition`", columnDefinition = "text")
|
||||||
private String transitionCondition;
|
private String condition;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 优先级,数字越小优先级越高
|
* 优先级,数字越小优先级越高
|
||||||
@ -54,24 +53,31 @@ public class TransitionConfig extends Entity<Long> {
|
|||||||
private Integer priority = 0;
|
private Integer priority = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 工作流定义ID
|
* 所属工作流定义ID
|
||||||
*/
|
*/
|
||||||
|
@NotNull(message = "工作流定义ID不能为空")
|
||||||
@Column(name = "workflow_definition_id", nullable = false)
|
@Column(name = "workflow_definition_id", nullable = false)
|
||||||
private Long workflowDefinitionId;
|
private Long workflowDefinitionId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流转描述,用于说明流转的用途
|
||||||
|
*/
|
||||||
|
@Column(columnDefinition = "text")
|
||||||
|
private String description;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查流转配置是否有效
|
* 检查流转配置是否有效
|
||||||
*
|
*
|
||||||
* @return true if valid, false otherwise
|
* @return true if valid, false otherwise
|
||||||
*/
|
*/
|
||||||
public boolean isValid() {
|
public boolean isValid() {
|
||||||
if (sourceNodeId == null || sourceNodeId.trim().isEmpty()) {
|
if (from == null || from.trim().isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (targetNodeId == null || targetNodeId.trim().isEmpty()) {
|
if (to == null || to.trim().isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (sourceNodeId.equals(targetNodeId)) {
|
if (from.equals(to)) {
|
||||||
return false; // 不允许自循环
|
return false; // 不允许自循环
|
||||||
}
|
}
|
||||||
if (workflowDefinitionId == null) {
|
if (workflowDefinitionId == null) {
|
||||||
@ -86,7 +92,7 @@ public class TransitionConfig extends Entity<Long> {
|
|||||||
* @return true if conditional, false otherwise
|
* @return true if conditional, false otherwise
|
||||||
*/
|
*/
|
||||||
public boolean isConditional() {
|
public boolean isConditional() {
|
||||||
return transitionCondition != null && !transitionCondition.trim().isEmpty();
|
return condition != null && !condition.trim().isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -0,0 +1,31 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.enums;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonValue;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 脚本语言类型枚举
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public enum ScriptLanguageEnum {
|
||||||
|
|
||||||
|
SHELL("shell", "Shell脚本"),
|
||||||
|
PYTHON("python", "Python脚本"),
|
||||||
|
JAVASCRIPT("javascript", "JavaScript脚本"),
|
||||||
|
GROOVY("groovy", "Groovy脚本");
|
||||||
|
|
||||||
|
@JsonValue
|
||||||
|
private final String code;
|
||||||
|
private final String description;
|
||||||
|
|
||||||
|
public static ScriptLanguageEnum fromCode(String code) {
|
||||||
|
for (ScriptLanguageEnum language : values()) {
|
||||||
|
if (language.getCode().equals(code)) {
|
||||||
|
return language;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Unknown script language code: " + code);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.repository;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.workflow.entity.NodeConfig;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface INodeConfigRepository extends JpaRepository<NodeConfig, Long> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据工作流定义ID查找所有节点配置
|
||||||
|
*
|
||||||
|
* @param workflowDefinitionId 工作流定义ID
|
||||||
|
* @return 节点配置列表
|
||||||
|
*/
|
||||||
|
List<NodeConfig> findByWorkflowDefinitionId(Long workflowDefinitionId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据工作流定义ID删除所有节点配置
|
||||||
|
*
|
||||||
|
* @param workflowDefinitionId 工作流定义ID
|
||||||
|
*/
|
||||||
|
void deleteByWorkflowDefinitionId(Long workflowDefinitionId);
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.repository;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.workflow.entity.TransitionConfig;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface ITransitionConfigRepository extends JpaRepository<TransitionConfig, Long> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据工作流定义ID查找所有流转配置
|
||||||
|
*
|
||||||
|
* @param workflowDefinitionId 工作流定义ID
|
||||||
|
* @return 流转配置列表
|
||||||
|
*/
|
||||||
|
List<TransitionConfig> findByWorkflowDefinitionId(Long workflowDefinitionId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据工作流定义ID删除所有流转配置
|
||||||
|
*
|
||||||
|
* @param workflowDefinitionId 工作流定义ID
|
||||||
|
*/
|
||||||
|
void deleteByWorkflowDefinitionId(Long workflowDefinitionId);
|
||||||
|
}
|
||||||
@ -420,12 +420,34 @@ CREATE TABLE wf_node_definition (
|
|||||||
name VARCHAR(100) NOT NULL COMMENT '节点名称',
|
name VARCHAR(100) NOT NULL COMMENT '节点名称',
|
||||||
type TINYINT NOT NULL COMMENT '节点类型(0:开始节点,1:结束节点,2:任务节点,3:网关节点,4:子流程节点,5:Shell脚本节点,6:审批节点,7:Jenkins任务节点,8:Git操作节点)',
|
type TINYINT NOT NULL COMMENT '节点类型(0:开始节点,1:结束节点,2:任务节点,3:网关节点,4:子流程节点,5:Shell脚本节点,6:审批节点,7:Jenkins任务节点,8:Git操作节点)',
|
||||||
config TEXT NULL COMMENT '节点配置(JSON)',
|
config TEXT NULL COMMENT '节点配置(JSON)',
|
||||||
|
description TEXT NULL COMMENT '节点描述',
|
||||||
order_num INT NOT NULL DEFAULT 0 COMMENT '排序号',
|
order_num INT NOT NULL DEFAULT 0 COMMENT '排序号',
|
||||||
|
|
||||||
CONSTRAINT FK_node_definition_workflow FOREIGN KEY (workflow_definition_id) REFERENCES wf_workflow_definition (id),
|
CONSTRAINT FK_node_definition_workflow FOREIGN KEY (workflow_definition_id) REFERENCES wf_workflow_definition (id),
|
||||||
CONSTRAINT UK_node_definition_workflow_node UNIQUE (workflow_definition_id, node_id)
|
CONSTRAINT UK_node_definition_workflow_node UNIQUE (workflow_definition_id, node_id)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='节点定义表';
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='节点定义表';
|
||||||
|
|
||||||
|
-- 流转配置表
|
||||||
|
CREATE TABLE wf_transition_config (
|
||||||
|
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
||||||
|
create_by VARCHAR(255) NULL COMMENT '创建人',
|
||||||
|
create_time DATETIME(6) NULL COMMENT '创建时间',
|
||||||
|
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除(0:未删除,1:已删除)',
|
||||||
|
update_by VARCHAR(255) NULL COMMENT '更新人',
|
||||||
|
update_time DATETIME(6) NULL COMMENT '更新时间',
|
||||||
|
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
||||||
|
|
||||||
|
workflow_definition_id BIGINT NOT NULL COMMENT '工作流定义ID',
|
||||||
|
`from` VARCHAR(100) NOT NULL COMMENT '源节点ID',
|
||||||
|
`to` VARCHAR(100) NOT NULL COMMENT '目标节点ID',
|
||||||
|
`condition` TEXT NULL COMMENT '流转条件',
|
||||||
|
description TEXT NULL COMMENT '流转描述',
|
||||||
|
priority INT NOT NULL DEFAULT 0 COMMENT '优先级',
|
||||||
|
|
||||||
|
CONSTRAINT FK_transition_config_workflow FOREIGN KEY (workflow_definition_id) REFERENCES wf_workflow_definition (id),
|
||||||
|
CONSTRAINT UK_transition_config_workflow_nodes UNIQUE (workflow_definition_id, `from`, `to`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='流转配置表';
|
||||||
|
|
||||||
-- 工作流实例表
|
-- 工作流实例表
|
||||||
CREATE TABLE wf_workflow_instance (
|
CREATE TABLE wf_workflow_instance (
|
||||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
||||||
@ -464,6 +486,7 @@ CREATE TABLE wf_node_instance (
|
|||||||
start_time DATETIME(6) NULL COMMENT '开始时间',
|
start_time DATETIME(6) NULL COMMENT '开始时间',
|
||||||
end_time DATETIME(6) NULL COMMENT '结束时间',
|
end_time DATETIME(6) NULL COMMENT '结束时间',
|
||||||
config TEXT NULL COMMENT '节点配置(JSON)',
|
config TEXT NULL COMMENT '节点配置(JSON)',
|
||||||
|
description TEXT NULL COMMENT '节点描述',
|
||||||
input TEXT NULL COMMENT '输入参数(JSON)',
|
input TEXT NULL COMMENT '输入参数(JSON)',
|
||||||
output TEXT NULL COMMENT '输出结果(JSON)',
|
output TEXT NULL COMMENT '输出结果(JSON)',
|
||||||
error TEXT NULL COMMENT '错误信息',
|
error TEXT NULL COMMENT '错误信息',
|
||||||
|
|||||||
@ -296,66 +296,15 @@ true, NOW(), 'system', NOW(), 'system', 1, false),
|
|||||||
true, NOW(), 'system', NOW(), 'system', 1, false),
|
true, NOW(), 'system', NOW(), 'system', 1, false),
|
||||||
|
|
||||||
-- 任务节点类型
|
-- 任务节点类型
|
||||||
(2003, 'SHELL', 'Shell脚本节点', '执行Shell脚本的任务节点', 'TASK', 'code', '#1890ff',
|
(2003, 'SCRIPT', '脚本执行节点', '执行各种脚本语言的节点', 'TASK', 'code', '#13c2c2',
|
||||||
'[{
|
'[{
|
||||||
"code": "SHELL",
|
"code": "SCRIPT",
|
||||||
"name": "Shell脚本执行器",
|
"name": "脚本执行器",
|
||||||
"description": "执行Shell脚本,支持配置超时时间和工作目录",
|
"description": "支持执行多种脚本语言(Shell、Python、JavaScript等)",
|
||||||
|
"supportedLanguages": ["shell"],
|
||||||
"configSchema": {
|
"configSchema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": ["script"],
|
"required": ["name", "script", "language"],
|
||||||
"properties": {
|
|
||||||
"script": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "脚本内容",
|
|
||||||
"format": "shell",
|
|
||||||
"description": "需要执行的Shell脚本内容"
|
|
||||||
},
|
|
||||||
"timeout": {
|
|
||||||
"type": "number",
|
|
||||||
"title": "超时时间",
|
|
||||||
"description": "脚本执行的最大时间(秒)",
|
|
||||||
"minimum": 1,
|
|
||||||
"maximum": 3600,
|
|
||||||
"default": 300
|
|
||||||
},
|
|
||||||
"workingDirectory": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "工作目录",
|
|
||||||
"description": "脚本执行的工作目录",
|
|
||||||
"default": "/tmp"
|
|
||||||
},
|
|
||||||
"retryTimes": {
|
|
||||||
"type": "number",
|
|
||||||
"title": "重试次数",
|
|
||||||
"minimum": 0,
|
|
||||||
"maximum": 10,
|
|
||||||
"default": 0
|
|
||||||
},
|
|
||||||
"retryInterval": {
|
|
||||||
"type": "number",
|
|
||||||
"title": "重试间隔(秒)",
|
|
||||||
"minimum": 1,
|
|
||||||
"maximum": 3600,
|
|
||||||
"default": 60
|
|
||||||
},
|
|
||||||
"environment": {
|
|
||||||
"type": "object",
|
|
||||||
"title": "环境变量",
|
|
||||||
"description": "脚本执行时的环境变量",
|
|
||||||
"additionalProperties": {"type": "string"}
|
|
||||||
},
|
|
||||||
"successExitCode": {
|
|
||||||
"type": "number",
|
|
||||||
"title": "成功退出码",
|
|
||||||
"description": "脚本执行成功的退出码",
|
|
||||||
"default": 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}]',
|
|
||||||
'{
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@ -368,16 +317,144 @@ true, NOW(), 'system', NOW(), 'system', 1, false),
|
|||||||
"title": "节点描述",
|
"title": "节点描述",
|
||||||
"maxLength": 200
|
"maxLength": 200
|
||||||
},
|
},
|
||||||
"executor": {
|
"script": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"title": "执行器",
|
"title": "脚本内容",
|
||||||
"enum": ["SHELL"],
|
"format": "script",
|
||||||
"enumNames": ["Shell脚本执行器"]
|
"description": "需要执行的脚本内容"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "脚本语言",
|
||||||
|
"enum": ["shell", "python", "javascript", "groovy"],
|
||||||
|
"enumNames": ["Shell脚本 (已支持)", "Python脚本 (开发中)", "JavaScript脚本 (开发中)", "Groovy脚本 (开发中)"],
|
||||||
|
"default": "shell",
|
||||||
|
"description": "脚本语言类型",
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"const": "shell",
|
||||||
|
"title": "Shell脚本"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"const": "python",
|
||||||
|
"title": "Python脚本",
|
||||||
|
"readOnly": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"const": "javascript",
|
||||||
|
"title": "JavaScript脚本",
|
||||||
|
"readOnly": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"const": "groovy",
|
||||||
|
"title": "Groovy脚本",
|
||||||
|
"readOnly": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"interpreter": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "解释器路径",
|
||||||
|
"description": "脚本解释器的路径,例如:/bin/bash, /usr/bin/python3",
|
||||||
|
"default": "/bin/bash"
|
||||||
|
},
|
||||||
|
"workingDirectory": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "工作目录",
|
||||||
|
"description": "脚本执行的工作目录",
|
||||||
|
"default": "/tmp"
|
||||||
|
},
|
||||||
|
"timeout": {
|
||||||
|
"type": "integer",
|
||||||
|
"title": "超时时间",
|
||||||
|
"description": "脚本执行的最大时间(秒)",
|
||||||
|
"minimum": 1,
|
||||||
|
"maximum": 3600,
|
||||||
|
"default": 300
|
||||||
|
},
|
||||||
|
"environment": {
|
||||||
|
"type": "object",
|
||||||
|
"title": "环境变量",
|
||||||
|
"description": "脚本执行时的环境变量",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": ["name", "executor"]
|
"successExitCode": {
|
||||||
|
"type": "integer",
|
||||||
|
"title": "成功退出码",
|
||||||
|
"description": "脚本执行成功时的退出码",
|
||||||
|
"default": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}]',
|
||||||
|
'{
|
||||||
|
"type": "object",
|
||||||
|
"required": ["name", "script", "language"],
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "节点名称",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 50
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "节点描述",
|
||||||
|
"maxLength": 200
|
||||||
|
},
|
||||||
|
"script": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "脚本内容",
|
||||||
|
"format": "script",
|
||||||
|
"description": "需要执行的脚本内容"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "脚本语言",
|
||||||
|
"enum": ["shell", "python", "javascript", "groovy"],
|
||||||
|
"enumNames": ["Shell脚本 (已支持)", "Python脚本 (开发中)", "JavaScript脚本 (开发中)", "Groovy脚本 (开发中)"],
|
||||||
|
"default": "shell",
|
||||||
|
"description": "脚本语言类型"
|
||||||
|
},
|
||||||
|
"interpreter": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "解释器路径",
|
||||||
|
"description": "脚本解释器的路径,例如:/bin/bash, /usr/bin/python3",
|
||||||
|
"default": "/bin/bash"
|
||||||
|
},
|
||||||
|
"workingDirectory": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "工作目录",
|
||||||
|
"description": "脚本执行的工作目录",
|
||||||
|
"default": "/tmp"
|
||||||
|
},
|
||||||
|
"timeout": {
|
||||||
|
"type": "integer",
|
||||||
|
"title": "超时时间",
|
||||||
|
"description": "脚本执行的最大时间(秒)",
|
||||||
|
"minimum": 1,
|
||||||
|
"maximum": 3600,
|
||||||
|
"default": 300
|
||||||
|
},
|
||||||
|
"environment": {
|
||||||
|
"type": "object",
|
||||||
|
"title": "环境变量",
|
||||||
|
"description": "脚本执行时的环境变量",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"successExitCode": {
|
||||||
|
"type": "integer",
|
||||||
|
"title": "成功退出码",
|
||||||
|
"description": "脚本执行成功时的退出码",
|
||||||
|
"default": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
}',
|
}',
|
||||||
'{"name": "Shell脚本", "executor": "SHELL"}',
|
'{"name": "脚本执行", "language": "shell", "script": "echo \"Hello World\""}',
|
||||||
true, NOW(), 'system', NOW(), 'system', 1, false),
|
true, NOW(), 'system', NOW(), 'system', 1, false),
|
||||||
|
|
||||||
-- Git节点类型
|
-- Git节点类型
|
||||||
|
|||||||
@ -127,7 +127,6 @@ workflow.node.not.found=\u5DE5\u4F5C\u6D41\u8282\u70B9\u4E0D\u5B58\u5728
|
|||||||
workflow.node.type.not.supported=\u4E0D\u652F\u6301\u7684\u8282\u70B9\u7C7B\u578B
|
workflow.node.type.not.supported=\u4E0D\u652F\u6301\u7684\u8282\u70B9\u7C7B\u578B
|
||||||
workflow.node.config.invalid=\u8282\u70B9\u914D\u7F6E\u65E0\u6548
|
workflow.node.config.invalid=\u8282\u70B9\u914D\u7F6E\u65E0\u6548
|
||||||
workflow.node.execution.failed=\u8282\u70B9\u6267\u884C\u5931\u8D25
|
workflow.node.execution.failed=\u8282\u70B9\u6267\u884C\u5931\u8D25
|
||||||
workflow.node.timeout=\u8282\u70B9\u6267\u884C\u8D85\u65F6
|
|
||||||
workflow.variable.not.found=\u5DE5\u4F5C\u6D41\u53D8\u91CF\u4E0D\u5B58\u5728
|
workflow.variable.not.found=\u5DE5\u4F5C\u6D41\u53D8\u91CF\u4E0D\u5B58\u5728
|
||||||
workflow.variable.type.invalid=\u5DE5\u4F5C\u6D41\u53D8\u91CF\u7C7B\u578B\u65E0\u6548
|
workflow.variable.type.invalid=\u5DE5\u4F5C\u6D41\u53D8\u91CF\u7C7B\u578B\u65E0\u6548
|
||||||
workflow.permission.denied=\u5DE5\u4F5C\u6D41\u6743\u9650\u4E0D\u8DB3
|
workflow.permission.denied=\u5DE5\u4F5C\u6D41\u6743\u9650\u4E0D\u8DB3
|
||||||
@ -136,7 +135,7 @@ workflow.approval.rejected=\u5BA1\u6279\u88AB\u62D2\u7EDD
|
|||||||
workflow.dependency.not.satisfied=\u4F9D\u8D56\u6761\u4EF6\u4E0D\u6EE1\u8DB3
|
workflow.dependency.not.satisfied=\u4F9D\u8D56\u6761\u4EF6\u4E0D\u6EE1\u8DB3
|
||||||
workflow.circular.dependency=\u5B58\u5728\u5FAA\u73AF\u4F9D\u8D56
|
workflow.circular.dependency=\u5B58\u5728\u5FAA\u73AF\u4F9D\u8D56
|
||||||
workflow.schedule.invalid=\u8C03\u5EA6\u914D\u7F6E\u65E0\u6548
|
workflow.schedule.invalid=\u8C03\u5EA6\u914D\u7F6E\u65E0\u6548
|
||||||
workflow.concurrent.limit.exceeded=\u8D85\u51FA\u5E76\u53D1\u9650\u5236
|
workflow.concurrent.limit.exceeded=\u8FC7\u51FA\u5E76\u53D1\u9650\u5236
|
||||||
|
|
||||||
# Workflow error messages
|
# Workflow error messages
|
||||||
workflow.not.found=\u5DE5\u4F5C\u6D41\u5B9A\u4E49\u4E0D\u5B58\u5728
|
workflow.not.found=\u5DE5\u4F5C\u6D41\u5B9A\u4E49\u4E0D\u5B58\u5728
|
||||||
@ -240,3 +239,5 @@ workflow.node.executor.not.found=\u672A\u627E\u5230\u5DE5\u4F5C\u6D41\u8282\u70B
|
|||||||
|
|
||||||
workflow.variable.serialize.error=\u5DE5\u4F5C\u6D41\u53D8\u91CF\u5E8F\u5217\u5316\u5931\u8D25: {0}
|
workflow.variable.serialize.error=\u5DE5\u4F5C\u6D41\u53D8\u91CF\u5E8F\u5217\u5316\u5931\u8D25: {0}
|
||||||
workflow.variable.deserialize.error=\u5DE5\u4F5C\u6D41\u53D8\u91CF\u53CD\u5E8F\u5217\u5316\u5931\u8D25: {0}
|
workflow.variable.deserialize.error=\u5DE5\u4F5C\u6D41\u53D8\u91CF\u53CD\u5E8F\u5217\u5316\u5931\u8D25: {0}
|
||||||
|
|
||||||
|
workflow.config.error=\u5DE5\u4F5C\u6D41\u914D\u7F6E\u9519\u8BEF: {0}
|
||||||
|
|||||||
@ -44,6 +44,8 @@ workflow.circular.dependency=Circular dependency detected in workflow
|
|||||||
workflow.schedule.invalid=Invalid workflow schedule configuration
|
workflow.schedule.invalid=Invalid workflow schedule configuration
|
||||||
workflow.concurrent.limit.exceeded=Workflow concurrent limit exceeded
|
workflow.concurrent.limit.exceeded=Workflow concurrent limit exceeded
|
||||||
|
|
||||||
|
workflow.config.error=Workflow configuration error: {0}
|
||||||
|
|
||||||
# Workflow Node Type Errors (2200-2299)
|
# Workflow Node Type Errors (2200-2299)
|
||||||
workflow.node.type.not.found=Node type does not exist or has been deleted
|
workflow.node.type.not.found=Node type does not exist or has been deleted
|
||||||
workflow.node.type.disabled=Node type is disabled and cannot be used
|
workflow.node.type.disabled=Node type is disabled and cannot be used
|
||||||
|
|||||||
@ -132,3 +132,4 @@ workflow.dependency.not.satisfied=工作流依赖条件未满足
|
|||||||
workflow.circular.dependency=工作流存在循环依赖
|
workflow.circular.dependency=工作流存在循环依赖
|
||||||
workflow.schedule.invalid=工作流调度配置无效
|
workflow.schedule.invalid=工作流调度配置无效
|
||||||
workflow.concurrent.limit.exceeded=工作流并发限制超出
|
workflow.concurrent.limit.exceeded=工作流并发限制超出
|
||||||
|
workflow.config.error=工作流配置错误:{0}
|
||||||
Loading…
Reference in New Issue
Block a user