From 216839dd5978b086bbf4541060fa7da16127a746 Mon Sep 17 00:00:00 2001 From: dengqichen Date: Mon, 9 Dec 2024 15:05:01 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=8E=89=E6=89=80=E6=9C=89?= =?UTF-8?q?=E6=B2=A1=E7=94=A8=E7=9A=84=E5=B7=A5=E4=BD=9C=E6=B5=81=E7=9B=B8?= =?UTF-8?q?=E5=85=B3=E7=9A=84=E7=B1=BB=EF=BC=8C=E9=87=8D=E6=96=B0=E5=BC=80?= =?UTF-8?q?=E5=8F=91=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/NodeInstanceApiController.java | 33 +- .../workflow/api/NodeTypeApiController.java | 15 - .../api/WorkflowInstanceApiController.java | 53 +-- .../workflow/converter/JsonConverter.java | 6 +- .../converter/impl/DefaultJsonConverter.java | 10 +- .../backend/workflow/dto/NodeTypeDTO.java | 4 +- .../dto/request/WorkflowStartRequest.java | 19 + .../engine/DefaultWorkflowEngine.java | 347 ------------------ .../workflow/engine/WorkflowContext.java | 116 ------ .../workflow/engine/WorkflowEngine.java | 53 --- .../engine/config/WorkflowEngineConfig.java | 22 +- .../context/DefaultWorkflowContext.java | 97 ----- .../context/WorkflowContextOperations.java | 51 --- ...ition.java => NodeExecutorDefinition.java} | 20 +- .../engine/executor/AbstractNodeExecutor.java | 164 +++++++++ .../engine/executor/EndNodeExecutor.java | 38 ++ .../engine/executor/INodeExecutor.java | 27 ++ .../engine/executor/ScriptNodeExecutor.java | 36 ++ .../engine/executor/StartNodeExecutor.java | 37 ++ .../config/ApprovalNodeExecutorConfig.java | 2 +- .../config/ConditionNodeExecutorConfig.java | 2 +- .../config/GitNodeExecutorConfig.java | 2 +- .../config/HttpNodeExecutorConfig.java | 2 +- .../config/JenkinsNodeExecutorConfig.java | 2 +- .../config/NacosNodeExecutorConfig.java | 2 +- .../executor/config/NodeExecutorConfig.java | 30 ++ .../config/NotifyNodeExecutorConfig.java | 2 +- .../config/ParallelNodeExecutorConfig.java | 2 +- .../config/ScriptNodeExecutorConfig.java | 2 +- .../gateway/BranchConvergeConfig.java | 35 -- .../gateway/ConditionalGatewayConfig.java | 46 --- .../gateway/ExclusiveGatewayConfig.java | 41 --- .../executor/gateway/GatewayConfig.java | 41 --- .../executor/gateway/GatewayNodeExecutor.java | 59 --- .../gateway/InclusiveGatewayConfig.java | 51 --- .../gateway/ParallelGatewayConfig.java | 51 --- .../executor/node/AbstractNodeExecutor.java | 133 ------- .../engine/executor/node/EndNodeExecutor.java | 54 --- .../engine/executor/node/NodeExecutor.java | 37 -- .../executor/node/ScriptNodeExecutor.java | 196 ---------- .../executor/node/ShellNodeExecutor.java | 185 ---------- .../executor/node/StartNodeExecutor.java | 57 --- .../executor/node/TaskNodeExecutor.java | 115 ------ .../node/config/NodeExecutorConfig.java | 48 --- .../node/config/ShellNodeExecutorConfig.java | 55 --- .../executor/task/HttpTaskExecutor.java | 61 --- .../executor/task/JavaTaskExecutor.java | 50 --- .../executor/task/ShellTaskExecutor.java | 66 ---- .../engine/executor/task/TaskConfig.java | 45 --- .../engine/executor/task/TaskExecutor.java | 22 -- .../model/NodeConfig.java} | 19 +- .../engine/model/TransitionConfig.java | 35 ++ .../workflow/engine/model/WorkflowGraph.java | 42 +++ .../parser/WorkflowDefinitionParser.java | 180 +++------ .../script/command/ScriptCommand.java | 5 +- .../script/command/ScriptLanguageSupport.java | 2 +- .../command/impl/PythonScriptCommand.java | 8 +- .../command/impl/ShellScriptCommand.java | 8 +- .../registry/ScriptCommandRegistry.java | 6 +- .../engine/transition/TransitionExecutor.java | 33 -- .../engine/transition/TransitionRule.java | 28 -- .../transition/TransitionRuleEngine.java | 79 ---- .../backend/workflow/entity/NodeConfig.java | 126 ------- .../backend/workflow/entity/NodeInstance.java | 2 +- .../workflow/entity/TransitionConfig.java | 106 ------ .../monitor/WorkflowContextMonitor.java | 149 -------- .../repository/INodeConfigRepository.java | 26 -- .../ITransitionConfigRepository.java | 26 -- .../workflow/service/INodeTypeService.java | 19 - .../service/IWorkflowEngineService.java | 16 + .../service/WorkflowVariableOperations.java | 52 --- .../ConcurrentWorkflowVariableOperations.java | 70 ---- .../service/impl/NodeTypeServiceImpl.java | 26 -- .../impl/WorkflowEngineServiceImpl.java | 84 +++++ .../impl/WorkflowInstanceServiceImpl.java | 8 - backend/src/main/resources/application.yml | 2 +- .../db/migration/V1.0.0__init_schema.sql | 20 - .../api/ExternalSystemApiControllerTest.java | 69 ---- .../backend/api/TenantApiControllerTest.java | 87 ----- .../impl/ExternalSystemServiceImplTest.java | 226 ------------ .../service/impl/MenuServiceImplTest.java | 236 ------------ .../service/impl/TenantServiceImplTest.java | 102 ----- .../engine/DefaultWorkflowEngineTest.java | 305 --------------- .../workflow/engine/WorkflowShellTest.java | 36 -- .../executor/StartNodeExecutorTest.java | 65 ---- .../engine/executor/TaskNodeExecutorTest.java | 175 --------- ...currentWorkflowVariableOperationsTest.java | 117 ------ .../service/WorkflowInstanceServiceTest.java | 188 ---------- 88 files changed, 632 insertions(+), 4795 deletions(-) create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/request/WorkflowStartRequest.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/DefaultWorkflowEngine.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/WorkflowContext.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/WorkflowEngine.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/context/DefaultWorkflowContext.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/context/WorkflowContextOperations.java rename backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/definition/{TaskExecutorDefinition.java => NodeExecutorDefinition.java} (75%) create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/AbstractNodeExecutor.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/EndNodeExecutor.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/INodeExecutor.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/ScriptNodeExecutor.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/StartNodeExecutor.java rename backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/{node => }/config/ApprovalNodeExecutorConfig.java (91%) rename backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/{node => }/config/ConditionNodeExecutorConfig.java (91%) rename backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/{node => }/config/GitNodeExecutorConfig.java (91%) rename backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/{node => }/config/HttpNodeExecutorConfig.java (92%) rename backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/{node => }/config/JenkinsNodeExecutorConfig.java (91%) rename backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/{node => }/config/NacosNodeExecutorConfig.java (92%) create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/config/NodeExecutorConfig.java rename backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/{node => }/config/NotifyNodeExecutorConfig.java (92%) rename backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/{node => }/config/ParallelNodeExecutorConfig.java (92%) rename backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/{node => }/config/ScriptNodeExecutorConfig.java (94%) delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/gateway/BranchConvergeConfig.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/gateway/ConditionalGatewayConfig.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/gateway/ExclusiveGatewayConfig.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/gateway/GatewayConfig.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/gateway/GatewayNodeExecutor.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/gateway/InclusiveGatewayConfig.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/gateway/ParallelGatewayConfig.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/AbstractNodeExecutor.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/EndNodeExecutor.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/NodeExecutor.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/ScriptNodeExecutor.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/ShellNodeExecutor.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/StartNodeExecutor.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/TaskNodeExecutor.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/config/NodeExecutorConfig.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/config/ShellNodeExecutorConfig.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/task/HttpTaskExecutor.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/task/JavaTaskExecutor.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/task/ShellTaskExecutor.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/task/TaskConfig.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/task/TaskExecutor.java rename backend/src/main/java/com/qqchen/deploy/backend/workflow/{dto/NodeConfigDTO.java => engine/model/NodeConfig.java} (57%) create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/model/TransitionConfig.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/model/WorkflowGraph.java rename backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/{executor/node => }/script/command/ScriptCommand.java (61%) rename backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/{executor/node => }/script/command/ScriptLanguageSupport.java (80%) rename backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/{executor/node => }/script/command/impl/PythonScriptCommand.java (71%) rename backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/{executor/node => }/script/command/impl/ShellScriptCommand.java (71%) rename backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/{executor/node => }/script/registry/ScriptCommandRegistry.java (86%) delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/transition/TransitionExecutor.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/transition/TransitionRule.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/transition/TransitionRuleEngine.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/NodeConfig.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/TransitionConfig.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/monitor/WorkflowContextMonitor.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/INodeConfigRepository.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/ITransitionConfigRepository.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowEngineService.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/service/WorkflowVariableOperations.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/ConcurrentWorkflowVariableOperations.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowEngineServiceImpl.java delete mode 100644 backend/src/test/java/com/qqchen/deploy/backend/api/ExternalSystemApiControllerTest.java delete mode 100644 backend/src/test/java/com/qqchen/deploy/backend/api/TenantApiControllerTest.java delete mode 100644 backend/src/test/java/com/qqchen/deploy/backend/service/impl/ExternalSystemServiceImplTest.java delete mode 100644 backend/src/test/java/com/qqchen/deploy/backend/service/impl/MenuServiceImplTest.java delete mode 100644 backend/src/test/java/com/qqchen/deploy/backend/service/impl/TenantServiceImplTest.java delete mode 100644 backend/src/test/java/com/qqchen/deploy/backend/workflow/engine/DefaultWorkflowEngineTest.java delete mode 100644 backend/src/test/java/com/qqchen/deploy/backend/workflow/engine/WorkflowShellTest.java delete mode 100644 backend/src/test/java/com/qqchen/deploy/backend/workflow/engine/executor/StartNodeExecutorTest.java delete mode 100644 backend/src/test/java/com/qqchen/deploy/backend/workflow/engine/executor/TaskNodeExecutorTest.java delete mode 100644 backend/src/test/java/com/qqchen/deploy/backend/workflow/service/ConcurrentWorkflowVariableOperationsTest.java delete mode 100644 backend/src/test/java/com/qqchen/deploy/backend/workflow/service/WorkflowInstanceServiceTest.java diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/NodeInstanceApiController.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/NodeInstanceApiController.java index 4baa88bb..973a88b3 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/NodeInstanceApiController.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/NodeInstanceApiController.java @@ -1,21 +1,15 @@ package com.qqchen.deploy.backend.workflow.api; -import com.qqchen.deploy.backend.framework.api.Response; import com.qqchen.deploy.backend.framework.controller.BaseController; import com.qqchen.deploy.backend.workflow.dto.NodeInstanceDTO; import com.qqchen.deploy.backend.workflow.entity.NodeInstance; -import com.qqchen.deploy.backend.workflow.engine.WorkflowEngine; import com.qqchen.deploy.backend.workflow.query.NodeInstanceQuery; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.annotation.Resource; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; import java.util.List; -import java.util.Map; @Slf4j @RestController @@ -23,30 +17,9 @@ import java.util.Map; @Tag(name = "节点实例管理", description = "节点实例管理相关接口") public class NodeInstanceApiController extends BaseController { - @Resource - private WorkflowEngine workflowEngine; - - @Operation(summary = "完成节点") - @PostMapping("/{id}/complete") - public Response completeNode( - @Parameter(description = "节点实例ID", required = true) @PathVariable Long id, - @Parameter(description = "输出变量") @RequestBody(required = false) Map variables - ) { - workflowEngine.completeNode(id, variables); - return Response.success(); - } - - @Operation(summary = "重试节点") - @PostMapping("/{id}/retry") - public Response retryNode( - @Parameter(description = "节点实例ID", required = true) @PathVariable Long id - ) { - workflowEngine.executeNode(id); - return Response.success(); - } - @Override protected void exportData(HttpServletResponse response, List data) { - // TODO: 实现导出功能 + } -} \ No newline at end of file +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/NodeTypeApiController.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/NodeTypeApiController.java index f23c5cc2..9e37a3e4 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/NodeTypeApiController.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/NodeTypeApiController.java @@ -1,20 +1,14 @@ package com.qqchen.deploy.backend.workflow.api; -import com.qqchen.deploy.backend.framework.api.Response; import com.qqchen.deploy.backend.framework.controller.BaseController; import com.qqchen.deploy.backend.workflow.dto.NodeTypeDTO; import com.qqchen.deploy.backend.workflow.dto.query.NodeTypeQuery; -import com.qqchen.deploy.backend.workflow.engine.definition.TaskExecutorDefinition; import com.qqchen.deploy.backend.workflow.entity.NodeType; import com.qqchen.deploy.backend.workflow.service.INodeTypeService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -32,15 +26,6 @@ public class NodeTypeApiController extends BaseController> getExecutors( - @Parameter(description = "节点类型", required = true) @PathVariable String type - ) { - log.debug("获取节点类型[{}]支持的执行器列表", type); - return Response.success(nodeTypeService.getExecutors(type)); - } - @Override protected void exportData(HttpServletResponse response, List data) { diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/WorkflowInstanceApiController.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/WorkflowInstanceApiController.java index 0468ac25..518e484d 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/WorkflowInstanceApiController.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/WorkflowInstanceApiController.java @@ -3,8 +3,9 @@ package com.qqchen.deploy.backend.workflow.api; import com.qqchen.deploy.backend.framework.api.Response; import com.qqchen.deploy.backend.framework.controller.BaseController; import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceDTO; +import com.qqchen.deploy.backend.workflow.dto.request.WorkflowStartRequest; import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; -import com.qqchen.deploy.backend.workflow.engine.WorkflowEngine; +import com.qqchen.deploy.backend.workflow.service.IWorkflowEngineService; import com.qqchen.deploy.backend.workflow.query.WorkflowInstanceQuery; import com.qqchen.deploy.backend.workflow.service.IWorkflowInstanceService; import com.qqchen.deploy.backend.workflow.converter.WorkflowInstanceConverter; @@ -27,7 +28,7 @@ import java.util.Map; public class WorkflowInstanceApiController extends BaseController { @Resource - private WorkflowEngine workflowEngine; + private IWorkflowEngineService workflowEngine; @Resource private IWorkflowInstanceService workflowInstanceService; @@ -37,42 +38,10 @@ public class WorkflowInstanceApiController extends BaseController startWorkflow(@RequestBody StartWorkflowRequest request) { - WorkflowInstance instance = workflowEngine.startWorkflow( - request.getWorkflowCode(), - request.getBusinessKey(), - request.getVariables() - ); - return Response.success(converter.toDto(instance)); + public Response startWorkflow(@RequestBody WorkflowStartRequest request) { + return Response.success(converter.toDto(workflowEngine.startWorkflow(request))); } - @Operation(summary = "终止工作流实例") - @PostMapping("/{id}/terminate") - public Response terminateWorkflow( - @Parameter(description = "工作流实例ID", required = true) @PathVariable Long id, - @Parameter(description = "终止原因") @RequestParam(required = false) String reason - ) { - workflowEngine.terminateWorkflow(id, reason); - return Response.success(); - } - - @Operation(summary = "暂停工作流实例") - @PostMapping("/{id}/pause") - public Response pauseWorkflow( - @Parameter(description = "工作流实例ID", required = true) @PathVariable Long id - ) { - workflowEngine.pauseWorkflow(id); - return Response.success(); - } - - @Operation(summary = "恢复工作流实例") - @PostMapping("/{id}/resume") - public Response resumeWorkflow( - @Parameter(description = "工作流实例ID", required = true) @PathVariable Long id - ) { - workflowEngine.resumeWorkflow(id); - return Response.success(); - } @Override public void exportData(HttpServletResponse response, List data) { @@ -80,16 +49,4 @@ public class WorkflowInstanceApiController extends BaseController variables; - } } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/JsonConverter.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/JsonConverter.java index 12f8fed4..68061548 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/JsonConverter.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/JsonConverter.java @@ -1,6 +1,6 @@ package com.qqchen.deploy.backend.workflow.converter; -import com.qqchen.deploy.backend.workflow.engine.definition.TaskExecutorDefinition; +import com.qqchen.deploy.backend.workflow.engine.definition.NodeExecutorDefinition; import java.util.List; @@ -16,7 +16,7 @@ public interface JsonConverter { * @param json JSON字符串 * @return 执行器定义列表 */ - List toExecutorList(String json); + List toExecutorList(String json); /** * 将执行器定义列表转换为JSON字符串 @@ -24,5 +24,5 @@ public interface JsonConverter { * @param executors 执行器定义列表 * @return JSON字符串 */ - String fromExecutorList(List executors); + String fromExecutorList(List executors); } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/impl/DefaultJsonConverter.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/impl/DefaultJsonConverter.java index 2dd3dac5..9b3b4e66 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/impl/DefaultJsonConverter.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/impl/DefaultJsonConverter.java @@ -7,7 +7,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.qqchen.deploy.backend.framework.enums.ResponseCode; import com.qqchen.deploy.backend.framework.exception.SystemException; import com.qqchen.deploy.backend.workflow.converter.JsonConverter; -import com.qqchen.deploy.backend.workflow.engine.definition.TaskExecutorDefinition; +import com.qqchen.deploy.backend.workflow.engine.definition.NodeExecutorDefinition; import org.springframework.stereotype.Component; import java.util.ArrayList; @@ -27,7 +27,7 @@ public class DefaultJsonConverter implements JsonConverter { } @Override - public List toExecutorList(String json) { + public List toExecutorList(String json) { if (json == null || json.isEmpty()) { return new ArrayList<>(); } @@ -38,14 +38,14 @@ public class DefaultJsonConverter implements JsonConverter { throw new SystemException(ResponseCode.WORKFLOW_CONFIG_INVALID, new Object[]{"Executors JSON must be an array"}, null); } - List result = new ArrayList<>(); + List result = new ArrayList<>(); for (JsonNode node : rootNode) { // 将configSchema转换为字符串 if (node.has("configSchema")) { ((ObjectNode) node).put("configSchema", node.get("configSchema").toString()); } // 将处理后的节点转换为TaskExecutorDefinition对象 - result.add(objectMapper.treeToValue(node, TaskExecutorDefinition.class)); + result.add(objectMapper.treeToValue(node, NodeExecutorDefinition.class)); } return result; } catch (JsonProcessingException e) { @@ -54,7 +54,7 @@ public class DefaultJsonConverter implements JsonConverter { } @Override - public String fromExecutorList(List executors) { + public String fromExecutorList(List executors) { if (executors == null || executors.isEmpty()) { return "[]"; } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/NodeTypeDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/NodeTypeDTO.java index c267f704..c93c8ddd 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/NodeTypeDTO.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/NodeTypeDTO.java @@ -1,7 +1,7 @@ package com.qqchen.deploy.backend.workflow.dto; import com.qqchen.deploy.backend.framework.dto.BaseDTO; -import com.qqchen.deploy.backend.workflow.engine.definition.TaskExecutorDefinition; +import com.qqchen.deploy.backend.workflow.engine.definition.NodeExecutorDefinition; import com.qqchen.deploy.backend.workflow.enums.NodeCategoryEnum; import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; @@ -60,7 +60,7 @@ public class NodeTypeDTO extends BaseDTO { * 3. 每种执行器都有自己的配置模式 */ @Valid - private List executors; + private List executors; /** * 节点配置模式(JSON) diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/request/WorkflowStartRequest.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/request/WorkflowStartRequest.java new file mode 100644 index 00000000..5adf7f63 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/request/WorkflowStartRequest.java @@ -0,0 +1,19 @@ +package com.qqchen.deploy.backend.workflow.dto.request; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +import java.util.Map; + +@Data +public class WorkflowStartRequest { + + @NotBlank(message = "工作流编码") + private String workflowCode; + + @NotBlank(message = "业务标识") + private String businessKey; + + @NotBlank(message = "环境变量") + private Map variables; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/DefaultWorkflowEngine.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/DefaultWorkflowEngine.java deleted file mode 100644 index 57b2413e..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/DefaultWorkflowEngine.java +++ /dev/null @@ -1,347 +0,0 @@ -package com.qqchen.deploy.backend.workflow.engine; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.qqchen.deploy.backend.framework.enums.ResponseCode; -import com.qqchen.deploy.backend.workflow.engine.context.DefaultWorkflowContext; -import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContextOperations; -import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException; -import com.qqchen.deploy.backend.workflow.engine.executor.node.NodeExecutor; -import com.qqchen.deploy.backend.workflow.engine.parser.WorkflowDefinitionParser; -import com.qqchen.deploy.backend.workflow.entity.NodeConfig; -import com.qqchen.deploy.backend.workflow.entity.NodeInstance; -import com.qqchen.deploy.backend.workflow.entity.TransitionConfig; -import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition; -import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; -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; -import jakarta.annotation.Resource; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Transactional; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.Map; - -@Slf4j -@Component -public class DefaultWorkflowEngine implements WorkflowEngine { - - @Resource - private IWorkflowDefinitionRepository workflowDefinitionRepository; - - @Resource - private IWorkflowInstanceRepository workflowInstanceRepository; - - @Resource - private INodeInstanceRepository nodeInstanceRepository; - - @Resource - private INodeConfigRepository nodeConfigRepository; - - @Resource - private ITransitionConfigRepository transitionConfigRepository; - - @Resource - private Map nodeExecutors; - - @Resource - private WorkflowVariableOperations variableOperations; - - @Resource - private ObjectMapper objectMapper; - - @Resource - private WorkflowDefinitionParser workflowDefinitionParser; - - @Override - @Transactional - public WorkflowInstance startWorkflow(String workflowCode, String businessKey, Map variables) { - // 1. 获取工作流定义 - WorkflowDefinition definition = workflowDefinitionRepository.findByCodeAndDeletedFalse(workflowCode); - if (definition == null) { - throw new WorkflowEngineException(ResponseCode.WORKFLOW_NOT_FOUND); - } - - // 2. 检查工作流定义状态 - if (definition.getStatus() != WorkflowDefinitionStatusEnum.PUBLISHED) { - throw new WorkflowEngineException(ResponseCode.WORKFLOW_NOT_PUBLISHED); - } - - // 3. 创建工作流实例 - WorkflowInstance instance = new WorkflowInstance(); - instance.setWorkflowDefinition(definition); - instance.setBusinessKey(businessKey); - instance.setStatus(WorkflowInstanceStatusEnum.RUNNING); - instance.setCreateTime(LocalDateTime.now()); - workflowInstanceRepository.save(instance); - - // 4. 初始化工作流变量 - if (variables != null && !variables.isEmpty()) { - variableOperations.setVariables(instance.getId(), variables); - } - - // 5. 解析并保存节点和流转配置 - try { - // 清除旧的配置(如果存在) - nodeConfigRepository.deleteByWorkflowDefinitionId(definition.getId()); - transitionConfigRepository.deleteByWorkflowDefinitionId(definition.getId()); - - // 解析并保存新的配置 - List nodeConfigs = workflowDefinitionParser.parseNodeConfig(definition.getNodeConfig()); - List 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.RUNNING); - 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 - @Transactional - public void executeNode(Long nodeInstanceId) { - NodeInstance nodeInstance = nodeInstanceRepository.findById(nodeInstanceId) - .orElseThrow(() -> new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_NOT_FOUND)); - - WorkflowInstance instance = nodeInstance.getWorkflowInstance(); - if (!instance.canExecuteNode()) { - throw new WorkflowEngineException(ResponseCode.WORKFLOW_INSTANCE_NOT_RUNNING); - } - - // 获取节点执行器 - NodeExecutor executor = nodeExecutors.get(nodeInstance.getNodeType()); - if (executor == null) { - throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTOR_NOT_FOUND); - } - - // 创建执行上下文 - WorkflowContextOperations context = DefaultWorkflowContext.builder() - .workflowInstance(instance) - .variableOperations(variableOperations) - .build(); - - try { - // 执行节点 - executor.execute(nodeInstance, context); - - // 更新节点状态 - nodeInstance.setStatus(NodeStatusEnum.COMPLETED); - nodeInstance.setEndTime(LocalDateTime.now()); - nodeInstanceRepository.save(nodeInstance); - - // 从数据库获取流转配置 - List transitions = transitionConfigRepository - .findByWorkflowDefinitionId(instance.getWorkflowDefinition().getId()); - - // 获取当前节点的后续节点 - List nextNodeIds = transitions.stream() - .filter(t -> t.getFrom().equals(nodeInstance.getNodeId())) - .map(TransitionConfig::getTo) - .toList(); - - // 获取节点配置 - List nodeConfigs = nodeConfigRepository - .findByWorkflowDefinitionId(instance.getWorkflowDefinition().getId()); - - // 创建并执行后续节点 - 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); - } - - // 检查是否所有节点都已完成 - List uncompletedNodes = nodeInstanceRepository - .findByWorkflowInstanceAndStatusNot(instance, NodeStatusEnum.COMPLETED); - - if (uncompletedNodes.isEmpty()) { - 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) { - NodeInstance nextNode = new NodeInstance(); - nextNode.setNodeId(nextNodeId); - nextNode.setWorkflowInstance(instance); - nextNode.setNodeType(nodeConfig.getType()); - nextNode.setName(nodeConfig.getName()); -// nextNode.setConfigObject(nodeExecuteConfigConverter.toNodeExecutorConfig(nodeConfig)); -// nextNode.setConfigObject(nodeConfig); - nextNode.setStatus(NodeStatusEnum.PENDING); - nodeInstanceRepository.save(nextNode); - - // 递归执行后续节点 - executeNode(nextNode.getId()); - } - - @Override - @Transactional - public void completeNode(Long nodeInstanceId, Map variables) { - NodeInstance nodeInstance = nodeInstanceRepository.findById(nodeInstanceId) - .orElseThrow(() -> new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_NOT_FOUND)); - - WorkflowInstance instance = nodeInstance.getWorkflowInstance(); - - // 设置节点输出变量 - if (variables != null && !variables.isEmpty()) { - variableOperations.setVariables(instance.getId(), variables); - } - - nodeInstance.setStatus(NodeStatusEnum.COMPLETED); - nodeInstance.setEndTime(LocalDateTime.now()); - nodeInstanceRepository.save(nodeInstance); - } - - @Override - @Transactional - public void terminateWorkflow(Long instanceId, String reason) { - WorkflowInstance instance = workflowInstanceRepository.findById(instanceId) - .orElseThrow(() -> new WorkflowEngineException(ResponseCode.WORKFLOW_INSTANCE_NOT_FOUND)); - - instance.terminate(reason); - workflowInstanceRepository.save(instance); - - // 清理上下文缓存 - variableOperations.clearVariables(instance.getId()); - } - - @Override - @Transactional - public void pauseWorkflow(Long instanceId) { - WorkflowInstance instance = workflowInstanceRepository.findById(instanceId) - .orElseThrow(() -> new WorkflowEngineException(ResponseCode.WORKFLOW_INSTANCE_NOT_FOUND)); - - if (!instance.canPause()) { - throw new WorkflowEngineException(ResponseCode.WORKFLOW_INSTANCE_NOT_RUNNING); - } - - instance.pause(); - workflowInstanceRepository.save(instance); - } - - @Override - @Transactional - public void resumeWorkflow(Long instanceId) { - WorkflowInstance instance = workflowInstanceRepository.findById(instanceId) - .orElseThrow(() -> new WorkflowEngineException(ResponseCode.WORKFLOW_INSTANCE_NOT_FOUND)); - - if (!instance.canResume()) { - throw new WorkflowEngineException(ResponseCode.WORKFLOW_INSTANCE_NOT_PAUSED); - } - - instance.resume(); - workflowInstanceRepository.save(instance); - } - - @Override - @Transactional - public void retryWorkflow(Long instanceId) { - WorkflowInstance instance = workflowInstanceRepository.findById(instanceId) - .orElseThrow(() -> new WorkflowEngineException(ResponseCode.WORKFLOW_INSTANCE_NOT_FOUND)); - - if (!instance.canRetry()) { - throw new WorkflowEngineException(ResponseCode.WORKFLOW_INSTANCE_NOT_RUNNING); - } - - // 重试工作流实例 - instance.retry(); - workflowInstanceRepository.save(instance); - - // 获取失败的节点 - List failedNodes = nodeInstanceRepository.findByWorkflowInstanceAndStatus( - instance, NodeStatusEnum.FAILED); - - // 重试失败的节点 - for (NodeInstance node : failedNodes) { - node.setStatus(NodeStatusEnum.PENDING); - node.setError(null); - nodeInstanceRepository.save(node); - executeNode(node.getId()); - } - } - - @Override - @Transactional - public void checkTimeout(Long instanceId, long timeoutMillis) { - WorkflowInstance instance = workflowInstanceRepository.findById(instanceId) - .orElseThrow(() -> new WorkflowEngineException(ResponseCode.WORKFLOW_INSTANCE_NOT_FOUND)); - - if (instance.isTimeout(timeoutMillis)) { - String error = "Workflow execution timeout after " + timeoutMillis + " milliseconds"; - terminateWorkflow(instanceId, error); - } - } - - private NodeInstance createStartNode(WorkflowDefinition definition, WorkflowInstance instance) { - NodeInstance startNode = new NodeInstance(); - startNode.setWorkflowInstance(instance); - startNode.setNodeId("start"); - startNode.setNodeType(NodeTypeEnum.START); - startNode.setName("开始节点"); - startNode.setStatus(NodeStatusEnum.PENDING); - startNode.setCreateTime(LocalDateTime.now()); - return nodeInstanceRepository.save(startNode); - } -} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/WorkflowContext.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/WorkflowContext.java deleted file mode 100644 index 766d8681..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/WorkflowContext.java +++ /dev/null @@ -1,116 +0,0 @@ -package com.qqchen.deploy.backend.workflow.engine; - -import com.qqchen.deploy.backend.workflow.entity.NodeInstance; -import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; -import com.qqchen.deploy.backend.workflow.service.WorkflowVariableOperations; -import lombok.Data; -import org.springframework.beans.factory.annotation.Autowired; - -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -/** - * 工作流上下文 - * 负责管理工作流执行过程中的运行时状态 - */ -@Data -public class WorkflowContext { - - /** - * 工作流实例ID - */ - private final Long instanceId; - - /** - * 当前节点实例 - */ - private NodeInstance currentNode; - - /** - * 所有节点实例 - */ - private List allNodes; - - /** - * 临时变量(节点间传递,不持久化) - */ - private final Map tempVariables; - - /** - * 变量操作服务 - */ - private final WorkflowVariableOperations variableOperations; - - public WorkflowContext(Long instanceId, WorkflowVariableOperations variableOperations) { - this.instanceId = instanceId; - this.tempVariables = new ConcurrentHashMap<>(); - this.variableOperations = variableOperations; - } - - /** - * 获取变量值(委托给 WorkflowVariableOperations) - * @deprecated 建议直接使用 WorkflowVariableOperations - */ - @Deprecated - public Object getVariable(String key) { - return variableOperations.getVariables(instanceId).get(key); - } - - /** - * 设置变量值(委托给 WorkflowVariableOperations) - * @deprecated 建议直接使用 WorkflowVariableOperations - */ - @Deprecated - public void setVariable(String key, Object value) { - Map vars = new ConcurrentHashMap<>(); - vars.put(key, value); - variableOperations.setVariables(instanceId, vars); - } - - /** - * 获取所有变量(委托给 WorkflowVariableOperations) - * @deprecated 建议直接使用 WorkflowVariableOperations - */ - @Deprecated - public Map getVariables() { - return variableOperations.getVariables(instanceId); - } - - /** - * 设置多个变量(委托给 WorkflowVariableOperations) - * @deprecated 建议直接使用 WorkflowVariableOperations - */ - @Deprecated - public void setVariables(Map variables) { - variableOperations.setVariables(instanceId, variables); - } - - /** - * 获取临时变量值 - */ - public Object getTempVariable(String key) { - return tempVariables.get(key); - } - - /** - * 设置临时变量值 - */ - public void setTempVariable(String key, Object value) { - tempVariables.put(key, value); - } - - /** - * 获取所有临时变量 - */ - public Map getTempVariables() { - return new ConcurrentHashMap<>(tempVariables); - } - - /** - * 清除临时变量 - */ - public void clearTempVariables() { - tempVariables.clear(); - } -} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/WorkflowEngine.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/WorkflowEngine.java deleted file mode 100644 index 1dcd8cc9..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/WorkflowEngine.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.qqchen.deploy.backend.workflow.engine; - -import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; - -import java.util.Map; - -/** - * 工作流执行引擎接口 - */ -public interface WorkflowEngine { - - /** - * 启动工作流实例 - */ - WorkflowInstance startWorkflow(String workflowCode, String businessKey, Map variables); - - /** - * 执行节点 - */ - void executeNode(Long nodeInstanceId); - - /** - * 完成节点 - */ - void completeNode(Long nodeInstanceId, Map variables); - - /** - * 终止工作流实例 - */ - void terminateWorkflow(Long instanceId, String reason); - - /** - * 暂停工作流实例 - */ - void pauseWorkflow(Long instanceId); - - /** - * 恢复工作流实例 - */ - void resumeWorkflow(Long instanceId); - - /** - * 重试工作流实例 - */ - void retryWorkflow(Long instanceId); - - /** - * 检查工作流实例是否超时 - * @param instanceId 工作流实例ID - * @param timeoutMillis 超时时间(毫秒) - */ - void checkTimeout(Long instanceId, long timeoutMillis); -} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/config/WorkflowEngineConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/config/WorkflowEngineConfig.java index 8dd81bb8..f74f0426 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/config/WorkflowEngineConfig.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/config/WorkflowEngineConfig.java @@ -1,7 +1,6 @@ package com.qqchen.deploy.backend.workflow.engine.config; -import com.qqchen.deploy.backend.workflow.engine.executor.node.NodeExecutor; -import com.qqchen.deploy.backend.workflow.engine.executor.task.TaskExecutor; +import com.qqchen.deploy.backend.workflow.engine.executor.INodeExecutor; import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -15,20 +14,11 @@ import java.util.stream.Collectors; public class WorkflowEngineConfig { @Bean - public Map taskExecutorMap(List executors) { + public Map nodeExecutorMap(List executors) { return executors.stream() - .collect(Collectors.toMap( - executor -> executor.getClass().getSimpleName().replace("TaskExecutor", "").toUpperCase(), - Function.identity() - )); - } - - @Bean - public Map nodeExecutorMap(List executors) { - return executors.stream() - .collect(Collectors.toMap( - NodeExecutor::getNodeType, - Function.identity() - )); + .collect(Collectors.toMap( + INodeExecutor::getNodeType, + Function.identity() + )); } } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/context/DefaultWorkflowContext.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/context/DefaultWorkflowContext.java deleted file mode 100644 index 91fa8495..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/context/DefaultWorkflowContext.java +++ /dev/null @@ -1,97 +0,0 @@ -package com.qqchen.deploy.backend.workflow.engine.context; - -import com.qqchen.deploy.backend.system.enums.LogLevelEnum; -import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; -import com.qqchen.deploy.backend.workflow.service.WorkflowVariableOperations; -import lombok.Getter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Map; - -/** - * 默认工作流上下文实现 - */ -public class DefaultWorkflowContext implements WorkflowContextOperations { - - private static final Logger logger = LoggerFactory.getLogger(DefaultWorkflowContext.class); - - @Getter - private final WorkflowInstance workflowInstance; - - private final WorkflowVariableOperations variableOperations; - - public DefaultWorkflowContext(WorkflowInstance workflowInstance, WorkflowVariableOperations variableOperations) { - this.workflowInstance = workflowInstance; - this.variableOperations = variableOperations; - } - - @Override - public WorkflowInstance getInstance() { - return workflowInstance; - } - - @Override - public Object getVariable(String key) { - return variableOperations.getVariable(workflowInstance.getId(), key); - } - - @Override - public void setVariable(String key, Object value) { - variableOperations.setVariable(workflowInstance.getId(), key, value); - } - - @Override - public void setVariables(Map variables) { - variableOperations.setVariables(workflowInstance.getId(), variables); - } - - @Override - public Map getVariables() { - return variableOperations.getVariables(workflowInstance.getId()); - } - - @Override - public void log(String message, LogLevelEnum level) { - switch (level) { - case DEBUG -> logger.debug("[Workflow:{}] {}", workflowInstance.getId(), message); - case INFO -> logger.info("[Workflow:{}] {}", workflowInstance.getId(), message); - case WARN -> logger.warn("[Workflow:{}] {}", workflowInstance.getId(), message); - case ERROR -> logger.error("[Workflow:{}] {}", workflowInstance.getId(), message); - } - } - - @Override - public void log(String message, String detail, LogLevelEnum level) { - switch (level) { - case DEBUG -> logger.debug("[Workflow:{}] {} - Detail: {}", workflowInstance.getId(), message, detail); - case INFO -> logger.info("[Workflow:{}] {} - Detail: {}", workflowInstance.getId(), message, detail); - case WARN -> logger.warn("[Workflow:{}] {} - Detail: {}", workflowInstance.getId(), message, detail); - case ERROR -> logger.error("[Workflow:{}] {} - Detail: {}", workflowInstance.getId(), message, detail); - } - } - - public static class Builder { - private WorkflowInstance workflowInstance; - - private WorkflowVariableOperations variableOperations; - - public Builder workflowInstance(WorkflowInstance workflowInstance) { - this.workflowInstance = workflowInstance; - return this; - } - - public Builder variableOperations(WorkflowVariableOperations variableOperations) { - this.variableOperations = variableOperations; - return this; - } - - public DefaultWorkflowContext build() { - return new DefaultWorkflowContext(workflowInstance, variableOperations); - } - } - - public static Builder builder() { - return new Builder(); - } -} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/context/WorkflowContextOperations.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/context/WorkflowContextOperations.java deleted file mode 100644 index 3e7232ea..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/context/WorkflowContextOperations.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.qqchen.deploy.backend.workflow.engine.context; - -import com.qqchen.deploy.backend.system.enums.LogLevelEnum; -import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; - -import java.util.Map; - -/** - * 工作流上下文操作接口 - * 定义工作流执行过程中的上下文操作能力 - */ -public interface WorkflowContextOperations { - - /** - * 获取工作流实例 - */ - WorkflowInstance getInstance(); - - void setVariables(Map variables); - - /** - * 获取所有变量 - * @deprecated 使用 WorkflowVariableOperations 替代 - */ - @Deprecated - Map getVariables(); - - /** - * 获取变量 - * @deprecated 使用 WorkflowVariableOperations 替代 - */ - @Deprecated - Object getVariable(String name); - - /** - * 设置变量 - * @deprecated 使用 WorkflowVariableOperations 替代 - */ - @Deprecated - void setVariable(String name, Object value); - - /** - * 记录日志 - */ - void log(String message, LogLevelEnum level); - - /** - * 记录日志(带详情) - */ - void log(String message, String detail, LogLevelEnum level); -} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/definition/TaskExecutorDefinition.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/definition/NodeExecutorDefinition.java similarity index 75% rename from backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/definition/TaskExecutorDefinition.java rename to backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/definition/NodeExecutorDefinition.java index c9f7bbbe..bae6b3e1 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/definition/TaskExecutorDefinition.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/definition/NodeExecutorDefinition.java @@ -3,25 +3,9 @@ package com.qqchen.deploy.backend.workflow.engine.definition; import jakarta.validation.constraints.NotBlank; import lombok.Data; -/** - * 任务执行器定义 - * 用于定义工作流节点支持的任务执行器类型及其配置结构 - * 主要用于: - * 1. 节点类型配置:定义节点类型支持哪些执行器 - * 2. 流程设计:前端根据执行器定义动态渲染配置表单 - * 3. 执行引擎:根据执行器定义验证和执行任务 - */ @Data -public class TaskExecutorDefinition { +public class NodeExecutorDefinition { - /** - * 执行器编码 - * 用于标识执行器类型,需要与具体的执行器实现类对应 - * 例如: - * - SHELL:对应ShellTaskExecutor - * - JENKINS:对应JenkinsTaskExecutor - * - HTTP:对应HttpTaskExecutor - */ @NotBlank(message = "执行器编码不能为空") private String code; @@ -51,7 +35,7 @@ public class TaskExecutorDefinition { * 用于: * 1. 前端动态渲染配置表单 * 2. 后端验证配置参数 - * + * * 示例 - Shell执行器配置模式: * { * "type": "object", diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/AbstractNodeExecutor.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/AbstractNodeExecutor.java new file mode 100644 index 00000000..17838f6e --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/AbstractNodeExecutor.java @@ -0,0 +1,164 @@ +package com.qqchen.deploy.backend.workflow.engine.executor; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.qqchen.deploy.backend.framework.enums.ResponseCode; +import com.qqchen.deploy.backend.system.enums.LogLevelEnum; +import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException; +import com.qqchen.deploy.backend.workflow.engine.model.NodeConfig; +import com.qqchen.deploy.backend.workflow.engine.model.WorkflowGraph; +import com.qqchen.deploy.backend.workflow.entity.NodeInstance; +import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; +import com.qqchen.deploy.backend.workflow.enums.NodeStatusEnum; +import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum; +import com.qqchen.deploy.backend.workflow.repository.INodeInstanceRepository; +import com.qqchen.deploy.backend.workflow.service.IWorkflowLogService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Lazy; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + + +/** + * 抽象节点执行器 + */ +@Slf4j +public abstract class AbstractNodeExecutor implements INodeExecutor { + + @Resource + protected ObjectMapper objectMapper; + + @Resource + protected INodeInstanceRepository nodeInstanceRepository; + + @Resource + protected IWorkflowLogService workflowLogService; + + @Resource + @Lazy + private Map nodeExecutors; + + @Override + public void execute(WorkflowInstance workflowInstance, WorkflowGraph graph, NodeConfig currentNode) { + NodeInstance nodeInstance = null; + try { + nodeInstance = createNodeInstance(workflowInstance, currentNode); + } catch (JsonProcessingException e) { + throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED, e); + } + try { + // 1. 创建节点实例 + // 1. 前置处理 + beforeExecute(workflowInstance, nodeInstance); + + // 2. 执行节点逻辑 + doExecute(workflowInstance, nodeInstance); + + // 3. 后置处理 + afterExecute(workflowInstance, nodeInstance); + + // 4. 更新节点状态 + updateNodeStatus(nodeInstance, true); + + // 4. 获取并执行下一个节点 + List nextNodes = graph.getNextNodes(currentNode.getNodeId()); + executeNextNodes(workflowInstance, graph, nextNodes); + + } catch (Exception e) { + // 5. 异常处理 + handleExecutionError(workflowInstance, nodeInstance, e); + throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED, e); + } + } + + /** + * 执行后续节点 + */ + protected void executeNextNodes(WorkflowInstance workflowInstance, WorkflowGraph graph, List nextNodes) { + for (NodeConfig nextNode : nextNodes) { + INodeExecutor executor = nodeExecutors.get(nextNode.getType()); + executor.execute(workflowInstance, graph, nextNode); + } + } + + /** + * 创建节点实例 + */ + protected NodeInstance createNodeInstance(WorkflowInstance workflowInstance, NodeConfig config) throws JsonProcessingException { + NodeInstance node = new NodeInstance(); + node.setWorkflowInstance(workflowInstance); + node.setNodeId(config.getNodeId()); + node.setNodeType(config.getType()); + node.setName(config.getName()); + node.setConfig(objectMapper.writeValueAsString(config.getConfig())); + node.setStatus(NodeStatusEnum.RUNNING); + node.setCreateTime(LocalDateTime.now()); + node.setStartTime(LocalDateTime.now()); + return nodeInstanceRepository.save(node); + } + + /** + * 执行节点逻辑 + */ + protected abstract void doExecute(WorkflowInstance workflowInstance, NodeInstance nodeInstance); + + /** + * 前置处理 + */ + protected void beforeExecute(WorkflowInstance workflowInstance, NodeInstance nodeInstance) { + } + + /** + * 后置处理 + */ + protected void afterExecute(WorkflowInstance workflowInstance, NodeInstance nodeInstance) { + logNodeComplete(nodeInstance); + } + + /** + * 处理执行异常 + */ + protected void handleExecutionError(WorkflowInstance workflowInstance, NodeInstance nodeInstance, Exception e) { + log.error("Node execution failed. nodeInstance: {}, error: {}", nodeInstance.getId(), e.getMessage(), e); + logSystem(nodeInstance, LogLevelEnum.ERROR, String.format("节点执行失败: %s[%s]", nodeInstance.getName(), nodeInstance.getNodeId()), e.getMessage()); + logNodeError(nodeInstance, e.getMessage()); + updateNodeStatus(nodeInstance, false); + } + + /** + * 更新节点状态 + */ + private void updateNodeStatus(NodeInstance nodeInstance, boolean success) { + nodeInstance.setStatus(success ? NodeStatusEnum.COMPLETED : NodeStatusEnum.FAILED); + if (success) { + nodeInstance.setEndTime(LocalDateTime.now()); + } + nodeInstanceRepository.save(nodeInstance); + } + + /** + * 记录系统日志 + */ + protected void logSystem(NodeInstance nodeInstance, LogLevelEnum level, String message, String detail) { + workflowLogService.log(nodeInstance.getWorkflowInstance(), nodeInstance.getNodeId(), level, message, detail); + } + + + /** + * 记录节点完成日志 + */ + protected void logNodeComplete(NodeInstance nodeInstance) { + workflowLogService.log(nodeInstance.getWorkflowInstance(), nodeInstance.getNodeId(), LogLevelEnum.INFO, String.format("节点执行完成: %s", nodeInstance.getName()), null); + } + + /** + * 记录节点错误日志 + */ + protected void logNodeError(NodeInstance nodeInstance, String error) { + workflowLogService.log(nodeInstance.getWorkflowInstance(), nodeInstance.getNodeId(), LogLevelEnum.ERROR, String.format("节点执行失败: %s", nodeInstance.getName()), error); + nodeInstance.setError(error); + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/EndNodeExecutor.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/EndNodeExecutor.java new file mode 100644 index 00000000..6e790c86 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/EndNodeExecutor.java @@ -0,0 +1,38 @@ +package com.qqchen.deploy.backend.workflow.engine.executor; + +import com.qqchen.deploy.backend.workflow.entity.NodeInstance; +import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; +import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + + +/** + * 结束节点执行器 + * 负责完成工作流实例的执行 + */ +@Slf4j +@Component("endNodeExecutor") +public class EndNodeExecutor extends AbstractNodeExecutor { + + + @Override + protected void doExecute(WorkflowInstance workflowInstance, NodeInstance nodeInstance) { + + } + + @Override + public NodeTypeEnum getNodeType() { + return NodeTypeEnum.END; + } + + @Override + public void validate(String config) { + + } + + @Override + public void terminate(WorkflowInstance workflowInstance, NodeInstance nodeInstance) { + + } +} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/INodeExecutor.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/INodeExecutor.java new file mode 100644 index 00000000..1d38c659 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/INodeExecutor.java @@ -0,0 +1,27 @@ +package com.qqchen.deploy.backend.workflow.engine.executor; + +import com.qqchen.deploy.backend.workflow.engine.model.NodeConfig; +import com.qqchen.deploy.backend.workflow.engine.model.WorkflowGraph; +import com.qqchen.deploy.backend.workflow.entity.NodeInstance; +import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; +import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum; + +/** + * 节点执行器接口 + */ +public interface INodeExecutor { + + /** + * 获取支持的节点类型 + */ + NodeTypeEnum getNodeType(); + + void execute(WorkflowInstance workflowInstance, WorkflowGraph graph, NodeConfig currentNode); + + /** + * 验证节点配置 + */ + void validate(String config); + + void terminate(WorkflowInstance workflowInstance, NodeInstance nodeInstance); +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/ScriptNodeExecutor.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/ScriptNodeExecutor.java new file mode 100644 index 00000000..8bd07b29 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/ScriptNodeExecutor.java @@ -0,0 +1,36 @@ +package com.qqchen.deploy.backend.workflow.engine.executor; + +import com.qqchen.deploy.backend.workflow.entity.NodeInstance; +import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; +import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * 脚本节点执行器 + * 支持多种脚本语言(Python, Shell, JavaScript等) + */ +@Slf4j +@Component("scriptNodeExecutor") +public class ScriptNodeExecutor extends AbstractNodeExecutor { + + @Override + protected void doExecute(WorkflowInstance workflowInstance, NodeInstance nodeInstance) { + + } + + @Override + public NodeTypeEnum getNodeType() { + return NodeTypeEnum.SCRIPT; + } + + @Override + public void validate(String config) { + + } + + @Override + public void terminate(WorkflowInstance workflowInstance, NodeInstance nodeInstance) { + + } +} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/StartNodeExecutor.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/StartNodeExecutor.java new file mode 100644 index 00000000..546a8e5e --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/StartNodeExecutor.java @@ -0,0 +1,37 @@ +package com.qqchen.deploy.backend.workflow.engine.executor; + +import com.qqchen.deploy.backend.workflow.entity.NodeInstance; +import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; +import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * 开始节点执行器 + * 负责工作流实例的初始化工作 + */ +@Slf4j +@Component("startNodeExecutor") +public class StartNodeExecutor extends AbstractNodeExecutor { + + + @Override + protected void doExecute(WorkflowInstance workflowInstance, NodeInstance nodeInstance) { + + } + + @Override + public NodeTypeEnum getNodeType() { + return NodeTypeEnum.START; + } + + @Override + public void validate(String config) { + + } + + @Override + public void terminate(WorkflowInstance workflowInstance, NodeInstance nodeInstance) { + + } +} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/config/ApprovalNodeExecutorConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/config/ApprovalNodeExecutorConfig.java similarity index 91% rename from backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/config/ApprovalNodeExecutorConfig.java rename to backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/config/ApprovalNodeExecutorConfig.java index 9604c2f3..a2fb5057 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/config/ApprovalNodeExecutorConfig.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/config/ApprovalNodeExecutorConfig.java @@ -1,4 +1,4 @@ -package com.qqchen.deploy.backend.workflow.engine.executor.node.config; +package com.qqchen.deploy.backend.workflow.engine.executor.config; import lombok.Data; import lombok.EqualsAndHashCode; diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/config/ConditionNodeExecutorConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/config/ConditionNodeExecutorConfig.java similarity index 91% rename from backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/config/ConditionNodeExecutorConfig.java rename to backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/config/ConditionNodeExecutorConfig.java index a11283d2..bc0c5765 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/config/ConditionNodeExecutorConfig.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/config/ConditionNodeExecutorConfig.java @@ -1,4 +1,4 @@ -package com.qqchen.deploy.backend.workflow.engine.executor.node.config; +package com.qqchen.deploy.backend.workflow.engine.executor.config; import lombok.Data; import lombok.EqualsAndHashCode; diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/config/GitNodeExecutorConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/config/GitNodeExecutorConfig.java similarity index 91% rename from backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/config/GitNodeExecutorConfig.java rename to backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/config/GitNodeExecutorConfig.java index d4d7f243..531b8454 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/config/GitNodeExecutorConfig.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/config/GitNodeExecutorConfig.java @@ -1,4 +1,4 @@ -package com.qqchen.deploy.backend.workflow.engine.executor.node.config; +package com.qqchen.deploy.backend.workflow.engine.executor.config; import lombok.Data; import lombok.EqualsAndHashCode; diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/config/HttpNodeExecutorConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/config/HttpNodeExecutorConfig.java similarity index 92% rename from backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/config/HttpNodeExecutorConfig.java rename to backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/config/HttpNodeExecutorConfig.java index 0d0bc6c4..a72f8d03 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/config/HttpNodeExecutorConfig.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/config/HttpNodeExecutorConfig.java @@ -1,4 +1,4 @@ -package com.qqchen.deploy.backend.workflow.engine.executor.node.config; +package com.qqchen.deploy.backend.workflow.engine.executor.config; import lombok.Data; import lombok.EqualsAndHashCode; diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/config/JenkinsNodeExecutorConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/config/JenkinsNodeExecutorConfig.java similarity index 91% rename from backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/config/JenkinsNodeExecutorConfig.java rename to backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/config/JenkinsNodeExecutorConfig.java index 9025cba7..be92b9f0 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/config/JenkinsNodeExecutorConfig.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/config/JenkinsNodeExecutorConfig.java @@ -1,4 +1,4 @@ -package com.qqchen.deploy.backend.workflow.engine.executor.node.config; +package com.qqchen.deploy.backend.workflow.engine.executor.config; import lombok.Data; import lombok.EqualsAndHashCode; diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/config/NacosNodeExecutorConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/config/NacosNodeExecutorConfig.java similarity index 92% rename from backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/config/NacosNodeExecutorConfig.java rename to backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/config/NacosNodeExecutorConfig.java index c9462e39..336f877b 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/config/NacosNodeExecutorConfig.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/config/NacosNodeExecutorConfig.java @@ -1,4 +1,4 @@ -package com.qqchen.deploy.backend.workflow.engine.executor.node.config; +package com.qqchen.deploy.backend.workflow.engine.executor.config; import lombok.Data; import lombok.EqualsAndHashCode; diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/config/NodeExecutorConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/config/NodeExecutorConfig.java new file mode 100644 index 00000000..f7484750 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/config/NodeExecutorConfig.java @@ -0,0 +1,30 @@ +package com.qqchen.deploy.backend.workflow.engine.executor.config; + +import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum; +import lombok.Data; + +/** + * 节点配置基类 + */ +public class NodeExecutorConfig { + + /** + * 节点ID + */ + private String id; + + /** + * 节点名称 + */ + private String name; + + /** + * 节点类型 + */ + private NodeTypeEnum type; + + /** + * 描述 + */ + private String description; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/config/NotifyNodeExecutorConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/config/NotifyNodeExecutorConfig.java similarity index 92% rename from backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/config/NotifyNodeExecutorConfig.java rename to backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/config/NotifyNodeExecutorConfig.java index c88336c5..b3b1ad45 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/config/NotifyNodeExecutorConfig.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/config/NotifyNodeExecutorConfig.java @@ -1,4 +1,4 @@ -package com.qqchen.deploy.backend.workflow.engine.executor.node.config; +package com.qqchen.deploy.backend.workflow.engine.executor.config; import lombok.Data; import lombok.EqualsAndHashCode; diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/config/ParallelNodeExecutorConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/config/ParallelNodeExecutorConfig.java similarity index 92% rename from backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/config/ParallelNodeExecutorConfig.java rename to backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/config/ParallelNodeExecutorConfig.java index 2df275ed..3c30345a 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/config/ParallelNodeExecutorConfig.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/config/ParallelNodeExecutorConfig.java @@ -1,4 +1,4 @@ -package com.qqchen.deploy.backend.workflow.engine.executor.node.config; +package com.qqchen.deploy.backend.workflow.engine.executor.config; import lombok.Data; import lombok.EqualsAndHashCode; diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/config/ScriptNodeExecutorConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/config/ScriptNodeExecutorConfig.java similarity index 94% rename from backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/config/ScriptNodeExecutorConfig.java rename to backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/config/ScriptNodeExecutorConfig.java index eb4fbafa..6f128d2f 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/config/ScriptNodeExecutorConfig.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/config/ScriptNodeExecutorConfig.java @@ -1,4 +1,4 @@ -package com.qqchen.deploy.backend.workflow.engine.executor.node.config; +package com.qqchen.deploy.backend.workflow.engine.executor.config; import com.qqchen.deploy.backend.workflow.enums.ScriptLanguageEnum; import lombok.Data; diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/gateway/BranchConvergeConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/gateway/BranchConvergeConfig.java deleted file mode 100644 index 2650bc36..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/gateway/BranchConvergeConfig.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.qqchen.deploy.backend.workflow.engine.executor.gateway; - -import lombok.Data; - -/** - * 分支汇聚配置 - */ -@Data -public class BranchConvergeConfig { - /** - * 汇聚策略 - */ - private ConvergeStrategy strategy; - - /** - * 需要完成的分支数量(N个完成时使用) - */ - private Integer requiredCount; - - public enum ConvergeStrategy { - ALL("全部完成"), - ANY("任一完成"), - N("N个完成"); - - private final String description; - - ConvergeStrategy(String description) { - this.description = description; - } - - public String getDescription() { - return description; - } - } -} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/gateway/ConditionalGatewayConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/gateway/ConditionalGatewayConfig.java deleted file mode 100644 index fb6fdfe5..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/gateway/ConditionalGatewayConfig.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.qqchen.deploy.backend.workflow.engine.executor.gateway; - -import lombok.Data; -import lombok.EqualsAndHashCode; - -import java.util.List; - -/** - * 基于条件的网关配置基类 - */ -@Data -@EqualsAndHashCode(callSuper = true) -public abstract class ConditionalGatewayConfig extends GatewayConfig { - /** - * 条件分支列表 - */ - private List branches; - - /** - * 默认分支节点ID - */ - private String defaultNodeId; - - @Data - public static class ConditionalBranch { - /** - * 分支名称 - */ - private String name; - - /** - * 条件表达式 - */ - private String condition; - - /** - * 目标节点ID - */ - private String to; - - /** - * 分支描述 - */ - private String description; - } -} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/gateway/ExclusiveGatewayConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/gateway/ExclusiveGatewayConfig.java deleted file mode 100644 index 107f4e63..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/gateway/ExclusiveGatewayConfig.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.qqchen.deploy.backend.workflow.engine.executor.gateway; - -import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContextOperations; -import lombok.Data; -import lombok.EqualsAndHashCode; -import org.springframework.expression.ExpressionParser; -import org.springframework.expression.spel.standard.SpelExpressionParser; -import org.springframework.expression.spel.support.StandardEvaluationContext; - -import java.util.Collections; -import java.util.List; - -/** - * 排他网关配置 - */ -@Data -@EqualsAndHashCode(callSuper = true) -public class ExclusiveGatewayConfig extends ConditionalGatewayConfig { - private final ExpressionParser expressionParser = new SpelExpressionParser(); - - @Override - public List getNextNodeIds(WorkflowContextOperations context) { - // 返回第一个满足条件的分支的targetNodeId - for (ConditionalBranch branch : getBranches()) { - if (evaluateCondition(branch.getCondition(), context)) { - return Collections.singletonList(branch.getTo()); - } - } - return Collections.singletonList(getDefaultNodeId()); - } - - private boolean evaluateCondition(String condition, WorkflowContextOperations context) { - try { - StandardEvaluationContext evaluationContext = new StandardEvaluationContext(context); - return Boolean.TRUE.equals(expressionParser.parseExpression(condition) - .getValue(evaluationContext, Boolean.class)); - } catch (Exception e) { - return false; - } - } -} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/gateway/GatewayConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/gateway/GatewayConfig.java deleted file mode 100644 index 86a905f2..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/gateway/GatewayConfig.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.qqchen.deploy.backend.workflow.engine.executor.gateway; - -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContextOperations; -import com.qqchen.deploy.backend.workflow.enums.GatewayTypeEnum; -import lombok.Data; - -import java.util.List; - -/** - * 网关配置基类 - */ -@Data -@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") -@JsonSubTypes({ - @JsonSubTypes.Type(value = ExclusiveGatewayConfig.class, name = "EXCLUSIVE"), - @JsonSubTypes.Type(value = ParallelGatewayConfig.class, name = "PARALLEL"), - @JsonSubTypes.Type(value = InclusiveGatewayConfig.class, name = "INCLUSIVE") -}) -public abstract class GatewayConfig { - /** - * 网关类型 - */ - private GatewayTypeEnum type; - - /** - * 网关名称 - */ - private String name; - - /** - * 网关描述 - */ - private String description; - - /** - * 获取下一个节点IDs - */ - public abstract List getNextNodeIds(WorkflowContextOperations context); -} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/gateway/GatewayNodeExecutor.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/gateway/GatewayNodeExecutor.java deleted file mode 100644 index 6754167d..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/gateway/GatewayNodeExecutor.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.qqchen.deploy.backend.workflow.engine.executor.gateway; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.qqchen.deploy.backend.framework.enums.ResponseCode; -import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContextOperations; -import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException; -import com.qqchen.deploy.backend.workflow.engine.executor.node.NodeExecutor; -import com.qqchen.deploy.backend.workflow.entity.NodeInstance; -import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; - -import java.util.List; - -@Slf4j -@Component -public class GatewayNodeExecutor implements NodeExecutor { - - private final ObjectMapper objectMapper = new ObjectMapper(); - - @Override - public NodeTypeEnum getNodeType() { - return NodeTypeEnum.GATEWAY; - } - - @Override - public void execute(NodeInstance nodeInstance, WorkflowContextOperations context) { - try { - // 解析网关配置 - GatewayConfig config = objectMapper.readValue(nodeInstance.getConfig(), GatewayConfig.class); - - // 获取下一个节点IDs - List nextNodeIds = config.getNextNodeIds(context); - - // 设置输出 - nodeInstance.setOutput(objectMapper.createObjectNode() - .put("nextNodeIds", String.join(",", nextNodeIds)) - .toString()); - - } catch (Exception e) { - log.error("Gateway node execution failed", e); - throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED, e); - } - } - - @Override - public void validate(String config) { - try { - objectMapper.readValue(config, GatewayConfig.class); - } catch (Exception e) { - throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR); - } - } - - @Override - public void terminate(NodeInstance nodeInstance, WorkflowContextOperations context) { - // Gateway nodes are instant operations, no need to terminate - } -} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/gateway/InclusiveGatewayConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/gateway/InclusiveGatewayConfig.java deleted file mode 100644 index 9e6913bc..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/gateway/InclusiveGatewayConfig.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.qqchen.deploy.backend.workflow.engine.executor.gateway; - -import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContextOperations; -import lombok.Data; -import lombok.EqualsAndHashCode; -import org.springframework.expression.ExpressionParser; -import org.springframework.expression.spel.standard.SpelExpressionParser; -import org.springframework.expression.spel.support.StandardEvaluationContext; - -import java.util.List; -import java.util.stream.Collectors; - -/** - * 包容网关配置 - */ -@Data -@EqualsAndHashCode(callSuper = true) -public class InclusiveGatewayConfig extends ConditionalGatewayConfig { - /** - * 汇聚配置 - */ - private BranchConvergeConfig convergeConfig; - - private final ExpressionParser expressionParser = new SpelExpressionParser(); - - @Override - public List getNextNodeIds(WorkflowContextOperations context) { - // 返回所有满足条件的分支的targetNodeId - List nextNodeIds = getBranches().stream() - .filter(branch -> evaluateCondition(branch.getCondition(), context)) - .map(ConditionalBranch::getTo) - .collect(Collectors.toList()); - - // 如果没有满足条件的分支,使用默认分支 - if (nextNodeIds.isEmpty() && getDefaultNodeId() != null) { - nextNodeIds.add(getDefaultNodeId()); - } - - return nextNodeIds; - } - - private boolean evaluateCondition(String condition, WorkflowContextOperations context) { - try { - StandardEvaluationContext evaluationContext = new StandardEvaluationContext(context); - return Boolean.TRUE.equals(expressionParser.parseExpression(condition) - .getValue(evaluationContext, Boolean.class)); - } catch (Exception e) { - return false; - } - } -} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/gateway/ParallelGatewayConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/gateway/ParallelGatewayConfig.java deleted file mode 100644 index 70b22e76..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/gateway/ParallelGatewayConfig.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.qqchen.deploy.backend.workflow.engine.executor.gateway; - -import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContextOperations; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import java.util.List; -import java.util.stream.Collectors; - -/** - * 并行网关配置 - */ -@Data -@EqualsAndHashCode(callSuper = true) -public class ParallelGatewayConfig extends GatewayConfig { - /** - * 并行分支列表 - */ - private List branches; - - /** - * 汇聚配置 - */ - private BranchConvergeConfig convergeConfig; - - @Data - public static class ParallelBranch { - /** - * 分支名称 - */ - private String name; - - /** - * 目标节点ID - */ - private String to; - - /** - * 分支描述 - */ - private String description; - } - - @Override - public List getNextNodeIds(WorkflowContextOperations context) { - // 返回所有分支的targetNodeId - return branches.stream() - .map(ParallelBranch::getTo) - .collect(Collectors.toList()); - } -} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/AbstractNodeExecutor.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/AbstractNodeExecutor.java deleted file mode 100644 index 3a480c2d..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/AbstractNodeExecutor.java +++ /dev/null @@ -1,133 +0,0 @@ -package com.qqchen.deploy.backend.workflow.engine.executor.node; - -import com.qqchen.deploy.backend.framework.enums.ResponseCode; -import com.qqchen.deploy.backend.system.enums.LogLevelEnum; -import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContextOperations; -import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException; -import com.qqchen.deploy.backend.workflow.entity.NodeInstance; -import com.qqchen.deploy.backend.workflow.enums.NodeStatusEnum; -import com.qqchen.deploy.backend.workflow.repository.INodeInstanceRepository; -import com.qqchen.deploy.backend.workflow.service.IWorkflowLogService; -import com.qqchen.deploy.backend.workflow.service.WorkflowVariableOperations; -import jakarta.annotation.Resource; -import lombok.extern.slf4j.Slf4j; - -import java.time.LocalDateTime; - -/** - * 抽象节点执行器 - */ -@Slf4j -public abstract class AbstractNodeExecutor implements NodeExecutor { - - @Resource - protected WorkflowVariableOperations variableOperations; - - @Resource - protected INodeInstanceRepository nodeInstanceRepository; - - @Resource - protected IWorkflowLogService workflowLogService; - - @Override - public void execute(NodeInstance nodeInstance, WorkflowContextOperations context) { - try { - // 1. 前置处理 - beforeExecute(nodeInstance, context); - - // 2. 执行节点逻辑 - doExecute(nodeInstance, context); - - // 3. 后置处理 - afterExecute(nodeInstance, context); - - // 4. 更新节点状态 - updateNodeStatus(nodeInstance, true); - - } catch (Exception e) { - // 5. 异常处理 - handleExecutionError(nodeInstance, context, e); - throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED, e); - } - } - - /** - * 执行节点逻辑 - */ - protected abstract void doExecute(NodeInstance nodeInstance, WorkflowContextOperations context); - - /** - * 前置处理 - */ - protected void beforeExecute(NodeInstance nodeInstance, WorkflowContextOperations context) { - context.log("Starting node execution: " + nodeInstance.getName(), LogLevelEnum.INFO); - logNodeStart(nodeInstance); - } - - /** - * 后置处理 - */ - protected void afterExecute(NodeInstance nodeInstance, WorkflowContextOperations context) { - context.log("Node execution completed: " + nodeInstance.getName(), LogLevelEnum.INFO); - logNodeComplete(nodeInstance); - } - - /** - * 处理执行异常 - */ - protected void handleExecutionError(NodeInstance nodeInstance, WorkflowContextOperations context, Exception e) { - log.error("Node execution failed. nodeInstance: {}, error: {}", nodeInstance.getId(), e.getMessage(), e); - context.log("Node execution failed: " + e.getMessage(), LogLevelEnum.ERROR); - logSystem(nodeInstance, LogLevelEnum.ERROR, - String.format("节点执行失败: %s[%s]", nodeInstance.getName(), nodeInstance.getNodeId()), - e.getMessage()); - logNodeError(nodeInstance, e.getMessage()); - updateNodeStatus(nodeInstance, false); - } - - /** - * 更新节点状态 - */ - private void updateNodeStatus(NodeInstance nodeInstance, boolean success) { - nodeInstance.setStatus(success ? NodeStatusEnum.COMPLETED : NodeStatusEnum.FAILED); - if (success) { - nodeInstance.setEndTime(LocalDateTime.now()); - } - nodeInstanceRepository.save(nodeInstance); - } - - /** - * 记录系统日志 - */ - protected void logSystem(NodeInstance nodeInstance, LogLevelEnum level, String message, String detail) { - workflowLogService.log(nodeInstance.getWorkflowInstance(), nodeInstance.getNodeId(), level, message, detail); - } - - /** - * 记录节点开始日志 - */ - protected void logNodeStart(NodeInstance nodeInstance) { - workflowLogService.log(nodeInstance.getWorkflowInstance(), nodeInstance.getNodeId(), - LogLevelEnum.INFO, String.format("节点开始执行: %s", nodeInstance.getName()), null); - nodeInstance.setStatus(NodeStatusEnum.RUNNING); - nodeInstance.setStartTime(LocalDateTime.now()); - nodeInstanceRepository.save(nodeInstance); - } - - /** - * 记录节点完成日志 - */ - protected void logNodeComplete(NodeInstance nodeInstance) { - workflowLogService.log(nodeInstance.getWorkflowInstance(), nodeInstance.getNodeId(), - LogLevelEnum.INFO, String.format("节点执行完成: %s", nodeInstance.getName()), null); - } - - /** - * 记录节点错误日志 - */ - protected void logNodeError(NodeInstance nodeInstance, String error) { - workflowLogService.log(nodeInstance.getWorkflowInstance(), nodeInstance.getNodeId(), - LogLevelEnum.ERROR, String.format("节点执行失败: %s", nodeInstance.getName()), error); - nodeInstance.setError(error); - } -} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/EndNodeExecutor.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/EndNodeExecutor.java deleted file mode 100644 index 51084f93..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/EndNodeExecutor.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.qqchen.deploy.backend.workflow.engine.executor.node; - -import com.qqchen.deploy.backend.system.enums.LogLevelEnum; -import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContextOperations; -import com.qqchen.deploy.backend.workflow.entity.NodeInstance; -import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; -import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum; -import com.qqchen.deploy.backend.workflow.enums.WorkflowInstanceStatusEnum; -import com.qqchen.deploy.backend.workflow.repository.IWorkflowInstanceRepository; -import jakarta.annotation.Resource; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; - -import java.time.LocalDateTime; - -/** - * 结束节点执行器 - * 负责完成工作流实例的执行 - */ -@Slf4j -@Component("endNodeExecutor") -public class EndNodeExecutor extends AbstractNodeExecutor { - - @Resource - private IWorkflowInstanceRepository workflowInstanceRepository; - - @Override - public NodeTypeEnum getNodeType() { - return NodeTypeEnum.END; - } - - @Override - public void validate(String config) { - // 结束节点不需要配置,无需验证 - } - - @Override - protected void doExecute(NodeInstance nodeInstance, WorkflowContextOperations context) { - // 完成工作流实例 - WorkflowInstance instance = nodeInstance.getWorkflowInstance(); - instance.setStatus(WorkflowInstanceStatusEnum.COMPLETED); - instance.setEndTime(LocalDateTime.now()); - workflowInstanceRepository.save(instance); - - // 记录日志 - context.log("工作流执行完成", LogLevelEnum.INFO); - } - - @Override - public void terminate(NodeInstance nodeInstance, WorkflowContextOperations context) { - // 结束节点不需要终止逻辑 - context.log("结束节点无需终止操作", LogLevelEnum.INFO); - } -} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/NodeExecutor.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/NodeExecutor.java deleted file mode 100644 index d5d05b60..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/NodeExecutor.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.qqchen.deploy.backend.workflow.engine.executor.node; - -import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContextOperations; -import com.qqchen.deploy.backend.workflow.entity.NodeInstance; -import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum; - -/** - * 节点执行器接口 - */ -public interface NodeExecutor { - - /** - * 获取支持的节点类型 - */ - NodeTypeEnum getNodeType(); - - /** - * 执行节点 - * - * @param nodeInstance 节点实例 - * @param context 工作流上下文 - */ - void execute(NodeInstance nodeInstance, WorkflowContextOperations context); - - /** - * 验证节点配置 - */ - void validate(String config); - - /** - * 终止节点执行 - * - * @param nodeInstance 节点实例 - * @param context 工作流上下文 - */ - void terminate(NodeInstance nodeInstance, WorkflowContextOperations context); -} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/ScriptNodeExecutor.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/ScriptNodeExecutor.java deleted file mode 100644 index 7f27ab4b..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/ScriptNodeExecutor.java +++ /dev/null @@ -1,196 +0,0 @@ -package com.qqchen.deploy.backend.workflow.engine.executor.node; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.qqchen.deploy.backend.system.enums.LogLevelEnum; -import com.qqchen.deploy.backend.framework.enums.ResponseCode; -import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException; -import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContextOperations; -import com.qqchen.deploy.backend.workflow.engine.executor.node.config.ScriptNodeExecutorConfig; -import com.qqchen.deploy.backend.workflow.entity.NodeInstance; -import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum; -import com.qqchen.deploy.backend.workflow.enums.ScriptLanguageEnum; -import com.qqchen.deploy.backend.workflow.service.WorkflowVariableOperations; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; -import jakarta.annotation.Resource; - -import java.io.BufferedReader; -import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.HashMap; -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等) - */ -@Slf4j -@Component -public class ScriptNodeExecutor extends AbstractNodeExecutor { - - @Resource - private ObjectMapper objectMapper; - - @Resource - private WorkflowVariableOperations variableOperations; - - @Resource - private ScriptCommandRegistry commandRegistry; - - private final ExecutorService executorService = Executors.newCachedThreadPool(); - - @Override - public NodeTypeEnum getNodeType() { - return NodeTypeEnum.SCRIPT; - } - - @Override - public void validate(String config) { - try { - ScriptNodeExecutorConfig scriptConfig = objectMapper.readValue(config, ScriptNodeExecutorConfig.class); - // 验证脚本内容 - if (scriptConfig.getScript() == null || scriptConfig.getScript().trim().isEmpty()) { - throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR, "Script content cannot be empty"); - } - // 验证脚本语言 - if (scriptConfig.getLanguage() == null) { - throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR, "Script language must be specified"); - } - // 验证其他参数 - if (scriptConfig.getTimeout() != null && scriptConfig.getTimeout() <= 0) { - throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR, "Timeout must be positive"); - } - if (scriptConfig.getRetryTimes() != null && scriptConfig.getRetryTimes() < 0) { - throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR, "Retry times cannot be negative"); - } - if (scriptConfig.getRetryInterval() != null && scriptConfig.getRetryInterval() < 0) { - throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR, "Retry interval cannot be negative"); - } - } catch (Exception e) { - throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR, e); - } - } - - @Override - protected void doExecute(NodeInstance nodeInstance, WorkflowContextOperations context) { - try { -// String configJson = nodeInstance.getConfig(); -// ScriptNodeExecutorConfig config = objectMapper.readValue(configJson, ScriptNodeExecutorConfig.class); -// -// // 设置重试次数和间隔 -// int maxAttempts = config.getRetryTimes() != null ? config.getRetryTimes() : 1; -// long retryInterval = config.getRetryInterval() != null ? config.getRetryInterval() : 0; -// -// Exception lastException = null; -// for (int attempt = 1; attempt <= maxAttempts; attempt++) { -// try { -// // 执行脚本 - ScriptNodeExecutorConfig config = new ScriptNodeExecutorConfig(); - config.setLanguage(ScriptLanguageEnum.SHELL); - config.setInterpreter("/bin/bash"); - config.setScript("ls -a"); - executeScript(config, nodeInstance, context); -// return; // 执行成功,直接返回 -// } catch (Exception e) { -// lastException = e; -// if (attempt < maxAttempts) { -// context.log(String.format("Script execution failed (attempt %d/%d), retrying in %d seconds", -// attempt, maxAttempts, retryInterval), LogLevelEnum.WARN); -// Thread.sleep(retryInterval * 1000L); -// } -// } -// } -// -// // 如果所有重试都失败,抛出最后一个异常 -// if (lastException != null) { -// throw lastException; -// } - - } catch (Exception e) { - throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED, e); - } - } - - private void executeScript(ScriptNodeExecutorConfig config, NodeInstance nodeInstance, WorkflowContextOperations context) throws Exception { - ProcessBuilder processBuilder = new ProcessBuilder(); - - // 获取命令实现并构建命令 - ScriptCommand command = commandRegistry.getCommand(config.getLanguage()); - List commandList = command.buildCommand(config); - - processBuilder.command(commandList); - - // 设置工作目录 - if (config.getWorkingDirectory() != null && !config.getWorkingDirectory().trim().isEmpty()) { - processBuilder.directory(new java.io.File(config.getWorkingDirectory())); - } - - // 设置环境变量 - if (config.getEnvironment() != null && !config.getEnvironment().isEmpty()) { - Map env = processBuilder.environment(); - env.putAll(config.getEnvironment()); - } - - Process process = processBuilder.start(); - - // 创建用于读取输出的Future - Future> outputFuture = executorService.submit(() -> readOutput(process.getInputStream())); - Future> errorFuture = executorService.submit(() -> readOutput(process.getErrorStream())); - - // 等待进程执行完成或超时 - boolean completed = true; - if (config.getTimeout() != null && config.getTimeout() > 0) { - completed = process.waitFor(config.getTimeout(), TimeUnit.SECONDS); - if (!completed) { - process.destroyForcibly(); - throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED, - String.format("Script execution timed out after %d seconds", config.getTimeout())); - } - } else { - process.waitFor(); - } - - // 获取输出结果 - List output = outputFuture.get(5, TimeUnit.SECONDS); // 给5秒时间读取输出 - List error = errorFuture.get(5, TimeUnit.SECONDS); - - // 检查退出码 - int exitCode = process.exitValue(); - if (config.getSuccessExitCode() != null && exitCode != config.getSuccessExitCode()) { - throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED, - String.format("Script execution failed with exit code: %d%nError output: %s", - exitCode, String.join("\n", error))); - } - - // 设置输出变量 - Map outputVariables = new HashMap<>(); - outputVariables.put("scriptOutput", String.join("\n", output)); - outputVariables.put("exitCode", exitCode); - variableOperations.setVariables(nodeInstance.getWorkflowInstance().getId(), outputVariables); - - // 记录执行日志 - context.log(String.format("Script executed successfully with exit code: %d", exitCode), LogLevelEnum.INFO); - } - - private List readOutput(java.io.InputStream inputStream) throws Exception { - List output = new ArrayList<>(); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { - String line; - while ((line = reader.readLine()) != null) { - output.add(line); - } - } - return output; - } - - @Override - public void terminate(NodeInstance nodeInstance, WorkflowContextOperations context) { - // TODO: 实现终止脚本进程的逻辑 - context.log("Script node termination is not implemented yet", LogLevelEnum.WARN); - } -} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/ShellNodeExecutor.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/ShellNodeExecutor.java deleted file mode 100644 index b5332af0..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/ShellNodeExecutor.java +++ /dev/null @@ -1,185 +0,0 @@ -package com.qqchen.deploy.backend.workflow.engine.executor.node; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.qqchen.deploy.backend.system.enums.LogLevelEnum; -import com.qqchen.deploy.backend.framework.enums.ResponseCode; -import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException; -import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContextOperations; -import com.qqchen.deploy.backend.workflow.engine.executor.node.config.ShellNodeExecutorConfig; -import com.qqchen.deploy.backend.workflow.entity.NodeInstance; -import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum; -import com.qqchen.deploy.backend.workflow.service.WorkflowVariableOperations; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; -import jakarta.annotation.Resource; - -import java.io.BufferedReader; -import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.*; - -/** - * Shell节点执行器 - * @deprecated 请使用 {@link ScriptNodeExecutor} 替代 - */ -@Deprecated(since = "1.0", forRemoval = true) -@Slf4j -@Component -public class ShellNodeExecutor extends AbstractNodeExecutor { - - @Resource - private ObjectMapper objectMapper; - - @Resource - private WorkflowVariableOperations variableOperations; - - private final ExecutorService executorService = Executors.newCachedThreadPool(); - - @Override - public NodeTypeEnum getNodeType() { - return NodeTypeEnum.SHELL; - } - - @Override - public void validate(String config) { - try { - ShellNodeExecutorConfig shellConfig = objectMapper.readValue(config, ShellNodeExecutorConfig.class); - // 验证执行器类型 - if (!"SHELL".equals(shellConfig.getExecutor())) { - throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR); - } - // 验证脚本内容 - if (shellConfig.getScript() == null || shellConfig.getScript().trim().isEmpty()) { - throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR); - } - // 验证其他参数 - if (shellConfig.getTimeout() != null && shellConfig.getTimeout() <= 0) { - throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR); - } - if (shellConfig.getRetryTimes() != null && shellConfig.getRetryTimes() < 0) { - throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR); - } - if (shellConfig.getRetryInterval() != null && shellConfig.getRetryInterval() < 0) { - throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR); - } - } catch (Exception e) { - throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR); - } - } - - @Override - protected void doExecute(NodeInstance nodeInstance, WorkflowContextOperations context) { - try { - String configJson = nodeInstance.getConfig(); - ShellNodeExecutorConfig config = objectMapper.readValue(configJson, ShellNodeExecutorConfig.class); - - // 验证执行器类型 - if (!"SHELL".equals(config.getExecutor())) { - throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR); - } - - // 设置重试次数和间隔 - int maxAttempts = config.getRetryTimes() != null ? config.getRetryTimes() : 1; - long retryInterval = config.getRetryInterval() != null ? config.getRetryInterval() : 0; - - Exception lastException = null; - for (int attempt = 1; attempt <= maxAttempts; attempt++) { - try { - // 执行Shell命令 - executeShellCommand(config, nodeInstance, context); - return; // 执行成功,直接返回 - } catch (Exception e) { - lastException = e; - if (attempt < maxAttempts) { - context.log(String.format("Shell execution failed (attempt %d/%d), retrying in %d seconds", attempt, maxAttempts, retryInterval), LogLevelEnum.WARN); - Thread.sleep(retryInterval * 1000L); - } - } - } - - // 如果所有重试都失败,抛出最后一个异常 - if (lastException != null) { - throw lastException; - } - - } catch (Exception e) { - throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED, e); - } - } - - private void executeShellCommand(ShellNodeExecutorConfig config, NodeInstance nodeInstance, WorkflowContextOperations context) throws Exception { - ProcessBuilder processBuilder = new ProcessBuilder(); - processBuilder.command("sh", "-c", config.getScript()); - - // 设置工作目录 - if (config.getWorkingDirectory() != null && !config.getWorkingDirectory().trim().isEmpty()) { - processBuilder.directory(new java.io.File(config.getWorkingDirectory())); - } - - // 设置环境变量 - if (config.getEnvironment() != null && !config.getEnvironment().isEmpty()) { - Map env = processBuilder.environment(); - env.putAll(config.getEnvironment()); - } - - Process process = processBuilder.start(); - - // 创建用于读取输出的Future - Future> outputFuture = executorService.submit(() -> readOutput(process.getInputStream())); - Future> errorFuture = executorService.submit(() -> readOutput(process.getErrorStream())); - - // 等待进程执行完成或超时 - boolean completed = true; - if (config.getTimeout() != null && config.getTimeout() > 0) { - completed = process.waitFor(config.getTimeout(), TimeUnit.SECONDS); - if (!completed) { - process.destroyForcibly(); - throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED, - String.format("Shell execution timed out after %d seconds", config.getTimeout())); - } - } else { - process.waitFor(); - } - - // 获取输出结果 - List output = outputFuture.get(5, TimeUnit.SECONDS); // 给5秒时间读取输出 - List error = errorFuture.get(5, TimeUnit.SECONDS); - - // 检查退出码 - int exitCode = process.exitValue(); - if (config.getSuccessExitCode() != null && exitCode != config.getSuccessExitCode()) { - throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED, - String.format("Shell execution failed with exit code: %d%nError output: %s", - exitCode, String.join("\n", error))); - } - - // 设置输出变量 - Map outputVariables = new HashMap<>(); - outputVariables.put("shellOutput", String.join("\n", output)); - outputVariables.put("exitCode", exitCode); - variableOperations.setVariables(nodeInstance.getWorkflowInstance().getId(), outputVariables); - - // 记录执行日志 - context.log(String.format("Shell script executed successfully with exit code: %d", exitCode), LogLevelEnum.INFO); - } - - private List readOutput(java.io.InputStream inputStream) throws Exception { - List output = new ArrayList<>(); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { - String line; - while ((line = reader.readLine()) != null) { - output.add(line); - } - } - return output; - } - - @Override - public void terminate(NodeInstance nodeInstance, WorkflowContextOperations context) { - // TODO: 实现终止Shell进程的逻辑 - context.log("Shell node termination is not implemented yet", LogLevelEnum.WARN); - } -} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/StartNodeExecutor.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/StartNodeExecutor.java deleted file mode 100644 index 5a15acc5..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/StartNodeExecutor.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.qqchen.deploy.backend.workflow.engine.executor.node; - -import com.qqchen.deploy.backend.system.enums.LogLevelEnum; -import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContextOperations; -import com.qqchen.deploy.backend.workflow.entity.NodeInstance; -import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; -import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; - -/** - * 开始节点执行器 - * 负责工作流实例的初始化工作 - */ -@Slf4j -@Component("startNodeExecutor") -public class StartNodeExecutor extends AbstractNodeExecutor { - - @Override - public NodeTypeEnum getNodeType() { - return NodeTypeEnum.START; - } - - @Override - public void validate(String config) { - // 开始节点不需要配置,无需验证 - } - - @Override - protected void doExecute(NodeInstance nodeInstance, WorkflowContextOperations context) { - WorkflowInstance instance = context.getInstance(); - - // 记录启动日志 - String message = String.format( - "工作流[%s]开始执行,实例ID: %d,业务键: %s", - instance.getWorkflowDefinition().getName(), - instance.getId(), - instance.getBusinessKey() - ); - context.log(message, LogLevelEnum.INFO); - - // 记录启动时间 - log.info("Workflow instance {} started at {}", instance.getId(), instance.getCreateTime()); - } - - @Override - public void terminate(NodeInstance nodeInstance, WorkflowContextOperations context) { - // 开始节点的终止意味着整个工作流的终止 - WorkflowInstance instance = nodeInstance.getWorkflowInstance(); - String message = String.format( - "工作流[%s]在启动阶段被终止,实例ID: %d", - instance.getWorkflowDefinition().getName(), - instance.getId() - ); - context.log(message, LogLevelEnum.WARN); - } -} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/TaskNodeExecutor.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/TaskNodeExecutor.java deleted file mode 100644 index 0f7649d5..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/TaskNodeExecutor.java +++ /dev/null @@ -1,115 +0,0 @@ -package com.qqchen.deploy.backend.workflow.engine.executor.node; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.qqchen.deploy.backend.system.enums.LogLevelEnum; -import com.qqchen.deploy.backend.framework.enums.ResponseCode; -import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContextOperations; -import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException; -import com.qqchen.deploy.backend.workflow.engine.executor.task.TaskConfig; -import com.qqchen.deploy.backend.workflow.entity.NodeInstance; -import com.qqchen.deploy.backend.workflow.enums.NodeStatusEnum; -import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum; -import jakarta.annotation.Resource; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; - -@Slf4j -@Component -public class TaskNodeExecutor implements NodeExecutor { - - @Resource - private ObjectMapper objectMapper; - - @Override - public NodeTypeEnum getNodeType() { - return NodeTypeEnum.TASK; - } - - @Override - public void execute(NodeInstance nodeInstance, WorkflowContextOperations context) { - try { - // 1. 解析任务配置 - TaskConfig config = parseConfig(nodeInstance.getConfig()); - - // 2. 执行具体任务 - executeTask(config, nodeInstance, context); - - // 3. 更新节点状态 - nodeInstance.setStatus(NodeStatusEnum.COMPLETED); - context.log("任务节点执行完成", LogLevelEnum.INFO); - - } catch (Exception e) { - log.error("任务节点执行失败", e); - throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED, e); - } - } - - @Override - public void validate(String config) { - try { - TaskConfig taskConfig = parseConfig(config); - // 验证必填字段 - if (taskConfig.getType() == null) { - throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR, "任务类型不能为空"); - } - } catch (Exception e) { - throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR, e); - } - } - - @Override - public void terminate(NodeInstance nodeInstance, WorkflowContextOperations context) { - // 终止任务执行 - TaskConfig config = parseConfig(nodeInstance.getConfig()); - terminateTask(config, nodeInstance, context); - } - - private TaskConfig parseConfig(String config) { - try { - return objectMapper.readValue(config, TaskConfig.class); - } catch (Exception e) { - throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR, e); - } - } - - private void executeTask(TaskConfig config, NodeInstance nodeInstance, WorkflowContextOperations context) { - switch (config.getType()) { - case HTTP: - executeHttpTask(config, nodeInstance, context); - break; - case JAVA: - executeJavaTask(config, nodeInstance, context); - break; - default: - throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_TYPE_NOT_SUPPORTED, "不支持的任务类型: " + config.getType()); - } - } - - private void terminateTask(TaskConfig config, NodeInstance nodeInstance, WorkflowContextOperations context) { - // 根据任务类型执行终止操作 - switch (config.getType()) { - case HTTP: - terminateHttpTask(config, nodeInstance, context); - break; - case JAVA: - terminateJavaTask(config, nodeInstance, context); - break; - } - } - - private void executeHttpTask(TaskConfig config, NodeInstance nodeInstance, WorkflowContextOperations context) { - // TODO: 实现HTTP请求执行 - } - - private void executeJavaTask(TaskConfig config, NodeInstance nodeInstance, WorkflowContextOperations context) { - // TODO: 实现Java方法调用 - } - - private void terminateHttpTask(TaskConfig config, NodeInstance nodeInstance, WorkflowContextOperations context) { - // TODO: 实现HTTP请求终止 - } - - private void terminateJavaTask(TaskConfig config, NodeInstance nodeInstance, WorkflowContextOperations context) { - // TODO: 实现Java方法终止 - } -} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/config/NodeExecutorConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/config/NodeExecutorConfig.java deleted file mode 100644 index d0f75675..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/config/NodeExecutorConfig.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.qqchen.deploy.backend.workflow.engine.executor.node.config; - -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum; -import lombok.Data; - -/** - * 节点配置基类 - */ -@Data -@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") -@JsonIgnoreProperties(ignoreUnknown = true) // 忽略未知字段 -@JsonSubTypes({ - @JsonSubTypes.Type(value = ApprovalNodeExecutorConfig.class, name = "APPROVAL"), - @JsonSubTypes.Type(value = ScriptNodeExecutorConfig.class, name = "SCRIPT"), - @JsonSubTypes.Type(value = ShellNodeExecutorConfig.class, name = "SHELL"), - @JsonSubTypes.Type(value = JenkinsNodeExecutorConfig.class, name = "JENKINS"), - @JsonSubTypes.Type(value = GitNodeExecutorConfig.class, name = "GIT"), - @JsonSubTypes.Type(value = ConditionNodeExecutorConfig.class, name = "CONDITION"), - @JsonSubTypes.Type(value = ParallelNodeExecutorConfig.class, name = "PARALLEL"), - @JsonSubTypes.Type(value = NacosNodeExecutorConfig.class, name = "NACOS"), - @JsonSubTypes.Type(value = HttpNodeExecutorConfig.class, name = "HTTP"), - @JsonSubTypes.Type(value = NotifyNodeExecutorConfig.class, name = "NOTIFY") -}) -public class NodeExecutorConfig { - - /** - * 节点ID - */ - private String id; - - /** - * 节点名称 - */ - private String name; - - /** - * 节点类型 - */ - private NodeTypeEnum type; - - /** - * 描述 - */ - private String description; -} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/config/ShellNodeExecutorConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/config/ShellNodeExecutorConfig.java deleted file mode 100644 index cc56171d..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/config/ShellNodeExecutorConfig.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.qqchen.deploy.backend.workflow.engine.executor.node.config; - -import lombok.Data; -import lombok.EqualsAndHashCode; - -import java.util.Map; - -/** - * Shell节点配置 - * @deprecated 请使用 {@link ScriptNodeExecutorConfig} 替代,设置 language="shell" - */ -@Deprecated(since = "1.0", forRemoval = true) -@Data -@EqualsAndHashCode(callSuper = true) -public class ShellNodeExecutorConfig extends NodeExecutorConfig { - /** - * 执行器类型,固定为 SHELL - */ - private String executor = "SHELL"; - - /** - * Shell脚本内容 - */ - private String script; - - /** - * 工作目录 - */ - private String workingDirectory; - - /** - * 超时时间(秒) - */ - private Integer timeout; - - /** - * 重试次数 - */ - private Integer retryTimes; - - /** - * 重试间隔(秒) - */ - private Integer retryInterval; - - /** - * 环境变量 - */ - private Map environment; - - /** - * 成功退出码 - */ - private Integer successExitCode = 0; -} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/task/HttpTaskExecutor.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/task/HttpTaskExecutor.java deleted file mode 100644 index 4b3eb611..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/task/HttpTaskExecutor.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.qqchen.deploy.backend.workflow.engine.executor.task; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.qqchen.deploy.backend.system.enums.LogLevelEnum; -import com.qqchen.deploy.backend.framework.enums.ResponseCode; -import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContextOperations; -import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException; -import com.qqchen.deploy.backend.workflow.entity.NodeInstance; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.*; -import org.springframework.stereotype.Component; -import org.springframework.web.client.RestTemplate; - -import java.util.Map; - -@Slf4j -@Component -public class HttpTaskExecutor implements TaskExecutor { - - private final ObjectMapper objectMapper = new ObjectMapper(); - private final RestTemplate restTemplate = new RestTemplate(); - - @Override - public void execute(NodeInstance nodeInstance, WorkflowContextOperations context, Map parameters) { - String url = (String) parameters.get("url"); - HttpMethod method = HttpMethod.valueOf((String) parameters.getOrDefault("method", "GET")); - Object body = parameters.get("body"); - Map headers = (Map) parameters.get("headers"); - - try { - HttpHeaders httpHeaders = new HttpHeaders(); - if (headers != null) { - headers.forEach(httpHeaders::add); - } - - HttpEntity requestEntity = new HttpEntity<>(body, httpHeaders); - ResponseEntity response = restTemplate.exchange(url, method, requestEntity, String.class); - - // 记录执行结果 - nodeInstance.setOutput(objectMapper.createObjectNode() - .put("statusCode", response.getStatusCode().value()) - .put("body", response.getBody()) - .toString()); - - if (!response.getStatusCode().is2xxSuccessful()) { - throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED, - "HTTP request failed with status: " + response.getStatusCode()); - } - - context.log("HTTP请求执行成功", response.getBody(), LogLevelEnum.INFO); - - } catch (Exception e) { - throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED, e); - } - } - - @Override - public void terminate(NodeInstance nodeInstance, WorkflowContextOperations context) { - // HTTP请求无需终止操作 - } -} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/task/JavaTaskExecutor.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/task/JavaTaskExecutor.java deleted file mode 100644 index b9b69392..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/task/JavaTaskExecutor.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.qqchen.deploy.backend.workflow.engine.executor.task; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.qqchen.deploy.backend.system.enums.LogLevelEnum; -import com.qqchen.deploy.backend.framework.enums.ResponseCode; -import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContextOperations; -import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException; -import com.qqchen.deploy.backend.workflow.entity.NodeInstance; -import lombok.extern.slf4j.Slf4j; -import org.springframework.context.ApplicationContext; -import org.springframework.stereotype.Component; - -import jakarta.annotation.Resource; -import java.lang.reflect.Method; -import java.util.Map; - -@Slf4j -@Component -public class JavaTaskExecutor implements TaskExecutor { - - @Resource - private ApplicationContext applicationContext; - - private final ObjectMapper objectMapper = new ObjectMapper(); - - @Override - public void execute(NodeInstance nodeInstance, WorkflowContextOperations context, Map parameters) { - String className = parameters.get("className").toString(); - String methodName = parameters.get("methodName").toString(); - - try { - Class clazz = Class.forName(className); - Object instance = applicationContext.getBean(clazz); - Method method = clazz.getMethod(methodName, NodeInstance.class, WorkflowContextOperations.class, Map.class); - method.invoke(instance, nodeInstance, context, parameters); - } catch (ClassNotFoundException e) { - throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR, "Class not found: " + className); - } catch (NoSuchMethodException e) { - throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR, "Method not found: " + methodName); - } catch (Exception e) { - throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED, e); - } - } - - @Override - public void terminate(NodeInstance nodeInstance, WorkflowContextOperations context) { - // Java任务无法中断,记录日志 - context.log("Java task cannot be terminated: " + nodeInstance.getNodeId(), LogLevelEnum.WARN); - } -} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/task/ShellTaskExecutor.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/task/ShellTaskExecutor.java deleted file mode 100644 index 96ee25f8..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/task/ShellTaskExecutor.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.qqchen.deploy.backend.workflow.engine.executor.task; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.qqchen.deploy.backend.system.enums.LogLevelEnum; -import com.qqchen.deploy.backend.framework.enums.ResponseCode; -import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContextOperations; -import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException; -import com.qqchen.deploy.backend.workflow.entity.NodeInstance; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.exec.*; -import org.springframework.stereotype.Component; - -import java.io.ByteArrayOutputStream; -import java.util.Map; -import java.util.concurrent.TimeUnit; - -@Slf4j -@Component -public class ShellTaskExecutor implements TaskExecutor { - - private final ObjectMapper objectMapper = new ObjectMapper(); - - @Override - public void execute(NodeInstance nodeInstance, WorkflowContextOperations context, Map parameters) { - String command = (String) parameters.get("command"); - Integer timeout = (Integer) parameters.getOrDefault("timeout", 300); - - CommandLine cmdLine = CommandLine.parse(command); - DefaultExecutor executor = new DefaultExecutor(); - executor.setExitValues(null); - - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - ByteArrayOutputStream errorStream = new ByteArrayOutputStream(); - executor.setStreamHandler(new PumpStreamHandler(outputStream, errorStream)); - - ExecuteWatchdog watchdog = new ExecuteWatchdog(TimeUnit.SECONDS.toMillis(timeout)); - executor.setWatchdog(watchdog); - - try { - int exitValue = executor.execute(cmdLine); - String output = outputStream.toString(); - String error = errorStream.toString(); - - // 记录执行结果 - nodeInstance.setOutput(objectMapper.createObjectNode() - .put("exitValue", exitValue) - .put("output", output) - .put("error", error) - .toString()); - - if (exitValue != 0) { - throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED, error); - } - - context.log("Shell命令执行成功", output, LogLevelEnum.INFO); - - } catch (Exception e) { - throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED, e); - } - } - - @Override - public void terminate(NodeInstance nodeInstance, WorkflowContextOperations context) { - // TODO: 实现Shell命令终止逻辑 - } -} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/task/TaskConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/task/TaskConfig.java deleted file mode 100644 index 68e9fa25..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/task/TaskConfig.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.qqchen.deploy.backend.workflow.engine.executor.task; - -import com.qqchen.deploy.backend.workflow.enums.TaskTypeEnum; -import lombok.Data; - -import java.util.Map; - -@Data -public class TaskConfig { - - /** - * 任务类型 - */ - private TaskTypeEnum type; - - /** - * 任务名称 - */ - private String name; - - /** - * 任务描述 - */ - private String description; - - /** - * 任务超时时间(秒) - */ - private Integer timeout; - - /** - * 重试次数 - */ - private Integer retryCount; - - /** - * 重试间隔(秒) - */ - private Integer retryInterval; - - /** - * 任务参数 - */ - private Map parameters; -} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/task/TaskExecutor.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/task/TaskExecutor.java deleted file mode 100644 index 8805a9d4..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/task/TaskExecutor.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.qqchen.deploy.backend.workflow.engine.executor.task; - -import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContextOperations; -import com.qqchen.deploy.backend.workflow.entity.NodeInstance; - -import java.util.Map; - -/** - * 任务执行器接口 - */ -public interface TaskExecutor { - - /** - * 执行任务 - */ - void execute(NodeInstance nodeInstance, WorkflowContextOperations context, Map parameters); - - /** - * 终止任务 - */ - void terminate(NodeInstance nodeInstance, WorkflowContextOperations context); -} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/NodeConfigDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/model/NodeConfig.java similarity index 57% rename from backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/NodeConfigDTO.java rename to backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/model/NodeConfig.java index 2125b4d2..2a7c5da6 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/NodeConfigDTO.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/model/NodeConfig.java @@ -1,4 +1,4 @@ -package com.qqchen.deploy.backend.workflow.dto; +package com.qqchen.deploy.backend.workflow.engine.model; import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum; import lombok.Data; @@ -6,15 +6,15 @@ import lombok.Data; import java.util.Map; /** - * 节点配置DTO - * 用于前端传递节点配置信息 + * 节点配置 + * 用于解析工作流定义中的节点配置JSON */ @Data -public class NodeConfigDTO { +public class NodeConfig { /** * 节点ID */ - private String id; + private String nodeId; /** * 节点类型 @@ -27,12 +27,7 @@ public class NodeConfigDTO { private String name; /** - * 节点配置 + * 节点配置,不同类型的节点有不同的配置项 */ private Map config; - - /** - * 节点描述 - */ - private String description; -} +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/model/TransitionConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/model/TransitionConfig.java new file mode 100644 index 00000000..1349b58a --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/model/TransitionConfig.java @@ -0,0 +1,35 @@ +package com.qqchen.deploy.backend.workflow.engine.model; + +import lombok.Data; + +/** + * 流转配置 + * 用于解析工作流定义中的流转配置JSON + */ +@Data +public class TransitionConfig { + /** + * 来源节点ID + */ + private String from; + + /** + * 目标节点ID + */ + private String to; + + /** + * 流转条件 + */ + private String condition; + + /** + * 流转描述 + */ + private String description; + + /** + * 优先级,数字越小优先级越高 + */ + private Integer priority; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/model/WorkflowGraph.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/model/WorkflowGraph.java new file mode 100644 index 00000000..bb3e52bd --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/model/WorkflowGraph.java @@ -0,0 +1,42 @@ +package com.qqchen.deploy.backend.workflow.engine.model; + +import com.qqchen.deploy.backend.framework.enums.ResponseCode; +import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException; +import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum; +import lombok.Builder; +import lombok.Data; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +@Data +@Builder +public class WorkflowGraph { + + private List nodes; + + private List transitions; + + public NodeConfig getStartNode() { + return nodes.stream() + .filter(node -> node.getType() == NodeTypeEnum.START) + .findFirst() + .orElseThrow(() -> new WorkflowEngineException(ResponseCode.WORKFLOW_NOT_FOUND)); + } + + public List getNextNodes(String nodeId) { + return transitions.stream() + .filter(t -> t.getFrom().equals(nodeId)) + .map(t -> findNodeById(t.getTo())) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + private NodeConfig findNodeById(String nodeId) { + return nodes.stream() + .filter(n -> n.getNodeId().equals(nodeId)) + .findFirst() + .orElse(null); + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/parser/WorkflowDefinitionParser.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/parser/WorkflowDefinitionParser.java index fd7bdbeb..9eed5a23 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/parser/WorkflowDefinitionParser.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/parser/WorkflowDefinitionParser.java @@ -1,15 +1,15 @@ package com.qqchen.deploy.backend.workflow.engine.parser; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.qqchen.deploy.backend.framework.enums.ResponseCode; import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException; -import com.qqchen.deploy.backend.workflow.entity.NodeConfig; -import com.qqchen.deploy.backend.workflow.entity.TransitionConfig; +import com.qqchen.deploy.backend.workflow.engine.model.NodeConfig; +import com.qqchen.deploy.backend.workflow.engine.model.TransitionConfig; +import com.qqchen.deploy.backend.workflow.engine.model.WorkflowGraph; +import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition; import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum; -import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @@ -20,149 +20,59 @@ import java.util.Map; /** * 工作流定义解析器 */ -@Slf4j @Component +@Slf4j public class WorkflowDefinitionParser { + private final ObjectMapper objectMapper; - @Resource - private ObjectMapper objectMapper; + public WorkflowDefinitionParser(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } - /** - * 解析节点配置 - * - * @param nodeConfig 节点配置JSON字符串 - * @return 节点配置列表 - */ - public List parseNodeConfig(String nodeConfig) { + public WorkflowGraph parse(WorkflowDefinition definition) { try { - log.debug("Parsing node config: {}", nodeConfig); - JsonNode rootNode = objectMapper.readTree(nodeConfig); - JsonNode nodesNode = rootNode.get("nodes"); - if (nodesNode == null || !nodesNode.isArray()) { - throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_INVALID); - } + // 解析节点配置 + JsonNode rootNode = objectMapper.readTree(definition.getNodeConfig()); + List nodes = parseNodes(rootNode.get("nodes")); - List nodes = new ArrayList<>(); - for (JsonNode node : nodesNode) { - 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>() {})); - } - - // 可选字段 - if (node.has("description")) { - config.setDescription(node.get("description").asText()); - } - - nodes.add(config); - log.debug("Parsed node: id={}, type={}", config.getNodeId(), config.getType()); - } - return nodes; - } catch (JsonProcessingException e) { - log.error("Failed to parse node config: {}", e.getMessage(), e); - throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_INVALID, e); + // 解析流转配置 + JsonNode transitionRoot = objectMapper.readTree(definition.getTransitionConfig()); + List transitions = parseTransitions(transitionRoot.get("transitions")); + + return WorkflowGraph.builder() + .nodes(nodes) + .transitions(transitions) + .build(); + + } catch (Exception e) { + log.error("Failed to parse workflow definition", e); + throw new WorkflowEngineException(ResponseCode.WORKFLOW_CONFIG_ERROR); } } - /** - * 解析流转配置 - * - * @param transitionConfig 流转配置JSON字符串 - * @return 流转配置列表 - */ - public List parseTransitionConfig(String transitionConfig) { - try { - log.debug("Parsing transition config: {}", transitionConfig); - JsonNode rootNode = objectMapper.readTree(transitionConfig); - JsonNode transitionsNode = rootNode.get("transitions"); - if (transitionsNode == null || !transitionsNode.isArray()) { - throw new WorkflowEngineException(ResponseCode.WORKFLOW_CONFIG_INVALID); - } - - List transitions = new ArrayList<>(); - for (JsonNode node : transitionsNode) { - 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.getFrom(), - config.getTo(), - config.getPriority()); - } - return transitions; - } catch (JsonProcessingException e) { - log.error("Failed to parse transition config: {}", e.getMessage(), e); - throw new WorkflowEngineException(ResponseCode.WORKFLOW_CONFIG_INVALID, e); + private List parseNodes(JsonNode nodesNode) { + List nodes = new ArrayList<>(); + for (JsonNode node : nodesNode) { + NodeConfig config = new NodeConfig(); + config.setNodeId(node.get("id").asText()); + config.setType(NodeTypeEnum.valueOf(node.get("type").asText())); + config.setName(node.get("name").asText()); + config.setConfig(objectMapper.convertValue(node.get("config"), new TypeReference>() {})); + nodes.add(config); } + return nodes; } - /** - * 验证节点配置的完整性 - * - * @param nodes 节点配置列表 - * @param transitions 流转配置列表 - */ - public void validateConfig(List nodes, List transitions) { - // 1. 检查是否有开始节点和结束节点 - boolean hasStart = false; - 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; - } - } - - if (!hasStart || !hasEnd) { - throw new WorkflowEngineException(ResponseCode.WORKFLOW_CONFIG_INVALID, - "工作流必须包含开始节点和结束节点"); - } - - // 2. 检查流转配置的完整性 - for (TransitionConfig transition : transitions) { - boolean sourceExists = false; - boolean targetExists = false; - - for (NodeConfig node : nodes) { - if (node.getNodeId().equals(transition.getFrom())) { - sourceExists = true; - } - if (node.getNodeId().equals(transition.getTo())) { - targetExists = true; - } - } - - if (!sourceExists || !targetExists) { - throw new WorkflowEngineException(ResponseCode.WORKFLOW_CONFIG_INVALID, - String.format("流转配置中的节点不存在: %s -> %s", - transition.getFrom(), - transition.getTo())); - } + private List parseTransitions(JsonNode transitionsNode) { + List transitions = new ArrayList<>(); + for (JsonNode transition : transitionsNode) { + TransitionConfig config = new TransitionConfig(); + config.setFrom(transition.get("from").asText()); + config.setTo(transition.get("to").asText()); + config.setCondition(transition.get("condition").asText()); + config.setPriority(transition.get("priority").asInt()); + transitions.add(config); } + return transitions; } } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/script/command/ScriptCommand.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/script/command/ScriptCommand.java similarity index 61% rename from backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/script/command/ScriptCommand.java rename to backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/script/command/ScriptCommand.java index 1611cf95..3fbcbe9b 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/script/command/ScriptCommand.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/script/command/ScriptCommand.java @@ -1,6 +1,7 @@ -package com.qqchen.deploy.backend.workflow.engine.executor.node.script.command; +package com.qqchen.deploy.backend.workflow.engine.script.command; + +import com.qqchen.deploy.backend.workflow.engine.executor.config.ScriptNodeExecutorConfig; -import com.qqchen.deploy.backend.workflow.engine.executor.node.config.ScriptNodeExecutorConfig; import java.util.List; /** diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/script/command/ScriptLanguageSupport.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/script/command/ScriptLanguageSupport.java similarity index 80% rename from backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/script/command/ScriptLanguageSupport.java rename to backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/script/command/ScriptLanguageSupport.java index 2c3a3f0b..54d52f28 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/script/command/ScriptLanguageSupport.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/script/command/ScriptLanguageSupport.java @@ -1,4 +1,4 @@ -package com.qqchen.deploy.backend.workflow.engine.executor.node.script.command; +package com.qqchen.deploy.backend.workflow.engine.script.command; import com.qqchen.deploy.backend.workflow.enums.ScriptLanguageEnum; import java.lang.annotation.*; diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/script/command/impl/PythonScriptCommand.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/script/command/impl/PythonScriptCommand.java similarity index 71% rename from backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/script/command/impl/PythonScriptCommand.java rename to backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/script/command/impl/PythonScriptCommand.java index f8366099..9c7d22c6 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/script/command/impl/PythonScriptCommand.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/script/command/impl/PythonScriptCommand.java @@ -1,10 +1,10 @@ -package com.qqchen.deploy.backend.workflow.engine.executor.node.script.command.impl; +package com.qqchen.deploy.backend.workflow.engine.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.ScriptNodeExecutorConfig; -import com.qqchen.deploy.backend.workflow.engine.executor.node.script.command.ScriptCommand; -import com.qqchen.deploy.backend.workflow.engine.executor.node.script.command.ScriptLanguageSupport; +import com.qqchen.deploy.backend.workflow.engine.executor.config.ScriptNodeExecutorConfig; +import com.qqchen.deploy.backend.workflow.engine.script.command.ScriptCommand; +import com.qqchen.deploy.backend.workflow.engine.script.command.ScriptLanguageSupport; import com.qqchen.deploy.backend.workflow.enums.ScriptLanguageEnum; import org.springframework.stereotype.Component; import java.util.Arrays; diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/script/command/impl/ShellScriptCommand.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/script/command/impl/ShellScriptCommand.java similarity index 71% rename from backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/script/command/impl/ShellScriptCommand.java rename to backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/script/command/impl/ShellScriptCommand.java index b569311f..071ff003 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/script/command/impl/ShellScriptCommand.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/script/command/impl/ShellScriptCommand.java @@ -1,10 +1,10 @@ -package com.qqchen.deploy.backend.workflow.engine.executor.node.script.command.impl; +package com.qqchen.deploy.backend.workflow.engine.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.ScriptNodeExecutorConfig; -import com.qqchen.deploy.backend.workflow.engine.executor.node.script.command.ScriptCommand; -import com.qqchen.deploy.backend.workflow.engine.executor.node.script.command.ScriptLanguageSupport; +import com.qqchen.deploy.backend.workflow.engine.executor.config.ScriptNodeExecutorConfig; +import com.qqchen.deploy.backend.workflow.engine.script.command.ScriptCommand; +import com.qqchen.deploy.backend.workflow.engine.script.command.ScriptLanguageSupport; import com.qqchen.deploy.backend.workflow.enums.ScriptLanguageEnum; import org.springframework.stereotype.Component; import java.util.Arrays; diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/script/registry/ScriptCommandRegistry.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/script/registry/ScriptCommandRegistry.java similarity index 86% rename from backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/script/registry/ScriptCommandRegistry.java rename to backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/script/registry/ScriptCommandRegistry.java index f68fc23f..40cf9f60 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/node/script/registry/ScriptCommandRegistry.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/script/registry/ScriptCommandRegistry.java @@ -1,9 +1,9 @@ -package com.qqchen.deploy.backend.workflow.engine.executor.node.script.registry; +package com.qqchen.deploy.backend.workflow.engine.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.engine.script.command.ScriptCommand; +import com.qqchen.deploy.backend.workflow.engine.script.command.ScriptLanguageSupport; import com.qqchen.deploy.backend.workflow.enums.ScriptLanguageEnum; import jakarta.annotation.PostConstruct; import org.springframework.beans.factory.annotation.Autowired; diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/transition/TransitionExecutor.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/transition/TransitionExecutor.java deleted file mode 100644 index c634d890..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/transition/TransitionExecutor.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.qqchen.deploy.backend.workflow.engine.transition; - -import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContextOperations; -import com.qqchen.deploy.backend.workflow.entity.NodeInstance; -import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition; -import com.qqchen.deploy.backend.workflow.repository.INodeInstanceRepository; -import jakarta.annotation.Resource; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; - -import java.time.LocalDateTime; -import java.util.List; - -@Slf4j -@Component -public class TransitionExecutor { - - @Resource - private TransitionRuleEngine transitionRuleEngine; - - @Resource - private INodeInstanceRepository nodeInstanceRepository; - - /** - * 执行节点流转 - */ - public void executeTransition(NodeInstance currentNode, WorkflowDefinition definition, WorkflowContextOperations context) { - // 1. 获取下一个节点ID列表 - List nextNodeIds = transitionRuleEngine.getNextNodeIds(currentNode, definition, context); - - //这里应该从WorkflowDefinition类的transitionConfig获取 - } -} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/transition/TransitionRule.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/transition/TransitionRule.java deleted file mode 100644 index 17135644..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/transition/TransitionRule.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.qqchen.deploy.backend.workflow.engine.transition; - -import lombok.Data; -import java.util.List; - -@Data -public class TransitionRule { - - /** - * 源节点ID - */ - private String from; - - /** - * 目标节点ID - */ - private String to; - - /** - * 条件表达式 - */ - private String condition; - - /** - * 优先级 - */ - private Integer priority; -} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/transition/TransitionRuleEngine.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/transition/TransitionRuleEngine.java deleted file mode 100644 index 5682e907..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/transition/TransitionRuleEngine.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.qqchen.deploy.backend.workflow.engine.transition; - -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.qqchen.deploy.backend.framework.enums.ResponseCode; -import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContextOperations; -import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException; -import com.qqchen.deploy.backend.workflow.entity.NodeInstance; -import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition; -import lombok.extern.slf4j.Slf4j; -import org.springframework.expression.Expression; -import org.springframework.expression.ExpressionParser; -import org.springframework.expression.spel.standard.SpelExpressionParser; -import org.springframework.expression.spel.support.StandardEvaluationContext; -import org.springframework.stereotype.Component; - -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; - -@Slf4j -@Component -public class TransitionRuleEngine { - - private final ObjectMapper objectMapper = new ObjectMapper(); - private final ExpressionParser expressionParser = new SpelExpressionParser(); - - /** - * 获取下一个节点ID列表 - */ - public List getNextNodeIds(NodeInstance currentNode, WorkflowDefinition definition, WorkflowContextOperations context) { - try { - // 解析流转规则 - List rules = parseTransitionRules(definition.getTransitionConfig()); - - // 过滤当前节点的规则并按优先级排序 - List nodeRules = rules.stream() - .filter(rule -> rule.getFrom().equals(currentNode.getNodeId())) - .sorted(Comparator.comparing(TransitionRule::getPriority)) - .toList(); - - // 执行规则匹配 - List nextNodeIds = new ArrayList<>(); - for (TransitionRule rule : nodeRules) { - if (evaluateCondition(rule.getCondition(), context)) { - nextNodeIds.add(rule.getTo()); - } - } - - return nextNodeIds; - - } catch (Exception e) { - throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED, e); - } - } - - private List parseTransitionRules(String config) { - try { - return objectMapper.readValue(config, new TypeReference>() {}); - } catch (Exception e) { - throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR, e); - } - } - - private boolean evaluateCondition(String condition, WorkflowContextOperations context) { - if (condition == null || condition.trim().isEmpty()) { - return true; - } - - try { - Expression expression = expressionParser.parseExpression(condition); - StandardEvaluationContext evaluationContext = new StandardEvaluationContext(); - evaluationContext.setVariables(context.getVariables()); - return expression.getValue(evaluationContext, Boolean.class); - } catch (Exception e) { - throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR, e); - } - } -} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/NodeConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/NodeConfig.java deleted file mode 100644 index 17c31487..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/NodeConfig.java +++ /dev/null @@ -1,126 +0,0 @@ -package com.qqchen.deploy.backend.workflow.entity; - -import com.qqchen.deploy.backend.framework.domain.Entity; -import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum; -import jakarta.persistence.Column; -import jakarta.persistence.Table; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import lombok.Data; -import lombok.EqualsAndHashCode; -import org.hibernate.annotations.JdbcTypeCode; -import org.hibernate.type.SqlTypes; - -import java.util.Map; - -/** - * 节点配置 - * 用于定义工作流中的节点,包括节点的基本信息和特定配置 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@jakarta.persistence.Entity -@Table(name = "wf_node_config") -public class NodeConfig extends Entity { - /** - * 节点ID,在同一个工作流中必须唯一 - */ - @NotBlank(message = "节点ID不能为空") - @Column(nullable = false) - private String nodeId; - - /** - * 节点名称,用于显示 - */ - @NotBlank(message = "节点名称不能为空") - @Column(nullable = false) - private String name; - - /** - * 节点类型,决定了节点的行为 - * START: 开始节点,每个工作流必须有且只有一个 - * END: 结束节点,每个工作流必须至少有一个 - * TASK: 任务节点,执行具体的任务 - * GATEWAY: 网关节点,控制流程的分支和合并 - */ - @NotNull(message = "节点类型不能为空") - @Column(nullable = false) - private NodeTypeEnum type; - - /** - * 所属工作流定义ID - */ - @NotNull(message = "工作流定义ID不能为空") - @Column(name = "workflow_definition_id", nullable = false) - private Long workflowDefinitionId; - - /** - * 节点配置,不同类型的节点有不同的配置 - * TASK节点: - * - type: SHELL/HTTP/JAVA - * - config: 具体的任务配置 - * GATEWAY节点: - * - type: EXCLUSIVE/PARALLEL/INCLUSIVE - * - conditions: 分支条件配置 - */ - @JdbcTypeCode(SqlTypes.JSON) - @Column(columnDefinition = "json") - private Map config; - - /** - * 节点描述,用于说明节点的用途 - */ - @Column(columnDefinition = "text") - private String description; - - /** - * 检查节点配置是否有效 - * - * @return true if valid, false otherwise - */ - public boolean isValid() { - if (nodeId == null || nodeId.trim().isEmpty()) { - return false; - } - if (name == null || name.trim().isEmpty()) { - return false; - } - if (type == null) { - return false; - } - - // 检查特定类型节点的配置 - if (type == NodeTypeEnum.TASK && (config == null || !config.containsKey("type"))) { - return false; - } - if (type == NodeTypeEnum.GATEWAY && (config == null || !config.containsKey("type"))) { - return false; - } - - return true; - } - - /** - * 获取任务类型(仅对TASK类型节点有效) - * - * @return 任务类型 - */ - public String getTaskType() { - if (type != NodeTypeEnum.TASK || config == null) { - return null; - } - return (String) config.get("type"); - } - - /** - * 获取网关类型(仅对GATEWAY类型节点有效) - * - * @return 网关类型 - */ - public String getGatewayType() { - if (type != NodeTypeEnum.GATEWAY || config == null) { - return null; - } - return (String) config.get("type"); - } -} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/NodeInstance.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/NodeInstance.java index 82e3ea4e..6fe2d51a 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/NodeInstance.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/NodeInstance.java @@ -6,7 +6,7 @@ import com.qqchen.deploy.backend.framework.domain.Entity; import com.qqchen.deploy.backend.framework.enums.ResponseCode; import com.qqchen.deploy.backend.framework.utils.SpringUtils; import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException; -import com.qqchen.deploy.backend.workflow.engine.executor.node.config.NodeExecutorConfig; +import com.qqchen.deploy.backend.workflow.engine.executor.config.NodeExecutorConfig; import com.qqchen.deploy.backend.workflow.enums.NodeStatusEnum; import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum; import jakarta.persistence.*; diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/TransitionConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/TransitionConfig.java deleted file mode 100644 index 11946af0..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/TransitionConfig.java +++ /dev/null @@ -1,106 +0,0 @@ -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; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import lombok.Data; -import lombok.EqualsAndHashCode; - -/** - * 流转配置 - * 用于定义工作流中节点之间的连接关系和流转条件 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@jakarta.persistence.Entity -@Table(name = "wf_transition_config") -public class TransitionConfig extends Entity { - /** - * 源节点ID - */ - @NotBlank(message = "源节点ID不能为空") - @Column(name = "`from`", nullable = false) - private String from; - - /** - * 目标节点ID - */ - @NotBlank(message = "目标节点ID不能为空") - @Column(name = "`to`", nullable = false) - private String to; - - /** - * 流转条件,使用SpEL表达式 - * 为空表示无条件流转 - * 示例: - * - "${status == 'SUCCESS'}" - * - "${amount > 1000}" - * - "${result.code == 200 && result.data != null}" - */ - @Column(name = "`condition`", columnDefinition = "text") - private String condition; - - /** - * 优先级,数字越小优先级越高 - * 用于控制多个出向流转的执行顺序 - * 默认为0 - */ - @NotNull(message = "优先级不能为空") - @Column(nullable = false) - private Integer priority = 0; - - /** - * 所属工作流定义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 (from == null || from.trim().isEmpty()) { - return false; - } - if (to == null || to.trim().isEmpty()) { - return false; - } - if (from.equals(to)) { - return false; // 不允许自循环 - } - if (workflowDefinitionId == null) { - return false; - } - return true; - } - - /** - * 是否是条件流转 - * - * @return true if conditional, false otherwise - */ - public boolean isConditional() { - return condition != null && !condition.trim().isEmpty(); - } - - /** - * 获取优先级,如果未设置则返回默认值0 - * - * @return 优先级值 - */ - public int getPriorityValue() { - return priority != null ? priority : 0; - } -} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/monitor/WorkflowContextMonitor.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/monitor/WorkflowContextMonitor.java deleted file mode 100644 index 65682897..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/monitor/WorkflowContextMonitor.java +++ /dev/null @@ -1,149 +0,0 @@ -package com.qqchen.deploy.backend.workflow.monitor; - -import io.micrometer.core.instrument.Counter; -import io.micrometer.core.instrument.Gauge; -import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.core.instrument.Timer; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; - -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.DoubleAdder; - -/** - * 工作流上下文监控 - * 负责收集和记录工作流变量操作的各项指标 - */ -@Slf4j -@Component -public class WorkflowContextMonitor { - - private final MeterRegistry meterRegistry; - private final Counter variableSetCounter; - private final Counter variableGetCounter; - private final Counter cacheHitCounter; - private final Counter cacheMissCounter; - private final Timer variableOperationTimer; - private final Timer transactionTimer; - private final Gauge contextCacheSize; - private final DoubleAdder cacheSize = new DoubleAdder(); - - public WorkflowContextMonitor(MeterRegistry registry) { - this.meterRegistry = registry; - // 变量操作计数器 - this.variableSetCounter = Counter.builder("workflow.variable.operations") - .tag("type", "set") - .description("Number of variable set operations") - .register(registry); - - this.variableGetCounter = Counter.builder("workflow.variable.operations") - .tag("type", "get") - .description("Number of variable get operations") - .register(registry); - - // 缓存命中计数器 - this.cacheHitCounter = Counter.builder("workflow.context.cache") - .tag("result", "hit") - .description("Number of context cache hits") - .register(registry); - - this.cacheMissCounter = Counter.builder("workflow.context.cache") - .tag("result", "miss") - .description("Number of context cache misses") - .register(registry); - - // 操作耗时计时器 - this.variableOperationTimer = Timer.builder("workflow.variable.operation.duration") - .description("Time taken for variable operations") - .publishPercentiles(0.5, 0.95, 0.99) - .register(registry); - - this.transactionTimer = Timer.builder("workflow.variable.transaction.duration") - .description("Time taken for variable transactions") - .publishPercentiles(0.5, 0.95, 0.99) - .register(registry); - - // 缓存大小测量 - this.contextCacheSize = Gauge.builder("workflow.context.cache.size", - cacheSize::doubleValue) // 使用 DoubleAdder 来存储和获取值 - .description("Current size of workflow context cache") - .register(registry); - } - - /** - * 记录变量设置操作 - */ - public void recordVariableSet() { - variableSetCounter.increment(); - } - - /** - * 记录变量获取操作 - */ - public void recordVariableGet() { - variableGetCounter.increment(); - } - - /** - * 记录缓存命中 - */ - public void recordCacheHit() { - cacheHitCounter.increment(); - } - - /** - * 记录缓存未命中 - */ - public void recordCacheMiss() { - cacheMissCounter.increment(); - } - - /** - * 记录操作耗时 - */ - public Timer.Sample startOperation() { - return Timer.start(); - } - - /** - * 停止操作计时 - */ - public void stopOperation(Timer.Sample sample) { - sample.stop(variableOperationTimer); - } - - /** - * 记录事务耗时 - */ - public Timer.Sample startTransaction() { - return Timer.start(); - } - - /** - * 停止事务计时 - */ - public void stopTransaction(Timer.Sample sample) { - sample.stop(transactionTimer); - } - - /** - * 更新缓存大小 - */ - public void updateCacheSize(long size) { - cacheSize.reset(); - cacheSize.add(size); - } - - /** - * 记录错误 - */ - public void recordError(String operation, Exception e) { - log.error("Workflow variable operation error. Operation: {}, Error: {}", - operation, e.getMessage(), e); - Counter.builder("workflow.variable.errors") - .tag("operation", operation) - .tag("error", e.getClass().getSimpleName()) - .register(meterRegistry) - .increment(); - } -} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/INodeConfigRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/INodeConfigRepository.java deleted file mode 100644 index acbb9774..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/INodeConfigRepository.java +++ /dev/null @@ -1,26 +0,0 @@ -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 { - - /** - * 根据工作流定义ID查找所有节点配置 - * - * @param workflowDefinitionId 工作流定义ID - * @return 节点配置列表 - */ - List findByWorkflowDefinitionId(Long workflowDefinitionId); - - /** - * 根据工作流定义ID删除所有节点配置 - * - * @param workflowDefinitionId 工作流定义ID - */ - void deleteByWorkflowDefinitionId(Long workflowDefinitionId); -} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/ITransitionConfigRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/ITransitionConfigRepository.java deleted file mode 100644 index 48bb5aac..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/ITransitionConfigRepository.java +++ /dev/null @@ -1,26 +0,0 @@ -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 { - - /** - * 根据工作流定义ID查找所有流转配置 - * - * @param workflowDefinitionId 工作流定义ID - * @return 流转配置列表 - */ - List findByWorkflowDefinitionId(Long workflowDefinitionId); - - /** - * 根据工作流定义ID删除所有流转配置 - * - * @param workflowDefinitionId 工作流定义ID - */ - void deleteByWorkflowDefinitionId(Long workflowDefinitionId); -} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/INodeTypeService.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/INodeTypeService.java index c2e1b9af..91a274a5 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/INodeTypeService.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/INodeTypeService.java @@ -2,11 +2,8 @@ package com.qqchen.deploy.backend.workflow.service; import com.qqchen.deploy.backend.framework.service.IBaseService; import com.qqchen.deploy.backend.workflow.dto.NodeTypeDTO; -import com.qqchen.deploy.backend.workflow.engine.definition.TaskExecutorDefinition; import com.qqchen.deploy.backend.workflow.entity.NodeType; -import java.util.List; - /** * 节点类型服务接口 */ @@ -20,22 +17,6 @@ public interface INodeTypeService extends IBaseService getExecutors(String code); /** * 启用节点类型 diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowEngineService.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowEngineService.java new file mode 100644 index 00000000..37b1e553 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowEngineService.java @@ -0,0 +1,16 @@ +package com.qqchen.deploy.backend.workflow.service; + +import com.qqchen.deploy.backend.workflow.dto.request.WorkflowStartRequest; +import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; + +/** + * 工作流执行引擎接口 + */ +public interface IWorkflowEngineService { + + /** + * 启动工作流实例 + */ + WorkflowInstance startWorkflow(WorkflowStartRequest request); + +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/WorkflowVariableOperations.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/WorkflowVariableOperations.java deleted file mode 100644 index b4ce69c1..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/WorkflowVariableOperations.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.qqchen.deploy.backend.workflow.service; - -import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; - -import java.util.Map; - -/** - * 工作流变量操作接口 - */ -public interface WorkflowVariableOperations { - - /** - * 获取工作流实例的所有变量 - * - * @param workflowInstanceId 工作流实例ID - * @return 变量Map - */ - Map getVariables(Long workflowInstanceId); - - /** - * 设置工作流实例的变量 - * - * @param workflowInstanceId 工作流实例ID - * @param variables 变量Map - */ - void setVariables(Long workflowInstanceId, Map variables); - - /** - * 获取工作流实例的指定变量 - * - * @param workflowInstanceId 工作流实例ID - * @param key 变量键 - * @return 变量值 - */ - Object getVariable(Long workflowInstanceId, String key); - - /** - * 设置工作流实例的指定变量 - * - * @param workflowInstanceId 工作流实例ID - * @param key 变量键 - * @param value 变量值 - */ - void setVariable(Long workflowInstanceId, String key, Object value); - - /** - * 清除实例的所有变量 - * - * @param workflowInstanceId 工作流实例ID - */ - void clearVariables(Long workflowInstanceId); -} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/ConcurrentWorkflowVariableOperations.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/ConcurrentWorkflowVariableOperations.java deleted file mode 100644 index 0434a95f..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/ConcurrentWorkflowVariableOperations.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.qqchen.deploy.backend.workflow.service.impl; - -import com.github.benmanes.caffeine.cache.Cache; -import com.github.benmanes.caffeine.cache.Caffeine; -import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; -import com.qqchen.deploy.backend.workflow.service.IWorkflowVariableService; -import com.qqchen.deploy.backend.workflow.service.WorkflowVariableOperations; -import jakarta.annotation.Resource; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.transaction.support.TransactionTemplate; - -import java.util.Map; -import java.util.concurrent.TimeUnit; - -/** - * 并发安全的工作流变量操作实现 - */ -@Slf4j -@Service -public class ConcurrentWorkflowVariableOperations implements WorkflowVariableOperations { - - @Resource - private IWorkflowVariableService variableService; - - private final Cache> variableCache; - - public ConcurrentWorkflowVariableOperations() { - this.variableCache = Caffeine.newBuilder() - .expireAfterWrite(30, TimeUnit.MINUTES) - .maximumSize(10000) - .build(); - } - - @Override - public Map getVariables(Long workflowInstanceId) { - return variableCache.get(workflowInstanceId, - id -> variableService.getVariables(id)); - } - - @Override - public void setVariables(Long workflowInstanceId, Map variables) { - if (variables == null || variables.isEmpty()) { - return; - } - variableService.setVariables(workflowInstanceId, variables); - variableCache.put(workflowInstanceId, variables); - } - - @Override - public Object getVariable(Long workflowInstanceId, String key) { - Map variables = getVariables(workflowInstanceId); - return variables != null ? variables.get(key) : null; - } - - @Override - public void setVariable(Long workflowInstanceId, String key, Object value) { - Map variables = getVariables(workflowInstanceId); - if (variables != null) { - variables.put(key, value); - setVariables(workflowInstanceId, variables); - } - } - - @Override - public void clearVariables(Long workflowInstanceId) { - variableService.clearVariables(workflowInstanceId); - variableCache.invalidate(workflowInstanceId); - } -} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/NodeTypeServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/NodeTypeServiceImpl.java index 03a99789..cb46209f 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/NodeTypeServiceImpl.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/NodeTypeServiceImpl.java @@ -6,18 +6,13 @@ import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl; import com.qqchen.deploy.backend.workflow.converter.JsonConverter; import com.qqchen.deploy.backend.workflow.converter.NodeTypeConverter; import com.qqchen.deploy.backend.workflow.dto.NodeTypeDTO; -import com.qqchen.deploy.backend.workflow.engine.definition.TaskExecutorDefinition; import com.qqchen.deploy.backend.workflow.entity.NodeType; -import com.qqchen.deploy.backend.workflow.enums.NodeCategoryEnum; import com.qqchen.deploy.backend.workflow.repository.INodeTypeRepository; import com.qqchen.deploy.backend.workflow.service.INodeTypeService; import jakarta.annotation.Resource; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.Collections; -import java.util.List; - /** * 节点类型服务实现类 */ @@ -40,27 +35,6 @@ public class NodeTypeServiceImpl extends BaseServiceImpl new BusinessException(ResponseCode.WORKFLOW_NODE_TYPE_NOT_FOUND)); } - @Override - public List getExecutors(String code) { - // 1. 查询节点类型 - NodeType nodeType = nodeTypeRepository.findByCodeAndDeletedFalse(code) - .orElseThrow(() -> new BusinessException(ResponseCode.WORKFLOW_NODE_TYPE_NOT_FOUND)); - - // 2. 检查节点类型是否为任务节点 - if (!NodeCategoryEnum.TASK.equals(nodeType.getCategory())) { - // 非任务节点没有执行器列表 - return Collections.emptyList(); - } - - // 3. 检查节点类型是否启用 - if (!nodeType.getEnabled()) { - throw new BusinessException(ResponseCode.WORKFLOW_NODE_TYPE_DISABLED); - } - - // 4. 将JSON字符串转换为执行器列表 - return jsonConverter.toExecutorList(nodeType.getExecutors()); - } - @Override @Transactional public void enable(Long id) { diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowEngineServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowEngineServiceImpl.java new file mode 100644 index 00000000..5f03bbff --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowEngineServiceImpl.java @@ -0,0 +1,84 @@ +package com.qqchen.deploy.backend.workflow.service.impl; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.qqchen.deploy.backend.framework.enums.ResponseCode; +import com.qqchen.deploy.backend.workflow.dto.request.WorkflowStartRequest; +import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException; +import com.qqchen.deploy.backend.workflow.engine.executor.INodeExecutor; +import com.qqchen.deploy.backend.workflow.engine.model.NodeConfig; +import com.qqchen.deploy.backend.workflow.engine.model.WorkflowGraph; +import com.qqchen.deploy.backend.workflow.engine.parser.WorkflowDefinitionParser; +import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition; +import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; +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.INodeInstanceRepository; +import com.qqchen.deploy.backend.workflow.repository.IWorkflowDefinitionRepository; +import com.qqchen.deploy.backend.workflow.repository.IWorkflowInstanceRepository; +import com.qqchen.deploy.backend.workflow.service.IWorkflowEngineService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.Map; + +@Slf4j +@Component +public class WorkflowEngineServiceImpl implements IWorkflowEngineService { + + @Resource + private IWorkflowDefinitionRepository workflowDefinitionRepository; + + @Resource + private IWorkflowInstanceRepository workflowInstanceRepository; + + @Resource + private INodeInstanceRepository nodeInstanceRepository; + + @Resource + @Lazy + private Map nodeExecutors; + + @Resource + private ObjectMapper objectMapper; + + @Resource + private WorkflowDefinitionParser workflowDefinitionParser; + + @Override + @Transactional + public WorkflowInstance startWorkflow(WorkflowStartRequest request) { + // 1. 获取工作流定义 + WorkflowDefinition definition = workflowDefinitionRepository.findByCodeAndDeletedFalse(request.getWorkflowCode()); + if (definition == null) { + throw new WorkflowEngineException(ResponseCode.WORKFLOW_NOT_FOUND); + } + + // 2. 检查工作流定义状态 + if (definition.getStatus() != WorkflowDefinitionStatusEnum.PUBLISHED) { + throw new WorkflowEngineException(ResponseCode.WORKFLOW_NOT_PUBLISHED); + } + + // 3. 创建工作流实例 + WorkflowInstance workflowInstance = new WorkflowInstance(); + workflowInstance.setWorkflowDefinition(definition); + workflowInstance.setBusinessKey(request.getBusinessKey()); + workflowInstance.setStatus(WorkflowInstanceStatusEnum.RUNNING); + workflowInstance.setCreateTime(LocalDateTime.now()); + workflowInstanceRepository.save(workflowInstance); + + // 5. 解析工作流配置 + WorkflowGraph graph = workflowDefinitionParser.parse(definition); + + // 6. 获取并执行开始节点 + NodeConfig startNode = graph.getStartNode(); + INodeExecutor executor = nodeExecutors.get(startNode.getType()); + executor.execute(workflowInstance, graph, startNode); + return workflowInstance; + } + +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowInstanceServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowInstanceServiceImpl.java index 82b030f3..fb5f9792 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowInstanceServiceImpl.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowInstanceServiceImpl.java @@ -11,7 +11,6 @@ import com.qqchen.deploy.backend.workflow.enums.WorkflowDefinitionStatusEnum; import com.qqchen.deploy.backend.workflow.repository.IWorkflowDefinitionRepository; import com.qqchen.deploy.backend.workflow.repository.IWorkflowInstanceRepository; import com.qqchen.deploy.backend.workflow.service.IWorkflowInstanceService; -import com.qqchen.deploy.backend.workflow.service.WorkflowVariableOperations; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -33,8 +32,6 @@ public class WorkflowInstanceServiceImpl extends BaseServiceImpl externalSystemService.validateUniqueConstraints(systemDTO)) - .isInstanceOf(UniqueConstraintException.class) - .hasFieldOrPropertyWithValue("errorCode", ResponseCode.EXTERNAL_SYSTEM_NAME_EXISTS); - } - - @Test - void validateUniqueConstraints_WhenTypeAndUrlExists_ShouldThrowException() { - // Mock - when(externalSystemRepository.existsByNameAndDeletedFalse(systemDTO.getName())).thenReturn(false); - when(externalSystemRepository.existsByTypeAndUrlAndDeletedFalse(systemDTO.getType(), systemDTO.getUrl())) - .thenReturn(true); - - // 验证 - assertThatThrownBy(() -> externalSystemService.validateUniqueConstraints(systemDTO)) - .isInstanceOf(UniqueConstraintException.class) - .hasFieldOrPropertyWithValue("errorCode", ResponseCode.EXTERNAL_SYSTEM_TYPE_URL_EXISTS); - } - - @Test - void testConnection_WhenSystemDisabled_ShouldThrowException() { - // 准备数据 - system.setEnabled(false); - when(externalSystemRepository.findById(1L)).thenReturn(Optional.of(system)); - - // 验证 - assertThatThrownBy(() -> externalSystemService.testConnection(1L)) - .isInstanceOf(BusinessException.class) - .hasFieldOrPropertyWithValue("errorCode", ResponseCode.EXTERNAL_SYSTEM_DISABLED); - } - - @Test - void testConnection_WhenSystemEnabled_ShouldReturnTrue() { - // Mock - when(externalSystemRepository.findById(1L)).thenReturn(Optional.of(system)); - - // 执行 - boolean result = externalSystemService.testConnection(1L); - - // 验证 - assertThat(result).isTrue(); - } - - @Test - void syncData_WhenSystemDisabled_ShouldThrowException() { - // 准备数据 - system.setEnabled(false); - when(externalSystemRepository.findById(1L)).thenReturn(Optional.of(system)); - - // 验证 - assertThatThrownBy(() -> externalSystemService.syncData(1L)) - .isInstanceOf(BusinessException.class) - .hasFieldOrPropertyWithValue("errorCode", ResponseCode.EXTERNAL_SYSTEM_DISABLED); - } - - @Test - void syncData_WhenSuccessful_ShouldUpdateStatus() { - // Mock - when(externalSystemRepository.findById(1L)).thenReturn(Optional.of(system)); - when(externalSystemRepository.save(any(ExternalSystem.class))).thenReturn(system); - - // 执行 - externalSystemService.syncData(1L); - - // 验证 - assertThat(system.getSyncStatus()).isEqualTo(ExternalSystemSyncStatusEnum.SUCCESS); - assertThat(system.getLastSyncTime()).isNotNull(); - verify(externalSystemRepository, times(2)).save(any(ExternalSystem.class)); - } - - @Test - void updateStatus_ShouldUpdateSystemStatus() { - // Mock - when(externalSystemRepository.findById(1L)).thenReturn(Optional.of(system)); - when(externalSystemRepository.save(any(ExternalSystem.class))).thenReturn(system); - - // 执行 - externalSystemService.updateStatus(1L, false); - - // 验证 - assertThat(system.getEnabled()).isFalse(); - verify(externalSystemRepository).save(system); - } - - @Test - void validateUniqueConstraints_WhenGitWithoutToken_ShouldThrowException() { - // 准备数据 - systemDTO.setType(ExternalSystemTypeEnum.GIT); - systemDTO.setAuthType(ExternalSystemAuthTypeEnum.TOKEN); - systemDTO.setToken(null); - - // Mock - when(externalSystemRepository.existsByNameAndDeletedFalse(systemDTO.getName())).thenReturn(false); - when(externalSystemRepository.existsByTypeAndUrlAndDeletedFalse(systemDTO.getType(), systemDTO.getUrl())) - .thenReturn(false); - - // 验证 - assertThatThrownBy(() -> externalSystemService.validateUniqueConstraints(systemDTO)) - .isInstanceOf(BusinessException.class) - .hasFieldOrPropertyWithValue("errorCode", ResponseCode.EXTERNAL_SYSTEM_GIT_TOKEN_REQUIRED); - } - - @Test - void validateUniqueConstraints_WhenGitWithWrongAuthType_ShouldThrowException() { - // 准备数据 - systemDTO.setType(ExternalSystemTypeEnum.GIT); - systemDTO.setAuthType(ExternalSystemAuthTypeEnum.BASIC); - - // Mock - when(externalSystemRepository.existsByNameAndDeletedFalse(systemDTO.getName())).thenReturn(false); - when(externalSystemRepository.existsByTypeAndUrlAndDeletedFalse(systemDTO.getType(), systemDTO.getUrl())) - .thenReturn(false); - - // 验证 - assertThatThrownBy(() -> externalSystemService.validateUniqueConstraints(systemDTO)) - .isInstanceOf(BusinessException.class) - .hasFieldOrPropertyWithValue("errorCode", ResponseCode.EXTERNAL_SYSTEM_GIT_AUTH_TYPE_ERROR); - } - - @Test - void update_WhenGitWithoutToken_ShouldThrowException() { - // 准备数据 - systemDTO.setType(ExternalSystemTypeEnum.GIT); - systemDTO.setAuthType(ExternalSystemAuthTypeEnum.TOKEN); - systemDTO.setToken(null); - - // 验证 - assertThatThrownBy(() -> externalSystemService.update(1L, systemDTO)) - .isInstanceOf(BusinessException.class) - .hasFieldOrPropertyWithValue("errorCode", ResponseCode.EXTERNAL_SYSTEM_GIT_TOKEN_REQUIRED); - } - - @Test - void update_WhenGitWithWrongAuthType_ShouldThrowException() { - // 准备数据 - systemDTO.setType(ExternalSystemTypeEnum.GIT); - systemDTO.setAuthType(ExternalSystemAuthTypeEnum.BASIC); - - // 验证 - assertThatThrownBy(() -> externalSystemService.update(1L, systemDTO)) - .isInstanceOf(BusinessException.class) - .hasFieldOrPropertyWithValue("errorCode", ResponseCode.EXTERNAL_SYSTEM_GIT_AUTH_TYPE_ERROR); - } - - @Test - void testConnection_WhenSuccess_ShouldUpdateLastConnectTime() { - // Mock - when(externalSystemRepository.findById(1L)).thenReturn(Optional.of(system)); - when(externalSystemRepository.save(any(ExternalSystem.class))).thenReturn(system); - - // 执行 - boolean result = externalSystemService.testConnection(1L); - - // 验证 - assertThat(result).isTrue(); - assertThat(system.getLastConnectTime()).isNotNull(); - verify(externalSystemRepository).save(system); - } -} \ No newline at end of file diff --git a/backend/src/test/java/com/qqchen/deploy/backend/service/impl/MenuServiceImplTest.java b/backend/src/test/java/com/qqchen/deploy/backend/service/impl/MenuServiceImplTest.java deleted file mode 100644 index 13784407..00000000 --- a/backend/src/test/java/com/qqchen/deploy/backend/service/impl/MenuServiceImplTest.java +++ /dev/null @@ -1,236 +0,0 @@ -package com.qqchen.deploy.backend.service.impl; - -import com.qqchen.deploy.backend.system.converter.MenuConverter; -import com.qqchen.deploy.backend.system.entity.Menu; -import com.qqchen.deploy.backend.system.entity.Permission; -import com.qqchen.deploy.backend.system.model.MenuDTO; -import com.qqchen.deploy.backend.system.model.response.MenuPermissionTreeResponse; -import com.qqchen.deploy.backend.system.model.response.MenuResponse; -import com.qqchen.deploy.backend.system.model.response.PermissionResponse; -import com.qqchen.deploy.backend.system.repository.IMenuRepository; -import com.qqchen.deploy.backend.system.repository.IPermissionRepository; -import com.qqchen.deploy.backend.system.service.impl.MenuServiceImpl; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.mock.mockito.MockBean; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.when; - -@SpringBootTest -class MenuServiceImplTest { - - @MockBean - private IMenuRepository menuRepository; - - @MockBean - private IPermissionRepository permissionRepository; - - @MockBean - private MenuConverter menuConverter; - - @Autowired - private MenuServiceImpl menuService; - - private Menu rootMenu; - private Menu childMenu; - private Permission permission1; - private Permission permission2; - - @BeforeEach - void setUp() { - // 准备测试数据 - rootMenu = new Menu(); - rootMenu.setId(1L); - rootMenu.setName("根菜单"); - rootMenu.setSort(1); - rootMenu.setType(1); - rootMenu.setParentId(0L); - - childMenu = new Menu(); - childMenu.setId(2L); - childMenu.setName("子菜单"); - childMenu.setSort(1); - childMenu.setType(2); - childMenu.setParentId(1L); - - permission1 = new Permission(); - permission1.setId(1L); - permission1.setMenuId(2L); - permission1.setName("查看"); - permission1.setCode("VIEW"); - permission1.setSort(1); - - permission2 = new Permission(); - permission2.setId(2L); - permission2.setMenuId(2L); - permission2.setName("编辑"); - permission2.setCode("EDIT"); - permission2.setSort(2); - } - - @Test - void getPermissionTree_ShouldReturnCorrectStructure() { - // 准备测试数据 - List menus = Arrays.asList(rootMenu, childMenu); - List permissions = Arrays.asList(permission1, permission2); - - // Mock Repository方法 - when(menuRepository.findByDeletedFalseOrderBySort()).thenReturn(menus); - when(permissionRepository.findAllByDeletedFalseOrderBySort()).thenReturn(permissions); - - // Mock Converter方法 - MenuPermissionTreeResponse rootResponse = createMenuPermissionResponse(rootMenu); - MenuPermissionTreeResponse childResponse = createMenuPermissionResponse(childMenu); - when(menuConverter.toMenuPermissionResponse(rootMenu)).thenReturn(rootResponse); - when(menuConverter.toMenuPermissionResponse(childMenu)).thenReturn(childResponse); - when(menuConverter.toPermissionResponseList(Arrays.asList(permission1, permission2))) - .thenReturn(Arrays.asList( - createPermissionResponse(permission1), - createPermissionResponse(permission2) - )); - - // 执行测试 - List result = menuService.getPermissionTree(); - - // 验证结果 - assertThat(result).isNotNull(); - assertThat(result).hasSize(1); // 只有一个根节点 - - MenuPermissionTreeResponse root = result.get(0); - assertThat(root.getId()).isEqualTo(1L); - assertThat(root.getName()).isEqualTo("根菜单"); - assertThat(root.getPermissionChildren()).hasSize(1); // 有一个子节点 - - MenuPermissionTreeResponse child = root.getPermissionChildren().get(0); - assertThat(child.getId()).isEqualTo(2L); - assertThat(child.getName()).isEqualTo("子菜单"); - assertThat(child.getPermissions()).hasSize(2); // 有两个权限 - - PermissionResponse firstPermission = child.getPermissions().get(0); - assertThat(firstPermission.getCode()).isEqualTo("VIEW"); - assertThat(firstPermission.getName()).isEqualTo("查看"); - } - - private MenuPermissionTreeResponse createMenuPermissionResponse(Menu menu) { - MenuPermissionTreeResponse response = new MenuPermissionTreeResponse(); - response.setId(menu.getId()); - response.setName(menu.getName()); - response.setParentId(menu.getParentId()); - response.setType(menu.getType()); - response.setSort(menu.getSort()); - return response; - } - - private PermissionResponse createPermissionResponse(Permission permission) { - PermissionResponse response = new PermissionResponse(); - response.setId(permission.getId()); - response.setCode(permission.getCode()); - response.setName(permission.getName()); - response.setType(permission.getType()); - response.setSort(permission.getSort()); - return response; - } - - @Test - void getPermissionTree_WithEmptyData_ShouldReturnEmptyList() { - // Mock空数据 - when(menuRepository.findByDeletedFalseOrderBySort()).thenReturn(List.of()); - when(permissionRepository.findAllByDeletedFalseOrderBySort()).thenReturn(List.of()); - - // 执行测试 - List result = menuService.getPermissionTree(); - - // 验证结果 - assertThat(result).isNotNull(); - assertThat(result).isEmpty(); - } - - @Test - void getMenuTree_ShouldReturnCorrectStructure() { - // 准备测试数据 - List menus = Arrays.asList(rootMenu, childMenu); - - // 准备DTO对象 - MenuDTO rootDto = convertToDto(rootMenu); - MenuDTO childDto = convertToDto(childMenu); - rootDto.setChildren(new ArrayList<>(Arrays.asList(childDto))); - List dtoList = new ArrayList<>(Arrays.asList(rootDto, childDto)); - - // 准备Response对象 - MenuResponse rootResponse = createMenuResponse(rootMenu); - MenuResponse childResponse = createMenuResponse(childMenu); - rootResponse.setChildren(new ArrayList<>(Arrays.asList(childResponse))); - List responseList = new ArrayList<>(Arrays.asList(rootResponse)); - - // Mock Repository方法 - when(menuRepository.findByDeletedFalseOrderBySort()).thenReturn(menus); - - // Mock Converter方法 - when(menuConverter.toDtoList(menus)).thenReturn(dtoList); - when(menuConverter.toResponseList(List.of(rootDto))).thenReturn(responseList); - - // 执行测试 - List result = menuService.getMenuTree(); - - // 验证结果 - assertThat(result).isNotNull(); - assertThat(result).hasSize(1); // 只有一个根节点 - - MenuResponse root = result.get(0); - assertThat(root.getId()).isEqualTo(1L); - assertThat(root.getName()).isEqualTo("根菜单"); - assertThat(root.getType()).isEqualTo(1); - assertThat(root.getParentId()).isEqualTo(0L); - assertThat(root.getChildren()).hasSize(1); // 有一个子节点 - - MenuResponse child = root.getChildren().get(0); - assertThat(child.getId()).isEqualTo(2L); - assertThat(child.getName()).isEqualTo("子菜单"); - assertThat(child.getType()).isEqualTo(2); - assertThat(child.getParentId()).isEqualTo(1L); - assertThat(child.getChildren()).isEmpty(); // 没有子节点 - } - - @Test - void getMenuTree_WithEmptyData_ShouldReturnEmptyList() { - // Mock空数据 - when(menuRepository.findByDeletedFalseOrderBySort()).thenReturn(List.of()); - - // 执行测试 - List result = menuService.getMenuTree(); - - // 验证结果 - assertThat(result).isNotNull(); - assertThat(result).isEmpty(); - } - - private MenuResponse createMenuResponse(Menu menu) { - MenuResponse response = new MenuResponse(); - response.setId(menu.getId()); - response.setName(menu.getName()); - response.setParentId(menu.getParentId()); - response.setType(menu.getType()); - response.setSort(menu.getSort()); - response.setChildren(new ArrayList<>()); - return response; - } - - private MenuDTO convertToDto(Menu menu) { - MenuDTO dto = new MenuDTO(); - dto.setId(menu.getId()); - dto.setName(menu.getName()); - dto.setParentId(menu.getParentId()); - dto.setType(menu.getType()); - dto.setSort(menu.getSort()); - dto.setChildren(new ArrayList<>()); - return dto; - } -} \ No newline at end of file diff --git a/backend/src/test/java/com/qqchen/deploy/backend/service/impl/TenantServiceImplTest.java b/backend/src/test/java/com/qqchen/deploy/backend/service/impl/TenantServiceImplTest.java deleted file mode 100644 index bdc16e6a..00000000 --- a/backend/src/test/java/com/qqchen/deploy/backend/service/impl/TenantServiceImplTest.java +++ /dev/null @@ -1,102 +0,0 @@ -package com.qqchen.deploy.backend.service.impl; - -import com.qqchen.deploy.backend.system.entity.Tenant; -import com.qqchen.deploy.backend.system.repository.ITenantRepository; -import com.qqchen.deploy.backend.system.service.impl.TenantServiceImpl; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.mock.mockito.MockBean; - -import java.util.Optional; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; - -/** - * 租户服务测试类 - * - * @author QQChen - * @version 1.0.0 - */ -@SpringBootTest -class TenantServiceImplTest { - - @MockBean - private ITenantRepository tenantRepository; - - @Autowired - private TenantServiceImpl tenantService; - - private Tenant tenant; - - @BeforeEach - void setUp() { - // 准备测试数据 - tenant = new Tenant(); - tenant.setId(1L); - tenant.setName("测试租户"); - tenant.setCode("TEST"); - tenant.setEnabled(true); - } - - @Test - void getStatus_WhenEnabled_ShouldReturnTrue() { - // Mock - when(tenantRepository.findById(1L)).thenReturn(Optional.of(tenant)); - - // 执行 - boolean result = tenantService.getStatus(1L); - - // 验证 - assertThat(result).isTrue(); - } - - @Test - void getStatus_WhenDisabled_ShouldReturnFalse() { - // 准备数据 - tenant.setEnabled(false); - - // Mock - when(tenantRepository.findById(1L)).thenReturn(Optional.of(tenant)); - - // 执行 - boolean result = tenantService.getStatus(1L); - - // 验证 - assertThat(result).isFalse(); - } - - @Test - void updateStatus_WhenDisabled_ShouldUpdateTenantStatus() { - // Mock - when(tenantRepository.findById(1L)).thenReturn(Optional.of(tenant)); - when(tenantRepository.save(any(Tenant.class))).thenReturn(tenant); - - // 执行 - tenantService.updateStatus(1L, false); - - // 验证 - assertThat(tenant.getEnabled()).isFalse(); - verify(tenantRepository).save(tenant); - } - - @Test - void updateStatus_WhenEnabled_ShouldUpdateTenantStatus() { - // 准备数据 - tenant.setEnabled(false); - - // Mock - when(tenantRepository.findById(1L)).thenReturn(Optional.of(tenant)); - when(tenantRepository.save(any(Tenant.class))).thenReturn(tenant); - - // 执行 - tenantService.updateStatus(1L, true); - - // 验证 - assertThat(tenant.getEnabled()).isTrue(); - verify(tenantRepository).save(tenant); - } -} \ No newline at end of file diff --git a/backend/src/test/java/com/qqchen/deploy/backend/workflow/engine/DefaultWorkflowEngineTest.java b/backend/src/test/java/com/qqchen/deploy/backend/workflow/engine/DefaultWorkflowEngineTest.java deleted file mode 100644 index 466a1a09..00000000 --- a/backend/src/test/java/com/qqchen/deploy/backend/workflow/engine/DefaultWorkflowEngineTest.java +++ /dev/null @@ -1,305 +0,0 @@ -package com.qqchen.deploy.backend.workflow.engine; - -import com.qqchen.deploy.backend.framework.enums.ResponseCode; -import com.qqchen.deploy.backend.workflow.engine.context.DefaultWorkflowContext; -import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContext; -import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException; -import com.qqchen.deploy.backend.workflow.engine.executor.node.NodeExecutor; -import com.qqchen.deploy.backend.workflow.entity.NodeInstance; -import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition; -import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; -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.INodeInstanceRepository; -import com.qqchen.deploy.backend.workflow.repository.IWorkflowDefinitionRepository; -import com.qqchen.deploy.backend.workflow.repository.IWorkflowInstanceRepository; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; - -class DefaultWorkflowEngineTest { - - @Mock - private IWorkflowDefinitionRepository workflowDefinitionRepository; - - @Mock - private IWorkflowInstanceRepository workflowInstanceRepository; - - @Mock - private INodeInstanceRepository nodeInstanceRepository; - - @Mock - private Map nodeExecutors; - - @Mock - private DefaultWorkflowContext.Factory workflowContextFactory; - - @Mock - private NodeExecutor startNodeExecutor; - - @Mock - private NodeExecutor taskNodeExecutor; - - @Mock - private DefaultWorkflowContext workflowContext; - - @InjectMocks - private DefaultWorkflowEngine workflowEngine; - - @BeforeEach - void setUp() { - MockitoAnnotations.openMocks(this); - - // 配置基本的Mock行为 - when(nodeExecutors.get(NodeTypeEnum.START)).thenReturn(startNodeExecutor); - when(nodeExecutors.get(NodeTypeEnum.TASK)).thenReturn(taskNodeExecutor); - when(workflowContextFactory.create(any())).thenReturn(workflowContext); - } - - @Test - void startWorkflow_Success() { - // 准备测试数据 - String workflowCode = "test-workflow"; - String businessKey = "test-key"; - Map variables = new HashMap<>(); - variables.put("key1", "value1"); - - WorkflowDefinition definition = new WorkflowDefinition(); - definition.setId(1L); - definition.setCode(workflowCode); - definition.setStatus(WorkflowDefinitionStatusEnum.PUBLISHED); - - NodeInstance startNode = new NodeInstance(); - startNode.setId(1L); - startNode.setNodeType(NodeTypeEnum.START); - startNode.setStatus(NodeStatusEnum.PENDING); - - // 配置Mock行为 - when(workflowDefinitionRepository.findByCodeAndDeletedFalse(workflowCode)).thenReturn(definition); - when(workflowInstanceRepository.save(any())).thenAnswer(i -> i.getArgument(0)); - when(nodeInstanceRepository.save(any())).thenReturn(startNode); - when(nodeInstanceRepository.findById(startNode.getId())).thenReturn(Optional.of(startNode)); - doNothing().when(startNodeExecutor).execute(any(), any()); - - // 执行测试 - WorkflowInstance result = workflowEngine.startWorkflow(workflowCode, businessKey, variables); - - // 验证结果 - assertNotNull(result); - assertEquals(WorkflowInstanceStatusEnum.RUNNING, result.getStatus()); - assertNotNull(result.getStartTime()); - - verify(workflowDefinitionRepository).findByCodeAndDeletedFalse(workflowCode); - verify(workflowInstanceRepository).save(any()); - verify(nodeInstanceRepository).save(any()); - verify(workflowContextFactory).create(any()); - verify(workflowContext, times(variables.size())).setVariable(anyString(), any()); - } - - @Test - void startWorkflow_WorkflowNotFound() { - // 准备测试数据 - String workflowCode = "non-existent"; - - // 配置Mock行为 - when(workflowDefinitionRepository.findByCodeAndDeletedFalse(workflowCode)).thenReturn(null); - - // 执行测试并验证异常 - WorkflowEngineException exception = assertThrows(WorkflowEngineException.class, - () -> workflowEngine.startWorkflow(workflowCode, "test", null)); - assertTrue(exception.getMessage().contains(ResponseCode.WORKFLOW_NOT_FOUND.name())); - } - - @Test - void startWorkflow_WorkflowNotPublished() { - // 准备测试数据 - String workflowCode = "draft-workflow"; - WorkflowDefinition definition = new WorkflowDefinition(); - definition.setCode(workflowCode); - definition.setStatus(WorkflowDefinitionStatusEnum.DRAFT); - - // 配置Mock行为 - when(workflowDefinitionRepository.findByCodeAndDeletedFalse(workflowCode)).thenReturn(definition); - - // 执行测试并验证异常 - WorkflowEngineException exception = assertThrows(WorkflowEngineException.class, - () -> workflowEngine.startWorkflow(workflowCode, "test", null)); - assertTrue(exception.getMessage().contains(ResponseCode.WORKFLOW_NOT_PUBLISHED.name())); - } - - @Test - void executeNode_Success() { - // 准备测试数据 - Long nodeInstanceId = 1L; - NodeInstance nodeInstance = new NodeInstance(); - nodeInstance.setId(nodeInstanceId); - nodeInstance.setNodeType(NodeTypeEnum.TASK); - nodeInstance.setStatus(NodeStatusEnum.PENDING); - - WorkflowInstance instance = new WorkflowInstance(); - instance.setStatus(WorkflowInstanceStatusEnum.RUNNING); - nodeInstance.setWorkflowInstance(instance); - - // 配置Mock行为 - when(nodeInstanceRepository.findById(nodeInstanceId)).thenReturn(Optional.of(nodeInstance)); - doNothing().when(taskNodeExecutor).execute(any(), any()); - - // 执行测试 - workflowEngine.executeNode(nodeInstanceId); - - // 验证结果 - assertEquals(NodeStatusEnum.COMPLETED, nodeInstance.getStatus()); - assertNotNull(nodeInstance.getEndTime()); - - verify(nodeInstanceRepository).findById(nodeInstanceId); - verify(taskNodeExecutor).execute(any(), any()); - verify(nodeInstanceRepository).save(nodeInstance); - } - - @Test - void executeNode_NodeNotFound() { - // 准备测试数据 - Long nodeInstanceId = 999L; - - // 配置Mock行为 - when(nodeInstanceRepository.findById(nodeInstanceId)).thenReturn(Optional.empty()); - - // 执行测试并验证异常 - WorkflowEngineException exception = assertThrows(WorkflowEngineException.class, - () -> workflowEngine.executeNode(nodeInstanceId)); - assertTrue(exception.getMessage().contains(ResponseCode.WORKFLOW_NODE_NOT_FOUND.name())); - } - - @Test - void executeNode_WorkflowNotRunning() { - // 准备测试数据 - Long nodeInstanceId = 1L; - NodeInstance nodeInstance = new NodeInstance(); - nodeInstance.setId(nodeInstanceId); - nodeInstance.setNodeType(NodeTypeEnum.TASK); - - WorkflowInstance instance = new WorkflowInstance(); - instance.setStatus(WorkflowInstanceStatusEnum.COMPLETED); - nodeInstance.setWorkflowInstance(instance); - - // 配置Mock行为 - when(nodeInstanceRepository.findById(nodeInstanceId)).thenReturn(Optional.of(nodeInstance)); - - // 执行测试并验证异常 - WorkflowEngineException exception = assertThrows(WorkflowEngineException.class, - () -> workflowEngine.executeNode(nodeInstanceId)); - assertTrue(exception.getMessage().contains(ResponseCode.WORKFLOW_INSTANCE_NOT_RUNNING.name())); - } - - @Test - void terminateWorkflow_Success() { - // 准备测试数据 - Long instanceId = 1L; - String reason = "Test termination"; - - WorkflowInstance instance = new WorkflowInstance(); - instance.setId(instanceId); - instance.setStatus(WorkflowInstanceStatusEnum.RUNNING); - - NodeInstance runningNode = new NodeInstance(); - runningNode.setNodeType(NodeTypeEnum.TASK); - runningNode.setStatus(NodeStatusEnum.RUNNING); - - // 配置Mock行为 - when(workflowInstanceRepository.findById(instanceId)).thenReturn(Optional.of(instance)); - when(nodeInstanceRepository.findByWorkflowInstanceIdAndStatus(instanceId, NodeStatusEnum.RUNNING)) - .thenReturn(Arrays.asList(runningNode)); - doNothing().when(taskNodeExecutor).terminate(any(), any()); - - // 执行测试 - workflowEngine.terminateWorkflow(instanceId, reason); - - // 验证结果 - assertEquals(WorkflowInstanceStatusEnum.TERMINATED, instance.getStatus()); - assertEquals(reason, instance.getError()); - assertNotNull(instance.getEndTime()); - assertEquals(NodeStatusEnum.TERMINATED, runningNode.getStatus()); - assertNotNull(runningNode.getEndTime()); - - verify(workflowInstanceRepository).findById(instanceId); - verify(nodeInstanceRepository).findByWorkflowInstanceIdAndStatus(instanceId, NodeStatusEnum.RUNNING); - verify(taskNodeExecutor).terminate(any(), any()); - verify(nodeInstanceRepository).save(runningNode); - verify(workflowInstanceRepository).save(instance); - } - - @Test - void pauseWorkflow_Success() { - // 准备测试数据 - Long instanceId = 1L; - WorkflowInstance instance = new WorkflowInstance(); - instance.setId(instanceId); - instance.setStatus(WorkflowInstanceStatusEnum.RUNNING); - - NodeInstance runningNode = new NodeInstance(); - runningNode.setStatus(NodeStatusEnum.RUNNING); - - // 配置Mock行为 - when(workflowInstanceRepository.findById(instanceId)).thenReturn(Optional.of(instance)); - when(nodeInstanceRepository.findByWorkflowInstanceIdAndStatus(instanceId, NodeStatusEnum.RUNNING)) - .thenReturn(Arrays.asList(runningNode)); - - // 执行测试 - workflowEngine.pauseWorkflow(instanceId); - - // 验证结果 - assertEquals(WorkflowInstanceStatusEnum.PAUSED, instance.getStatus()); - assertEquals(NodeStatusEnum.PAUSED, runningNode.getStatus()); - - verify(workflowInstanceRepository).findById(instanceId); - verify(nodeInstanceRepository).findByWorkflowInstanceIdAndStatus(instanceId, NodeStatusEnum.RUNNING); - verify(nodeInstanceRepository).save(runningNode); - verify(workflowInstanceRepository).save(instance); - } - - @Test - void resumeWorkflow_Success() { - // 准备测试数据 - Long instanceId = 1L; - WorkflowInstance instance = new WorkflowInstance(); - instance.setId(instanceId); - instance.setStatus(WorkflowInstanceStatusEnum.PAUSED); - - NodeInstance pausedNode = new NodeInstance(); - pausedNode.setId(2L); - pausedNode.setStatus(NodeStatusEnum.PAUSED); - pausedNode.setWorkflowInstance(instance); - - // 配置Mock行为 - when(workflowInstanceRepository.findById(instanceId)).thenReturn(Optional.of(instance)); - when(nodeInstanceRepository.findByWorkflowInstanceIdAndStatus(instanceId, NodeStatusEnum.PAUSED)) - .thenReturn(Arrays.asList(pausedNode)); - when(nodeInstanceRepository.findById(pausedNode.getId())).thenReturn(Optional.of(pausedNode)); - doNothing().when(taskNodeExecutor).execute(any(), any()); - - // 执行测试 - workflowEngine.resumeWorkflow(instanceId); - - // 验证结果 - assertEquals(WorkflowInstanceStatusEnum.RUNNING, instance.getStatus()); - assertEquals(NodeStatusEnum.RUNNING, pausedNode.getStatus()); - - verify(workflowInstanceRepository).findById(instanceId); - verify(nodeInstanceRepository).findByWorkflowInstanceIdAndStatus(instanceId, NodeStatusEnum.PAUSED); - verify(nodeInstanceRepository).save(pausedNode); - verify(workflowInstanceRepository).save(instance); - } -} \ No newline at end of file diff --git a/backend/src/test/java/com/qqchen/deploy/backend/workflow/engine/WorkflowShellTest.java b/backend/src/test/java/com/qqchen/deploy/backend/workflow/engine/WorkflowShellTest.java deleted file mode 100644 index 95b47791..00000000 --- a/backend/src/test/java/com/qqchen/deploy/backend/workflow/engine/WorkflowShellTest.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.qqchen.deploy.backend.workflow.engine; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition; -import com.qqchen.deploy.backend.workflow.repository.IWorkflowDefinitionRepository; -import jakarta.annotation.Resource; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.core.io.ClassPathResource; - -import java.nio.file.Files; -import java.util.HashMap; - -@SpringBootTest -public class WorkflowShellTest { - - @Resource - private WorkflowEngine workflowEngine; - - @Resource - private IWorkflowDefinitionRepository workflowDefinitionRepository; - - @Resource - private ObjectMapper objectMapper; - - @Test - public void testShellWorkflow() throws Exception { - // 1. 加载工作流定义 - String json = Files.readString(new ClassPathResource("workflow-shell-test.json").getFile().toPath()); - WorkflowDefinition definition = objectMapper.readValue(json, WorkflowDefinition.class); - workflowDefinitionRepository.save(definition); - - // 2. 启动工作流实例 - workflowEngine.startWorkflow(definition.getId(), "TEST-" + System.currentTimeMillis(), new HashMap<>()); - } -} diff --git a/backend/src/test/java/com/qqchen/deploy/backend/workflow/engine/executor/StartNodeExecutorTest.java b/backend/src/test/java/com/qqchen/deploy/backend/workflow/engine/executor/StartNodeExecutorTest.java deleted file mode 100644 index 7e737661..00000000 --- a/backend/src/test/java/com/qqchen/deploy/backend/workflow/engine/executor/StartNodeExecutorTest.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.qqchen.deploy.backend.workflow.engine.executor; - -import com.qqchen.deploy.backend.system.enums.LogLevelEnum; -import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContext; -import com.qqchen.deploy.backend.workflow.engine.executor.node.StartNodeExecutor; -import com.qqchen.deploy.backend.workflow.entity.NodeInstance; -import com.qqchen.deploy.backend.workflow.enums.NodeStatusEnum; -import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.verify; - -class StartNodeExecutorTest { - - @Mock - private WorkflowContext workflowContext; - - @InjectMocks - private StartNodeExecutor startNodeExecutor; - - @BeforeEach - void setUp() { - MockitoAnnotations.openMocks(this); - } - - @Test - void getNodeType_ShouldReturnStartType() { - assertEquals(NodeTypeEnum.START, startNodeExecutor.getNodeType()); - } - - @Test - void execute_ShouldCompleteNodeAndLog() { - // 准备测试数据 - NodeInstance nodeInstance = new NodeInstance(); - nodeInstance.setNodeType(NodeTypeEnum.START); - nodeInstance.setStatus(NodeStatusEnum.PENDING); - - // 执行测试 - startNodeExecutor.execute(nodeInstance, workflowContext); - - // 验证结果 - assertEquals(NodeStatusEnum.COMPLETED, nodeInstance.getStatus()); - verify(workflowContext).log("开始节点执行完成", LogLevelEnum.INFO); - } - - @Test - void validate_ShouldDoNothing() { - // 开始节点无需配置,验证方法不会抛出异常 - startNodeExecutor.validate(null); - startNodeExecutor.validate(""); - startNodeExecutor.validate("{}"); - } - - @Test - void terminate_ShouldDoNothing() { - // 开始节点无需终止操作,验证方法不会抛出异常 - NodeInstance nodeInstance = new NodeInstance(); - startNodeExecutor.terminate(nodeInstance, workflowContext); - } -} \ No newline at end of file diff --git a/backend/src/test/java/com/qqchen/deploy/backend/workflow/engine/executor/TaskNodeExecutorTest.java b/backend/src/test/java/com/qqchen/deploy/backend/workflow/engine/executor/TaskNodeExecutorTest.java deleted file mode 100644 index 5bbbe530..00000000 --- a/backend/src/test/java/com/qqchen/deploy/backend/workflow/engine/executor/TaskNodeExecutorTest.java +++ /dev/null @@ -1,175 +0,0 @@ -//package com.qqchen.deploy.backend.workflow.engine.executor; -// -//import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContext; -//import com.qqchen.deploy.backend.workflow.engine.executor.task.TaskExecutor; -//import com.qqchen.deploy.backend.workflow.entity.NodeInstance; -//import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; -//import com.qqchen.deploy.backend.workflow.enums.NodeStatusEnum; -//import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum; -//import com.qqchen.deploy.backend.workflow.repository.INodeInstanceRepository; -//import org.junit.jupiter.api.BeforeEach; -//import org.junit.jupiter.api.Test; -//import org.mockito.InjectMocks; -//import org.mockito.Mock; -//import org.mockito.MockitoAnnotations; -// -//import java.util.HashMap; -//import java.util.Map; -// -//import static org.junit.jupiter.api.Assertions.*; -//import static org.mockito.ArgumentMatchers.any; -//import static org.mockito.Mockito.*; -// -//class TaskNodeExecutorTest { -// -// @Mock -// private INodeInstanceRepository nodeInstanceRepository; -// -// @Mock -// private Map taskExecutors; -// -// @Mock -// private TaskExecutor shellTaskExecutor; -// -// @Mock -// private WorkflowContext context; -// -// @InjectMocks -// private TaskNodeExecutor taskNodeExecutor; -// -// @BeforeEach -// void setUp() { -// MockitoAnnotations.openMocks(this); -// -// // 设置任务执行器映射 -// when(taskExecutors.get("SHELL")).thenReturn(shellTaskExecutor); -// } -// -// @Test -// void testExecute() { -// // 准备测试数据 -// WorkflowInstance instance = new WorkflowInstance(); -// instance.setId(1L); -// -// NodeInstance taskNode = new NodeInstance(); -// taskNode.setId(1L); -// taskNode.setNodeType(NodeTypeEnum.TASK); -// taskNode.setStatus(NodeStatusEnum.PENDING); -// taskNode.setConfig("{\"type\":\"SHELL\",\"script\":\"echo hello\"}"); -// -// Map input = new HashMap<>(); -// input.put("param1", "value1"); -// -// // 设置Mock行为 -// when(context.getWorkflowInstance()).thenReturn(instance); -// when(context.getVariables()).thenReturn(input); -// when(shellTaskExecutor.execute(any(), any())).thenReturn(true); -// -// // 执行测试 -// boolean result = taskNodeExecutor.execute(context, taskNode); -// -// // 验证结果 -// assertTrue(result); -// assertEquals(NodeStatusEnum.RUNNING, taskNode.getStatus()); -// assertNotNull(taskNode.getStartTime()); -// verify(nodeInstanceRepository, times(1)).save(taskNode); -// verify(shellTaskExecutor, times(1)).execute(any(), any()); -// } -// -// @Test -// void testExecuteWithError() { -// // 准备测试数据 -// WorkflowInstance instance = new WorkflowInstance(); -// instance.setId(1L); -// -// NodeInstance taskNode = new NodeInstance(); -// taskNode.setId(1L); -// taskNode.setNodeType(NodeTypeEnum.TASK); -// taskNode.setStatus(NodeStatusEnum.PENDING); -// taskNode.setConfig("{\"type\":\"SHELL\",\"script\":\"echo hello\"}"); -// -// // 设置Mock行为 -// when(context.getWorkflowInstance()).thenReturn(instance); -// when(shellTaskExecutor.execute(any(), any())).thenReturn(false); -// -// // 执行测试 -// boolean result = taskNodeExecutor.execute(context, taskNode); -// -// // 验证结果 -// assertFalse(result); -// assertEquals(NodeStatusEnum.FAILED, taskNode.getStatus()); -// assertNotNull(taskNode.getStartTime()); -// assertNotNull(taskNode.getEndTime()); -// verify(nodeInstanceRepository, times(1)).save(taskNode); -// verify(shellTaskExecutor, times(1)).execute(any(), any()); -// } -// -// @Test -// void testCanExecute() { -// // 准备测试数据 -// NodeInstance taskNode = new NodeInstance(); -// taskNode.setNodeType(NodeTypeEnum.TASK); -// taskNode.setStatus(NodeStatusEnum.PENDING); -// -// // 执行测试 -// boolean result = taskNodeExecutor.canExecute(taskNode); -// -// // 验证结果 -// assertTrue(result); -// } -// -// @Test -// void testCannotExecuteWrongType() { -// // 准备测试数据 -// NodeInstance startNode = new NodeInstance(); -// startNode.setNodeType(NodeTypeEnum.START); -// startNode.setStatus(NodeStatusEnum.PENDING); -// -// // 执行测试 -// boolean result = taskNodeExecutor.canExecute(startNode); -// -// // 验证结果 -// assertFalse(result); -// } -// -// @Test -// void testCannotExecuteWrongStatus() { -// // 准备测试数据 -// NodeInstance taskNode = new NodeInstance(); -// taskNode.setNodeType(NodeTypeEnum.TASK); -// taskNode.setStatus(NodeStatusEnum.RUNNING); -// -// // 执行测试 -// boolean result = taskNodeExecutor.canExecute(taskNode); -// -// // 验证结果 -// assertFalse(result); -// } -// -// @Test -// void testExecuteWithInvalidConfig() { -// // 准备测试数据 -// WorkflowInstance instance = new WorkflowInstance(); -// instance.setId(1L); -// -// NodeInstance taskNode = new NodeInstance(); -// taskNode.setId(1L); -// taskNode.setNodeType(NodeTypeEnum.TASK); -// taskNode.setStatus(NodeStatusEnum.PENDING); -// taskNode.setConfig("invalid json"); -// -// // 设置Mock行为 -// when(context.getWorkflowInstance()).thenReturn(instance); -// -// // 执行测试 -// boolean result = taskNodeExecutor.execute(context, taskNode); -// -// // 验证结果 -// assertFalse(result); -// assertEquals(NodeStatusEnum.FAILED, taskNode.getStatus()); -// assertNotNull(taskNode.getStartTime()); -// assertNotNull(taskNode.getEndTime()); -// assertNotNull(taskNode.getError()); -// verify(nodeInstanceRepository, times(1)).save(taskNode); -// } -//} \ No newline at end of file diff --git a/backend/src/test/java/com/qqchen/deploy/backend/workflow/service/ConcurrentWorkflowVariableOperationsTest.java b/backend/src/test/java/com/qqchen/deploy/backend/workflow/service/ConcurrentWorkflowVariableOperationsTest.java deleted file mode 100644 index e98e4e1b..00000000 --- a/backend/src/test/java/com/qqchen/deploy/backend/workflow/service/ConcurrentWorkflowVariableOperationsTest.java +++ /dev/null @@ -1,117 +0,0 @@ -package com.qqchen.deploy.backend.workflow.service; - -import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContext; -import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; -import com.qqchen.deploy.backend.workflow.service.impl.ConcurrentWorkflowVariableOperations; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.transaction.support.TransactionTemplate; - -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.*; - -@ExtendWith(MockitoExtension.class) -class ConcurrentWorkflowVariableOperationsTest { - - @Mock - private IWorkflowVariableService variableService; - - @Mock - private TransactionTemplate transactionTemplate; - - @Mock - private WorkflowContext.Factory contextFactory; - - @InjectMocks - private ConcurrentWorkflowVariableOperations operations; - - private WorkflowInstance instance; - private WorkflowContext context; - - @BeforeEach - void setUp() { - instance = new WorkflowInstance(); - instance.setId(1L); - - context = mock(WorkflowContext.class); - - when(contextFactory.create(any(), anyMap())).thenReturn(context); - when(transactionTemplate.execute(any())).thenAnswer(invocation -> { - return invocation.getArgument(0, TransactionCallback.class).doInTransaction(null); - }); - } - - @Test - void setVariables_ShouldHandleConcurrentAccess() throws InterruptedException { - // 准备测试数据 - int threadCount = 10; - ExecutorService executorService = Executors.newFixedThreadPool(threadCount); - CountDownLatch latch = new CountDownLatch(threadCount); - - // 模拟多线程并发访问 - for (int i = 0; i < threadCount; i++) { - final int index = i; - executorService.submit(() -> { - try { - Map variables = new HashMap<>(); - variables.put("key" + index, "value" + index); - operations.setVariables(instance, variables); - } finally { - latch.countDown(); - } - }); - } - - // 等待所有线程完成 - latch.await(); - executorService.shutdown(); - - // 验证结果 - verify(variableService, times(threadCount)).setVariable(eq(instance), anyString(), any()); - } - - @Test - void getVariables_ShouldReturnCachedValues() { - // 准备测试数据 - Map expectedVariables = new HashMap<>(); - expectedVariables.put("key1", "value1"); - when(context.getVariables()).thenReturn(expectedVariables); - - // 第一次调用 - Map result1 = operations.getVariables(instance); - assertEquals(expectedVariables, result1); - - // 第二次调用应该返回缓存的结果 - Map result2 = operations.getVariables(instance); - assertEquals(expectedVariables, result2); - - // 验证 variableService 只被调用一次 - verify(variableService, times(1)).getVariables(instance); - } - - @Test - void clearContext_ShouldRemoveFromCache() { - // 首先获取变量以确保上下文被缓存 - operations.getVariables(instance); - - // 清除上下文 - operations.clearContext(instance); - - // 再次获取变量,应该重新从服务加载 - operations.getVariables(instance); - - // 验证 variableService 被调用了两次 - verify(variableService, times(2)).getVariables(instance); - } -} diff --git a/backend/src/test/java/com/qqchen/deploy/backend/workflow/service/WorkflowInstanceServiceTest.java b/backend/src/test/java/com/qqchen/deploy/backend/workflow/service/WorkflowInstanceServiceTest.java deleted file mode 100644 index 5f851296..00000000 --- a/backend/src/test/java/com/qqchen/deploy/backend/workflow/service/WorkflowInstanceServiceTest.java +++ /dev/null @@ -1,188 +0,0 @@ -//package com.qqchen.deploy.backend.workflow.service; -// -//import com.qqchen.deploy.backend.framework.enums.ResponseCode; -//import com.qqchen.deploy.backend.framework.exception.BusinessException; -//import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceDTO; -//import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition; -//import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; -//import com.qqchen.deploy.backend.workflow.enums.WorkflowInstanceStatusEnum; -//import com.qqchen.deploy.backend.workflow.enums.WorkflowDefinitionStatusEnum; -//import com.qqchen.deploy.backend.workflow.repository.IWorkflowDefinitionRepository; -//import com.qqchen.deploy.backend.workflow.repository.IWorkflowInstanceRepository; -//import com.qqchen.deploy.backend.workflow.service.impl.WorkflowInstanceServiceImpl; -//import org.junit.jupiter.api.BeforeEach; -//import org.junit.jupiter.api.Test; -//import org.junit.jupiter.api.extension.ExtendWith; -//import org.mockito.InjectMocks; -//import org.mockito.Mock; -//import org.mockito.junit.jupiter.MockitoExtension; -// -//import java.util.HashMap; -//import java.util.Map; -//import java.util.Optional; -// -//import static org.junit.jupiter.api.Assertions.*; -//import static org.mockito.ArgumentMatchers.any; -//import static org.mockito.ArgumentMatchers.anyLong; -//import static org.mockito.Mockito.*; -// -//@ExtendWith(MockitoExtension.class) -//class WorkflowInstanceServiceTest { -// -// @Mock -// private IWorkflowDefinitionRepository workflowDefinitionRepository; -// -// @Mock -// private IWorkflowInstanceRepository workflowInstanceRepository; -// -// @Mock -// private IWorkflowVariableService workflowVariableService; -// -// @InjectMocks -// private WorkflowInstanceServiceImpl workflowInstanceService; -// -// private WorkflowDefinition definition; -// private WorkflowInstance instance; -// private Map variables; -// -// @BeforeEach -// void setUp() { -// definition = new WorkflowDefinition(); -// definition.setId(1L); -// definition.setCode("TEST-WF"); -// definition.setName("Test Workflow"); -// definition.setStatus(WorkflowDefinitionStatusEnum.PUBLISHED); -// definition.setEnabled(true); -// -// instance = new WorkflowInstance(); -// instance.setId(1L); -// instance.setDefinition(definition); -// instance.setBusinessKey("TEST-KEY"); -// instance.setStatus(WorkflowInstanceStatusEnum.CREATED); -// -// variables = new HashMap<>(); -// variables.put("key1", "value1"); -// variables.put("key2", "value2"); -// } -// -// @Test -// void createInstance_Success() { -// when(workflowDefinitionRepository.findById(anyLong())).thenReturn(Optional.of(definition)); -// when(workflowInstanceRepository.save(any(WorkflowInstance.class))).thenReturn(instance); -// -// WorkflowInstanceDTO result = workflowInstanceService.createInstance(1L, "TEST-KEY", variables); -// -// assertNotNull(result); -// assertEquals(WorkflowInstanceStatusEnum.CREATED, result.getStatus()); -// assertEquals("TEST-KEY", result.getBusinessKey()); -// verify(workflowDefinitionRepository).findById(1L); -// verify(workflowInstanceRepository).save(any(WorkflowInstance.class)); -// verify(workflowVariableService, times(2)).setVariable(anyLong(), anyString(), any()); -// } -// -// @Test -// void createInstance_WorkflowNotFound_ThrowsException() { -// when(workflowDefinitionRepository.findById(anyLong())).thenReturn(Optional.empty()); -// -// BusinessException exception = assertThrows(BusinessException.class, -// () -> workflowInstanceService.createInstance(1L, "TEST-KEY", variables)); -// assertEquals(ResponseCode.WORKFLOW_NOT_FOUND, exception.getCode()); -// } -// -// @Test -// void createInstance_WorkflowNotPublished_ThrowsException() { -// definition.setStatus(WorkflowDefinitionStatusEnum.DRAFT); -// when(workflowDefinitionRepository.findById(anyLong())).thenReturn(Optional.of(definition)); -// -// BusinessException exception = assertThrows(BusinessException.class, -// () -> workflowInstanceService.createInstance(1L, "TEST-KEY", variables)); -// assertEquals(ResponseCode.WORKFLOW_NOT_PUBLISHED, exception.getCode()); -// } -// -// @Test -// void startInstance_Success() { -// when(workflowInstanceRepository.findById(anyLong())).thenReturn(Optional.of(instance)); -// when(workflowInstanceRepository.save(any(WorkflowInstance.class))).thenReturn(instance); -// -// WorkflowInstanceDTO result = workflowInstanceService.startInstance(1L); -// -// assertNotNull(result); -// assertEquals(WorkflowInstanceStatusEnum.RUNNING, result.getStatus()); -// assertNotNull(result.getStartTime()); -// verify(workflowInstanceRepository).findById(1L); -// verify(workflowInstanceRepository).save(any(WorkflowInstance.class)); -// } -// -// @Test -// void startInstance_NotFound_ThrowsException() { -// when(workflowInstanceRepository.findById(anyLong())).thenReturn(Optional.empty()); -// -// BusinessException exception = assertThrows(BusinessException.class, -// () -> workflowInstanceService.startInstance(1L)); -// assertEquals(ResponseCode.WORKFLOW_INSTANCE_NOT_FOUND, exception.getCode()); -// } -// -// @Test -// void startInstance_NotCreated_ThrowsException() { -// instance.setStatus(WorkflowInstanceStatusEnum.RUNNING); -// when(workflowInstanceRepository.findById(anyLong())).thenReturn(Optional.of(instance)); -// -// BusinessException exception = assertThrows(BusinessException.class, -// () -> workflowInstanceService.startInstance(1L)); -// assertEquals(ResponseCode.WORKFLOW_INSTANCE_NOT_RUNNING, exception.getCode()); -// } -// -// @Test -// void pauseInstance_Success() { -// instance.setStatus(WorkflowInstanceStatusEnum.RUNNING); -// when(workflowInstanceRepository.findById(anyLong())).thenReturn(Optional.of(instance)); -// when(workflowInstanceRepository.save(any(WorkflowInstance.class))).thenReturn(instance); -// -// WorkflowInstanceDTO result = workflowInstanceService.pauseInstance(1L); -// -// assertNotNull(result); -// assertEquals(WorkflowInstanceStatusEnum.PAUSED, result.getStatus()); -// verify(workflowInstanceRepository).findById(1L); -// verify(workflowInstanceRepository).save(any(WorkflowInstance.class)); -// } -// -// @Test -// void resumeInstance_Success() { -// instance.setStatus(WorkflowInstanceStatusEnum.PAUSED); -// when(workflowInstanceRepository.findById(anyLong())).thenReturn(Optional.of(instance)); -// when(workflowInstanceRepository.save(any(WorkflowInstance.class))).thenReturn(instance); -// -// WorkflowInstanceDTO result = workflowInstanceService.resumeInstance(1L); -// -// assertNotNull(result); -// assertEquals(WorkflowInstanceStatusEnum.RUNNING, result.getStatus()); -// verify(workflowInstanceRepository).findById(1L); -// verify(workflowInstanceRepository).save(any(WorkflowInstance.class)); -// } -// -// @Test -// void terminateInstance_Success() { -// instance.setStatus(WorkflowInstanceStatusEnum.RUNNING); -// when(workflowInstanceRepository.findById(anyLong())).thenReturn(Optional.of(instance)); -// when(workflowInstanceRepository.save(any(WorkflowInstance.class))).thenReturn(instance); -// -// WorkflowInstanceDTO result = workflowInstanceService.terminateInstance(1L, "Test reason"); -// -// assertNotNull(result); -// assertEquals(WorkflowInstanceStatusEnum.TERMINATED, result.getStatus()); -// assertEquals("Test reason", result.getError()); -// assertNotNull(result.getEndTime()); -// verify(workflowInstanceRepository).findById(1L); -// verify(workflowInstanceRepository).save(any(WorkflowInstance.class)); -// } -// -// @Test -// void terminateInstance_AlreadyTerminated_ThrowsException() { -// instance.setStatus(WorkflowInstanceStatusEnum.TERMINATED); -// when(workflowInstanceRepository.findById(anyLong())).thenReturn(Optional.of(instance)); -// -// BusinessException exception = assertThrows(BusinessException.class, -// () -> workflowInstanceService.terminateInstance(1L, "Test reason")); -// assertEquals(ResponseCode.WORKFLOW_INSTANCE_NOT_RUNNING, exception.getCode()); -// } -//} \ No newline at end of file