From 4f58a9216702aea4324acd26c27168a6484c1de3 Mon Sep 17 00:00:00 2001 From: dengqichen Date: Fri, 20 Dec 2024 18:30:52 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8F=8D=E5=BA=8F=E5=88=97=E5=8C=96=E9=97=AE?= =?UTF-8?q?=E9=A2=98=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../workflow/delegate/BaseTaskDelegate.java | 78 ++++ .../workflow/delegate/ShellTaskDelegate.java | 279 ++++++------- .../BaseNodeLocalVariables.java | 8 - .../ScriptNodeLocalVariables.java | 9 + .../backend/workflow/util/BpmnConverter.java | 366 +++++++++++++----- .../backend/workflow/util/BpmnConverter1.java | 299 ++++++++++++++ 6 files changed, 775 insertions(+), 264 deletions(-) create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/delegate/BaseTaskDelegate.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/util/BpmnConverter1.java diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/delegate/BaseTaskDelegate.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/delegate/BaseTaskDelegate.java new file mode 100644 index 00000000..ffba312b --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/delegate/BaseTaskDelegate.java @@ -0,0 +1,78 @@ +package com.qqchen.deploy.backend.workflow.delegate; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.delegate.JavaDelegate; +import org.flowable.common.engine.api.delegate.Expression; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * 任务委派者基类 + * 负责处理panelVariables和localVariables的转换和注入 + * + * @param

Panel变量类型 + * @param Local变量类型 + */ +@Slf4j +public abstract class BaseTaskDelegate implements JavaDelegate { + + @Autowired + private ObjectMapper objectMapper; + + // 字段注入,由Flowable自动设置 + protected Expression panelVariables; + protected Expression localVariables; + + @Override + public void execute(DelegateExecution execution) { + try { + // 获取并转换Panel变量 + P panelVars = null; + if (panelVariables != null) { + String panelVarsJson = panelVariables.getValue(execution).toString(); + JsonNode panelVarsNode = objectMapper.readTree(panelVarsJson); + panelVars = objectMapper.treeToValue(panelVarsNode, getPanelVariablesClass()); + } + + // 获取并转换Local变量 + L localVars = null; + if (localVariables != null) { + String localVarsJson = localVariables.getValue(execution).toString(); + JsonNode localVarsNode = objectMapper.readTree(localVarsJson); + localVars = objectMapper.treeToValue(localVarsNode, getLocalVariablesClass()); + } + + // 执行具体的任务逻辑 + executeInternal(execution, panelVars, localVars); + + } catch (Exception e) { + log.error("Task execution failed", e); + throw new RuntimeException("Task execution failed: " + e.getMessage(), e); + } + } + + /** + * 获取Panel变量的类型 + * + * @return Panel变量的Class对象 + */ + protected abstract Class

getPanelVariablesClass(); + + /** + * 获取Local变量的类型 + * + * @return Local变量的Class对象 + */ + protected abstract Class getLocalVariablesClass(); + + /** + * 执行具体的任务逻辑 + * + * @param execution DelegateExecution对象 + * @param panelVariables 转换后的Panel变量 + * @param localVariables 转换后的Local变量 + */ + protected abstract void executeInternal(DelegateExecution execution, P panelVariables, L localVariables); +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/delegate/ShellTaskDelegate.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/delegate/ShellTaskDelegate.java index c76892c6..f3e9d7c4 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/delegate/ShellTaskDelegate.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/delegate/ShellTaskDelegate.java @@ -1,189 +1,156 @@ package com.qqchen.deploy.backend.workflow.delegate; import com.qqchen.deploy.backend.workflow.constants.WorkFlowConstants; -import com.qqchen.deploy.backend.workflow.event.ShellLogEvent; +import com.qqchen.deploy.backend.workflow.dto.definition.node.localVariables.ScriptNodeLocalVariables; +import com.qqchen.deploy.backend.workflow.dto.definition.node.panelVariables.ScriptNodePanelVariables; import com.qqchen.deploy.backend.workflow.enums.NodeLogTypeEnums; +import com.qqchen.deploy.backend.workflow.event.ShellLogEvent; +import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; -import org.flowable.engine.impl.el.FixedValue; -import org.flowable.engine.RuntimeService; -import org.flowable.engine.ManagementService; import org.flowable.engine.delegate.BpmnError; import org.flowable.engine.delegate.DelegateExecution; -import org.flowable.engine.delegate.JavaDelegate; -import org.flowable.common.engine.api.delegate.Expression; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Component; -import jakarta.annotation.Resource; import org.springframework.util.StringUtils; -import java.io.*; +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.util.Map; -import java.util.concurrent.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; /** - * Shell任务委托执行器 + * Shell脚本任务的委派者实现 */ @Slf4j @Component -public class ShellTaskDelegate implements JavaDelegate { +public class ShellTaskDelegate extends BaseTaskDelegate { @Resource private ApplicationEventPublisher eventPublisher; - @Resource - private RuntimeService runtimeService; - - @Resource - private ManagementService managementService; - - private FixedValue code; // 添加字段 - private FixedValue name; - private FixedValue script; - private FixedValue language; - private FixedValue interpreter; - - private Expression workDir; - - private Expression env; - // 用于存储实时输出的Map private static final Map outputMap = new ConcurrentHashMap<>(); private static final Map errorMap = new ConcurrentHashMap<>(); + @Override + protected Class getPanelVariablesClass() { + return ScriptNodePanelVariables.class; + } @Override - public void execute(DelegateExecution execution) { - log.info("ShellTaskDelegate开始执行, processInstanceId={}, executionId={}", - execution.getProcessInstanceId(), execution.getId()); - // 从字段注入中获取值 - String scriptValue = script != null ? script.getValue(execution).toString() : null; - String workDirValue = workDir != null ? workDir.getValue(execution).toString() : null; - @SuppressWarnings("unchecked") - Map envValue = env != null ? (Map) env.getValue(execution) : null; + protected Class getLocalVariablesClass() { + return ScriptNodeLocalVariables.class; + } - log.info("字段注入的值: script={}, workDir={}, env={}", scriptValue, workDirValue, envValue); - - // 如果流程变量中有值,优先使用流程变量 - if (execution.hasVariable("script")) { - scriptValue = (String) execution.getVariable("script"); -// log.info("从流程变量获取到script={}", scriptValue); - } - if (execution.hasVariable("workDir")) { - workDirValue = (String) execution.getVariable("workDir"); -// log.info("从流程变量获取到workDir={}", workDirValue); - } - if (execution.hasVariable("env")) { - @SuppressWarnings("unchecked") - Map envFromVar = (Map) execution.getVariable("env"); - envValue = envFromVar; -// log.info("从流程变量获取到env={}", envValue); + @Override + protected void executeInternal(DelegateExecution execution, + ScriptNodePanelVariables panelVariables, + ScriptNodeLocalVariables localVariables) { + if (panelVariables == null || panelVariables.getScript() == null) { + throw new BpmnError(WorkFlowConstants.WORKFLOW_EXEC_ERROR, "Script is required but not provided"); } - if (scriptValue == null) { -// log.error("脚本内容为空,执行失败"); - throw new BpmnError(WorkFlowConstants.WORKFLOW_EXEC_ERROR, "Script is required but not provided"); - } - - try { - log.info("准备执行脚本: {}", scriptValue); - // 使用processInstanceId而不是executionId - String processInstanceId = execution.getProcessInstanceId(); - outputMap.put(processInstanceId, new StringBuilder()); - errorMap.put(processInstanceId, new StringBuilder()); - - // 创建进程构建器 - ProcessBuilder processBuilder = new ProcessBuilder(); - - // 根据操作系统选择合适的shell - String os = System.getProperty("os.name").toLowerCase(); - if (os.contains("win")) { - // Windows系统使用cmd - processBuilder.command("cmd", "/c", scriptValue); - } else { - // Unix-like系统使用bash - processBuilder.command("bash", "-c", scriptValue); - } - - // 设置工作目录 - if (StringUtils.hasText(workDirValue)) { - // Windows系统路径处理 - if (os.contains("win")) { - // 确保使用Windows风格的路径分隔符 - workDirValue = workDirValue.replace("/", "\\"); - // 如果路径以\开头,去掉第一个\ - if (workDirValue.startsWith("\\")) { - workDirValue = workDirValue.substring(1); - } - } - File workDirFile = new File(workDirValue); - if (!workDirFile.exists()) { - workDirFile.mkdirs(); - } - processBuilder.directory(workDirFile); - } - - // 设置环境变量 - if (envValue != null) { - processBuilder.environment().putAll(envValue); - } - - // 执行命令 - log.info("执行shell脚本: {}", scriptValue); - Process process = processBuilder.start(); - - // 使用BufferedReader实时读取输出 - BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); - BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream())); - - // 创建线程池处理输出 - ExecutorService executorService = Executors.newFixedThreadPool(2); - - // 处理标准输出 - Future outputFuture = executorService.submit(() -> - processInputStream(process.getInputStream(), processInstanceId, NodeLogTypeEnums.STDOUT)); - - // 处理错误输出 - Future errorFuture = executorService.submit(() -> - processInputStream(process.getErrorStream(), processInstanceId, NodeLogTypeEnums.STDERR)); - - // 等待进程完成 - int exitCode = process.waitFor(); - - // 等待输出处理完成 - outputFuture.get(5, TimeUnit.SECONDS); - errorFuture.get(5, TimeUnit.SECONDS); - - // 关闭线程池 - executorService.shutdown(); - - // 设置最终结果 - StringBuilder finalOutput = outputMap.get(processInstanceId); - StringBuilder finalError = errorMap.get(processInstanceId); - - execution.setVariable("shellOutput", finalOutput.toString()); - execution.setVariable("shellError", finalError.toString()); - execution.setVariable("shellExitCode", exitCode); - - // 清理缓存 - outputMap.remove(processInstanceId); - errorMap.remove(processInstanceId); - - if (exitCode != 0) { - log.error("Shell脚本执行失败,退出码: {}", exitCode); - execution.setVariable("errorDetail", "Shell脚本执行失败,退出码: " + exitCode); - throw new BpmnError(WorkFlowConstants.WORKFLOW_EXEC_ERROR, "Shell脚本执行失败,退出码: " + exitCode); -// throw new RuntimeException("Shell脚本执行失败,退出码: " + exitCode); - } - log.info("Shell脚本执行成功"); - log.debug("脚本输出: {}", finalOutput); - - } catch (Exception e) { - log.error("Shell脚本执行失败", e); -// runtimeService.deleteProcessInstance(execution.getProcessInstanceId(), e.getMessage()); -// throw new FlowableException("任务执行失败: " + e.getMessage(), e); - throw new BpmnError(WorkFlowConstants.WORKFLOW_EXEC_ERROR, e.getMessage()); - } +// try { +// log.info("准备执行脚本: {}", panelVariables.getScript()); +// // 使用processInstanceId而不是executionId +// String processInstanceId = execution.getProcessInstanceId(); +// outputMap.put(processInstanceId, new StringBuilder()); +// errorMap.put(processInstanceId, new StringBuilder()); +// +// // 创建进程构建器 +// ProcessBuilder processBuilder = new ProcessBuilder(); +// +// // 根据操作系统选择合适的shell +// String os = System.getProperty("os.name").toLowerCase(); +// if (os.contains("win")) { +// // Windows系统使用cmd +// processBuilder.command("cmd", "/c", panelVariables.getScript()); +// } else { +// // Unix-like系统使用bash +// processBuilder.command("bash", "-c", panelVariables.getScript()); +// } +// +// // 设置工作目录 +// if (StringUtils.hasText(localVariables.getWorkDir())) { +// // Windows系统路径处理 +// String workDirValue = localVariables.getWorkDir(); +// if (os.contains("win")) { +// // 确保使用Windows风格的路径分隔符 +// workDirValue = workDirValue.replace("/", "\\"); +// // 如果路径以\开头,去掉第一个\ +// if (workDirValue.startsWith("\\")) { +// workDirValue = workDirValue.substring(1); +// } +// } +// File workDirFile = new File(workDirValue); +// if (!workDirFile.exists()) { +// workDirFile.mkdirs(); +// } +// processBuilder.directory(workDirFile); +// } +// +// // 设置环境变量 +// if (localVariables.getEnv() != null) { +// processBuilder.environment().putAll(localVariables.getEnv()); +// } +// +// // 执行命令 +// log.info("执行shell脚本: {}", panelVariables.getScript()); +// Process process = processBuilder.start(); +// +// // 创建线程池处理输出 +// ExecutorService executorService = Executors.newFixedThreadPool(2); +// +// // 处理标准输出 +// Future outputFuture = executorService.submit(() -> +// processInputStream(process.getInputStream(), processInstanceId, NodeLogTypeEnums.STDOUT)); +// +// // 处理错误输出 +// Future errorFuture = executorService.submit(() -> +// processInputStream(process.getErrorStream(), processInstanceId, NodeLogTypeEnums.STDERR)); +// +// // 等待进程完成 +// int exitCode = process.waitFor(); +// +// // 等待输出处理完成 +// outputFuture.get(5, TimeUnit.SECONDS); +// errorFuture.get(5, TimeUnit.SECONDS); +// +// // 关闭线程池 +// executorService.shutdown(); +// +// // 设置最终结果 +// StringBuilder finalOutput = outputMap.get(processInstanceId); +// StringBuilder finalError = errorMap.get(processInstanceId); +// +// execution.setVariable("shellOutput", finalOutput.toString()); +// execution.setVariable("shellError", finalError.toString()); +// execution.setVariable("shellExitCode", exitCode); +// +// // 清理缓存 +// outputMap.remove(processInstanceId); +// errorMap.remove(processInstanceId); +// +// if (exitCode != 0) { +// log.error("Shell脚本执行失败,退出码: {}", exitCode); +// execution.setVariable("errorDetail", "Shell脚本执行失败,退出码: " + exitCode); +// throw new BpmnError(WorkFlowConstants.WORKFLOW_EXEC_ERROR, "Shell脚本执行失败,退出码: " + exitCode); +// } +// log.info("Shell脚本执行成功"); +// log.debug("脚本输出: {}", finalOutput); +// +// } catch (Exception e) { +// log.error("Shell脚本执行失败", e); +// throw new BpmnError(WorkFlowConstants.WORKFLOW_EXEC_ERROR, e.getMessage()); +// } } private void processInputStream(InputStream inputStream, String processInstanceId, NodeLogTypeEnums logType) { @@ -212,6 +179,4 @@ public class ShellTaskDelegate implements JavaDelegate { log.error("Error reading process output", e); } } - - } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/definition/node/localVariables/BaseNodeLocalVariables.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/definition/node/localVariables/BaseNodeLocalVariables.java index 01fc9073..44098776 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/definition/node/localVariables/BaseNodeLocalVariables.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/definition/node/localVariables/BaseNodeLocalVariables.java @@ -9,12 +9,4 @@ import lombok.Data; @Data public class BaseNodeLocalVariables { - @SchemaProperty( - title = "委派者", - description = "委派者", - defaultValue = "com.qqchen.deploy.backend.workflow.delegate.ShellTaskDelegate", - required = true - ) - private String delegate; - } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/definition/node/localVariables/ScriptNodeLocalVariables.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/definition/node/localVariables/ScriptNodeLocalVariables.java index 291d2103..bf60d5c5 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/definition/node/localVariables/ScriptNodeLocalVariables.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/definition/node/localVariables/ScriptNodeLocalVariables.java @@ -1,5 +1,6 @@ package com.qqchen.deploy.backend.workflow.dto.definition.node.localVariables; +import com.qqchen.deploy.backend.workflow.annotation.SchemaProperty; import lombok.Data; import lombok.EqualsAndHashCode; @@ -7,4 +8,12 @@ import lombok.EqualsAndHashCode; @EqualsAndHashCode(callSuper = true) public class ScriptNodeLocalVariables extends BaseNodeLocalVariables { + @SchemaProperty( + title = "委派者", + description = "委派者", + defaultValue = "${shellTaskDelegate}", + required = true + ) + private String delegate; + } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/util/BpmnConverter.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/util/BpmnConverter.java index 3f35a343..65a6d96e 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/util/BpmnConverter.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/util/BpmnConverter.java @@ -41,58 +41,17 @@ public class BpmnConverter { try { log.debug("开始转换工作流定义为XML, processKey: {}", processKey); - // 创建BPMN模型 + // 步骤1:创建和初始化BPMN模型 BpmnModel bpmnModel = new BpmnModel(); - bpmnModel.setTargetNamespace("http://www.flowable.org/test"); - bpmnModel.addError(WorkFlowConstants.WORKFLOW_EXEC_ERROR, WorkFlowConstants.WORKFLOW_EXEC_ERROR); + Process process = createAndInitializeBpmnModel(processKey, bpmnModel); - Process process = new Process(); - // 确保processKey符合NCName规则 - String validProcessKey = processKey.replaceAll("[^a-zA-Z0-9-_.]", "_"); - process.setId(validProcessKey); - process.setName(processKey); // 保持原始processKey作为显示名称 - process.setExecutable(true); - bpmnModel.addProcess(process); + // 步骤2:转换所有节点 + Map idMapping = convertNodes(graph.getNodes(), process); - // 创建节点ID到规范化ID的映射 - Map idMapping = new HashMap<>(); + // 步骤3:转换连线 + convertEdgesToSequenceFlows(graph.getEdges(), idMapping, process); - // 转换节点 - for (WorkflowDefinitionNode node : graph.getNodes()) { - log.debug("转换节点: {}, 类型: {}", node.getNodeName(), node.getNodeCode()); - - // 通过NodeTypeEnums获取对应的BpmnTypeEnums中定义的实例类型 - @SuppressWarnings("unchecked") - Class instanceClass = (Class) NodeTypeEnums.valueOf(node.getNodeCode()) - .getBpmnType() - .getInstance(); - - // 创建节点实例 - FlowElement element = instanceClass.getDeclaredConstructor().newInstance(); - String validId = sanitizeId(node.getId()); - idMapping.put(node.getId(), validId); - element.setId(validId); - element.setName(node.getNodeName()); - - // 设置节点特定属性 - configureFlowElement(element, node, process); - - process.addFlowElement(element); - } - - // 转换连线 - for (WorkflowDefinitionEdge edge : graph.getEdges()) { - log.debug("转换连线: from {} to {}", edge.getFrom(), edge.getTo()); - - SequenceFlow flow = new SequenceFlow(); - flow.setId("FLOW_" + edge.getId().replaceAll("[^a-zA-Z0-9-_.]", "_")); - flow.setName(edge.getName()); - flow.setSourceRef(idMapping.get(edge.getFrom())); - flow.setTargetRef(idMapping.get(edge.getTo())); - process.addFlowElement(flow); - } - - // 自动布局 + // 步骤4:自动布局 new BpmnAutoLayout(bpmnModel).execute(); // 转换为XML @@ -109,6 +68,85 @@ public class BpmnConverter { } } + /** + * 创建并初始化BPMN模型 + * + * @param processKey 流程定义的唯一标识 + * @param bpmnModel BPMN模型对象 + * @return 初始化后的Process对象 + */ + private Process createAndInitializeBpmnModel(String processKey, BpmnModel bpmnModel) { + // 步骤1.1:设置BPMN模型的基本属性 + bpmnModel.setTargetNamespace("http://www.flowable.org/test"); + bpmnModel.addError(WorkFlowConstants.WORKFLOW_EXEC_ERROR, WorkFlowConstants.WORKFLOW_EXEC_ERROR); + + // 步骤1.2:创建并配置Process对象 + Process process = new Process(); + // 确保processKey符合NCName规则 + String validProcessKey = processKey.replaceAll("[^a-zA-Z0-9-_.]", "_"); + process.setId(validProcessKey); + process.setName(processKey); // 保持原始processKey作为显示名称 + process.setExecutable(true); + + // 步骤1.3:将Process添加到BPMN模型中 + bpmnModel.addProcess(process); + + return process; + } + + /** + * 转换工作流节点为BPMN节点 + * + * @param nodes 工作流定义节点列表 + * @param process 流程定义对象 + * @return 节点ID映射关系 + */ + private Map convertNodes(List nodes, Process process) { + // 步骤2.1:创建节点ID映射 + Map idMapping = new HashMap<>(); + + // 步骤2.2:遍历并转换每个节点 + for (WorkflowDefinitionNode node : nodes) { + log.debug("转换节点: {}, 类型: {}", node.getNodeName(), node.getNodeCode()); + convertSingleNode(node, process, idMapping); + } + + return idMapping; + } + + /** + * 转换单个工作流节点为BPMN节点 + * + * @param node 工作流节点定义 + * @param process 流程定义对象 + * @param idMapping 节点ID映射关系 + */ + private void convertSingleNode(WorkflowDefinitionNode node, Process process, Map idMapping) { + try { + // 步骤2.3:获取节点对应的BPMN元素类型 + @SuppressWarnings("unchecked") + Class instanceClass = (Class) NodeTypeEnums.valueOf(node.getNodeCode()) + .getBpmnType() + .getInstance(); + + // 步骤2.4:创建节点实例并设置基本属性 + FlowElement element = instanceClass.getDeclaredConstructor().newInstance(); + String validId = sanitizeId(node.getId()); + idMapping.put(node.getId(), validId); + element.setId(validId); + element.setName(node.getNodeName()); + + // 步骤2.5:配置节点的特定属性 + configureFlowElement(element, node, process); + + // 步骤2.6:将节点添加到流程中 + process.addFlowElement(element); + } catch (Exception e) { + log.error("节点转换失败: {}", node.getNodeName(), e); + throw new RuntimeException("节点转换失败: " + e.getMessage(), e); + } + } + private String sanitizeId(String id) { return "NODE_" + id.replaceAll("[^a-zA-Z0-9-_.]", "_"); } @@ -121,64 +159,157 @@ public class BpmnConverter { * @param process 当前流程 */ private void configureFlowElement(FlowElement element, WorkflowDefinitionNode node, Process process) { - // 为所有节点添加执行监听器 - Map> extensionElements = new HashMap<>(); - List executionListeners = new ArrayList<>(); + // 步骤1:创建基本的扩展元素 + Map> extensionElements = createBaseExtensionElements(); - // 开始事件监听器 - ExtensionElement startListener = createExecutionListener("start", "${globalNodeExecutionListener}"); - executionListeners.add(startListener); - - // 结束事件监听器 - ExtensionElement endListener = createExecutionListener("end", "${globalNodeExecutionListener}"); - executionListeners.add(endListener); - - extensionElements.put("executionListener", executionListeners); - - // 根据节点类型进行特定配置 + // 步骤2:根据节点类型进行特定配置 if (element instanceof ServiceTask) { - ServiceTask serviceTask = (ServiceTask) element; - // 设置委托表达式 -// String delegate = (String) node.getLocalVariables().get("delegate"); -// serviceTask.setImplementationType("delegateExpression"); -// serviceTask.setImplementation(delegate); - - // 配置重试策略 - ExtensionElement retryConfig = new ExtensionElement(); - retryConfig.setName("failedJobRetryTimeCycle"); - retryConfig.setNamespace("http://flowable.org/bpmn"); - retryConfig.setNamespacePrefix("flowable"); - retryConfig.setElementText("R0/PT1H"); // 设置为0次重试 - - List retryElements = new ArrayList<>(); - retryElements.add(retryConfig); - extensionElements.put("failedJobRetryTimeCycle", retryElements); - - // 添加字段注入 -// List fieldExtensions = new ArrayList<>(); -// node.getLocalVariables().forEach((key, value) -> { -// if (value != null && !"delegate".equals(key)) { -// FieldExtension fieldExtension = new FieldExtension(); -// fieldExtension.setFieldName(key); -// fieldExtension.setStringValue(String.valueOf(value)); -// fieldExtensions.add(fieldExtension); -// } -// }); - - // 设置到服务任务 -// serviceTask.setFieldExtensions(fieldExtensions); - serviceTask.setExtensionElements(extensionElements); - - // 添加错误边界事件 - addErrorBoundaryEventHandler(process, serviceTask); + configureServiceTask((ServiceTask) element, node, process, extensionElements); } else if (element instanceof StartEvent || element instanceof EndEvent) { - // 为开始节点和结束节点设置监听器 + // 为开始节点和结束节点设置监��器 element.setExtensionElements(extensionElements); } } /** - * 创建执行监听器扩展元素 + * 创建基本的扩展元素(包含执行监听器) + * + * @return 包含基本扩展元素的Map + */ + private Map> createBaseExtensionElements() { + Map> extensionElements = new HashMap<>(); + List executionListeners = new ArrayList<>(); + + // 添加开始事件监听器 + ExtensionElement startListener = createExecutionListener("start", "${globalNodeExecutionListener}"); + executionListeners.add(startListener); + + // 添加结束事件监听器 + ExtensionElement endListener = createExecutionListener("end", "${globalNodeExecutionListener}"); + executionListeners.add(endListener); + + extensionElements.put("executionListener", executionListeners); + return extensionElements; + } + + /** + * 配置服务任务节点 + * + * @param serviceTask 服务任务节点 + * @param node 工作流节点定义 + * @param process 当前流程 + * @param extensionElements 扩展元素 + */ + private void configureServiceTask(ServiceTask serviceTask, WorkflowDefinitionNode node, Process process, Map> extensionElements) { + if (node.getLocalVariables() != null && node.getLocalVariables().has("delegate")) { + String delegate = node.getLocalVariables().get("delegate").asText(); + serviceTask.setImplementationType("delegateExpression"); + serviceTask.setImplementation(delegate); + } + + addExecutionVariables(extensionElements, node); + addRetryStrategy(extensionElements); + serviceTask.setExtensionElements(extensionElements); + addErrorBoundaryEventHandler(process, serviceTask); + } + + /** + * 创建扩展属性 + * + * @param name 属性名 + * @param value 属性值 + * @return 扩展属性 + */ + private ExtensionAttribute createAttribute(String name, String value) { + ExtensionAttribute attribute = new ExtensionAttribute(); + attribute.setName(name); + attribute.setValue(value); + return attribute; + } + + /** + * 添加执行实例级变量 + * + * @param extensionElements 扩展元素 + * @param node 工作流节点定义 + */ + private void addExecutionVariables(Map> extensionElements, WorkflowDefinitionNode node) { + // 添加panelVariables变量 + if (node.getPanelVariables() != null) { + ExtensionElement fieldElement = new ExtensionElement(); + fieldElement.setName("field"); + fieldElement.setNamespace("http://flowable.org/bpmn"); + fieldElement.setNamespacePrefix("flowable"); + + // 创建field的子元素string + ExtensionElement stringElement = new ExtensionElement(); + stringElement.setName("string"); + stringElement.setNamespace("http://flowable.org/bpmn"); + stringElement.setNamespacePrefix("flowable"); + // 转义JSON内容 + String escapedJson = escapeXml(node.getPanelVariables().toString()); + stringElement.setElementText(escapedJson); + + // 设置field的name属性 + Map> fieldAttributes = new HashMap<>(); + fieldAttributes.put("name", Collections.singletonList(createAttribute("name", "panelVariables"))); + fieldElement.setAttributes(fieldAttributes); + + // 添加string子元素到field + fieldElement.addChildElement(stringElement); + + // 添加field到extensionElements + extensionElements.computeIfAbsent("field", k -> new ArrayList<>()).add(fieldElement); + } + + // 添加localVariables变量 + if (node.getLocalVariables() != null) { + ExtensionElement fieldElement = new ExtensionElement(); + fieldElement.setName("field"); + fieldElement.setNamespace("http://flowable.org/bpmn"); + fieldElement.setNamespacePrefix("flowable"); + + // 创建field的子元素string + ExtensionElement stringElement = new ExtensionElement(); + stringElement.setName("string"); + stringElement.setNamespace("http://flowable.org/bpmn"); + stringElement.setNamespacePrefix("flowable"); + // 转义JSON内容 + String escapedJson = escapeXml(node.getLocalVariables().toString()); + stringElement.setElementText(escapedJson); + + // 设置field的name属性 + Map> fieldAttributes = new HashMap<>(); + fieldAttributes.put("name", Collections.singletonList(createAttribute("name", "localVariables"))); + fieldElement.setAttributes(fieldAttributes); + + // 添加string子元素到field + fieldElement.addChildElement(stringElement); + + // 添加field到extensionElements + extensionElements.computeIfAbsent("field", k -> new ArrayList<>()).add(fieldElement); + } + } + + /** + * 添加重试策略配置 + * + * @param extensionElements 扩展元素 + */ + private void addRetryStrategy(Map> extensionElements) { + ExtensionElement retryConfig = new ExtensionElement(); + retryConfig.setName("failedJobRetryTimeCycle"); + retryConfig.setNamespace("http://flowable.org/bpmn"); + retryConfig.setNamespacePrefix("flowable"); + retryConfig.setElementText("R0/PT1H"); // 设置为0次重试 + + List retryElements = new ArrayList<>(); + retryElements.add(retryConfig); + extensionElements.put("failedJobRetryTimeCycle", retryElements); + } + + /** + * 创建执行监听器扩展��素 * * @param event 事件类型(start/end) * @param delegateExpression 委托表达式 @@ -210,7 +341,7 @@ public class BpmnConverter { } /** - * 为服务任务添加错误边界事件和错误结���事件 + * 为服务任务添加错误边界事件和错误结束事件 * 当服务任务执行失败时,会触发错误边界事件,并流转到错误结束事件 * * @param process BPMN流程定义 @@ -286,4 +417,41 @@ public class BpmnConverter { return errorFlow; } + + /** + * 转换连��为顺序流 + * + * @param edges 工作流定义边集合 + * @param idMapping 节点ID映射 + * @param process 流程定义 + */ + private void convertEdgesToSequenceFlows(List edges, Map idMapping, Process process) { + for (WorkflowDefinitionEdge edge : edges) { + log.debug("转换连线: from {} to {}", edge.getFrom(), edge.getTo()); + + SequenceFlow flow = new SequenceFlow(); + flow.setId("FLOW_" + edge.getId().replaceAll("[^a-zA-Z0-9-_.]", "_")); + flow.setName(edge.getName()); + flow.setSourceRef(idMapping.get(edge.getFrom())); + flow.setTargetRef(idMapping.get(edge.getTo())); + process.addFlowElement(flow); + } + } + + /** + * 转义XML特殊字符 + * + * @param input 需要转义的字符串 + * @return 转义后的字符串 + */ + private String escapeXml(String input) { + if (input == null) { + return null; + } + return input.replace("&", "&") + .replace("<", "<") + .replace(">", ">") + .replace("\"", """) + .replace("'", "'"); + } } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/util/BpmnConverter1.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/util/BpmnConverter1.java new file mode 100644 index 00000000..0164a533 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/util/BpmnConverter1.java @@ -0,0 +1,299 @@ +package com.qqchen.deploy.backend.workflow.util; + +import com.qqchen.deploy.backend.workflow.constants.WorkFlowConstants; +import com.qqchen.deploy.backend.workflow.dto.definition.workflow.WorkflowDefinitionEdge; +import com.qqchen.deploy.backend.workflow.dto.definition.workflow.WorkflowDefinitionGraph; +import com.qqchen.deploy.backend.workflow.dto.definition.workflow.WorkflowDefinitionNode; +import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnums; +import lombok.extern.slf4j.Slf4j; +import org.flowable.bpmn.BpmnAutoLayout; +import org.flowable.bpmn.converter.BpmnXMLConverter; +import org.flowable.bpmn.model.BoundaryEvent; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.bpmn.model.EndEvent; +import org.flowable.bpmn.model.ErrorEventDefinition; +import org.flowable.bpmn.model.ExtensionAttribute; +import org.flowable.bpmn.model.ExtensionElement; +import org.flowable.bpmn.model.FlowElement; +import org.flowable.bpmn.model.Process; +import org.flowable.bpmn.model.SequenceFlow; +import org.flowable.bpmn.model.ServiceTask; +import org.flowable.bpmn.model.StartEvent; +import org.flowable.bpmn.model.TerminateEventDefinition; +import org.springframework.stereotype.Component; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * BPMN 模型转换工具 + * + * @author cascade + * @date 2024-12-11 + */ +@Slf4j +@Component +public class BpmnConverter1 { + + /** + * 将工作流定义图转换为Flowable XML + * + * @param graph 工作流定义图 + * @param processKey 流程定义的唯一标识 + * @return Flowable XML字符串 + * @throws RuntimeException 当转换失败时抛出 + */ + public String convertToXml(WorkflowDefinitionGraph graph, String processKey) { + try { + log.debug("开始转换工作流定义为XML, processKey: {}", processKey); + + // 创建BPMN模型 + BpmnModel bpmnModel = new BpmnModel(); + bpmnModel.setTargetNamespace("http://www.flowable.org/test"); + bpmnModel.addError(WorkFlowConstants.WORKFLOW_EXEC_ERROR, WorkFlowConstants.WORKFLOW_EXEC_ERROR); + + Process process = new Process(); + // 确保processKey符合NCName规则 + String validProcessKey = processKey.replaceAll("[^a-zA-Z0-9-_.]", "_"); + process.setId(validProcessKey); + process.setName(processKey); // 保持原始processKey作为显示名称 + process.setExecutable(true); + bpmnModel.addProcess(process); + + // 创建节点ID到规范化ID的映射 + Map idMapping = new HashMap<>(); + + // 转换节点 + for (WorkflowDefinitionNode node : graph.getNodes()) { + log.debug("转换节点: {}, 类型: {}", node.getNodeName(), node.getNodeCode()); + + // 通过NodeTypeEnums获取对应的BpmnTypeEnums中定义的实例类型 + @SuppressWarnings("unchecked") + Class instanceClass = (Class) NodeTypeEnums.valueOf(node.getNodeCode()) + .getBpmnType() + .getInstance(); + + // 创建节点实例 + FlowElement element = instanceClass.getDeclaredConstructor().newInstance(); + String validId = sanitizeId(node.getId()); + idMapping.put(node.getId(), validId); + element.setId(validId); + element.setName(node.getNodeName()); + + // 设置节点特定属性 + configureFlowElement(element, node, process); + + process.addFlowElement(element); + } + + // 转换连线 + for (WorkflowDefinitionEdge edge : graph.getEdges()) { + log.debug("转换连线: from {} to {}", edge.getFrom(), edge.getTo()); + + SequenceFlow flow = new SequenceFlow(); + flow.setId("FLOW_" + edge.getId().replaceAll("[^a-zA-Z0-9-_.]", "_")); + flow.setName(edge.getName()); + flow.setSourceRef(idMapping.get(edge.getFrom())); + flow.setTargetRef(idMapping.get(edge.getTo())); + process.addFlowElement(flow); + } + + // 自动布局 + new BpmnAutoLayout(bpmnModel).execute(); + + // 转换为XML + BpmnXMLConverter converter = new BpmnXMLConverter(); + byte[] bytes = converter.convertToXML(bpmnModel); + String xml = new String(bytes, StandardCharsets.UTF_8); + + log.debug("工作流定义转换完成"); + return xml; + + } catch (Exception e) { + log.error("转换工作流定义为XML失败", e); + throw new RuntimeException("转换工作流定义为XML失败: " + e.getMessage(), e); + } + } + + private String sanitizeId(String id) { + return "NODE_" + id.replaceAll("[^a-zA-Z0-9-_.]", "_"); + } + + /** + * 配置流程节点的特定属性 + * + * @param element 流程节点元素 + * @param node 工作流节点定义 + * @param process 当前流程 + */ + private void configureFlowElement(FlowElement element, WorkflowDefinitionNode node, Process process) { + // 为所有节点添加执行监听器 + Map> extensionElements = new HashMap<>(); + List executionListeners = new ArrayList<>(); + + // 开始事件监听器 + ExtensionElement startListener = createExecutionListener("start", "${globalNodeExecutionListener}"); + executionListeners.add(startListener); + + // 结束事件监听器 + ExtensionElement endListener = createExecutionListener("end", "${globalNodeExecutionListener}"); + executionListeners.add(endListener); + + extensionElements.put("executionListener", executionListeners); + + // 根据节点类型进行特定配置 + if (element instanceof ServiceTask) { + ServiceTask serviceTask = (ServiceTask) element; + // 设置委托表达式 +// String delegate = (String) node.getLocalVariables().get("delegate"); +// serviceTask.setImplementationType("delegateExpression"); +// serviceTask.setImplementation(delegate); + + // 配置重试策略 + ExtensionElement retryConfig = new ExtensionElement(); + retryConfig.setName("failedJobRetryTimeCycle"); + retryConfig.setNamespace("http://flowable.org/bpmn"); + retryConfig.setNamespacePrefix("flowable"); + retryConfig.setElementText("R0/PT1H"); // 设置为0次重试 + + List retryElements = new ArrayList<>(); + retryElements.add(retryConfig); + extensionElements.put("failedJobRetryTimeCycle", retryElements); + + // 添加字段注入 +// List fieldExtensions = new ArrayList<>(); +// node.getLocalVariables().forEach((key, value) -> { +// if (value != null && !"delegate".equals(key)) { +// FieldExtension fieldExtension = new FieldExtension(); +// fieldExtension.setFieldName(key); +// fieldExtension.setStringValue(String.valueOf(value)); +// fieldExtensions.add(fieldExtension); +// } +// }); + + // 设置到服务任务 +// serviceTask.setFieldExtensions(fieldExtensions); + serviceTask.setExtensionElements(extensionElements); + + // 添加错误边界事件 + addErrorBoundaryEventHandler(process, serviceTask); + } else if (element instanceof StartEvent || element instanceof EndEvent) { + // 为开始节点和结束节点设置监听器 + element.setExtensionElements(extensionElements); + } + } + + /** + * 创建执行监听器扩展元素 + * + * @param event 事件类型(start/end) + * @param delegateExpression 委托表达式 + * @return 配置好的监听器扩展元素 + */ + private ExtensionElement createExecutionListener(String event, String delegateExpression) { + ExtensionElement listener = new ExtensionElement(); + listener.setName("executionListener"); + listener.setNamespace("http://flowable.org/bpmn"); + listener.setNamespacePrefix("flowable"); + + // 设置事件属性 + ExtensionAttribute eventAttr = new ExtensionAttribute(); + eventAttr.setName("event"); + eventAttr.setValue(event); + + // 设置委托表达式属性 + ExtensionAttribute delegateAttr = new ExtensionAttribute(); + delegateAttr.setName("delegateExpression"); + delegateAttr.setValue(delegateExpression); + + // 添加属性到监听器 + Map> attributes = new HashMap<>(); + attributes.put("event", Collections.singletonList(eventAttr)); + attributes.put("delegateExpression", Collections.singletonList(delegateAttr)); + listener.setAttributes(attributes); + + return listener; + } + + /** + * 为服务任务添加错误边界事件和错误结���事件 + * 当服务任务执行失败时,会触发错误边界事件,并流转到错误结束事件 + * + * @param process BPMN流程定义 + * @param serviceTask 需要添加错误处理的服务任务 + */ + private void addErrorBoundaryEventHandler(Process process, ServiceTask serviceTask) { + BoundaryEvent boundaryEvent = createErrorBoundaryEvent(serviceTask); + EndEvent errorEndEvent = createErrorEndEvent(serviceTask); + SequenceFlow errorFlow = createErrorSequenceFlow(boundaryEvent, errorEndEvent); + + // 将错误处理相关的元素添加到流程中 + process.addFlowElement(boundaryEvent); + process.addFlowElement(errorEndEvent); + process.addFlowElement(errorFlow); + } + + /** + * 创建错误边界事件 + * + * @param serviceTask 关联的服务任务 + * @return 配置好的错误边界事件 + */ + private BoundaryEvent createErrorBoundaryEvent(ServiceTask serviceTask) { + BoundaryEvent boundaryEvent = new BoundaryEvent(); + boundaryEvent.setId(WorkFlowConstants.BOUNDARY_EVENT_ERROR_PREFIX + serviceTask.getId()); + boundaryEvent.setName("边界事件异常"); + boundaryEvent.setAttachedToRef(serviceTask); + boundaryEvent.setAttachedToRefId(serviceTask.getId()); + boundaryEvent.setCancelActivity(true); // 确保取消原有活动 + + // 配置错误事件定义 + ErrorEventDefinition errorEventDefinition = new ErrorEventDefinition(); + errorEventDefinition.setErrorCode(WorkFlowConstants.WORKFLOW_EXEC_ERROR); + boundaryEvent.addEventDefinition(errorEventDefinition); + + return boundaryEvent; + } + + /** + * 创建错误结束事件 + * + * @param serviceTask 关联的服务任务 + * @return 配置好的错误结束事件 + */ + private EndEvent createErrorEndEvent(ServiceTask serviceTask) { + EndEvent errorEndEvent = new EndEvent(); + errorEndEvent.setId(WorkFlowConstants.END_EVENT_ERROR_PREFIX + serviceTask.getId()); + errorEndEvent.setName("结束事件异常"); + // 添加终止定义 + TerminateEventDefinition terminateEventDefinition = new TerminateEventDefinition(); + errorEndEvent.addEventDefinition(terminateEventDefinition); + +// ErrorEventDefinition errorEventDefinition = new ErrorEventDefinition(); +// errorEventDefinition.setErrorCode(WorkFlowConstants.WORKFLOW_EXEC_ERROR); +// errorEndEvent.addEventDefinition(errorEventDefinition); + + return errorEndEvent; + } + + /** + * 创建错误处理流程的连线 + * + * @param boundaryEvent 错误边界事件 + * @param errorEndEvent 错误结束事件 + * @return 配置好的连线 + */ + private SequenceFlow createErrorSequenceFlow(BoundaryEvent boundaryEvent, EndEvent errorEndEvent) { + SequenceFlow errorFlow = new SequenceFlow(); + errorFlow.setId(WorkFlowConstants.SEQUENCE_FLOW_ERROR_PREFIX + boundaryEvent.getAttachedToRefId()); + errorFlow.setName("连接线异常"); + errorFlow.setSourceRef(boundaryEvent.getId()); + errorFlow.setTargetRef(errorEndEvent.getId()); + + return errorFlow; + } +}