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