Commit 210b4e54d703cd639435689266a19e35ae1e3ea9

Authored by jason
1 parent 18f6bdd1

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

yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java 0 → 100644
  1 +package cn.iocoder.yudao.module.bpm.enums.definition;
  2 +
  3 +import cn.hutool.core.util.ArrayUtil;
  4 +import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
  5 +import lombok.AllArgsConstructor;
  6 +import lombok.Getter;
  7 +
  8 +import java.util.Arrays;
  9 +import java.util.Objects;
  10 +
  11 +/**
  12 + * 仿钉钉的流程器设计器的模型节点类型
  13 + *
  14 + * @author jason
  15 + */
  16 +@Getter
  17 +@AllArgsConstructor
  18 +public enum BpmSimpleModelNodeType implements IntArrayValuable {
  19 +
  20 + START_NODE(-1, "开始节点"),
  21 + START_USER_NODE(0, "发起人结点"),
  22 + APPROVE_USER_NODE (1, "审批人节点"),
  23 + EXCLUSIVE_GATEWAY_NODE(4, "排他网关"),
  24 + END_NODE(-2, "结束节点");
  25 +
  26 + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmSimpleModelNodeType::getType).toArray();
  27 +
  28 + private final Integer type;
  29 + private final String name;
  30 +
  31 + public static boolean isGatewayNode(Integer type) {
  32 + // TODO 后续增加并行网关的支持
  33 + return Objects.equals(EXCLUSIVE_GATEWAY_NODE.getType(), type);
  34 + }
  35 +
  36 + public static BpmSimpleModelNodeType valueOf(Integer type) {
  37 + return ArrayUtil.firstMatch(nodeType -> nodeType.getType().equals(type), values());
  38 + }
  39 +
  40 + @Override
  41 + public int[] array() {
  42 + return ARRAYS;
  43 + }
  44 +}
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmSimpleModelController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.bpm.controller.admin.definition;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelSaveReqVO;
  5 +import cn.iocoder.yudao.module.bpm.service.definition.BpmSimpleModelService;
  6 +import io.swagger.v3.oas.annotations.Operation;
  7 +import io.swagger.v3.oas.annotations.tags.Tag;
  8 +import jakarta.annotation.Resource;
  9 +import jakarta.validation.Valid;
  10 +import org.springframework.security.access.prepost.PreAuthorize;
  11 +import org.springframework.web.bind.annotation.PostMapping;
  12 +import org.springframework.web.bind.annotation.RequestBody;
  13 +import org.springframework.web.bind.annotation.RequestMapping;
  14 +import org.springframework.web.bind.annotation.RestController;
  15 +
  16 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  17 +
  18 +@Tag(name = "管理后台 - BPM 仿钉钉流程设计器")
  19 +@RestController
  20 +@RequestMapping("/bpm/simple")
  21 +public class BpmSimpleModelController {
  22 + @Resource
  23 + private BpmSimpleModelService bpmSimpleModelService;
  24 +
  25 + @PostMapping("/save")
  26 + @Operation(summary = "保存仿钉钉流程设计模型")
  27 + @PreAuthorize("@ss.hasPermission('bpm:model:update')")
  28 + public CommonResult<Boolean> saveSimpleModel(@Valid @RequestBody BpmSimpleModelSaveReqVO reqVO) {
  29 + return success(bpmSimpleModelService.saveSimpleModel(reqVO));
  30 + }
  31 +}
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/simple/BpmSimpleModelNodeVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple;
  2 +
  3 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  4 +import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType;
  5 +import io.swagger.v3.oas.annotations.media.Schema;
  6 +import jakarta.validation.constraints.NotEmpty;
  7 +import jakarta.validation.constraints.NotNull;
  8 +import lombok.Data;
  9 +
  10 +import java.util.List;
  11 +import java.util.Map;
  12 +
  13 +@Schema(description = "管理后台 - 仿钉钉流程设计模型节点 VO")
  14 +@Data
  15 +public class BpmSimpleModelNodeVO {
  16 +
  17 + @Schema(description = "模型节点编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "StartEvent_1")
  18 + @NotEmpty(message = "模型节点编号不能为空")
  19 + private String id;
  20 +
  21 + @Schema(description = "模型节点类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  22 + @NotNull(message = "模型节点类型不能为空")
  23 + @InEnum(BpmSimpleModelNodeType.class)
  24 + private Integer type;
  25 +
  26 + @Schema(description = "模型节点名称", example = "领导审批")
  27 + private String name;
  28 +
  29 + @Schema(description = "孩子节点")
  30 + private BpmSimpleModelNodeVO childNode;
  31 +
  32 + @Schema(description = "网关节点的条件节点")
  33 + private List<BpmSimpleModelNodeVO> conditionNodes;
  34 +
  35 + @Schema(description = "节点的属性")
  36 + private Map<String, Object> attributes;
  37 +}
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/simple/BpmSimpleModelSaveReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.Valid;
  5 +import jakarta.validation.constraints.NotEmpty;
  6 +import jakarta.validation.constraints.NotNull;
  7 +import lombok.Data;
  8 +
  9 +@Schema(description = "管理后台 - 仿钉钉流程设计模型的新增/修改 Request VO")
  10 +@Data
  11 +public class BpmSimpleModelSaveReqVO {
  12 +
  13 + @Schema(description = "流程模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  14 + @NotEmpty(message = "流程模型编号不能为空")
  15 + private String modelId; // 对应 Flowable act_re_model 表 ID_ 字段
  16 +
  17 + @Schema(description = "仿钉钉流程设计模型对象", requiredMode = Schema.RequiredMode.REQUIRED)
  18 + @NotNull(message = "仿钉钉流程设计模型对象不能为空")
  19 + @Valid
  20 + private BpmSimpleModelNodeVO simpleModelBody;
  21 +}
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 { @@ -23,4 +23,14 @@ public interface BpmnModelConstants {
23 */ 23 */
24 String USER_TASK_CANDIDATE_PARAM = "candidateParam"; 24 String USER_TASK_CANDIDATE_PARAM = "candidateParam";
25 25
  26 + /**
  27 + * BPMN Start Event 节点 Id, 用于后端生成 Start Event 节点
  28 + */
  29 + String START_EVENT_ID = "StartEvent_1";
  30 +
  31 + /**
  32 + * BPMN End Event 节点 Id, 用于后端生成 End Event 节点
  33 + */
  34 + String END_EVENT_ID = "EndEvent_1";
  35 +
26 } 36 }
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java
1 package cn.iocoder.yudao.module.bpm.framework.flowable.core.util; 1 package cn.iocoder.yudao.module.bpm.framework.flowable.core.util;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.lang.Assert;
  5 +import cn.hutool.core.lang.TypeReference;
  6 +import cn.hutool.core.map.MapUtil;
4 import cn.hutool.core.util.ArrayUtil; 7 import cn.hutool.core.util.ArrayUtil;
  8 +import cn.hutool.core.util.StrUtil;
5 import cn.iocoder.yudao.framework.common.util.number.NumberUtils; 9 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
  10 +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelNodeVO;
  11 +import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType;
6 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; 12 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
  13 +import org.flowable.bpmn.BpmnAutoLayout;
7 import org.flowable.bpmn.converter.BpmnXMLConverter; 14 import org.flowable.bpmn.converter.BpmnXMLConverter;
8 import org.flowable.bpmn.model.Process; 15 import org.flowable.bpmn.model.Process;
9 import org.flowable.bpmn.model.*; 16 import org.flowable.bpmn.model.*;
10 import org.flowable.common.engine.impl.util.io.BytesStreamSource; 17 import org.flowable.common.engine.impl.util.io.BytesStreamSource;
11 18
12 -import java.util.*; 19 +import java.util.ArrayList;
  20 +import java.util.HashSet;
  21 +import java.util.List;
  22 +import java.util.Set;
13 23
14 /** 24 /**
15 * 流程模型转操作工具类 25 * 流程模型转操作工具类
@@ -326,4 +336,148 @@ public class BpmnModelUtils { @@ -326,4 +336,148 @@ public class BpmnModelUtils {
326 return userTaskList; 336 return userTaskList;
327 } 337 }
328 338
  339 + /**
  340 + * 仿钉钉流程设计模型数据结构(json) 转换成 Bpmn Model (待完善)
  341 + * @param processId 流程标识
  342 + * @param processName 流程名称
  343 + * @param simpleModelNode 仿钉钉流程设计模型数据结构
  344 + * @return Bpmn Model
  345 + */
  346 + public static BpmnModel convertSimpleModelToBpmnModel(String processId, String processName,BpmSimpleModelNodeVO simpleModelNode) {
  347 + BpmnModel bpmnModel = new BpmnModel();
  348 + Process mainProcess = new Process();
  349 + mainProcess.setId(processId);
  350 + mainProcess.setName(processName);
  351 + mainProcess.setExecutable(Boolean.TRUE);
  352 + bpmnModel.addProcess(mainProcess);
  353 + // 目前前端传进来的节点。 没有 start event 节点 和 end event 节点。
  354 + // 在最前面加上 start event node
  355 + BpmSimpleModelNodeVO startEventNode = new BpmSimpleModelNodeVO();
  356 + startEventNode.setId(BpmnModelConstants.START_EVENT_ID);
  357 + startEventNode.setName("开始");
  358 + startEventNode.setType(BpmSimpleModelNodeType.START_NODE.getType());
  359 + startEventNode.setChildNode(simpleModelNode);
  360 + // 添加 FlowNode
  361 + addBpmnFlowNode(mainProcess, startEventNode);
  362 + // 单独添加 end event 节点
  363 + addBpmnEndEventNode(mainProcess);
  364 + // 添加节点之间的连线 Sequence Flow
  365 + addBpmnSequenceFlow(mainProcess, startEventNode);
  366 + // 自动布局
  367 + new BpmnAutoLayout(bpmnModel).execute();
  368 +
  369 + return bpmnModel;
  370 + }
  371 +
  372 + private static void addBpmnSequenceFlow(Process mainProcess, BpmSimpleModelNodeVO node) {
  373 + // 节点为 null 退出
  374 + if (node == null) {
  375 + return;
  376 + }
  377 + BpmSimpleModelNodeVO childNode = node.getChildNode();
  378 + // 如果后续节点为 null. 添加与结束节点的连线
  379 + if (childNode == null) {
  380 + addBpmnSequenceFlowElement(mainProcess, node.getId(),BpmnModelConstants.END_EVENT_ID, null);
  381 + return;
  382 + }
  383 + BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType());
  384 + Assert.notNull(nodeType, "模型节点类型不支持");
  385 + switch(nodeType){
  386 + case START_NODE :
  387 + case START_USER_NODE :
  388 + case APPROVE_USER_NODE :
  389 + addBpmnSequenceFlowElement(mainProcess, node.getId(), childNode.getId(), null);
  390 + break;
  391 + default : {
  392 + // TODO 其它节点类型的实现
  393 + }
  394 + }
  395 + // 递归调用后续节点
  396 + addBpmnSequenceFlow(mainProcess, childNode);
  397 + }
  398 +
  399 + private static void addBpmnSequenceFlowElement(Process mainProcess, String sourceId, String targetId, String conditionExpression) {
  400 + SequenceFlow sequenceFlow = new SequenceFlow(sourceId, targetId);
  401 + if (StrUtil.isNotEmpty(conditionExpression)) {
  402 + sequenceFlow.setConditionExpression(conditionExpression);
  403 + }
  404 + mainProcess.addFlowElement(sequenceFlow);
  405 +
  406 + }
  407 +
  408 + private static void addBpmnFlowNode(Process mainProcess, BpmSimpleModelNodeVO simpleModelNode) {
  409 + // 节点为 null 退出
  410 + if (simpleModelNode == null) {
  411 + return;
  412 + }
  413 + BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(simpleModelNode.getType());
  414 + Assert.notNull(nodeType, "模型节点类型不支持");
  415 + switch(nodeType){
  416 + case START_NODE :
  417 + addBpmnStartEventNode(mainProcess, simpleModelNode);
  418 + break;
  419 + case START_USER_NODE :
  420 + case APPROVE_USER_NODE :
  421 + addBpmnUserTaskEventNode(mainProcess, simpleModelNode);
  422 + break;
  423 + default : {
  424 + // TODO 其它节点类型的实现
  425 + }
  426 + }
  427 +
  428 + // 如果不是网关类型的接口, 并且chileNode为空退出
  429 + if (!BpmSimpleModelNodeType.isGatewayNode(simpleModelNode.getType()) && simpleModelNode.getChildNode() == null) {
  430 + return;
  431 + }
  432 +
  433 + // 如果是网关类型接口. 递归添加条件节点
  434 + if (BpmSimpleModelNodeType.isGatewayNode(simpleModelNode.getType()) && ArrayUtil.isNotEmpty(simpleModelNode.getConditionNodes())) {
  435 + for (BpmSimpleModelNodeVO node : simpleModelNode.getConditionNodes()) {
  436 + addBpmnFlowNode(mainProcess, node);
  437 + }
  438 + }
  439 +
  440 + // chileNode不为空,递归添加子节点
  441 + if (simpleModelNode.getChildNode() != null) {
  442 + addBpmnFlowNode(mainProcess, simpleModelNode.getChildNode());
  443 + }
  444 + }
  445 +
  446 + private static void addBpmnEndEventNode(Process mainProcess) {
  447 + EndEvent endEvent = new EndEvent();
  448 + endEvent.setId(BpmnModelConstants.END_EVENT_ID);
  449 + endEvent.setName("结束");
  450 + mainProcess.addFlowElement(endEvent);
  451 + }
  452 +
  453 + private static void addBpmnUserTaskEventNode(Process mainProcess, BpmSimpleModelNodeVO node) {
  454 + UserTask userTask = new UserTask();
  455 + userTask.setId(node.getId());
  456 + userTask.setName(node.getName());
  457 + Integer candidateStrategy = MapUtil.getInt(node.getAttributes(),BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY);
  458 + List<Integer> candidateParam = MapUtil.get(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_PARAM, new TypeReference<>() {});
  459 + // 添加自定义属性
  460 + addExtensionAttribute(userTask, BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY,
  461 + candidateStrategy == null ? null : String.valueOf(candidateStrategy));
  462 + addExtensionAttribute(userTask, BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM,
  463 + CollUtil.join(candidateParam, ","));
  464 + mainProcess.addFlowElement(userTask);
  465 + }
  466 +
  467 + private static void addExtensionAttribute(FlowElement element, String namespace, String name, String value) {
  468 + if (value == null) {
  469 + return;
  470 + }
  471 + ExtensionAttribute extensionAttribute = new ExtensionAttribute(name, value);
  472 + extensionAttribute.setNamespace(namespace);
  473 + element.addAttribute(extensionAttribute);
  474 + }
  475 +
  476 + private static void addBpmnStartEventNode(Process mainProcess, BpmSimpleModelNodeVO node) {
  477 + StartEvent startEvent = new StartEvent();
  478 + startEvent.setId(node.getId());
  479 + startEvent.setName(node.getName());
  480 + mainProcess.addFlowElement(startEvent);
  481 + }
  482 +
329 } 483 }
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 { @@ -46,6 +46,15 @@ public interface BpmModelService {
46 */ 46 */
47 byte[] getModelBpmnXML(String id); 47 byte[] getModelBpmnXML(String id);
48 48
  49 +
  50 + /**
  51 + * 保存流程模型的 BPMN XML
  52 + *
  53 + * @param id 编号
  54 + * @param bpmnXml BPMN XML
  55 + */
  56 + void saveModelBpmnXml(String id, String bpmnXml);
  57 +
49 /** 58 /**
50 * 修改流程模型 59 * 修改流程模型
51 * 60 *
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 { @@ -103,7 +103,7 @@ public class BpmModelServiceImpl implements BpmModelService {
103 // 保存流程定义 103 // 保存流程定义
104 repositoryService.saveModel(model); 104 repositoryService.saveModel(model);
105 // 保存 BPMN XML 105 // 保存 BPMN XML
106 - saveModelBpmnXml(model, bpmnXml); 106 + saveModelBpmnXml(model.getId(), bpmnXml);
107 return model.getId(); 107 return model.getId();
108 } 108 }
109 109
@@ -121,7 +121,7 @@ public class BpmModelServiceImpl implements BpmModelService { @@ -121,7 +121,7 @@ public class BpmModelServiceImpl implements BpmModelService {
121 // 更新模型 121 // 更新模型
122 repositoryService.saveModel(model); 122 repositoryService.saveModel(model);
123 // 更新 BPMN XML 123 // 更新 BPMN XML
124 - saveModelBpmnXml(model, updateReqVO.getBpmnXml()); 124 + saveModelBpmnXml(model.getId(), updateReqVO.getBpmnXml());
125 } 125 }
126 126
127 @Override 127 @Override
@@ -236,11 +236,12 @@ public class BpmModelServiceImpl implements BpmModelService { @@ -236,11 +236,12 @@ public class BpmModelServiceImpl implements BpmModelService {
236 } 236 }
237 } 237 }
238 238
239 - private void saveModelBpmnXml(Model model, String bpmnXml) { 239 + @Override
  240 + public void saveModelBpmnXml(String id, String bpmnXml) {
240 if (StrUtil.isEmpty(bpmnXml)) { 241 if (StrUtil.isEmpty(bpmnXml)) {
241 return; 242 return;
242 } 243 }
243 - repositoryService.addModelEditorSource(model.getId(), StrUtil.utf8Bytes(bpmnXml)); 244 + repositoryService.addModelEditorSource(id, StrUtil.utf8Bytes(bpmnXml));
244 } 245 }
245 246
246 /** 247 /**
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelService.java 0 → 100644
  1 +package cn.iocoder.yudao.module.bpm.service.definition;
  2 +
  3 +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelSaveReqVO;
  4 +import jakarta.validation.Valid;
  5 +
  6 +/**
  7 + * 仿钉钉流程设计 Service 接口
  8 + *
  9 + * @author jason
  10 + */
  11 +public interface BpmSimpleModelService {
  12 +
  13 + /**
  14 + * 保存仿钉钉流程设计模型
  15 + * @param reqVO 请求信息
  16 + */
  17 + Boolean saveSimpleModel(@Valid BpmSimpleModelSaveReqVO reqVO);
  18 +}
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.bpm.service.definition;
  2 +
  3 +import cn.hutool.core.util.ArrayUtil;
  4 +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelSaveReqVO;
  5 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
  6 +import jakarta.annotation.Resource;
  7 +import org.flowable.bpmn.model.BpmnModel;
  8 +import org.flowable.engine.repository.Model;
  9 +import org.springframework.stereotype.Service;
  10 +import org.springframework.validation.annotation.Validated;
  11 +
  12 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  13 +import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.MODEL_NOT_EXISTS;
  14 +
  15 +/**
  16 + * 仿钉钉流程设计 Service 实现类
  17 + *
  18 + * @author jason
  19 + */
  20 +@Service
  21 +@Validated
  22 +public class BpmSimpleModelServiceImpl implements BpmSimpleModelService {
  23 + @Resource
  24 + private BpmModelService bpmModelService;
  25 +
  26 + @Override
  27 + public Boolean saveSimpleModel(BpmSimpleModelSaveReqVO reqVO) {
  28 + Model model = bpmModelService.getModel(reqVO.getModelId());
  29 + if (model == null) {
  30 + throw exception(MODEL_NOT_EXISTS);
  31 + }
  32 + byte[] bpmnBytes = bpmModelService.getModelBpmnXML(reqVO.getModelId());
  33 +
  34 + if (ArrayUtil.isEmpty(bpmnBytes)) {
  35 + // BPMN XML 不存在。新增
  36 + BpmnModel bpmnModel = BpmnModelUtils.convertSimpleModelToBpmnModel(model.getKey(), model.getName(), reqVO.getSimpleModelBody());
  37 + bpmModelService.saveModelBpmnXml(model.getId(),BpmnModelUtils.getBpmnXml(bpmnModel));
  38 + return Boolean.TRUE;
  39 + } else {
  40 + // TODO BPMN XML 已经存在。如何修改 ??
  41 + return Boolean.FALSE;
  42 + }
  43 + }
  44 +}