Răsfoiți Sursa

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

jason 1 an în urmă
părinte
comite
210b4e54d7
10 a modificat fișierele cu 374 adăugiri și 5 ștergeri
  1. 44 0
      yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java
  2. 31 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmSimpleModelController.java
  3. 37 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/simple/BpmSimpleModelNodeVO.java
  4. 21 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/simple/BpmSimpleModelSaveReqVO.java
  5. 10 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java
  6. 155 1
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java
  7. 9 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java
  8. 5 4
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java
  9. 18 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelService.java
  10. 44 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelServiceImpl.java

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

@@ -0,0 +1,44 @@
+package cn.iocoder.yudao.module.bpm.enums.definition;
+
+import cn.hutool.core.util.ArrayUtil;
+import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * 仿钉钉的流程器设计器的模型节点类型
+ *
+ * @author jason
+ */
+@Getter
+@AllArgsConstructor
+public enum BpmSimpleModelNodeType implements IntArrayValuable {
+
+    START_NODE(-1, "开始节点"),
+    START_USER_NODE(0, "发起人结点"),
+    APPROVE_USER_NODE (1, "审批人节点"),
+    EXCLUSIVE_GATEWAY_NODE(4, "排他网关"),
+    END_NODE(-2, "结束节点");
+
+    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmSimpleModelNodeType::getType).toArray();
+
+    private final Integer type;
+    private final String name;
+
+    public static boolean isGatewayNode(Integer type) {
+        // TODO 后续增加并行网关的支持
+        return Objects.equals(EXCLUSIVE_GATEWAY_NODE.getType(), type);
+    }
+
+    public static BpmSimpleModelNodeType valueOf(Integer type) {
+        return ArrayUtil.firstMatch(nodeType -> nodeType.getType().equals(type), values());
+    }
+
+    @Override
+    public int[] array() {
+        return ARRAYS;
+    }
+}

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

@@ -0,0 +1,31 @@
+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.BpmSimpleModelSaveReqVO;
+import cn.iocoder.yudao.module.bpm.service.definition.BpmSimpleModelService;
+import io.swagger.v3.oas.annotations.Operation;
+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 static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - BPM 仿钉钉流程设计器")
+@RestController
+@RequestMapping("/bpm/simple")
+public class BpmSimpleModelController {
+    @Resource
+    private BpmSimpleModelService bpmSimpleModelService;
+
+    @PostMapping("/save")
+    @Operation(summary = "保存仿钉钉流程设计模型")
+    @PreAuthorize("@ss.hasPermission('bpm:model:update')")
+    public CommonResult<Boolean> saveSimpleModel(@Valid @RequestBody BpmSimpleModelSaveReqVO reqVO) {
+        return success(bpmSimpleModelService.saveSimpleModel(reqVO));
+    }
+}

+ 37 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/simple/BpmSimpleModelNodeVO.java

@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple;
+
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.util.List;
+import java.util.Map;
+
+@Schema(description = "管理后台 - 仿钉钉流程设计模型节点 VO")
+@Data
+public class BpmSimpleModelNodeVO {
+
+    @Schema(description = "模型节点编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "StartEvent_1")
+    @NotEmpty(message = "模型节点编号不能为空")
+    private String  id;
+
+    @Schema(description = "模型节点类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "模型节点类型不能为空")
+    @InEnum(BpmSimpleModelNodeType.class)
+    private Integer type;
+
+    @Schema(description = "模型节点名称", example = "领导审批")
+    private String name;
+
+    @Schema(description = "孩子节点")
+    private BpmSimpleModelNodeVO childNode;
+
+    @Schema(description = "网关节点的条件节点")
+    private List<BpmSimpleModelNodeVO> conditionNodes;
+
+    @Schema(description = "节点的属性")
+    private Map<String, Object>  attributes;
+}

+ 21 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/simple/BpmSimpleModelSaveReqVO.java

@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+@Schema(description = "管理后台 - 仿钉钉流程设计模型的新增/修改 Request VO")
+@Data
+public class BpmSimpleModelSaveReqVO {
+
+    @Schema(description = "流程模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotEmpty(message = "流程模型编号不能为空")
+    private String modelId; // 对应 Flowable act_re_model 表 ID_ 字段
+
+    @Schema(description = "仿钉钉流程设计模型对象", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "仿钉钉流程设计模型对象不能为空")
+    @Valid
+    private BpmSimpleModelNodeVO simpleModelBody;
+}

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

@@ -23,4 +23,14 @@ public interface BpmnModelConstants {
      */
     String USER_TASK_CANDIDATE_PARAM = "candidateParam";
 
+    /**
+     * BPMN Start Event 节点 Id, 用于后端生成 Start Event 节点
+     */
+    String START_EVENT_ID = "StartEvent_1";
+
+    /**
+     * BPMN End Event 节点 Id, 用于后端生成 End Event 节点
+     */
+    String END_EVENT_ID = "EndEvent_1";
+
 }

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

@@ -1,15 +1,25 @@
 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;
 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.util.io.BytesStreamSource;
 
-import java.util.*;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
 
 /**
  * 流程模型转操作工具类
@@ -326,4 +336,148 @@ public class BpmnModelUtils {
         return userTaskList;
     }
 
+    /**
+     * 仿钉钉流程设计模型数据结构(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 节点。
+        // 在最前面加上 start event node
+        BpmSimpleModelNodeVO startEventNode = new BpmSimpleModelNodeVO();
+        startEventNode.setId(BpmnModelConstants.START_EVENT_ID);
+        startEventNode.setName("开始");
+        startEventNode.setType(BpmSimpleModelNodeType.START_NODE.getType());
+        startEventNode.setChildNode(simpleModelNode);
+        // 添加 FlowNode
+        addBpmnFlowNode(mainProcess, startEventNode);
+        // 单独添加 end event 节点
+        addBpmnEndEventNode(mainProcess);
+        // 添加节点之间的连线 Sequence Flow
+        addBpmnSequenceFlow(mainProcess, startEventNode);
+        // 自动布局
+        new BpmnAutoLayout(bpmnModel).execute();
+
+        return bpmnModel;
+    }
+
+    private static void addBpmnSequenceFlow(Process mainProcess, BpmSimpleModelNodeVO node) {
+        // 节点为 null 退出
+        if (node == null) {
+            return;
+        }
+        BpmSimpleModelNodeVO childNode = node.getChildNode();
+        // 如果后续节点为 null. 添加与结束节点的连线
+        if (childNode == 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 :
+                addBpmnSequenceFlowElement(mainProcess, node.getId(), childNode.getId(), null);
+                break;
+            default : {
+                // TODO 其它节点类型的实现
+            }
+        }
+        // 递归调用后续节点
+        addBpmnSequenceFlow(mainProcess, childNode);
+    }
+
+    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);
+        }
+        mainProcess.addFlowElement(sequenceFlow);
+
+    }
+
+    private static void addBpmnFlowNode(Process mainProcess, BpmSimpleModelNodeVO simpleModelNode) {
+        // 节点为 null 退出
+        if (simpleModelNode == null) {
+            return;
+        }
+        BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(simpleModelNode.getType());
+        Assert.notNull(nodeType, "模型节点类型不支持");
+        switch(nodeType){
+            case START_NODE :
+                addBpmnStartEventNode(mainProcess, simpleModelNode);
+                break;
+            case START_USER_NODE :
+            case APPROVE_USER_NODE :
+                addBpmnUserTaskEventNode(mainProcess, simpleModelNode);
+                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);
+            }
+        }
+
+        // chileNode不为空,递归添加子节点
+        if (simpleModelNode.getChildNode() != null) {
+            addBpmnFlowNode(mainProcess, simpleModelNode.getChildNode());
+        }
+    }
+
+    private static void addBpmnEndEventNode(Process mainProcess) {
+        EndEvent endEvent = new EndEvent();
+        endEvent.setId(BpmnModelConstants.END_EVENT_ID);
+        endEvent.setName("结束");
+        mainProcess.addFlowElement(endEvent);
+    }
+
+    private static void addBpmnUserTaskEventNode(Process mainProcess, BpmSimpleModelNodeVO node) {
+        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<>() {});
+        // 添加自定义属性
+        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, ","));
+        mainProcess.addFlowElement(userTask);
+    }
+
+    private static void addExtensionAttribute(FlowElement element, String namespace, String name,  String value) {
+        if (value == null) {
+            return;
+        }
+        ExtensionAttribute extensionAttribute = new ExtensionAttribute(name, value);
+        extensionAttribute.setNamespace(namespace);
+        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);
+    }
+
 }

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

@@ -46,6 +46,15 @@ public interface BpmModelService {
      */
     byte[] getModelBpmnXML(String id);
 
+
+    /**
+     * 保存流程模型的 BPMN XML
+     *
+     * @param id 编号
+     * @param bpmnXml BPMN XML
+     */
+    void saveModelBpmnXml(String id, String bpmnXml);
+
     /**
      * 修改流程模型
      *

+ 5 - 4
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java

@@ -103,7 +103,7 @@ public class BpmModelServiceImpl implements BpmModelService {
         // 保存流程定义
         repositoryService.saveModel(model);
         // 保存 BPMN XML
-        saveModelBpmnXml(model, bpmnXml);
+        saveModelBpmnXml(model.getId(), bpmnXml);
         return model.getId();
     }
 
@@ -121,7 +121,7 @@ public class BpmModelServiceImpl implements BpmModelService {
         // 更新模型
         repositoryService.saveModel(model);
         // 更新 BPMN XML
-        saveModelBpmnXml(model, updateReqVO.getBpmnXml());
+        saveModelBpmnXml(model.getId(), updateReqVO.getBpmnXml());
     }
 
     @Override
@@ -236,11 +236,12 @@ public class BpmModelServiceImpl implements BpmModelService {
         }
     }
 
-    private void saveModelBpmnXml(Model model, String bpmnXml) {
+    @Override
+    public void saveModelBpmnXml(String id, String bpmnXml) {
         if (StrUtil.isEmpty(bpmnXml)) {
             return;
         }
-        repositoryService.addModelEditorSource(model.getId(), StrUtil.utf8Bytes(bpmnXml));
+        repositoryService.addModelEditorSource(id, StrUtil.utf8Bytes(bpmnXml));
     }
 
     /**

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

@@ -0,0 +1,18 @@
+package cn.iocoder.yudao.module.bpm.service.definition;
+
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelSaveReqVO;
+import jakarta.validation.Valid;
+
+/**
+ * 仿钉钉流程设计 Service 接口
+ *
+ * @author jason
+ */
+public interface BpmSimpleModelService {
+
+    /**
+     * 保存仿钉钉流程设计模型
+     * @param reqVO 请求信息
+     */
+    Boolean saveSimpleModel(@Valid  BpmSimpleModelSaveReqVO reqVO);
+}

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

@@ -0,0 +1,44 @@
+package cn.iocoder.yudao.module.bpm.service.definition;
+
+import cn.hutool.core.util.ArrayUtil;
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelSaveReqVO;
+import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
+import jakarta.annotation.Resource;
+import org.flowable.bpmn.model.BpmnModel;
+import org.flowable.engine.repository.Model;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.MODEL_NOT_EXISTS;
+
+/**
+ * 仿钉钉流程设计 Service 实现类
+ *
+ * @author jason
+ */
+@Service
+@Validated
+public class BpmSimpleModelServiceImpl implements BpmSimpleModelService {
+    @Resource
+    private BpmModelService bpmModelService;
+
+    @Override
+    public Boolean saveSimpleModel(BpmSimpleModelSaveReqVO reqVO) {
+        Model model = bpmModelService.getModel(reqVO.getModelId());
+        if (model == null) {
+            throw exception(MODEL_NOT_EXISTS);
+        }
+        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;
+        }
+    }
+}