From 9fce2fc9006c59457081427bfe89881f0b830576 Mon Sep 17 00:00:00 2001 From: dengqichen Date: Wed, 4 Dec 2024 17:11:28 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8F=AF=E6=AD=A3=E5=B8=B8=E5=90=AF=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/pom.xml | 6 + .../backend/framework/enums/ResponseCode.java | 5 + .../impl/ExternalSystemServiceImpl.java | 2 +- .../workflow/api/WorkflowApiController.java | 141 -------- .../api/dto/WorkflowDefinitionDTO.java | 52 --- .../workflow/api/query/NodeInstanceQuery.java | 58 ---- .../api/query/WorkflowInstanceQuery.java | 52 --- .../api/request/WorkflowLogQueryRequest.java | 52 --- .../api/request/WorkflowStartRequest.java | 35 -- .../workflow/api/response/WorkflowLogDTO.java | 65 ---- .../controller/NodeInstanceApiController.java | 64 ++-- .../WorkflowDefinitionApiController.java | 43 ++- .../WorkflowInstanceApiController.java | 98 +++--- .../controller/WorkflowLogApiController.java | 64 ++++ .../converter/NodeInstanceConverter.java | 4 +- .../WorkflowDefinitionConverter.java | 2 +- .../converter/WorkflowInstanceConverter.java | 5 +- .../converter/WorkflowLogConverter.java | 2 +- .../converter/WorkflowVariableConverter.java | 13 + .../workflow/dto/NodeDefinitionDTO.java | 47 +++ .../{api => }/dto/NodeInstanceDTO.java | 30 +- .../workflow/dto/WorkflowDefinitionDTO.java | 46 +++ .../{api => }/dto/WorkflowInstanceDTO.java | 22 +- .../backend/workflow/dto/WorkflowLogDTO.java | 41 +++ .../workflow/dto/WorkflowVariableDTO.java | 45 +++ .../engine/DefaultWorkflowEngine.java | 222 ++++++++++++ .../workflow/engine/WorkflowEngine.java | 55 ++- .../context/DefaultWorkflowContext.java | 75 ++++ .../engine/context/WorkflowContext.java | 42 +++ .../exception/WorkflowEngineException.java | 20 ++ .../engine/executor/EndNodeExecutor.java | 53 +++ .../engine/executor/GatewayCondition.java | 22 ++ .../engine/executor/GatewayConfig.java | 38 ++ .../engine/executor/GatewayNodeExecutor.java | 149 ++++++++ .../workflow/engine/executor/GatewayType.java | 16 + .../engine/executor/NodeExecutor.java | 31 ++ .../engine/executor/StartNodeExecutor.java | 36 ++ .../workflow/engine/executor/TaskConfig.java | 44 +++ .../engine/executor/TaskNodeExecutor.java | 131 +++++++ .../workflow/engine/executor/TaskType.java | 16 + .../executor/task/HttpTaskExecutor.java | 61 ++++ .../executor/task/JavaTaskExecutor.java | 51 +++ .../executor/task/ShellTaskExecutor.java | 66 ++++ .../engine/executor/task/TaskExecutor.java | 22 ++ .../engine/impl/DefaultWorkflowEngine.java | 220 ------------ .../workflow/engine/model/NodeConfig.java | 1 + .../engine/model/TransitionConfig.java | 1 + .../engine/node/AbstractNodeExecutor.java | 135 +++----- .../node/executor/ShellNodeExecutor.java | 150 +++++--- .../parser/WorkflowDefinitionParser.java | 151 ++++++++ .../engine/transition/TransitionExecutor.java | 63 ++++ .../engine/transition/TransitionRule.java | 28 ++ .../transition/TransitionRuleEngine.java | 79 +++++ .../workflow/entity/NodeDefinition.java | 52 +++ .../workflow/entity/WorkflowDefinition.java | 40 +-- .../workflow/entity/WorkflowInstance.java | 2 +- .../backend/workflow/entity/WorkflowLog.java | 38 +- .../workflow/entity/WorkflowPermission.java | 48 --- .../workflow/entity/WorkflowVariable.java | 21 +- .../workflow/enums/NodeStatusEnum.java | 4 +- .../backend/workflow/enums/NodeTypeEnum.java | 20 +- .../workflow/enums/VariableScopeEnum.java | 19 + .../workflow/enums/WorkflowStatusEnum.java | 6 +- .../workflow/query/NodeInstanceQuery.java | 29 ++ .../query/WorkflowDefinitionQuery.java | 27 +- .../workflow/query/WorkflowInstanceQuery.java | 22 ++ .../workflow/query/WorkflowLogQuery.java | 25 ++ .../repository/INodeDefinitionRepository.java | 24 ++ .../repository/INodeInstanceRepository.java | 35 +- .../IWorkflowDefinitionRepository.java | 17 +- .../IWorkflowInstanceRepository.java | 22 +- .../repository/IWorkflowLogRepository.java | 69 +--- .../IWorkflowPermissionRepository.java | 123 ------- .../IWorkflowVariableRepository.java | 36 +- .../service/INodeInstanceService.java | 48 --- .../service/IWorkflowDefinitionService.java | 39 ++- .../service/IWorkflowInstanceService.java | 44 +-- .../workflow/service/IWorkflowLogService.java | 135 +------- .../service/IWorkflowPermissionService.java | 96 ------ .../service/IWorkflowVariableService.java | 56 +-- .../service/impl/NodeInstanceServiceImpl.java | 80 ----- .../impl/WorkflowDefinitionServiceImpl.java | 98 +++--- .../impl/WorkflowInstanceServiceImpl.java | 64 +--- .../service/impl/WorkflowLogServiceImpl.java | 186 ++-------- .../impl/WorkflowPermissionServiceImpl.java | 127 ------- .../impl/WorkflowVariableServiceImpl.java | 163 +++++---- .../db/migration/V1.0.0__init_schema.sql | 208 +++++------ .../db/migration/V1.0.1__init_data.sql | 117 ------- .../src/main/resources/messages.properties | 41 ++- .../impl/ExternalSystemServiceImplTest.java | 29 +- .../workflow/engine/WorkflowEngineTest.java | 325 ++++++++++++++++++ .../WorkflowDefinitionServiceTest.java | 95 +++++ 92 files changed, 3102 insertions(+), 2500 deletions(-) delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/api/WorkflowApiController.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/api/dto/WorkflowDefinitionDTO.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/api/query/NodeInstanceQuery.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/api/query/WorkflowInstanceQuery.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/api/request/WorkflowLogQueryRequest.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/api/request/WorkflowStartRequest.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/api/response/WorkflowLogDTO.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/controller/WorkflowLogApiController.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/WorkflowVariableConverter.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/NodeDefinitionDTO.java rename backend/src/main/java/com/qqchen/deploy/backend/workflow/{api => }/dto/NodeInstanceDTO.java (69%) create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowDefinitionDTO.java rename backend/src/main/java/com/qqchen/deploy/backend/workflow/{api => }/dto/WorkflowInstanceDTO.java (66%) create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowLogDTO.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowVariableDTO.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/DefaultWorkflowEngine.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/context/DefaultWorkflowContext.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/context/WorkflowContext.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/exception/WorkflowEngineException.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/GatewayCondition.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/GatewayConfig.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/GatewayNodeExecutor.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/GatewayType.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/NodeExecutor.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/StartNodeExecutor.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/TaskConfig.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/TaskNodeExecutor.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/TaskType.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/task/HttpTaskExecutor.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/task/JavaTaskExecutor.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/task/ShellTaskExecutor.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/task/TaskExecutor.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/impl/DefaultWorkflowEngine.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/model/NodeConfig.java 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/parser/WorkflowDefinitionParser.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/transition/TransitionExecutor.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/transition/TransitionRule.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/transition/TransitionRuleEngine.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/NodeDefinition.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowPermission.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/VariableScopeEnum.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/query/NodeInstanceQuery.java rename backend/src/main/java/com/qqchen/deploy/backend/workflow/{api => }/query/WorkflowDefinitionQuery.java (61%) create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/query/WorkflowInstanceQuery.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/query/WorkflowLogQuery.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/INodeDefinitionRepository.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowPermissionRepository.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/service/INodeInstanceService.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowPermissionService.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/NodeInstanceServiceImpl.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowPermissionServiceImpl.java create mode 100644 backend/src/test/java/com/qqchen/deploy/backend/workflow/engine/WorkflowEngineTest.java create mode 100644 backend/src/test/java/com/qqchen/deploy/backend/workflow/service/WorkflowDefinitionServiceTest.java diff --git a/backend/pom.xml b/backend/pom.xml index 039ea8fd..0c60cf85 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -190,6 +190,12 @@ assertj-core test + + + org.apache.commons + commons-exec + 1.3 + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/framework/enums/ResponseCode.java b/backend/src/main/java/com/qqchen/deploy/backend/framework/enums/ResponseCode.java index 5845da6b..547bbe7d 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/framework/enums/ResponseCode.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/framework/enums/ResponseCode.java @@ -98,6 +98,9 @@ public enum ResponseCode { WORKFLOW_NOT_PUBLISHED(2704, "workflow.not.published"), WORKFLOW_ALREADY_PUBLISHED(2705, "workflow.already.published"), WORKFLOW_ALREADY_DISABLED(2706, "workflow.already.disabled"), + WORKFLOW_NOT_DRAFT(2707, "workflow.not.draft"), + WORKFLOW_NOT_DISABLED(2708, "workflow.not.disabled"), + WORKFLOW_INVALID_STATUS(2709, "workflow.invalid.status"), WORKFLOW_INSTANCE_NOT_FOUND(2710, "workflow.instance.not.found"), WORKFLOW_INSTANCE_ALREADY_COMPLETED(2711, "workflow.instance.already.completed"), WORKFLOW_INSTANCE_ALREADY_CANCELED(2712, "workflow.instance.already.canceled"), @@ -107,6 +110,8 @@ public enum ResponseCode { WORKFLOW_NODE_CONFIG_INVALID(2722, "workflow.node.config.invalid"), WORKFLOW_NODE_EXECUTION_FAILED(2723, "workflow.node.execution.failed"), WORKFLOW_NODE_TIMEOUT(2724, "workflow.node.timeout"), + WORKFLOW_NODE_CONFIG_ERROR(2725, "workflow.node.config.error"), + WORKFLOW_EXECUTION_ERROR(2726, "workflow.execution.error"), WORKFLOW_VARIABLE_NOT_FOUND(2730, "workflow.variable.not.found"), WORKFLOW_VARIABLE_TYPE_INVALID(2731, "workflow.variable.type.invalid"), WORKFLOW_PERMISSION_DENIED(2740, "workflow.permission.denied"), diff --git a/backend/src/main/java/com/qqchen/deploy/backend/system/service/impl/ExternalSystemServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/system/service/impl/ExternalSystemServiceImpl.java index 8e8940b9..033e10dc 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/system/service/impl/ExternalSystemServiceImpl.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/system/service/impl/ExternalSystemServiceImpl.java @@ -62,7 +62,7 @@ public class ExternalSystemServiceImpl extends BaseServiceImpl startWorkflow(@Valid @RequestBody WorkflowStartRequest request) { - return Response.success(workflowInstanceService.start( - request.getDefinitionId(), - request.getProjectEnvId(), - request.getVariables() != null ? request.getVariables().toString() : null - )); - } - - @Operation(summary = "取消工作流") - @PostMapping("/{instanceId}/cancel") - public Response cancelWorkflow( - @Parameter(description = "工作流实例ID", required = true) @PathVariable Long instanceId - ) { - return Response.success(workflowInstanceService.cancel(instanceId)); - } - - @Operation(summary = "获取工作流实例详情") - @GetMapping("/{instanceId}") - public Response getWorkflowInstance( - @Parameter(description = "工作流实例ID", required = true) @PathVariable Long instanceId - ) { - return Response.success(workflowInstanceService.findById(instanceId)); - } - - @Operation(summary = "获取工作流节点列表") - @GetMapping("/{instanceId}/nodes") - public Response> getWorkflowNodes( - @Parameter(description = "工作流实例ID", required = true) @PathVariable Long instanceId - ) { - return Response.success(nodeInstanceService.findByWorkflowInstanceId(instanceId)); - } - - @Operation(summary = "重试工作流节点") - @PostMapping("/node/{nodeId}/retry") - public Response retryNode( - @Parameter(description = "节点实例ID", required = true) @PathVariable Long nodeId - ) { - // TODO: 实现节点重试逻辑 - return Response.success(true); - } - - @Operation(summary = "跳过工作流节点") - @PostMapping("/node/{nodeId}/skip") - public Response skipNode( - @Parameter(description = "节点实例ID", required = true) @PathVariable Long nodeId - ) { - // TODO: 实现节点跳过逻辑 - return Response.success(true); - } - - @Operation(summary = "查询工作流日志") - @PostMapping("/logs") - public Response> getLogs(@Valid @RequestBody WorkflowLogQueryRequest request) { - Page logs = workflowLogService.findLogs( - request.getWorkflowInstanceId(), - request.getType(), - request.getLevel(), - PageRequest.of(request.getPageNum() - 1, request.getPageSize()) - ); - return Response.success(logs.map(workflowLogConverter::toDto)); - } - - @Operation(summary = "查询节点日志") - @GetMapping("/node/{nodeId}/logs") - public Response> getNodeLogs( - @Parameter(description = "节点实例ID", required = true) @PathVariable Long nodeId, - @Parameter(description = "日志类型") @RequestParam(required = false) LogTypeEnum type, - @Parameter(description = "日志级别") @RequestParam(required = false) LogLevelEnum level - ) { - List logs = workflowLogService.findNodeLogs(nodeId, type, level); - return Response.success(logs.stream() - .map(workflowLogConverter::toDto) - .collect(Collectors.toList())); - } - - @Operation(summary = "分页查询节点日志") - @GetMapping("/node/{nodeId}/logs/page") - public Response> getNodeLogsPage( - @Parameter(description = "节点实例ID", required = true) @PathVariable Long nodeId, - @Parameter(description = "日志类型") @RequestParam(required = false) LogTypeEnum type, - @Parameter(description = "日志级别") @RequestParam(required = false) LogLevelEnum level, - @Parameter(description = "页码") @RequestParam(defaultValue = "1") Integer pageNum, - @Parameter(description = "每页大小") @RequestParam(defaultValue = "20") Integer pageSize - ) { - Page logs = workflowLogService.findNodeLogs( - nodeId, type, level, PageRequest.of(pageNum - 1, pageSize) - ); - return Response.success(logs.map(workflowLogConverter::toDto)); - } -} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/dto/WorkflowDefinitionDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/dto/WorkflowDefinitionDTO.java deleted file mode 100644 index 2d779e9e..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/dto/WorkflowDefinitionDTO.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.qqchen.deploy.backend.workflow.api.dto; - -import com.qqchen.deploy.backend.framework.dto.BaseDTO; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Size; -import lombok.Data; -import lombok.EqualsAndHashCode; - -/** - * 工作流定义DTO - */ -@Data -@EqualsAndHashCode(callSuper = true) -public class WorkflowDefinitionDTO extends BaseDTO { - - /** - * 工作流编码 - */ - @NotBlank(message = "工作流编码不能为空") - @Size(max = 50, message = "工作流编码长度不能超过50") - private String code; - - /** - * 工作流名称 - */ - @NotBlank(message = "工作流名称不能为空") - @Size(max = 100, message = "工作流名称长度不能超过100") - private String name; - - /** - * 描述 - */ - private String description; - - /** - * 工作流定义(JSON) - */ - @NotBlank(message = "工作流定义不能为空") - private String content; - - /** - * 类型:DEPLOY/CONFIG_SYNC - */ - @NotBlank(message = "工作流类型不能为空") - @Size(max = 20, message = "工作流类型长度不能超过20") - private String type; - - /** - * 状态:DRAFT/PUBLISHED/DISABLED - */ - private String status; -} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/query/NodeInstanceQuery.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/query/NodeInstanceQuery.java deleted file mode 100644 index fdde4a8c..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/query/NodeInstanceQuery.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.qqchen.deploy.backend.workflow.api.query; - -import com.qqchen.deploy.backend.framework.annotation.QueryField; -import com.qqchen.deploy.backend.framework.enums.QueryType; -import com.qqchen.deploy.backend.framework.query.BaseQuery; -import lombok.Data; -import lombok.EqualsAndHashCode; -import java.time.LocalDateTime; - -/** - * 节点实例查询对象 - */ -@Data -@EqualsAndHashCode(callSuper = true) -public class NodeInstanceQuery extends BaseQuery { - - /** - * 工作流实例ID - */ - @QueryField(field = "workflowInstance.id") - private Long workflowInstanceId; - - /** - * 节点ID - */ - @QueryField(field = "nodeId") - private String nodeId; - - /** - * 节点类型 - */ - @QueryField(field = "nodeType") - private String nodeType; - - /** - * 节点名称 - */ - @QueryField(field = "name", type = QueryType.LIKE) - private String name; - - /** - * 节点状态 - */ - @QueryField(field = "status") - private String status; - -// /** -// * 开始时间范围(起始) -// */ -// @QueryField(field = "startTime", type = QueryType.BETWEEN) -// private LocalDateTime startTimeBegin; -// -// /** -// * 开始时间范围(结束) -// */ -// @QueryField(field = "startTime", type = QueryType.LESS_THAN_OR_EQUAL) -// private LocalDateTime startTimeEnd; -} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/query/WorkflowInstanceQuery.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/query/WorkflowInstanceQuery.java deleted file mode 100644 index 01b3289e..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/query/WorkflowInstanceQuery.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.qqchen.deploy.backend.workflow.api.query; - -import com.qqchen.deploy.backend.framework.annotation.QueryField; -import com.qqchen.deploy.backend.framework.enums.QueryType; -import com.qqchen.deploy.backend.framework.query.BaseQuery; -import lombok.Data; -import lombok.EqualsAndHashCode; -import java.time.LocalDateTime; - -/** - * 工作流实例查询对象 - */ -@Data -@EqualsAndHashCode(callSuper = true) -public class WorkflowInstanceQuery extends BaseQuery { - - /** - * 工作流定义ID - */ - @QueryField(field = "definition.id") - private Long definitionId; - - /** - * 项目环境ID - */ - @QueryField(field = "projectEnvId") - private Long projectEnvId; - - /** - * 工作流状态 - */ - @QueryField(field = "status") - private String status; - -// /** -// * 开始时间范围(起始) -// */ -// @QueryField(field = "startTime", type = QueryType.GREATER_THAN_OR_EQUAL) -// private LocalDateTime startTimeBegin; -// -// /** -// * 开始时间范围(结束) -// */ -// @QueryField(field = "startTime", type = QueryType.LESS_THAN_OR_EQUAL) -// private LocalDateTime startTimeEnd; - - /** - * 工作流定义名称 - */ - @QueryField(field = "definition.name", type = QueryType.LIKE) - private String workflowName; -} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/request/WorkflowLogQueryRequest.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/request/WorkflowLogQueryRequest.java deleted file mode 100644 index e829f38e..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/request/WorkflowLogQueryRequest.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.qqchen.deploy.backend.workflow.api.request; - -import com.qqchen.deploy.backend.enums.LogLevelEnum; -import com.qqchen.deploy.backend.enums.LogTypeEnum; -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -/** - * 工作流日志查询请求 - */ -@Data -@Schema(description = "工作流日志查询请求") -public class WorkflowLogQueryRequest { - - /** - * 工作流实例ID - */ - @NotNull(message = "工作流实例ID不能为空") - @Schema(description = "工作流实例ID", required = true) - private Long workflowInstanceId; - - /** - * 节点实例ID - */ - @Schema(description = "节点实例ID") - private Long nodeInstanceId; - - /** - * 日志类型 - */ - @Schema(description = "日志类型") - private LogTypeEnum type; - - /** - * 日志级别 - */ - @Schema(description = "日志级别") - private LogLevelEnum level; - - /** - * 页码 - */ - @Schema(description = "页码", defaultValue = "1") - private Integer pageNum = 1; - - /** - * 每页大小 - */ - @Schema(description = "每页大小", defaultValue = "20") - private Integer pageSize = 20; -} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/request/WorkflowStartRequest.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/request/WorkflowStartRequest.java deleted file mode 100644 index 62ed566a..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/request/WorkflowStartRequest.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.qqchen.deploy.backend.workflow.api.request; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -import java.util.Map; - -/** - * 工作流启动请求 - */ -@Data -@Schema(description = "工作流启动请求") -public class WorkflowStartRequest { - - /** - * 工作流定义ID - */ - @NotNull(message = "工作流定义ID不能为空") - @Schema(description = "工作流定义ID", required = true) - private Long definitionId; - - /** - * 项目环境ID - */ - @NotNull(message = "项目环境ID不能为空") - @Schema(description = "项目环境ID", required = true) - private Long projectEnvId; - - /** - * 工作流变量 - */ - @Schema(description = "工作流变量") - private Map variables; -} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/response/WorkflowLogDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/response/WorkflowLogDTO.java deleted file mode 100644 index 43d5932f..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/response/WorkflowLogDTO.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.qqchen.deploy.backend.workflow.api.response; - -import com.qqchen.deploy.backend.enums.LogLevelEnum; -import com.qqchen.deploy.backend.enums.LogTypeEnum; -import com.qqchen.deploy.backend.framework.dto.BaseDTO; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import java.time.LocalDateTime; - -/** - * 工作流日志DTO - */ -@Data -@Schema(description = "工作流日志") -public class WorkflowLogDTO extends BaseDTO { - - /** - * 日志ID - */ - @Schema(description = "日志ID") - private Long id; - - /** - * 工作流实例ID - */ - @Schema(description = "工作流实例ID") - private Long workflowInstanceId; - - /** - * 节点实例ID - */ - @Schema(description = "节点实例ID") - private Long nodeInstanceId; - - /** - * 日志类型 - */ - @Schema(description = "日志类型") - private LogTypeEnum type; - - /** - * 日志级别 - */ - @Schema(description = "日志级别") - private LogLevelEnum level; - - /** - * 日志内容 - */ - @Schema(description = "日志内容") - private String content; - - /** - * 详细信息 - */ - @Schema(description = "详细信息") - private String detail; - - /** - * 创建时间 - */ - @Schema(description = "创建时间") - private LocalDateTime createTime; -} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/controller/NodeInstanceApiController.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/controller/NodeInstanceApiController.java index 01661007..d43aaed0 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/controller/NodeInstanceApiController.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/controller/NodeInstanceApiController.java @@ -2,10 +2,10 @@ package com.qqchen.deploy.backend.workflow.controller; import com.qqchen.deploy.backend.framework.api.Response; import com.qqchen.deploy.backend.framework.controller.BaseController; -import com.qqchen.deploy.backend.workflow.api.dto.NodeInstanceDTO; -import com.qqchen.deploy.backend.workflow.api.query.NodeInstanceQuery; +import com.qqchen.deploy.backend.workflow.dto.NodeInstanceDTO; import com.qqchen.deploy.backend.workflow.entity.NodeInstance; -import com.qqchen.deploy.backend.workflow.service.INodeInstanceService; +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; @@ -15,58 +15,38 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; import java.util.List; +import java.util.Map; -/** - * 节点实例控制器 - */ @Slf4j @RestController -@RequestMapping("/api/v1/workflow/node") -@Tag(name = "工作流节点管理", description = "工作流节点管理相关接口") +@RequestMapping("/api/v1/node-instances") +@Tag(name = "节点实例管理", description = "节点实例管理相关接口") public class NodeInstanceApiController extends BaseController { @Resource - private INodeInstanceService nodeInstanceService; + private WorkflowEngine workflowEngine; - - @Operation(summary = "根据工作流实例ID查询节点实例列表") - @GetMapping("/workflow/{workflowInstanceId}") - public Response> findByWorkflowInstanceId( - @Parameter(description = "工作流实例ID", required = true) - @PathVariable Long workflowInstanceId) { - return Response.success(nodeInstanceService.findByWorkflowInstanceId(workflowInstanceId)); + @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 = "根据工作流实例ID和状态查询节点实例列表") - @GetMapping("/workflow/{workflowInstanceId}/status/{status}") - public Response> findByWorkflowInstanceIdAndStatus( - @Parameter(description = "工作流实例ID", required = true) - @PathVariable Long workflowInstanceId, - @Parameter(description = "状态", required = true) - @PathVariable String status) { - return Response.success(nodeInstanceService.findByWorkflowInstanceIdAndStatus(workflowInstanceId, status)); - } - - @Operation(summary = "更新节点状态") - @PostMapping("/{id}/status") - public Response updateStatus( - @Parameter(description = "节点实例ID", required = true) - @PathVariable Long id, - @Parameter(description = "状态", required = true) - @RequestParam String status, - @Parameter(description = "输出结果") - @RequestParam(required = false) String output, - @Parameter(description = "错误信息") - @RequestParam(required = false) String error) { - return Response.success(nodeInstanceService.updateStatus(id, status, output, error)); + @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: 实现导出功能 } - - protected INodeInstanceService getService() { - return nodeInstanceService; - } } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/controller/WorkflowDefinitionApiController.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/controller/WorkflowDefinitionApiController.java index 53d60c1d..4cadabd6 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/controller/WorkflowDefinitionApiController.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/controller/WorkflowDefinitionApiController.java @@ -2,9 +2,9 @@ package com.qqchen.deploy.backend.workflow.controller; import com.qqchen.deploy.backend.framework.api.Response; import com.qqchen.deploy.backend.framework.controller.BaseController; -import com.qqchen.deploy.backend.workflow.api.dto.WorkflowDefinitionDTO; -import com.qqchen.deploy.backend.workflow.api.query.WorkflowDefinitionQuery; +import com.qqchen.deploy.backend.workflow.dto.WorkflowDefinitionDTO; import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition; +import com.qqchen.deploy.backend.workflow.query.WorkflowDefinitionQuery; import com.qqchen.deploy.backend.workflow.service.IWorkflowDefinitionService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -16,12 +16,9 @@ import org.springframework.web.bind.annotation.*; import java.util.List; -/** - * 工作流定义控制器 - */ @Slf4j @RestController -@RequestMapping("/api/v1/workflow/definition") +@RequestMapping("/api/v1/workflow-definitions") @Tag(name = "工作流定义管理", description = "工作流定义管理相关接口") public class WorkflowDefinitionApiController extends BaseController { @@ -30,31 +27,33 @@ public class WorkflowDefinitionApiController extends BaseController publish( - @Parameter(description = "工作流定义ID", required = true) - @PathVariable Long id) { - return Response.success(workflowDefinitionService.publish(id)); + public Response publish( + @Parameter(description = "工作流定义ID", required = true) @PathVariable Long id + ) { + workflowDefinitionService.publish(id); + return Response.success(); } @Operation(summary = "禁用工作流") @PostMapping("/{id}/disable") - public Response disable( - @Parameter(description = "工作流定义ID", required = true) - @PathVariable Long id) { - return Response.success(workflowDefinitionService.disable(id)); + public Response disable( + @Parameter(description = "工作流定义ID", required = true) @PathVariable Long id + ) { + workflowDefinitionService.disable(id); + return Response.success(); } - @Operation(summary = "根据编码查询工作流定义") - @GetMapping("/code/{code}") - public Response findByCode( - @Parameter(description = "工作流编码", required = true) - @PathVariable String code) { - return Response.success(workflowDefinitionService.findByCode(code)); + @Operation(summary = "启用工作流") + @PostMapping("/{id}/enable") + public Response enable( + @Parameter(description = "工作流定义ID", required = true) @PathVariable Long id + ) { + workflowDefinitionService.enable(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/controller/WorkflowInstanceApiController.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/controller/WorkflowInstanceApiController.java index 1de44b97..22323005 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/controller/WorkflowInstanceApiController.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/controller/WorkflowInstanceApiController.java @@ -2,78 +2,94 @@ package com.qqchen.deploy.backend.workflow.controller; import com.qqchen.deploy.backend.framework.api.Response; import com.qqchen.deploy.backend.framework.controller.BaseController; -import com.qqchen.deploy.backend.workflow.api.dto.WorkflowInstanceDTO; -import com.qqchen.deploy.backend.workflow.api.query.WorkflowInstanceQuery; +import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceDTO; import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; +import com.qqchen.deploy.backend.workflow.engine.WorkflowEngine; +import com.qqchen.deploy.backend.workflow.query.WorkflowInstanceQuery; import com.qqchen.deploy.backend.workflow.service.IWorkflowInstanceService; +import com.qqchen.deploy.backend.workflow.converter.WorkflowInstanceConverter; 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.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; import java.util.List; +import java.util.Map; -/** - * 工作流实例控制器 - */ @Slf4j @RestController -@RequestMapping("/api/v1/workflow/instance") +@RequestMapping("/api/v1/workflow-instances") @Tag(name = "工作流实例管理", description = "工作流实例管理相关接口") public class WorkflowInstanceApiController extends BaseController { - private final IWorkflowInstanceService workflowInstanceService; + @Resource + private WorkflowEngine workflowEngine; - public WorkflowInstanceApiController(IWorkflowInstanceService workflowInstanceService) { - this.workflowInstanceService = workflowInstanceService; - } + @Resource + private IWorkflowInstanceService workflowInstanceService; + + @Resource + private WorkflowInstanceConverter converter; @Operation(summary = "启动工作流实例") @PostMapping("/start") - public Response start( - @Parameter(description = "工作流定义ID", required = true) - @RequestParam Long definitionId, - @Parameter(description = "项目环境ID", required = true) - @RequestParam Long projectEnvId, - @Parameter(description = "工作流变量") - @RequestParam(required = false) String variables) { - return Response.success(workflowInstanceService.start(definitionId, projectEnvId, variables)); + public Response startWorkflow(@RequestBody StartWorkflowRequest request) { + WorkflowInstance instance = workflowEngine.startWorkflow( + request.getWorkflowCode(), + request.getBusinessKey(), + request.getVariables() + ); + return Response.success(converter.toDto(instance)); } - @Operation(summary = "取消工作流实例") - @PostMapping("/{id}/cancel") - public Response cancel( - @Parameter(description = "工作流实例ID", required = true) - @PathVariable Long id) { - return Response.success(workflowInstanceService.cancel(id)); + @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 = "根据项目环境ID查询工作流实例列表") - @GetMapping("/project-env/{projectEnvId}") - public Response> findByProjectEnvId( - @Parameter(description = "项目环境ID", required = true) - @PathVariable Long projectEnvId) { - return Response.success(workflowInstanceService.findByProjectEnvId(projectEnvId)); + @Operation(summary = "暂停工作流实例") + @PostMapping("/{id}/pause") + public Response pauseWorkflow( + @Parameter(description = "工作流实例ID", required = true) @PathVariable Long id + ) { + workflowEngine.pauseWorkflow(id); + return Response.success(); } - @Operation(summary = "根据项目环境ID和状态查询工作流实例列表") - @GetMapping("/project-env/{projectEnvId}/status/{status}") - public Response> findByProjectEnvIdAndStatus( - @Parameter(description = "项目环境ID", required = true) - @PathVariable Long projectEnvId, - @Parameter(description = "状态", required = true) - @PathVariable String status) { - return Response.success(workflowInstanceService.findByProjectEnvIdAndStatus(projectEnvId, status)); + @Operation(summary = "恢复工作流实例") + @PostMapping("/{id}/resume") + public Response resumeWorkflow( + @Parameter(description = "工作流实例ID", required = true) @PathVariable Long id + ) { + workflowEngine.resumeWorkflow(id); + return Response.success(); } @Override - protected void exportData(HttpServletResponse response, List data) { + public void exportData(HttpServletResponse response, List data) { // TODO: 实现导出功能 } - protected IWorkflowInstanceService getService() { - return workflowInstanceService; + + @Data + public static class StartWorkflowRequest { + + @Parameter(description = "工作流编码", required = true) + private String workflowCode; + + @Parameter(description = "业务标识", required = true) + private String businessKey; + + @Parameter(description = "工作流变量") + private Map variables; } } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/controller/WorkflowLogApiController.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/controller/WorkflowLogApiController.java new file mode 100644 index 00000000..eed949f8 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/controller/WorkflowLogApiController.java @@ -0,0 +1,64 @@ +package com.qqchen.deploy.backend.workflow.controller; + +import com.qqchen.deploy.backend.enums.LogLevelEnum; +import com.qqchen.deploy.backend.framework.api.Response; +import com.qqchen.deploy.backend.framework.controller.BaseController; +import com.qqchen.deploy.backend.workflow.dto.WorkflowLogDTO; +import com.qqchen.deploy.backend.workflow.entity.WorkflowLog; +import com.qqchen.deploy.backend.workflow.query.WorkflowLogQuery; +import com.qqchen.deploy.backend.workflow.service.IWorkflowLogService; +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; + +@Slf4j +@RestController +@RequestMapping("/api/v1/workflow-logs") +@Tag(name = "工作流日志管理", description = "工作流日志管理相关接口") +public class WorkflowLogApiController extends BaseController { + + @Resource + private IWorkflowLogService workflowLogService; + + @Operation(summary = "查询工作流实例日志") + @GetMapping("/workflow/{workflowInstanceId}") + public Response> getWorkflowLogs( + @Parameter(description = "工作流实例ID", required = true) @PathVariable Long workflowInstanceId + ) { + return Response.success(workflowLogService.getLogs(workflowInstanceId)); + } + + @Operation(summary = "查询节点实例日志") + @GetMapping("/node/{workflowInstanceId}/{nodeId}") + public Response> getNodeLogs( + @Parameter(description = "工作流实例ID", required = true) @PathVariable Long workflowInstanceId, + @Parameter(description = "节点ID", required = true) @PathVariable String nodeId + ) { + return Response.success(workflowLogService.getNodeLogs(workflowInstanceId, nodeId)); + } + + @Operation(summary = "记录日志") + @PostMapping + public Response log( + @Parameter(description = "工作流实例ID", required = true) @RequestParam Long workflowInstanceId, + @Parameter(description = "节点ID") @RequestParam(required = false) String nodeId, + @Parameter(description = "日志内容", required = true) @RequestParam String message, + @Parameter(description = "日志级别", required = true) @RequestParam LogLevelEnum level, + @Parameter(description = "详细信息") @RequestParam(required = false) String detail + ) { + workflowLogService.log(workflowInstanceId, nodeId, message, level, detail); + return Response.success(); + } + + @Override + public 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/converter/NodeInstanceConverter.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/NodeInstanceConverter.java index 5c6829a8..64caefa8 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/NodeInstanceConverter.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/NodeInstanceConverter.java @@ -1,7 +1,7 @@ package com.qqchen.deploy.backend.workflow.converter; import com.qqchen.deploy.backend.framework.converter.BaseConverter; -import com.qqchen.deploy.backend.workflow.api.dto.NodeInstanceDTO; +import com.qqchen.deploy.backend.workflow.dto.NodeInstanceDTO; import com.qqchen.deploy.backend.workflow.entity.NodeInstance; import org.mapstruct.Mapper; import org.mapstruct.Mapping; @@ -12,9 +12,11 @@ import org.mapstruct.Mapping; @Mapper(config = BaseConverter.class) public interface NodeInstanceConverter extends BaseConverter { + @Override @Mapping(target = "workflowInstanceId", source = "workflowInstance.id") NodeInstanceDTO toDto(NodeInstance entity); + @Override @Mapping(target = "workflowInstance.id", source = "workflowInstanceId") NodeInstance toEntity(NodeInstanceDTO dto); } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/WorkflowDefinitionConverter.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/WorkflowDefinitionConverter.java index 611447a1..95a51649 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/WorkflowDefinitionConverter.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/WorkflowDefinitionConverter.java @@ -1,7 +1,7 @@ package com.qqchen.deploy.backend.workflow.converter; import com.qqchen.deploy.backend.framework.converter.BaseConverter; -import com.qqchen.deploy.backend.workflow.api.dto.WorkflowDefinitionDTO; +import com.qqchen.deploy.backend.workflow.dto.WorkflowDefinitionDTO; import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition; import org.mapstruct.Mapper; diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/WorkflowInstanceConverter.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/WorkflowInstanceConverter.java index ef8664cf..80668abd 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/WorkflowInstanceConverter.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/WorkflowInstanceConverter.java @@ -1,7 +1,7 @@ package com.qqchen.deploy.backend.workflow.converter; import com.qqchen.deploy.backend.framework.converter.BaseConverter; -import com.qqchen.deploy.backend.workflow.api.dto.WorkflowInstanceDTO; +import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceDTO; import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; import org.mapstruct.Mapper; import org.mapstruct.Mapping; @@ -12,10 +12,11 @@ import org.mapstruct.Mapping; @Mapper(config = BaseConverter.class) public interface WorkflowInstanceConverter extends BaseConverter { + @Override @Mapping(target = "definitionId", source = "definition.id") - @Mapping(target = "workflowName", source = "definition.name") WorkflowInstanceDTO toDto(WorkflowInstance entity); + @Override @Mapping(target = "definition.id", source = "definitionId") WorkflowInstance toEntity(WorkflowInstanceDTO dto); } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/WorkflowLogConverter.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/WorkflowLogConverter.java index f2760192..97ce2971 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/WorkflowLogConverter.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/WorkflowLogConverter.java @@ -1,7 +1,7 @@ package com.qqchen.deploy.backend.workflow.converter; import com.qqchen.deploy.backend.framework.converter.BaseConverter; -import com.qqchen.deploy.backend.workflow.api.response.WorkflowLogDTO; +import com.qqchen.deploy.backend.workflow.dto.WorkflowLogDTO; import com.qqchen.deploy.backend.workflow.entity.WorkflowLog; import org.mapstruct.Mapper; diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/WorkflowVariableConverter.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/WorkflowVariableConverter.java new file mode 100644 index 00000000..d7b7af3a --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/WorkflowVariableConverter.java @@ -0,0 +1,13 @@ +package com.qqchen.deploy.backend.workflow.converter; + +import com.qqchen.deploy.backend.framework.converter.BaseConverter; +import com.qqchen.deploy.backend.workflow.dto.WorkflowVariableDTO; +import com.qqchen.deploy.backend.workflow.entity.WorkflowVariable; +import org.mapstruct.Mapper; + +/** + * 工作流变量转换器 + */ +@Mapper(config = BaseConverter.class) +public interface WorkflowVariableConverter extends BaseConverter { +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/NodeDefinitionDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/NodeDefinitionDTO.java new file mode 100644 index 00000000..d70a4243 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/NodeDefinitionDTO.java @@ -0,0 +1,47 @@ +package com.qqchen.deploy.backend.workflow.dto; + +import com.qqchen.deploy.backend.framework.dto.BaseDTO; +import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class NodeDefinitionDTO extends BaseDTO { + + /** + * 工作流定义ID + */ + @NotNull(message = "工作流定义ID不能为空") + private Long workflowDefinitionId; + + /** + * 节点ID + */ + @NotBlank(message = "节点ID不能为空") + private String nodeId; + + /** + * 节点名称 + */ + @NotBlank(message = "节点名称不能为空") + private String name; + + /** + * 节点类型 + */ + @NotNull(message = "节点类型不能为空") + private NodeTypeEnum type; + + /** + * 节点配置(JSON) + */ + private String config; + + /** + * 序号 + */ + private Integer sequence; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/dto/NodeInstanceDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/NodeInstanceDTO.java similarity index 69% rename from backend/src/main/java/com/qqchen/deploy/backend/workflow/api/dto/NodeInstanceDTO.java rename to backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/NodeInstanceDTO.java index 6c299631..281366f5 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/dto/NodeInstanceDTO.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/NodeInstanceDTO.java @@ -1,16 +1,15 @@ -package com.qqchen.deploy.backend.workflow.api.dto; +package com.qqchen.deploy.backend.workflow.dto; import com.qqchen.deploy.backend.framework.dto.BaseDTO; +import com.qqchen.deploy.backend.workflow.enums.NodeStatusEnum; +import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Size; import lombok.Data; import lombok.EqualsAndHashCode; + import java.time.LocalDateTime; -/** - * 节点实例DTO - */ @Data @EqualsAndHashCode(callSuper = true) public class NodeInstanceDTO extends BaseDTO { @@ -25,27 +24,24 @@ public class NodeInstanceDTO extends BaseDTO { * 节点ID */ @NotBlank(message = "节点ID不能为空") - @Size(max = 50, message = "节点ID长度不能超过50") private String nodeId; /** * 节点类型 */ - @NotBlank(message = "节点类型不能为空") - @Size(max = 50, message = "节点类型长度不能超过50") - private String nodeType; + @NotNull(message = "节点类型不能为空") + private NodeTypeEnum nodeType; /** * 节点名称 */ @NotBlank(message = "节点名称不能为空") - @Size(max = 100, message = "节点名称长度不能超过100") private String name; /** - * 状态:PENDING/RUNNING/COMPLETED/FAILED/CANCELED + * 节点状态 */ - private String status; + private NodeStatusEnum status; /** * 开始时间 @@ -57,6 +53,11 @@ public class NodeInstanceDTO extends BaseDTO { */ private LocalDateTime endTime; + /** + * 节点配置(JSON) + */ + private String config; + /** * 输入参数(JSON) */ @@ -71,4 +72,9 @@ public class NodeInstanceDTO extends BaseDTO { * 错误信息 */ private String error; + + /** + * 前置节点ID + */ + private String preNodeId; } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowDefinitionDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowDefinitionDTO.java new file mode 100644 index 00000000..a2aee719 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowDefinitionDTO.java @@ -0,0 +1,46 @@ +package com.qqchen.deploy.backend.workflow.dto; + +import com.qqchen.deploy.backend.framework.dto.BaseDTO; +import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnum; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class WorkflowDefinitionDTO extends BaseDTO { + + /** + * 工作流名称 + */ + @NotBlank(message = "工作流名称不能为空") + private String name; + + /** + * 工作流编码 + */ + @NotBlank(message = "工作流编码不能为空") + private String code; + + /** + * 描述 + */ + private String description; + + /** + * 状态 + */ + @NotNull(message = "状态不能为空") + private WorkflowStatusEnum status; + + /** + * 节点配置(JSON) + */ + private String nodeConfig; + + /** + * 流转配置(JSON) + */ + private String transitionConfig; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/dto/WorkflowInstanceDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowInstanceDTO.java similarity index 66% rename from backend/src/main/java/com/qqchen/deploy/backend/workflow/api/dto/WorkflowInstanceDTO.java rename to backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowInstanceDTO.java index 2b6340e9..e84ce18d 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/dto/WorkflowInstanceDTO.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowInstanceDTO.java @@ -1,14 +1,13 @@ -package com.qqchen.deploy.backend.workflow.api.dto; +package com.qqchen.deploy.backend.workflow.dto; import com.qqchen.deploy.backend.framework.dto.BaseDTO; +import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnum; import jakarta.validation.constraints.NotNull; import lombok.Data; import lombok.EqualsAndHashCode; + import java.time.LocalDateTime; -/** - * 工作流实例DTO - */ @Data @EqualsAndHashCode(callSuper = true) public class WorkflowInstanceDTO extends BaseDTO { @@ -20,15 +19,15 @@ public class WorkflowInstanceDTO extends BaseDTO { private Long definitionId; /** - * 项目环境ID + * 业务标识 */ - @NotNull(message = "项目环境ID不能为空") - private Long projectEnvId; + @NotNull(message = "业务标识不能为空") + private String businessKey; /** - * 状态:RUNNING/COMPLETED/FAILED/CANCELED + * 状态 */ - private String status; + private WorkflowStatusEnum status; /** * 开始时间 @@ -49,9 +48,4 @@ public class WorkflowInstanceDTO extends BaseDTO { * 错误信息 */ private String error; - - /** - * 工作流定义名称(冗余字段) - */ - private String workflowName; } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowLogDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowLogDTO.java new file mode 100644 index 00000000..4b88e44c --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowLogDTO.java @@ -0,0 +1,41 @@ +package com.qqchen.deploy.backend.workflow.dto; + +import com.qqchen.deploy.backend.enums.LogLevelEnum; +import com.qqchen.deploy.backend.framework.dto.BaseDTO; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class WorkflowLogDTO extends BaseDTO { + + /** + * 工作流实例ID + */ + @NotNull(message = "工作流实例ID不能为空") + private Long workflowInstanceId; + + /** + * 节点ID + */ + private String nodeId; + + /** + * 日志内容 + */ + @NotBlank(message = "日志内容不能为空") + private String message; + + /** + * 日志级别 + */ + @NotNull(message = "日志级别不能为空") + private LogLevelEnum level; + + /** + * 详细信息 + */ + private String detail; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowVariableDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowVariableDTO.java new file mode 100644 index 00000000..dad46991 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowVariableDTO.java @@ -0,0 +1,45 @@ +package com.qqchen.deploy.backend.workflow.dto; + +import com.qqchen.deploy.backend.framework.dto.BaseDTO; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 工作流变量DTO + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class WorkflowVariableDTO extends BaseDTO { + + /** + * 工作流实例ID + */ + @NotNull(message = "工作流实例ID不能为空") + private Long workflowInstanceId; + + /** + * 变量名称 + */ + @NotBlank(message = "变量名称不能为空") + private String name; + + /** + * 变量值(JSON) + */ + @NotBlank(message = "变量值不能为空") + private String value; + + /** + * 变量类型 + */ + @NotBlank(message = "变量类型不能为空") + private String type; + + /** + * 变量作用域 + */ + @NotBlank(message = "变量作用域不能为空") + private String scope; +} \ 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 new file mode 100644 index 00000000..41ef4fd5 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/DefaultWorkflowEngine.java @@ -0,0 +1,222 @@ +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.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.WorkflowStatusEnum; +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 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 Map nodeExecutors; + + @Resource + private DefaultWorkflowContext.Factory workflowContextFactory; + + @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); + } + + if (definition.getStatus() != WorkflowStatusEnum.PUBLISHED) { + throw new WorkflowEngineException(ResponseCode.WORKFLOW_NOT_PUBLISHED); + } + + // 2. 创建工作流实例 + WorkflowInstance instance = new WorkflowInstance(); + instance.setDefinition(definition); + instance.setStatus(WorkflowStatusEnum.RUNNING); + instance.setStartTime(LocalDateTime.now()); + workflowInstanceRepository.save(instance); + + // 3. 创建工作流上下文 + WorkflowContext context = workflowContextFactory.create(instance); + if (variables != null) { + variables.forEach((key, value) -> context.setVariable(key, value)); + } + + // 4. 创建开始节点实例 + NodeInstance startNode = createStartNode(definition, instance.getId()); + executeNode(startNode.getId()); + + return instance; + } + + @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.getStatus() != WorkflowStatusEnum.RUNNING) { + throw new WorkflowEngineException(ResponseCode.WORKFLOW_INSTANCE_NOT_RUNNING); + } + + try { + // 1. 获取节点执行器 + NodeExecutor executor = nodeExecutors.get(nodeInstance.getNodeType()); + if (executor == null) { + throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_TYPE_NOT_SUPPORTED); + } + + // 2. 执行节点 + WorkflowContext context = workflowContextFactory.create(instance); + executor.execute(nodeInstance, context); + nodeInstance.setStatus(NodeStatusEnum.COMPLETED); + nodeInstance.setEndTime(LocalDateTime.now()); + nodeInstanceRepository.save(nodeInstance); + + } catch (Exception e) { + nodeInstance.setStatus(NodeStatusEnum.FAILED); + nodeInstance.setEndTime(LocalDateTime.now()); + nodeInstance.setError(e.getMessage()); + nodeInstanceRepository.save(nodeInstance); + throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED, e); + } + } + + @Override + @Transactional + public void completeNode(Long nodeInstanceId, Map variables) { + NodeInstance nodeInstance = nodeInstanceRepository.findById(nodeInstanceId) + .orElseThrow(() -> new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_NOT_FOUND)); + + if (nodeInstance.getStatus() != NodeStatusEnum.RUNNING) { + throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED, + "Node is not in running status"); + } + + WorkflowInstance instance = nodeInstance.getWorkflowInstance(); + WorkflowContext context = workflowContextFactory.create(instance); + if (variables != null) { + variables.forEach((key, value) -> context.setVariable(key, value)); + } + + 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)); + + if (instance.getStatus() != WorkflowStatusEnum.RUNNING) { + throw new WorkflowEngineException(ResponseCode.WORKFLOW_INSTANCE_NOT_RUNNING); + } + + // 终止所有运行中的节点 + List runningNodes = nodeInstanceRepository.findByInstanceIdAndStatus( + instanceId, NodeStatusEnum.RUNNING); + + WorkflowContext context = workflowContextFactory.create(instance); + for (NodeInstance node : runningNodes) { + NodeExecutor executor = nodeExecutors.get(node.getNodeType()); + if (executor != null) { + executor.terminate(node, context); + } + node.setStatus(NodeStatusEnum.TERMINATED); + node.setEndTime(LocalDateTime.now()); + nodeInstanceRepository.save(node); + } + + instance.setStatus(WorkflowStatusEnum.TERMINATED); + instance.setEndTime(LocalDateTime.now()); + instance.setError(reason); + workflowInstanceRepository.save(instance); + } + + @Override + @Transactional + public void pauseWorkflow(Long instanceId) { + WorkflowInstance instance = workflowInstanceRepository.findById(instanceId) + .orElseThrow(() -> new WorkflowEngineException(ResponseCode.WORKFLOW_INSTANCE_NOT_FOUND)); + + if (instance.getStatus() != WorkflowStatusEnum.RUNNING) { + throw new WorkflowEngineException(ResponseCode.WORKFLOW_INSTANCE_NOT_RUNNING); + } + + // 暂停所有运行中的节点 + List runningNodes = nodeInstanceRepository.findByInstanceIdAndStatus( + instanceId, NodeStatusEnum.RUNNING); + + for (NodeInstance node : runningNodes) { + node.setStatus(NodeStatusEnum.PAUSED); + nodeInstanceRepository.save(node); + } + + instance.setStatus(WorkflowStatusEnum.PAUSED); + 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.getStatus() != WorkflowStatusEnum.PAUSED) { + throw new WorkflowEngineException(ResponseCode.WORKFLOW_INSTANCE_NOT_RUNNING); + } + + // 恢复所有暂停的节点 + List pausedNodes = nodeInstanceRepository.findByInstanceIdAndStatus( + instanceId, NodeStatusEnum.PAUSED); + + for (NodeInstance node : pausedNodes) { + node.setStatus(NodeStatusEnum.RUNNING); + nodeInstanceRepository.save(node); + executeNode(node.getId()); + } + + instance.setStatus(WorkflowStatusEnum.RUNNING); + workflowInstanceRepository.save(instance); + } + + private NodeInstance createStartNode(WorkflowDefinition definition, Long instanceId) { + NodeInstance startNode = new NodeInstance(); + startNode.setWorkflowInstanceId(instanceId); + 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/WorkflowEngine.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/WorkflowEngine.java index 0853f737..8e46750e 100644 --- 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 @@ -1,54 +1,41 @@ package com.qqchen.deploy.backend.workflow.engine; -import com.qqchen.deploy.backend.workflow.api.dto.WorkflowInstanceDTO; +import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; import java.util.Map; /** - * 工作流引擎接口 + * 工作流执行引擎接口 */ public interface WorkflowEngine { - + /** * 启动工作流实例 - * - * @param instanceId 工作流实例ID - * @param variables 工作流变量 */ - void startInstance(Long instanceId, Map variables); - + WorkflowInstance startWorkflow(String workflowCode, String businessKey, Map variables); + /** - * 取消工作流实例 - * - * @param instanceId 工作流实例ID + * 执行节点 */ - void cancelInstance(Long instanceId); - + void executeNode(Long nodeInstanceId); + + /** + * 完成节点 + */ + void completeNode(Long nodeInstanceId, Map variables); + + /** + * 终止工作流实例 + */ + void terminateWorkflow(Long instanceId, String reason); + /** * 暂停工作流实例 - * - * @param instanceId 工作流实例ID */ - void pauseInstance(Long instanceId); - + void pauseWorkflow(Long instanceId); + /** * 恢复工作流实例 - * - * @param instanceId 工作流实例ID */ - void resumeInstance(Long instanceId); - - /** - * 重试工作流节点 - * - * @param nodeInstanceId 节点实例ID - */ - void retryNode(Long nodeInstanceId); - - /** - * 跳过工作流节点 - * - * @param nodeInstanceId 节点实例ID - */ - void skipNode(Long nodeInstanceId); + void resumeWorkflow(Long instanceId); } \ 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 new file mode 100644 index 00000000..828f8527 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/context/DefaultWorkflowContext.java @@ -0,0 +1,75 @@ +package com.qqchen.deploy.backend.workflow.engine.context; + +import com.qqchen.deploy.backend.enums.LogLevelEnum; +import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; +import com.qqchen.deploy.backend.workflow.service.IWorkflowLogService; +import com.qqchen.deploy.backend.workflow.service.IWorkflowVariableService; +import lombok.Getter; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; + +@Component +public class DefaultWorkflowContext implements WorkflowContext { + + @Getter + private final WorkflowInstance instance; + + private final Map variables; + + private final IWorkflowVariableService variableService; + + private final IWorkflowLogService logService; + + private DefaultWorkflowContext(WorkflowInstance instance, IWorkflowVariableService variableService, IWorkflowLogService logService) { + this.instance = instance; + this.variables = new HashMap<>(); + this.variableService = variableService; + this.logService = logService; + } + + @Override + public Map getVariables() { + return new HashMap<>(variables); + } + + @Override + public Object getVariable(String name) { + return variables.get(name); + } + + @Override + public void setVariable(String name, Object value) { + variables.put(name, value); + variableService.setVariable(instance.getId(), name, value); + } + + @Override + public void log(String message, LogLevelEnum level) { + log(message, null, level); + } + + @Override + public void log(String message, String detail, LogLevelEnum level) { + logService.log(instance.getId(), null, message, level, detail); + } + + /** + * 工作流上下文工厂类 + */ + @Component + public static class Factory { + private final IWorkflowVariableService variableService; + private final IWorkflowLogService logService; + + public Factory(IWorkflowVariableService variableService, IWorkflowLogService logService) { + this.variableService = variableService; + this.logService = logService; + } + + public DefaultWorkflowContext create(WorkflowInstance instance) { + return new DefaultWorkflowContext(instance, variableService, logService); + } + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/context/WorkflowContext.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/context/WorkflowContext.java new file mode 100644 index 00000000..11c9916c --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/context/WorkflowContext.java @@ -0,0 +1,42 @@ +package com.qqchen.deploy.backend.workflow.engine.context; + +import com.qqchen.deploy.backend.enums.LogLevelEnum; +import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; + +import java.util.Map; + +/** + * 工作流上下文 + */ +public interface WorkflowContext { + + /** + * 获取工作流实例 + */ + WorkflowInstance getInstance(); + + /** + * 获取所有变量 + */ + Map getVariables(); + + /** + * 获取变量 + */ + Object getVariable(String name); + + /** + * 设置变量 + */ + void setVariable(String name, Object value); + + /** + * 记录日志 + */ + void log(String message, LogLevelEnum level); + + /** + * 记录日志(带详情) + */ + void log(String message, String detail, LogLevelEnum level); +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/exception/WorkflowEngineException.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/exception/WorkflowEngineException.java new file mode 100644 index 00000000..7e75aeba --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/exception/WorkflowEngineException.java @@ -0,0 +1,20 @@ +package com.qqchen.deploy.backend.workflow.engine.exception; + +import com.qqchen.deploy.backend.framework.exception.BusinessException; +import com.qqchen.deploy.backend.framework.enums.ResponseCode; + +public class WorkflowEngineException extends BusinessException { + + public WorkflowEngineException(ResponseCode code) { + super(code); + } + + public WorkflowEngineException(ResponseCode code, String... args) { + super(code, args); + } + + public WorkflowEngineException(ResponseCode code, Throwable cause) { + super(code, new Object[]{cause.getMessage()}); + initCause(cause); + } +} \ 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..fb631d7d --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/EndNodeExecutor.java @@ -0,0 +1,53 @@ +package com.qqchen.deploy.backend.workflow.engine.executor; + +import com.qqchen.deploy.backend.enums.LogLevelEnum; +import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContext; +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.enums.WorkflowStatusEnum; +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 +public class EndNodeExecutor implements NodeExecutor { + + @Resource + private IWorkflowInstanceRepository workflowInstanceRepository; + + @Override + public NodeTypeEnum getNodeType() { + return NodeTypeEnum.END; + } + + @Override + public void execute(NodeInstance nodeInstance, WorkflowContext context) { + // 1. 完成结束节点 + nodeInstance.setStatus(NodeStatusEnum.COMPLETED); + nodeInstance.setEndTime(LocalDateTime.now()); + + // 2. 完成工作流实例 + WorkflowInstance instance = context.getInstance(); + instance.setStatus(WorkflowStatusEnum.COMPLETED); + instance.setEndTime(LocalDateTime.now()); + workflowInstanceRepository.save(instance); + + context.log("工作流执行完成", LogLevelEnum.INFO); + } + + @Override + public void validate(String config) { + // 结束节点无需配置 + } + + @Override + public void terminate(NodeInstance nodeInstance, WorkflowContext context) { + // 结束节点无需终止操作 + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/GatewayCondition.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/GatewayCondition.java new file mode 100644 index 00000000..ee6f28a6 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/GatewayCondition.java @@ -0,0 +1,22 @@ +package com.qqchen.deploy.backend.workflow.engine.executor; + +import lombok.Data; + +@Data +public class GatewayCondition { + + /** + * 条件表达式(SpEL表达式) + */ + private String expression; + + /** + * 下一个节点ID + */ + private String nextNodeId; + + /** + * 条件描述 + */ + private String description; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/GatewayConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/GatewayConfig.java new file mode 100644 index 00000000..5cb80ad8 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/GatewayConfig.java @@ -0,0 +1,38 @@ +package com.qqchen.deploy.backend.workflow.engine.executor; + +import lombok.Data; +import java.util.List; + +@Data +public class GatewayConfig { + + /** + * 网关类型 + */ + private GatewayType type; + + /** + * 网关名称 + */ + private String name; + + /** + * 网关描述 + */ + private String description; + + /** + * 条件列表(排他网关和包容网关使用) + */ + private List conditions; + + /** + * 默认节点ID(排他网关使用) + */ + private String defaultNodeId; + + /** + * 并行节点ID列表(并行网关使用) + */ + private List parallelNodeIds; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/GatewayNodeExecutor.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/GatewayNodeExecutor.java new file mode 100644 index 00000000..82329553 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/GatewayNodeExecutor.java @@ -0,0 +1,149 @@ +package com.qqchen.deploy.backend.workflow.engine.executor; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.qqchen.deploy.backend.framework.enums.ResponseCode; +import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContext; +import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException; +import com.qqchen.deploy.backend.workflow.entity.NodeInstance; +import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum; +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.List; +import java.util.Map; + +@Slf4j +@Component +public class GatewayNodeExecutor implements NodeExecutor { + + private final ObjectMapper objectMapper = new ObjectMapper(); + private final ExpressionParser expressionParser = new SpelExpressionParser(); + + @Override + public NodeTypeEnum getNodeType() { + return NodeTypeEnum.GATEWAY; + } + + @Override + public void execute(NodeInstance nodeInstance, WorkflowContext context) { + try { + GatewayConfig config = objectMapper.readValue(nodeInstance.getConfig(), GatewayConfig.class); + + if (config.getType() == null) { + throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR, "Gateway type is required"); + } + + switch (config.getType()) { + case EXCLUSIVE: + handleExclusiveGateway(nodeInstance, context, config); + break; + case PARALLEL: + handleParallelGateway(nodeInstance, context, config); + break; + case INCLUSIVE: + handleInclusiveGateway(nodeInstance, context, config); + break; + default: + throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_TYPE_NOT_SUPPORTED, + "Unsupported gateway type: " + config.getType()); + } + } catch (Exception e) { + throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED, e); + } + } + + private void handleExclusiveGateway(NodeInstance nodeInstance, WorkflowContext context, GatewayConfig config) { + try { + for (GatewayCondition condition : config.getConditions()) { + if (evaluateCondition(condition.getExpression(), context)) { + nodeInstance.setOutput(objectMapper.createObjectNode() + .put("nextNodeId", condition.getNextNodeId()) + .toString()); + return; + } + } + throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED, + "No condition matched in exclusive gateway"); + } catch (Exception e) { + throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED, e); + } + } + + private void handleParallelGateway(NodeInstance nodeInstance, WorkflowContext context, GatewayConfig config) { + try { + List nextNodeIds = new ArrayList<>(); + for (GatewayCondition condition : config.getConditions()) { + nextNodeIds.add(condition.getNextNodeId()); + } + nodeInstance.setOutput(objectMapper.createObjectNode() + .put("nextNodeIds", String.join(",", nextNodeIds)) + .toString()); + } catch (Exception e) { + throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED, e); + } + } + + private void handleInclusiveGateway(NodeInstance nodeInstance, WorkflowContext context, GatewayConfig config) { + try { + List nextNodeIds = new ArrayList<>(); + for (GatewayCondition condition : config.getConditions()) { + if (evaluateCondition(condition.getExpression(), context)) { + nextNodeIds.add(condition.getNextNodeId()); + } + } + if (nextNodeIds.isEmpty()) { + throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED, + "No condition matched in inclusive gateway"); + } + nodeInstance.setOutput(objectMapper.createObjectNode() + .put("nextNodeIds", String.join(",", nextNodeIds)) + .toString()); + } catch (Exception e) { + throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED, e); + } + } + + private boolean evaluateCondition(String expression, WorkflowContext context) { + try { + StandardEvaluationContext evaluationContext = new StandardEvaluationContext(); + evaluationContext.setVariables(context.getVariables()); + Expression exp = expressionParser.parseExpression(expression); + return exp.getValue(evaluationContext, Boolean.class); + } catch (Exception e) { + throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED, e); + } + } + + @Override + public void terminate(NodeInstance nodeInstance, WorkflowContext context) { + // Gateway nodes are instant operations, no need to terminate + } + + @Override + public void validate(String config) { + try { + GatewayConfig gatewayConfig = objectMapper.readValue(config, GatewayConfig.class); + if (gatewayConfig.getType() == null) { + throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR, "Gateway type is required"); + } + // 验证条件表达式 + if (gatewayConfig.getConditions() != null) { + for (GatewayCondition condition : gatewayConfig.getConditions()) { + try { + expressionParser.parseExpression(condition.getExpression()); + } catch (Exception e) { + throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_ERROR, + "Invalid condition expression: " + condition.getExpression()); + } + } + } + } 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/engine/executor/GatewayType.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/GatewayType.java new file mode 100644 index 00000000..a7c9b19a --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/GatewayType.java @@ -0,0 +1,16 @@ +package com.qqchen.deploy.backend.workflow.engine.executor; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum GatewayType { + + EXCLUSIVE("EXCLUSIVE", "排他网关"), + PARALLEL("PARALLEL", "并行网关"), + INCLUSIVE("INCLUSIVE", "包容网关"); + + private final String code; + private final String description; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/NodeExecutor.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/NodeExecutor.java new file mode 100644 index 00000000..ef062058 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/NodeExecutor.java @@ -0,0 +1,31 @@ +package com.qqchen.deploy.backend.workflow.engine.executor; + +import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContext; +import com.qqchen.deploy.backend.workflow.entity.NodeInstance; +import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum; + +/** + * 节点执行器接口 + */ +public interface NodeExecutor { + + /** + * 获取支持的节点类型 + */ + NodeTypeEnum getNodeType(); + + /** + * 执行节点 + */ + void execute(NodeInstance nodeInstance, WorkflowContext context); + + /** + * 验证节点配置 + */ + void validate(String config); + + /** + * 终止节点执行 + */ + void terminate(NodeInstance nodeInstance, WorkflowContext context); +} \ No newline at end of file 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..331edd01 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/StartNodeExecutor.java @@ -0,0 +1,36 @@ +package com.qqchen.deploy.backend.workflow.engine.executor; + +import com.qqchen.deploy.backend.enums.LogLevelEnum; +import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContext; +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 lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class StartNodeExecutor implements NodeExecutor { + + @Override + public NodeTypeEnum getNodeType() { + return NodeTypeEnum.START; + } + + @Override + public void execute(NodeInstance nodeInstance, WorkflowContext context) { + // 开始节点直接完成 + nodeInstance.setStatus(NodeStatusEnum.COMPLETED); + context.log("开始节点执行完成", LogLevelEnum.INFO); + } + + @Override + public void validate(String config) { + // 开始节点无需配置 + } + + @Override + public void terminate(NodeInstance nodeInstance, WorkflowContext context) { + // 开始节点无需终止操作 + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/TaskConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/TaskConfig.java new file mode 100644 index 00000000..b0c0a17e --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/TaskConfig.java @@ -0,0 +1,44 @@ +package com.qqchen.deploy.backend.workflow.engine.executor; + +import lombok.Data; + +import java.util.Map; + +@Data +public class TaskConfig { + + /** + * 任务类型 + */ + private TaskType 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/TaskNodeExecutor.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/TaskNodeExecutor.java new file mode 100644 index 00000000..c322e995 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/TaskNodeExecutor.java @@ -0,0 +1,131 @@ +package com.qqchen.deploy.backend.workflow.engine.executor; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.qqchen.deploy.backend.enums.LogLevelEnum; +import com.qqchen.deploy.backend.framework.enums.ResponseCode; +import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContext; +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.enums.NodeTypeEnum; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Map; + +@Slf4j +@Component +public class TaskNodeExecutor implements NodeExecutor { + + @Resource + private ObjectMapper objectMapper; + + @Override + public NodeTypeEnum getNodeType() { + return NodeTypeEnum.TASK; + } + + @Override + public void execute(NodeInstance nodeInstance, WorkflowContext 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, WorkflowContext 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, WorkflowContext context) { + switch (config.getType()) { + case SHELL: + executeShellTask(config, nodeInstance, context); + break; + 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, WorkflowContext context) { + // 根据任务类型执行终止操作 + switch (config.getType()) { + case SHELL: + terminateShellTask(config, nodeInstance, context); + break; + case HTTP: + terminateHttpTask(config, nodeInstance, context); + break; + case JAVA: + terminateJavaTask(config, nodeInstance, context); + break; + } + } + + // 具体任务执行方法... + private void executeShellTask(TaskConfig config, NodeInstance nodeInstance, WorkflowContext context) { + // TODO: 实现Shell脚本执行 + } + + private void executeHttpTask(TaskConfig config, NodeInstance nodeInstance, WorkflowContext context) { + // TODO: 实现HTTP请求执行 + } + + private void executeJavaTask(TaskConfig config, NodeInstance nodeInstance, WorkflowContext context) { + // TODO: 实现Java方法调用 + } + + private void terminateShellTask(TaskConfig config, NodeInstance nodeInstance, WorkflowContext context) { + // TODO: 实现Shell脚本终止 + } + + private void terminateHttpTask(TaskConfig config, NodeInstance nodeInstance, WorkflowContext context) { + // TODO: 实现HTTP请求终止 + } + + private void terminateJavaTask(TaskConfig config, NodeInstance nodeInstance, WorkflowContext context) { + // TODO: 实现Java方法终止 + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/TaskType.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/TaskType.java new file mode 100644 index 00000000..93d01e8d --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/TaskType.java @@ -0,0 +1,16 @@ +package com.qqchen.deploy.backend.workflow.engine.executor; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum TaskType { + + SHELL("SHELL", "Shell脚本"), + HTTP("HTTP", "HTTP请求"), + JAVA("JAVA", "Java方法"); + + private final String code; + private final String description; +} \ No newline at end of file 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 new file mode 100644 index 00000000..f67e0f7d --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/task/HttpTaskExecutor.java @@ -0,0 +1,61 @@ +package com.qqchen.deploy.backend.workflow.engine.executor.task; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.qqchen.deploy.backend.enums.LogLevelEnum; +import com.qqchen.deploy.backend.framework.enums.ResponseCode; +import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContext; +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, WorkflowContext 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, WorkflowContext 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 new file mode 100644 index 00000000..91219ea9 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/task/JavaTaskExecutor.java @@ -0,0 +1,51 @@ +package com.qqchen.deploy.backend.workflow.engine.executor.task; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.qqchen.deploy.backend.enums.LogLevelEnum; +import com.qqchen.deploy.backend.framework.enums.ResponseCode; +import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContext; +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 org.springframework.util.ReflectionUtils; + +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, WorkflowContext 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, WorkflowContext.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, WorkflowContext 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 new file mode 100644 index 00000000..84bf28e2 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/task/ShellTaskExecutor.java @@ -0,0 +1,66 @@ +package com.qqchen.deploy.backend.workflow.engine.executor.task; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.qqchen.deploy.backend.enums.LogLevelEnum; +import com.qqchen.deploy.backend.framework.enums.ResponseCode; +import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContext; +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, WorkflowContext 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, WorkflowContext context) { + // TODO: 实现Shell命令终止逻辑 + } +} \ 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 new file mode 100644 index 00000000..fd7c6df6 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/executor/task/TaskExecutor.java @@ -0,0 +1,22 @@ +package com.qqchen.deploy.backend.workflow.engine.executor.task; + +import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContext; +import com.qqchen.deploy.backend.workflow.entity.NodeInstance; + +import java.util.Map; + +/** + * 任务执行器接口 + */ +public interface TaskExecutor { + + /** + * 执行任务 + */ + void execute(NodeInstance nodeInstance, WorkflowContext context, Map parameters); + + /** + * 终止任务 + */ + void terminate(NodeInstance nodeInstance, WorkflowContext context); +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/impl/DefaultWorkflowEngine.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/impl/DefaultWorkflowEngine.java deleted file mode 100644 index 57819c16..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/impl/DefaultWorkflowEngine.java +++ /dev/null @@ -1,220 +0,0 @@ -package com.qqchen.deploy.backend.workflow.engine.impl; - -import com.qqchen.deploy.backend.framework.enums.ResponseCode; -import com.qqchen.deploy.backend.framework.exception.BusinessException; -import com.qqchen.deploy.backend.workflow.engine.WorkflowContext; -import com.qqchen.deploy.backend.workflow.engine.WorkflowEngine; -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.WorkflowStatusEnum; -import com.qqchen.deploy.backend.workflow.repository.INodeInstanceRepository; -import com.qqchen.deploy.backend.workflow.repository.IWorkflowInstanceRepository; -import com.qqchen.deploy.backend.workflow.service.IWorkflowLogService; -import com.qqchen.deploy.backend.workflow.service.IWorkflowVariableService; -import jakarta.annotation.Resource; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; -import java.util.Map; -import java.util.Optional; - -/** - * 工作流引擎默认实现 - */ -@Slf4j -@Service -public class DefaultWorkflowEngine implements WorkflowEngine { - - @Resource - private IWorkflowInstanceRepository workflowInstanceRepository; - - @Resource - private INodeInstanceRepository nodeInstanceRepository; - - @Resource - private IWorkflowVariableService workflowVariableService; - - @Resource - private IWorkflowLogService workflowLogService; - - @Override - @Transactional - public void startInstance(Long instanceId, Map variables) { - WorkflowInstance instance = getWorkflowInstance(instanceId); - if (instance.getStatus() != WorkflowStatusEnum.CREATED) { - throw new BusinessException(ResponseCode.WORKFLOW_INSTANCE_NOT_RUNNING); - } - - WorkflowContext context = initContext(instance, variables); - - try { - instance.setStatus(WorkflowStatusEnum.RUNNING); - workflowInstanceRepository.save(instance); - - workflowVariableService.saveVariables(instanceId, variables); - workflowLogService.logWorkflowStart(instance); - - executeNextNode(context); - } catch (Exception e) { - log.error("Failed to start workflow instance: {}", instanceId, e); - instance.setStatus(WorkflowStatusEnum.FAILED); - workflowInstanceRepository.save(instance); - workflowLogService.logWorkflowError(instance, e.getMessage()); - throw new BusinessException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED); - } - } - - @Override - @Transactional - public void cancelInstance(Long instanceId) { - WorkflowInstance instance = getWorkflowInstance(instanceId); - if (!instance.getStatus().canCancel()) { - throw new BusinessException(ResponseCode.WORKFLOW_INSTANCE_ALREADY_CANCELED); - } - - instance.setStatus(WorkflowStatusEnum.CANCELLED); - workflowInstanceRepository.save(instance); - - List runningNodes = nodeInstanceRepository.findByWorkflowInstanceIdAndStatus( - instanceId, NodeStatusEnum.RUNNING.name()); - runningNodes.forEach(node -> { - node.setStatus(NodeStatusEnum.CANCELLED); - nodeInstanceRepository.save(node); - }); - - workflowLogService.logWorkflowCancel(instance); - } - - @Override - @Transactional - public void pauseInstance(Long instanceId) { - WorkflowInstance instance = getWorkflowInstance(instanceId); - if (!instance.getStatus().canPause()) { - throw new BusinessException(ResponseCode.WORKFLOW_INSTANCE_NOT_RUNNING); - } - - instance.setStatus(WorkflowStatusEnum.PAUSED); - workflowInstanceRepository.save(instance); - - List runningNodes = nodeInstanceRepository.findByWorkflowInstanceIdAndStatus( - instanceId, NodeStatusEnum.RUNNING.name()); - runningNodes.forEach(node -> { - node.setStatus(NodeStatusEnum.PAUSED); - nodeInstanceRepository.save(node); - }); - - workflowLogService.logWorkflowPause(instance); - } - - @Override - @Transactional - public void resumeInstance(Long instanceId) { - WorkflowInstance instance = getWorkflowInstance(instanceId); - if (!instance.getStatus().canResume()) { - throw new BusinessException(ResponseCode.WORKFLOW_INSTANCE_NOT_RUNNING); - } - - instance.setStatus(WorkflowStatusEnum.RUNNING); - workflowInstanceRepository.save(instance); - - List pausedNodes = nodeInstanceRepository.findByWorkflowInstanceIdAndStatus( - instanceId, NodeStatusEnum.PAUSED.name()); - pausedNodes.forEach(node -> { - node.setStatus(NodeStatusEnum.RUNNING); - nodeInstanceRepository.save(node); - }); - - workflowLogService.logWorkflowResume(instance); - } - - @Override - @Transactional - public void retryNode(Long nodeInstanceId) { - NodeInstance node = getNodeInstance(nodeInstanceId); - if (node.getStatus() != NodeStatusEnum.FAILED) { - throw new BusinessException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED); - } - - WorkflowInstance instance = getWorkflowInstance(node.getWorkflowInstanceId()); - if (instance.getStatus() != WorkflowStatusEnum.FAILED) { - throw new BusinessException(ResponseCode.WORKFLOW_INSTANCE_NOT_RUNNING); - } - - node.setStatus(NodeStatusEnum.RUNNING); - nodeInstanceRepository.save(node); - - instance.setStatus(WorkflowStatusEnum.RUNNING); - workflowInstanceRepository.save(instance); - - workflowLogService.logNodeRetry(node); - } - - @Override - @Transactional - public void skipNode(Long nodeInstanceId) { - NodeInstance node = getNodeInstance(nodeInstanceId); - if (node.getStatus() != NodeStatusEnum.FAILED) { - throw new BusinessException(ResponseCode.WORKFLOW_NODE_EXECUTION_FAILED); - } - - node.setStatus(NodeStatusEnum.SKIPPED); - nodeInstanceRepository.save(node); - - WorkflowInstance instance = getWorkflowInstance(node.getWorkflowInstanceId()); - WorkflowContext context = initContext(instance, workflowVariableService.getVariables(instance.getId())); - context.setCurrentNode(node); - - executeNextNode(context); - workflowLogService.logNodeSkip(node); - } - - private WorkflowInstance getWorkflowInstance(Long instanceId) { - return workflowInstanceRepository.findById(instanceId) - .orElseThrow(() -> new BusinessException(ResponseCode.WORKFLOW_INSTANCE_NOT_FOUND)); - } - - private NodeInstance getNodeInstance(Long nodeInstanceId) { - return nodeInstanceRepository.findById(nodeInstanceId) - .orElseThrow(() -> new BusinessException(ResponseCode.WORKFLOW_NODE_NOT_FOUND)); - } - - private WorkflowContext initContext(WorkflowInstance instance, Map variables) { - WorkflowContext context = new WorkflowContext(); - context.setWorkflowInstance(instance); - context.setVariables(variables); - context.setAllNodes(nodeInstanceRepository.findByWorkflowInstanceId(instance.getId())); - return context; - } - - private void executeNextNode(WorkflowContext context) { - NodeInstance currentNode = context.getCurrentNode(); - List allNodes = context.getAllNodes(); - - Optional nextNode; - if (currentNode == null) { - nextNode = allNodes.stream() - .filter(node -> node.getPreNodeId() == null) - .findFirst(); - } else { - nextNode = allNodes.stream() - .filter(node -> currentNode.getId().equals(node.getPreNodeId())) - .findFirst(); - } - - if (nextNode.isPresent()) { - NodeInstance node = nextNode.get(); - node.setStatus(NodeStatusEnum.RUNNING); - nodeInstanceRepository.save(node); - workflowLogService.logNodeStart(node); - // TODO: 实际节点执行逻辑 - } else { - WorkflowInstance instance = context.getWorkflowInstance(); - instance.setStatus(WorkflowStatusEnum.COMPLETED); - workflowInstanceRepository.save(instance); - workflowLogService.logWorkflowComplete(instance); - } - } -} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/model/NodeConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/model/NodeConfig.java new file mode 100644 index 00000000..0519ecba --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/model/NodeConfig.java @@ -0,0 +1 @@ + \ 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..0519ecba --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/model/TransitionConfig.java @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/AbstractNodeExecutor.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/AbstractNodeExecutor.java index 2fd9b147..38c1c76a 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/AbstractNodeExecutor.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/AbstractNodeExecutor.java @@ -1,9 +1,11 @@ package com.qqchen.deploy.backend.workflow.engine.node; -import com.fasterxml.jackson.databind.ObjectMapper; import com.qqchen.deploy.backend.enums.LogLevelEnum; -import com.qqchen.deploy.backend.workflow.engine.WorkflowContext; +import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContext; +import com.qqchen.deploy.backend.workflow.engine.executor.NodeExecutor; 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.service.IWorkflowLogService; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; @@ -17,99 +19,70 @@ public abstract class AbstractNodeExecutor implements NodeExecutor { @Resource protected IWorkflowLogService workflowLogService; - @Resource - protected ObjectMapper objectMapper; - @Override - public boolean execute(NodeInstance nodeInstance, WorkflowContext context) { + public void execute(NodeInstance nodeInstance, WorkflowContext context) { try { - // 解析节点配置 - NodeConfig config = parseConfig(nodeInstance.getConfig()); - - // 执行前处理 - beforeExecute(nodeInstance, context, config); - - // 执行节点 - boolean result = doExecute(nodeInstance, context, config); - - // 执行后处理 - afterExecute(nodeInstance, context, config, result); - - return result; + // 1. 记录系统日志 + logSystem(context.getInstance(), LogLevelEnum.INFO, + String.format("开始执行节点: %s[%s]", nodeInstance.getName(), nodeInstance.getNodeId()), + null); + + // 2. 记录节点开始日志 + logNodeStart(nodeInstance); + + // 3. 执行节点 + doExecute(nodeInstance, context); + + // 4. 记录节点完成日志 + logNodeComplete(nodeInstance); + } catch (Exception e) { - log.error("Execute node failed: {}", e.getMessage(), e); - workflowLogService.logSystem(context.getWorkflowInstance(), LogLevelEnum.ERROR, - "Execute node failed: " + e.getMessage(), e.toString()); - return false; + // 5. 记录错误日志 + logSystem(context.getInstance(), LogLevelEnum.ERROR, + String.format("节点执行失败: %s[%s]", nodeInstance.getName(), nodeInstance.getNodeId()), + e.getMessage()); + logNodeError(nodeInstance, e.getMessage()); + throw e; } } - @Override - public void cancel(NodeInstance nodeInstance, WorkflowContext context) { - try { - // 解析节点配置 - NodeConfig config = parseConfig(nodeInstance.getConfig()); - - // 执行取消操作 - doCancel(nodeInstance, context, config); - } catch (Exception e) { - log.error("Cancel node failed: {}", e.getMessage(), e); - workflowLogService.logSystem(context.getWorkflowInstance(), LogLevelEnum.ERROR, - "Cancel node failed: " + e.getMessage(), e.toString()); - } - } - - /** - * 解析节点配置 - * - * @param configJson 配置JSON - * @return 节点配置 - */ - protected abstract NodeConfig parseConfig(String configJson) throws Exception; - - /** - * 执行前处理 - * - * @param nodeInstance 节点实例 - * @param context 工作流上下文 - * @param config 节点配置 - */ - protected void beforeExecute(NodeInstance nodeInstance, WorkflowContext context, NodeConfig config) { - workflowLogService.logNodeStart(nodeInstance); - } - /** * 执行节点 - * - * @param nodeInstance 节点实例 - * @param context 工作流上下文 - * @param config 节点配置 - * @return 执行结果 */ - protected abstract boolean doExecute(NodeInstance nodeInstance, WorkflowContext context, NodeConfig config) throws Exception; + protected abstract void doExecute(NodeInstance nodeInstance, WorkflowContext context); /** - * 执行后处理 - * - * @param nodeInstance 节点实例 - * @param context 工作流上下文 - * @param config 节点配置 - * @param success 是否成功 + * 记录系统日志 */ - protected void afterExecute(NodeInstance nodeInstance, WorkflowContext context, NodeConfig config, boolean success) { - if (success) { - workflowLogService.logNodeComplete(nodeInstance); - } else { - workflowLogService.logNodeError(nodeInstance, "Node execution failed"); - } + protected void logSystem(WorkflowInstance instance, LogLevelEnum level, String message, String detail) { + workflowLogService.log(instance.getId(), null, message, level, detail); } /** - * 执行取消操作 - * - * @param nodeInstance 节点实例 - * @param context 工作流上下文 - * @param config 节点配置 + * 记录节点开始日志 */ - protected abstract void doCancel(NodeInstance nodeInstance, WorkflowContext context, NodeConfig config) throws Exception; + protected void logNodeStart(NodeInstance nodeInstance) { + workflowLogService.log(nodeInstance.getWorkflowInstanceId(), nodeInstance.getNodeId(), + String.format("节点开始执行: %s", nodeInstance.getName()), LogLevelEnum.INFO, null); + nodeInstance.setStatus(NodeStatusEnum.RUNNING); + } + + /** + * 记录节点完成日志 + */ + protected void logNodeComplete(NodeInstance nodeInstance) { + workflowLogService.log(nodeInstance.getWorkflowInstanceId(), nodeInstance.getNodeId(), + String.format("节点执行完成: %s", nodeInstance.getName()), LogLevelEnum.INFO, null); + nodeInstance.setStatus(NodeStatusEnum.COMPLETED); + } + + /** + * 记录节点错误日志 + */ + protected void logNodeError(NodeInstance nodeInstance, String error) { + workflowLogService.log(nodeInstance.getWorkflowInstanceId(), nodeInstance.getNodeId(), + String.format("节点执行失败: %s", nodeInstance.getName()), LogLevelEnum.ERROR, error); + nodeInstance.setStatus(NodeStatusEnum.FAILED); + nodeInstance.setError(error); + } } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/executor/ShellNodeExecutor.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/executor/ShellNodeExecutor.java index 0e5b5cad..81c3b523 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/executor/ShellNodeExecutor.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/node/executor/ShellNodeExecutor.java @@ -1,13 +1,14 @@ package com.qqchen.deploy.backend.workflow.engine.node.executor; -import com.qqchen.deploy.backend.workflow.engine.WorkflowContext; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContext; import com.qqchen.deploy.backend.workflow.engine.node.AbstractNodeExecutor; -import com.qqchen.deploy.backend.workflow.engine.node.NodeConfig; -import com.qqchen.deploy.backend.workflow.engine.node.NodeType; -import com.qqchen.deploy.backend.workflow.engine.node.ScriptNodeConfig; +import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum; import com.qqchen.deploy.backend.workflow.entity.NodeInstance; +import com.qqchen.deploy.backend.enums.LogLevelEnum; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; +import jakarta.annotation.Resource; import java.io.BufferedReader; import java.io.InputStreamReader; @@ -19,68 +20,115 @@ import java.util.Map; @Component public class ShellNodeExecutor extends AbstractNodeExecutor { + @Resource + private ObjectMapper objectMapper; + @Override - public NodeType getNodeType() { - return NodeType.SCRIPT; + public NodeTypeEnum getNodeType() { + return NodeTypeEnum.TASK; } @Override - protected NodeConfig parseConfig(String configJson) throws Exception { - return objectMapper.readValue(configJson, ScriptNodeConfig.class); + public void validate(String config) { + } @Override - protected boolean doExecute(NodeInstance nodeInstance, WorkflowContext context, NodeConfig config) throws Exception { - ScriptNodeConfig scriptConfig = (ScriptNodeConfig) config; - ProcessBuilder processBuilder = new ProcessBuilder(); - processBuilder.command("sh", "-c", scriptConfig.getScript()); - - if (scriptConfig.getWorkingDirectory() != null) { - processBuilder.directory(new java.io.File(scriptConfig.getWorkingDirectory())); - } + protected void doExecute(NodeInstance nodeInstance, WorkflowContext context) { + try { + String configJson = nodeInstance.getConfig(); + ShellConfig config = objectMapper.readValue(configJson, ShellConfig.class); + ProcessBuilder processBuilder = new ProcessBuilder(); + processBuilder.command("sh", "-c", config.getScript()); + + if (config.getWorkingDirectory() != null) { + processBuilder.directory(new java.io.File(config.getWorkingDirectory())); + } - if (scriptConfig.getEnvironment() != null) { - Map env = processBuilder.environment(); - env.putAll(scriptConfig.getEnvironment()); - } + if (config.getEnvironment() != null) { + Map env = processBuilder.environment(); + env.putAll(config.getEnvironment()); + } - Process process = processBuilder.start(); - - // 读取标准输出 - BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); - List output = new ArrayList<>(); - String line; - while ((line = reader.readLine()) != null) { - output.add(line); - } + Process process = processBuilder.start(); + + // 读取标准输出 + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + List output = new ArrayList<>(); + String line; + while ((line = reader.readLine()) != null) { + output.add(line); + } - // 读取错误输出 - BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream())); - List error = new ArrayList<>(); - while ((line = errorReader.readLine()) != null) { - error.add(line); - } + // 读取错误输出 + BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream())); + List error = new ArrayList<>(); + while ((line = errorReader.readLine()) != null) { + error.add(line); + } - // 等待进程结束 - int exitCode = process.waitFor(); - - // 检查退出码 - if (scriptConfig.getSuccessExitCode() != null && exitCode != scriptConfig.getSuccessExitCode()) { - throw new RuntimeException("Shell script execution failed with exit code: " + exitCode + - "\nError output: " + String.join("\n", error)); - } + // 等待进程结束 + int exitCode = process.waitFor(); + + // 检查退出码 + if (config.getSuccessExitCode() != null && exitCode != config.getSuccessExitCode()) { + throw new RuntimeException("Shell script execution failed with exit code: " + exitCode + + "\nError output: " + String.join("\n", error)); + } - // 设置输出结果 - nodeInstance.setOutput(String.join("\n", output)); - if (!error.isEmpty()) { - nodeInstance.setError(String.join("\n", error)); + // 设置输出结果 + nodeInstance.setOutput(String.join("\n", output)); + if (!error.isEmpty()) { + nodeInstance.setError(String.join("\n", error)); + } + } catch (Exception e) { + throw new RuntimeException("Shell script execution failed", e); } - - return exitCode == 0; } @Override - protected void doCancel(NodeInstance nodeInstance, WorkflowContext context, NodeConfig config) throws Exception { - // TODO: 实现取消逻辑,例如终止正在运行的进程 + public void terminate(NodeInstance nodeInstance, WorkflowContext context) { + // TODO: 实现终止Shell进程的逻辑 + context.log("Shell node termination is not implemented yet", LogLevelEnum.WARN); + } + + public static class ShellConfig { + private String script; + private String workingDirectory; + private Map environment; + private Integer successExitCode; + + // Getters and setters + public String getScript() { + return script; + } + + public void setScript(String script) { + this.script = script; + } + + public String getWorkingDirectory() { + return workingDirectory; + } + + public void setWorkingDirectory(String workingDirectory) { + this.workingDirectory = workingDirectory; + } + + public Map getEnvironment() { + return environment; + } + + public void setEnvironment(Map environment) { + this.environment = environment; + } + + public Integer getSuccessExitCode() { + return successExitCode; + } + + public void setSuccessExitCode(Integer successExitCode) { + this.successExitCode = successExitCode; + } } } \ 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 new file mode 100644 index 00000000..9c51ea5e --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/parser/WorkflowDefinitionParser.java @@ -0,0 +1,151 @@ +package com.qqchen.deploy.backend.workflow.engine.parser; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.qqchen.deploy.backend.framework.enums.ResponseCode; +import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException; +import com.qqchen.deploy.backend.workflow.engine.node.NodeConfig; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; + +/** + * 工作流定义解析器 + */ +@Slf4j +@Component +public class WorkflowDefinitionParser { + + @Resource + private ObjectMapper objectMapper; + + /** + * 解析节点配置 + * + * @param nodeConfig 节点配置JSON字符串 + * @return 节点配置列表 + */ + public List parseNodeConfig(String nodeConfig) { + 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); + } + + List nodes = new ArrayList<>(); + for (JsonNode node : nodesNode) { + NodeConfig config = objectMapper.treeToValue(node, NodeConfig.class); + nodes.add(config); + log.debug("Parsed node: id={}, type={}", config.getId(), 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); + } + } + + /** + * 解析流转配置 + * + * @param transitionConfig 流转配置JSON字符串 + * @return 流转配置列表 + */ + public List parseTransitionConfig(String transitionConfig) { + try { + log.debug("Parsing transition config: {}", transitionConfig); + List transitions = objectMapper.readValue( + transitionConfig, + new TypeReference>() {} + ); + + transitions.forEach(transition -> + log.debug("Parsed transition: {} -> {}, priority={}", + transition.getSourceNodeId(), + transition.getTargetNodeId(), + transition.getPriority()) + ); + + return transitions; + } catch (JsonProcessingException e) { + log.error("Failed to parse transition config: {}", e.getMessage(), e); + throw new WorkflowEngineException(ResponseCode.WORKFLOW_TRANSITION_CONFIG_INVALID, e); + } + } + + /** + * 验证节点配置的完整性 + * + * @param nodes 节点配置列表 + * @param transitions 流转配置列表 + */ + public void validateConfig(List nodes, List transitions) { + // 1. 检查是否有开始节点和结束节点 + boolean hasStart = false; + boolean hasEnd = false; + for (NodeConfig node : nodes) { + switch (node.getType()) { + case START -> hasStart = true; + case END -> hasEnd = true; + } + } + + if (!hasStart || !hasEnd) { + throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_INVALID, + "Workflow must have both START and END nodes"); + } + + // 2. 检查流转配置中的节点ID是否都存在 + for (TransitionConfig transition : transitions) { + boolean sourceExists = false; + boolean targetExists = false; + + for (NodeConfig node : nodes) { + if (node.getId().equals(transition.getSourceNodeId())) { + sourceExists = true; + } + if (node.getId().equals(transition.getTargetNodeId())) { + targetExists = true; + } + } + + if (!sourceExists || !targetExists) { + throw new WorkflowEngineException(ResponseCode.WORKFLOW_TRANSITION_CONFIG_INVALID, + String.format("Invalid node reference in transition: %s -> %s", + transition.getSourceNodeId(), transition.getTargetNodeId())); + } + } + + // 3. 检查是否存在孤立节点(没有入边或出边的非开始/结束节点) + for (NodeConfig node : nodes) { + if (node.getType() == NodeTypeEnum.START || node.getType() == NodeTypeEnum.END) { + continue; + } + + boolean hasIncoming = false; + boolean hasOutgoing = false; + + for (TransitionConfig transition : transitions) { + if (transition.getTargetNodeId().equals(node.getId())) { + hasIncoming = true; + } + if (transition.getSourceNodeId().equals(node.getId())) { + hasOutgoing = true; + } + } + + if (!hasIncoming || !hasOutgoing) { + throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_CONFIG_INVALID, + String.format("Isolated node found: %s", node.getId())); + } + } + + log.info("Workflow configuration validation passed"); + } +} \ No newline at end of file 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 new file mode 100644 index 00000000..64a7e277 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/transition/TransitionExecutor.java @@ -0,0 +1,63 @@ +package com.qqchen.deploy.backend.workflow.engine.transition; + +import com.qqchen.deploy.backend.enums.LogLevelEnum; +import com.qqchen.deploy.backend.framework.enums.ResponseCode; +import com.qqchen.deploy.backend.workflow.engine.context.WorkflowContext; +import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException; +import com.qqchen.deploy.backend.workflow.entity.NodeDefinition; +import com.qqchen.deploy.backend.workflow.entity.NodeInstance; +import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition; +import com.qqchen.deploy.backend.workflow.enums.NodeStatusEnum; +import com.qqchen.deploy.backend.workflow.repository.INodeDefinitionRepository; +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 INodeDefinitionRepository nodeDefinitionRepository; + + @Resource + private INodeInstanceRepository nodeInstanceRepository; + + /** + * 执行节点流转 + */ + public void executeTransition(NodeInstance currentNode, WorkflowDefinition definition, WorkflowContext context) { + // 1. 获取下一个节点ID列表 + List nextNodeIds = transitionRuleEngine.getNextNodeIds(currentNode, definition, context); + + // 2. 创建下一个节点实例 + for (String nodeId : nextNodeIds) { + NodeDefinition nextNodeDef = nodeDefinitionRepository.findByWorkflowDefinitionIdAndNodeIdAndDeletedFalse( + definition.getId(), nodeId); + if (nextNodeDef == null) { + throw new WorkflowEngineException(ResponseCode.WORKFLOW_NODE_NOT_FOUND, "未找到节点定义: " + nodeId); + } + + NodeInstance nextNode = new NodeInstance(); + nextNode.setWorkflowInstanceId(currentNode.getWorkflowInstanceId()); + nextNode.setNodeId(nodeId); + nextNode.setNodeType(nextNodeDef.getType()); + nextNode.setName(nextNodeDef.getName()); + nextNode.setConfig(nextNodeDef.getConfig()); + nextNode.setStatus(NodeStatusEnum.PENDING); + nextNode.setCreateTime(LocalDateTime.now()); + + nodeInstanceRepository.save(nextNode); + + context.log(String.format("创建下一个节点实例: %s[%s]", nextNode.getName(), nextNode.getNodeId()), + LogLevelEnum.INFO); + } + } +} \ 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 new file mode 100644 index 00000000..eb72e0a4 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/transition/TransitionRule.java @@ -0,0 +1,28 @@ +package com.qqchen.deploy.backend.workflow.engine.transition; + +import lombok.Data; +import java.util.List; + +@Data +public class TransitionRule { + + /** + * 源节点ID + */ + private String sourceNodeId; + + /** + * 目标节点ID + */ + private String targetNodeId; + + /** + * 条件表达式 + */ + 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 new file mode 100644 index 00000000..1df4624f --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/engine/transition/TransitionRuleEngine.java @@ -0,0 +1,79 @@ +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.WorkflowContext; +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, WorkflowContext context) { + try { + // 解析流转规则 + List rules = parseTransitionRules(definition.getTransitionConfig()); + + // 过滤当前节点的规则并按优先级排序 + List nodeRules = rules.stream() + .filter(rule -> rule.getSourceNodeId().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.getTargetNodeId()); + } + } + + 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, WorkflowContext 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/NodeDefinition.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/NodeDefinition.java new file mode 100644 index 00000000..f3f4f291 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/NodeDefinition.java @@ -0,0 +1,52 @@ +package com.qqchen.deploy.backend.workflow.entity; + +import com.qqchen.deploy.backend.framework.annotation.LogicDelete; +import com.qqchen.deploy.backend.framework.domain.Entity; +import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum; +import jakarta.persistence.*; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +@jakarta.persistence.Entity +@Table(name = "wf_node_definition") +@LogicDelete +public class NodeDefinition extends Entity { + + /** + * 工作流定义ID + */ + @Column(name = "workflow_definition_id", nullable = false) + private Long workflowDefinitionId; + + /** + * 节点ID + */ + @Column(nullable = false) + private String nodeId; + + /** + * 节点名称 + */ + @Column(nullable = false) + private String name; + + /** + * 节点类型 + */ + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private NodeTypeEnum type; + + /** + * 节点配置(JSON) + */ + @Column(columnDefinition = "TEXT") + private String config; + + /** + * 序号 + */ + private Integer sequence; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowDefinition.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowDefinition.java index ecdccba2..a1caa71a 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowDefinition.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowDefinition.java @@ -2,8 +2,8 @@ package com.qqchen.deploy.backend.workflow.entity; import com.qqchen.deploy.backend.framework.annotation.LogicDelete; import com.qqchen.deploy.backend.framework.domain.Entity; -import jakarta.persistence.Column; -import jakarta.persistence.Table; +import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnum; +import jakarta.persistence.*; import lombok.Data; import lombok.EqualsAndHashCode; @@ -14,43 +14,43 @@ import lombok.EqualsAndHashCode; @Data @EqualsAndHashCode(callSuper = true) @jakarta.persistence.Entity -@Table(name = "sys_workflow_definition") +@Table(name = "wf_definition") @LogicDelete public class WorkflowDefinition extends Entity { - /** - * 工作流编码 - */ - @Column(name = "code", nullable = false, length = 50) - private String code; - /** * 工作流名称 */ - @Column(name = "name", nullable = false, length = 100) + @Column(nullable = false) private String name; + /** + * 工作流编码 + */ + @Column(nullable = false, unique = true) + private String code; + /** * 描述 */ - @Column(name = "description", columnDefinition = "TEXT") private String description; /** - * 工作流定义(JSON) + * 状态 */ - @Column(name = "content", nullable = false, columnDefinition = "TEXT") - private String content; + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private WorkflowStatusEnum status = WorkflowStatusEnum.DRAFT; /** - * 类型:DEPLOY/CONFIG_SYNC + * 节点配置(JSON) */ - @Column(name = "type", nullable = false, length = 20) - private String type; + @Column(columnDefinition = "TEXT") + private String nodeConfig; /** - * 状态:DRAFT/PUBLISHED/DISABLED + * 流转配置(JSON) */ - @Column(name = "status", nullable = false, length = 20) - private String status = "DRAFT"; + @Column(columnDefinition = "TEXT") + private String transitionConfig; } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowInstance.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowInstance.java index 35fb2cfb..fd8bc4d2 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowInstance.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowInstance.java @@ -20,7 +20,7 @@ import java.time.LocalDateTime; @Data @EqualsAndHashCode(callSuper = true) @jakarta.persistence.Entity -@Table(name = "sys_workflow_instance") +@Table(name = "wf_instance") @LogicDelete public class WorkflowInstance extends Entity { diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowLog.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowLog.java index 608f9bca..5b4e9a3e 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowLog.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowLog.java @@ -1,55 +1,47 @@ package com.qqchen.deploy.backend.workflow.entity; import com.qqchen.deploy.backend.enums.LogLevelEnum; -import com.qqchen.deploy.backend.enums.LogTypeEnum; +import com.qqchen.deploy.backend.framework.annotation.LogicDelete; import com.qqchen.deploy.backend.framework.domain.Entity; import jakarta.persistence.Column; -import jakarta.persistence.EnumType; -import jakarta.persistence.Enumerated; import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; /** - * 工作流日志 + * 工作流日志实体 */ @Data @EqualsAndHashCode(callSuper = true) @jakarta.persistence.Entity -@Table(name = "wf_workflow_log") +@Table(name = "wf_log") +@LogicDelete public class WorkflowLog extends Entity { /** * 工作流实例ID */ - @Column(nullable = false) + @Column(name = "workflow_instance_id", nullable = false) private Long workflowInstanceId; /** - * 节点实例ID + * 节点ID */ - private Long nodeInstanceId; - - /** - * 日志类型 - */ - @Enumerated(EnumType.STRING) - @Column(nullable = false) - private LogTypeEnum type; - - /** - * 日志级别 - */ - @Enumerated(EnumType.STRING) - @Column(nullable = false) - private LogLevelEnum level; + @Column(name = "node_id") + private String nodeId; /** * 日志内容 */ - @Column(columnDefinition = "TEXT", nullable = false) + @Column(nullable = false) private String content; + /** + * 日志级别 + */ + @Column(nullable = false) + private LogLevelEnum level; + /** * 详细信息 */ diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowPermission.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowPermission.java deleted file mode 100644 index 873af021..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowPermission.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.qqchen.deploy.backend.workflow.entity; - -import com.qqchen.deploy.backend.enums.PermissionTypeEnum; -import com.qqchen.deploy.backend.framework.domain.Entity; -import jakarta.persistence.Column; -import jakarta.persistence.EnumType; -import jakarta.persistence.Enumerated; -import jakarta.persistence.Table; -import lombok.Data; -import lombok.EqualsAndHashCode; - -/** - * 工作流权限 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@jakarta.persistence.Entity -@Table(name = "wf_workflow_permission") -public class WorkflowPermission extends Entity { - - /** - * 工作流定义ID - */ - @Column(nullable = false) - private Long workflowDefinitionId; - - /** - * 权限类型 - */ - @Enumerated(EnumType.STRING) - @Column(nullable = false) - private PermissionTypeEnum type; - - /** - * 用户ID - */ - private Long userId; - - /** - * 角色ID - */ - private Long roleId; - - /** - * 部门ID - */ - private Long departmentId; -} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowVariable.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowVariable.java index 4ed39983..9997184f 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowVariable.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowVariable.java @@ -1,36 +1,39 @@ package com.qqchen.deploy.backend.workflow.entity; +import com.qqchen.deploy.backend.framework.annotation.LogicDelete; import com.qqchen.deploy.backend.framework.domain.Entity; +import com.qqchen.deploy.backend.workflow.enums.VariableScopeEnum; import jakarta.persistence.Column; import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; /** - * 工作流变量 + * 工作流变量实体 */ @Data @EqualsAndHashCode(callSuper = true) @jakarta.persistence.Entity -@Table(name = "wf_workflow_variable") +@Table(name = "wf_variable") +@LogicDelete public class WorkflowVariable extends Entity { /** * 工作流实例ID */ - @Column(nullable = false) + @Column(name = "workflow_instance_id", nullable = false) private Long workflowInstanceId; /** - * 变量名 + * 变量名称 */ @Column(nullable = false) private String name; /** - * 变量值 + * 变量值(JSON) */ - @Column(columnDefinition = "TEXT") + @Column(columnDefinition = "TEXT", nullable = false) private String value; /** @@ -38,4 +41,10 @@ public class WorkflowVariable extends Entity { */ @Column(nullable = false) private String type; + + /** + * 变量作用域 + */ + @Column(nullable = false) + private String scope = VariableScopeEnum.INSTANCE.name(); } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/NodeStatusEnum.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/NodeStatusEnum.java index c97ac187..70e69fb4 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/NodeStatusEnum.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/NodeStatusEnum.java @@ -16,7 +16,9 @@ public enum NodeStatusEnum { COMPLETED("COMPLETED", "已完成"), FAILED("FAILED", "执行失败"), CANCELLED("CANCELLED", "已取消"), - SKIPPED("SKIPPED", "已跳过"); + SKIPPED("SKIPPED", "已跳过"), + SUSPENDED("SUSPENDED", "已暂停"), + TERMINATED("TERMINATED", "已终止"); private final String code; private final String description; diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/NodeTypeEnum.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/NodeTypeEnum.java index 15e2c54b..4eca7b04 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/NodeTypeEnum.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/NodeTypeEnum.java @@ -1,29 +1,21 @@ package com.qqchen.deploy.backend.workflow.enums; +import lombok.AllArgsConstructor; import lombok.Getter; /** * 节点类型枚举 */ @Getter +@AllArgsConstructor public enum NodeTypeEnum { START("START", "开始节点"), END("END", "结束节点"), - DEPLOY("DEPLOY", "部署节点"), - APPROVAL("APPROVAL", "审批节点"), - SCRIPT("SCRIPT", "脚本节点"), - SHELL("SHELL", "Shell脚本节点"), - CONFIG_SYNC("CONFIG_SYNC", "配置同步节点"), - CONDITION("CONDITION", "条件节点"), - PARALLEL("PARALLEL", "并行节点"), - SERIAL("SERIAL", "串行节点"); + TASK("TASK", "任务节点"), + GATEWAY("GATEWAY", "网关节点"), + SUB_PROCESS("SUB_PROCESS", "子流程节点"); private final String code; - private final String desc; - - NodeTypeEnum(String code, String desc) { - this.code = code; - this.desc = desc; - } + private final String description; } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/VariableScopeEnum.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/VariableScopeEnum.java new file mode 100644 index 00000000..d217eb7e --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/VariableScopeEnum.java @@ -0,0 +1,19 @@ +package com.qqchen.deploy.backend.workflow.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 变量作用域枚举 + */ +@Getter +@AllArgsConstructor +public enum VariableScopeEnum { + + INSTANCE("INSTANCE", "工作流实例"), + NODE("NODE", "节点实例"), + GLOBAL("GLOBAL", "全局"); + + private final String code; + private final String description; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/WorkflowStatusEnum.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/WorkflowStatusEnum.java index e4cac237..1bd0dc69 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/WorkflowStatusEnum.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/WorkflowStatusEnum.java @@ -22,7 +22,9 @@ public enum WorkflowStatusEnum { PAUSED("PAUSED", "已暂停"), COMPLETED("COMPLETED", "已完成"), FAILED("FAILED", "执行失败"), - CANCELLED("CANCELLED", "已取消"); + CANCELLED("CANCELLED", "已取消"), + SUSPENDED("SUSPENDED", "已暂停"), + TERMINATED("TERMINATED", "已终止"); private final String code; private final String description; @@ -31,7 +33,7 @@ public enum WorkflowStatusEnum { * 判断是否为终态 */ public boolean isFinalStatus() { - return this == COMPLETED || this == FAILED || this == CANCELLED; + return this == COMPLETED || this == FAILED || this == CANCELLED || this == SUSPENDED || this == TERMINATED; } /** diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/query/NodeInstanceQuery.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/query/NodeInstanceQuery.java new file mode 100644 index 00000000..9a4f5c92 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/query/NodeInstanceQuery.java @@ -0,0 +1,29 @@ +package com.qqchen.deploy.backend.workflow.query; + +import com.qqchen.deploy.backend.framework.annotation.QueryField; +import com.qqchen.deploy.backend.framework.enums.QueryType; +import com.qqchen.deploy.backend.framework.query.BaseQuery; +import com.qqchen.deploy.backend.workflow.enums.NodeStatusEnum; +import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnum; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class NodeInstanceQuery extends BaseQuery { + + @QueryField(field = "workflowInstanceId") + private Long workflowInstanceId; + + @QueryField(field = "nodeId") + private String nodeId; + + @QueryField(field = "nodeType") + private NodeTypeEnum nodeType; + + @QueryField(field = "name", type = QueryType.LIKE) + private String name; + + @QueryField(field = "status") + private NodeStatusEnum status; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/query/WorkflowDefinitionQuery.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/query/WorkflowDefinitionQuery.java similarity index 61% rename from backend/src/main/java/com/qqchen/deploy/backend/workflow/api/query/WorkflowDefinitionQuery.java rename to backend/src/main/java/com/qqchen/deploy/backend/workflow/query/WorkflowDefinitionQuery.java index 25d1f841..d302d2c9 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/query/WorkflowDefinitionQuery.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/query/WorkflowDefinitionQuery.java @@ -1,39 +1,22 @@ -package com.qqchen.deploy.backend.workflow.api.query; +package com.qqchen.deploy.backend.workflow.query; import com.qqchen.deploy.backend.framework.annotation.QueryField; import com.qqchen.deploy.backend.framework.enums.QueryType; import com.qqchen.deploy.backend.framework.query.BaseQuery; +import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnum; import lombok.Data; import lombok.EqualsAndHashCode; -/** - * 工作流定义查询对象 - */ @Data @EqualsAndHashCode(callSuper = true) public class WorkflowDefinitionQuery extends BaseQuery { - /** - * 工作流编码 - */ - @QueryField(field = "code", type = QueryType.LIKE) - private String code; - - /** - * 工作流名称 - */ @QueryField(field = "name", type = QueryType.LIKE) private String name; - /** - * 工作流类型 - */ - @QueryField(field = "type") - private String type; + @QueryField(field = "code", type = QueryType.LIKE) + private String code; - /** - * 工作流状态 - */ @QueryField(field = "status") - private String status; + private WorkflowStatusEnum status; } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/query/WorkflowInstanceQuery.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/query/WorkflowInstanceQuery.java new file mode 100644 index 00000000..4c0c06e3 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/query/WorkflowInstanceQuery.java @@ -0,0 +1,22 @@ +package com.qqchen.deploy.backend.workflow.query; + +import com.qqchen.deploy.backend.framework.annotation.QueryField; +import com.qqchen.deploy.backend.framework.enums.QueryType; +import com.qqchen.deploy.backend.framework.query.BaseQuery; +import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnum; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class WorkflowInstanceQuery extends BaseQuery { + + @QueryField(field = "businessKey", type = QueryType.LIKE) + private String businessKey; + + @QueryField(field = "definitionId") + private Long definitionId; + + @QueryField(field = "status") + private WorkflowStatusEnum status; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/query/WorkflowLogQuery.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/query/WorkflowLogQuery.java new file mode 100644 index 00000000..1ac1a5f1 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/query/WorkflowLogQuery.java @@ -0,0 +1,25 @@ +package com.qqchen.deploy.backend.workflow.query; + +import com.qqchen.deploy.backend.enums.LogLevelEnum; +import com.qqchen.deploy.backend.framework.annotation.QueryField; +import com.qqchen.deploy.backend.framework.enums.QueryType; +import com.qqchen.deploy.backend.framework.query.BaseQuery; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class WorkflowLogQuery extends BaseQuery { + + @QueryField(field = "workflowInstanceId") + private Long workflowInstanceId; + + @QueryField(field = "nodeId") + private String nodeId; + + @QueryField(field = "level") + private LogLevelEnum level; + + @QueryField(field = "message", type = QueryType.LIKE) + private String message; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/INodeDefinitionRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/INodeDefinitionRepository.java new file mode 100644 index 00000000..36b38baf --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/INodeDefinitionRepository.java @@ -0,0 +1,24 @@ +package com.qqchen.deploy.backend.workflow.repository; + +import com.qqchen.deploy.backend.framework.repository.IBaseRepository; +import com.qqchen.deploy.backend.workflow.entity.NodeDefinition; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface INodeDefinitionRepository extends IBaseRepository { + + /** + * 查询工作流定义的所有节点 + */ + @Query("SELECT n FROM NodeDefinition n WHERE n.workflowDefinitionId = :definitionId AND n.deleted = false ORDER BY n.sequence") + List findByWorkflowDefinitionIdOrderBySequence(@Param("definitionId") Long definitionId); + + /** + * 根据节点ID查询节点定义 + */ + NodeDefinition findByWorkflowDefinitionIdAndNodeIdAndDeletedFalse(Long workflowDefinitionId, String nodeId); +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/INodeInstanceRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/INodeInstanceRepository.java index 45693e40..2cd3c210 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/INodeInstanceRepository.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/INodeInstanceRepository.java @@ -2,33 +2,30 @@ package com.qqchen.deploy.backend.workflow.repository; import com.qqchen.deploy.backend.framework.repository.IBaseRepository; import com.qqchen.deploy.backend.workflow.entity.NodeInstance; +import com.qqchen.deploy.backend.workflow.enums.NodeStatusEnum; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; + import java.util.List; -/** - * 节点实例仓库接口 - */ @Repository public interface INodeInstanceRepository extends IBaseRepository { - - /** - * 根据工作流实例ID查询节点实例列表 - * - * @param workflowInstanceId 工作流实例ID - * @return 节点实例列表 - */ - @Query("SELECT n FROM NodeInstance n WHERE n.workflowInstance.id = :workflowInstanceId AND n.deleted = false ORDER BY n.createTime ASC") - List findByWorkflowInstanceId(@Param("workflowInstanceId") Long workflowInstanceId); /** - * 根据工作流实例ID和状态查询节点实例列表 - * - * @param workflowInstanceId 工作流实例ID - * @param status 状态 - * @return 节点实例列表 + * 查询工作流实例的所有节点 */ - @Query("SELECT n FROM NodeInstance n WHERE n.workflowInstance.id = :workflowInstanceId AND n.status = :status AND n.deleted = false ORDER BY n.createTime ASC") - List findByWorkflowInstanceIdAndStatus(@Param("workflowInstanceId") Long workflowInstanceId, @Param("status") String status); + @Query("SELECT n FROM NodeInstance n WHERE n.workflowInstanceId = :instanceId AND n.deleted = false ORDER BY n.createTime") + List findByWorkflowInstanceIdOrderByCreateTime(@Param("instanceId") Long instanceId); + + /** + * 查询指定状态的节点实例 + */ + List findByWorkflowInstanceIdAndStatusAndDeletedFalse(Long workflowInstanceId, NodeStatusEnum status); + + List findByInstanceIdAndStatus(Long instanceId, NodeStatusEnum status); + + List findByInstanceId(Long instanceId); + + void deleteByInstanceId(Long instanceId); } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowDefinitionRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowDefinitionRepository.java index 6a788bea..edee816a 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowDefinitionRepository.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowDefinitionRepository.java @@ -11,18 +11,17 @@ import org.springframework.stereotype.Repository; public interface IWorkflowDefinitionRepository extends IBaseRepository { /** - * 根据编码查询未删除的工作流定义 - * - * @param code 工作流编码 - * @return 工作流定义 + * 根据编码查询工作流定义 */ WorkflowDefinition findByCodeAndDeletedFalse(String code); - + /** - * 检查编码是否存在(排除已删除的) - * - * @param code 工作流编码 - * @return 是否存在 + * 检查编码是否存在 */ boolean existsByCodeAndDeletedFalse(String code); + + /** + * 检查名称是否存在 + */ + boolean existsByNameAndDeletedFalse(String name); } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowInstanceRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowInstanceRepository.java index 95912e0a..3e370b56 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowInstanceRepository.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowInstanceRepository.java @@ -2,9 +2,11 @@ package com.qqchen.deploy.backend.workflow.repository; import com.qqchen.deploy.backend.framework.repository.IBaseRepository; import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; +import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnum; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; + import java.util.List; /** @@ -14,21 +16,13 @@ import java.util.List; public interface IWorkflowInstanceRepository extends IBaseRepository { /** - * 根据项目环境ID查询工作流实例列表 - * - * @param projectEnvId 项目环境ID - * @return 工作流实例列表 + * 查询指定状态的工作流实例 */ - @Query("SELECT w FROM WorkflowInstance w WHERE w.projectEnvId = :projectEnvId AND w.deleted = false ORDER BY w.createTime DESC") - List findByProjectEnvId(@Param("projectEnvId") Long projectEnvId); - + @Query("SELECT i FROM WorkflowInstance i WHERE i.status IN :statuses AND i.deleted = false") + List findByStatusIn(@Param("statuses") List statuses); + /** - * 根据项目环境ID和状态查询工作流实例列表 - * - * @param projectEnvId 项目环境ID - * @param status 状态 - * @return 工作流实例列表 + * 查询项目环境的工作流实例 */ - @Query("SELECT w FROM WorkflowInstance w WHERE w.projectEnvId = :projectEnvId AND w.status = :status AND w.deleted = false ORDER BY w.createTime DESC") - List findByProjectEnvIdAndStatus(@Param("projectEnvId") Long projectEnvId, @Param("status") String status); + List findByProjectEnvIdAndDeletedFalseOrderByCreateTimeDesc(Long projectEnvId); } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowLogRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowLogRepository.java index a2095f38..531c1771 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowLogRepository.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowLogRepository.java @@ -1,11 +1,7 @@ package com.qqchen.deploy.backend.workflow.repository; -import com.qqchen.deploy.backend.enums.LogLevelEnum; -import com.qqchen.deploy.backend.enums.LogTypeEnum; import com.qqchen.deploy.backend.framework.repository.IBaseRepository; import com.qqchen.deploy.backend.workflow.entity.WorkflowLog; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; @@ -19,70 +15,19 @@ import java.util.List; public interface IWorkflowLogRepository extends IBaseRepository { /** - * 查询节点日志 - * - * @param nodeInstanceId 节点实例ID - * @param type 日志类型 - * @param level 日志级别 - * @return 日志列表 + * 查询工作流实例的所有日志 */ - @Query("SELECT l FROM WorkflowLog l WHERE l.nodeInstanceId = :nodeInstanceId " + - "AND (:type IS NULL OR l.type = :type) " + - "AND (:level IS NULL OR l.level = :level) " + - "AND l.deleted = false " + - "ORDER BY l.createTime DESC") - List findNodeLogs( - @Param("nodeInstanceId") Long nodeInstanceId, - @Param("type") LogTypeEnum type, - @Param("level") LogLevelEnum level - ); + @Query("SELECT l FROM WorkflowLog l WHERE l.workflowInstanceId = :instanceId AND l.deleted = false ORDER BY l.createTime") + List findByWorkflowInstanceId(@Param("instanceId") Long instanceId); /** - * 分页查询节点日志 - * - * @param nodeInstanceId 节点实例ID - * @param type 日志类型 - * @param level 日志级别 - * @param pageable 分页参数 - * @return 日志分页 + * 查询节点实例的所有日志 */ - @Query("SELECT l FROM WorkflowLog l WHERE l.nodeInstanceId = :nodeInstanceId " + - "AND (:type IS NULL OR l.type = :type) " + - "AND (:level IS NULL OR l.level = :level) " + - "AND l.deleted = false " + - "ORDER BY l.createTime DESC") - Page findNodeLogs( - @Param("nodeInstanceId") Long nodeInstanceId, - @Param("type") LogTypeEnum type, - @Param("level") LogLevelEnum level, - Pageable pageable - ); - - /** - * 分页查询工作流日志 - * - * @param workflowInstanceId 工作流实例ID - * @param type 日志类型 - * @param level 日志级别 - * @param pageable 分页参数 - * @return 日志分页 - */ - @Query("SELECT l FROM WorkflowLog l WHERE l.workflowInstanceId = :workflowInstanceId " + - "AND (:type IS NULL OR l.type = :type) " + - "AND (:level IS NULL OR l.level = :level) " + - "AND l.deleted = false " + - "ORDER BY l.createTime DESC") - Page findByWorkflowInstanceIdAndTypeAndLevel( - @Param("workflowInstanceId") Long workflowInstanceId, - @Param("type") LogTypeEnum type, - @Param("level") LogLevelEnum level, - Pageable pageable - ); + @Query("SELECT l FROM WorkflowLog l WHERE l.workflowInstanceId = :instanceId AND l.nodeId = :nodeId AND l.deleted = false ORDER BY l.createTime") + List findByWorkflowInstanceIdAndNodeId(@Param("instanceId") Long instanceId, @Param("nodeId") String nodeId); /** * 删除工作流实例的所有日志 - * - * @param workflowInstanceId 工作流实例ID */ - void deleteByWorkflowInstanceId(Long workflowInstanceId); + void deleteByWorkflowInstanceId(Long instanceId); } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowPermissionRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowPermissionRepository.java deleted file mode 100644 index c72d5d04..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowPermissionRepository.java +++ /dev/null @@ -1,123 +0,0 @@ -package com.qqchen.deploy.backend.workflow.repository; - -import com.qqchen.deploy.backend.enums.PermissionTypeEnum; -import com.qqchen.deploy.backend.framework.repository.IBaseRepository; -import com.qqchen.deploy.backend.workflow.entity.WorkflowPermission; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Repository; - -import java.util.List; -import java.util.Optional; - -/** - * 工作流权限仓库接口 - */ -@Repository -public interface IWorkflowPermissionRepository extends IBaseRepository { - - /** - * 根据工作流定义ID查询权限列表 - * - * @param workflowDefinitionId 工作流定义ID - * @return 权限列表 - */ - List findByWorkflowDefinitionId(Long workflowDefinitionId); - - /** - * 根据工作流定义ID和权限类型分页查询权限 - * - * @param workflowDefinitionId 工作流定义ID - * @param type 权限类型 - * @param pageable 分页参数 - * @return 权限分页 - */ - Page findByWorkflowDefinitionIdAndType(Long workflowDefinitionId, PermissionTypeEnum type, Pageable pageable); - - /** - * 查询用户权限 - * - * @param workflowDefinitionId 工作流定义ID - * @param userId 用户ID - * @param type 权限类型 - * @return 权限 - */ - Optional findByWorkflowDefinitionIdAndUserIdAndType(Long workflowDefinitionId, Long userId, PermissionTypeEnum type); - - /** - * 查询角色权限 - * - * @param workflowDefinitionId 工作流定义ID - * @param roleId 角色ID - * @param type 权限类型 - * @return 权限 - */ - Optional findByWorkflowDefinitionIdAndRoleIdAndType(Long workflowDefinitionId, Long roleId, PermissionTypeEnum type); - - /** - * 查询部门权限 - * - * @param workflowDefinitionId 工作流定义ID - * @param departmentId 部门ID - * @param type 权限类型 - * @return 权限 - */ - Optional findByWorkflowDefinitionIdAndDepartmentIdAndType(Long workflowDefinitionId, Long departmentId, PermissionTypeEnum type); - - /** - * 检查用户权限是否存在 - * - * @param workflowDefinitionId 工作流定义ID - * @param userId 用户ID - * @param type 权限类型 - * @return 是否存在 - */ - boolean existsByWorkflowDefinitionIdAndUserIdAndType(Long workflowDefinitionId, Long userId, PermissionTypeEnum type); - - /** - * 检查角色权限是否存在 - * - * @param workflowDefinitionId 工作流定义ID - * @param roleId 角色ID - * @param type 权限类型 - * @return 是否存在 - */ - boolean existsByWorkflowDefinitionIdAndRoleIdAndType(Long workflowDefinitionId, Long roleId, PermissionTypeEnum type); - - /** - * 检查部门权限是否存在 - * - * @param workflowDefinitionId 工作流定义ID - * @param departmentId 部门ID - * @param type 权限类型 - * @return 是否存在 - */ - boolean existsByWorkflowDefinitionIdAndDepartmentIdAndType(Long workflowDefinitionId, Long departmentId, PermissionTypeEnum type); - - /** - * 删除用户权限 - * - * @param workflowDefinitionId 工作流定义ID - * @param userId 用户ID - * @param type 权限类型 - */ - void deleteByWorkflowDefinitionIdAndUserIdAndType(Long workflowDefinitionId, Long userId, PermissionTypeEnum type); - - /** - * 删除角色权限 - * - * @param workflowDefinitionId 工作流定义ID - * @param roleId 角色ID - * @param type 权限类型 - */ - void deleteByWorkflowDefinitionIdAndRoleIdAndType(Long workflowDefinitionId, Long roleId, PermissionTypeEnum type); - - /** - * 删除部门权限 - * - * @param workflowDefinitionId 工作流定义ID - * @param departmentId 部门ID - * @param type 权限类型 - */ - void deleteByWorkflowDefinitionIdAndDepartmentIdAndType(Long workflowDefinitionId, Long departmentId, PermissionTypeEnum type); -} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowVariableRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowVariableRepository.java index 12f0bffc..0629beb9 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowVariableRepository.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowVariableRepository.java @@ -2,6 +2,8 @@ package com.qqchen.deploy.backend.workflow.repository; import com.qqchen.deploy.backend.framework.repository.IBaseRepository; import com.qqchen.deploy.backend.workflow.entity.WorkflowVariable; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import java.util.List; @@ -14,34 +16,24 @@ import java.util.Optional; public interface IWorkflowVariableRepository extends IBaseRepository { /** - * 根据工作流实例ID查询变量列表 - * - * @param workflowInstanceId 工作流实例ID - * @return 变量列表 + * 查询工作流实例的所有变量 */ - List findByWorkflowInstanceId(Long workflowInstanceId); + @Query("SELECT v FROM WorkflowVariable v WHERE v.workflowInstanceId = :instanceId AND v.deleted = false") + List findByWorkflowInstanceId(@Param("instanceId") Long instanceId); /** - * 根据工作流实例ID和变量名查询变量 - * - * @param workflowInstanceId 工作流实例ID - * @param name 变量名 - * @return 变量 + * 查询工作流实例的指定作用域的变量 + */ + @Query("SELECT v FROM WorkflowVariable v WHERE v.workflowInstanceId = :instanceId AND v.scope = :scope AND v.deleted = false") + List findByWorkflowInstanceIdAndScope(@Param("instanceId") Long instanceId, @Param("scope") String scope); + + /** + * 查询工作流实例的指定变量 */ Optional findByWorkflowInstanceIdAndName(Long workflowInstanceId, String name); /** - * 根据工作流实例ID和变量名删除变量 - * - * @param workflowInstanceId 工作流实例ID - * @param name 变量名 + * 删除工作流实例的所有变量 */ - void deleteByWorkflowInstanceIdAndName(Long workflowInstanceId, String name); - - /** - * 根据工作流实例ID删除所有变量 - * - * @param workflowInstanceId 工作流实例ID - */ - void deleteByWorkflowInstanceId(Long workflowInstanceId); + void deleteByWorkflowInstanceId(Long instanceId); } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/INodeInstanceService.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/INodeInstanceService.java deleted file mode 100644 index 3892b5e5..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/INodeInstanceService.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.qqchen.deploy.backend.workflow.service; - -import com.qqchen.deploy.backend.framework.service.IBaseService; -import com.qqchen.deploy.backend.workflow.api.dto.NodeInstanceDTO; -import com.qqchen.deploy.backend.workflow.entity.NodeInstance; -import com.qqchen.deploy.backend.workflow.enums.NodeStatusEnum; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; - -/** - * 节点实例服务接口 - */ -public interface INodeInstanceService extends IBaseService { - - /** - * 根据工作流实例ID查询节点实例列表 - * - * @param workflowInstanceId 工作流实例ID - * @return 节点实例列表 - */ - List findByWorkflowInstanceId(Long workflowInstanceId); - - /** - * 根据工作流实例ID和状态查询节点实例列表 - * - * @param workflowInstanceId 工作流实例ID - * @param status 状态 - * @return 节点实例列表 - */ - List findByWorkflowInstanceIdAndStatus(Long workflowInstanceId, String status); - - /** - * 更新节点状态 - * - * @param id 节点实例ID - * @param status 状态 - * @param output 输出结果 - * @param error 错误信息 - * @return 是否成功 - */ - boolean updateStatus(Long id, String status, String output, String error); - - List findByWorkflowInstanceIdAndStatus(Long workflowInstanceId, NodeStatusEnum status); - - @Transactional - void cancelRunningNodes(Long workflowInstanceId); -} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowDefinitionService.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowDefinitionService.java index 2d182377..32e9f773 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowDefinitionService.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowDefinitionService.java @@ -1,8 +1,9 @@ package com.qqchen.deploy.backend.workflow.service; import com.qqchen.deploy.backend.framework.service.IBaseService; -import com.qqchen.deploy.backend.workflow.api.dto.WorkflowDefinitionDTO; +import com.qqchen.deploy.backend.workflow.dto.WorkflowDefinitionDTO; import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition; +import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnum; /** * 工作流定义服务接口 @@ -10,26 +11,32 @@ import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition; public interface IWorkflowDefinitionService extends IBaseService { /** - * 发布工作流 - * - * @param id 工作流定义ID - * @return 是否成功 + * 发布工作流定义 */ - boolean publish(Long id); + void publish(Long id); + + /** + * 禁用工作流定义 + */ + void disable(Long id); + + /** + * 启用工作流定义 + */ + void enable(Long id); /** - * 禁用工作流 - * - * @param id 工作流定义ID - * @return 是否成功 + * 检查工作流名称是否唯一 */ - boolean disable(Long id); + void checkNameUnique(String name); /** - * 根据编码查询工作流定义 - * - * @param code 工作流编码 - * @return 工作流定义 + * 检查工作流编码是否唯一 */ - WorkflowDefinitionDTO findByCode(String code); + void checkCodeUnique(String code); + + /** + * 验证工作流状态 + */ + void validateStatus(WorkflowStatusEnum status); } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowInstanceService.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowInstanceService.java index 026fcc12..b73e0037 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowInstanceService.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowInstanceService.java @@ -1,53 +1,11 @@ package com.qqchen.deploy.backend.workflow.service; import com.qqchen.deploy.backend.framework.service.IBaseService; -import com.qqchen.deploy.backend.workflow.api.dto.WorkflowInstanceDTO; +import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceDTO; import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; -import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnum; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; /** * 工作流实例服务接口 */ public interface IWorkflowInstanceService extends IBaseService { - - @Transactional - void updateStatus(Long id, WorkflowStatusEnum status, String error); - - /** - * 启动工作流实例 - * - * @param definitionId 工作流定义ID - * @param projectEnvId 项目环境ID - * @param variables 工作流变量 - * @return 工作流实例 - */ - WorkflowInstanceDTO start(Long definitionId, Long projectEnvId, String variables); - - /** - * 取消工作流实例 - * - * @param id 工作流实例ID - * @return 是否成功 - */ - boolean cancel(Long id); - - /** - * 根据项目环境ID查询工作流实例列表 - * - * @param projectEnvId 项目环境ID - * @return 工作流实例列表 - */ - List findByProjectEnvId(Long projectEnvId); - - /** - * 根据项目环境ID和状态查询工作流实例列表 - * - * @param projectEnvId 项目环境ID - * @param status 状态 - * @return 工作流实例列表 - */ - List findByProjectEnvIdAndStatus(Long projectEnvId, String status); } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowLogService.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowLogService.java index 85643580..b5be735b 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowLogService.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowLogService.java @@ -1,147 +1,34 @@ package com.qqchen.deploy.backend.workflow.service; import com.qqchen.deploy.backend.enums.LogLevelEnum; -import com.qqchen.deploy.backend.enums.LogTypeEnum; -import com.qqchen.deploy.backend.workflow.entity.NodeInstance; -import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; +import com.qqchen.deploy.backend.framework.service.IBaseService; +import com.qqchen.deploy.backend.workflow.dto.WorkflowLogDTO; import com.qqchen.deploy.backend.workflow.entity.WorkflowLog; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; import java.util.List; /** * 工作流日志服务接口 */ -public interface IWorkflowLogService { +public interface IWorkflowLogService extends IBaseService { /** - * 记录工作流开始日志 - * - * @param instance 工作流实例 + * 记录日志 */ - void logWorkflowStart(WorkflowInstance instance); + void log(Long workflowInstanceId, String nodeId, String message, LogLevelEnum level, String detail); /** - * 记录工作流完成日志 - * - * @param instance 工作流实例 + * 查询工作流实例的所有日志 */ - void logWorkflowComplete(WorkflowInstance instance); + List getLogs(Long workflowInstanceId); /** - * 记录工作流暂停日志 - * - * @param instance 工作流实例 + * 查询节点实例的所有日志 */ - void logWorkflowPause(WorkflowInstance instance); + List getNodeLogs(Long workflowInstanceId, String nodeId); /** - * 记录工作流恢复日志 - * - * @param instance 工作流实例 + * 删除工作流实例的所有日志 */ - void logWorkflowResume(WorkflowInstance instance); - - /** - * 记录工作流取消日志 - * - * @param instance 工作流实例 - */ - void logWorkflowCancel(WorkflowInstance instance); - - /** - * 记录工作流错误日志 - * - * @param instance 工作流实例 - * @param error 错误信息 - */ - void logWorkflowError(WorkflowInstance instance, String error); - - /** - * 记录节点开始日志 - * - * @param node 节点实例 - */ - void logNodeStart(NodeInstance node); - - /** - * 记录节点完成日志 - * - * @param node 节点实例 - */ - void logNodeComplete(NodeInstance node); - - /** - * 记录节点错误日志 - * - * @param node 节点实例 - * @param error 错误信息 - */ - void logNodeError(NodeInstance node, String error); - - /** - * 记录节点重试日志 - * - * @param node 节点实例 - */ - void logNodeRetry(NodeInstance node); - - /** - * 记录节点跳过日志 - * - * @param node 节点实例 - */ - void logNodeSkip(NodeInstance node); - - /** - * 记录变量更新日志 - * - * @param instance 工作流实例 - * @param variableName 变量名 - * @param value 变量值 - */ - void logVariableUpdate(WorkflowInstance instance, String variableName, Object value); - - /** - * 记录系统日志 - * - * @param instance 工作流实例 - * @param level 日志级别 - * @param content 日志内容 - * @param detail 详细信息 - */ - void logSystem(WorkflowInstance instance, LogLevelEnum level, String content, String detail); - - /** - * 分页查询工作流日志 - * - * @param workflowInstanceId 工作流实例ID - * @param type 日志类型 - * @param level 日志级别 - * @param pageable 分页参数 - * @return 日志分页 - */ - Page findLogs(Long workflowInstanceId, LogTypeEnum type, LogLevelEnum level, Pageable pageable); - - /** - * 查询节点日志 - * - * @param nodeInstanceId 节点实例ID - * @param type 日志类型 - * @param level 日志级别 - * @return 日志列表 - */ - List findNodeLogs(Long nodeInstanceId, LogTypeEnum type, LogLevelEnum level); - - /** - * 分页查询节点日志 - * - * @param nodeInstanceId 节点实例ID - * @param type 日志类型 - * @param level 日志级别 - * @param pageable 分页参数 - * @return 日志分页 - */ - Page findNodeLogs(Long nodeInstanceId, LogTypeEnum type, LogLevelEnum level, Pageable pageable); + void deleteLogs(Long workflowInstanceId); } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowPermissionService.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowPermissionService.java deleted file mode 100644 index b128e08a..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowPermissionService.java +++ /dev/null @@ -1,96 +0,0 @@ -package com.qqchen.deploy.backend.workflow.service; - -import com.qqchen.deploy.backend.enums.PermissionTypeEnum; -import com.qqchen.deploy.backend.workflow.entity.WorkflowPermission; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; - -import java.util.List; - -/** - * 工作流权限服务接口 - */ -public interface IWorkflowPermissionService { - - /** - * 添加用户权限 - * - * @param workflowDefinitionId 工作流定义ID - * @param userId 用户ID - * @param type 权限类型 - */ - void addUserPermission(Long workflowDefinitionId, Long userId, PermissionTypeEnum type); - - /** - * 添加角色权限 - * - * @param workflowDefinitionId 工作流定义ID - * @param roleId 角色ID - * @param type 权限类型 - */ - void addRolePermission(Long workflowDefinitionId, Long roleId, PermissionTypeEnum type); - - /** - * 添加部门权限 - * - * @param workflowDefinitionId 工作流定义ID - * @param departmentId 部门ID - * @param type 权限类型 - */ - void addDepartmentPermission(Long workflowDefinitionId, Long departmentId, PermissionTypeEnum type); - - /** - * 移除用户权限 - * - * @param workflowDefinitionId 工作流定义ID - * @param userId 用户ID - * @param type 权限类型 - */ - void removeUserPermission(Long workflowDefinitionId, Long userId, PermissionTypeEnum type); - - /** - * 移除角色权限 - * - * @param workflowDefinitionId 工作流定义ID - * @param roleId 角色ID - * @param type 权限类型 - */ - void removeRolePermission(Long workflowDefinitionId, Long roleId, PermissionTypeEnum type); - - /** - * 移除部门权限 - * - * @param workflowDefinitionId 工作流定义ID - * @param departmentId 部门ID - * @param type 权限类型 - */ - void removeDepartmentPermission(Long workflowDefinitionId, Long departmentId, PermissionTypeEnum type); - - /** - * 检查用户是否有指定权限 - * - * @param workflowDefinitionId 工作流定义ID - * @param userId 用户ID - * @param type 权限类型 - * @return 是否有权限 - */ - boolean hasPermission(Long workflowDefinitionId, Long userId, PermissionTypeEnum type); - - /** - * 获取工作流定义的所有权限 - * - * @param workflowDefinitionId 工作流定义ID - * @return 权限列表 - */ - List getPermissions(Long workflowDefinitionId); - - /** - * 分页查询工作流权限 - * - * @param workflowDefinitionId 工作流定义ID - * @param type 权限类型 - * @param pageable 分页参数 - * @return 权限分页 - */ - Page findPermissions(Long workflowDefinitionId, PermissionTypeEnum type, Pageable pageable); -} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowVariableService.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowVariableService.java index a6b11095..8cdabefe 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowVariableService.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowVariableService.java @@ -1,58 +1,34 @@ package com.qqchen.deploy.backend.workflow.service; +import com.qqchen.deploy.backend.framework.service.IBaseService; +import com.qqchen.deploy.backend.workflow.dto.WorkflowVariableDTO; +import com.qqchen.deploy.backend.workflow.entity.WorkflowVariable; +import com.qqchen.deploy.backend.workflow.enums.VariableScopeEnum; + import java.util.Map; /** * 工作流变量服务接口 */ -public interface IWorkflowVariableService { +public interface IWorkflowVariableService extends IBaseService { /** - * 保存工作流变量 - * - * @param workflowInstanceId 工作流实例ID - * @param variables 变量Map - */ - void saveVariables(Long workflowInstanceId, Map variables); - - /** - * 获取工作流变量 - * - * @param workflowInstanceId 工作流实例ID - * @return 变量Map - */ - Map getVariables(Long workflowInstanceId); - - /** - * 获取工作流变量值 - * - * @param workflowInstanceId 工作流实例ID - * @param name 变量名 - * @return 变量值 - */ - Object getVariable(Long workflowInstanceId, String name); - - /** - * 设置工作流变量值 - * - * @param workflowInstanceId 工作流实例ID - * @param name 变量名 - * @param value 变量值 + * 设置变量 */ void setVariable(Long workflowInstanceId, String name, Object value); /** - * 删除工作流变量 - * - * @param workflowInstanceId 工作流实例ID - * @param name 变量名 + * 获取所有变量 */ - void deleteVariable(Long workflowInstanceId, String name); + Map getVariables(Long workflowInstanceId); /** - * 清空工作流变量 - * - * @param workflowInstanceId 工作流实例ID + * 获取指定作用域的变量 */ - void clearVariables(Long workflowInstanceId); + Map getVariablesByScope(Long workflowInstanceId, VariableScopeEnum scope); + + /** + * 删除所有变量 + */ + void deleteVariables(Long workflowInstanceId); } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/NodeInstanceServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/NodeInstanceServiceImpl.java deleted file mode 100644 index 7c51cf0c..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/NodeInstanceServiceImpl.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.qqchen.deploy.backend.workflow.service.impl; - -import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl; -import com.qqchen.deploy.backend.workflow.api.dto.NodeInstanceDTO; -import com.qqchen.deploy.backend.workflow.converter.NodeInstanceConverter; -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.INodeInstanceService; -import jakarta.annotation.Resource; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.stream.Collectors; - -@Service -public class NodeInstanceServiceImpl extends BaseServiceImpl implements INodeInstanceService { - - @Resource - private INodeInstanceRepository nodeInstanceRepository; - - @Resource - private NodeInstanceConverter nodeInstanceConverter; - - @Override - public List findByWorkflowInstanceId(Long workflowInstanceId) { - return nodeInstanceRepository.findByWorkflowInstanceId(workflowInstanceId) - .stream() - .map(nodeInstanceConverter::toDto) - .collect(Collectors.toList()); - } - - @Override - public List findByWorkflowInstanceIdAndStatus(Long workflowInstanceId, String status) { - return nodeInstanceRepository.findByWorkflowInstanceIdAndStatus(workflowInstanceId, status) - .stream() - .map(nodeInstanceConverter::toDto) - .collect(Collectors.toList()); - } - - @Override - @Transactional - public boolean updateStatus(Long id, String status, String output, String error) { - NodeInstance node = super.converter.toEntity(super.findById(id)); - node.setStatus(NodeStatusEnum.valueOf(status)); - node.setOutput(output); - node.setError(error); - - if (NodeStatusEnum.RUNNING.name().equals(status)) { - node.setStartTime(LocalDateTime.now()); - } else if (NodeStatusEnum.COMPLETED.name().equals(status) || - NodeStatusEnum.FAILED.name().equals(status) || - NodeStatusEnum.CANCELLED.name().equals(status) || - NodeStatusEnum.SKIPPED.name().equals(status)) { - node.setEndTime(LocalDateTime.now()); - } - - nodeInstanceRepository.save(node); - return true; - } - - @Override - public List findByWorkflowInstanceIdAndStatus(Long workflowInstanceId, NodeStatusEnum status) { - return nodeInstanceRepository.findByWorkflowInstanceIdAndStatus(workflowInstanceId, status.name()); - } - - @Override - @Transactional - public void cancelRunningNodes(Long workflowInstanceId) { - List runningNodes = nodeInstanceRepository.findByWorkflowInstanceIdAndStatus( - workflowInstanceId, NodeStatusEnum.RUNNING.name()); - runningNodes.forEach(node -> { - node.setStatus(NodeStatusEnum.CANCELLED); - node.setEndTime(LocalDateTime.now()); - nodeInstanceRepository.save(node); - }); - } -} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowDefinitionServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowDefinitionServiceImpl.java index 7f365051..b6ed8a72 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowDefinitionServiceImpl.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowDefinitionServiceImpl.java @@ -1,8 +1,9 @@ package com.qqchen.deploy.backend.workflow.service.impl; +import com.qqchen.deploy.backend.framework.enums.ResponseCode; +import com.qqchen.deploy.backend.framework.exception.BusinessException; import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl; -import com.qqchen.deploy.backend.workflow.api.dto.WorkflowDefinitionDTO; -import com.qqchen.deploy.backend.workflow.converter.WorkflowDefinitionConverter; +import com.qqchen.deploy.backend.workflow.dto.WorkflowDefinitionDTO; import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition; import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnum; import com.qqchen.deploy.backend.workflow.repository.IWorkflowDefinitionRepository; @@ -12,52 +13,69 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -/** - * 工作流定义服务实现类 - */ @Slf4j @Service -public class WorkflowDefinitionServiceImpl extends BaseServiceImpl - implements IWorkflowDefinitionService { +public class WorkflowDefinitionServiceImpl + extends BaseServiceImpl + implements IWorkflowDefinitionService { @Resource private IWorkflowDefinitionRepository workflowDefinitionRepository; - @Resource - private WorkflowDefinitionConverter workflowDefinitionConverter; - @Override @Transactional - public boolean publish(Long id) { - WorkflowDefinition definition = findEntityById(id); - definition.setStatus(WorkflowStatusEnum.PUBLISHED.getCode()); - workflowDefinitionRepository.save(definition); - return true; - } - - @Override - @Transactional - public boolean disable(Long id) { - WorkflowDefinition definition = findEntityById(id); - definition.setStatus(WorkflowStatusEnum.DISABLED.getCode()); - workflowDefinitionRepository.save(definition); - return true; - } - - @Override - public WorkflowDefinitionDTO findByCode(String code) { - WorkflowDefinition definition = workflowDefinitionRepository.findByCodeAndDeletedFalse(code); - return workflowDefinitionConverter.toDto(definition); - } - - @Transactional - public WorkflowDefinitionDTO save(WorkflowDefinitionDTO dto) { - // 检查编码是否已存在 - if (workflowDefinitionRepository.existsByCodeAndDeletedFalse(dto.getCode())) { - throw new IllegalArgumentException("工作流编码已存在"); + public void publish(Long id) { + WorkflowDefinition definition = findByIdWithLock(id); + if (definition.getStatus() != WorkflowStatusEnum.DRAFT) { + throw new BusinessException(ResponseCode.WORKFLOW_NOT_DRAFT); + } + definition.setStatus(WorkflowStatusEnum.PUBLISHED); + repository.save(definition); + } + + @Override + @Transactional + public void disable(Long id) { + WorkflowDefinition definition = findByIdWithLock(id); + if (definition.getStatus() != WorkflowStatusEnum.PUBLISHED) { + throw new BusinessException(ResponseCode.WORKFLOW_NOT_PUBLISHED); + } + definition.setStatus(WorkflowStatusEnum.DISABLED); + repository.save(definition); + } + + @Override + @Transactional + public void enable(Long id) { + WorkflowDefinition definition = findByIdWithLock(id); + if (definition.getStatus() != WorkflowStatusEnum.DISABLED) { + throw new BusinessException(ResponseCode.WORKFLOW_NOT_DISABLED); + } + definition.setStatus(WorkflowStatusEnum.PUBLISHED); + repository.save(definition); + } + + @Override + @Transactional + public void checkNameUnique(String name) { + if (workflowDefinitionRepository.existsByNameAndDeletedFalse(name)) { + throw new BusinessException(ResponseCode.WORKFLOW_NAME_EXISTS); + } + } + + @Override + @Transactional + public void checkCodeUnique(String code) { + if (workflowDefinitionRepository.existsByCodeAndDeletedFalse(code)) { + throw new BusinessException(ResponseCode.WORKFLOW_CODE_EXISTS); + } + } + + @Override + @Transactional + public void validateStatus(WorkflowStatusEnum status) { + if (status == null) { + throw new BusinessException(ResponseCode.WORKFLOW_INVALID_STATUS); } - // 设置初始状态为草稿 - dto.setStatus(WorkflowStatusEnum.DRAFT.getCode()); - return super.create(dto); } } \ 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 6a3acfeb..9083781d 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 @@ -1,66 +1,16 @@ package com.qqchen.deploy.backend.workflow.service.impl; import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl; -import com.qqchen.deploy.backend.workflow.api.dto.WorkflowInstanceDTO; +import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceDTO; import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; -import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnum; -import com.qqchen.deploy.backend.workflow.repository.IWorkflowInstanceRepository; -import com.qqchen.deploy.backend.workflow.service.INodeInstanceService; import com.qqchen.deploy.backend.workflow.service.IWorkflowInstanceService; -import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.time.LocalDateTime; -import java.util.List; +/** + * 工作流实例服务实现类 + */ +@Slf4j @Service public class WorkflowInstanceServiceImpl extends BaseServiceImpl implements IWorkflowInstanceService { - - @Resource - private IWorkflowInstanceRepository workflowInstanceRepository; - - @Resource - private INodeInstanceService nodeInstanceService; - - @Override - @Transactional - public void updateStatus(Long id, WorkflowStatusEnum status, String error) { - WorkflowInstance instance = super.converter.toEntity(super.findById(id)); - instance.setStatus(status); - instance.setError(error); - - if (status == WorkflowStatusEnum.RUNNING) { - instance.setStartTime(LocalDateTime.now()); - } else if (status == WorkflowStatusEnum.COMPLETED || status == WorkflowStatusEnum.FAILED || - status == WorkflowStatusEnum.CANCELLED) { - instance.setEndTime(LocalDateTime.now()); - } - - if (status == WorkflowStatusEnum.CANCELLED) { - nodeInstanceService.cancelRunningNodes(id); - } - - workflowInstanceRepository.save(instance); - } - - @Override - public WorkflowInstanceDTO start(Long definitionId, Long projectEnvId, String variables) { - return null; - } - - @Override - public boolean cancel(Long id) { - return false; - } - - @Override - public List findByProjectEnvId(Long projectEnvId) { - return List.of(); - } - - @Override - public List findByProjectEnvIdAndStatus(Long projectEnvId, String status) { - return List.of(); - } -} \ No newline at end of file +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowLogServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowLogServiceImpl.java index 99d11798..5e295b6f 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowLogServiceImpl.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowLogServiceImpl.java @@ -1,181 +1,59 @@ package com.qqchen.deploy.backend.workflow.service.impl; import com.qqchen.deploy.backend.enums.LogLevelEnum; -import com.qqchen.deploy.backend.enums.LogTypeEnum; -import com.qqchen.deploy.backend.workflow.entity.NodeInstance; -import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; +import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl; +import com.qqchen.deploy.backend.workflow.dto.WorkflowLogDTO; import com.qqchen.deploy.backend.workflow.entity.WorkflowLog; import com.qqchen.deploy.backend.workflow.repository.IWorkflowLogRepository; import com.qqchen.deploy.backend.workflow.service.IWorkflowLogService; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; +import java.util.stream.Collectors; /** * 工作流日志服务实现类 */ @Slf4j @Service -public class WorkflowLogServiceImpl implements IWorkflowLogService { +public class WorkflowLogServiceImpl extends BaseServiceImpl implements IWorkflowLogService { @Resource - private IWorkflowLogRepository workflowLogRepository; + private IWorkflowLogRepository logRepository; @Override - public List findNodeLogs(Long nodeInstanceId, LogTypeEnum type, LogLevelEnum level) { - return workflowLogRepository.findNodeLogs(nodeInstanceId, type, level); - } - - @Override - public Page findNodeLogs(Long nodeInstanceId, LogTypeEnum type, LogLevelEnum level, Pageable pageable) { - return workflowLogRepository.findNodeLogs(nodeInstanceId, type, level, pageable); - } - - @Override - public Page findLogs(Long workflowInstanceId, LogTypeEnum type, LogLevelEnum level, Pageable pageable) { - return workflowLogRepository.findByWorkflowInstanceIdAndTypeAndLevel(workflowInstanceId, type, level, pageable); - } - - @Override - public void logWorkflowStart(WorkflowInstance instance) { + @Transactional + public void log(Long workflowInstanceId, String nodeId, String message, LogLevelEnum level, String detail) { WorkflowLog log = new WorkflowLog(); - log.setWorkflowInstanceId(instance.getId()); - log.setType(LogTypeEnum.WORKFLOW_START); - log.setLevel(LogLevelEnum.INFO); - log.setContent("工作流开始执行"); - workflowLogRepository.save(log); - } - - @Override - public void logWorkflowComplete(WorkflowInstance instance) { - WorkflowLog log = new WorkflowLog(); - log.setWorkflowInstanceId(instance.getId()); - log.setType(LogTypeEnum.WORKFLOW_COMPLETE); - log.setLevel(LogLevelEnum.INFO); - log.setContent("工作流执行完成"); - workflowLogRepository.save(log); - } - - @Override - public void logWorkflowPause(WorkflowInstance instance) { - WorkflowLog log = new WorkflowLog(); - log.setWorkflowInstanceId(instance.getId()); - log.setType(LogTypeEnum.WORKFLOW_PAUSE); - log.setLevel(LogLevelEnum.INFO); - log.setContent("工作流已暂停"); - workflowLogRepository.save(log); - } - - @Override - public void logWorkflowResume(WorkflowInstance instance) { - WorkflowLog log = new WorkflowLog(); - log.setWorkflowInstanceId(instance.getId()); - log.setType(LogTypeEnum.WORKFLOW_RESUME); - log.setLevel(LogLevelEnum.INFO); - log.setContent("工作流已恢复"); - workflowLogRepository.save(log); - } - - @Override - public void logWorkflowCancel(WorkflowInstance instance) { - WorkflowLog log = new WorkflowLog(); - log.setWorkflowInstanceId(instance.getId()); - log.setType(LogTypeEnum.WORKFLOW_CANCEL); - log.setLevel(LogLevelEnum.INFO); - log.setContent("工作流已取消"); - workflowLogRepository.save(log); - } - - @Override - public void logWorkflowError(WorkflowInstance instance, String error) { - WorkflowLog log = new WorkflowLog(); - log.setWorkflowInstanceId(instance.getId()); - log.setType(LogTypeEnum.WORKFLOW_ERROR); - log.setLevel(LogLevelEnum.ERROR); - log.setContent("工作流执行出错"); - log.setDetail(error); - workflowLogRepository.save(log); - } - - @Override - public void logNodeStart(NodeInstance node) { - WorkflowLog log = new WorkflowLog(); - log.setWorkflowInstanceId(node.getWorkflowInstance().getId()); - log.setNodeInstanceId(node.getId()); - log.setType(LogTypeEnum.NODE_START); - log.setLevel(LogLevelEnum.INFO); - log.setContent(String.format("节点[%s]开始执行", node.getName())); - workflowLogRepository.save(log); - } - - @Override - public void logNodeComplete(NodeInstance node) { - WorkflowLog log = new WorkflowLog(); - log.setWorkflowInstanceId(node.getWorkflowInstance().getId()); - log.setNodeInstanceId(node.getId()); - log.setType(LogTypeEnum.NODE_COMPLETE); - log.setLevel(LogLevelEnum.INFO); - log.setContent(String.format("节点[%s]执行完成", node.getName())); - workflowLogRepository.save(log); - } - - @Override - public void logNodeError(NodeInstance node, String error) { - WorkflowLog log = new WorkflowLog(); - log.setWorkflowInstanceId(node.getWorkflowInstance().getId()); - log.setNodeInstanceId(node.getId()); - log.setType(LogTypeEnum.NODE_ERROR); - log.setLevel(LogLevelEnum.ERROR); - log.setContent(String.format("节点[%s]执行出错", node.getName())); - log.setDetail(error); - workflowLogRepository.save(log); - } - - @Override - public void logNodeRetry(NodeInstance node) { - WorkflowLog log = new WorkflowLog(); - log.setWorkflowInstanceId(node.getWorkflowInstance().getId()); - log.setNodeInstanceId(node.getId()); - log.setType(LogTypeEnum.NODE_RETRY); - log.setLevel(LogLevelEnum.WARN); - log.setContent(String.format("节点[%s]重试执行", node.getName())); - workflowLogRepository.save(log); - } - - @Override - public void logNodeSkip(NodeInstance node) { - WorkflowLog log = new WorkflowLog(); - log.setWorkflowInstanceId(node.getWorkflowInstance().getId()); - log.setNodeInstanceId(node.getId()); - log.setType(LogTypeEnum.NODE_SKIP); - log.setLevel(LogLevelEnum.WARN); - log.setContent(String.format("节点[%s]已跳过", node.getName())); - workflowLogRepository.save(log); - } - - @Override - public void logVariableUpdate(WorkflowInstance instance, String variableName, Object value) { - WorkflowLog log = new WorkflowLog(); - log.setWorkflowInstanceId(instance.getId()); - log.setType(LogTypeEnum.VARIABLE_UPDATE); - log.setLevel(LogLevelEnum.INFO); - log.setContent(String.format("变量[%s]更新为[%s]", variableName, value)); - workflowLogRepository.save(log); - } - - @Override - public void logSystem(WorkflowInstance instance, LogLevelEnum level, String content, String detail) { - WorkflowLog log = new WorkflowLog(); - log.setWorkflowInstanceId(instance.getId()); - log.setType(LogTypeEnum.SYSTEM); + log.setWorkflowInstanceId(workflowInstanceId); + log.setNodeId(nodeId); + log.setContent(message); log.setLevel(level); - log.setContent(content); log.setDetail(detail); - workflowLogRepository.save(log); + repository.save(log); + } + + @Override + @Transactional(readOnly = true) + public List getLogs(Long workflowInstanceId) { + List logs = logRepository.findByWorkflowInstanceId(workflowInstanceId); + return logs.stream().map(converter::toDto).collect(Collectors.toList()); + } + + @Override + @Transactional(readOnly = true) + public List getNodeLogs(Long workflowInstanceId, String nodeId) { + List logs = logRepository.findByWorkflowInstanceIdAndNodeId(workflowInstanceId, nodeId); + return logs.stream().map(converter::toDto).collect(Collectors.toList()); + } + + @Override + @Transactional + public void deleteLogs(Long workflowInstanceId) { + logRepository.deleteByWorkflowInstanceId(workflowInstanceId); + log.debug("删除工作流日志成功: instanceId={}", workflowInstanceId); } } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowPermissionServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowPermissionServiceImpl.java deleted file mode 100644 index 5016d0f4..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowPermissionServiceImpl.java +++ /dev/null @@ -1,127 +0,0 @@ -package com.qqchen.deploy.backend.workflow.service.impl; - -import com.qqchen.deploy.backend.enums.PermissionTypeEnum; -import com.qqchen.deploy.backend.system.entity.User; -import com.qqchen.deploy.backend.system.repository.IUserRepository; -import com.qqchen.deploy.backend.workflow.entity.WorkflowPermission; -import com.qqchen.deploy.backend.workflow.repository.IWorkflowPermissionRepository; -import com.qqchen.deploy.backend.workflow.service.IWorkflowPermissionService; -import jakarta.annotation.Resource; -import lombok.extern.slf4j.Slf4j; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; -import java.util.Optional; - -/** - * 工作流权限服务实现 - */ -@Slf4j -@Service -public class WorkflowPermissionServiceImpl implements IWorkflowPermissionService { - - @Resource - private IWorkflowPermissionRepository workflowPermissionRepository; - - @Resource - private IUserRepository userRepository; - - @Override - @Transactional - public void addUserPermission(Long workflowDefinitionId, Long userId, PermissionTypeEnum type) { - Optional existingPermission = workflowPermissionRepository - .findByWorkflowDefinitionIdAndUserIdAndType(workflowDefinitionId, userId, type); - if (existingPermission.isEmpty()) { - WorkflowPermission permission = new WorkflowPermission(); - permission.setWorkflowDefinitionId(workflowDefinitionId); - permission.setUserId(userId); - permission.setType(type); - workflowPermissionRepository.save(permission); - } - } - - @Override - @Transactional - public void addRolePermission(Long workflowDefinitionId, Long roleId, PermissionTypeEnum type) { - Optional existingPermission = workflowPermissionRepository - .findByWorkflowDefinitionIdAndRoleIdAndType(workflowDefinitionId, roleId, type); - if (existingPermission.isEmpty()) { - WorkflowPermission permission = new WorkflowPermission(); - permission.setWorkflowDefinitionId(workflowDefinitionId); - permission.setRoleId(roleId); - permission.setType(type); - workflowPermissionRepository.save(permission); - } - } - - @Override - @Transactional - public void addDepartmentPermission(Long workflowDefinitionId, Long departmentId, PermissionTypeEnum type) { - Optional existingPermission = workflowPermissionRepository - .findByWorkflowDefinitionIdAndDepartmentIdAndType(workflowDefinitionId, departmentId, type); - if (existingPermission.isEmpty()) { - WorkflowPermission permission = new WorkflowPermission(); - permission.setWorkflowDefinitionId(workflowDefinitionId); - permission.setDepartmentId(departmentId); - permission.setType(type); - workflowPermissionRepository.save(permission); - } - } - - @Override - @Transactional - public void removeUserPermission(Long workflowDefinitionId, Long userId, PermissionTypeEnum type) { - workflowPermissionRepository.deleteByWorkflowDefinitionIdAndUserIdAndType(workflowDefinitionId, userId, type); - } - - @Override - @Transactional - public void removeRolePermission(Long workflowDefinitionId, Long roleId, PermissionTypeEnum type) { - workflowPermissionRepository.deleteByWorkflowDefinitionIdAndRoleIdAndType(workflowDefinitionId, roleId, type); - } - - @Override - @Transactional - public void removeDepartmentPermission(Long workflowDefinitionId, Long departmentId, PermissionTypeEnum type) { - workflowPermissionRepository.deleteByWorkflowDefinitionIdAndDepartmentIdAndType(workflowDefinitionId, departmentId, type); - } - - @Override - public boolean hasPermission(Long workflowDefinitionId, Long userId, PermissionTypeEnum type) { - // 检查用户直接权限 - if (workflowPermissionRepository.existsByWorkflowDefinitionIdAndUserIdAndType(workflowDefinitionId, userId, type)) { - return true; - } - - // 获取用户信息 - Optional userOpt = userRepository.findById(userId); - if (userOpt.isEmpty()) { - return false; - } - User user = userOpt.get(); - -// // 检查角色权限 -// if (user.getRoleId() != null && workflowPermissionRepository -// .existsByWorkflowDefinitionIdAndRoleIdAndType(workflowDefinitionId, user.getRoleId(), type)) { -// return true; -// } -// -// // 检查部门权限 -// return user.getDepartmentId() != null && workflowPermissionRepository -// .existsByWorkflowDefinitionIdAndDepartmentIdAndType(workflowDefinitionId, user.getDepartmentId(), type); - return true; - } - - @Override - public List getPermissions(Long workflowDefinitionId) { - return workflowPermissionRepository.findByWorkflowDefinitionId(workflowDefinitionId); - } - - @Override - public Page findPermissions(Long workflowDefinitionId, PermissionTypeEnum type, Pageable pageable) { - return workflowPermissionRepository.findByWorkflowDefinitionIdAndType(workflowDefinitionId, type, pageable); - } -} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowVariableServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowVariableServiceImpl.java index f10a8a4b..2a75ee3a 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowVariableServiceImpl.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowVariableServiceImpl.java @@ -1,107 +1,106 @@ package com.qqchen.deploy.backend.workflow.service.impl; +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.framework.exception.BusinessException; +import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl; +import com.qqchen.deploy.backend.workflow.dto.WorkflowVariableDTO; +import com.qqchen.deploy.backend.workflow.engine.exception.WorkflowEngineException; import com.qqchen.deploy.backend.workflow.entity.WorkflowVariable; +import com.qqchen.deploy.backend.workflow.enums.VariableScopeEnum; import com.qqchen.deploy.backend.workflow.repository.IWorkflowVariableRepository; import com.qqchen.deploy.backend.workflow.service.IWorkflowVariableService; import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.HashMap; +import java.util.List; import java.util.Map; -import java.util.Optional; -import java.util.stream.Collectors; +/** + * 工作流变量服务实现类 + */ +@Slf4j @Service -public class WorkflowVariableServiceImpl implements IWorkflowVariableService { +public class WorkflowVariableServiceImpl extends BaseServiceImpl implements IWorkflowVariableService { @Resource - private IWorkflowVariableRepository workflowVariableRepository; + private IWorkflowVariableRepository variableRepository; - @Override - @Transactional - public void saveVariables(Long workflowInstanceId, Map variables) { - if (variables == null || variables.isEmpty()) { - throw new BusinessException(ResponseCode.WORKFLOW_VARIABLE_TYPE_INVALID); - } - - // 删除旧的变量 - workflowVariableRepository.deleteByWorkflowInstanceId(workflowInstanceId); - - // 保存新的变量 - variables.forEach((name, value) -> { - WorkflowVariable variable = new WorkflowVariable(); - variable.setWorkflowInstanceId(workflowInstanceId); - variable.setName(name); - variable.setValue(value.toString()); - variable.setType(value.getClass().getSimpleName()); - workflowVariableRepository.save(variable); - }); - } - - @Override - public Map getVariables(Long workflowInstanceId) { - return workflowVariableRepository.findByWorkflowInstanceId(workflowInstanceId) - .stream() - .collect(Collectors.toMap( - WorkflowVariable::getName, - this::convertValue - )); - } - - @Override - public Object getVariable(Long workflowInstanceId, String name) { - Optional variable = workflowVariableRepository.findByWorkflowInstanceIdAndName(workflowInstanceId, name); - return variable.map(this::convertValue).orElse(null); - } + private final ObjectMapper objectMapper = new ObjectMapper(); @Override @Transactional public void setVariable(Long workflowInstanceId, String name, Object value) { - WorkflowVariable variable = workflowVariableRepository.findByWorkflowInstanceIdAndName(workflowInstanceId, name) - .orElseGet(() -> { - WorkflowVariable newVar = new WorkflowVariable(); - newVar.setWorkflowInstanceId(workflowInstanceId); - newVar.setName(name); - return newVar; - }); - - variable.setValue(value.toString()); - variable.setType(value.getClass().getSimpleName()); - workflowVariableRepository.save(variable); - } - - @Override - @Transactional - public void deleteVariable(Long workflowInstanceId, String name) { - workflowVariableRepository.deleteByWorkflowInstanceIdAndName(workflowInstanceId, name); - } - - @Override - @Transactional - public void clearVariables(Long workflowInstanceId) { - workflowVariableRepository.deleteByWorkflowInstanceId(workflowInstanceId); - } - - private Object convertValue(WorkflowVariable variable) { try { - switch (variable.getType()) { - case "String": - return variable.getValue(); - case "Integer": - return Integer.parseInt(variable.getValue()); - case "Long": - return Long.parseLong(variable.getValue()); - case "Double": - return Double.parseDouble(variable.getValue()); - case "Boolean": - return Boolean.parseBoolean(variable.getValue()); - default: - throw new BusinessException(ResponseCode.WORKFLOW_VARIABLE_TYPE_INVALID); - } + WorkflowVariable variable = variableRepository.findByWorkflowInstanceIdAndName(workflowInstanceId, name) + .orElse(new WorkflowVariable()); + + variable.setWorkflowInstanceId(workflowInstanceId); + variable.setName(name); + variable.setValue(objectMapper.writeValueAsString(value)); + variable.setType(value.getClass().getName()); + + repository.save(variable); } catch (Exception e) { - throw new BusinessException(ResponseCode.WORKFLOW_VARIABLE_TYPE_INVALID); + throw new WorkflowEngineException(ResponseCode.WORKFLOW_VARIABLE_TYPE_INVALID, e); + } + } + + @Override + @Transactional(readOnly = true) + public Map getVariables(Long workflowInstanceId) { + try { + List variables = variableRepository.findByWorkflowInstanceId(workflowInstanceId); + Map result = new HashMap<>(); + + for (WorkflowVariable variable : variables) { + Object value = deserializeValue(variable); + result.put(variable.getName(), value); + } + + return result; + } catch (Exception e) { + throw new WorkflowEngineException(ResponseCode.WORKFLOW_EXECUTION_ERROR, e); + } + } + + @Override + @Transactional(readOnly = true) + public Map getVariablesByScope(Long workflowInstanceId, VariableScopeEnum scope) { + try { + List variables = variableRepository.findByWorkflowInstanceIdAndScope( + workflowInstanceId, scope.name()); + Map result = new HashMap<>(); + + for (WorkflowVariable variable : variables) { + Object value = deserializeValue(variable); + result.put(variable.getName(), value); + } + + return result; + } catch (Exception e) { + throw new WorkflowEngineException(ResponseCode.WORKFLOW_EXECUTION_ERROR, e); + } + } + + @Override + @Transactional + public void deleteVariables(Long workflowInstanceId) { + variableRepository.deleteByWorkflowInstanceId(workflowInstanceId); + log.debug("删除工作流变量成功: instanceId={}", workflowInstanceId); + } + + private Object deserializeValue(WorkflowVariable variable) { + try { + Class type = Class.forName(variable.getType()); + return objectMapper.readValue(variable.getValue(), type); + } catch (ClassNotFoundException e) { + throw new WorkflowEngineException(ResponseCode.WORKFLOW_VARIABLE_TYPE_INVALID, e); + } catch (JsonProcessingException e) { + throw new WorkflowEngineException(ResponseCode.WORKFLOW_VARIABLE_TYPE_INVALID, e); } } } \ No newline at end of file diff --git a/backend/src/main/resources/db/migration/V1.0.0__init_schema.sql b/backend/src/main/resources/db/migration/V1.0.0__init_schema.sql index 18ed02db..9b310820 100644 --- a/backend/src/main/resources/db/migration/V1.0.0__init_schema.sql +++ b/backend/src/main/resources/db/migration/V1.0.0__init_schema.sql @@ -145,7 +145,7 @@ CREATE TABLE sys_role_tag ( version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号', name VARCHAR(50) NOT NULL COMMENT '标签名称', - color VARCHAR(20) NULL COMMENT '标签���六进制颜色码)' + color VARCHAR(20) NULL COMMENT '标签进制颜色码)' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='角色标签表'; -- 角色标签关联表 @@ -378,111 +378,123 @@ CREATE TABLE deploy_repo_branch ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='代码仓库分支表'; -- 工作流定义表 -CREATE TABLE sys_workflow_definition ( - id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID', - create_by VARCHAR(255) NULL COMMENT '创建人', - create_time DATETIME(6) NULL COMMENT '创建时间', - deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除', - update_by VARCHAR(255) NULL COMMENT '更新人', - update_time DATETIME(6) NULL COMMENT '更新时间', - version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号', - tenant_id BIGINT NOT NULL COMMENT '租户ID', - - code VARCHAR(50) NOT NULL COMMENT '工作流编码', - name VARCHAR(100) NOT NULL COMMENT '工作流名称', - description TEXT COMMENT '描述', - content TEXT NOT NULL COMMENT '工作流定义(JSON)', - type VARCHAR(20) NOT NULL COMMENT '类型:DEPLOY/CONFIG_SYNC', - status VARCHAR(20) NOT NULL DEFAULT 'DRAFT' COMMENT '状态:DRAFT/PUBLISHED/DISABLED', - - CONSTRAINT uk_workflow_code UNIQUE (tenant_id, code), - CONSTRAINT uk_workflow_name UNIQUE (tenant_id, name) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='工作流定义表'; +CREATE TABLE wf_definition ( + id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID', + name VARCHAR(100) NOT NULL COMMENT '工作流名称', + code VARCHAR(50) NOT NULL COMMENT '工作流编码', + description TEXT COMMENT '描述', + status VARCHAR(20) NOT NULL COMMENT '状态', + node_config TEXT COMMENT '节点配置(JSON)', + transition_config TEXT COMMENT '流转配置(JSON)', + create_time DATETIME NOT NULL COMMENT '创建时间', + create_by VARCHAR(50) NOT NULL COMMENT '创建人', + update_time DATETIME NOT NULL COMMENT '更新时间', + update_by VARCHAR(50) NOT NULL COMMENT '更新人', + version INT NOT NULL DEFAULT 0 COMMENT '版本号', + deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否删除', + UNIQUE KEY uk_code (code) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='工作流定义表'; --- 工作流实例表 -CREATE TABLE sys_workflow_instance ( - id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID', - create_by VARCHAR(255) NULL COMMENT '创建人', - create_time DATETIME(6) NULL COMMENT '创建时间', - deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除', - update_by VARCHAR(255) NULL COMMENT '更新人', - update_time DATETIME(6) NULL COMMENT '更新时间', - version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号', - tenant_id BIGINT NOT NULL COMMENT '租户ID', - +-- 节点定义表 +CREATE TABLE wf_node_definition ( + id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID', + workflow_definition_id BIGINT NOT NULL COMMENT '工作流定义ID', + node_id VARCHAR(50) NOT NULL COMMENT '节点ID', + name VARCHAR(100) NOT NULL COMMENT '节点名称', + type VARCHAR(50) NOT NULL COMMENT '节点类型', + config TEXT COMMENT '节点配置(JSON)', + sequence INT NOT NULL DEFAULT 0 COMMENT '序号', + create_time DATETIME NOT NULL COMMENT '创建时间', + create_by VARCHAR(50) NOT NULL COMMENT '创建人', + update_time DATETIME NOT NULL COMMENT '更新时间', + update_by VARCHAR(50) NOT NULL COMMENT '更新人', + version INT NOT NULL DEFAULT 0 COMMENT '版本号', + deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否删除', + UNIQUE KEY uk_workflow_node (workflow_definition_id, node_id), + CONSTRAINT fk_node_workflow FOREIGN KEY (workflow_definition_id) REFERENCES wf_definition(id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='节点定义表'; + +-- 工作流实例表(修改现有表) +DROP TABLE IF EXISTS sys_workflow_instance; +CREATE TABLE wf_instance ( + id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID', definition_id BIGINT NOT NULL COMMENT '工作流定义ID', - project_env_id BIGINT NOT NULL COMMENT '项目环境ID', - status VARCHAR(20) NOT NULL COMMENT '状态:RUNNING/COMPLETED/FAILED/CANCELED', - start_time DATETIME NOT NULL COMMENT '开始时间', - end_time DATETIME COMMENT '结束时间', - variables TEXT COMMENT '工作流变量(JSON)', - error TEXT COMMENT '错误信息', - - CONSTRAINT fk_instance_definition FOREIGN KEY (definition_id) REFERENCES sys_workflow_definition(id) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='工作流实例表'; + business_key VARCHAR(100) NOT NULL COMMENT '业务标识', + status VARCHAR(20) NOT NULL COMMENT '状态', + variables TEXT COMMENT '工作流变量(JSON)', + start_time DATETIME NOT NULL COMMENT '开始时间', + end_time DATETIME COMMENT '结束时间', + error TEXT COMMENT '错误信息', + create_time DATETIME NOT NULL COMMENT '创建时间', + create_by VARCHAR(50) NOT NULL COMMENT '创建人', + update_time DATETIME NOT NULL COMMENT '更新时间', + update_by VARCHAR(50) NOT NULL COMMENT '更新人', + version INT NOT NULL DEFAULT 0 COMMENT '版本号', + deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否删除', + CONSTRAINT fk_instance_definition FOREIGN KEY (definition_id) REFERENCES wf_definition(id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='工作流实例表'; --- 节点实例表 -CREATE TABLE sys_node_instance ( - id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID', - create_by VARCHAR(255) NULL COMMENT '创建人', - create_time DATETIME(6) NULL COMMENT '创建时间', - deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除', - update_by VARCHAR(255) NULL COMMENT '更新人', - update_time DATETIME(6) NULL COMMENT '更新时间', - version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号', - tenant_id BIGINT NOT NULL COMMENT '租户ID', - +-- 节点实例表(修改现有表) +DROP TABLE IF EXISTS sys_node_instance; +CREATE TABLE wf_node_instance ( + id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID', workflow_instance_id BIGINT NOT NULL COMMENT '工作流实例ID', - node_id VARCHAR(50) NOT NULL COMMENT '节点ID', - node_type VARCHAR(50) NOT NULL COMMENT '节点类型', - name VARCHAR(100) NOT NULL COMMENT '节点名称', - status VARCHAR(20) NOT NULL COMMENT '状态:PENDING/RUNNING/COMPLETED/FAILED/CANCELED', - start_time DATETIME COMMENT '开始时间', - end_time DATETIME COMMENT '结束时间', - input TEXT COMMENT '输入参数(JSON)', - output TEXT COMMENT '输出结果(JSON)', - error TEXT COMMENT '错误信息', - - CONSTRAINT fk_node_workflow_instance FOREIGN KEY (workflow_instance_id) REFERENCES sys_workflow_instance(id) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='节点实例表'; + node_id VARCHAR(50) NOT NULL COMMENT '节点ID', + node_type VARCHAR(50) NOT NULL COMMENT '节点类型', + name VARCHAR(100) NOT NULL COMMENT '节点名称', + status VARCHAR(20) NOT NULL COMMENT '状���', + config TEXT COMMENT '节点配置(JSON)', + input TEXT COMMENT '输入参数(JSON)', + output TEXT COMMENT '输出结果(JSON)', + error TEXT COMMENT '错误信息', + start_time DATETIME COMMENT '开始时间', + end_time DATETIME COMMENT '结束时间', + create_time DATETIME NOT NULL COMMENT '创建时间', + create_by VARCHAR(50) NOT NULL COMMENT '创建人', + update_time DATETIME NOT NULL COMMENT '更新时间', + update_by VARCHAR(50) NOT NULL COMMENT '更新人', + version INT NOT NULL DEFAULT 0 COMMENT '版本号', + deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否删除', + CONSTRAINT fk_node_instance_workflow FOREIGN KEY (workflow_instance_id) REFERENCES wf_instance(id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='节点实例表'; -- 工作流变量表 -CREATE TABLE wf_workflow_variable ( - id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键', - workflow_instance_id BIGINT NOT NULL COMMENT '工作流实例ID', - name VARCHAR(255) NOT NULL COMMENT '变量名', - value TEXT COMMENT '变量值', - type VARCHAR(255) NOT NULL COMMENT '变量类型', - create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - create_by BIGINT NOT NULL COMMENT '创建人', - update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - update_by BIGINT NOT NULL COMMENT '更新人', - version INT NOT NULL DEFAULT 0 COMMENT '版本号', - deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否删除', - INDEX idx_workflow_instance_id (workflow_instance_id), - INDEX idx_name (name) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='工作流变量'; +CREATE TABLE wf_variable ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + workflow_instance_id BIGINT NOT NULL, + name VARCHAR(255) NOT NULL, + value TEXT NOT NULL, + type VARCHAR(255), + scope VARCHAR(50), + create_time DATETIME NOT NULL, + create_by VARCHAR(50), + update_time DATETIME, + update_by VARCHAR(50), + version INT DEFAULT 0, + deleted BOOLEAN DEFAULT FALSE, + CONSTRAINT uk_wf_variable UNIQUE (workflow_instance_id, name, deleted) +); -- 工作流日志表 -CREATE TABLE wf_workflow_log ( - id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键', - workflow_instance_id BIGINT NOT NULL COMMENT '工作流实例ID', - node_instance_id BIGINT COMMENT '节点实例ID', - type VARCHAR(50) NOT NULL COMMENT '日志类型', - level VARCHAR(20) NOT NULL COMMENT '日志级别', - content TEXT NOT NULL COMMENT '日志内容', - detail TEXT COMMENT '详细信息', - create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - create_by BIGINT NOT NULL COMMENT '创建人', - update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - update_by BIGINT NOT NULL COMMENT '更新人', - version INT NOT NULL DEFAULT 0 COMMENT '版本号', - deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否删除', - INDEX idx_workflow_instance_id (workflow_instance_id), - INDEX idx_node_instance_id (node_instance_id), - INDEX idx_type (type), - INDEX idx_level (level) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='工作流日志'; +CREATE TABLE wf_log ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + workflow_instance_id BIGINT NOT NULL, + node_id VARCHAR(50), + message VARCHAR(1000) NOT NULL, + level VARCHAR(20) NOT NULL, + detail TEXT, + create_time DATETIME NOT NULL, + create_by VARCHAR(50), + update_time DATETIME, + update_by VARCHAR(50), + version INT DEFAULT 0, + deleted BOOLEAN DEFAULT FALSE +); + +-- 创建索引 +CREATE INDEX idx_wf_variable_instance ON wf_variable (workflow_instance_id); +CREATE INDEX idx_wf_log_instance ON wf_log (workflow_instance_id); +CREATE INDEX idx_wf_log_node ON wf_log (workflow_instance_id, node_id); -- 工作流权限表 CREATE TABLE wf_workflow_permission ( diff --git a/backend/src/main/resources/db/migration/V1.0.1__init_data.sql b/backend/src/main/resources/db/migration/V1.0.1__init_data.sql index e1fa1bb7..5fb50831 100644 --- a/backend/src/main/resources/db/migration/V1.0.1__init_data.sql +++ b/backend/src/main/resources/db/migration/V1.0.1__init_data.sql @@ -150,121 +150,4 @@ INSERT INTO sys_external_system ( '链宇GIT', 'GIT', 'http://119.3.203.210:8088/', NULL, 1, 1, 'TOKEN', NULL, NULL, 'cNSud7D1GmYQKEMco7s5', NULL, NULL, NULL, '{}' -); - --- 初始化工作流定义数据 -INSERT INTO sys_workflow_definition ( - create_by, create_time, deleted, update_by, update_time, version, tenant_id, - code, name, description, content, type, status -) VALUES ( - 'admin', NOW(), 0, 'admin', NOW(), 0, 1, - 'DEPLOY_JAVA_APP', 'Java应用部署流程', 'Java应用标准部署流程', - '{ - "nodes": [ - { - "nodeId": "start", - "type": "START", - "name": "开始", - "nextNodeId": "build" - }, - { - "nodeId": "build", - "type": "JENKINS", - "name": "构建应用", - "jenkinsServerId": 1, - "jobName": "$${projectName}-build", - "parameters": { - "BRANCH": "$${branch}", - "ENV": "$${env}" - }, - "nextNodeId": "approval" - }, - { - "nodeId": "approval", - "type": "APPROVAL", - "name": "部署审批", - "approverType": "ROLE", - "approverIds": [1], - "strategy": "ANY", - "nextNodeId": "deploy" - }, - { - "nodeId": "deploy", - "type": "SCRIPT", - "name": "部署应用", - "scriptType": "SHELL", - "content": "kubectl apply -f $${deployYaml}", - "nextNodeId": "end" - }, - { - "nodeId": "end", - "type": "END", - "name": "结束" - } - ] - }', - 'DEPLOY', - 'PUBLISHED' -); - -INSERT INTO sys_workflow_definition ( - create_by, create_time, deleted, update_by, update_time, version, tenant_id, - code, name, description, content, type, status -) VALUES ( - 'admin', NOW(), 0, 'admin', NOW(), 0, 1, - 'SYNC_NACOS_CONFIG', 'Nacos配置同步流程', 'Nacos配置跨环境同步流程', - '{ - "nodes": [ - { - "nodeId": "start", - "type": "START", - "name": "开始", - "nextNodeId": "export" - }, - { - "nodeId": "export", - "type": "NACOS", - "name": "导出配置", - "nacosServerId": 1, - "operation": "EXPORT", - "namespace": "$${sourceNamespace}", - "nextNodeId": "approval" - }, - { - "nodeId": "approval", - "type": "APPROVAL", - "name": "同步审批", - "approverType": "ROLE", - "approverIds": [1], - "strategy": "ANY", - "nextNodeId": "import" - }, - { - "nodeId": "import", - "type": "NACOS", - "name": "导入配置", - "nacosServerId": 2, - "operation": "IMPORT", - "namespace": "$${targetNamespace}", - "nextNodeId": "notify" - }, - { - "nodeId": "notify", - "type": "NOTIFY", - "name": "通知结果", - "notifyType": "DINGTALK", - "receiverType": "ROLE", - "receiverIds": [1], - "title": "配置同步完成通知", - "nextNodeId": "end" - }, - { - "nodeId": "end", - "type": "END", - "name": "结束" - } - ] - }', - 'CONFIG_SYNC', - 'PUBLISHED' ); \ No newline at end of file diff --git a/backend/src/main/resources/messages.properties b/backend/src/main/resources/messages.properties index 641bc79c..4616a745 100644 --- a/backend/src/main/resources/messages.properties +++ b/backend/src/main/resources/messages.properties @@ -109,4 +109,43 @@ workflow.instance.cannot.retry=工作流实例无法重试 node.instance.not.found=节点实例不存在 node.instance.cannot.retry=节点实例无法重试 node.instance.cannot.skip=节点实例无法跳过 -node.executor.not.found=节点执行器不存在 \ No newline at end of file +node.executor.not.found=节点执行器不存在 + +# 工作流相关消息 +workflow.not.found=工作流定义不存在 +workflow.code.exists=工作流编码已存在 +workflow.name.exists=工作流名称已存在 +workflow.disabled=工作流已禁用 +workflow.not.published=工作流未发布 +workflow.already.published=工作流已发布 +workflow.already.disabled=工作流已禁用 +workflow.instance.not.found=工作流实例不存在 +workflow.instance.already.completed=工作流实例已完成 +workflow.instance.already.canceled=工作流实例已取消 +workflow.instance.not.running=工作流实例未运行 +workflow.node.not.found=工作流节点不存在 +workflow.node.type.not.supported=不支持的节点类型 +workflow.node.config.invalid=节点配置无效 +workflow.node.execution.failed=节点执行失败 +workflow.node.timeout=节点执行超时 +workflow.variable.not.found=工作流变量不存在 +workflow.variable.type.invalid=工作流变量类型无效 +workflow.permission.denied=工作流权限不足 +workflow.approval.required=需要审批 +workflow.approval.rejected=审批被拒绝 +workflow.dependency.not.satisfied=依赖条件不满足 +workflow.circular.dependency=存在循环依赖 +workflow.schedule.invalid=调度配置无效 +workflow.concurrent.limit.exceeded=超出并发限制 + +# Workflow error messages +workflow.not.found=工作流定义不存在 +workflow.code.exists=工作流编码已存在 +workflow.name.exists=工作流名称已存在 +workflow.invalid.status=工作流状态无效 +workflow.node.not.found=工作流节点不存在 +workflow.node.config.error=工作流节点配置错误 +workflow.execution.error=工作流执行错误 +workflow.not.draft=只有草稿状态的工作流定义可以发布 +workflow.not.published=只有已发布状态的工作流定义可以禁用 +workflow.not.disabled=只有已禁用状态的工作流定义可以启用 \ No newline at end of file diff --git a/backend/src/test/java/com/qqchen/deploy/backend/service/impl/ExternalSystemServiceImplTest.java b/backend/src/test/java/com/qqchen/deploy/backend/service/impl/ExternalSystemServiceImplTest.java index 3542fce7..716bedea 100644 --- a/backend/src/test/java/com/qqchen/deploy/backend/service/impl/ExternalSystemServiceImplTest.java +++ b/backend/src/test/java/com/qqchen/deploy/backend/service/impl/ExternalSystemServiceImplTest.java @@ -4,6 +4,9 @@ import com.qqchen.deploy.backend.system.entity.ExternalSystem; import com.qqchen.deploy.backend.framework.enums.ResponseCode; import com.qqchen.deploy.backend.framework.exception.BusinessException; import com.qqchen.deploy.backend.framework.exception.UniqueConstraintException; +import com.qqchen.deploy.backend.system.enums.ExternalSystemAuthTypeEnum; +import com.qqchen.deploy.backend.system.enums.ExternalSystemSyncStatusEnum; +import com.qqchen.deploy.backend.system.enums.ExternalSystemTypeEnum; import com.qqchen.deploy.backend.system.model.ExternalSystemDTO; import com.qqchen.deploy.backend.system.repository.IExternalSystemRepository; import com.qqchen.deploy.backend.system.service.impl.ExternalSystemServiceImpl; @@ -38,9 +41,9 @@ class ExternalSystemServiceImplTest { system = new ExternalSystem(); system.setId(1L); system.setName("测试Jenkins"); - system.setType(ExternalSystem.ExternalSystemSystemType.JENKINS); + system.setType(ExternalSystemTypeEnum.JENKINS); system.setUrl("http://jenkins.test.com"); - system.setAuthType(ExternalSystem.AuthType.BASIC); + system.setAuthType(ExternalSystemAuthTypeEnum.BASIC); system.setUsername("admin"); system.setPassword("password"); system.setEnabled(true); @@ -48,9 +51,9 @@ class ExternalSystemServiceImplTest { systemDTO = new ExternalSystemDTO(); systemDTO.setName("测试Jenkins"); - systemDTO.setType(ExternalSystem.ExternalSystemSystemType.JENKINS); + systemDTO.setType(ExternalSystemTypeEnum.JENKINS); systemDTO.setUrl("http://jenkins.test.com"); - systemDTO.setAuthType(ExternalSystem.AuthType.BASIC); + systemDTO.setAuthType(ExternalSystemAuthTypeEnum.BASIC); systemDTO.setUsername("admin"); systemDTO.setPassword("password"); systemDTO.setEnabled(true); @@ -127,7 +130,7 @@ class ExternalSystemServiceImplTest { externalSystemService.syncData(1L); // 验证 - assertThat(system.getSyncStatus()).isEqualTo(ExternalSystem.SyncStatus.SUCCESS); + assertThat(system.getSyncStatus()).isEqualTo(ExternalSystemSyncStatusEnum.SUCCESS); assertThat(system.getLastSyncTime()).isNotNull(); verify(externalSystemRepository, times(2)).save(any(ExternalSystem.class)); } @@ -149,8 +152,8 @@ class ExternalSystemServiceImplTest { @Test void validateUniqueConstraints_WhenGitWithoutToken_ShouldThrowException() { // 准备数据 - systemDTO.setType(ExternalSystem.ExternalSystemSystemType.GIT); - systemDTO.setAuthType(ExternalSystem.AuthType.TOKEN); + systemDTO.setType(ExternalSystemTypeEnum.GIT); + systemDTO.setAuthType(ExternalSystemAuthTypeEnum.TOKEN); systemDTO.setToken(null); // Mock @@ -167,8 +170,8 @@ class ExternalSystemServiceImplTest { @Test void validateUniqueConstraints_WhenGitWithWrongAuthType_ShouldThrowException() { // 准备数据 - systemDTO.setType(ExternalSystem.ExternalSystemSystemType.GIT); - systemDTO.setAuthType(ExternalSystem.AuthType.BASIC); + systemDTO.setType(ExternalSystemTypeEnum.GIT); + systemDTO.setAuthType(ExternalSystemAuthTypeEnum.BASIC); // Mock when(externalSystemRepository.existsByNameAndDeletedFalse(systemDTO.getName())).thenReturn(false); @@ -184,8 +187,8 @@ class ExternalSystemServiceImplTest { @Test void update_WhenGitWithoutToken_ShouldThrowException() { // 准备数据 - systemDTO.setType(ExternalSystem.ExternalSystemSystemType.GIT); - systemDTO.setAuthType(ExternalSystem.AuthType.TOKEN); + systemDTO.setType(ExternalSystemTypeEnum.GIT); + systemDTO.setAuthType(ExternalSystemAuthTypeEnum.TOKEN); systemDTO.setToken(null); // 验证 @@ -197,8 +200,8 @@ class ExternalSystemServiceImplTest { @Test void update_WhenGitWithWrongAuthType_ShouldThrowException() { // 准备数据 - systemDTO.setType(ExternalSystem.ExternalSystemSystemType.GIT); - systemDTO.setAuthType(ExternalSystem.AuthType.BASIC); + systemDTO.setType(ExternalSystemTypeEnum.GIT); + systemDTO.setAuthType(ExternalSystemAuthTypeEnum.BASIC); // 验证 assertThatThrownBy(() -> externalSystemService.update(1L, systemDTO)) diff --git a/backend/src/test/java/com/qqchen/deploy/backend/workflow/engine/WorkflowEngineTest.java b/backend/src/test/java/com/qqchen/deploy/backend/workflow/engine/WorkflowEngineTest.java new file mode 100644 index 00000000..14515d85 --- /dev/null +++ b/backend/src/test/java/com/qqchen/deploy/backend/workflow/engine/WorkflowEngineTest.java @@ -0,0 +1,325 @@ +package com.qqchen.deploy.backend.workflow.engine; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.qqchen.deploy.backend.workflow.dto.WorkflowDefinitionDTO; +import com.qqchen.deploy.backend.workflow.engine.context.DefaultWorkflowContext; +import com.qqchen.deploy.backend.workflow.engine.executor.*; +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.WorkflowStatusEnum; +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.IWorkflowDefinitionService; +import com.qqchen.deploy.backend.workflow.service.IWorkflowLogService; +import com.qqchen.deploy.backend.workflow.service.IWorkflowVariableService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.HashMap; +import java.util.List; +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.anyString; +import static org.mockito.Mockito.*; + +@Slf4j +@SpringBootTest(classes = { + WorkflowEngineTest.TestConfig.class, + DefaultWorkflowEngine.class, + ObjectMapper.class +}) +class WorkflowEngineTest { + + @Configuration + static class TestConfig { + @Bean + Map nodeExecutors() { + Map executors = new HashMap<>(); + StartNodeExecutor startExecutor = mock(StartNodeExecutor.class); + doNothing().when(startExecutor).execute(any(), any()); + + EndNodeExecutor endExecutor = mock(EndNodeExecutor.class); + doNothing().when(endExecutor).execute(any(), any()); + + TaskNodeExecutor taskExecutor = mock(TaskNodeExecutor.class); + doNothing().when(taskExecutor).execute(any(), any()); + + GatewayNodeExecutor gatewayExecutor = mock(GatewayNodeExecutor.class); + doNothing().when(gatewayExecutor).execute(any(), any()); + + executors.put(NodeTypeEnum.START, startExecutor); + executors.put(NodeTypeEnum.END, endExecutor); + executors.put(NodeTypeEnum.TASK, taskExecutor); + executors.put(NodeTypeEnum.GATEWAY, gatewayExecutor); + return executors; + } + + @Bean + IWorkflowVariableService workflowVariableService() { + IWorkflowVariableService service = mock(IWorkflowVariableService.class); + doNothing().when(service).setVariable(any(), anyString(), any()); + return service; + } + + @Bean + IWorkflowLogService workflowLogService() { + return mock(IWorkflowLogService.class); + } + + @Bean + DefaultWorkflowContext.Factory workflowContextFactory( + IWorkflowVariableService variableService, + IWorkflowLogService logService + ) { + return new DefaultWorkflowContext.Factory(variableService, logService); + } + } + + @Resource + private WorkflowEngine workflowEngine; + + @MockBean + private IWorkflowDefinitionService workflowDefinitionService; + + @MockBean + private IWorkflowDefinitionRepository workflowDefinitionRepository; + + @MockBean + private IWorkflowInstanceRepository workflowInstanceRepository; + + @MockBean + private INodeInstanceRepository nodeInstanceRepository; + + @Resource + private ObjectMapper objectMapper; + + private NodeInstance savedStartNode; + + @BeforeEach + void setUp() { + log.info("Setting up workflow engine test mocks..."); + + // Mock workflowDefinitionService + when(workflowDefinitionService.create(any())).thenAnswer(i -> { + WorkflowDefinitionDTO dto = i.getArgument(0); + dto.setId(1L); + log.info("Created workflow definition with ID: {}", dto.getId()); + return dto; + }); + + // Mock workflowDefinitionRepository + when(workflowDefinitionRepository.findByCodeAndDeletedFalse(anyString())) + .thenAnswer(i -> { + WorkflowDefinition definition = new WorkflowDefinition(); + definition.setId(1L); + definition.setName("简单测试工作流"); + definition.setCode("SIMPLE_TEST_WORKFLOW"); + definition.setStatus(WorkflowStatusEnum.PUBLISHED); + definition.setNodeConfig(createSimpleWorkflow().getNodeConfig()); + definition.setTransitionConfig(createSimpleWorkflow().getTransitionConfig()); + log.info("Found workflow definition: code={}, status={}", definition.getCode(), definition.getStatus()); + return definition; + }); + + // Mock workflowInstanceRepository + when(workflowInstanceRepository.save(any())).thenAnswer(i -> { + WorkflowInstance instance = i.getArgument(0); + if (instance.getId() == null) { + instance.setId(1L); + } + log.info("Saved workflow instance: id={}, status={}", instance.getId(), instance.getStatus()); + return instance; + }); + + // Mock nodeInstanceRepository + when(nodeInstanceRepository.save(any())).thenAnswer(i -> { + NodeInstance node = i.getArgument(0); + if (node.getId() == null) { + node.setId(System.nanoTime()); + } + savedStartNode = node; + + // Create and set the workflow instance if it doesn't exist + if (node.getWorkflowInstance() == null) { + WorkflowInstance instance = new WorkflowInstance(); + instance.setId(1L); + instance.setStatus(WorkflowStatusEnum.RUNNING); + node.setWorkflowInstance(instance); + } + + log.info("Saved node instance: id={}, type={}, status={}, workflowInstanceId={}", + node.getId(), node.getNodeType(), node.getStatus(), node.getWorkflowInstance().getId()); + return node; + }); + + when(nodeInstanceRepository.findById(any())).thenAnswer(i -> { + if (savedStartNode != null && savedStartNode.getId().equals(i.getArgument(0))) { + log.info("Found node instance by id: {}, type={}, status={}", + savedStartNode.getId(), savedStartNode.getNodeType(), savedStartNode.getStatus()); + return Optional.of(savedStartNode); + } + log.warn("Node instance not found with id: {}", (Object)i.getArgument(0)); + return Optional.empty(); + }); + + when(nodeInstanceRepository.findByWorkflowInstanceIdOrderByCreateTime(any())) + .thenAnswer(i -> { + WorkflowInstance instance = new WorkflowInstance(); + instance.setId(1L); + instance.setStatus(WorkflowStatusEnum.RUNNING); + + List nodes = List.of( + createNodeInstance(NodeTypeEnum.START, NodeStatusEnum.COMPLETED), + createNodeInstance(NodeTypeEnum.TASK, NodeStatusEnum.COMPLETED), + createNodeInstance(NodeTypeEnum.END, NodeStatusEnum.COMPLETED) + ); + + // Set workflow instance for each node + nodes.forEach(node -> node.setWorkflowInstance(instance)); + + log.info("Found {} nodes for workflow instance id={}", nodes.size(), i.getArgument(0)); + nodes.forEach(node -> log.info("Node: type={}, status={}", node.getNodeType(), node.getStatus())); + + return nodes; + }); + + log.info("Test setup completed"); + } + + @Test + void testSimpleWorkflow() { + log.info("Starting simple workflow test"); + + // 1. 创建工作流定义 + WorkflowDefinitionDTO definition = createSimpleWorkflow(); + log.info("Created workflow definition: name={}, code={}", definition.getName(), definition.getCode()); + + // 2. 启动工作流实例 + Map variables = new HashMap<>(); + variables.put("input", "test"); + log.info("Starting workflow with variables: {}", variables); + + WorkflowInstance instance = workflowEngine.startWorkflow( + definition.getCode(), + "TEST-" + System.currentTimeMillis(), + variables + ); + + // 3. 验证工作流实例状态 + assertNotNull(instance); + assertEquals(WorkflowStatusEnum.RUNNING, instance.getStatus()); + log.info("Workflow instance started: id={}, status={}", instance.getId(), instance.getStatus()); + + // 4. 验证节点执行状态 + List nodes = nodeInstanceRepository.findByWorkflowInstanceIdOrderByCreateTime(instance.getId()); + assertEquals(3, nodes.size()); + log.info("Found {} nodes in workflow instance", nodes.size()); + + // 验证开始节点 + NodeInstance startNode = nodes.get(0); + assertEquals(NodeTypeEnum.START, startNode.getNodeType()); + assertEquals(NodeStatusEnum.COMPLETED, startNode.getStatus()); + log.info("Start node validation passed: id={}, status={}", startNode.getId(), startNode.getStatus()); + + // 验证Shell节点 + NodeInstance shellNode = nodes.get(1); + assertEquals(NodeTypeEnum.TASK, shellNode.getNodeType()); + assertEquals(NodeStatusEnum.COMPLETED, shellNode.getStatus()); + log.info("Shell node validation passed: id={}, status={}", shellNode.getId(), shellNode.getStatus()); + + // 验证结束节点 + NodeInstance endNode = nodes.get(2); + assertEquals(NodeTypeEnum.END, endNode.getNodeType()); + assertEquals(NodeStatusEnum.COMPLETED, endNode.getStatus()); + log.info("End node validation passed: id={}, status={}", endNode.getId(), endNode.getStatus()); + + log.info("Simple workflow test completed successfully"); + } + + private WorkflowDefinitionDTO createSimpleWorkflow() { + WorkflowDefinitionDTO dto = new WorkflowDefinitionDTO(); + dto.setId(1L); + dto.setName("简单测试工作流"); + dto.setCode("SIMPLE_TEST_WORKFLOW"); + dto.setStatus(WorkflowStatusEnum.PUBLISHED); + + // 节点配置 + String nodeConfig = """ + { + "nodes": [ + { + "id": "start", + "name": "开始", + "type": "START" + }, + { + "id": "shell", + "name": "Shell任务", + "type": "TASK", + "config": { + "type": "SHELL", + "script": "echo 'Hello World'", + "workingDirectory": "/tmp", + "successExitCode": 0 + } + }, + { + "id": "end", + "name": "结束", + "type": "END" + } + ] + } + """; + dto.setNodeConfig(nodeConfig); + + // 流转配置 + String transitionConfig = """ + [ + { + "sourceNodeId": "start", + "targetNodeId": "shell", + "condition": null, + "priority": 1 + }, + { + "sourceNodeId": "shell", + "targetNodeId": "end", + "condition": null, + "priority": 1 + } + ] + """; + dto.setTransitionConfig(transitionConfig); + + return dto; + } + + private NodeInstance createNodeInstance(NodeTypeEnum type, NodeStatusEnum status) { + NodeInstance node = new NodeInstance(); + node.setId(System.nanoTime()); + node.setNodeType(type); + node.setStatus(status); + + // Create and set a workflow instance + WorkflowInstance instance = new WorkflowInstance(); + instance.setId(1L); + instance.setStatus(WorkflowStatusEnum.RUNNING); + node.setWorkflowInstance(instance); + + return node; + } +} \ No newline at end of file diff --git a/backend/src/test/java/com/qqchen/deploy/backend/workflow/service/WorkflowDefinitionServiceTest.java b/backend/src/test/java/com/qqchen/deploy/backend/workflow/service/WorkflowDefinitionServiceTest.java new file mode 100644 index 00000000..511c92aa --- /dev/null +++ b/backend/src/test/java/com/qqchen/deploy/backend/workflow/service/WorkflowDefinitionServiceTest.java @@ -0,0 +1,95 @@ +package com.qqchen.deploy.backend.workflow.service; + +import com.qqchen.deploy.backend.framework.exception.BusinessException; +import com.qqchen.deploy.backend.workflow.dto.WorkflowDefinitionDTO; +import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition; +import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnum; +import com.qqchen.deploy.backend.workflow.repository.IWorkflowDefinitionRepository; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import jakarta.annotation.Resource; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@SpringBootTest +class WorkflowDefinitionServiceTest { + + @Resource + private IWorkflowDefinitionService workflowDefinitionService; + + @MockBean + private IWorkflowDefinitionRepository workflowDefinitionRepository; + + @Test + void testCreateWorkflowDefinition() { + // 准备测试数据 + WorkflowDefinitionDTO dto = new WorkflowDefinitionDTO(); + dto.setName("测试工作流"); + dto.setCode("TEST_WORKFLOW"); + dto.setStatus(WorkflowStatusEnum.DRAFT); + + // Mock repository方法 + when(workflowDefinitionRepository.existsByCodeAndDeletedFalse(anyString())).thenReturn(false); + when(workflowDefinitionRepository.save(any())).thenAnswer(i -> i.getArgument(0)); + + // 执行测试 + WorkflowDefinitionDTO result = workflowDefinitionService.create(dto); + + // 验证结果 + assertNotNull(result); + assertEquals(dto.getName(), result.getName()); + assertEquals(dto.getCode(), result.getCode()); + assertEquals(WorkflowStatusEnum.DRAFT, result.getStatus()); + + // 验证repository方法调用 + verify(workflowDefinitionRepository).existsByCodeAndDeletedFalse(dto.getCode()); + verify(workflowDefinitionRepository).save(any()); + } + + @Test + void testPublishWorkflow() { + // 准备测试数据 + Long id = 1L; + WorkflowDefinition definition = new WorkflowDefinition(); + definition.setId(id); + definition.setStatus(WorkflowStatusEnum.DRAFT); + + // Mock repository方法 + when(workflowDefinitionRepository.findByIdWithLock(id)).thenReturn(Optional.of(definition)); + when(workflowDefinitionRepository.save(any())).thenAnswer(i -> i.getArgument(0)); + + // 执行测试 + workflowDefinitionService.publish(id); + + // 验证结果 + assertEquals(WorkflowStatusEnum.PUBLISHED, definition.getStatus()); + + // 验证repository方法调用 + verify(workflowDefinitionRepository).findByIdWithLock(id); + verify(workflowDefinitionRepository).save(definition); + } + + @Test + void testPublishWorkflowWithInvalidStatus() { + // 准备测试数据 + Long id = 1L; + WorkflowDefinition definition = new WorkflowDefinition(); + definition.setId(id); + definition.setStatus(WorkflowStatusEnum.PUBLISHED); + + // Mock repository方法 + when(workflowDefinitionRepository.findByIdWithLock(id)).thenReturn(Optional.of(definition)); + + // 执行测试并验证异常 + assertThrows(BusinessException.class, () -> workflowDefinitionService.publish(id)); + + // 验证repository方法调用 + verify(workflowDefinitionRepository).findByIdWithLock(id); + verify(workflowDefinitionRepository, never()).save(any()); + } +} \ No newline at end of file