Browse Source

仿钉钉流程设计- code review 修改。扩展属性保存在 extensionElement 尝试

jason 1 year ago
parent
commit
9456d461f9

+ 18 - 17
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java

@@ -18,20 +18,17 @@ import java.util.Objects;
 public enum BpmSimpleModelNodeType implements IntArrayValuable {
 
     // TODO @jaosn:-1、0、1、4、-2 是前端已经定义好的么?感觉未来可以考虑搞成和 BPMN 尽量一致的单词哈;类似 usertask 用户审批;
-    // TODO @jason:_NODE 都删除掉哈;
-    START_EVENT_NODE(0, "开始节点"),
-    END_EVENT_NODE(-2, "结束节点"), // TODO @jaosn:挪到 START_EVENT_NODE 后;
-
-    APPROVE_USER_NODE(1, "审批人节点"), // TODO @jaosn:是不是这里从 10 开始好点;相当于说,0-9 给开始和结束;10-19 给各种节点;20-29 给各种条件;TODO @jason:改成 USER_TASK 是不是好点呀
-    // 抄送人节点、对应 BPMN 的 ScriptTask. 使用ScriptTask 原因。好像 ServiceTask 自定义属性不能写入 XML;
-    // TODO @jason:ServiceTask 自定义 xml,有没啥报错信息;
-    SCRIPT_TASK_NODE(2, "抄送人节点"), // TODO @jason:是不是改成 COPY_TASK 好一点哈;
-
-    EXCLUSIVE_GATEWAY_NODE(4, "排他网关"), // TODO @jason:是不是改成叫 条件分支?
-    PARALLEL_GATEWAY_FORK_NODE(5, "并行网关分叉节点"), // TODO @jason:是不是一个 并行分支 ?就可以啦?
-    PARALLEL_GATEWAY_JOIN_NODE(6, "并行网关聚合节点"),
-    INCLUSIVE_GATEWAY_FORK_NODE(7, "包容网关分叉节点"),
-    INCLUSIVE_GATEWAY_JOIN_NODE(8, "包容网关聚合节点"),
+    START_EVENT(0, "开始节点"),
+    END_EVENT(-2, "结束节点"), // TODO @jaosn:挪到 START_EVENT_NODE 后;
+
+    USER_TASK(1, "审批人节点"), // TODO @jaosn:是不是这里从 10 开始好点;相当于说,0-9 给开始和结束;10-19 给各种节点;20-29 给各种条件; TODO 后面改改
+    COPY_TASK(2, "抄送人节点"),
+
+    EXCLUSIVE_GATEWAY(4, "排他网关"), // TODO @jason:是不是改成叫 条件分支?
+    PARALLEL_GATEWAY_FORK(5, "并行网关分叉节点"), // TODO @jason:是不是一个 并行分支 ?就可以啦? 后面是否去掉并行网关。只用包容网关
+    PARALLEL_GATEWAY_JOIN(6, "并行网关聚合节点"),
+    INCLUSIVE_GATEWAY_FORK(7, "包容网关分叉节点"),
+    INCLUSIVE_GATEWAY_JOIN(8, "包容网关聚合节点"),
     ;
 
     public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmSimpleModelNodeType::getType).toArray();
@@ -39,9 +36,13 @@ public enum BpmSimpleModelNodeType implements IntArrayValuable {
     private final Integer type;
     private final String name;
 
-    public static boolean isGatewayNode(Integer type) {
-        return Objects.equals(EXCLUSIVE_GATEWAY_NODE.getType(), type) || Objects.equals(PARALLEL_GATEWAY_FORK_NODE.getType(), type)
-                || Objects.equals(INCLUSIVE_GATEWAY_FORK_NODE.getType(), type) ;
+    /**
+     * 判断是否为分支节点
+     * @param type 节点类型
+     */
+    public static boolean isBranchNode(Integer type) {
+        return Objects.equals(EXCLUSIVE_GATEWAY.getType(), type) || Objects.equals(PARALLEL_GATEWAY_FORK.getType(), type)
+                || Objects.equals(INCLUSIVE_GATEWAY_FORK.getType(), type) ;
     }
 
     public static BpmSimpleModelNodeType valueOf(Integer type) {

+ 15 - 244
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java

@@ -1,43 +1,41 @@
 package cn.iocoder.yudao.module.bpm.framework.flowable.core.util;
 
 import cn.hutool.core.collection.CollUtil;
-import cn.hutool.core.lang.Assert;
-import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.util.ArrayUtil;
-import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
-import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelNodeVO;
-import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
-import org.flowable.bpmn.BpmnAutoLayout;
 import org.flowable.bpmn.converter.BpmnXMLConverter;
 import org.flowable.bpmn.model.Process;
 import org.flowable.bpmn.model.*;
-import org.flowable.common.engine.impl.scripting.ScriptingEngines;
 import org.flowable.common.engine.impl.util.io.BytesStreamSource;
 
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import static org.flowable.bpmn.constants.BpmnXMLConstants.*;
+import java.util.*;
 
 /**
  * 流程模型转操作工具类
  */
 public class BpmnModelUtils {
 
-    public static final String BPMN_SIMPLE_COPY_EXECUTION_SCRIPT = "#{bpmSimpleNodeService.copy(execution)}";
-
     public static Integer parseCandidateStrategy(FlowElement userTask) {
-        return NumberUtils.parseInt(userTask.getAttributeValue(
+        Integer candidateStrategy = NumberUtils.parseInt(userTask.getAttributeValue(
                 BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY));
+        // @芋艿 尝试从 ExtensionElement 取. 后续相关扩展是否都可以 存 extensionElement。 如表单权限。 按钮权限
+        if (candidateStrategy == null) {
+            ExtensionElement element = CollUtil.getFirst(userTask.getExtensionElements().get(BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY));
+            candidateStrategy = NumberUtils.parseInt(Optional.ofNullable(element).map(ExtensionElement::getElementText).orElse(null));
+
+        }
+        return candidateStrategy;
     }
 
     public static String parseCandidateParam(FlowElement userTask) {
-        return userTask.getAttributeValue(
+        String candidateParam =  userTask.getAttributeValue(
                 BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM);
+        if (candidateParam == null) {
+            ExtensionElement element = CollUtil.getFirst(userTask.getExtensionElements().get(BpmnModelConstants.USER_TASK_CANDIDATE_PARAM));
+            candidateParam = Optional.ofNullable(element).map(ExtensionElement::getElementText).orElse(null);
+        }
+        return candidateParam;
     }
 
     /**
@@ -339,231 +337,4 @@ public class BpmnModelUtils {
         }
         return userTaskList;
     }
-
-    // ========== TODO @jason:单独出一个 SimpleModelUtils;定位上,它是 BPMN 的精简模式 ==========
-
-    /**
-     * 仿钉钉流程设计模型数据结构(json) 转换成 Bpmn Model (待完善)
-     *
-     * @param processId       流程标识
-     * @param processName     流程名称
-     * @param simpleModelNode 仿钉钉流程设计模型数据结构
-     * @return Bpmn Model
-     */
-    public static BpmnModel convertSimpleModelToBpmnModel(String processId, String processName, BpmSimpleModelNodeVO simpleModelNode) {
-        BpmnModel bpmnModel = new BpmnModel();
-        Process mainProcess = new Process();
-        mainProcess.setId(processId);
-        mainProcess.setName(processName);
-        mainProcess.setExecutable(Boolean.TRUE);
-        bpmnModel.addProcess(mainProcess);
-        // 前端模型数据结构。 有 start event 节点. 没有 end event 节点。
-        // 添加 FlowNode
-        addBpmnFlowNode(mainProcess, simpleModelNode);
-        // 单独添加 end event 节点
-        addBpmnEndEventNode(mainProcess);
-        // 添加节点之间的连线 Sequence Flow
-        addBpmnSequenceFlow(mainProcess, simpleModelNode, BpmnModelConstants.END_EVENT_ID);
-        // 自动布局
-        new BpmnAutoLayout(bpmnModel).execute();
-        return bpmnModel;
-    }
-
-    private static void addBpmnSequenceFlow(Process mainProcess, BpmSimpleModelNodeVO node, String endId) {
-        // 节点为 null 退出
-        if (node == null || node.getId() == null) {
-            return;
-        }
-        BpmSimpleModelNodeVO childNode = node.getChildNode();
-        // 如果不是网关节点、且后续节点为 null. 添加与结束节点的连线
-        if (!BpmSimpleModelNodeType.isGatewayNode(node.getType()) && (childNode == null || childNode.getId() == null)) {
-            addBpmnSequenceFlowElement(mainProcess, node.getId(), endId, null, null);
-            return;
-        }
-        BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType());
-        Assert.notNull(nodeType, "模型节点类型不支持");
-        // TODO @jason:建议是,addXXX 都改成 buildXXX,构建出一个什么;然后返回之后,让这个方法添加到自己的结果里;
-        switch (nodeType) {
-            case START_EVENT_NODE:
-            case APPROVE_USER_NODE:
-            case SCRIPT_TASK_NODE:
-            case PARALLEL_GATEWAY_JOIN_NODE:
-            case INCLUSIVE_GATEWAY_JOIN_NODE:{
-                addBpmnSequenceFlowElement(mainProcess, node.getId(), childNode.getId(), null, null);
-                // 递归调用后续节点
-                addBpmnSequenceFlow(mainProcess, childNode, endId);
-                break;
-            }
-            case PARALLEL_GATEWAY_FORK_NODE:
-            case EXCLUSIVE_GATEWAY_NODE:
-            case INCLUSIVE_GATEWAY_FORK_NODE:{
-                String gateWayEndId = (childNode == null || childNode.getId() == null) ? BpmnModelConstants.END_EVENT_ID : childNode.getId();
-                List<BpmSimpleModelNodeVO> conditionNodes = node.getConditionNodes();
-                Assert.notEmpty(conditionNodes, "网关节点的条件节点不能为空");
-                for (int i = 0; i < conditionNodes.size(); i++) {
-                    BpmSimpleModelNodeVO item = conditionNodes.get(i);
-                    BpmSimpleModelNodeVO nextNodeOnCondition = item.getChildNode();
-                    if (nextNodeOnCondition != null && nextNodeOnCondition.getId() != null) {
-                        addBpmnSequenceFlowElement(mainProcess, node.getId(), nextNodeOnCondition.getId(),
-                                String.format("%s_SequenceFlow_%d", node.getId(), i + 1), null);
-                        addBpmnSequenceFlow(mainProcess, nextNodeOnCondition, gateWayEndId);
-                    } else {
-                        addBpmnSequenceFlowElement(mainProcess, node.getId(), gateWayEndId,
-                                String.format("%s_SequenceFlow_%d", node.getId(), i + 1), null);
-                    }
-                }
-                // 递归调用后续节点
-                addBpmnSequenceFlow(mainProcess, childNode, endId);
-                break;
-            }
-            default: {
-                // TODO 其它节点类型的实现
-            }
-        }
-
-    }
-
-    private static void addBpmnSequenceFlowElement(Process mainProcess, String sourceId, String targetId, String seqFlowId, String conditionExpression) {
-        SequenceFlow sequenceFlow = new SequenceFlow(sourceId, targetId);
-        if (StrUtil.isNotEmpty(conditionExpression)) {
-            sequenceFlow.setConditionExpression(conditionExpression);
-        }
-        if (StrUtil.isNotEmpty(seqFlowId)) {
-            sequenceFlow.setId(seqFlowId);
-        }
-        mainProcess.addFlowElement(sequenceFlow);
-    }
-
-    private static void addBpmnFlowNode(Process mainProcess, BpmSimpleModelNodeVO simpleModelNode) {
-        // 节点为 null 退出
-        if (simpleModelNode == null || simpleModelNode.getId() == null) {
-            return;
-        }
-        BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(simpleModelNode.getType());
-        Assert.notNull(nodeType, "模型节点类型不支持");
-        switch (nodeType) {
-            case START_EVENT_NODE:
-                addBpmnStartEventNode(mainProcess, simpleModelNode);
-                break;
-            case APPROVE_USER_NODE:
-                addBpmnUserTaskNode(mainProcess, simpleModelNode);
-                break;
-            case SCRIPT_TASK_NODE:
-                addBpmnScriptTaSskNode(mainProcess, simpleModelNode);
-                break;
-            case EXCLUSIVE_GATEWAY_NODE:
-                addBpmnExclusiveGatewayNode(mainProcess, simpleModelNode);
-                break;
-            case PARALLEL_GATEWAY_FORK_NODE:
-            case PARALLEL_GATEWAY_JOIN_NODE:
-                addBpmnParallelGatewayNode(mainProcess, simpleModelNode);
-                break;
-            case INCLUSIVE_GATEWAY_FORK_NODE:
-                addBpmnInclusiveGatewayNode(mainProcess, simpleModelNode, Boolean.TRUE);
-                break;
-            case INCLUSIVE_GATEWAY_JOIN_NODE:
-                addBpmnInclusiveGatewayNode(mainProcess, simpleModelNode, Boolean.FALSE);
-                break;
-            default: {
-                // TODO 其它节点类型的实现
-            }
-        }
-
-        // 如果不是网关类型的接口, 并且chileNode为空退出
-        if (!BpmSimpleModelNodeType.isGatewayNode(simpleModelNode.getType()) && simpleModelNode.getChildNode() == null) {
-            return;
-        }
-
-        // 如果是网关类型接口. 递归添加条件节点
-        if (BpmSimpleModelNodeType.isGatewayNode(simpleModelNode.getType()) && ArrayUtil.isNotEmpty(simpleModelNode.getConditionNodes())) {
-            for (BpmSimpleModelNodeVO node : simpleModelNode.getConditionNodes()) {
-                addBpmnFlowNode(mainProcess, node.getChildNode());
-            }
-        }
-
-        // chileNode不为空,递归添加子节点
-        if (simpleModelNode.getChildNode() != null) {
-            addBpmnFlowNode(mainProcess, simpleModelNode.getChildNode());
-        }
-    }
-
-    private static void addBpmnParallelGatewayNode(Process mainProcess, BpmSimpleModelNodeVO node) {
-        ParallelGateway parallelGateway = new ParallelGateway();
-        parallelGateway.setId(node.getId());
-        mainProcess.addFlowElement(parallelGateway);
-    }
-
-    private static void addBpmnScriptTaSskNode(Process mainProcess, BpmSimpleModelNodeVO node) {
-        ScriptTask scriptTask = new ScriptTask();
-        scriptTask.setId(node.getId());
-        scriptTask.setName(node.getName());
-        // TODO @jason:建议使用 ServiceTask,通过 executionListeners 实现;
-        scriptTask.setScriptFormat(ScriptingEngines.DEFAULT_SCRIPTING_LANGUAGE);
-        scriptTask.setScript(BPMN_SIMPLE_COPY_EXECUTION_SCRIPT);
-        // 添加自定义属性
-        // TODO @jason:可以使用 ServiceTask 搞 ExtensionAttribute 么?
-        addExtensionAttributes(node, scriptTask);
-        mainProcess.addFlowElement(scriptTask);
-    }
-
-    private static void addExtensionAttributes(BpmSimpleModelNodeVO node, FlowElement flowElement) {
-        Integer candidateStrategy = MapUtil.getInt(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY);
-        addExtensionAttributes(flowElement, BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY,
-                candidateStrategy == null ? null : String.valueOf(candidateStrategy));
-        addExtensionAttributes(flowElement, BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM,
-                MapUtil.getStr(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_PARAM));
-    }
-
-    private static void addBpmnExclusiveGatewayNode(Process mainProcess, BpmSimpleModelNodeVO node) {
-        Assert.notEmpty(node.getConditionNodes(), "网关节点的条件节点不能为空");
-        ExclusiveGateway exclusiveGateway = new ExclusiveGateway();
-        exclusiveGateway.setId(node.getId());
-        // 网关的最后一个条件为 网关的 default sequence flow
-        exclusiveGateway.setDefaultFlow(String.format("%s_SequenceFlow_%d", node.getId(), node.getConditionNodes().size()));
-        mainProcess.addFlowElement(exclusiveGateway);
-    }
-
-    private static void addBpmnInclusiveGatewayNode(Process mainProcess, BpmSimpleModelNodeVO node, Boolean isFork) {
-        InclusiveGateway inclusiveGateway = new InclusiveGateway();
-        inclusiveGateway.setId(node.getId());
-        if (isFork) {
-            Assert.notEmpty(node.getConditionNodes(), "网关节点的条件节点不能为空");
-            // 网关的最后一个条件为 网关的 default sequence flow
-            inclusiveGateway.setDefaultFlow(String.format("%s_SequenceFlow_%d", node.getId(), node.getConditionNodes().size()));
-        }
-        mainProcess.addFlowElement(inclusiveGateway);
-    }
-
-    private static void addBpmnEndEventNode(Process mainProcess) {
-        EndEvent endEvent = new EndEvent();
-        endEvent.setId(BpmnModelConstants.END_EVENT_ID);
-        endEvent.setName("结束");
-        mainProcess.addFlowElement(endEvent);
-    }
-
-    private static void addBpmnUserTaskNode(Process mainProcess, BpmSimpleModelNodeVO node) {
-        UserTask userTask = new UserTask();
-        userTask.setId(node.getId());
-        userTask.setName(node.getName());
-        addExtensionAttributes(node, userTask);
-        mainProcess.addFlowElement(userTask);
-    }
-
-    private static void addExtensionAttributes(FlowElement element, String namespace, String name, String value) {
-        if (value == null) {
-            return;
-        }
-        ExtensionAttribute extensionAttribute = new ExtensionAttribute(name, value);
-        extensionAttribute.setNamespace(namespace);
-        extensionAttribute.setNamespacePrefix(FLOWABLE_EXTENSIONS_PREFIX);
-        element.addAttribute(extensionAttribute);
-    }
-
-    private static void addBpmnStartEventNode(Process mainProcess, BpmSimpleModelNodeVO node) {
-        StartEvent startEvent = new StartEvent();
-        startEvent.setId(node.getId());
-        startEvent.setName(node.getName());
-        mainProcess.addFlowElement(startEvent);
-    }
-
 }

+ 270 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java

@@ -0,0 +1,270 @@
+package cn.iocoder.yudao.module.bpm.framework.flowable.core.util;
+
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelNodeVO;
+import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType;
+import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
+import org.flowable.bpmn.BpmnAutoLayout;
+import org.flowable.bpmn.model.*;
+import org.flowable.bpmn.model.Process;
+
+import java.util.List;
+
+import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_NAMESPACE;
+import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_PREFIX;
+
+/**
+ * 仿钉钉快搭模型相关的工具方法
+ *
+ * @author jason
+ */
+public class SimpleModelUtils {
+
+    public static final String BPMN_SIMPLE_COPY_EXECUTION_SCRIPT = "#{bpmSimpleNodeService.copy(execution)}";
+
+    /**
+     * 仿钉钉流程设计模型数据结构(json) 转换成 Bpmn Model (待完善)
+     *
+     * @param processId       流程标识
+     * @param processName     流程名称
+     * @param simpleModelNode 仿钉钉流程设计模型数据结构
+     * @return Bpmn Model
+     */
+    public static BpmnModel convertSimpleModelToBpmnModel(String processId, String processName, BpmSimpleModelNodeVO simpleModelNode) {
+        BpmnModel bpmnModel = new BpmnModel();
+        Process mainProcess = new Process();
+        mainProcess.setId(processId);
+        mainProcess.setName(processName);
+        mainProcess.setExecutable(Boolean.TRUE);
+        bpmnModel.addProcess(mainProcess);
+        // 前端模型数据结构。 有 start event 节点. 没有 end event 节点。
+        // 从 SimpleModel 构建 FlowNode 并添加到 Main Process
+        buildAndAddBpmnFlowNode(simpleModelNode, mainProcess);
+        // 单独构建 end event 节点
+        buildAndAddBpmnEndEvent(mainProcess);
+        // 构建并添加节点之间的连线 Sequence Flow
+        buildAndAddBpmnSequenceFlow(mainProcess, simpleModelNode, BpmnModelConstants.END_EVENT_ID);
+        // 自动布局
+        new BpmnAutoLayout(bpmnModel).execute();
+        return bpmnModel;
+    }
+
+    private static void buildAndAddBpmnSequenceFlow(Process mainProcess, BpmSimpleModelNodeVO node, String targetId) {
+        // 节点为 null 退出
+        if (node == null || node.getId() == null) {
+            return;
+        }
+        BpmSimpleModelNodeVO childNode = node.getChildNode();
+        // 如果不是网关节点、且后续节点为 null. 添加与结束节点的连线
+        if (!BpmSimpleModelNodeType.isBranchNode(node.getType()) && (childNode == null || childNode.getId() == null)) {
+            SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), targetId, null, null);
+            mainProcess.addFlowElement(sequenceFlow);
+            return;
+        }
+        BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType());
+        Assert.notNull(nodeType, "模型节点类型不支持");
+        switch (nodeType) {
+            case START_EVENT:
+            case USER_TASK:
+            case COPY_TASK:
+            case PARALLEL_GATEWAY_JOIN:
+            case INCLUSIVE_GATEWAY_JOIN:{
+                SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), childNode.getId(), null, null);
+                mainProcess.addFlowElement(sequenceFlow);
+                // 递归调用后续节点
+                buildAndAddBpmnSequenceFlow(mainProcess, childNode, targetId);
+                break;
+            }
+            case PARALLEL_GATEWAY_FORK:
+            case EXCLUSIVE_GATEWAY:
+            case INCLUSIVE_GATEWAY_FORK:{
+                String sequenceFlowTargetId = (childNode == null || childNode.getId() == null) ? BpmnModelConstants.END_EVENT_ID : childNode.getId();
+                List<BpmSimpleModelNodeVO> conditionNodes = node.getConditionNodes();
+                Assert.notEmpty(conditionNodes, "网关节点的条件节点不能为空");
+                for (int i = 0; i < conditionNodes.size(); i++) {
+                    BpmSimpleModelNodeVO item = conditionNodes.get(i);
+                    BpmSimpleModelNodeVO nextNodeOnCondition = item.getChildNode();
+                    if (nextNodeOnCondition != null && nextNodeOnCondition.getId() != null) {
+                        SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), nextNodeOnCondition.getId(),
+                                String.format("%s_SequenceFlow_%d", node.getId(), i + 1), null);
+                        mainProcess.addFlowElement(sequenceFlow);
+                        // 递归调用后续节点
+                        buildAndAddBpmnSequenceFlow(mainProcess, nextNodeOnCondition, sequenceFlowTargetId);
+                    } else {
+                        SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), sequenceFlowTargetId,
+                                String.format("%s_SequenceFlow_%d", node.getId(), i + 1), null);
+                        mainProcess.addFlowElement(sequenceFlow);
+                    }
+                }
+                // 递归调用后续节点
+                buildAndAddBpmnSequenceFlow(mainProcess, childNode, targetId);
+                break;
+            }
+            default: {
+                // TODO 其它节点类型的实现
+            }
+        }
+
+    }
+
+    private static SequenceFlow buildBpmnSequenceFlow(String sourceId, String targetId, String seqFlowId, String conditionExpression) {
+        SequenceFlow sequenceFlow = new SequenceFlow(sourceId, targetId);
+        if (StrUtil.isNotEmpty(conditionExpression)) {
+            sequenceFlow.setConditionExpression(conditionExpression);
+        }
+        if (StrUtil.isNotEmpty(seqFlowId)) {
+            sequenceFlow.setId(seqFlowId);
+        }
+        return sequenceFlow;
+    }
+
+    private static void buildAndAddBpmnFlowNode(BpmSimpleModelNodeVO simpleModelNode, Process mainProcess) {
+        // 节点为 null 退出
+        if (simpleModelNode == null || simpleModelNode.getId() == null) {
+            return;
+        }
+        BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(simpleModelNode.getType());
+        Assert.notNull(nodeType, "模型节点类型不支持");
+        switch (nodeType) {
+            case START_EVENT: {
+                StartEvent startEvent = buildBpmnStartEvent(simpleModelNode);
+                mainProcess.addFlowElement(startEvent);
+                break;
+            }
+            case USER_TASK: {
+                UserTask userTask = buildBpmnUserTask(simpleModelNode);
+                mainProcess.addFlowElement(userTask);
+                break;
+            }
+            case COPY_TASK: {
+                ServiceTask serviceTask = buildBpmnServiceTask(simpleModelNode);
+                mainProcess.addFlowElement(serviceTask);
+                break;
+            }
+            case EXCLUSIVE_GATEWAY: {
+                ExclusiveGateway exclusiveGateway = buildBpmnExclusiveGateway(simpleModelNode);
+                mainProcess.addFlowElement(exclusiveGateway);
+                break;
+            }
+            case PARALLEL_GATEWAY_FORK:
+            case PARALLEL_GATEWAY_JOIN: {
+                ParallelGateway parallelGateway = buildBpmnParallelGateway(simpleModelNode);
+                mainProcess.addFlowElement(parallelGateway);
+                break;
+            }
+            case INCLUSIVE_GATEWAY_FORK: {
+                InclusiveGateway inclusiveGateway = buildBpmnInclusiveGateway(simpleModelNode, Boolean.TRUE);
+                mainProcess.addFlowElement(inclusiveGateway);
+                break;
+            }
+            case INCLUSIVE_GATEWAY_JOIN: {
+                InclusiveGateway inclusiveGateway = buildBpmnInclusiveGateway(simpleModelNode, Boolean.FALSE);
+                mainProcess.addFlowElement(inclusiveGateway);
+                break;
+            }
+            default: {
+                // TODO 其它节点类型的实现
+            }
+        }
+
+        // 如果不是网关类型的接口, 并且chileNode为空退出
+        if (!BpmSimpleModelNodeType.isBranchNode(simpleModelNode.getType()) && simpleModelNode.getChildNode() == null) {
+            return;
+        }
+
+        // 如果是网关类型接口. 递归添加条件节点
+        if (BpmSimpleModelNodeType.isBranchNode(simpleModelNode.getType()) && ArrayUtil.isNotEmpty(simpleModelNode.getConditionNodes())) {
+            for (BpmSimpleModelNodeVO node : simpleModelNode.getConditionNodes()) {
+                buildAndAddBpmnFlowNode(node.getChildNode(), mainProcess);
+            }
+        }
+
+        // chileNode不为空,递归添加子节点
+        if (simpleModelNode.getChildNode() != null) {
+            buildAndAddBpmnFlowNode(simpleModelNode.getChildNode(), mainProcess);
+        }
+    }
+
+    private static ParallelGateway buildBpmnParallelGateway(BpmSimpleModelNodeVO node) {
+        ParallelGateway parallelGateway = new ParallelGateway();
+        parallelGateway.setId(node.getId());
+        return parallelGateway;
+    }
+
+    private static ServiceTask buildBpmnServiceTask(BpmSimpleModelNodeVO node) {
+        ServiceTask serviceTask = new ServiceTask();
+        serviceTask.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_EXPRESSION);
+        serviceTask.setImplementation(BPMN_SIMPLE_COPY_EXECUTION_SCRIPT);
+        serviceTask.setId(node.getId());
+        serviceTask.setName(node.getName());
+        // TODO @jason:建议使用 ServiceTask,通过 executionListeners 实现;
+        // @芋艿 ServiceTask 就可以了吧。 不需要 executionListeners
+        addExtensionElement(node, serviceTask);
+        return serviceTask;
+    }
+
+    private static void addExtensionElement(BpmSimpleModelNodeVO node, FlowElement flowElement) {
+        Integer candidateStrategy = MapUtil.getInt(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY);
+        addExtensionElement(flowElement, FLOWABLE_EXTENSIONS_NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY,
+                candidateStrategy == null ? null : String.valueOf(candidateStrategy));
+        addExtensionElement(flowElement, FLOWABLE_EXTENSIONS_NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM,
+                MapUtil.getStr(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_PARAM));
+    }
+
+    private static ExclusiveGateway buildBpmnExclusiveGateway(BpmSimpleModelNodeVO node) {
+        Assert.notEmpty(node.getConditionNodes(), "网关节点的条件节点不能为空");
+        ExclusiveGateway exclusiveGateway = new ExclusiveGateway();
+        exclusiveGateway.setId(node.getId());
+        // 网关的最后一个条件为 网关的 default sequence flow
+        exclusiveGateway.setDefaultFlow(String.format("%s_SequenceFlow_%d", node.getId(), node.getConditionNodes().size()));
+        return exclusiveGateway;
+    }
+
+    private static InclusiveGateway buildBpmnInclusiveGateway(BpmSimpleModelNodeVO node, Boolean isFork) {
+        InclusiveGateway inclusiveGateway = new InclusiveGateway();
+        inclusiveGateway.setId(node.getId());
+        if (isFork) {
+            Assert.notEmpty(node.getConditionNodes(), "网关节点的条件节点不能为空");
+            // 网关的最后一个条件为 网关的 default sequence flow
+            inclusiveGateway.setDefaultFlow(String.format("%s_SequenceFlow_%d", node.getId(), node.getConditionNodes().size()));
+        }
+        return inclusiveGateway;
+    }
+
+    private static void buildAndAddBpmnEndEvent(Process mainProcess) {
+        EndEvent endEvent = new EndEvent();
+        endEvent.setId(BpmnModelConstants.END_EVENT_ID);
+        endEvent.setName("结束");
+        mainProcess.addFlowElement(endEvent);
+    }
+
+    private static UserTask buildBpmnUserTask(BpmSimpleModelNodeVO node) {
+        UserTask userTask = new UserTask();
+        userTask.setId(node.getId());
+        userTask.setName(node.getName());
+        addExtensionElement(node, userTask);
+        return userTask;
+    }
+
+    private static void addExtensionElement(FlowElement element, String namespace, String name, String value) {
+        if (value == null) {
+            return;
+        }
+        ExtensionElement extensionElement = new ExtensionElement();
+        extensionElement.setNamespace(namespace);
+        extensionElement.setNamespacePrefix(FLOWABLE_EXTENSIONS_PREFIX);
+        extensionElement.setElementText(value);
+        extensionElement.setName(name);
+        element.addExtensionElement(extensionElement);
+    }
+
+    private static StartEvent buildBpmnStartEvent(BpmSimpleModelNodeVO node) {
+        StartEvent startEvent = new StartEvent();
+        startEvent.setId(node.getId());
+        startEvent.setName(node.getName());
+        return startEvent;
+    }
+}

+ 10 - 9
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelServiceImpl.java

@@ -10,6 +10,7 @@ import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimp
 import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
+import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils;
 import jakarta.annotation.Resource;
 import org.flowable.bpmn.model.*;
 import org.flowable.engine.repository.Model;
@@ -22,7 +23,7 @@ import java.util.Map;
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.CONVERT_TO_SIMPLE_MODEL_NOT_SUPPORT;
 import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.MODEL_NOT_EXISTS;
-import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.START_EVENT_NODE;
+import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.START_EVENT;
 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.USER_TASK_CANDIDATE_PARAM;
 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY;
 
@@ -56,7 +57,7 @@ public class BpmSimpleModelServiceImpl implements BpmSimpleModelService {
 //            return Boolean.FALSE;
 //        }
         // 1. JSON 转换成 bpmnModel
-        BpmnModel bpmnModel = BpmnModelUtils.convertSimpleModelToBpmnModel(model.getKey(), model.getName(), reqVO.getSimpleModelBody());
+        BpmnModel bpmnModel = SimpleModelUtils.convertSimpleModelToBpmnModel(model.getKey(), model.getName(), reqVO.getSimpleModelBody());
         // 2.1 保存 Bpmn XML
         bpmModelService.saveModelBpmnXml(model.getId(), StrUtil.utf8Bytes(BpmnModelUtils.getBpmnXml(bpmnModel)));
         // 2.2 保存 JSON 数据
@@ -93,7 +94,7 @@ public class BpmSimpleModelServiceImpl implements BpmSimpleModelService {
             return null;
         }
         BpmSimpleModelNodeVO rootNode = new BpmSimpleModelNodeVO();
-        rootNode.setType(START_EVENT_NODE.getType());
+        rootNode.setType(START_EVENT.getType());
         rootNode.setId(startEvent.getId());
         rootNode.setName(startEvent.getName());
         recursiveBuildSimpleModelNode(startEvent, rootNode);
@@ -105,10 +106,10 @@ public class BpmSimpleModelServiceImpl implements BpmSimpleModelService {
         Assert.notNull(nodeType, "节点类型不支持");
         // 校验节点是否支持转仿钉钉的流程模型
         List<SequenceFlow> outgoingFlows = validateCanConvertSimpleNode(nodeType, currentFlowNode);
-        if (CollUtil.isEmpty(outgoingFlows) || outgoingFlows.get(0).getTargetFlowElement() == null) {
+        if (CollUtil.isEmpty(outgoingFlows) || CollUtil.getFirst(outgoingFlows).getTargetFlowElement() == null) {
             return;
         }
-        FlowElement targetElement = outgoingFlows.get(0).getTargetFlowElement();
+        FlowElement targetElement = CollUtil.getFirst(outgoingFlows).getTargetFlowElement();
         // 如果是 EndEvent 直接退出
         if (targetElement instanceof EndEvent) {
             return;
@@ -123,7 +124,7 @@ public class BpmSimpleModelServiceImpl implements BpmSimpleModelService {
 
     private BpmSimpleModelNodeVO convertUserTaskToSimpleModelNode(UserTask userTask) {
         BpmSimpleModelNodeVO simpleModelNodeVO = new BpmSimpleModelNodeVO();
-        simpleModelNodeVO.setType(BpmSimpleModelNodeType.APPROVE_USER_NODE.getType());
+        simpleModelNodeVO.setType(BpmSimpleModelNodeType.USER_TASK.getType());
         simpleModelNodeVO.setName(userTask.getName());
         simpleModelNodeVO.setId(userTask.getId());
         Map<String, Object> attributes = MapUtil.newHashMap();
@@ -137,13 +138,13 @@ public class BpmSimpleModelServiceImpl implements BpmSimpleModelService {
 
     private List<SequenceFlow> validateCanConvertSimpleNode(BpmSimpleModelNodeType nodeType, FlowNode currentFlowNode) {
         switch (nodeType) {
-            case START_EVENT_NODE:
-            case APPROVE_USER_NODE: {
+            case START_EVENT:
+            case USER_TASK: {
                 List<SequenceFlow> outgoingFlows = currentFlowNode.getOutgoingFlows();
                 if (CollUtil.isNotEmpty(outgoingFlows) && outgoingFlows.size() > 1) {
                     throw exception(CONVERT_TO_SIMPLE_MODEL_NOT_SUPPORT);
                 }
-                validIsSupportFlowNode(outgoingFlows.get(0).getTargetFlowElement());
+                validIsSupportFlowNode(CollUtil.getFirst(outgoingFlows).getTargetFlowElement());
                 return outgoingFlows;
             }
             default: {

+ 1 - 1
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java

@@ -47,7 +47,7 @@ public class BpmProcessInstanceCopyServiceImpl implements BpmProcessInstanceCopy
     // TODO @芋艿:这里多加了一个 name;
     @Override
     public void createProcessInstanceCopy(Collection<Long> userIds, String processInstanceId, String taskId, String taskName) {
-        // 1.1 校验任务存在 暂时去掉这个校验. 因为任务可能仿钉钉快搭的抄送节点(ScriptTask) TODO jason:抄送节点,会没有来源的 taskId 么?
+        // 1.1 校验任务存在 暂时去掉这个校验. 因为任务可能仿钉钉快搭的抄送节点(ScriptTask) TODO jason:抄送节点,会没有来源的 taskId 么? @芋艿 是否校验一下 传递进来的 id 不为空就行
 //        Task task = taskService.getTask(taskId);
 //        if (ObjectUtil.isNull(task)) {
 //            throw exception(ErrorCodeConstants.TASK_NOT_EXISTS);

+ 4 - 2
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmSimpleNodeService.java

@@ -9,7 +9,8 @@ import org.springframework.stereotype.Service;
 import java.util.Set;
 
 /**
- * 仿钉钉快搭各个节点 Service TODO @jason:注释要有空行哈;
+ * 仿钉钉快搭各个节点 Service
+ *
  * @author jason
  */
 @Service
@@ -21,7 +22,8 @@ public class BpmSimpleNodeService {
     private BpmProcessInstanceCopyService processInstanceCopyService;
 
     /**
-     * 仿钉钉快搭抄送 TODO @jason:注释要有空行哈;
+     * 仿钉钉快搭抄送
+     *
      * @param execution 执行的任务(ScriptTask)
      */
     public Boolean copy(DelegateExecution execution) {