diff --git a/backend/src/main/java/com/qqchen/deploy/backend/framework/security/filter/JwtAuthenticationFilter.java b/backend/src/main/java/com/qqchen/deploy/backend/framework/security/filter/JwtAuthenticationFilter.java index 3856d551..94ba5843 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/framework/security/filter/JwtAuthenticationFilter.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/framework/security/filter/JwtAuthenticationFilter.java @@ -102,10 +102,18 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { } private String getJwtFromRequest(HttpServletRequest request) { + // 1. 先从Authorization header获取 String bearerToken = request.getHeader("Authorization"); - if (!StringUtils.hasText(bearerToken) || !bearerToken.startsWith("Bearer ")) { - return null; + if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { + return bearerToken.substring(7); } - return bearerToken.substring(7); + + // 2. 如果header中没有,则尝试从URL参数获取 + String token = request.getParameter("token"); + if (StringUtils.hasText(token)) { + return token; + } + + return null; } } \ No newline at end of file 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 index 9849018a..d2f459b0 100644 --- 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 @@ -4,11 +4,12 @@ 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.WorkflowDesignDTO; 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.enums.WorkflowInstanceStatus; +import com.qqchen.deploy.backend.workflow.enums.WorkflowInstanceStatusEnums; import com.qqchen.deploy.backend.workflow.query.WorkflowDefinitionQuery; import com.qqchen.deploy.backend.workflow.service.IWorkflowDefinitionService; import io.swagger.v3.oas.annotations.Operation; @@ -21,17 +22,13 @@ 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; @@ -53,20 +50,26 @@ public class WorkflowDefinitionApiController extends BaseController deployWorkflow(@RequestBody WorkflowDefinitionDTO dto) { - return Response.success(workflowDefinitionService.deployWorkflow(dto)); + @Operation(summary = "保存工作流设计") + @PostMapping("/design") + public Response saveWorkflowDesign(@RequestBody WorkflowDefinitionDTO dto) throws Exception { + return Response.success(workflowDefinitionService.saveWorkflowDesign(dto)); } +// @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 = "流程标识", 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); @@ -80,7 +83,7 @@ public class WorkflowDefinitionApiController extends BaseController suspendWorkflow( - @Parameter(description = "流程实例ID", required = true) @PathVariable String processInstanceId + @Parameter(description = "流程实例ID", required = true) @PathVariable String processInstanceId ) { workflowDefinitionService.suspendWorkflow(processInstanceId); return Response.success(); @@ -89,7 +92,7 @@ public class WorkflowDefinitionApiController extends BaseController resumeWorkflow( - @Parameter(description = "流程实例ID", required = true) @PathVariable String processInstanceId + @Parameter(description = "流程实例ID", required = true) @PathVariable String processInstanceId ) { workflowDefinitionService.resumeWorkflow(processInstanceId); return Response.success(); @@ -98,12 +101,12 @@ public class WorkflowDefinitionApiController extends BaseController getWorkflowInstance( - @Parameter(description = "流程实例ID", required = true) @PathVariable String processInstanceId + @Parameter(description = "流程实例ID", required = true) @PathVariable String processInstanceId ) { HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery() - .processInstanceId(processInstanceId) - .includeProcessVariables() - .singleResult(); + .processInstanceId(processInstanceId) + .includeProcessVariables() + .singleResult(); if (historicProcessInstance == null) { return Response.error(ResponseCode.WORKFLOW_NOT_FOUND); @@ -117,38 +120,38 @@ public class WorkflowDefinitionApiController extends BaseController activities = historyService.createHistoricActivityInstanceQuery() - .processInstanceId(processInstanceId) - .orderByHistoricActivityInstanceStartTime() - .asc() - .list(); + .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()); + .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")); - } + // 如果是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()); + return activityInstance; + }) + .collect(Collectors.toList()); instanceDTO.setActivities(activityInstances); @@ -158,116 +161,39 @@ public class WorkflowDefinitionApiController extends BaseController 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 ? WorkflowInstanceStatus.COMPLETED : WorkflowInstanceStatus.FAILED); -// } else { -// executionDTO.setStatus(WorkflowInstanceStatus.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(WorkflowInstanceStatus.RUNNING); -// } else { -// stage.setStatus(activity.getDeleteReason() == null ? WorkflowInstanceStatus.COMPLETED : WorkflowInstanceStatus.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(workflowDefinitionService.getWorkflowExecution(processInstanceId)); } @Operation(summary = "查询工作流实例列表") @GetMapping("/instances") public Response> listWorkflowInstances( - @Parameter(description = "流程标识") @RequestParam(required = false) String processKey, - @Parameter(description = "业务标识") @RequestParam(required = false) String businessKey + @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(); - + .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 ? WorkflowInstanceStatus.COMPLETED : WorkflowInstanceStatus.RUNNING); - instanceDTO.setVariables(historicProcessInstance.getProcessVariables()); - return instanceDTO; - }) - .collect(Collectors.toList()); - + .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 ? WorkflowInstanceStatusEnums.COMPLETED : WorkflowInstanceStatusEnums.RUNNING); + instanceDTO.setVariables(historicProcessInstance.getProcessVariables()); + return instanceDTO; + }) + .collect(Collectors.toList()); + return Response.success(instanceDTOs); } @@ -278,25 +204,25 @@ public class WorkflowDefinitionApiController extends BaseController result = new HashMap<>(); - + // 获取历史活动实例 HistoricActivityInstance activity = historyService.createHistoricActivityInstanceQuery() .processInstanceId(processInstanceId) .activityId(nodeId) .singleResult(); - + if (activity == null) { return Response.error(ResponseCode.WORKFLOW_NODE_NOT_FOUND); } // 获取执行ID String executionId = activity.getExecutionId(); - + // 获取节点相关的变量 List variables = historyService.createHistoricVariableInstanceQuery() .processInstanceId(processInstanceId) .list(); - + // 收集日志信息 variables.forEach(variable -> { String variableName = variable.getVariableName(); @@ -306,7 +232,7 @@ public class WorkflowDefinitionApiController extends BaseController disable( + @Parameter(description = "工作流定义ID", required = true) @PathVariable Long id + ) { + workflowDefinitionService.disable(id); + return Response.success(); + } + + @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) { diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/WorkflowDesignApiController.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/WorkflowDesignApiController.java deleted file mode 100644 index 5f3f6125..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/WorkflowDesignApiController.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.qqchen.deploy.backend.workflow.api; - -import com.qqchen.deploy.backend.framework.api.Response; -import com.qqchen.deploy.backend.framework.controller.BaseController; -import com.qqchen.deploy.backend.workflow.dto.WorkflowDesignDTO; -import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition; -import com.qqchen.deploy.backend.workflow.query.WorkflowDefinitionQuery; -import com.qqchen.deploy.backend.workflow.service.IWorkflowDesignService; -import io.swagger.v3.oas.annotations.Operation; -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/design") -@Tag(name = "工作流设计", description = "工作流设计相关接口") -public class WorkflowDesignApiController extends BaseController { - - @Resource - private IWorkflowDesignService workflowDesignService; - - @Operation(summary = "保存工作流设计") - @PostMapping("/save") - public Response saveWorkflowDesign(@RequestBody WorkflowDesignDTO dto) throws Exception { - return Response.success(workflowDesignService.saveWorkflowDesign(dto)); - } - - @Override - protected void exportData(HttpServletResponse response, List data) { - // TODO: 实现导出功能 - } -} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/controller/ShellLogController.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/WorkflowNodeInstanceApiController.java similarity index 74% rename from backend/src/main/java/com/qqchen/deploy/backend/workflow/controller/ShellLogController.java rename to backend/src/main/java/com/qqchen/deploy/backend/workflow/api/WorkflowNodeInstanceApiController.java index d9d4d21e..ea24e5fc 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/controller/ShellLogController.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/WorkflowNodeInstanceApiController.java @@ -1,4 +1,4 @@ -package com.qqchen.deploy.backend.workflow.controller; +package com.qqchen.deploy.backend.workflow.api; import com.qqchen.deploy.backend.workflow.event.ShellLogEvent; import lombok.extern.slf4j.Slf4j; @@ -12,13 +12,13 @@ import java.util.concurrent.ConcurrentHashMap; @Slf4j @RestController @CrossOrigin(origins = "*", allowedHeaders = "*") -@RequestMapping("/api/v1/shell-logs") -public class ShellLogController { +@RequestMapping("/api/v1/workflow/instance") +public class WorkflowNodeInstanceApiController { // 存储每个进程的SSE发射器 private static final Map EMITTERS = new ConcurrentHashMap<>(); - @GetMapping(value = "/{processInstanceId}", produces = "text/event-stream") + @GetMapping(value = "/log/{processInstanceId}", produces = "text/event-stream") public SseEmitter streamLogs(@PathVariable String processInstanceId) { SseEmitter emitter = new SseEmitter(Long.MAX_VALUE); @@ -54,18 +54,25 @@ public class ShellLogController { @EventListener public void handleShellLogEvent(ShellLogEvent event) { - SseEmitter emitter = EMITTERS.get(event.getProcessInstanceId()); + String processInstanceId = event.getProcessInstanceId(); + log.debug("Received log event for process: {}, type: {}, message: {}", + processInstanceId, event.getLogType(), event.getLogMessage()); + + SseEmitter emitter = EMITTERS.get(processInstanceId); if (emitter != null) { try { // 发送日志事件到客户端 emitter.send(SseEmitter.event() .name(event.getLogType().toString()) .data(event.getLogMessage())); + log.debug("Sent log event to client for process: {}", processInstanceId); } catch (IOException e) { - log.error("Error sending event for process: {}", event.getProcessInstanceId(), e); - EMITTERS.remove(event.getProcessInstanceId()); + log.error("Error sending event for process: {}", processInstanceId, e); + EMITTERS.remove(processInstanceId); emitter.completeWithError(e); } + } else { + log.warn("No emitter found for process: {}", processInstanceId); } } } \ No newline at end of file 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 index 966e79a3..d51cbb75 100644 --- 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 @@ -1,7 +1,7 @@ package com.qqchen.deploy.backend.workflow.delegate; import com.qqchen.deploy.backend.workflow.event.ShellLogEvent; -import com.qqchen.deploy.backend.workflow.enums.LogType; +import com.qqchen.deploy.backend.workflow.enums.NodeLogTypeEnums; import lombok.extern.slf4j.Slf4j; import org.flowable.engine.RuntimeService; import org.flowable.engine.ManagementService; @@ -41,7 +41,7 @@ public class ShellTaskDelegate implements JavaDelegate { private static final Map outputMap = new ConcurrentHashMap<>(); private static final Map errorMap = new ConcurrentHashMap<>(); - private void processInputStream(InputStream inputStream, String processInstanceId, LogType logType) { + private void processInputStream(InputStream inputStream, String processInstanceId, NodeLogTypeEnums logType) { try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { String line; while ((line = reader.readLine()) != null) { @@ -49,7 +49,7 @@ public class ShellTaskDelegate implements JavaDelegate { eventPublisher.publishEvent(new ShellLogEvent(processInstanceId, line, logType)); // 同时保存到StringBuilder中 - if (logType == LogType.STDOUT) { + if (logType == NodeLogTypeEnums.STDOUT) { StringBuilder output = outputMap.get(processInstanceId); synchronized (output) { output.append(line).append("\n"); @@ -95,10 +95,10 @@ public class ShellTaskDelegate implements JavaDelegate { } try { - // 初始化输出缓存 - String executionId = execution.getId(); - outputMap.put(executionId, new StringBuilder()); - errorMap.put(executionId, new StringBuilder()); + // 使用processInstanceId而不是executionId + String processInstanceId = execution.getProcessInstanceId(); + outputMap.put(processInstanceId, new StringBuilder()); + errorMap.put(processInstanceId, new StringBuilder()); // 创建进程构建器 ProcessBuilder processBuilder = new ProcessBuilder(); @@ -149,11 +149,11 @@ public class ShellTaskDelegate implements JavaDelegate { // 处理标准输出 Future outputFuture = executorService.submit(() -> - processInputStream(process.getInputStream(), executionId, LogType.STDOUT)); + processInputStream(process.getInputStream(), processInstanceId, NodeLogTypeEnums.STDOUT)); // 处理错误输出 Future errorFuture = executorService.submit(() -> - processInputStream(process.getErrorStream(), executionId, LogType.STDERR)); + processInputStream(process.getErrorStream(), processInstanceId, NodeLogTypeEnums.STDERR)); // 等待进程完成 int exitCode = process.waitFor(); @@ -166,16 +166,16 @@ public class ShellTaskDelegate implements JavaDelegate { executorService.shutdown(); // 设置最终结果 - StringBuilder finalOutput = outputMap.get(executionId); - StringBuilder finalError = errorMap.get(executionId); + StringBuilder finalOutput = outputMap.get(processInstanceId); + StringBuilder finalError = errorMap.get(processInstanceId); execution.setVariable("shellOutput", finalOutput.toString()); execution.setVariable("shellError", finalError.toString()); execution.setVariable("shellExitCode", exitCode); // 清理缓存 - outputMap.remove(executionId); - errorMap.remove(executionId); + outputMap.remove(processInstanceId); + errorMap.remove(processInstanceId); if (exitCode != 0) { log.error("Shell script execution failed with exit code: {}", exitCode); 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 f730479a..242ba3d5 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 @@ -1,6 +1,8 @@ package com.qqchen.deploy.backend.workflow.dto; +import com.fasterxml.jackson.databind.JsonNode; import com.qqchen.deploy.backend.framework.dto.BaseDTO; +import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnums; import lombok.Data; import lombok.EqualsAndHashCode; @@ -30,6 +32,10 @@ public class WorkflowDefinitionDTO extends BaseDTO { * BPMN XML内容 */ private String bpmnXml; + + private JsonNode graphJson; + + private WorkflowStatusEnums status; /** * 流程描述 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 fc6be72b..7d3efd37 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 @@ -2,6 +2,7 @@ package com.qqchen.deploy.backend.workflow.dto; import com.fasterxml.jackson.databind.JsonNode; import com.qqchen.deploy.backend.framework.dto.BaseDTO; +import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnums; import lombok.Data; /** @@ -28,4 +29,6 @@ public class WorkflowDesignDTO extends BaseDTO { * 流程图数据 */ private JsonNode graphJson; + + private WorkflowStatusEnums status; } 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 index 6d6deb81..18f9b8a5 100644 --- 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 @@ -1,6 +1,6 @@ package com.qqchen.deploy.backend.workflow.dto; -import com.qqchen.deploy.backend.workflow.enums.WorkflowInstanceStatus; +import com.qqchen.deploy.backend.workflow.enums.WorkflowInstanceStatusEnums; import lombok.Data; import java.util.Date; import java.util.List; @@ -31,7 +31,7 @@ public class WorkflowExecutionDTO { /** * 流程状态 */ - private WorkflowInstanceStatus status; + private WorkflowInstanceStatusEnums status; /** * 当前活动节点ID @@ -93,7 +93,7 @@ public class WorkflowExecutionDTO { /** * 节点状态 */ - private WorkflowInstanceStatus status; + private WorkflowInstanceStatusEnums status; /** * 开始时间 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 index a6dca641..4db5846e 100644 --- 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 @@ -1,6 +1,6 @@ package com.qqchen.deploy.backend.workflow.dto; -import com.qqchen.deploy.backend.workflow.enums.WorkflowInstanceStatus; +import com.qqchen.deploy.backend.workflow.enums.WorkflowInstanceStatusEnums; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.util.Date; @@ -32,7 +32,7 @@ public class WorkflowInstanceDTO { private String startUserId; @Schema(description = "状态(RUNNING/COMPLETED/FAILED)") - private WorkflowInstanceStatus status; + private WorkflowInstanceStatusEnums status; @Data @Schema(description = "活动节点信息") 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 7e776d03..3dd6270e 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,9 +1,12 @@ package com.qqchen.deploy.backend.workflow.entity; import com.fasterxml.jackson.databind.JsonNode; +import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnums; import com.vladmihalcea.hibernate.type.json.JsonType; 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; @@ -48,6 +51,10 @@ public class WorkflowDefinition extends Entity { @Type(JsonType.class) @Column(name = "graph_json", columnDefinition = "json") private JsonNode graphJson; + + @Column(nullable = false) + @Enumerated(EnumType.STRING) + private WorkflowStatusEnums status; /** * 流程描述 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 d251d591..21726e52 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 @@ -1,7 +1,7 @@ package com.qqchen.deploy.backend.workflow.entity; import com.qqchen.deploy.backend.framework.domain.Entity; -import com.qqchen.deploy.backend.workflow.enums.WorkflowInstanceStatus; +import com.qqchen.deploy.backend.workflow.enums.WorkflowInstanceStatusEnums; import jakarta.persistence.Column; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; @@ -43,7 +43,7 @@ public class WorkflowInstance extends Entity { */ @Column(nullable = false) @Enumerated(EnumType.STRING) - private WorkflowInstanceStatus status; + private WorkflowInstanceStatusEnums status; /** * 流程变量(JSON) diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/BpmnNodeType.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/BpmnNodeType.java new file mode 100644 index 00000000..54e8dfb2 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/BpmnNodeType.java @@ -0,0 +1,33 @@ +package com.qqchen.deploy.backend.workflow.enums; + +import lombok.Getter; + +/** + * BPMN节点类型枚举 + */ +@Getter +public enum BpmnNodeType { + START("start", "startEvent"), + END("end", "endEvent"), + USER_TASK("userTask", "userTask"), + SERVICE_TASK("serviceTask", "serviceTask"), + SHELL_TASK("shellTask", "serviceTask"), + EXCLUSIVE_GATEWAY("exclusiveGateway", "exclusiveGateway"); + + private final String shape; + private final String bpmnType; + + BpmnNodeType(String shape, String bpmnType) { + this.shape = shape; + this.bpmnType = bpmnType; + } + + public static BpmnNodeType fromShape(String shape) { + for (BpmnNodeType type : values()) { + if (type.getShape().equals(shape)) { + return type; + } + } + return null; + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/LogType.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/NodeLogTypeEnums.java similarity index 80% rename from backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/LogType.java rename to backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/NodeLogTypeEnums.java index 1c755db4..42ade217 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/LogType.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/NodeLogTypeEnums.java @@ -3,7 +3,7 @@ package com.qqchen.deploy.backend.workflow.enums; /** * 日志类型枚举 */ -public enum LogType { +public enum NodeLogTypeEnums { STDOUT, // 标准输出 STDERR // 错误输出 } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/WorkflowInstanceStatus.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/WorkflowInstanceStatusEnums.java similarity index 89% rename from backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/WorkflowInstanceStatus.java rename to backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/WorkflowInstanceStatusEnums.java index c6fd61c9..064e9f94 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/WorkflowInstanceStatus.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/WorkflowInstanceStatusEnums.java @@ -8,7 +8,7 @@ import lombok.Getter; * NOT_STARTED -> CREATED -> RUNNING -> (SUSPENDED) -> COMPLETED/TERMINATED/FAILED */ @Getter -public enum WorkflowInstanceStatus { +public enum WorkflowInstanceStatusEnums { /** * 未开始:节点尚未开始执行 @@ -55,7 +55,7 @@ public enum WorkflowInstanceStatus { */ private final String description; - WorkflowInstanceStatus(String code, String description) { + WorkflowInstanceStatusEnums(String code, String description) { this.code = code; this.description = description; } @@ -66,8 +66,8 @@ public enum WorkflowInstanceStatus { * @param code 状态编码 * @return 对应的枚举实例 */ - public static WorkflowInstanceStatus fromCode(String code) { - for (WorkflowInstanceStatus status : WorkflowInstanceStatus.values()) { + public static WorkflowInstanceStatusEnums fromCode(String code) { + for (WorkflowInstanceStatusEnums status : WorkflowInstanceStatusEnums.values()) { if (status.getCode().equals(code)) { return status; } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/WorkflowStatusEnums.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/WorkflowStatusEnums.java new file mode 100644 index 00000000..fbdff7f7 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/WorkflowStatusEnums.java @@ -0,0 +1,21 @@ +package com.qqchen.deploy.backend.workflow.enums; + +import lombok.Getter; + +@Getter +public enum WorkflowStatusEnums { + + DRAFT("DRAFT","草稿"), + PUBLISHED("PUBLISHED","已发布"), + DISABLED("DISABLED","已禁用"); + + private final String code; + + private final String description; + + WorkflowStatusEnums(String code, String description) { + this.code = code; + this.description = description; + } + +} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/event/ShellLogEvent.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/event/ShellLogEvent.java index 69d86be1..77519a95 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/event/ShellLogEvent.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/event/ShellLogEvent.java @@ -1,6 +1,6 @@ package com.qqchen.deploy.backend.workflow.event; -import com.qqchen.deploy.backend.workflow.enums.LogType; +import com.qqchen.deploy.backend.workflow.enums.NodeLogTypeEnums; import lombok.AllArgsConstructor; import lombok.Getter; @@ -12,5 +12,5 @@ import lombok.Getter; public class ShellLogEvent { private String processInstanceId; private String logMessage; - private LogType logType; + private NodeLogTypeEnums logType; } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/handler/BpmnNodeHandler.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/handler/BpmnNodeHandler.java new file mode 100644 index 00000000..6b215ca8 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/handler/BpmnNodeHandler.java @@ -0,0 +1,44 @@ +package com.qqchen.deploy.backend.workflow.handler; + +import com.fasterxml.jackson.databind.JsonNode; +import com.qqchen.deploy.backend.workflow.enums.BpmnNodeType; +import com.qqchen.deploy.backend.workflow.model.BpmnNodeConfig; +import org.flowable.bpmn.model.FlowElement; + +/** + * BPMN节点处理器接口 + * + * @param 流程元素类型 + */ +public interface BpmnNodeHandler { + /** + * 处理节点数据 + * + * @param nodeData JSON节点数据 + * @param element 流程元素 + * @param config 节点配置 + */ + void handle(JsonNode nodeData, T element, BpmnNodeConfig config); + + /** + * 创建流程元素 + * + * @param config 节点配置 + * @return 流程元素 + */ + T createElement(BpmnNodeConfig config); + + /** + * 获取处理器类型 + * + * @return 节点类型 + */ + BpmnNodeType getType(); + + /** + * 获取处理的元素类型 + * + * @return 元素类型Class + */ + Class getElementType(); +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/handler/impl/ShellTaskHandler.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/handler/impl/ShellTaskHandler.java new file mode 100644 index 00000000..7d6e8452 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/handler/impl/ShellTaskHandler.java @@ -0,0 +1,68 @@ +package com.qqchen.deploy.backend.workflow.handler.impl; + +import com.fasterxml.jackson.databind.JsonNode; +import com.qqchen.deploy.backend.workflow.enums.BpmnNodeType; +import com.qqchen.deploy.backend.workflow.handler.BpmnNodeHandler; +import com.qqchen.deploy.backend.workflow.model.BpmnNodeConfig; +import lombok.extern.slf4j.Slf4j; +import org.flowable.bpmn.model.FieldExtension; +import org.flowable.bpmn.model.ServiceTask; +import org.springframework.stereotype.Component; + +import java.util.Map; + +/** + * Shell任务处理器 + */ +@Slf4j +@Component +public class ShellTaskHandler implements BpmnNodeHandler { + + @Override + public void handle(JsonNode nodeData, ServiceTask element, BpmnNodeConfig config) { + element.setImplementationType("delegateExpression"); + element.setImplementation("${shellTaskDelegate}"); + element.setAsynchronous(true); // Shell任务默认异步执行 + + // 从配置中获取字段 + Map properties = config.getProperties(); + properties.forEach((key, value) -> { + if (value != null) { + addFieldExtension(element, key, value.toString()); + } + }); + + // 处理扩展字段 + Map extensions = config.getExtensions(); + extensions.forEach((key, value) -> { + if (value != null) { + addFieldExtension(element, key, value); + } + }); + } + + @Override + public ServiceTask createElement(BpmnNodeConfig config) { + ServiceTask task = new ServiceTask(); + task.setId(config.getId()); + task.setName(config.getName()); + return task; + } + + @Override + public BpmnNodeType getType() { + return BpmnNodeType.SHELL_TASK; + } + + @Override + public Class getElementType() { + return ServiceTask.class; + } + + private void addFieldExtension(ServiceTask serviceTask, String name, String value) { + FieldExtension field = new FieldExtension(); + field.setFieldName(name); + field.setStringValue(value); + serviceTask.getFieldExtensions().add(field); + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/model/BpmnNodeConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/model/BpmnNodeConfig.java new file mode 100644 index 00000000..418f04cc --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/model/BpmnNodeConfig.java @@ -0,0 +1,38 @@ +package com.qqchen.deploy.backend.workflow.model; + +import com.qqchen.deploy.backend.workflow.enums.BpmnNodeType; +import lombok.Data; + +import java.util.HashMap; +import java.util.Map; + +/** + * BPMN节点配置 + */ +@Data +public class BpmnNodeConfig { + /** + * 节点ID + */ + private String id; + + /** + * 节点名称 + */ + private String name; + + /** + * 节点类型 + */ + private BpmnNodeType type; + + /** + * 节点属性 + */ + private Map properties = new HashMap<>(); + + /** + * 扩展字段 + */ + private Map extensions = new HashMap<>(); +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/parser/BpmnNodeConfigParser.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/parser/BpmnNodeConfigParser.java new file mode 100644 index 00000000..c918e148 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/parser/BpmnNodeConfigParser.java @@ -0,0 +1,76 @@ +package com.qqchen.deploy.backend.workflow.parser; + +import com.fasterxml.jackson.databind.JsonNode; +import com.qqchen.deploy.backend.workflow.enums.BpmnNodeType; +import com.qqchen.deploy.backend.workflow.model.BpmnNodeConfig; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; + +/** + * BPMN节点配置解析器 + */ +@Component +public class BpmnNodeConfigParser { + + /** + * 解析节点配置 + * + * @param node JSON节点数据 + * @return 节点配置 + */ + public BpmnNodeConfig parse(JsonNode node) { + BpmnNodeConfig config = new BpmnNodeConfig(); + config.setId(node.path("id").asText()); + config.setName(node.path("data").path("label").asText()); + config.setType(BpmnNodeType.fromShape(node.path("shape").asText())); + + // 解析属性 + JsonNode data = node.path("data"); + + // 处理通用属性 + Map properties = new HashMap<>(); + if (data.has("properties")) { + data.path("properties").fields() + .forEachRemaining(entry -> properties.put( + entry.getKey(), + parseValue(entry.getValue()) + )); + } + + // 处理特定任务属性 + if (data.has("serviceTask") && data.path("serviceTask").has("fields")) { + JsonNode fields = data.path("serviceTask").path("fields"); + fields.fields().forEachRemaining(entry -> + properties.put(entry.getKey(), parseValue(entry.getValue())) + ); + } + + config.setProperties(properties); + + // 解析扩展字段 + Map extensions = new HashMap<>(); + if (data.has("extensions")) { + data.path("extensions").fields() + .forEachRemaining(entry -> extensions.put( + entry.getKey(), + entry.getValue().asText() + )); + } + config.setExtensions(extensions); + + return config; + } + + /** + * 解析JSON值 + */ + private Object parseValue(JsonNode value) { + if (value.isTextual()) return value.asText(); + if (value.isNumber()) return value.numberValue(); + if (value.isBoolean()) return value.asBoolean(); + if (value.isObject() || value.isArray()) return value.toString(); + return null; + } +} \ 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 31a5ed62..9718e4bb 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,10 +2,12 @@ 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.WorkflowDesignDTO; 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; +import org.springframework.transaction.annotation.Transactional; import java.util.Map; @@ -13,7 +15,9 @@ import java.util.Map; * 工作流定义服务接口 */ public interface IWorkflowDefinitionService extends IBaseService { - + + WorkflowDesignDTO saveWorkflowDesign(WorkflowDefinitionDTO dto) throws Exception; + /** * 部署工作流 * @@ -53,4 +57,8 @@ public interface IWorkflowDefinitionService extends IBaseService { - - /** - * 保存工作流设计 - */ - WorkflowDesignDTO saveWorkflowDesign(WorkflowDesignDTO dto) throws Exception; -} 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 d221f923..a7ca19c7 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 @@ -2,7 +2,7 @@ package com.qqchen.deploy.backend.workflow.service; import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceDTO; import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; -import com.qqchen.deploy.backend.workflow.enums.WorkflowInstanceStatus; +import com.qqchen.deploy.backend.workflow.enums.WorkflowInstanceStatusEnums; import org.flowable.engine.runtime.ProcessInstance; import java.util.List; @@ -26,7 +26,7 @@ public interface IWorkflowInstanceService { * @param status 新状态 * @return 更新后的工作流实例 */ - WorkflowInstance updateInstanceStatus(String processInstanceId, WorkflowInstanceStatus status); + WorkflowInstance updateInstanceStatus(String processInstanceId, WorkflowInstanceStatusEnums status); /** * 获取工作流实例详情 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 105af45e..052e051d 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 @@ -2,16 +2,18 @@ package com.qqchen.deploy.backend.workflow.service.impl; import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl; import com.qqchen.deploy.backend.workflow.dto.WorkflowDefinitionDTO; +import com.qqchen.deploy.backend.workflow.dto.WorkflowDesignDTO; 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 com.qqchen.deploy.backend.workflow.enums.WorkflowInstanceStatus; +import com.qqchen.deploy.backend.workflow.enums.WorkflowInstanceStatusEnums; +import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnums; import com.qqchen.deploy.backend.workflow.repository.IWorkflowDefinitionRepository; import com.qqchen.deploy.backend.workflow.service.IWorkflowDefinitionService; +import com.qqchen.deploy.backend.workflow.util.BpmnConverter; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.SequenceFlow; -import org.flowable.bpmn.model.StartEvent; import org.flowable.engine.HistoryService; import org.flowable.engine.ManagementService; import org.flowable.engine.RepositoryService; @@ -19,25 +21,19 @@ import org.flowable.engine.RuntimeService; import org.flowable.engine.TaskService; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricProcessInstance; -import org.flowable.engine.repository.Deployment; import org.flowable.bpmn.model.BpmnModel; +import org.flowable.engine.repository.Deployment; import org.flowable.engine.runtime.Execution; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.bpmn.model.Process; import org.flowable.bpmn.model.FlowElement; -import org.flowable.bpmn.model.ServiceTask; -import org.flowable.job.api.Job; -import org.flowable.task.api.Task; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -47,8 +43,7 @@ import java.util.stream.Collectors; */ @Slf4j @Service -public class WorkflowDefinitionServiceImpl extends BaseServiceImpl - implements IWorkflowDefinitionService { +public class WorkflowDefinitionServiceImpl extends BaseServiceImpl implements IWorkflowDefinitionService { @Resource private RepositoryService repositoryService; @@ -68,6 +63,47 @@ public class WorkflowDefinitionServiceImpl extends BaseServiceImpl new RuntimeException("Workflow definition not found: " + id)); + + // 2. 查询部署 + List deployments = repositoryService.createDeploymentQuery() + .processDefinitionKey(definition.getKey()) + .list(); + + if (deployments.isEmpty()) { + // 如果没有部署,直接更新状态 + definition.setStatus(WorkflowStatusEnums.DISABLED); + workflowDefinitionRepository.save(definition); + return; + } + + // 3. 查询历史实例 + long historyCount = historyService.createHistoricProcessInstanceQuery() + .processDefinitionKey(definition.getKey()) + .count(); + + if (historyCount == 0) { + // 如果从未运行过,直接删除部署 + for (Deployment deployment : deployments) { + repositoryService.deleteDeployment(deployment.getId(), true); + } + } else { + // 如果有运行历史,则挂起流程定义 + repositoryService.suspendProcessDefinitionByKey(definition.getKey()); + } + + // 4. 更新我们的流程定义状态 + definition.setStatus(WorkflowStatusEnums.DISABLED); + workflowDefinitionRepository.save(definition); + + log.info("Successfully disabled workflow definition: {}, key: {}, hasHistory: {}", + id, definition.getKey(), historyCount > 0); + + } catch (Exception e) { + log.error("Failed to disable workflow definition: {}", id, e); + throw new RuntimeException("Failed to disable workflow definition", e); + } + } + + @Override + public void enable(Long id) { + WorkflowDefinition definition = workflowDefinitionRepository.findById(id) + .orElseThrow(() -> new RuntimeException("Workflow definition not found: " + id)); + repositoryService.createDeployment().addString(definition.getKey() + ".bpmn20.xml", definition.getBpmnXml()) + .key(definition.getKey()).name(definition.getName()).deploy(); + } + } 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 deleted file mode 100644 index 6fcab203..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowDesignServiceImpl.java +++ /dev/null @@ -1,67 +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.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; -import com.qqchen.deploy.backend.workflow.service.IWorkflowDefinitionService; -import com.qqchen.deploy.backend.workflow.service.IWorkflowDesignService; -import com.qqchen.deploy.backend.workflow.util.BpmnConverter; -import jakarta.annotation.Resource; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -/** - * 工作流设计服务实现 - */ -@Service -public class WorkflowDesignServiceImpl extends BaseServiceImpl implements IWorkflowDesignService { - - @Resource - private IWorkflowDefinitionService workflowDefinitionService; - - @Resource - private IWorkflowDefinitionRepository workflowDefinitionRepository; - - @Resource - private BpmnConverter bpmnConverter; - - @Override - @Transactional(rollbackFor = Exception.class) - public WorkflowDesignDTO saveWorkflowDesign(WorkflowDesignDTO dto) throws Exception { - // 转换图形JSON为BPMN XML - String bpmnXml = bpmnConverter.convertToBpmnXml(dto.getGraphJson(), dto.getKey()); - - // 创建工作流定义 - WorkflowDefinition definition = new WorkflowDefinition(); - definition.setName(dto.getName()); - definition.setKey(dto.getKey()); - definition.setDescription(dto.getDescription()); - definition.setGraphJson(dto.getGraphJson()); - definition.setBpmnXml(bpmnXml); - definition.setFlowVersion(1); // 设置初始版本为1 - - // 保存工作流定义 - definition = workflowDefinitionRepository.save(definition); - - // 部署工作流 - WorkflowDefinitionDTO deployDto = new WorkflowDefinitionDTO(); - deployDto.setId(definition.getId()); - deployDto.setName(definition.getName()); - deployDto.setKey(definition.getKey()); - deployDto.setBpmnXml(definition.getBpmnXml()); - deployDto.setDescription(definition.getDescription()); - workflowDefinitionService.deployWorkflow(deployDto); - - // 手动转换为 WorkflowDesignDTO - WorkflowDesignDTO result = new WorkflowDesignDTO(); - result.setId(definition.getId()); - result.setName(definition.getName()); - result.setKey(definition.getKey()); - result.setDescription(definition.getDescription()); - result.setGraphJson(definition.getGraphJson()); - return result; - } -} 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 02a13da5..9099f9b3 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 @@ -4,7 +4,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceDTO; import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; -import com.qqchen.deploy.backend.workflow.enums.WorkflowInstanceStatus; +import com.qqchen.deploy.backend.workflow.enums.WorkflowInstanceStatusEnums; import com.qqchen.deploy.backend.workflow.repository.WorkflowInstanceRepository; import com.qqchen.deploy.backend.workflow.service.IWorkflowInstanceService; import lombok.RequiredArgsConstructor; @@ -37,7 +37,7 @@ public class WorkflowInstanceServiceImpl implements IWorkflowInstanceService { instance.setProcessInstanceId(processInstance.getId()); instance.setProcessDefinitionId(Long.valueOf(processInstance.getProcessDefinitionId().split(":")[0])); instance.setBusinessKey(processInstance.getBusinessKey()); - instance.setStatus(WorkflowInstanceStatus.RUNNING); + instance.setStatus(WorkflowInstanceStatusEnums.RUNNING); instance.setStartTime(LocalDateTime.now()); try { @@ -52,12 +52,12 @@ public class WorkflowInstanceServiceImpl implements IWorkflowInstanceService { @Override @Transactional - public WorkflowInstance updateInstanceStatus(String processInstanceId, WorkflowInstanceStatus status) { + public WorkflowInstance updateInstanceStatus(String processInstanceId, WorkflowInstanceStatusEnums status) { WorkflowInstance instance = workflowInstanceRepository.findByProcessInstanceId(processInstanceId) .orElseThrow(() -> new RuntimeException("Workflow instance not found: " + processInstanceId)); instance.setStatus(status); - if (status == WorkflowInstanceStatus.COMPLETED || status == WorkflowInstanceStatus.FAILED) { + if (status == WorkflowInstanceStatusEnums.COMPLETED || status == WorkflowInstanceStatusEnums.FAILED) { instance.setEndTime(LocalDateTime.now()); } @@ -130,7 +130,7 @@ public class WorkflowInstanceServiceImpl implements IWorkflowInstanceService { log.error("Failed to serialize variables", e); } - instance.setStatus(WorkflowInstanceStatus.COMPLETED); + instance.setStatus(WorkflowInstanceStatusEnums.COMPLETED); instance.setEndTime(LocalDateTime.now()); workflowInstanceRepository.save(instance); } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/util/BpmnConverter.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/util/BpmnConverter.java index 9629c9d9..5fc8f888 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/util/BpmnConverter.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/util/BpmnConverter.java @@ -2,124 +2,108 @@ package com.qqchen.deploy.backend.workflow.util; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.qqchen.deploy.backend.workflow.enums.BpmnNodeType; +import com.qqchen.deploy.backend.workflow.handler.BpmnNodeHandler; +import com.qqchen.deploy.backend.workflow.model.BpmnNodeConfig; +import com.qqchen.deploy.backend.workflow.parser.BpmnNodeConfigParser; +import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.BpmnAutoLayout; +import org.flowable.bpmn.converter.BpmnXMLConverter; import org.flowable.bpmn.model.*; import org.flowable.bpmn.model.Process; import org.springframework.stereotype.Component; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; /** * BPMN转换工具类 */ +@Slf4j @Component public class BpmnConverter { + private final Map> handlers; private final ObjectMapper objectMapper; + private final BpmnNodeConfigParser configParser; + private final BpmnXMLConverter bpmnXMLConverter; - public BpmnConverter(ObjectMapper objectMapper) { + public BpmnConverter(ObjectMapper objectMapper, + List> nodeHandlers, + BpmnNodeConfigParser configParser) { this.objectMapper = objectMapper; + this.configParser = configParser; + this.bpmnXMLConverter = new BpmnXMLConverter(); + this.handlers = nodeHandlers.stream() + .collect(Collectors.toMap(BpmnNodeHandler::getType, Function.identity())); } - /** - * 将X6 JSON转换为BPMN XML - */ - public String convertToBpmnXml(JsonNode x6Json, String processId) throws Exception { + public String convertToBpmnXml(String x6Json, String processId) throws Exception { + JsonNode jsonNode = objectMapper.readTree(x6Json); + // 创建BPMN模型 BpmnModel bpmnModel = new BpmnModel(); Process process = new Process(); - bpmnModel.addProcess(process); - process.setId(processId); process.setName(processId); + process.setExecutable(true); // 设置为可执行 + bpmnModel.addProcess(process); - // 解析节点 + // 处理节点 Map elementMap = new HashMap<>(); - JsonNode cells = x6Json.get("cells"); + JsonNode cells = jsonNode.get("cells"); + if (cells != null) { + // 第一遍:处理所有节点 for (JsonNode cell : cells) { - String shape = cell.get("shape").asText(); - String id = cell.get("id").asText(); - String label = cell.path("data").path("label").asText(id); - - FlowElement element = null; - switch (shape) { - case "start": - StartEvent startEvent = new StartEvent(); - startEvent.setId(id); - startEvent.setName(label); - element = startEvent; - break; - case "end": - EndEvent endEvent = new EndEvent(); - endEvent.setId(id); - endEvent.setName(label); - element = endEvent; - break; - case "userTask": - UserTask userTask = new UserTask(); - userTask.setId(id); - userTask.setName(label); - element = userTask; - break; - case "serviceTask": - 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}"); - - // 设置为异步执行 - serviceTask.setAsynchronous(true); - - 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; + if (!isNode(cell)) { + continue; } - if (element != null) { - process.addFlowElement(element); - elementMap.put(id, element); + String shape = cell.path("shape").asText(); + String id = cell.path("id").asText(); + String label = cell.path("data").path("label").asText(""); + + // 处理特殊节点类型 + if ("start".equals(shape)) { + StartEvent startEvent = new StartEvent(); + startEvent.setId(id); + startEvent.setName(label); + process.addFlowElement(startEvent); + elementMap.put(id, startEvent); + continue; + } else if ("end".equals(shape)) { + EndEvent endEvent = new EndEvent(); + endEvent.setId(id); + endEvent.setName(label); + process.addFlowElement(endEvent); + elementMap.put(id, endEvent); + continue; + } + + // 解析节点配置 + BpmnNodeConfig config = configParser.parse(cell); + if (config.getType() != null) { + // 使用对应的处理器处理节点 + BpmnNodeHandler handler = handlers.get(config.getType()); + if (handler != null) { + FlowElement element = createAndHandleElement(handler, cell, config); + process.addFlowElement(element); + elementMap.put(config.getId(), element); + } } } - // 解析连线 + // 确保存在开始事件和结束事件(如果没有显式定义) + ensureStartAndEndEvents(process, elementMap); + + // 第二遍:处理所有连线 for (JsonNode cell : cells) { - if (cell.has("source") && cell.has("target")) { - String sourceId = cell.get("source").asText(); - String targetId = cell.get("target").asText(); - String id = cell.get("id").asText(); - - FlowElement sourceElement = elementMap.get(sourceId); - FlowElement targetElement = elementMap.get(targetId); - - if (sourceElement != null && targetElement != null) { - SequenceFlow sequenceFlow = new SequenceFlow(); - sequenceFlow.setId(id); - sequenceFlow.setSourceRef(sourceId); - sequenceFlow.setTargetRef(targetId); - process.addFlowElement(sequenceFlow); - } + if (isEdge(cell)) { + handleSequenceFlow(cell, process, elementMap); } } } @@ -128,9 +112,107 @@ public class BpmnConverter { new BpmnAutoLayout(bpmnModel).execute(); // 转换为XML - org.flowable.bpmn.converter.BpmnXMLConverter converter = new org.flowable.bpmn.converter.BpmnXMLConverter(); - byte[] xml = converter.convertToXML(bpmnModel); + byte[] xmlBytes = bpmnXMLConverter.convertToXML(bpmnModel); + return new String(xmlBytes); + } + + private void ensureStartAndEndEvents(Process process, Map elementMap) { + // 查找现有的开始和结束事件 + StartEvent startEvent = null; + EndEvent endEvent = null; + FlowElement firstElement = null; + FlowElement lastElement = null; - return new String(xml); + // 遍历所有节点,找到开始和结束事件,以及第一个和最后一个任务节点 + for (FlowElement element : process.getFlowElements()) { + if (element instanceof StartEvent) { + startEvent = (StartEvent) element; + } else if (element instanceof EndEvent) { + endEvent = (EndEvent) element; + } else if (!(element instanceof SequenceFlow)) { + if (firstElement == null) { + firstElement = element; + } + lastElement = element; + } + } + + // 如果没有开始事件,创建一个并连接到第一个任务 + if (startEvent == null && firstElement != null) { + startEvent = new StartEvent(); + startEvent.setId("startEvent"); + startEvent.setName("开始"); + process.addFlowElement(startEvent); + elementMap.put(startEvent.getId(), startEvent); + + // 创建从开始事件到第一个任务的连线 + SequenceFlow startFlow = new SequenceFlow(); + startFlow.setId("flow_start"); + startFlow.setSourceRef(startEvent.getId()); + startFlow.setTargetRef(firstElement.getId()); + process.addFlowElement(startFlow); + } + + // 如果没有结束事件,创建一个并从最后一个任务连接到它 + if (endEvent == null && lastElement != null) { + endEvent = new EndEvent(); + endEvent.setId("endEvent"); + endEvent.setName("结束"); + process.addFlowElement(endEvent); + elementMap.put(endEvent.getId(), endEvent); + + // 创建从最后一个任务到结束事件的连线 + SequenceFlow endFlow = new SequenceFlow(); + endFlow.setId("flow_end"); + endFlow.setSourceRef(lastElement.getId()); + endFlow.setTargetRef(endEvent.getId()); + process.addFlowElement(endFlow); + } + } + + private boolean isNode(JsonNode cell) { + return cell.has("shape") && !cell.path("shape").asText().equals("edge"); + } + + private boolean isEdge(JsonNode cell) { + return cell.has("shape") && cell.path("shape").asText().equals("edge"); + } + + @SuppressWarnings("unchecked") + private T createAndHandleElement( + BpmnNodeHandler handler, JsonNode nodeData, BpmnNodeConfig config) { + T element = handler.createElement(config); + handler.handle(nodeData, element, config); + return element; + } + + private void handleSequenceFlow(JsonNode cell, Process process, Map elementMap) { + String sourceId = cell.path("source").asText(); + String targetId = cell.path("target").asText(); + String id = cell.path("id").asText(); + + FlowElement sourceElement = elementMap.get(sourceId); + FlowElement targetElement = elementMap.get(targetId); + + if (sourceElement != null && targetElement != null) { + SequenceFlow sequenceFlow = new SequenceFlow(); + sequenceFlow.setId(id); + sequenceFlow.setSourceRef(sourceId); + sequenceFlow.setTargetRef(targetId); + + // 设置连线名称 + JsonNode label = cell.path("data").path("label"); + if (!label.isMissingNode()) { + sequenceFlow.setName(label.asText()); + } + + // 设置条件表达式 + JsonNode condition = cell.path("data").path("condition"); + if (!condition.isMissingNode()) { + sequenceFlow.setConditionExpression(condition.asText()); + } + + process.addFlowElement(sequenceFlow); + } } } 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 988ad5c3..b90fd899 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 @@ -397,6 +397,7 @@ CREATE TABLE workflow_definition ( flow_version INT NOT NULL COMMENT '流程版本', bpmn_xml TEXT NOT NULL COMMENT 'BPMN XML内容', graph_json JSON COMMENT 'x6 JSON内容', + status VARCHAR(32) NOT NULL COMMENT '状态', description VARCHAR(255) NULL COMMENT '流程描述', CONSTRAINT UK_workflow_definition_key_version UNIQUE (`key`, flow_version) diff --git a/backend/src/test/java/com/qqchen/deploy/backend/workflow/util/BpmnConverterTest.java b/backend/src/test/java/com/qqchen/deploy/backend/workflow/util/BpmnConverterTest.java new file mode 100644 index 00000000..e64ee1a6 --- /dev/null +++ b/backend/src/test/java/com/qqchen/deploy/backend/workflow/util/BpmnConverterTest.java @@ -0,0 +1,102 @@ +package com.qqchen.deploy.backend.workflow.util; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.qqchen.deploy.backend.workflow.handler.impl.ShellTaskHandler; +import com.qqchen.deploy.backend.workflow.parser.BpmnNodeConfigParser; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.bpmn.model.Process; +import org.flowable.bpmn.model.ServiceTask; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.core.io.ClassPathResource; + +import java.nio.charset.StandardCharsets; +import java.util.Collections; + +import static org.junit.jupiter.api.Assertions.*; + +class BpmnConverterTest { + + private BpmnConverter bpmnConverter; + private ObjectMapper objectMapper; + + @BeforeEach + void setUp() { + objectMapper = new ObjectMapper(); + bpmnConverter = new BpmnConverter( + objectMapper, + Collections.singletonList(new ShellTaskHandler()), + new BpmnNodeConfigParser() + ); + } + + @Test + void testConvertShellTask() throws Exception { + // 准备测试数据 + String json = """ + { + "cells": [ + { + "id": "start", + "shape": "start", + "data": { + "label": "开始" + } + }, + { + "id": "shell1", + "shape": "shellTask", + "data": { + "label": "Shell脚本", + "serviceTask": { + "fields": { + "script": "echo 'Hello World'", + "workDir": "/tmp" + } + } + } + }, + { + "id": "flow1", + "shape": "edge", + "source": "start", + "target": "shell1", + "data": { + "label": "流转到Shell" + } + } + ] + } + """; + + // 执行转换 + String xml = bpmnConverter.convertToBpmnXml(json, "test_process"); + + // 验证结果 + assertNotNull(xml); + assertTrue(xml.contains("flowable:delegateExpression=\"${shellTaskDelegate}\"")); + assertTrue(xml.contains("")); + assertTrue(xml.contains("")); + } + + @Test + void testConvertComplexProcess() throws Exception { + // 从测试资源文件加载复杂流程JSON + ClassPathResource resource = new ClassPathResource("test-process.json"); + String json = new String(resource.getInputStream().readAllBytes(), StandardCharsets.UTF_8); + + // 执行转换 + String xml = bpmnConverter.convertToBpmnXml(json, "complex_process"); + + // 验证结果 + assertNotNull(xml); + // 验证基本结构 + assertTrue(xml.contains("