فهرست منبع

仿钉钉流程设计-后端实现30%

jason 1 سال پیش
والد
کامیت
b504d9841e

+ 2 - 0
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java

@@ -75,4 +75,6 @@ public interface ErrorCodeConstants {
     // ========== BPM 流程表达式 1-009-014-000 ==========
     ErrorCode PROCESS_EXPRESSION_NOT_EXISTS = new ErrorCode(1_009_014_000, "流程表达式不存在");
 
+    // ========== BPM 仿钉钉流程设计器 1-009-015-000 ==========
+    ErrorCode CONVERT_TO_SIMPLE_MODEL_NOT_SUPPORT = new ErrorCode(1_009_015_000, "该流程模型不支持仿钉钉设计流程");
 }

+ 11 - 4
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmSimpleModelController.java

@@ -1,17 +1,16 @@
 package cn.iocoder.yudao.module.bpm.controller.admin.definition;
 
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelNodeVO;
 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelSaveReqVO;
 import cn.iocoder.yudao.module.bpm.service.definition.BpmSimpleModelService;
 import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import jakarta.annotation.Resource;
 import jakarta.validation.Valid;
 import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
 
@@ -28,4 +27,12 @@ public class BpmSimpleModelController {
     public CommonResult<Boolean> saveSimpleModel(@Valid @RequestBody BpmSimpleModelSaveReqVO reqVO) {
         return success(bpmSimpleModelService.saveSimpleModel(reqVO));
     }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得仿钉钉流程设计模型")
+    @Parameter(name = "modelId", description = "流程模型编号", required = true, example = "a2c5eee0-eb6c-11ee-abf4-0c37967c420a")
+    public CommonResult<BpmSimpleModelNodeVO> getSimpleModel(@RequestParam("modelId") String modelId){
+        return success(bpmSimpleModelService.getSimpleModel(modelId));
+    }
+
 }

+ 9 - 4
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java

@@ -1,5 +1,10 @@
 package cn.iocoder.yudao.module.bpm.framework.flowable.core.enums;
 
+import com.google.common.collect.ImmutableSet;
+import org.flowable.bpmn.model.*;
+
+import java.util.Set;
+
 /**
  * BPMN XML 常量信息
  *
@@ -24,13 +29,13 @@ public interface BpmnModelConstants {
     String USER_TASK_CANDIDATE_PARAM = "candidateParam";
 
     /**
-     * BPMN Start Event 节点 Id, 用于后端生成 Start Event 节点
+     * BPMN End Event 节点 Id, 用于后端生成 End Event 节点
      */
-    String START_EVENT_ID = "StartEvent_1";
+    String END_EVENT_ID = "EndEvent_1";
 
     /**
-     * BPMN End Event 节点 Id, 用于后端生成 End Event 节点
+     * 支持转仿钉钉设计模型的 Bpmn 节点
      */
-    String END_EVENT_ID = "EndEvent_1";
+    Set<Class<? extends FlowNode>> SUPPORT_CONVERT_SIMPLE_FlOW_NODES = ImmutableSet.of(UserTask.class,  EndEvent.class);
 
 }

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

@@ -2,7 +2,6 @@ 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.lang.TypeReference;
 import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.util.ArrayUtil;
 import cn.hutool.core.util.StrUtil;
@@ -338,31 +337,26 @@ public class BpmnModelUtils {
 
     /**
      * 仿钉钉流程设计模型数据结构(json) 转换成 Bpmn Model (待完善)
-     * @param processId  流程标识
-     * @param processName 流程名称
+     *
+     * @param processId       流程标识
+     * @param processName     流程名称
      * @param simpleModelNode 仿钉钉流程设计模型数据结构
      * @return Bpmn Model
      */
-    public static BpmnModel convertSimpleModelToBpmnModel(String processId, String processName,BpmSimpleModelNodeVO simpleModelNode) {
+    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 节点。
-        // 在最前面加上 start event node
-        BpmSimpleModelNodeVO startEventNode = new BpmSimpleModelNodeVO();
-        startEventNode.setId(BpmnModelConstants.START_EVENT_ID);
-        startEventNode.setName("开始");
-        startEventNode.setType(BpmSimpleModelNodeType.START_NODE.getType());
-        startEventNode.setChildNode(simpleModelNode);
+        // 前端模型数据结构。 有 start event 节点. 没有 end event 节点。
         // 添加 FlowNode
-        addBpmnFlowNode(mainProcess, startEventNode);
+        addBpmnFlowNode(mainProcess, simpleModelNode);
         // 单独添加 end event 节点
         addBpmnEndEventNode(mainProcess);
         // 添加节点之间的连线 Sequence Flow
-        addBpmnSequenceFlow(mainProcess, startEventNode);
+        addBpmnSequenceFlow(mainProcess, simpleModelNode);
         // 自动布局
         new BpmnAutoLayout(bpmnModel).execute();
 
@@ -371,24 +365,24 @@ public class BpmnModelUtils {
 
     private static void addBpmnSequenceFlow(Process mainProcess, BpmSimpleModelNodeVO node) {
         // 节点为 null 退出
-        if (node == null) {
+        if (node == null || node.getId() == null) {
             return;
         }
         BpmSimpleModelNodeVO childNode = node.getChildNode();
         // 如果后续节点为 null. 添加与结束节点的连线
-        if (childNode == null) {
-            addBpmnSequenceFlowElement(mainProcess, node.getId(),BpmnModelConstants.END_EVENT_ID, null);
+        if (childNode == null || childNode.getId() == null) {
+            addBpmnSequenceFlowElement(mainProcess, node.getId(), BpmnModelConstants.END_EVENT_ID, null);
             return;
         }
         BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType());
         Assert.notNull(nodeType, "模型节点类型不支持");
-        switch(nodeType){
-            case START_NODE :
-            case START_USER_NODE :
-            case APPROVE_USER_NODE :
+        switch (nodeType) {
+            case START_NODE:
+            case START_USER_NODE:
+            case APPROVE_USER_NODE:
                 addBpmnSequenceFlowElement(mainProcess, node.getId(), childNode.getId(), null);
                 break;
-            default : {
+            default: {
                 // TODO 其它节点类型的实现
             }
         }
@@ -396,7 +390,7 @@ public class BpmnModelUtils {
         addBpmnSequenceFlow(mainProcess, childNode);
     }
 
-    private static void addBpmnSequenceFlowElement(Process mainProcess,  String sourceId,  String targetId, String conditionExpression) {
+    private static void addBpmnSequenceFlowElement(Process mainProcess, String sourceId, String targetId, String conditionExpression) {
         SequenceFlow sequenceFlow = new SequenceFlow(sourceId, targetId);
         if (StrUtil.isNotEmpty(conditionExpression)) {
             sequenceFlow.setConditionExpression(conditionExpression);
@@ -407,20 +401,20 @@ public class BpmnModelUtils {
 
     private static void addBpmnFlowNode(Process mainProcess, BpmSimpleModelNodeVO simpleModelNode) {
         // 节点为 null 退出
-        if (simpleModelNode == null) {
+        if (simpleModelNode == null || simpleModelNode.getId() == null) {
             return;
         }
         BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(simpleModelNode.getType());
         Assert.notNull(nodeType, "模型节点类型不支持");
-        switch(nodeType){
-            case START_NODE :
+        switch (nodeType) {
+            case START_NODE:
                 addBpmnStartEventNode(mainProcess, simpleModelNode);
                 break;
-            case START_USER_NODE :
-            case APPROVE_USER_NODE :
+            case START_USER_NODE:
+            case APPROVE_USER_NODE:
                 addBpmnUserTaskEventNode(mainProcess, simpleModelNode);
                 break;
-            default : {
+            default: {
                 // TODO 其它节点类型的实现
             }
         }
@@ -454,17 +448,16 @@ public class BpmnModelUtils {
         UserTask userTask = new UserTask();
         userTask.setId(node.getId());
         userTask.setName(node.getName());
-        Integer candidateStrategy =  MapUtil.getInt(node.getAttributes(),BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY);
-        List<Integer> candidateParam = MapUtil.get(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_PARAM, new TypeReference<>() {});
+        Integer candidateStrategy = MapUtil.getInt(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY);
         // 添加自定义属性
         addExtensionAttribute(userTask, BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY,
                 candidateStrategy == null ? null : String.valueOf(candidateStrategy));
         addExtensionAttribute(userTask, BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM,
-                CollUtil.join(candidateParam, ","));
+                MapUtil.getStr(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_PARAM));
         mainProcess.addFlowElement(userTask);
     }
 
-    private static void addExtensionAttribute(FlowElement element, String namespace, String name,  String value) {
+    private static void addExtensionAttribute(FlowElement element, String namespace, String name, String value) {
         if (value == null) {
             return;
         }

+ 8 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelService.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.bpm.service.definition;
 
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelNodeVO;
 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelSaveReqVO;
 import jakarta.validation.Valid;
 
@@ -15,4 +16,11 @@ public interface BpmSimpleModelService {
      * @param reqVO 请求信息
      */
     Boolean saveSimpleModel(@Valid  BpmSimpleModelSaveReqVO reqVO);
+
+    /**
+     * 获取仿钉钉流程设计模型结构
+     * @param modelId 流程模型编号
+     * @return 仿钉钉流程设计模型结构
+     */
+    BpmSimpleModelNodeVO getSimpleModel(String modelId);
 }

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

@@ -1,16 +1,28 @@
 package cn.iocoder.yudao.module.bpm.service.definition;
 
-import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.map.MapUtil;
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelNodeVO;
 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelSaveReqVO;
+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 jakarta.annotation.Resource;
-import org.flowable.bpmn.model.BpmnModel;
+import org.flowable.bpmn.model.*;
 import org.flowable.engine.repository.Model;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
+import java.util.List;
+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_NODE;
+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;
 
 /**
  * 仿钉钉流程设计 Service 实现类
@@ -29,16 +41,124 @@ public class BpmSimpleModelServiceImpl implements BpmSimpleModelService {
         if (model == null) {
             throw exception(MODEL_NOT_EXISTS);
         }
-        byte[] bpmnBytes = bpmModelService.getModelBpmnXML(reqVO.getModelId());
+//        byte[] bpmnBytes = bpmModelService.getModelBpmnXML(reqVO.getModelId());
+//        if (ArrayUtil.isEmpty(bpmnBytes)) {
+//            //  BPMN XML 不存在。新增
+//            BpmnModel bpmnModel = BpmnModelUtils.convertSimpleModelToBpmnModel(model.getKey(), model.getName(), reqVO.getSimpleModelBody());
+//            bpmModelService.saveModelBpmnXml(model.getId(), BpmnModelUtils.getBpmnXml(bpmnModel));
+//            return Boolean.TRUE;
+//        } else {
+//            // TODO BPMN XML 已经存在。如何修改 ??
+//            return Boolean.FALSE;
+//        }
+        // 暂时直接修改
+        BpmnModel bpmnModel = BpmnModelUtils.convertSimpleModelToBpmnModel(model.getKey(), model.getName(), reqVO.getSimpleModelBody());
+        bpmModelService.saveModelBpmnXml(model.getId(), BpmnModelUtils.getBpmnXml(bpmnModel));
+        return Boolean.TRUE;
+    }
+
+    @Override
+    public BpmSimpleModelNodeVO getSimpleModel(String modelId) {
+        Model model = bpmModelService.getModel(modelId);
+        if (model == null) {
+            throw exception(MODEL_NOT_EXISTS);
+        }
+        byte[] bpmnBytes = bpmModelService.getModelBpmnXML(modelId);
+        BpmnModel bpmnModel = BpmnModelUtils.getBpmnModel(bpmnBytes);
+        return convertBpmnModelToSimpleModel(bpmnModel);
+
+    }
+
+    /**
+     * Bpmn Model 转换成 仿钉钉流程设计模型数据结构(json) 待完善
+     *
+     * @param bpmnModel Bpmn Model
+     * @return 仿钉钉流程设计模型数据结构
+     */
+    private BpmSimpleModelNodeVO convertBpmnModelToSimpleModel(BpmnModel bpmnModel) {
+        if (bpmnModel == null) {
+            return null;
+        }
+        StartEvent startEvent = BpmnModelUtils.getStartEvent(bpmnModel);
+        if (startEvent == null) {
+            return null;
+        }
+        BpmSimpleModelNodeVO rootNode = new BpmSimpleModelNodeVO();
+        rootNode.setType(START_NODE.getType());
+        rootNode.setId(startEvent.getId());
+        rootNode.setName(startEvent.getName());
+        recursiveBuildSimpleModelNode(startEvent, rootNode);
+        return rootNode;
+
+    }
 
-        if (ArrayUtil.isEmpty(bpmnBytes)) {
-            //  BPMN XML 不存在。新增
-            BpmnModel bpmnModel = BpmnModelUtils.convertSimpleModelToBpmnModel(model.getKey(), model.getName(), reqVO.getSimpleModelBody());
-            bpmModelService.saveModelBpmnXml(model.getId(),BpmnModelUtils.getBpmnXml(bpmnModel));
-            return Boolean.TRUE;
-        } else {
-            // TODO BPMN XML 已经存在。如何修改 ??
-            return Boolean.FALSE;
+    private void recursiveBuildSimpleModelNode(FlowNode currentFlowNode, BpmSimpleModelNodeVO currentSimpleModeNode) {
+        BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(currentSimpleModeNode.getType());
+        Assert.notNull(nodeType, "节点类型不支持");
+        // 校验节点是否支持转仿钉钉的流程模型
+        List<SequenceFlow> outgoingFlows = validateCanConvertSimpleNode(nodeType, currentFlowNode);
+        if (CollUtil.isEmpty(outgoingFlows) || outgoingFlows.get(0).getTargetFlowElement() == null) {
+            return;
+        }
+        FlowElement targetElement = outgoingFlows.get(0).getTargetFlowElement();
+        // 如果是 EndEvent 直接退出
+        if (targetElement instanceof EndEvent) {
+            return;
+        }
+        if (targetElement instanceof UserTask) {
+            BpmSimpleModelNodeVO childNode = convertUserTaskToSimpleModelNode((UserTask) targetElement);
+            currentSimpleModeNode.setChildNode(childNode);
+            recursiveBuildSimpleModelNode((FlowNode) targetElement, childNode);
+        }
+        // TODO 其它节点类型待实现
+    }
+
+
+    private BpmSimpleModelNodeVO convertUserTaskToSimpleModelNode(UserTask userTask) {
+        BpmSimpleModelNodeVO simpleModelNodeVO = new BpmSimpleModelNodeVO();
+        simpleModelNodeVO.setType(BpmSimpleModelNodeType.APPROVE_USER_NODE.getType());
+        simpleModelNodeVO.setName(userTask.getName());
+        simpleModelNodeVO.setId(userTask.getId());
+        Map<String, Object> attributes = MapUtil.newHashMap();
+        // TODO 暂时是普通审批,需要加会签
+        attributes.put("approveMethod", 1);
+        attributes.computeIfAbsent(USER_TASK_CANDIDATE_STRATEGY, (key) -> BpmnModelUtils.parseCandidateStrategy(userTask));
+        attributes.computeIfAbsent(USER_TASK_CANDIDATE_PARAM, (key) -> BpmnModelUtils.parseCandidateParam(userTask));
+        simpleModelNodeVO.setAttributes(attributes);
+        return simpleModelNodeVO;
+    }
+
+    private List<SequenceFlow> validateCanConvertSimpleNode(BpmSimpleModelNodeType nodeType, FlowNode currentFlowNode) {
+        switch (nodeType) {
+            case START_NODE:
+            case APPROVE_USER_NODE: {
+                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());
+                return outgoingFlows;
+            }
+            default: {
+                // TODO 其它节点类型待实现
+                throw exception(CONVERT_TO_SIMPLE_MODEL_NOT_SUPPORT);
+            }
+        }
+    }
+
+    private void validIsSupportFlowNode(FlowElement targetElement) {
+        if (targetElement == null) {
+            return;
+        }
+        boolean isSupport = false;
+        for (Class<? extends FlowNode> item : BpmnModelConstants.SUPPORT_CONVERT_SIMPLE_FlOW_NODES) {
+            if (item.isInstance(targetElement)) {
+                isSupport = true;
+                break;
+            }
+        }
+        if (!isSupport) {
+            throw exception(CONVERT_TO_SIMPLE_MODEL_NOT_SUPPORT);
         }
     }
 }