增加脚本适配工厂
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_CONFIG_ERROR(2725, "workflow.node.config.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_TYPE_INVALID(2731, "workflow.variable.type.invalid"),
|
||||
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 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
|
||||
*/
|
||||
@ -72,6 +62,11 @@ public class NodeInstanceDTO extends BaseDTO {
|
||||
*/
|
||||
private String config;
|
||||
|
||||
/**
|
||||
* 节点描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 输入参数(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.WorkflowDefinitionStatusEnum;
|
||||
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.ITransitionConfigRepository;
|
||||
import com.qqchen.deploy.backend.workflow.repository.IWorkflowDefinitionRepository;
|
||||
import com.qqchen.deploy.backend.workflow.repository.IWorkflowInstanceRepository;
|
||||
import com.qqchen.deploy.backend.workflow.service.WorkflowVariableOperations;
|
||||
@ -43,6 +45,12 @@ public class DefaultWorkflowEngine implements WorkflowEngine {
|
||||
@Resource
|
||||
private INodeInstanceRepository nodeInstanceRepository;
|
||||
|
||||
@Resource
|
||||
private INodeConfigRepository nodeConfigRepository;
|
||||
|
||||
@Resource
|
||||
private ITransitionConfigRepository transitionConfigRepository;
|
||||
|
||||
@Resource
|
||||
private Map<NodeTypeEnum, NodeExecutor> nodeExecutors;
|
||||
|
||||
@ -82,19 +90,51 @@ public class DefaultWorkflowEngine implements WorkflowEngine {
|
||||
variableOperations.setVariables(instance.getId(), variables);
|
||||
}
|
||||
|
||||
// 5. 创建并执行开始节点
|
||||
NodeInstance startNode = new NodeInstance();
|
||||
startNode.setWorkflowInstance(instance);
|
||||
startNode.setNodeId("start");
|
||||
startNode.setNodeType(NodeTypeEnum.START);
|
||||
startNode.setName("开始节点");
|
||||
startNode.setStatus(NodeStatusEnum.PENDING);
|
||||
startNode.setCreateTime(LocalDateTime.now());
|
||||
nodeInstanceRepository.save(startNode);
|
||||
// 5. 解析并保存节点和流转配置
|
||||
try {
|
||||
// 清除旧的配置(如果存在)
|
||||
nodeConfigRepository.deleteByWorkflowDefinitionId(definition.getId());
|
||||
transitionConfigRepository.deleteByWorkflowDefinitionId(definition.getId());
|
||||
|
||||
// 6. 执行开始节点
|
||||
executeNode(startNode.getId());
|
||||
return instance;
|
||||
// 解析并保存新的配置
|
||||
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();
|
||||
startNode.setWorkflowInstance(instance);
|
||||
startNode.setNodeId(startNodeConfig.getNodeId());
|
||||
startNode.setNodeType(startNodeConfig.getType());
|
||||
startNode.setName(startNodeConfig.getName());
|
||||
startNode.setConfig(objectMapper.writeValueAsString(startNodeConfig.getConfig()));
|
||||
startNode.setStatus(NodeStatusEnum.PENDING);
|
||||
startNode.setCreateTime(LocalDateTime.now());
|
||||
nodeInstanceRepository.save(startNode);
|
||||
|
||||
// 7. 执行开始节点
|
||||
executeNode(startNode.getId());
|
||||
|
||||
return instance;
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new WorkflowEngineException(ResponseCode.WORKFLOW_CONFIG_ERROR, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -120,47 +160,65 @@ public class DefaultWorkflowEngine implements WorkflowEngine {
|
||||
.variableOperations(variableOperations)
|
||||
.build();
|
||||
|
||||
// 执行节点
|
||||
executor.execute(nodeInstance, context);
|
||||
try {
|
||||
// 执行节点
|
||||
executor.execute(nodeInstance, context);
|
||||
|
||||
// 3. 更新节点状态
|
||||
nodeInstance.setStatus(NodeStatusEnum.COMPLETED);
|
||||
nodeInstance.setEndTime(LocalDateTime.now());
|
||||
nodeInstanceRepository.save(nodeInstance);
|
||||
// 更新节点状态
|
||||
nodeInstance.setStatus(NodeStatusEnum.COMPLETED);
|
||||
nodeInstance.setEndTime(LocalDateTime.now());
|
||||
nodeInstanceRepository.save(nodeInstance);
|
||||
|
||||
// 4. 获取并执行后续节点
|
||||
WorkflowDefinition definition = instance.getWorkflowDefinition();
|
||||
List<TransitionConfig> transitions = workflowDefinitionParser.parseTransitionConfig(definition.getTransitionConfig());
|
||||
List<NodeConfig> nodeConfigs = workflowDefinitionParser.parseNodeConfig(definition.getNodeConfig());
|
||||
// 从数据库获取流转配置
|
||||
List<TransitionConfig> transitions = transitionConfigRepository
|
||||
.findByWorkflowDefinitionId(instance.getWorkflowDefinition().getId());
|
||||
|
||||
// 获取当前节点的后续节点
|
||||
List<String> nextNodeIds = transitions.stream()
|
||||
.filter(t -> t.getSourceNodeId().equals(nodeInstance.getNodeId()))
|
||||
.map(TransitionConfig::getTargetNodeId)
|
||||
.toList();
|
||||
// 获取当前节点的后续节点
|
||||
List<String> nextNodeIds = transitions.stream()
|
||||
.filter(t -> t.getFrom().equals(nodeInstance.getNodeId()))
|
||||
.map(TransitionConfig::getTo)
|
||||
.toList();
|
||||
|
||||
// 创建并执行后续节点
|
||||
for (String nextNodeId : nextNodeIds) {
|
||||
NodeConfig nodeConfig = nodeConfigs.stream()
|
||||
.filter(n -> n.getNodeId().equals(nextNodeId))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
// 获取节点配置
|
||||
List<NodeConfig> nodeConfigs = nodeConfigRepository
|
||||
.findByWorkflowDefinitionId(instance.getWorkflowDefinition().getId());
|
||||
|
||||
if (nodeConfig == null) {
|
||||
log.error("Node configuration not found for node: {}", nextNodeId);
|
||||
continue;
|
||||
// 创建并执行后续节点
|
||||
for (String nextNodeId : nextNodeIds) {
|
||||
NodeConfig nodeConfig = nodeConfigs.stream()
|
||||
.filter(n -> n.getNodeId().equals(nextNodeId))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
if (nodeConfig == null) {
|
||||
log.error("Node configuration not found for node: {}", nextNodeId);
|
||||
continue;
|
||||
}
|
||||
|
||||
createAndExecuteNextNode(instance, nextNodeId, nodeConfig);
|
||||
}
|
||||
|
||||
createAndExecuteNextNode(instance, nextNodeId, nodeConfig);
|
||||
}
|
||||
// 检查是否所有节点都已完成
|
||||
List<NodeInstance> uncompletedNodes = nodeInstanceRepository
|
||||
.findByWorkflowInstanceAndStatusNot(instance, NodeStatusEnum.COMPLETED);
|
||||
|
||||
// 5. 检查是否所有节点都已完成
|
||||
List<NodeInstance> uncompletedNodes = nodeInstanceRepository.findByWorkflowInstanceAndStatusNot(
|
||||
instance, NodeStatusEnum.COMPLETED);
|
||||
if (uncompletedNodes.isEmpty()) {
|
||||
instance.complete();
|
||||
workflowInstanceRepository.save(instance);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 更新节点状态为失败
|
||||
nodeInstance.setStatus(NodeStatusEnum.FAILED);
|
||||
nodeInstance.setError(e.getMessage());
|
||||
nodeInstance.setEndTime(LocalDateTime.now());
|
||||
nodeInstanceRepository.save(nodeInstance);
|
||||
|
||||
if (uncompletedNodes.isEmpty()) {
|
||||
instance.complete();
|
||||
// 更新工作流实例状态为失败
|
||||
instance.setStatus(WorkflowInstanceStatusEnum.FAILED);
|
||||
instance.setError(e.getMessage());
|
||||
workflowInstanceRepository.save(instance);
|
||||
|
||||
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED, e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -178,7 +236,7 @@ public class DefaultWorkflowEngine implements WorkflowEngine {
|
||||
// 递归执行后续节点
|
||||
executeNode(nextNode.getId());
|
||||
} 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
|
||||
*/
|
||||
private String targetNodeId;
|
||||
private String to;
|
||||
|
||||
/**
|
||||
* 分支描述
|
||||
|
||||
@ -23,7 +23,7 @@ public class ExclusiveGatewayConfig extends ConditionalGatewayConfig {
|
||||
// 返回第一个满足条件的分支的targetNodeId
|
||||
for (ConditionalBranch branch : getBranches()) {
|
||||
if (evaluateCondition(branch.getCondition(), context)) {
|
||||
return Collections.singletonList(branch.getTargetNodeId());
|
||||
return Collections.singletonList(branch.getTo());
|
||||
}
|
||||
}
|
||||
return Collections.singletonList(getDefaultNodeId());
|
||||
|
||||
@ -28,7 +28,7 @@ public class InclusiveGatewayConfig extends ConditionalGatewayConfig {
|
||||
// 返回所有满足条件的分支的targetNodeId
|
||||
List<String> nextNodeIds = getBranches().stream()
|
||||
.filter(branch -> evaluateCondition(branch.getCondition(), context))
|
||||
.map(ConditionalBranch::getTargetNodeId)
|
||||
.map(ConditionalBranch::getTo)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 如果没有满足条件的分支,使用默认分支
|
||||
|
||||
@ -33,7 +33,7 @@ public class ParallelGatewayConfig extends GatewayConfig {
|
||||
/**
|
||||
* 目标节点ID
|
||||
*/
|
||||
private String targetNodeId;
|
||||
private String to;
|
||||
|
||||
/**
|
||||
* 分支描述
|
||||
@ -45,7 +45,7 @@ public class ParallelGatewayConfig extends GatewayConfig {
|
||||
public List<String> getNextNodeIds(WorkflowContextOperations context) {
|
||||
// 返回所有分支的targetNodeId
|
||||
return branches.stream()
|
||||
.map(ParallelBranch::getTargetNodeId)
|
||||
.map(ParallelBranch::getTo)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,6 +21,9 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
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等)
|
||||
@ -35,6 +38,9 @@ public class ScriptNodeExecutor extends AbstractNodeExecutor {
|
||||
@Resource
|
||||
private WorkflowVariableOperations variableOperations;
|
||||
|
||||
@Resource
|
||||
private ScriptCommandRegistry commandRegistry;
|
||||
|
||||
private final ExecutorService executorService = Executors.newCachedThreadPool();
|
||||
|
||||
@Override
|
||||
@ -51,7 +57,7 @@ public class ScriptNodeExecutor extends AbstractNodeExecutor {
|
||||
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");
|
||||
}
|
||||
// 验证其他参数
|
||||
@ -108,26 +114,11 @@ public class ScriptNodeExecutor extends AbstractNodeExecutor {
|
||||
private void executeScript(ScriptNodeConfig config, NodeInstance nodeInstance, WorkflowContextOperations context) throws Exception {
|
||||
ProcessBuilder processBuilder = new ProcessBuilder();
|
||||
|
||||
// 根据脚本语言设置命令
|
||||
List<String> command = new ArrayList<>();
|
||||
switch (config.getLanguage().toLowerCase()) {
|
||||
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());
|
||||
}
|
||||
// 获取命令实现并构建命令
|
||||
ScriptCommand command = commandRegistry.getCommand(config.getLanguage());
|
||||
List<String> commandList = command.buildCommand(config);
|
||||
|
||||
processBuilder.command(command);
|
||||
processBuilder.command(commandList);
|
||||
|
||||
// 设置工作目录
|
||||
if (config.getWorkingDirectory() != null && !config.getWorkingDirectory().trim().isEmpty()) {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package com.qqchen.deploy.backend.workflow.engine.executor.node.config;
|
||||
|
||||
import com.qqchen.deploy.backend.workflow.enums.ScriptLanguageEnum;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@ -20,7 +21,7 @@ public class ScriptNodeConfig extends NodeConfig {
|
||||
/**
|
||||
* 脚本语言: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.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 工作流定义解析器
|
||||
@ -36,14 +37,30 @@ public class WorkflowDefinitionParser {
|
||||
try {
|
||||
log.debug("Parsing node config: {}", nodeConfig);
|
||||
JsonNode rootNode = objectMapper.readTree(nodeConfig);
|
||||
JsonNode nodesNode = rootNode.get("nodeConfig");
|
||||
JsonNode nodesNode = rootNode.get("nodes");
|
||||
if (nodesNode == null || !nodesNode.isArray()) {
|
||||
throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_INVALID);
|
||||
}
|
||||
|
||||
List<NodeConfig> nodes = new ArrayList<>();
|
||||
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);
|
||||
log.debug("Parsed node: id={}, type={}", config.getNodeId(), config.getType());
|
||||
}
|
||||
@ -64,18 +81,32 @@ public class WorkflowDefinitionParser {
|
||||
try {
|
||||
log.debug("Parsing transition config: {}", transitionConfig);
|
||||
JsonNode rootNode = objectMapper.readTree(transitionConfig);
|
||||
JsonNode transitionsNode = rootNode.get("transitionConfig");
|
||||
JsonNode transitionsNode = rootNode.get("transitions");
|
||||
if (transitionsNode == null || !transitionsNode.isArray()) {
|
||||
throw new WorkflowEngineException(ResponseCode.WORKFLOW_CONFIG_INVALID);
|
||||
}
|
||||
|
||||
List<TransitionConfig> transitions = new ArrayList<>();
|
||||
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);
|
||||
log.debug("Parsed transition: {} -> {}, priority={}",
|
||||
config.getSourceNodeId(),
|
||||
config.getTargetNodeId(),
|
||||
config.getFrom(),
|
||||
config.getTo(),
|
||||
config.getPriority());
|
||||
}
|
||||
return transitions;
|
||||
@ -97,6 +128,10 @@ public class WorkflowDefinitionParser {
|
||||
boolean hasEnd = false;
|
||||
for (NodeConfig node : nodes) {
|
||||
if (node.getType() == NodeTypeEnum.START) {
|
||||
if (hasStart) {
|
||||
throw new WorkflowEngineException(ResponseCode.WORKFLOW_CONFIG_INVALID,
|
||||
"工作流只能有一个开始节点");
|
||||
}
|
||||
hasStart = true;
|
||||
} else if (node.getType() == NodeTypeEnum.END) {
|
||||
hasEnd = true;
|
||||
@ -104,7 +139,8 @@ public class WorkflowDefinitionParser {
|
||||
}
|
||||
|
||||
if (!hasStart || !hasEnd) {
|
||||
throw new WorkflowEngineException(ResponseCode.WORKFLOW_CONFIG_INVALID, "工作流必须包含开始节点和结束节点");
|
||||
throw new WorkflowEngineException(ResponseCode.WORKFLOW_CONFIG_INVALID,
|
||||
"工作流必须包含开始节点和结束节点");
|
||||
}
|
||||
|
||||
// 2. 检查流转配置的完整性
|
||||
@ -113,10 +149,10 @@ public class WorkflowDefinitionParser {
|
||||
boolean targetExists = false;
|
||||
|
||||
for (NodeConfig node : nodes) {
|
||||
if (node.getNodeId().equals(transition.getSourceNodeId())) {
|
||||
if (node.getNodeId().equals(transition.getFrom())) {
|
||||
sourceExists = true;
|
||||
}
|
||||
if (node.getNodeId().equals(transition.getTargetNodeId())) {
|
||||
if (node.getNodeId().equals(transition.getTo())) {
|
||||
targetExists = true;
|
||||
}
|
||||
}
|
||||
@ -124,8 +160,8 @@ public class WorkflowDefinitionParser {
|
||||
if (!sourceExists || !targetExists) {
|
||||
throw new WorkflowEngineException(ResponseCode.WORKFLOW_CONFIG_INVALID,
|
||||
String.format("流转配置中的节点不存在: %s -> %s",
|
||||
transition.getSourceNodeId(),
|
||||
transition.getTargetNodeId()));
|
||||
transition.getFrom(),
|
||||
transition.getTo()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,12 +9,12 @@ public class TransitionRule {
|
||||
/**
|
||||
* 源节点ID
|
||||
*/
|
||||
private String sourceNodeId;
|
||||
private String from;
|
||||
|
||||
/**
|
||||
* 目标节点ID
|
||||
*/
|
||||
private String targetNodeId;
|
||||
private String to;
|
||||
|
||||
/**
|
||||
* 条件表达式
|
||||
|
||||
@ -35,7 +35,7 @@ public class TransitionRuleEngine {
|
||||
|
||||
// 过滤当前节点的规则并按优先级排序
|
||||
List<TransitionRule> nodeRules = rules.stream()
|
||||
.filter(rule -> rule.getSourceNodeId().equals(currentNode.getNodeId()))
|
||||
.filter(rule -> rule.getFrom().equals(currentNode.getNodeId()))
|
||||
.sorted(Comparator.comparing(TransitionRule::getPriority))
|
||||
.toList();
|
||||
|
||||
@ -43,7 +43,7 @@ public class TransitionRuleEngine {
|
||||
List<String> nextNodeIds = new ArrayList<>();
|
||||
for (TransitionRule rule : nodeRules) {
|
||||
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)
|
||||
private NodeTypeEnum type;
|
||||
|
||||
/**
|
||||
* 所属工作流定义ID
|
||||
*/
|
||||
@NotNull(message = "工作流定义ID不能为空")
|
||||
@Column(name = "workflow_definition_id", nullable = false)
|
||||
private Long workflowDefinitionId;
|
||||
|
||||
/**
|
||||
* 节点配置,不同类型的节点有不同的配置
|
||||
* TASK节点:
|
||||
@ -61,10 +68,10 @@ public class NodeConfig extends Entity<Long> {
|
||||
private Map<String, Object> config;
|
||||
|
||||
/**
|
||||
* 工作流定义ID
|
||||
* 节点描述,用于说明节点的用途
|
||||
*/
|
||||
@Column(name = "workflow_definition_id", nullable = false)
|
||||
private Long workflowDefinitionId;
|
||||
@Column(columnDefinition = "text")
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 检查节点配置是否有效
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package com.qqchen.deploy.backend.workflow.entity;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.qqchen.deploy.backend.framework.domain.Entity;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Table;
|
||||
@ -19,19 +20,17 @@ import lombok.EqualsAndHashCode;
|
||||
public class TransitionConfig extends Entity<Long> {
|
||||
/**
|
||||
* 源节点ID
|
||||
* 流转的起始节点
|
||||
*/
|
||||
@NotBlank(message = "源节点ID不能为空")
|
||||
@Column(name = "source_node_id", nullable = false)
|
||||
private String sourceNodeId;
|
||||
@Column(name = "`from`", nullable = false)
|
||||
private String from;
|
||||
|
||||
/**
|
||||
* 目标节点ID
|
||||
* 流转的目标节点
|
||||
*/
|
||||
@NotBlank(message = "目标节点ID不能为空")
|
||||
@Column(name = "target_node_id", nullable = false)
|
||||
private String targetNodeId;
|
||||
@Column(name = "`to`", nullable = false)
|
||||
private String to;
|
||||
|
||||
/**
|
||||
* 流转条件,使用SpEL表达式
|
||||
@ -41,8 +40,8 @@ public class TransitionConfig extends Entity<Long> {
|
||||
* - "${amount > 1000}"
|
||||
* - "${result.code == 200 && result.data != null}"
|
||||
*/
|
||||
@Column(name = "transition_condition", columnDefinition = "text")
|
||||
private String transitionCondition;
|
||||
@Column(name = "`condition`", columnDefinition = "text")
|
||||
private String condition;
|
||||
|
||||
/**
|
||||
* 优先级,数字越小优先级越高
|
||||
@ -54,24 +53,31 @@ public class TransitionConfig extends Entity<Long> {
|
||||
private Integer priority = 0;
|
||||
|
||||
/**
|
||||
* 工作流定义ID
|
||||
* 所属工作流定义ID
|
||||
*/
|
||||
@NotNull(message = "工作流定义ID不能为空")
|
||||
@Column(name = "workflow_definition_id", nullable = false)
|
||||
private Long workflowDefinitionId;
|
||||
|
||||
/**
|
||||
* 流转描述,用于说明流转的用途
|
||||
*/
|
||||
@Column(columnDefinition = "text")
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 检查流转配置是否有效
|
||||
*
|
||||
* @return true if valid, false otherwise
|
||||
*/
|
||||
public boolean isValid() {
|
||||
if (sourceNodeId == null || sourceNodeId.trim().isEmpty()) {
|
||||
if (from == null || from.trim().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
if (targetNodeId == null || targetNodeId.trim().isEmpty()) {
|
||||
if (to == null || to.trim().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
if (sourceNodeId.equals(targetNodeId)) {
|
||||
if (from.equals(to)) {
|
||||
return false; // 不允许自循环
|
||||
}
|
||||
if (workflowDefinitionId == null) {
|
||||
@ -86,7 +92,7 @@ public class TransitionConfig extends Entity<Long> {
|
||||
* @return true if conditional, false otherwise
|
||||
*/
|
||||
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 '节点名称',
|
||||
type TINYINT NOT NULL COMMENT '节点类型(0:开始节点,1:结束节点,2:任务节点,3:网关节点,4:子流程节点,5:Shell脚本节点,6:审批节点,7:Jenkins任务节点,8:Git操作节点)',
|
||||
config TEXT NULL COMMENT '节点配置(JSON)',
|
||||
description TEXT NULL 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 UK_node_definition_workflow_node UNIQUE (workflow_definition_id, node_id)
|
||||
) 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 (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
||||
@ -464,6 +486,7 @@ CREATE TABLE wf_node_instance (
|
||||
start_time DATETIME(6) NULL COMMENT '开始时间',
|
||||
end_time DATETIME(6) NULL COMMENT '结束时间',
|
||||
config TEXT NULL COMMENT '节点配置(JSON)',
|
||||
description TEXT NULL COMMENT '节点描述',
|
||||
input TEXT NULL COMMENT '输入参数(JSON)',
|
||||
output TEXT NULL COMMENT '输出结果(JSON)',
|
||||
error TEXT NULL COMMENT '错误信息',
|
||||
|
||||
@ -296,28 +296,67 @@ 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",
|
||||
"name": "Shell脚本执行器",
|
||||
"description": "执行Shell脚本,支持配置超时时间和工作目录",
|
||||
"code": "SCRIPT",
|
||||
"name": "脚本执行器",
|
||||
"description": "支持执行多种脚本语言(Shell、Python、JavaScript等)",
|
||||
"supportedLanguages": ["shell"],
|
||||
"configSchema": {
|
||||
"type": "object",
|
||||
"required": ["script"],
|
||||
"required": ["name", "script", "language"],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"title": "节点名称",
|
||||
"minLength": 1,
|
||||
"maxLength": 50
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"title": "节点描述",
|
||||
"maxLength": 200
|
||||
},
|
||||
"script": {
|
||||
"type": "string",
|
||||
"title": "脚本内容",
|
||||
"format": "shell",
|
||||
"description": "需要执行的Shell脚本内容"
|
||||
"format": "script",
|
||||
"description": "需要执行的脚本内容"
|
||||
},
|
||||
"timeout": {
|
||||
"type": "number",
|
||||
"title": "超时时间",
|
||||
"description": "脚本执行的最大时间(秒)",
|
||||
"minimum": 1,
|
||||
"maximum": 3600,
|
||||
"default": 300
|
||||
"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",
|
||||
@ -325,30 +364,26 @@ true, NOW(), 'system', NOW(), 'system', 1, false),
|
||||
"description": "脚本执行的工作目录",
|
||||
"default": "/tmp"
|
||||
},
|
||||
"retryTimes": {
|
||||
"type": "number",
|
||||
"title": "重试次数",
|
||||
"minimum": 0,
|
||||
"maximum": 10,
|
||||
"default": 0
|
||||
},
|
||||
"retryInterval": {
|
||||
"type": "number",
|
||||
"title": "重试间隔(秒)",
|
||||
"timeout": {
|
||||
"type": "integer",
|
||||
"title": "超时时间",
|
||||
"description": "脚本执行的最大时间(秒)",
|
||||
"minimum": 1,
|
||||
"maximum": 3600,
|
||||
"default": 60
|
||||
"default": 300
|
||||
},
|
||||
"environment": {
|
||||
"type": "object",
|
||||
"title": "环境变量",
|
||||
"description": "脚本执行时的环境变量",
|
||||
"additionalProperties": {"type": "string"}
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"successExitCode": {
|
||||
"type": "number",
|
||||
"type": "integer",
|
||||
"title": "成功退出码",
|
||||
"description": "脚本执行成功的退出码",
|
||||
"description": "脚本执行成功时的退出码",
|
||||
"default": 0
|
||||
}
|
||||
}
|
||||
@ -356,6 +391,7 @@ true, NOW(), 'system', NOW(), 'system', 1, false),
|
||||
}]',
|
||||
'{
|
||||
"type": "object",
|
||||
"required": ["name", "script", "language"],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
@ -368,16 +404,57 @@ true, NOW(), 'system', NOW(), 'system', 1, false),
|
||||
"title": "节点描述",
|
||||
"maxLength": 200
|
||||
},
|
||||
"executor": {
|
||||
"script": {
|
||||
"type": "string",
|
||||
"title": "执行器",
|
||||
"enum": ["SHELL"],
|
||||
"enumNames": ["Shell脚本执行器"]
|
||||
"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
|
||||
}
|
||||
},
|
||||
"required": ["name", "executor"]
|
||||
}
|
||||
}',
|
||||
'{"name": "Shell脚本", "executor": "SHELL"}',
|
||||
'{"name": "脚本执行", "language": "shell", "script": "echo \"Hello World\""}',
|
||||
true, NOW(), 'system', NOW(), 'system', 1, false),
|
||||
|
||||
-- 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.config.invalid=\u8282\u70B9\u914D\u7F6E\u65E0\u6548
|
||||
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.type.invalid=\u5DE5\u4F5C\u6D41\u53D8\u91CF\u7C7B\u578B\u65E0\u6548
|
||||
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.circular.dependency=\u5B58\u5728\u5FAA\u73AF\u4F9D\u8D56
|
||||
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.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.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.concurrent.limit.exceeded=Workflow concurrent limit exceeded
|
||||
|
||||
workflow.config.error=Workflow configuration error: {0}
|
||||
|
||||
# Workflow Node Type Errors (2200-2299)
|
||||
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
|
||||
|
||||
@ -132,3 +132,4 @@ workflow.dependency.not.satisfied=工作流依赖条件未满足
|
||||
workflow.circular.dependency=工作流存在循环依赖
|
||||
workflow.schedule.invalid=工作流调度配置无效
|
||||
workflow.concurrent.limit.exceeded=工作流并发限制超出
|
||||
workflow.config.error=工作流配置错误:{0}
|
||||
Loading…
Reference in New Issue
Block a user