通用解析
This commit is contained in:
parent
412ead95a0
commit
96e122b54a
@ -102,10 +102,18 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String getJwtFromRequest(HttpServletRequest request) {
|
private String getJwtFromRequest(HttpServletRequest request) {
|
||||||
|
// 1. 先从Authorization header获取
|
||||||
String bearerToken = request.getHeader("Authorization");
|
String bearerToken = request.getHeader("Authorization");
|
||||||
if (!StringUtils.hasText(bearerToken) || !bearerToken.startsWith("Bearer ")) {
|
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
|
||||||
return null;
|
|
||||||
}
|
|
||||||
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.controller.BaseController;
|
||||||
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
|
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
|
||||||
import com.qqchen.deploy.backend.workflow.dto.WorkflowDefinitionDTO;
|
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.WorkflowExecutionDTO;
|
||||||
import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceDTO;
|
import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceDTO;
|
||||||
import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceCreateDTO;
|
import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceCreateDTO;
|
||||||
import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition;
|
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.query.WorkflowDefinitionQuery;
|
||||||
import com.qqchen.deploy.backend.workflow.service.IWorkflowDefinitionService;
|
import com.qqchen.deploy.backend.workflow.service.IWorkflowDefinitionService;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
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.RuntimeService;
|
||||||
import org.flowable.engine.history.HistoricActivityInstance;
|
import org.flowable.engine.history.HistoricActivityInstance;
|
||||||
import org.flowable.engine.history.HistoricProcessInstance;
|
import org.flowable.engine.history.HistoricProcessInstance;
|
||||||
import org.flowable.engine.runtime.ProcessInstance;
|
|
||||||
import org.flowable.variable.api.history.HistoricVariableInstance;
|
import org.flowable.variable.api.history.HistoricVariableInstance;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@ -53,12 +50,18 @@ public class WorkflowDefinitionApiController extends BaseController<WorkflowDefi
|
|||||||
@Resource
|
@Resource
|
||||||
private HistoryService historyService;
|
private HistoryService historyService;
|
||||||
|
|
||||||
@Operation(summary = "部署工作流")
|
@Operation(summary = "保存工作流设计")
|
||||||
@PostMapping("/deploy")
|
@PostMapping("/design")
|
||||||
public Response<WorkflowDefinitionDTO> deployWorkflow(@RequestBody WorkflowDefinitionDTO dto) {
|
public Response<WorkflowDesignDTO> saveWorkflowDesign(@RequestBody WorkflowDefinitionDTO dto) throws Exception {
|
||||||
return Response.success(workflowDefinitionService.deployWorkflow(dto));
|
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 = "启动工作流实例")
|
@Operation(summary = "启动工作流实例")
|
||||||
@PostMapping("/start")
|
@PostMapping("/start")
|
||||||
public Response<WorkflowInstanceCreateDTO> startWorkflow(
|
public Response<WorkflowInstanceCreateDTO> startWorkflow(
|
||||||
@ -117,7 +120,7 @@ public class WorkflowDefinitionApiController extends BaseController<WorkflowDefi
|
|||||||
instanceDTO.setEndTime(historicProcessInstance.getEndTime());
|
instanceDTO.setEndTime(historicProcessInstance.getEndTime());
|
||||||
instanceDTO.setDurationInMillis(historicProcessInstance.getDurationInMillis());
|
instanceDTO.setDurationInMillis(historicProcessInstance.getDurationInMillis());
|
||||||
instanceDTO.setStartUserId(historicProcessInstance.getStartUserId());
|
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());
|
instanceDTO.setVariables(historicProcessInstance.getProcessVariables());
|
||||||
|
|
||||||
// 查询活动节点历史
|
// 查询活动节点历史
|
||||||
@ -158,83 +161,6 @@ public class WorkflowDefinitionApiController extends BaseController<WorkflowDefi
|
|||||||
@Operation(summary = "获取工作流执行状态")
|
@Operation(summary = "获取工作流执行状态")
|
||||||
@GetMapping("/instance/{processInstanceId}/execution")
|
@GetMapping("/instance/{processInstanceId}/execution")
|
||||||
public ResponseEntity<WorkflowExecutionDTO> getWorkflowExecution(@PathVariable String processInstanceId) {
|
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));
|
return ResponseEntity.ok(workflowDefinitionService.getWorkflowExecution(processInstanceId));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -262,7 +188,7 @@ public class WorkflowDefinitionApiController extends BaseController<WorkflowDefi
|
|||||||
instanceDTO.setEndTime(historicProcessInstance.getEndTime());
|
instanceDTO.setEndTime(historicProcessInstance.getEndTime());
|
||||||
instanceDTO.setDurationInMillis(historicProcessInstance.getDurationInMillis());
|
instanceDTO.setDurationInMillis(historicProcessInstance.getDurationInMillis());
|
||||||
instanceDTO.setStartUserId(historicProcessInstance.getStartUserId());
|
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());
|
instanceDTO.setVariables(historicProcessInstance.getProcessVariables());
|
||||||
return instanceDTO;
|
return instanceDTO;
|
||||||
})
|
})
|
||||||
@ -318,6 +244,25 @@ public class WorkflowDefinitionApiController extends BaseController<WorkflowDefi
|
|||||||
return Response.success(result);
|
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
|
@Override
|
||||||
protected void exportData(HttpServletResponse response, List<WorkflowDefinitionDTO> data) {
|
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 com.qqchen.deploy.backend.workflow.event.ShellLogEvent;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@ -12,13 +12,13 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
@RestController
|
@RestController
|
||||||
@CrossOrigin(origins = "*", allowedHeaders = "*")
|
@CrossOrigin(origins = "*", allowedHeaders = "*")
|
||||||
@RequestMapping("/api/v1/shell-logs")
|
@RequestMapping("/api/v1/workflow/instance")
|
||||||
public class ShellLogController {
|
public class WorkflowNodeInstanceApiController {
|
||||||
|
|
||||||
// 存储每个进程的SSE发射器
|
// 存储每个进程的SSE发射器
|
||||||
private static final Map<String, SseEmitter> EMITTERS = new ConcurrentHashMap<>();
|
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) {
|
public SseEmitter streamLogs(@PathVariable String processInstanceId) {
|
||||||
SseEmitter emitter = new SseEmitter(Long.MAX_VALUE);
|
SseEmitter emitter = new SseEmitter(Long.MAX_VALUE);
|
||||||
|
|
||||||
@ -54,18 +54,25 @@ public class ShellLogController {
|
|||||||
|
|
||||||
@EventListener
|
@EventListener
|
||||||
public void handleShellLogEvent(ShellLogEvent event) {
|
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) {
|
if (emitter != null) {
|
||||||
try {
|
try {
|
||||||
// 发送日志事件到客户端
|
// 发送日志事件到客户端
|
||||||
emitter.send(SseEmitter.event()
|
emitter.send(SseEmitter.event()
|
||||||
.name(event.getLogType().toString())
|
.name(event.getLogType().toString())
|
||||||
.data(event.getLogMessage()));
|
.data(event.getLogMessage()));
|
||||||
|
log.debug("Sent log event to client for process: {}", processInstanceId);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
log.error("Error sending event for process: {}", event.getProcessInstanceId(), e);
|
log.error("Error sending event for process: {}", processInstanceId, e);
|
||||||
EMITTERS.remove(event.getProcessInstanceId());
|
EMITTERS.remove(processInstanceId);
|
||||||
emitter.completeWithError(e);
|
emitter.completeWithError(e);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
log.warn("No emitter found for process: {}", processInstanceId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
package com.qqchen.deploy.backend.workflow.delegate;
|
package com.qqchen.deploy.backend.workflow.delegate;
|
||||||
|
|
||||||
import com.qqchen.deploy.backend.workflow.event.ShellLogEvent;
|
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 lombok.extern.slf4j.Slf4j;
|
||||||
import org.flowable.engine.RuntimeService;
|
import org.flowable.engine.RuntimeService;
|
||||||
import org.flowable.engine.ManagementService;
|
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> outputMap = new ConcurrentHashMap<>();
|
||||||
private static final Map<String, StringBuilder> errorMap = 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))) {
|
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
|
||||||
String line;
|
String line;
|
||||||
while ((line = reader.readLine()) != null) {
|
while ((line = reader.readLine()) != null) {
|
||||||
@ -49,7 +49,7 @@ public class ShellTaskDelegate implements JavaDelegate {
|
|||||||
eventPublisher.publishEvent(new ShellLogEvent(processInstanceId, line, logType));
|
eventPublisher.publishEvent(new ShellLogEvent(processInstanceId, line, logType));
|
||||||
|
|
||||||
// 同时保存到StringBuilder中
|
// 同时保存到StringBuilder中
|
||||||
if (logType == LogType.STDOUT) {
|
if (logType == NodeLogTypeEnums.STDOUT) {
|
||||||
StringBuilder output = outputMap.get(processInstanceId);
|
StringBuilder output = outputMap.get(processInstanceId);
|
||||||
synchronized (output) {
|
synchronized (output) {
|
||||||
output.append(line).append("\n");
|
output.append(line).append("\n");
|
||||||
@ -95,10 +95,10 @@ public class ShellTaskDelegate implements JavaDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 初始化输出缓存
|
// 使用processInstanceId而不是executionId
|
||||||
String executionId = execution.getId();
|
String processInstanceId = execution.getProcessInstanceId();
|
||||||
outputMap.put(executionId, new StringBuilder());
|
outputMap.put(processInstanceId, new StringBuilder());
|
||||||
errorMap.put(executionId, new StringBuilder());
|
errorMap.put(processInstanceId, new StringBuilder());
|
||||||
|
|
||||||
// 创建进程构建器
|
// 创建进程构建器
|
||||||
ProcessBuilder processBuilder = new ProcessBuilder();
|
ProcessBuilder processBuilder = new ProcessBuilder();
|
||||||
@ -149,11 +149,11 @@ public class ShellTaskDelegate implements JavaDelegate {
|
|||||||
|
|
||||||
// 处理标准输出
|
// 处理标准输出
|
||||||
Future<?> outputFuture = executorService.submit(() ->
|
Future<?> outputFuture = executorService.submit(() ->
|
||||||
processInputStream(process.getInputStream(), executionId, LogType.STDOUT));
|
processInputStream(process.getInputStream(), processInstanceId, NodeLogTypeEnums.STDOUT));
|
||||||
|
|
||||||
// 处理错误输出
|
// 处理错误输出
|
||||||
Future<?> errorFuture = executorService.submit(() ->
|
Future<?> errorFuture = executorService.submit(() ->
|
||||||
processInputStream(process.getErrorStream(), executionId, LogType.STDERR));
|
processInputStream(process.getErrorStream(), processInstanceId, NodeLogTypeEnums.STDERR));
|
||||||
|
|
||||||
// 等待进程完成
|
// 等待进程完成
|
||||||
int exitCode = process.waitFor();
|
int exitCode = process.waitFor();
|
||||||
@ -166,16 +166,16 @@ public class ShellTaskDelegate implements JavaDelegate {
|
|||||||
executorService.shutdown();
|
executorService.shutdown();
|
||||||
|
|
||||||
// 设置最终结果
|
// 设置最终结果
|
||||||
StringBuilder finalOutput = outputMap.get(executionId);
|
StringBuilder finalOutput = outputMap.get(processInstanceId);
|
||||||
StringBuilder finalError = errorMap.get(executionId);
|
StringBuilder finalError = errorMap.get(processInstanceId);
|
||||||
|
|
||||||
execution.setVariable("shellOutput", finalOutput.toString());
|
execution.setVariable("shellOutput", finalOutput.toString());
|
||||||
execution.setVariable("shellError", finalError.toString());
|
execution.setVariable("shellError", finalError.toString());
|
||||||
execution.setVariable("shellExitCode", exitCode);
|
execution.setVariable("shellExitCode", exitCode);
|
||||||
|
|
||||||
// 清理缓存
|
// 清理缓存
|
||||||
outputMap.remove(executionId);
|
outputMap.remove(processInstanceId);
|
||||||
errorMap.remove(executionId);
|
errorMap.remove(processInstanceId);
|
||||||
|
|
||||||
if (exitCode != 0) {
|
if (exitCode != 0) {
|
||||||
log.error("Shell script execution failed with exit code: {}", exitCode);
|
log.error("Shell script execution failed with exit code: {}", exitCode);
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
package com.qqchen.deploy.backend.workflow.dto;
|
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.framework.dto.BaseDTO;
|
||||||
|
import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnums;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
@ -31,6 +33,10 @@ public class WorkflowDefinitionDTO extends BaseDTO {
|
|||||||
*/
|
*/
|
||||||
private String bpmnXml;
|
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.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
|
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
|
||||||
|
import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnums;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -28,4 +29,6 @@ public class WorkflowDesignDTO extends BaseDTO {
|
|||||||
* 流程图数据
|
* 流程图数据
|
||||||
*/
|
*/
|
||||||
private JsonNode graphJson;
|
private JsonNode graphJson;
|
||||||
|
|
||||||
|
private WorkflowStatusEnums status;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
package com.qqchen.deploy.backend.workflow.dto;
|
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 lombok.Data;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -31,7 +31,7 @@ public class WorkflowExecutionDTO {
|
|||||||
/**
|
/**
|
||||||
* 流程状态
|
* 流程状态
|
||||||
*/
|
*/
|
||||||
private WorkflowInstanceStatus status;
|
private WorkflowInstanceStatusEnums status;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 当前活动节点ID
|
* 当前活动节点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;
|
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 io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
@ -32,7 +32,7 @@ public class WorkflowInstanceDTO {
|
|||||||
private String startUserId;
|
private String startUserId;
|
||||||
|
|
||||||
@Schema(description = "状态(RUNNING/COMPLETED/FAILED)")
|
@Schema(description = "状态(RUNNING/COMPLETED/FAILED)")
|
||||||
private WorkflowInstanceStatus status;
|
private WorkflowInstanceStatusEnums status;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@Schema(description = "活动节点信息")
|
@Schema(description = "活动节点信息")
|
||||||
|
|||||||
@ -1,9 +1,12 @@
|
|||||||
package com.qqchen.deploy.backend.workflow.entity;
|
package com.qqchen.deploy.backend.workflow.entity;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnums;
|
||||||
import com.vladmihalcea.hibernate.type.json.JsonType;
|
import com.vladmihalcea.hibernate.type.json.JsonType;
|
||||||
import com.qqchen.deploy.backend.framework.domain.Entity;
|
import com.qqchen.deploy.backend.framework.domain.Entity;
|
||||||
import jakarta.persistence.Column;
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.EnumType;
|
||||||
|
import jakarta.persistence.Enumerated;
|
||||||
import jakarta.persistence.Table;
|
import jakarta.persistence.Table;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
@ -49,6 +52,10 @@ public class WorkflowDefinition extends Entity<Long> {
|
|||||||
@Column(name = "graph_json", columnDefinition = "json")
|
@Column(name = "graph_json", columnDefinition = "json")
|
||||||
private JsonNode graphJson;
|
private JsonNode graphJson;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
private WorkflowStatusEnums status;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 流程描述
|
* 流程描述
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
package com.qqchen.deploy.backend.workflow.entity;
|
package com.qqchen.deploy.backend.workflow.entity;
|
||||||
|
|
||||||
import com.qqchen.deploy.backend.framework.domain.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.Column;
|
||||||
import jakarta.persistence.EnumType;
|
import jakarta.persistence.EnumType;
|
||||||
import jakarta.persistence.Enumerated;
|
import jakarta.persistence.Enumerated;
|
||||||
@ -43,7 +43,7 @@ public class WorkflowInstance extends Entity<Long> {
|
|||||||
*/
|
*/
|
||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
@Enumerated(EnumType.STRING)
|
@Enumerated(EnumType.STRING)
|
||||||
private WorkflowInstanceStatus status;
|
private WorkflowInstanceStatusEnums status;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 流程变量(JSON)
|
* 流程变量(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, // 标准输出
|
STDOUT, // 标准输出
|
||||||
STDERR // 错误输出
|
STDERR // 错误输出
|
||||||
}
|
}
|
||||||
@ -8,7 +8,7 @@ import lombok.Getter;
|
|||||||
* NOT_STARTED -> CREATED -> RUNNING -> (SUSPENDED) -> COMPLETED/TERMINATED/FAILED
|
* NOT_STARTED -> CREATED -> RUNNING -> (SUSPENDED) -> COMPLETED/TERMINATED/FAILED
|
||||||
*/
|
*/
|
||||||
@Getter
|
@Getter
|
||||||
public enum WorkflowInstanceStatus {
|
public enum WorkflowInstanceStatusEnums {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 未开始:节点尚未开始执行
|
* 未开始:节点尚未开始执行
|
||||||
@ -55,7 +55,7 @@ public enum WorkflowInstanceStatus {
|
|||||||
*/
|
*/
|
||||||
private final String description;
|
private final String description;
|
||||||
|
|
||||||
WorkflowInstanceStatus(String code, String description) {
|
WorkflowInstanceStatusEnums(String code, String description) {
|
||||||
this.code = code;
|
this.code = code;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
}
|
}
|
||||||
@ -66,8 +66,8 @@ public enum WorkflowInstanceStatus {
|
|||||||
* @param code 状态编码
|
* @param code 状态编码
|
||||||
* @return 对应的枚举实例
|
* @return 对应的枚举实例
|
||||||
*/
|
*/
|
||||||
public static WorkflowInstanceStatus fromCode(String code) {
|
public static WorkflowInstanceStatusEnums fromCode(String code) {
|
||||||
for (WorkflowInstanceStatus status : WorkflowInstanceStatus.values()) {
|
for (WorkflowInstanceStatusEnums status : WorkflowInstanceStatusEnums.values()) {
|
||||||
if (status.getCode().equals(code)) {
|
if (status.getCode().equals(code)) {
|
||||||
return status;
|
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;
|
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.AllArgsConstructor;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
@ -12,5 +12,5 @@ import lombok.Getter;
|
|||||||
public class ShellLogEvent {
|
public class ShellLogEvent {
|
||||||
private String processInstanceId;
|
private String processInstanceId;
|
||||||
private String logMessage;
|
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.framework.service.IBaseService;
|
||||||
import com.qqchen.deploy.backend.workflow.dto.WorkflowDefinitionDTO;
|
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.WorkflowExecutionDTO;
|
||||||
import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceCreateDTO;
|
import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceCreateDTO;
|
||||||
import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition;
|
import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition;
|
||||||
import org.flowable.engine.runtime.ProcessInstance;
|
import org.flowable.engine.runtime.ProcessInstance;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@ -14,6 +16,8 @@ import java.util.Map;
|
|||||||
*/
|
*/
|
||||||
public interface IWorkflowDefinitionService extends IBaseService<WorkflowDefinition, WorkflowDefinitionDTO, Long> {
|
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
|
* @return 工作流执行状态DTO
|
||||||
*/
|
*/
|
||||||
WorkflowExecutionDTO getWorkflowExecution(String processInstanceId);
|
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.dto.WorkflowInstanceDTO;
|
||||||
import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance;
|
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 org.flowable.engine.runtime.ProcessInstance;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -26,7 +26,7 @@ public interface IWorkflowInstanceService {
|
|||||||
* @param status 新状态
|
* @param status 新状态
|
||||||
* @return 更新后的工作流实例
|
* @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.framework.service.impl.BaseServiceImpl;
|
||||||
import com.qqchen.deploy.backend.workflow.dto.WorkflowDefinitionDTO;
|
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.WorkflowExecutionDTO;
|
||||||
import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceCreateDTO;
|
import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceCreateDTO;
|
||||||
import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition;
|
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.repository.IWorkflowDefinitionRepository;
|
||||||
import com.qqchen.deploy.backend.workflow.service.IWorkflowDefinitionService;
|
import com.qqchen.deploy.backend.workflow.service.IWorkflowDefinitionService;
|
||||||
|
import com.qqchen.deploy.backend.workflow.util.BpmnConverter;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.flowable.bpmn.model.SequenceFlow;
|
import org.flowable.bpmn.model.SequenceFlow;
|
||||||
import org.flowable.bpmn.model.StartEvent;
|
|
||||||
import org.flowable.engine.HistoryService;
|
import org.flowable.engine.HistoryService;
|
||||||
import org.flowable.engine.ManagementService;
|
import org.flowable.engine.ManagementService;
|
||||||
import org.flowable.engine.RepositoryService;
|
import org.flowable.engine.RepositoryService;
|
||||||
@ -19,25 +21,19 @@ import org.flowable.engine.RuntimeService;
|
|||||||
import org.flowable.engine.TaskService;
|
import org.flowable.engine.TaskService;
|
||||||
import org.flowable.engine.history.HistoricActivityInstance;
|
import org.flowable.engine.history.HistoricActivityInstance;
|
||||||
import org.flowable.engine.history.HistoricProcessInstance;
|
import org.flowable.engine.history.HistoricProcessInstance;
|
||||||
import org.flowable.engine.repository.Deployment;
|
|
||||||
import org.flowable.bpmn.model.BpmnModel;
|
import org.flowable.bpmn.model.BpmnModel;
|
||||||
|
import org.flowable.engine.repository.Deployment;
|
||||||
import org.flowable.engine.runtime.Execution;
|
import org.flowable.engine.runtime.Execution;
|
||||||
import org.flowable.engine.runtime.ProcessInstance;
|
import org.flowable.engine.runtime.ProcessInstance;
|
||||||
import org.flowable.bpmn.model.Process;
|
import org.flowable.bpmn.model.Process;
|
||||||
import org.flowable.bpmn.model.FlowElement;
|
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.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@ -47,8 +43,7 @@ import java.util.stream.Collectors;
|
|||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
public class WorkflowDefinitionServiceImpl extends BaseServiceImpl<WorkflowDefinition, WorkflowDefinitionDTO, Long>
|
public class WorkflowDefinitionServiceImpl extends BaseServiceImpl<WorkflowDefinition, WorkflowDefinitionDTO, Long> implements IWorkflowDefinitionService {
|
||||||
implements IWorkflowDefinitionService {
|
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private RepositoryService repositoryService;
|
private RepositoryService repositoryService;
|
||||||
@ -68,6 +63,47 @@ public class WorkflowDefinitionServiceImpl extends BaseServiceImpl<WorkflowDefin
|
|||||||
@Resource
|
@Resource
|
||||||
private IWorkflowDefinitionRepository workflowDefinitionRepository;
|
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
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public WorkflowDefinitionDTO deployWorkflow(WorkflowDefinitionDTO dto) {
|
public WorkflowDefinitionDTO deployWorkflow(WorkflowDefinitionDTO dto) {
|
||||||
@ -206,10 +242,10 @@ public class WorkflowDefinitionServiceImpl extends BaseServiceImpl<WorkflowDefin
|
|||||||
// 设置流程状态
|
// 设置流程状态
|
||||||
if (historicInstance.getEndTime() != null) {
|
if (historicInstance.getEndTime() != null) {
|
||||||
executionDTO.setStatus(historicInstance.getDeleteReason() == null ?
|
executionDTO.setStatus(historicInstance.getDeleteReason() == null ?
|
||||||
WorkflowInstanceStatus.COMPLETED : WorkflowInstanceStatus.FAILED);
|
WorkflowInstanceStatusEnums.COMPLETED : WorkflowInstanceStatusEnums.FAILED);
|
||||||
} else {
|
} else {
|
||||||
executionDTO.setStatus(runningInstance != null && runningInstance.isSuspended() ?
|
executionDTO.setStatus(runningInstance != null && runningInstance.isSuspended() ?
|
||||||
WorkflowInstanceStatus.SUSPENDED : WorkflowInstanceStatus.RUNNING);
|
WorkflowInstanceStatusEnums.SUSPENDED : WorkflowInstanceStatusEnums.RUNNING);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 7. 构建节点状态列表
|
// 7. 构建节点状态列表
|
||||||
@ -224,7 +260,7 @@ public class WorkflowDefinitionServiceImpl extends BaseServiceImpl<WorkflowDefin
|
|||||||
|
|
||||||
// 如果已经有节点失败,后续节点都是未开始
|
// 如果已经有节点失败,后续节点都是未开始
|
||||||
if (hasFailedNode) {
|
if (hasFailedNode) {
|
||||||
stage.setStatus(WorkflowInstanceStatus.NOT_STARTED);
|
stage.setStatus(WorkflowInstanceStatusEnums.NOT_STARTED);
|
||||||
stages.add(stage);
|
stages.add(stage);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -232,7 +268,7 @@ public class WorkflowDefinitionServiceImpl extends BaseServiceImpl<WorkflowDefin
|
|||||||
// 判断节点状态
|
// 判断节点状态
|
||||||
if (runningNodes.contains(element.getId())) {
|
if (runningNodes.contains(element.getId())) {
|
||||||
// 正在执行的节点
|
// 正在执行的节点
|
||||||
stage.setStatus(WorkflowInstanceStatus.RUNNING);
|
stage.setStatus(WorkflowInstanceStatusEnums.RUNNING);
|
||||||
} else {
|
} else {
|
||||||
// 查找历史记录
|
// 查找历史记录
|
||||||
HistoricActivityInstance historicActivity = historicNodes.get(element.getId());
|
HistoricActivityInstance historicActivity = historicNodes.get(element.getId());
|
||||||
@ -243,19 +279,19 @@ public class WorkflowDefinitionServiceImpl extends BaseServiceImpl<WorkflowDefin
|
|||||||
if (historicActivity.getEndTime() != null) {
|
if (historicActivity.getEndTime() != null) {
|
||||||
if (historicActivity.getDeleteReason() != null) {
|
if (historicActivity.getDeleteReason() != null) {
|
||||||
// 节点失败
|
// 节点失败
|
||||||
stage.setStatus(WorkflowInstanceStatus.FAILED);
|
stage.setStatus(WorkflowInstanceStatusEnums.FAILED);
|
||||||
hasFailedNode = true;
|
hasFailedNode = true;
|
||||||
} else {
|
} else {
|
||||||
// 节点完成
|
// 节点完成
|
||||||
stage.setStatus(WorkflowInstanceStatus.COMPLETED);
|
stage.setStatus(WorkflowInstanceStatusEnums.COMPLETED);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 有开始时间但没有结束时间,说明正在运行
|
// 有开始时间但没有结束时间,说明正在运行
|
||||||
stage.setStatus(WorkflowInstanceStatus.RUNNING);
|
stage.setStatus(WorkflowInstanceStatusEnums.RUNNING);
|
||||||
}
|
}
|
||||||
} else {
|
} 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.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceDTO;
|
import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceDTO;
|
||||||
import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance;
|
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.repository.WorkflowInstanceRepository;
|
||||||
import com.qqchen.deploy.backend.workflow.service.IWorkflowInstanceService;
|
import com.qqchen.deploy.backend.workflow.service.IWorkflowInstanceService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@ -37,7 +37,7 @@ public class WorkflowInstanceServiceImpl implements IWorkflowInstanceService {
|
|||||||
instance.setProcessInstanceId(processInstance.getId());
|
instance.setProcessInstanceId(processInstance.getId());
|
||||||
instance.setProcessDefinitionId(Long.valueOf(processInstance.getProcessDefinitionId().split(":")[0]));
|
instance.setProcessDefinitionId(Long.valueOf(processInstance.getProcessDefinitionId().split(":")[0]));
|
||||||
instance.setBusinessKey(processInstance.getBusinessKey());
|
instance.setBusinessKey(processInstance.getBusinessKey());
|
||||||
instance.setStatus(WorkflowInstanceStatus.RUNNING);
|
instance.setStatus(WorkflowInstanceStatusEnums.RUNNING);
|
||||||
instance.setStartTime(LocalDateTime.now());
|
instance.setStartTime(LocalDateTime.now());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -52,12 +52,12 @@ public class WorkflowInstanceServiceImpl implements IWorkflowInstanceService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public WorkflowInstance updateInstanceStatus(String processInstanceId, WorkflowInstanceStatus status) {
|
public WorkflowInstance updateInstanceStatus(String processInstanceId, WorkflowInstanceStatusEnums status) {
|
||||||
WorkflowInstance instance = workflowInstanceRepository.findByProcessInstanceId(processInstanceId)
|
WorkflowInstance instance = workflowInstanceRepository.findByProcessInstanceId(processInstanceId)
|
||||||
.orElseThrow(() -> new RuntimeException("Workflow instance not found: " + processInstanceId));
|
.orElseThrow(() -> new RuntimeException("Workflow instance not found: " + processInstanceId));
|
||||||
|
|
||||||
instance.setStatus(status);
|
instance.setStatus(status);
|
||||||
if (status == WorkflowInstanceStatus.COMPLETED || status == WorkflowInstanceStatus.FAILED) {
|
if (status == WorkflowInstanceStatusEnums.COMPLETED || status == WorkflowInstanceStatusEnums.FAILED) {
|
||||||
instance.setEndTime(LocalDateTime.now());
|
instance.setEndTime(LocalDateTime.now());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,7 +130,7 @@ public class WorkflowInstanceServiceImpl implements IWorkflowInstanceService {
|
|||||||
log.error("Failed to serialize variables", e);
|
log.error("Failed to serialize variables", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
instance.setStatus(WorkflowInstanceStatus.COMPLETED);
|
instance.setStatus(WorkflowInstanceStatusEnums.COMPLETED);
|
||||||
instance.setEndTime(LocalDateTime.now());
|
instance.setEndTime(LocalDateTime.now());
|
||||||
workflowInstanceRepository.save(instance);
|
workflowInstanceRepository.save(instance);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,113 +2,194 @@ package com.qqchen.deploy.backend.workflow.util;
|
|||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
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.BpmnAutoLayout;
|
||||||
|
import org.flowable.bpmn.converter.BpmnXMLConverter;
|
||||||
import org.flowable.bpmn.model.*;
|
import org.flowable.bpmn.model.*;
|
||||||
import org.flowable.bpmn.model.Process;
|
import org.flowable.bpmn.model.Process;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BPMN转换工具类
|
* BPMN转换工具类
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
@Component
|
@Component
|
||||||
public class BpmnConverter {
|
public class BpmnConverter {
|
||||||
|
|
||||||
|
private final Map<BpmnNodeType, BpmnNodeHandler<?>> handlers;
|
||||||
private final ObjectMapper objectMapper;
|
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.objectMapper = objectMapper;
|
||||||
|
this.configParser = configParser;
|
||||||
|
this.bpmnXMLConverter = new BpmnXMLConverter();
|
||||||
|
this.handlers = nodeHandlers.stream()
|
||||||
|
.collect(Collectors.toMap(BpmnNodeHandler::getType, Function.identity()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public String convertToBpmnXml(String x6Json, String processId) throws Exception {
|
||||||
* 将X6 JSON转换为BPMN XML
|
JsonNode jsonNode = objectMapper.readTree(x6Json);
|
||||||
*/
|
|
||||||
public String convertToBpmnXml(JsonNode x6Json, String processId) throws Exception {
|
|
||||||
// 创建BPMN模型
|
// 创建BPMN模型
|
||||||
BpmnModel bpmnModel = new BpmnModel();
|
BpmnModel bpmnModel = new BpmnModel();
|
||||||
Process process = new Process();
|
Process process = new Process();
|
||||||
bpmnModel.addProcess(process);
|
|
||||||
|
|
||||||
process.setId(processId);
|
process.setId(processId);
|
||||||
process.setName(processId);
|
process.setName(processId);
|
||||||
|
process.setExecutable(true); // 设置为可执行
|
||||||
|
bpmnModel.addProcess(process);
|
||||||
|
|
||||||
// 解析节点
|
// 处理节点
|
||||||
Map<String, FlowElement> elementMap = new HashMap<>();
|
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;
|
if (cells != null) {
|
||||||
switch (shape) {
|
// 第一遍:处理所有节点
|
||||||
case "start":
|
for (JsonNode cell : cells) {
|
||||||
|
if (!isNode(cell)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 startEvent = new StartEvent();
|
||||||
startEvent.setId(id);
|
startEvent.setId(id);
|
||||||
startEvent.setName(label);
|
startEvent.setName(label);
|
||||||
element = startEvent;
|
process.addFlowElement(startEvent);
|
||||||
break;
|
elementMap.put(id, startEvent);
|
||||||
case "end":
|
continue;
|
||||||
|
} else if ("end".equals(shape)) {
|
||||||
EndEvent endEvent = new EndEvent();
|
EndEvent endEvent = new EndEvent();
|
||||||
endEvent.setId(id);
|
endEvent.setId(id);
|
||||||
endEvent.setName(label);
|
endEvent.setName(label);
|
||||||
element = endEvent;
|
process.addFlowElement(endEvent);
|
||||||
break;
|
elementMap.put(id, endEvent);
|
||||||
case "userTask":
|
continue;
|
||||||
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 (element != null) {
|
// 解析节点配置
|
||||||
|
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);
|
process.addFlowElement(element);
|
||||||
elementMap.put(id, element);
|
elementMap.put(config.getId(), element);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 解析连线
|
// 确保存在开始事件和结束事件(如果没有显式定义)
|
||||||
|
ensureStartAndEndEvents(process, elementMap);
|
||||||
|
|
||||||
|
// 第二遍:处理所有连线
|
||||||
for (JsonNode cell : cells) {
|
for (JsonNode cell : cells) {
|
||||||
if (cell.has("source") && cell.has("target")) {
|
if (isEdge(cell)) {
|
||||||
String sourceId = cell.get("source").asText();
|
handleSequenceFlow(cell, process, elementMap);
|
||||||
String targetId = cell.get("target").asText();
|
}
|
||||||
String id = cell.get("id").asText();
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 自动布局
|
||||||
|
new BpmnAutoLayout(bpmnModel).execute();
|
||||||
|
|
||||||
|
// 转换为XML
|
||||||
|
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;
|
||||||
|
|
||||||
|
// 遍历所有节点,找到开始和结束事件,以及第一个和最后一个任务节点
|
||||||
|
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 sourceElement = elementMap.get(sourceId);
|
||||||
FlowElement targetElement = elementMap.get(targetId);
|
FlowElement targetElement = elementMap.get(targetId);
|
||||||
@ -118,19 +199,20 @@ public class BpmnConverter {
|
|||||||
sequenceFlow.setId(id);
|
sequenceFlow.setId(id);
|
||||||
sequenceFlow.setSourceRef(sourceId);
|
sequenceFlow.setSourceRef(sourceId);
|
||||||
sequenceFlow.setTargetRef(targetId);
|
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);
|
process.addFlowElement(sequenceFlow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 自动布局
|
|
||||||
new BpmnAutoLayout(bpmnModel).execute();
|
|
||||||
|
|
||||||
// 转换为XML
|
|
||||||
org.flowable.bpmn.converter.BpmnXMLConverter converter = new org.flowable.bpmn.converter.BpmnXMLConverter();
|
|
||||||
byte[] xml = converter.convertToXML(bpmnModel);
|
|
||||||
|
|
||||||
return new String(xml);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -397,6 +397,7 @@ CREATE TABLE workflow_definition (
|
|||||||
flow_version INT NOT NULL COMMENT '流程版本',
|
flow_version INT NOT NULL COMMENT '流程版本',
|
||||||
bpmn_xml TEXT NOT NULL COMMENT 'BPMN XML内容',
|
bpmn_xml TEXT NOT NULL COMMENT 'BPMN XML内容',
|
||||||
graph_json JSON COMMENT 'x6 JSON内容',
|
graph_json JSON COMMENT 'x6 JSON内容',
|
||||||
|
status VARCHAR(32) NOT NULL COMMENT '状态',
|
||||||
description VARCHAR(255) NULL COMMENT '流程描述',
|
description VARCHAR(255) NULL COMMENT '流程描述',
|
||||||
|
|
||||||
CONSTRAINT UK_workflow_definition_key_version UNIQUE (`key`, flow_version)
|
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