diff --git a/backend/pom.xml b/backend/pom.xml
index df1607f2..28ff6804 100644
--- a/backend/pom.xml
+++ b/backend/pom.xml
@@ -70,6 +70,13 @@
mysql-connector-j
runtime
+
+
+
+ com.vladmihalcea
+ hibernate-types-60
+ 2.21.1
+
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/WorkflowDefinitionApiController.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/WorkflowDefinitionApiController.java
new file mode 100644
index 00000000..e26ac2e8
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/WorkflowDefinitionApiController.java
@@ -0,0 +1,277 @@
+package com.qqchen.deploy.backend.workflow.api;
+
+import com.qqchen.deploy.backend.framework.api.Response;
+import com.qqchen.deploy.backend.framework.controller.BaseController;
+import com.qqchen.deploy.backend.framework.enums.ResponseCode;
+import com.qqchen.deploy.backend.workflow.dto.WorkflowDefinitionDTO;
+import com.qqchen.deploy.backend.workflow.dto.WorkflowExecutionDTO;
+import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceDTO;
+import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceCreateDTO;
+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;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.flowable.engine.HistoryService;
+import org.flowable.engine.RuntimeService;
+import org.flowable.engine.history.HistoricActivityInstance;
+import org.flowable.engine.history.HistoricProcessInstance;
+import org.flowable.engine.runtime.ProcessInstance;
+import org.flowable.variable.api.history.HistoricVariableInstance;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+import java.util.stream.Collectors;
+
+/**
+ * 工作流定义控制器
+ */
+@Slf4j
+@RestController
+@RequestMapping("/api/v1/workflow/definition")
+@Tag(name = "工作流定义管理", description = "工作流定义管理相关接口")
+public class WorkflowDefinitionApiController extends BaseController {
+
+ @Resource
+ private IWorkflowDefinitionService workflowDefinitionService;
+
+ @Resource
+ private RuntimeService runtimeService;
+
+ @Resource
+ private HistoryService historyService;
+
+ @Operation(summary = "部署工作流")
+ @PostMapping("/deploy")
+ public Response deployWorkflow(@RequestBody WorkflowDefinitionDTO dto) {
+ return Response.success(workflowDefinitionService.deployWorkflow(dto));
+ }
+
+ @Operation(summary = "启动工作流实例")
+ @PostMapping("/start")
+ public Response startWorkflow(
+ @Parameter(description = "流程标识", required = true) @RequestParam String processKey,
+ @Parameter(description = "业务标识", required = true) @RequestParam String businessKey
+ ) {
+ Map variables = new HashMap<>();
+
+ try {
+ // 同步创建实例,立即返回实例ID
+ WorkflowInstanceCreateDTO result = workflowDefinitionService.startWorkflow(processKey, businessKey, variables);
+ return Response.success(result);
+ } catch (Exception e) {
+ log.error("Failed to start workflow", e);
+ return Response.error(ResponseCode.WORKFLOW_EXECUTION_ERROR);
+ }
+ }
+
+ @Operation(summary = "挂起工作流实例")
+ @PostMapping("/{processInstanceId}/suspend")
+ public Response suspendWorkflow(
+ @Parameter(description = "流程实例ID", required = true) @PathVariable String processInstanceId
+ ) {
+ workflowDefinitionService.suspendWorkflow(processInstanceId);
+ return Response.success();
+ }
+
+ @Operation(summary = "恢复工作流实例")
+ @PostMapping("/{processInstanceId}/resume")
+ public Response resumeWorkflow(
+ @Parameter(description = "流程实例ID", required = true) @PathVariable String processInstanceId
+ ) {
+ workflowDefinitionService.resumeWorkflow(processInstanceId);
+ return Response.success();
+ }
+
+ @Operation(summary = "查询工作流实例")
+ @GetMapping("/instance/{processInstanceId}")
+ public Response getWorkflowInstance(
+ @Parameter(description = "流程实例ID", required = true) @PathVariable String processInstanceId
+ ) {
+ HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
+ .processInstanceId(processInstanceId)
+ .includeProcessVariables()
+ .singleResult();
+
+ if (historicProcessInstance == null) {
+ return Response.error(ResponseCode.WORKFLOW_NOT_FOUND);
+ }
+
+ WorkflowInstanceDTO instanceDTO = new WorkflowInstanceDTO();
+ instanceDTO.setId(historicProcessInstance.getId());
+ instanceDTO.setProcessDefinitionId(historicProcessInstance.getProcessDefinitionId());
+ instanceDTO.setBusinessKey(historicProcessInstance.getBusinessKey());
+ instanceDTO.setStartTime(historicProcessInstance.getStartTime());
+ instanceDTO.setEndTime(historicProcessInstance.getEndTime());
+ instanceDTO.setDurationInMillis(historicProcessInstance.getDurationInMillis());
+ instanceDTO.setStartUserId(historicProcessInstance.getStartUserId());
+ instanceDTO.setStatus(historicProcessInstance.getEndTime() != null ? "COMPLETED" : "RUNNING");
+ instanceDTO.setVariables(historicProcessInstance.getProcessVariables());
+
+ // 查询活动节点历史
+ List activities = historyService.createHistoricActivityInstanceQuery()
+ .processInstanceId(processInstanceId)
+ .orderByHistoricActivityInstanceStartTime()
+ .asc()
+ .list();
+
+ List activityInstances = activities.stream()
+ .map(activity -> {
+ WorkflowInstanceDTO.ActivityInstance activityInstance = new WorkflowInstanceDTO.ActivityInstance();
+ activityInstance.setId(activity.getId());
+ activityInstance.setActivityId(activity.getActivityId());
+ activityInstance.setActivityName(activity.getActivityName());
+ activityInstance.setActivityType(activity.getActivityType());
+ activityInstance.setStartTime(activity.getStartTime());
+ activityInstance.setEndTime(activity.getEndTime());
+ activityInstance.setDurationInMillis(activity.getDurationInMillis());
+
+ // 如果是Shell任务,获取Shell相关变量
+ if ("serviceTask".equals(activity.getActivityType())) {
+ Map variables = historicProcessInstance.getProcessVariables();
+ activityInstance.setShellOutput((String) variables.get("shellOutput"));
+ activityInstance.setShellError((String) variables.get("shellError"));
+ activityInstance.setShellExitCode((Integer) variables.get("shellExitCode"));
+ }
+
+ return activityInstance;
+ })
+ .collect(Collectors.toList());
+
+ instanceDTO.setActivities(activityInstances);
+
+ return Response.success(instanceDTO);
+ }
+
+ @Operation(summary = "获取工作流执行状态")
+ @GetMapping("/instance/{processInstanceId}/execution")
+ public ResponseEntity getWorkflowExecution(@PathVariable String processInstanceId) {
+ // 获取历史活动实例
+ List activities = historyService.createHistoricActivityInstanceQuery()
+ .processInstanceId(processInstanceId)
+ .orderByHistoricActivityInstanceStartTime()
+ .asc()
+ .list();
+
+ // 获取流程实例
+ HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery()
+ .processInstanceId(processInstanceId)
+ .singleResult();
+
+ if (processInstance == null) {
+ return ResponseEntity.notFound().build();
+ }
+
+ // 获取所有流程变量
+ List allVariables = historyService.createHistoricVariableInstanceQuery()
+ .processInstanceId(processInstanceId)
+ .list();
+
+ // 构建变量映射
+ Map variableMap = new HashMap<>();
+ for (HistoricVariableInstance variable : allVariables) {
+ variableMap.put(variable.getVariableName(), variable.getValue());
+ }
+
+ // 构建执行状态DTO
+ WorkflowExecutionDTO executionDTO = new WorkflowExecutionDTO();
+ executionDTO.setProcessInstanceId(processInstance.getId());
+ executionDTO.setProcessDefinitionId(processInstance.getProcessDefinitionId());
+// executionDTO.setProcessDefinitionKey(processInstance.getProcessDefinitionKey());
+ executionDTO.setBusinessKey(processInstance.getBusinessKey());
+
+ // 设置流程状态
+ if (processInstance.getEndTime() != null) {
+ executionDTO.setStatus(processInstance.getDeleteReason() == null ? "COMPLETED" : "FAILED");
+ } else {
+ executionDTO.setStatus("RUNNING");
+ }
+
+ // 构建阶段列表
+ List stages = new ArrayList<>();
+ for (HistoricActivityInstance activity : activities) {
+ WorkflowExecutionDTO.StageDTO stage = new WorkflowExecutionDTO.StageDTO();
+ stage.setId(activity.getActivityId());
+ stage.setName(activity.getActivityName());
+ stage.setType(activity.getActivityType());
+// stage.setStartTime(activity.getStartTime() != null ? activity.getStartTime().toString() : null);
+// stage.setEndTime(activity.getEndTime() != null ? activity.getEndTime().toString() : null);
+
+ // 设置阶段状态
+ if (activity.getEndTime() == null) {
+ stage.setStatus("RUNNING");
+ } else {
+ stage.setStatus(activity.getDeleteReason() == null ? "COMPLETED" : "FAILED");
+ }
+
+ // 如果是Shell任务,获取输出
+ if ("serviceTask".equals(activity.getActivityType()) &&
+ activity.getActivityId().toLowerCase().contains("shell")) {
+
+ // 获取Shell任务的输出
+ String output = (String) variableMap.get("shellOutput");
+ String error = (String) variableMap.get("shellError");
+ Integer exitCode = (Integer) variableMap.get("shellExitCode");
+
+ stage.setOutput(output);
+ stage.setError(error);
+ stage.setExitCode(exitCode);
+ }
+
+ stages.add(stage);
+ }
+ executionDTO.setStages(stages);
+ executionDTO.setVariables(variableMap);
+
+ return ResponseEntity.ok(executionDTO);
+ }
+
+ @Operation(summary = "查询工作流实例列表")
+ @GetMapping("/instances")
+ public Response> listWorkflowInstances(
+ @Parameter(description = "流程标识") @RequestParam(required = false) String processKey,
+ @Parameter(description = "业务标识") @RequestParam(required = false) String businessKey
+ ) {
+ List historicProcessInstances = historyService.createHistoricProcessInstanceQuery()
+ .processDefinitionKey(processKey)
+ .processInstanceBusinessKey(businessKey)
+ .includeProcessVariables()
+ .orderByProcessInstanceStartTime()
+ .desc()
+ .list();
+
+ List instanceDTOs = historicProcessInstances.stream()
+ .map(historicProcessInstance -> {
+ WorkflowInstanceDTO instanceDTO = new WorkflowInstanceDTO();
+ instanceDTO.setId(historicProcessInstance.getId());
+ instanceDTO.setProcessDefinitionId(historicProcessInstance.getProcessDefinitionId());
+ instanceDTO.setBusinessKey(historicProcessInstance.getBusinessKey());
+ instanceDTO.setStartTime(historicProcessInstance.getStartTime());
+ instanceDTO.setEndTime(historicProcessInstance.getEndTime());
+ instanceDTO.setDurationInMillis(historicProcessInstance.getDurationInMillis());
+ instanceDTO.setStartUserId(historicProcessInstance.getStartUserId());
+ instanceDTO.setStatus(historicProcessInstance.getEndTime() != null ? "COMPLETED" : "RUNNING");
+ instanceDTO.setVariables(historicProcessInstance.getProcessVariables());
+ return instanceDTO;
+ })
+ .collect(Collectors.toList());
+
+ return Response.success(instanceDTOs);
+ }
+
+ @Override
+ protected void exportData(HttpServletResponse response, List data) {
+
+ }
+}
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/controller/WorkflowDesignController.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/WorkflowDesignApiController.java
similarity index 88%
rename from backend/src/main/java/com/qqchen/deploy/backend/workflow/controller/WorkflowDesignController.java
rename to backend/src/main/java/com/qqchen/deploy/backend/workflow/api/WorkflowDesignApiController.java
index b01dea59..5f3f6125 100644
--- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/controller/WorkflowDesignController.java
+++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/WorkflowDesignApiController.java
@@ -1,4 +1,4 @@
-package com.qqchen.deploy.backend.workflow.controller;
+package com.qqchen.deploy.backend.workflow.api;
import com.qqchen.deploy.backend.framework.api.Response;
import com.qqchen.deploy.backend.framework.controller.BaseController;
@@ -22,7 +22,7 @@ import java.util.List;
@RestController
@RequestMapping("/api/v1/workflow/design")
@Tag(name = "工作流设计", description = "工作流设计相关接口")
-public class WorkflowDesignController extends BaseController {
+public class WorkflowDesignApiController extends BaseController {
@Resource
private IWorkflowDesignService workflowDesignService;
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/controller/WorkflowDefinitionController.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/controller/WorkflowDefinitionController.java
deleted file mode 100644
index 245857e6..00000000
--- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/controller/WorkflowDefinitionController.java
+++ /dev/null
@@ -1,72 +0,0 @@
-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.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;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import jakarta.annotation.Resource;
-import jakarta.servlet.http.HttpServletResponse;
-import lombok.extern.slf4j.Slf4j;
-import org.flowable.engine.runtime.ProcessInstance;
-import org.springframework.web.bind.annotation.*;
-
-import java.util.List;
-import java.util.Map;
-
-/**
- * 工作流定义控制器
- */
-@Slf4j
-@RestController
-@RequestMapping("/api/v1/workflow/definition")
-@Tag(name = "工作流定义管理", description = "工作流定义管理相关接口")
-public class WorkflowDefinitionController extends BaseController {
-
- @Resource
- private IWorkflowDefinitionService workflowDefinitionService;
-
- @Operation(summary = "部署工作流")
- @PostMapping("/deploy")
- public Response deployWorkflow(@RequestBody WorkflowDefinitionDTO dto) {
- return Response.success(workflowDefinitionService.deployWorkflow(dto));
- }
-
- @Operation(summary = "启动工作流实例")
- @PostMapping("/start")
- public Response startWorkflow(
- @Parameter(description = "流程标识", required = true) @RequestParam String processKey,
- @Parameter(description = "业务标识", required = true) @RequestParam String businessKey,
- @Parameter(description = "流程变量") @RequestBody(required = false) Map variables
- ) {
- ProcessInstance instance = workflowDefinitionService.startWorkflow(processKey, businessKey, variables);
- return Response.success(instance.getId());
- }
-
- @Operation(summary = "挂起工作流实例")
- @PostMapping("/{processInstanceId}/suspend")
- public Response suspendWorkflow(
- @Parameter(description = "流程实例ID", required = true) @PathVariable String processInstanceId
- ) {
- workflowDefinitionService.suspendWorkflow(processInstanceId);
- return Response.success();
- }
-
- @Operation(summary = "恢复工作流实例")
- @PostMapping("/{processInstanceId}/resume")
- public Response resumeWorkflow(
- @Parameter(description = "流程实例ID", required = true) @PathVariable String processInstanceId
- ) {
- workflowDefinitionService.resumeWorkflow(processInstanceId);
- return Response.success();
- }
-
- @Override
- protected void exportData(HttpServletResponse response, List data) {
-
- }
-}
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/delegate/ShellTaskDelegate.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/delegate/ShellTaskDelegate.java
new file mode 100644
index 00000000..648d4a45
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/delegate/ShellTaskDelegate.java
@@ -0,0 +1,160 @@
+package com.qqchen.deploy.backend.workflow.delegate;
+
+import lombok.extern.slf4j.Slf4j;
+import org.flowable.engine.delegate.DelegateExecution;
+import org.flowable.engine.delegate.JavaDelegate;
+import org.flowable.common.engine.api.delegate.Expression;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Shell任务委托执行器
+ */
+@Slf4j
+@Component
+public class ShellTaskDelegate implements JavaDelegate {
+
+ private Expression script;
+ private Expression workDir;
+ private Expression env;
+
+ @Override
+ public void execute(DelegateExecution execution) {
+ // 从字段注入中获取值
+ String scriptValue = script != null ? script.getValue(execution).toString() : null;
+ String workDirValue = workDir != null ? workDir.getValue(execution).toString() : null;
+ @SuppressWarnings("unchecked")
+ Map envValue = env != null ? (Map) env.getValue(execution) : null;
+
+ // 如果流程变量中有值,优先使用流程变量
+ if (execution.hasVariable("script")) {
+ scriptValue = (String) execution.getVariable("script");
+ }
+ if (execution.hasVariable("workDir")) {
+ workDirValue = (String) execution.getVariable("workDir");
+ }
+ if (execution.hasVariable("env")) {
+ @SuppressWarnings("unchecked")
+ Map envFromVar = (Map) execution.getVariable("env");
+ envValue = envFromVar;
+ }
+
+ if (scriptValue == null) {
+ throw new RuntimeException("Script is required but not provided");
+ }
+
+ try {
+ // 创建进程构建器
+ ProcessBuilder processBuilder = new ProcessBuilder();
+ processBuilder.command("bash", "-c", scriptValue);
+
+ // 设置工作目录
+ if (StringUtils.hasText(workDirValue)) {
+ processBuilder.directory(new File(workDirValue));
+ }
+
+ // 设置环境变量
+ if (envValue != null) {
+ processBuilder.environment().putAll(envValue);
+ }
+
+ // 执行命令
+ log.info("Executing shell script: {}", scriptValue);
+ Process process = processBuilder.start();
+
+ // 使用BufferedReader实时读取输出
+ BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
+ BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
+
+ StringBuilder output = new StringBuilder();
+ StringBuilder error = new StringBuilder();
+
+ // 创建线程池处理输出
+ ExecutorService executorService = Executors.newFixedThreadPool(2);
+
+ // 处理标准输出
+ Future> outputFuture = executorService.submit(() -> {
+ String line;
+ try {
+ while ((line = reader.readLine()) != null) {
+ synchronized (output) {
+ output.append(line).append("\n");
+ }
+ log.info("Shell output: {}", line);
+ Thread.sleep(500); // 增加延迟,让输出更容易观察
+ }
+ } catch (Exception e) {
+ log.error("Error reading process output", e);
+ }
+ });
+
+ // 处理错误输出
+ Future> errorFuture = executorService.submit(() -> {
+ String line;
+ try {
+ while ((line = errorReader.readLine()) != null) {
+ synchronized (error) {
+ error.append(line).append("\n");
+ }
+ log.error("Shell error: {}", line);
+ }
+ } catch (IOException e) {
+ log.error("Error reading process error", e);
+ }
+ });
+
+ // 定期更新变量
+ while (!outputFuture.isDone() || !errorFuture.isDone()) {
+ synchronized (output) {
+ execution.setVariable("shellOutput", output.toString());
+ }
+ synchronized (error) {
+ execution.setVariable("shellError", error.toString());
+ }
+ Thread.sleep(1000); // 每秒更新一次变量
+ }
+
+ // 等待进程完成
+ int exitCode = process.waitFor();
+
+ // 等待输出处理完成
+ outputFuture.get(5, TimeUnit.SECONDS);
+ errorFuture.get(5, TimeUnit.SECONDS);
+
+ // 关闭线程池
+ executorService.shutdown();
+
+ // 设置最终结果
+ synchronized (output) {
+ execution.setVariable("shellOutput", output.toString());
+ }
+ synchronized (error) {
+ execution.setVariable("shellError", error.toString());
+ }
+ execution.setVariable("shellExitCode", exitCode);
+
+ if (exitCode != 0) {
+ log.error("Shell script execution failed with exit code: {}", exitCode);
+ log.error("Error output: {}", error);
+ throw new RuntimeException("Shell script execution failed with exit code: " + exitCode);
+ }
+
+ log.info("Shell script executed successfully");
+ log.debug("Script output: {}", output);
+
+ } catch (Exception e) {
+ log.error("Shell script execution failed", e);
+ throw new RuntimeException("Shell script execution failed: " + e.getMessage(), e);
+ }
+ }
+}
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
index d8c23df2..f730479a 100644
--- 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
@@ -24,7 +24,7 @@ public class WorkflowDefinitionDTO extends BaseDTO {
/**
* 流程版本
*/
- private Integer version;
+ private Integer flowVersion;
/**
* BPMN XML内容
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowDesignDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowDesignDTO.java
index 1ed09c9f..fc6be72b 100644
--- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowDesignDTO.java
+++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowDesignDTO.java
@@ -1,5 +1,6 @@
package com.qqchen.deploy.backend.workflow.dto;
+import com.fasterxml.jackson.databind.JsonNode;
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
import lombok.Data;
@@ -26,5 +27,5 @@ public class WorkflowDesignDTO extends BaseDTO {
/**
* 流程图数据
*/
- private String graphJson;
+ private JsonNode graphJson;
}
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowExecutionDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowExecutionDTO.java
new file mode 100644
index 00000000..91d91f42
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowExecutionDTO.java
@@ -0,0 +1,122 @@
+package com.qqchen.deploy.backend.workflow.dto;
+
+import lombok.Data;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+@Data
+public class WorkflowExecutionDTO {
+ /**
+ * 流程实例ID
+ */
+ private String processInstanceId;
+
+ /**
+ * 流程定义ID
+ */
+ private String processDefinitionId;
+
+ /**
+ * 流程定义名称
+ */
+ private String processDefinitionName;
+
+ /**
+ * 业务标识
+ */
+ private String businessKey;
+
+ /**
+ * 流程状态:RUNNING, SUSPENDED, COMPLETED
+ */
+ private String status;
+
+ /**
+ * 当前活动节点ID
+ */
+ private String currentActivityId;
+
+ /**
+ * 当前活动节点名称
+ */
+ private String currentActivityName;
+
+ /**
+ * 当前活动节点类型
+ */
+ private String currentActivityType;
+
+ /**
+ * 开始时间
+ */
+ private Date startTime;
+
+ /**
+ * 结束时间
+ */
+ private Date endTime;
+
+ /**
+ * 持续时间(毫秒)
+ */
+ private Long durationInMillis;
+
+ /**
+ * 流程变量
+ */
+ private Map variables;
+
+ /**
+ * 流程阶段列表
+ */
+ private List stages;
+
+ @Data
+ public static class StageDTO {
+ /**
+ * 节点ID
+ */
+ private String id;
+
+ /**
+ * 节点名称
+ */
+ private String name;
+
+ /**
+ * 节点类型:START_EVENT, USER_TASK, SERVICE_TASK, SHELL_TASK 等
+ */
+ private String type;
+
+ /**
+ * 节点状态:PENDING, RUNNING, COMPLETED, FAILED
+ */
+ private String status;
+
+ /**
+ * 开始时间
+ */
+ private Date startTime;
+
+ /**
+ * 结束时间
+ */
+ private Date endTime;
+
+ /**
+ * Shell任务的输出(如果是 SHELL_TASK 类型)
+ */
+ private String output;
+
+ /**
+ * Shell任务的错误信息(如果是 SHELL_TASK 类型)
+ */
+ private String error;
+
+ /**
+ * Shell任务的退出码(如果是 SHELL_TASK 类型)
+ */
+ private Integer exitCode;
+ }
+}
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowInstanceCreateDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowInstanceCreateDTO.java
new file mode 100644
index 00000000..e665ed97
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowInstanceCreateDTO.java
@@ -0,0 +1,20 @@
+package com.qqchen.deploy.backend.workflow.dto;
+
+import lombok.Data;
+
+@Data
+public class WorkflowInstanceCreateDTO {
+ private String processInstanceId;
+ private String processDefinitionKey;
+ private String businessKey;
+ private String status; // CREATING, RUNNING, ERROR
+
+ public static WorkflowInstanceCreateDTO of(String processInstanceId, String processDefinitionKey, String businessKey) {
+ WorkflowInstanceCreateDTO dto = new WorkflowInstanceCreateDTO();
+ dto.setProcessInstanceId(processInstanceId);
+ dto.setProcessDefinitionKey(processDefinitionKey);
+ dto.setBusinessKey(businessKey);
+ dto.setStatus("CREATING");
+ return dto;
+ }
+}
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowInstanceDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowInstanceDTO.java
new file mode 100644
index 00000000..a315b45b
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowInstanceDTO.java
@@ -0,0 +1,75 @@
+package com.qqchen.deploy.backend.workflow.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+@Data
+@Schema(description = "工作流实例信息")
+public class WorkflowInstanceDTO {
+ @Schema(description = "实例ID")
+ private String id;
+
+ @Schema(description = "流程定义ID")
+ private String processDefinitionId;
+
+ @Schema(description = "业务标识")
+ private String businessKey;
+
+ @Schema(description = "开始时间")
+ private Date startTime;
+
+ @Schema(description = "结束时间")
+ private Date endTime;
+
+ @Schema(description = "执行时长(毫秒)")
+ private Long durationInMillis;
+
+ @Schema(description = "启动用户ID")
+ private String startUserId;
+
+ @Schema(description = "状态(RUNNING/COMPLETED/FAILED)")
+ private String status;
+
+ @Data
+ @Schema(description = "活动节点信息")
+ public static class ActivityInstance {
+ @Schema(description = "活动ID")
+ private String id;
+
+ @Schema(description = "活动定义ID")
+ private String activityId;
+
+ @Schema(description = "活动名称")
+ private String activityName;
+
+ @Schema(description = "活动类型")
+ private String activityType;
+
+ @Schema(description = "开始时间")
+ private Date startTime;
+
+ @Schema(description = "结束时间")
+ private Date endTime;
+
+ @Schema(description = "执行时长(毫秒)")
+ private Long durationInMillis;
+
+ @Schema(description = "Shell输出")
+ private String shellOutput;
+
+ @Schema(description = "Shell错误")
+ private String shellError;
+
+ @Schema(description = "Shell退出码")
+ private Integer shellExitCode;
+ }
+
+ @Schema(description = "活动节点列表")
+ private List activities;
+
+ @Schema(description = "流程变量")
+ private Map variables;
+}
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 bfbd6343..7e776d03 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
@@ -1,10 +1,13 @@
package com.qqchen.deploy.backend.workflow.entity;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.vladmihalcea.hibernate.type.json.JsonType;
import com.qqchen.deploy.backend.framework.domain.Entity;
import jakarta.persistence.Column;
import jakarta.persistence.Table;
import lombok.Data;
import lombok.EqualsAndHashCode;
+import org.hibernate.annotations.Type;
/**
* 工作流定义实体
@@ -24,14 +27,14 @@ public class WorkflowDefinition extends Entity {
/**
* 流程标识
*/
- @Column(nullable = false)
+ @Column(name = "`key`", nullable = false)
private String key;
/**
* 流程版本
*/
- @Column(nullable = false)
- private Integer version;
+ @Column(name = "flow_version", nullable = false)
+ private Integer flowVersion;
/**
* BPMN XML内容
@@ -42,8 +45,9 @@ public class WorkflowDefinition extends Entity {
/**
* 流程图JSON数据
*/
- @Column(name = "graph_json", columnDefinition = "TEXT")
- private String graphJson;
+ @Type(JsonType.class)
+ @Column(name = "graph_json", columnDefinition = "json")
+ private JsonNode graphJson;
/**
* 流程描述
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 022b763c..31a5ed62 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
@@ -2,6 +2,8 @@ package com.qqchen.deploy.backend.workflow.service;
import com.qqchen.deploy.backend.framework.service.IBaseService;
import com.qqchen.deploy.backend.workflow.dto.WorkflowDefinitionDTO;
+import com.qqchen.deploy.backend.workflow.dto.WorkflowExecutionDTO;
+import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceCreateDTO;
import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition;
import org.flowable.engine.runtime.ProcessInstance;
@@ -28,7 +30,7 @@ public interface IWorkflowDefinitionService extends IBaseService variables);
+ WorkflowInstanceCreateDTO startWorkflow(String processKey, String businessKey, Map variables);
/**
* 挂起工作流实例
@@ -43,4 +45,12 @@ public interface IWorkflowDefinitionService extends IBaseService new RuntimeException("Workflow definition not found: " + dto.getId()));
+
+ // 转换为DTO
+ dto.setFlowVersion(definition.getFlowVersion());
+ return dto;
+
} catch (Exception e) {
log.error("Failed to deploy workflow: {}", dto.getName(), e);
throw new RuntimeException("Failed to deploy workflow", e);
@@ -50,19 +78,27 @@ public class WorkflowDefinitionServiceImpl extends BaseServiceImpl variables) {
+ @Transactional
+ public WorkflowInstanceCreateDTO startWorkflow(String processKey, String businessKey, Map variables) {
try {
- ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(
- processKey,
- businessKey,
- variables
- );
- log.info("Started workflow instance: {}", processInstance.getId());
- return processInstance;
+ // 1. 创建并异步启动流程实例
+ ProcessInstance processInstance = runtimeService.createProcessInstanceBuilder()
+ .processDefinitionKey(processKey)
+ .businessKey(businessKey)
+ .variables(variables)
+ .startAsync(); // 异步启动,会自动执行 shell 任务
+
+ // 2. 返回实例信息
+ WorkflowInstanceCreateDTO dto = new WorkflowInstanceCreateDTO();
+ dto.setProcessInstanceId(processInstance.getId());
+ dto.setProcessDefinitionKey(processKey);
+ dto.setBusinessKey(businessKey);
+ dto.setStatus("RUNNING"); // 因为实例已经在运行了
+
+ return dto;
} catch (Exception e) {
- log.error("Failed to start workflow: {}", processKey, e);
- throw new RuntimeException("Failed to start workflow", e);
+ log.error("Failed to create workflow: {}", processKey, e);
+ throw new RuntimeException("Failed to create workflow", e);
}
}
@@ -87,4 +123,132 @@ public class WorkflowDefinitionServiceImpl extends BaseServiceImpl activeActivityIds = runtimeService.getActiveActivityIds(processInstanceId);
+
+ // 3. 获取历史活动节点
+ List historicActivities = historyService.createHistoricActivityInstanceQuery()
+ .processInstanceId(processInstanceId)
+ .orderByHistoricActivityInstanceStartTime()
+ .asc()
+ .list();
+
+ // 4. 获取流程实例的历史信息
+ HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
+ .processInstanceId(processInstanceId)
+ .singleResult();
+
+ // 5. 获取流程定义模型
+ String processDefinitionId = processInstance.getProcessDefinitionId();
+ BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
+ Process process = bpmnModel.getMainProcess();
+ Collection flowElements = process.getFlowElements();
+
+ // 6. 创建返回对象
+ WorkflowExecutionDTO dto = new WorkflowExecutionDTO();
+ dto.setProcessInstanceId(processInstanceId);
+ dto.setProcessDefinitionId(processInstance.getProcessDefinitionId());
+ dto.setProcessDefinitionName(processInstance.getProcessDefinitionName());
+ dto.setBusinessKey(processInstance.getBusinessKey());
+ dto.setStatus(processInstance.isSuspended() ? "SUSPENDED" :
+ (historicProcessInstance != null && historicProcessInstance.getEndTime() != null) ? "COMPLETED" : "RUNNING");
+
+ // 7. 设置时间信息
+ if (historicProcessInstance != null) {
+ dto.setStartTime(historicProcessInstance.getStartTime());
+ dto.setEndTime(historicProcessInstance.getEndTime());
+ dto.setDurationInMillis(historicProcessInstance.getDurationInMillis());
+ }
+
+ // 8. 获取流程变量
+ Map variables = runtimeService.getVariables(processInstanceId);
+ dto.setVariables(variables);
+
+ // 9. 设置当前活动节点
+ if (!activeActivityIds.isEmpty()) {
+ String currentActivityId = activeActivityIds.get(0);
+ dto.setCurrentActivityId(currentActivityId);
+
+ // 从流程定义中找到当前节点的名称和类型
+ FlowElement currentElement = process.getFlowElement(currentActivityId);
+ if (currentElement != null) {
+ dto.setCurrentActivityName(currentElement.getName());
+ dto.setCurrentActivityType(currentElement.getClass().getSimpleName());
+ }
+ }
+
+ // 10. 创建历史活动实例的映射,用于快速查找
+ Map historicActivityMap = historicActivities.stream()
+ .collect(Collectors.toMap(
+ HistoricActivityInstance::getActivityId,
+ activity -> activity,
+ (existing, replacement) -> existing // 如果有重复,保留第一个
+ ));
+
+ // 11. 设置所有节点信息
+ List stages = new ArrayList<>();
+ for (FlowElement element : flowElements) {
+ // 只处理任务节点和事件节点
+ if (element instanceof Task || element instanceof Event) {
+ WorkflowExecutionDTO.StageDTO stage = new WorkflowExecutionDTO.StageDTO();
+ stage.setId(element.getId());
+ stage.setName(element.getName());
+ stage.setType(element.getClass().getSimpleName());
+
+ // 获取历史活动实例(如果存在)
+ HistoricActivityInstance historicActivity = historicActivityMap.get(element.getId());
+
+ if (historicActivity != null) {
+ // 节点已经执行过
+ stage.setStartTime(historicActivity.getStartTime());
+ stage.setEndTime(historicActivity.getEndTime());
+
+ if (historicActivity.getEndTime() != null) {
+ stage.setStatus("COMPLETED");
+ } else if (activeActivityIds.contains(element.getId())) {
+ stage.setStatus("RUNNING");
+ } else {
+ stage.setStatus("PENDING");
+ }
+
+ // 如果是Shell任务,获取输出
+ if (element instanceof ServiceTask && "shellTask".equals(((ServiceTask) element).getType())) {
+ String executionId = historicActivity.getExecutionId();
+ if (executionId != null) {
+ stage.setOutput((String) variables.get(executionId + "_shellOutput"));
+ stage.setError((String) variables.get(executionId + "_shellError"));
+ Integer exitCode = (Integer) variables.get(executionId + "_exitCode");
+ stage.setExitCode(exitCode != null ? exitCode : -1);
+ }
+ }
+ } else {
+ // 节点尚未执行
+ stage.setStatus("PENDING");
+ }
+
+ stages.add(stage);
+ }
+ }
+ dto.setStages(stages);
+
+ return dto;
+ } catch (Exception e) {
+ log.error("Failed to get workflow execution: {}", processInstanceId, e);
+ throw new RuntimeException("Failed to get workflow execution", e);
+ }
+ }
+
}
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowDesignServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowDesignServiceImpl.java
index 0e3b1a05..6fcab203 100644
--- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowDesignServiceImpl.java
+++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowDesignServiceImpl.java
@@ -2,6 +2,7 @@ package com.qqchen.deploy.backend.workflow.service.impl;
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl;
import com.qqchen.deploy.backend.workflow.dto.WorkflowDesignDTO;
+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.repository.IWorkflowDefinitionRepository;
@@ -40,8 +41,27 @@ public class WorkflowDesignServiceImpl extends BaseServiceImpl elementMap = new HashMap<>();
- JsonNode cells = jsonNode.get("cells");
+ JsonNode cells = x6Json.get("cells");
if (cells != null) {
for (JsonNode cell : cells) {
String shape = cell.get("shape").asText();
@@ -69,6 +67,29 @@ public class BpmnConverter {
ServiceTask serviceTask = new ServiceTask();
serviceTask.setId(id);
serviceTask.setName(label);
+
+ // 解析服务任务配置
+ JsonNode serviceTaskConfig = cell.path("data").path("serviceTask");
+ if (serviceTaskConfig != null) {
+ String type = serviceTaskConfig.path("type").asText();
+ String implementation = serviceTaskConfig.path("implementation").asText();
+
+ if ("shell".equals(type)) {
+ // 设置委托表达式,指向 ShellTaskDelegate
+ serviceTask.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION);
+ serviceTask.setImplementation("${shellTaskDelegate}");
+
+ JsonNode fields = serviceTaskConfig.path("fields");
+ if (fields != null) {
+ fields.fields().forEachRemaining(entry -> {
+ FieldExtension field = new FieldExtension();
+ field.setFieldName(entry.getKey());
+ field.setStringValue(entry.getValue().asText());
+ serviceTask.getFieldExtensions().add(field);
+ });
+ }
+ }
+ }
element = serviceTask;
break;
}
@@ -82,8 +103,8 @@ public class BpmnConverter {
// 解析连线
for (JsonNode cell : cells) {
if (cell.has("source") && cell.has("target")) {
- String sourceId = cell.get("source").get("cell").asText();
- String targetId = cell.get("target").get("cell").asText();
+ String sourceId = cell.get("source").asText();
+ String targetId = cell.get("target").asText();
String id = cell.get("id").asText();
FlowElement sourceElement = elementMap.get(sourceId);
@@ -104,9 +125,9 @@ public class BpmnConverter {
new BpmnAutoLayout(bpmnModel).execute();
// 转换为XML
- org.flowable.bpmn.converter.BpmnXMLConverter converter =
- new org.flowable.bpmn.converter.BpmnXMLConverter();
- byte[] xmlBytes = converter.convertToXML(bpmnModel);
- return new String(xmlBytes);
+ org.flowable.bpmn.converter.BpmnXMLConverter converter = new org.flowable.bpmn.converter.BpmnXMLConverter();
+ byte[] xml = converter.convertToXML(bpmnModel);
+
+ return new String(xml);
}
}
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 39784581..988ad5c3 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
@@ -396,7 +396,7 @@ CREATE TABLE workflow_definition (
`key` VARCHAR(50) NOT NULL COMMENT '流程标识',
flow_version INT NOT NULL COMMENT '流程版本',
bpmn_xml TEXT NOT NULL COMMENT 'BPMN XML内容',
- graph_json TEXT COMMENT 'x6 JSON内容',
+ graph_json JSON COMMENT 'x6 JSON内容',
description VARCHAR(255) NULL COMMENT '流程描述',
CONSTRAINT UK_workflow_definition_key_version UNIQUE (`key`, flow_version)
diff --git a/backend/src/test/resources/workflow-test.json b/backend/src/test/resources/workflow-test.json
new file mode 100644
index 00000000..78ee86c5
--- /dev/null
+++ b/backend/src/test/resources/workflow-test.json
@@ -0,0 +1,66 @@
+{
+ "cells": [
+ {
+ "id": "start",
+ "shape": "start",
+ "data": {
+ "label": "开始"
+ },
+ "position": {
+ "x": 100,
+ "y": 100
+ }
+ },
+ {
+ "id": "shell",
+ "shape": "serviceTask",
+ "data": {
+ "label": "Shell脚本",
+ "serviceTask": {
+ "type": "shell",
+ "implementation": "${shellTaskDelegate}",
+ "fields": {
+ "script": "for i in {1..20}; do echo \"Step $i: Starting...\"; sleep 1; echo \"Step $i: Running tests...\"; sleep 1; echo \"Step $i: Deploying...\"; sleep 1; echo \"Step $i: Completed\"; echo \"-------------------\"; done",
+ "workDir": "/tmp",
+ "env": {
+ "TEST_VAR": "test_value"
+ }
+ }
+ }
+ },
+ "position": {
+ "x": 300,
+ "y": 100
+ }
+ },
+ {
+ "id": "end",
+ "shape": "end",
+ "data": {
+ "label": "结束"
+ },
+ "position": {
+ "x": 500,
+ "y": 100
+ }
+ },
+ {
+ "id": "flow1",
+ "shape": "edge",
+ "source": "start",
+ "target": "shell",
+ "data": {
+ "label": "流转到Shell"
+ }
+ },
+ {
+ "id": "flow2",
+ "shape": "edge",
+ "source": "shell",
+ "target": "end",
+ "data": {
+ "label": "完成"
+ }
+ }
+ ]
+}