通用解析

This commit is contained in:
戚辰先生 2024-12-10 21:48:15 +08:00
parent 412ead95a0
commit 96e122b54a
30 changed files with 998 additions and 426 deletions

View File

@ -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;
}
}

View File

@ -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<WorkflowDefi
@Resource
private HistoryService historyService;
@Operation(summary = "部署工作流")
@PostMapping("/deploy")
public Response<WorkflowDefinitionDTO> deployWorkflow(@RequestBody WorkflowDefinitionDTO dto) {
return Response.success(workflowDefinitionService.deployWorkflow(dto));
@Operation(summary = "保存工作流设计")
@PostMapping("/design")
public Response<WorkflowDesignDTO> saveWorkflowDesign(@RequestBody WorkflowDefinitionDTO dto) throws Exception {
return Response.success(workflowDefinitionService.saveWorkflowDesign(dto));
}
// @Operation(summary = "部署工作流")
// @PostMapping("/deploy")
// public Response<WorkflowDefinitionDTO> deployWorkflow(@RequestBody WorkflowDefinitionDTO dto) {
// return Response.success(workflowDefinitionService.deployWorkflow(dto));
// }
@Operation(summary = "启动工作流实例")
@PostMapping("/start")
public Response<WorkflowInstanceCreateDTO> 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<String, Object> variables = new HashMap<>();
try {
// 同步创建实例立即返回实例ID
WorkflowInstanceCreateDTO result = workflowDefinitionService.startWorkflow(processKey, businessKey, variables);
@ -80,7 +83,7 @@ public class WorkflowDefinitionApiController extends BaseController<WorkflowDefi
@Operation(summary = "挂起工作流实例")
@PostMapping("/{processInstanceId}/suspend")
public Response<Void> 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<WorkflowDefi
@Operation(summary = "恢复工作流实例")
@PostMapping("/{processInstanceId}/resume")
public Response<Void> 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<WorkflowDefi
@Operation(summary = "查询工作流实例")
@GetMapping("/instance/{processInstanceId}")
public Response<WorkflowInstanceDTO> 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<WorkflowDefi
instanceDTO.setEndTime(historicProcessInstance.getEndTime());
instanceDTO.setDurationInMillis(historicProcessInstance.getDurationInMillis());
instanceDTO.setStartUserId(historicProcessInstance.getStartUserId());
instanceDTO.setStatus(historicProcessInstance.getEndTime() != null ? WorkflowInstanceStatus.COMPLETED : WorkflowInstanceStatus.RUNNING);
instanceDTO.setStatus(historicProcessInstance.getEndTime() != null ? WorkflowInstanceStatusEnums.COMPLETED : WorkflowInstanceStatusEnums.RUNNING);
instanceDTO.setVariables(historicProcessInstance.getProcessVariables());
// 查询活动节点历史
List<HistoricActivityInstance> activities = historyService.createHistoricActivityInstanceQuery()
.processInstanceId(processInstanceId)
.orderByHistoricActivityInstanceStartTime()
.asc()
.list();
.processInstanceId(processInstanceId)
.orderByHistoricActivityInstanceStartTime()
.asc()
.list();
List<WorkflowInstanceDTO.ActivityInstance> 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<String, Object> 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<String, Object> 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<WorkflowDefi
@Operation(summary = "获取工作流执行状态")
@GetMapping("/instance/{processInstanceId}/execution")
public ResponseEntity<WorkflowExecutionDTO> getWorkflowExecution(@PathVariable String processInstanceId) {
// // 获取历史活动实例
// List<HistoricActivityInstance> activities = historyService.createHistoricActivityInstanceQuery()
// .processInstanceId(processInstanceId)
// .orderByHistoricActivityInstanceStartTime()
// .asc()
// .list();
//
// // 获取流程实例
// HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery()
// .processInstanceId(processInstanceId)
// .singleResult();
//
// if (processInstance == null) {
// return ResponseEntity.notFound().build();
// }
//
// // 获取所有流程变量
// List<HistoricVariableInstance> allVariables = historyService.createHistoricVariableInstanceQuery()
// .processInstanceId(processInstanceId)
// .list();
//
// // 构建变量映射
// Map<String, Object> 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<WorkflowExecutionDTO.StageDTO> 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<List<WorkflowInstanceDTO>> 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<HistoricProcessInstance> historicProcessInstances = historyService.createHistoricProcessInstanceQuery()
.processDefinitionKey(processKey)
.processInstanceBusinessKey(businessKey)
.includeProcessVariables()
.orderByProcessInstanceStartTime()
.desc()
.list();
.processDefinitionKey(processKey)
.processInstanceBusinessKey(businessKey)
.includeProcessVariables()
.orderByProcessInstanceStartTime()
.desc()
.list();
List<WorkflowInstanceDTO> 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<WorkflowDefi
@Parameter(description = "节点ID", required = true) @PathVariable String nodeId
) {
Map<String, Object> 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<HistoricVariableInstance> variables = historyService.createHistoricVariableInstanceQuery()
.processInstanceId(processInstanceId)
.list();
// 收集日志信息
variables.forEach(variable -> {
String variableName = variable.getVariableName();
@ -306,7 +232,7 @@ public class WorkflowDefinitionApiController extends BaseController<WorkflowDefi
result.put(key, variable.getValue());
}
});
// 添加节点基本信息
result.put("nodeId", nodeId);
result.put("nodeName", activity.getActivityName());
@ -314,10 +240,29 @@ public class WorkflowDefinitionApiController extends BaseController<WorkflowDefi
result.put("startTime", activity.getStartTime());
result.put("endTime", activity.getEndTime());
result.put("status", activity.getEndTime() == null ? "RUNNING" : "COMPLETED");
return Response.success(result);
}
@Operation(summary = "禁用工作流")
@PostMapping("/{id}/disable")
public Response<Void> disable(
@Parameter(description = "工作流定义ID", required = true) @PathVariable Long id
) {
workflowDefinitionService.disable(id);
return Response.success();
}
@Operation(summary = "启用工作流")
@PostMapping("/{id}/enable")
public Response<Void> enable(
@Parameter(description = "工作流定义ID", required = true) @PathVariable Long id
) {
workflowDefinitionService.enable(id);
return Response.success();
}
@Override
protected void exportData(HttpServletResponse response, List<WorkflowDefinitionDTO> data) {

View File

@ -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<WorkflowDefinition, WorkflowDesignDTO, Long, WorkflowDefinitionQuery> {
@Resource
private IWorkflowDesignService workflowDesignService;
@Operation(summary = "保存工作流设计")
@PostMapping("/save")
public Response<WorkflowDesignDTO> saveWorkflowDesign(@RequestBody WorkflowDesignDTO dto) throws Exception {
return Response.success(workflowDesignService.saveWorkflowDesign(dto));
}
@Override
protected void exportData(HttpServletResponse response, List<WorkflowDesignDTO> data) {
// TODO: 实现导出功能
}
}

View File

@ -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<String, SseEmitter> 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);
}
}
}

View File

@ -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<String, StringBuilder> outputMap = new ConcurrentHashMap<>();
private static final Map<String, StringBuilder> 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);

View File

@ -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;
/**
* 流程描述

View File

@ -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;
}

View File

@ -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;
/**
* 开始时间

View File

@ -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 = "活动节点信息")

View File

@ -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<Long> {
@Type(JsonType.class)
@Column(name = "graph_json", columnDefinition = "json")
private JsonNode graphJson;
@Column(nullable = false)
@Enumerated(EnumType.STRING)
private WorkflowStatusEnums status;
/**
* 流程描述

View File

@ -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<Long> {
*/
@Column(nullable = false)
@Enumerated(EnumType.STRING)
private WorkflowInstanceStatus status;
private WorkflowInstanceStatusEnums status;
/**
* 流程变量(JSON)

View File

@ -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;
}
}

View File

@ -3,7 +3,7 @@ package com.qqchen.deploy.backend.workflow.enums;
/**
* 日志类型枚举
*/
public enum LogType {
public enum NodeLogTypeEnums {
STDOUT, // 标准输出
STDERR // 错误输出
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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 <T> 流程元素类型
*/
public interface BpmnNodeHandler<T extends FlowElement> {
/**
* 处理节点数据
*
* @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<T> getElementType();
}

View File

@ -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<ServiceTask> {
@Override
public void handle(JsonNode nodeData, ServiceTask element, BpmnNodeConfig config) {
element.setImplementationType("delegateExpression");
element.setImplementation("${shellTaskDelegate}");
element.setAsynchronous(true); // Shell任务默认异步执行
// 从配置中获取字段
Map<String, Object> properties = config.getProperties();
properties.forEach((key, value) -> {
if (value != null) {
addFieldExtension(element, key, value.toString());
}
});
// 处理扩展字段
Map<String, String> 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<ServiceTask> 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);
}
}

View File

@ -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<String, Object> properties = new HashMap<>();
/**
* 扩展字段
*/
private Map<String, String> extensions = new HashMap<>();
}

View File

@ -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<String, Object> 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<String, String> 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;
}
}

View File

@ -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<WorkflowDefinition, WorkflowDefinitionDTO, Long> {
WorkflowDesignDTO saveWorkflowDesign(WorkflowDefinitionDTO dto) throws Exception;
/**
* 部署工作流
*
@ -53,4 +57,8 @@ public interface IWorkflowDefinitionService extends IBaseService<WorkflowDefinit
* @return 工作流执行状态DTO
*/
WorkflowExecutionDTO getWorkflowExecution(String processInstanceId);
void disable(Long id);
void enable(Long id);
}

View File

@ -1,17 +0,0 @@
package com.qqchen.deploy.backend.workflow.service;
import com.qqchen.deploy.backend.framework.service.IBaseService;
import com.qqchen.deploy.backend.workflow.dto.WorkflowDesignDTO;
import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition;
import com.qqchen.deploy.backend.workflow.query.WorkflowDefinitionQuery;
/**
* 工作流设计服务接口
*/
public interface IWorkflowDesignService extends IBaseService<WorkflowDefinition, WorkflowDesignDTO, Long> {
/**
* 保存工作流设计
*/
WorkflowDesignDTO saveWorkflowDesign(WorkflowDesignDTO dto) throws Exception;
}

View File

@ -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);
/**
* 获取工作流实例详情

View File

@ -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<WorkflowDefinition, WorkflowDefinitionDTO, Long>
implements IWorkflowDefinitionService {
public class WorkflowDefinitionServiceImpl extends BaseServiceImpl<WorkflowDefinition, WorkflowDefinitionDTO, Long> implements IWorkflowDefinitionService {
@Resource
private RepositoryService repositoryService;
@ -68,6 +63,47 @@ public class WorkflowDefinitionServiceImpl extends BaseServiceImpl<WorkflowDefin
@Resource
private IWorkflowDefinitionRepository workflowDefinitionRepository;
@Resource
private BpmnConverter bpmnConverter;
@Override
@Transactional(rollbackFor = Exception.class)
public WorkflowDesignDTO saveWorkflowDesign(WorkflowDefinitionDTO dto) throws Exception {
// 转换图形JSON为BPMN XML
String bpmnXml = bpmnConverter.convertToBpmnXml(null, 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;
}
@Override
@Transactional(rollbackFor = Exception.class)
public WorkflowDefinitionDTO deployWorkflow(WorkflowDefinitionDTO dto) {
@ -152,7 +188,7 @@ public class WorkflowDefinitionServiceImpl extends BaseServiceImpl<WorkflowDefin
ProcessInstance runningInstance = runtimeService.createProcessInstanceQuery()
.processInstanceId(processInstanceId)
.singleResult();
HistoricProcessInstance historicInstance = historyService.createHistoricProcessInstanceQuery()
.processInstanceId(processInstanceId)
.singleResult();
@ -205,11 +241,11 @@ public class WorkflowDefinitionServiceImpl extends BaseServiceImpl<WorkflowDefin
// 设置流程状态
if (historicInstance.getEndTime() != null) {
executionDTO.setStatus(historicInstance.getDeleteReason() == null ?
WorkflowInstanceStatus.COMPLETED : WorkflowInstanceStatus.FAILED);
executionDTO.setStatus(historicInstance.getDeleteReason() == null ?
WorkflowInstanceStatusEnums.COMPLETED : WorkflowInstanceStatusEnums.FAILED);
} else {
executionDTO.setStatus(runningInstance != null && runningInstance.isSuspended() ?
WorkflowInstanceStatus.SUSPENDED : WorkflowInstanceStatus.RUNNING);
WorkflowInstanceStatusEnums.SUSPENDED : WorkflowInstanceStatusEnums.RUNNING);
}
// 7. 构建节点状态列表
@ -224,7 +260,7 @@ public class WorkflowDefinitionServiceImpl extends BaseServiceImpl<WorkflowDefin
// 如果已经有节点失败后续节点都是未开始
if (hasFailedNode) {
stage.setStatus(WorkflowInstanceStatus.NOT_STARTED);
stage.setStatus(WorkflowInstanceStatusEnums.NOT_STARTED);
stages.add(stage);
continue;
}
@ -232,7 +268,7 @@ public class WorkflowDefinitionServiceImpl extends BaseServiceImpl<WorkflowDefin
// 判断节点状态
if (runningNodes.contains(element.getId())) {
// 正在执行的节点
stage.setStatus(WorkflowInstanceStatus.RUNNING);
stage.setStatus(WorkflowInstanceStatusEnums.RUNNING);
} else {
// 查找历史记录
HistoricActivityInstance historicActivity = historicNodes.get(element.getId());
@ -243,19 +279,19 @@ public class WorkflowDefinitionServiceImpl extends BaseServiceImpl<WorkflowDefin
if (historicActivity.getEndTime() != null) {
if (historicActivity.getDeleteReason() != null) {
// 节点失败
stage.setStatus(WorkflowInstanceStatus.FAILED);
stage.setStatus(WorkflowInstanceStatusEnums.FAILED);
hasFailedNode = true;
} else {
// 节点完成
stage.setStatus(WorkflowInstanceStatus.COMPLETED);
stage.setStatus(WorkflowInstanceStatusEnums.COMPLETED);
}
} else {
// 有开始时间但没有结束时间说明正在运行
stage.setStatus(WorkflowInstanceStatus.RUNNING);
stage.setStatus(WorkflowInstanceStatusEnums.RUNNING);
}
} else {
// 没有历史记录未开始
stage.setStatus(WorkflowInstanceStatus.NOT_STARTED);
stage.setStatus(WorkflowInstanceStatusEnums.NOT_STARTED);
}
}
@ -270,4 +306,60 @@ public class WorkflowDefinitionServiceImpl extends BaseServiceImpl<WorkflowDefin
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void disable(Long id) {
try {
// 1. 查询流程定义
WorkflowDefinition definition = workflowDefinitionRepository.findById(id)
.orElseThrow(() -> new RuntimeException("Workflow definition not found: " + id));
// 2. 查询部署
List<Deployment> 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();
}
}

View File

@ -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<WorkflowDefinition, WorkflowDesignDTO, Long> 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;
}
}

View File

@ -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);
}

View File

@ -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<BpmnNodeType, BpmnNodeHandler<?>> handlers;
private final ObjectMapper objectMapper;
private final BpmnNodeConfigParser configParser;
private final BpmnXMLConverter bpmnXMLConverter;
public BpmnConverter(ObjectMapper objectMapper) {
public BpmnConverter(ObjectMapper objectMapper,
List<BpmnNodeHandler<?>> 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<String, FlowElement> 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<String, FlowElement> 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 extends FlowElement> T createAndHandleElement(
BpmnNodeHandler<T> 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<String, FlowElement> 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);
}
}
}

View File

@ -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)

View File

@ -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("<flowable:field name=\"script\">"));
assertTrue(xml.contains("<flowable:field name=\"workDir\">"));
}
@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("<process id=\"complex_process\""));
// 验证节点
assertTrue(xml.contains("<startEvent"));
assertTrue(xml.contains("<serviceTask"));
assertTrue(xml.contains("<endEvent"));
// 验证连线
assertTrue(xml.contains("<sequenceFlow"));
}
}

View File

@ -0,0 +1,155 @@
{
"cells": [
{
"id": "start",
"shape": "start",
"data": {
"label": "开始"
},
"position": {
"x": 100,
"y": 100
}
},
{
"id": "shell1",
"shape": "shellTask",
"data": {
"label": "准备环境",
"serviceTask": {
"fields": {
"script": "echo 'Preparing environment...'",
"workDir": "/tmp",
"env": {
"ENV": "test"
}
}
},
"properties": {
"timeout": 3600,
"retryCount": 3
},
"extensions": {
"group": "deployment",
"priority": "high"
}
},
"position": {
"x": 250,
"y": 100
}
},
{
"id": "gateway1",
"shape": "exclusiveGateway",
"data": {
"label": "检查结果"
},
"position": {
"x": 400,
"y": 100
}
},
{
"id": "shell2",
"shape": "shellTask",
"data": {
"label": "成功处理",
"serviceTask": {
"fields": {
"script": "echo 'Success!'",
"workDir": "/tmp"
}
}
},
"position": {
"x": 550,
"y": 50
}
},
{
"id": "shell3",
"shape": "shellTask",
"data": {
"label": "失败处理",
"serviceTask": {
"fields": {
"script": "echo 'Failed!'",
"workDir": "/tmp"
}
}
},
"position": {
"x": 550,
"y": 150
}
},
{
"id": "end",
"shape": "end",
"data": {
"label": "结束"
},
"position": {
"x": 700,
"y": 100
}
},
{
"id": "flow1",
"shape": "edge",
"source": "start",
"target": "shell1",
"data": {
"label": "开始准备"
}
},
{
"id": "flow2",
"shape": "edge",
"source": "shell1",
"target": "gateway1",
"data": {
"label": "准备完成"
}
},
{
"id": "flow3",
"shape": "edge",
"source": "gateway1",
"target": "shell2",
"data": {
"label": "成功",
"condition": "${exitCode == 0}"
}
},
{
"id": "flow4",
"shape": "edge",
"source": "gateway1",
"target": "shell3",
"data": {
"label": "失败",
"condition": "${exitCode != 0}"
}
},
{
"id": "flow5",
"shape": "edge",
"source": "shell2",
"target": "end",
"data": {
"label": "完成"
}
},
{
"id": "flow6",
"shape": "edge",
"source": "shell3",
"target": "end",
"data": {
"label": "完成"
}
}
]
}