通用解析
This commit is contained in:
parent
412ead95a0
commit
96e122b54a
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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,17 +50,23 @@ 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<>();
|
||||
|
||||
@ -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,115 +161,38 @@ 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);
|
||||
}
|
||||
@ -318,6 +244,25 @@ public class WorkflowDefinitionApiController extends BaseController<WorkflowDefi
|
||||
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) {
|
||||
|
||||
|
||||
@ -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: 实现导出功能
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -31,6 +33,10 @@ public class WorkflowDefinitionDTO extends BaseDTO {
|
||||
*/
|
||||
private String bpmnXml;
|
||||
|
||||
private JsonNode graphJson;
|
||||
|
||||
private WorkflowStatusEnums status;
|
||||
|
||||
/**
|
||||
* 流程描述
|
||||
*/
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
/**
|
||||
* 开始时间
|
||||
|
||||
@ -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 = "活动节点信息")
|
||||
|
||||
@ -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;
|
||||
@ -49,6 +52,10 @@ public class WorkflowDefinition extends Entity<Long> {
|
||||
@Column(name = "graph_json", columnDefinition = "json")
|
||||
private JsonNode graphJson;
|
||||
|
||||
@Column(nullable = false)
|
||||
@Enumerated(EnumType.STRING)
|
||||
private WorkflowStatusEnums status;
|
||||
|
||||
/**
|
||||
* 流程描述
|
||||
*/
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -3,7 +3,7 @@ package com.qqchen.deploy.backend.workflow.enums;
|
||||
/**
|
||||
* 日志类型枚举
|
||||
*/
|
||||
public enum LogType {
|
||||
public enum NodeLogTypeEnums {
|
||||
STDOUT, // 标准输出
|
||||
STDERR // 错误输出
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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<>();
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -14,6 +16,8 @@ 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);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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);
|
||||
|
||||
/**
|
||||
* 获取工作流实例详情
|
||||
|
||||
@ -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) {
|
||||
@ -206,10 +242,10 @@ public class WorkflowDefinitionServiceImpl extends BaseServiceImpl<WorkflowDefin
|
||||
// 设置流程状态
|
||||
if (historicInstance.getEndTime() != null) {
|
||||
executionDTO.setStatus(historicInstance.getDeleteReason() == null ?
|
||||
WorkflowInstanceStatus.COMPLETED : WorkflowInstanceStatus.FAILED);
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
return new String(xml);
|
||||
private void ensureStartAndEndEvents(Process process, Map<String, FlowElement> elementMap) {
|
||||
// 查找现有的开始和结束事件
|
||||
StartEvent startEvent = null;
|
||||
EndEvent endEvent = null;
|
||||
FlowElement firstElement = null;
|
||||
FlowElement lastElement = null;
|
||||
|
||||
// 遍历所有节点,找到开始和结束事件,以及第一个和最后一个任务节点
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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"));
|
||||
}
|
||||
}
|
||||
155
backend/src/test/resources/test-process.json
Normal file
155
backend/src/test/resources/test-process.json
Normal 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": "完成"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user