通用解析

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

View File

@ -102,10 +102,18 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
} }
private String getJwtFromRequest(HttpServletRequest request) { 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;
}
} }

View File

@ -4,11 +4,12 @@ import com.qqchen.deploy.backend.framework.api.Response;
import com.qqchen.deploy.backend.framework.controller.BaseController; import com.qqchen.deploy.backend.framework.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) {

View File

@ -1,40 +0,0 @@
package com.qqchen.deploy.backend.workflow.api;
import com.qqchen.deploy.backend.framework.api.Response;
import com.qqchen.deploy.backend.framework.controller.BaseController;
import com.qqchen.deploy.backend.workflow.dto.WorkflowDesignDTO;
import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition;
import com.qqchen.deploy.backend.workflow.query.WorkflowDefinitionQuery;
import com.qqchen.deploy.backend.workflow.service.IWorkflowDesignService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 工作流设计控制器
*/
@Slf4j
@RestController
@RequestMapping("/api/v1/workflow/design")
@Tag(name = "工作流设计", description = "工作流设计相关接口")
public class WorkflowDesignApiController extends BaseController<WorkflowDefinition, WorkflowDesignDTO, Long, WorkflowDefinitionQuery> {
@Resource
private IWorkflowDesignService workflowDesignService;
@Operation(summary = "保存工作流设计")
@PostMapping("/save")
public Response<WorkflowDesignDTO> saveWorkflowDesign(@RequestBody WorkflowDesignDTO dto) throws Exception {
return Response.success(workflowDesignService.saveWorkflowDesign(dto));
}
@Override
protected void exportData(HttpServletResponse response, List<WorkflowDesignDTO> data) {
// TODO: 实现导出功能
}
}

View File

@ -1,4 +1,4 @@
package com.qqchen.deploy.backend.workflow.controller; package com.qqchen.deploy.backend.workflow.api;
import com.qqchen.deploy.backend.workflow.event.ShellLogEvent; import 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);
} }
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,33 @@
package com.qqchen.deploy.backend.workflow.enums;
import lombok.Getter;
/**
* BPMN节点类型枚举
*/
@Getter
public enum BpmnNodeType {
START("start", "startEvent"),
END("end", "endEvent"),
USER_TASK("userTask", "userTask"),
SERVICE_TASK("serviceTask", "serviceTask"),
SHELL_TASK("shellTask", "serviceTask"),
EXCLUSIVE_GATEWAY("exclusiveGateway", "exclusiveGateway");
private final String shape;
private final String bpmnType;
BpmnNodeType(String shape, String bpmnType) {
this.shape = shape;
this.bpmnType = bpmnType;
}
public static BpmnNodeType fromShape(String shape) {
for (BpmnNodeType type : values()) {
if (type.getShape().equals(shape)) {
return type;
}
}
return null;
}
}

View File

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

View File

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

View File

@ -0,0 +1,21 @@
package com.qqchen.deploy.backend.workflow.enums;
import lombok.Getter;
@Getter
public enum WorkflowStatusEnums {
DRAFT("DRAFT","草稿"),
PUBLISHED("PUBLISHED","已发布"),
DISABLED("DISABLED","已禁用");
private final String code;
private final String description;
WorkflowStatusEnums(String code, String description) {
this.code = code;
this.description = description;
}
}

View File

@ -1,6 +1,6 @@
package com.qqchen.deploy.backend.workflow.event; 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;
} }

View File

@ -0,0 +1,44 @@
package com.qqchen.deploy.backend.workflow.handler;
import com.fasterxml.jackson.databind.JsonNode;
import com.qqchen.deploy.backend.workflow.enums.BpmnNodeType;
import com.qqchen.deploy.backend.workflow.model.BpmnNodeConfig;
import org.flowable.bpmn.model.FlowElement;
/**
* BPMN节点处理器接口
*
* @param <T> 流程元素类型
*/
public interface BpmnNodeHandler<T extends FlowElement> {
/**
* 处理节点数据
*
* @param nodeData JSON节点数据
* @param element 流程元素
* @param config 节点配置
*/
void handle(JsonNode nodeData, T element, BpmnNodeConfig config);
/**
* 创建流程元素
*
* @param config 节点配置
* @return 流程元素
*/
T createElement(BpmnNodeConfig config);
/**
* 获取处理器类型
*
* @return 节点类型
*/
BpmnNodeType getType();
/**
* 获取处理的元素类型
*
* @return 元素类型Class
*/
Class<T> getElementType();
}

View File

@ -0,0 +1,68 @@
package com.qqchen.deploy.backend.workflow.handler.impl;
import com.fasterxml.jackson.databind.JsonNode;
import com.qqchen.deploy.backend.workflow.enums.BpmnNodeType;
import com.qqchen.deploy.backend.workflow.handler.BpmnNodeHandler;
import com.qqchen.deploy.backend.workflow.model.BpmnNodeConfig;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.FieldExtension;
import org.flowable.bpmn.model.ServiceTask;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* Shell任务处理器
*/
@Slf4j
@Component
public class ShellTaskHandler implements BpmnNodeHandler<ServiceTask> {
@Override
public void handle(JsonNode nodeData, ServiceTask element, BpmnNodeConfig config) {
element.setImplementationType("delegateExpression");
element.setImplementation("${shellTaskDelegate}");
element.setAsynchronous(true); // Shell任务默认异步执行
// 从配置中获取字段
Map<String, Object> properties = config.getProperties();
properties.forEach((key, value) -> {
if (value != null) {
addFieldExtension(element, key, value.toString());
}
});
// 处理扩展字段
Map<String, String> extensions = config.getExtensions();
extensions.forEach((key, value) -> {
if (value != null) {
addFieldExtension(element, key, value);
}
});
}
@Override
public ServiceTask createElement(BpmnNodeConfig config) {
ServiceTask task = new ServiceTask();
task.setId(config.getId());
task.setName(config.getName());
return task;
}
@Override
public BpmnNodeType getType() {
return BpmnNodeType.SHELL_TASK;
}
@Override
public Class<ServiceTask> getElementType() {
return ServiceTask.class;
}
private void addFieldExtension(ServiceTask serviceTask, String name, String value) {
FieldExtension field = new FieldExtension();
field.setFieldName(name);
field.setStringValue(value);
serviceTask.getFieldExtensions().add(field);
}
}

View File

@ -0,0 +1,38 @@
package com.qqchen.deploy.backend.workflow.model;
import com.qqchen.deploy.backend.workflow.enums.BpmnNodeType;
import lombok.Data;
import java.util.HashMap;
import java.util.Map;
/**
* BPMN节点配置
*/
@Data
public class BpmnNodeConfig {
/**
* 节点ID
*/
private String id;
/**
* 节点名称
*/
private String name;
/**
* 节点类型
*/
private BpmnNodeType type;
/**
* 节点属性
*/
private Map<String, Object> properties = new HashMap<>();
/**
* 扩展字段
*/
private Map<String, String> extensions = new HashMap<>();
}

View File

@ -0,0 +1,76 @@
package com.qqchen.deploy.backend.workflow.parser;
import com.fasterxml.jackson.databind.JsonNode;
import com.qqchen.deploy.backend.workflow.enums.BpmnNodeType;
import com.qqchen.deploy.backend.workflow.model.BpmnNodeConfig;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
/**
* BPMN节点配置解析器
*/
@Component
public class BpmnNodeConfigParser {
/**
* 解析节点配置
*
* @param node JSON节点数据
* @return 节点配置
*/
public BpmnNodeConfig parse(JsonNode node) {
BpmnNodeConfig config = new BpmnNodeConfig();
config.setId(node.path("id").asText());
config.setName(node.path("data").path("label").asText());
config.setType(BpmnNodeType.fromShape(node.path("shape").asText()));
// 解析属性
JsonNode data = node.path("data");
// 处理通用属性
Map<String, Object> properties = new HashMap<>();
if (data.has("properties")) {
data.path("properties").fields()
.forEachRemaining(entry -> properties.put(
entry.getKey(),
parseValue(entry.getValue())
));
}
// 处理特定任务属性
if (data.has("serviceTask") && data.path("serviceTask").has("fields")) {
JsonNode fields = data.path("serviceTask").path("fields");
fields.fields().forEachRemaining(entry ->
properties.put(entry.getKey(), parseValue(entry.getValue()))
);
}
config.setProperties(properties);
// 解析扩展字段
Map<String, String> extensions = new HashMap<>();
if (data.has("extensions")) {
data.path("extensions").fields()
.forEachRemaining(entry -> extensions.put(
entry.getKey(),
entry.getValue().asText()
));
}
config.setExtensions(extensions);
return config;
}
/**
* 解析JSON值
*/
private Object parseValue(JsonNode value) {
if (value.isTextual()) return value.asText();
if (value.isNumber()) return value.numberValue();
if (value.isBoolean()) return value.asBoolean();
if (value.isObject() || value.isArray()) return value.toString();
return null;
}
}

View File

@ -2,10 +2,12 @@ package com.qqchen.deploy.backend.workflow.service;
import com.qqchen.deploy.backend.framework.service.IBaseService; import com.qqchen.deploy.backend.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);
} }

View File

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

View File

@ -2,7 +2,7 @@ package com.qqchen.deploy.backend.workflow.service;
import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceDTO; import com.qqchen.deploy.backend.workflow.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);
/** /**
* 获取工作流实例详情 * 获取工作流实例详情

View File

@ -2,16 +2,18 @@ package com.qqchen.deploy.backend.workflow.service.impl;
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl; import com.qqchen.deploy.backend.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();
}
} }

View File

@ -1,67 +0,0 @@
package com.qqchen.deploy.backend.workflow.service.impl;
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl;
import com.qqchen.deploy.backend.workflow.dto.WorkflowDesignDTO;
import com.qqchen.deploy.backend.workflow.dto.WorkflowDefinitionDTO;
import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition;
import com.qqchen.deploy.backend.workflow.query.WorkflowDefinitionQuery;
import com.qqchen.deploy.backend.workflow.repository.IWorkflowDefinitionRepository;
import com.qqchen.deploy.backend.workflow.service.IWorkflowDefinitionService;
import com.qqchen.deploy.backend.workflow.service.IWorkflowDesignService;
import com.qqchen.deploy.backend.workflow.util.BpmnConverter;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* 工作流设计服务实现
*/
@Service
public class WorkflowDesignServiceImpl extends BaseServiceImpl<WorkflowDefinition, WorkflowDesignDTO, Long> implements IWorkflowDesignService {
@Resource
private IWorkflowDefinitionService workflowDefinitionService;
@Resource
private IWorkflowDefinitionRepository workflowDefinitionRepository;
@Resource
private BpmnConverter bpmnConverter;
@Override
@Transactional(rollbackFor = Exception.class)
public WorkflowDesignDTO saveWorkflowDesign(WorkflowDesignDTO dto) throws Exception {
// 转换图形JSON为BPMN XML
String bpmnXml = bpmnConverter.convertToBpmnXml(dto.getGraphJson(), dto.getKey());
// 创建工作流定义
WorkflowDefinition definition = new WorkflowDefinition();
definition.setName(dto.getName());
definition.setKey(dto.getKey());
definition.setDescription(dto.getDescription());
definition.setGraphJson(dto.getGraphJson());
definition.setBpmnXml(bpmnXml);
definition.setFlowVersion(1); // 设置初始版本为1
// 保存工作流定义
definition = workflowDefinitionRepository.save(definition);
// 部署工作流
WorkflowDefinitionDTO deployDto = new WorkflowDefinitionDTO();
deployDto.setId(definition.getId());
deployDto.setName(definition.getName());
deployDto.setKey(definition.getKey());
deployDto.setBpmnXml(definition.getBpmnXml());
deployDto.setDescription(definition.getDescription());
workflowDefinitionService.deployWorkflow(deployDto);
// 手动转换为 WorkflowDesignDTO
WorkflowDesignDTO result = new WorkflowDesignDTO();
result.setId(definition.getId());
result.setName(definition.getName());
result.setKey(definition.getKey());
result.setDescription(definition.getDescription());
result.setGraphJson(definition.getGraphJson());
return result;
}
}

View File

@ -4,7 +4,7 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.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);
} }

View File

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

View File

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

View File

@ -0,0 +1,102 @@
package com.qqchen.deploy.backend.workflow.util;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.qqchen.deploy.backend.workflow.handler.impl.ShellTaskHandler;
import com.qqchen.deploy.backend.workflow.parser.BpmnNodeConfigParser;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.ServiceTask;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.core.io.ClassPathResource;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import static org.junit.jupiter.api.Assertions.*;
class BpmnConverterTest {
private BpmnConverter bpmnConverter;
private ObjectMapper objectMapper;
@BeforeEach
void setUp() {
objectMapper = new ObjectMapper();
bpmnConverter = new BpmnConverter(
objectMapper,
Collections.singletonList(new ShellTaskHandler()),
new BpmnNodeConfigParser()
);
}
@Test
void testConvertShellTask() throws Exception {
// 准备测试数据
String json = """
{
"cells": [
{
"id": "start",
"shape": "start",
"data": {
"label": "开始"
}
},
{
"id": "shell1",
"shape": "shellTask",
"data": {
"label": "Shell脚本",
"serviceTask": {
"fields": {
"script": "echo 'Hello World'",
"workDir": "/tmp"
}
}
}
},
{
"id": "flow1",
"shape": "edge",
"source": "start",
"target": "shell1",
"data": {
"label": "流转到Shell"
}
}
]
}
""";
// 执行转换
String xml = bpmnConverter.convertToBpmnXml(json, "test_process");
// 验证结果
assertNotNull(xml);
assertTrue(xml.contains("flowable:delegateExpression=\"${shellTaskDelegate}\""));
assertTrue(xml.contains("<flowable:field name=\"script\">"));
assertTrue(xml.contains("<flowable:field name=\"workDir\">"));
}
@Test
void testConvertComplexProcess() throws Exception {
// 从测试资源文件加载复杂流程JSON
ClassPathResource resource = new ClassPathResource("test-process.json");
String json = new String(resource.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
// 执行转换
String xml = bpmnConverter.convertToBpmnXml(json, "complex_process");
// 验证结果
assertNotNull(xml);
// 验证基本结构
assertTrue(xml.contains("<process id=\"complex_process\""));
// 验证节点
assertTrue(xml.contains("<startEvent"));
assertTrue(xml.contains("<serviceTask"));
assertTrue(xml.contains("<endEvent"));
// 验证连线
assertTrue(xml.contains("<sequenceFlow"));
}
}

View File

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