diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java index ec167719c..ba6b8c454 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java +++ b/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, "该流程模型不支持仿钉钉设计流程"); } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmSimpleModelController.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmSimpleModelController.java index f6fb33e16..40745513e 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmSimpleModelController.java +++ b/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 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 getSimpleModel(@RequestParam("modelId") String modelId){ + return success(bpmSimpleModelService.getSimpleModel(modelId)); + } + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java index 5283baa25..72d4e59ed 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java +++ b/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 常量信息 * @@ -23,14 +28,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"; + /** + * 支持转仿钉钉设计模型的 Bpmn 节点 + */ + Set> SUPPORT_CONVERT_SIMPLE_FlOW_NODES = ImmutableSet.of(UserTask.class, EndEvent.class); + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java index b66ef6a97..501cfb6a5 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java +++ b/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 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; } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelService.java index 1761ba3ff..2d6865d24 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelService.java +++ b/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); } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelServiceImpl.java index 8273ca176..eca3da184 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelServiceImpl.java +++ b/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; + } - 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; + @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; + + } + + private void recursiveBuildSimpleModelNode(FlowNode currentFlowNode, BpmSimpleModelNodeVO currentSimpleModeNode) { + BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(currentSimpleModeNode.getType()); + Assert.notNull(nodeType, "节点类型不支持"); + // 校验节点是否支持转仿钉钉的流程模型 + List 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 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 validateCanConvertSimpleNode(BpmSimpleModelNodeType nodeType, FlowNode currentFlowNode) { + switch (nodeType) { + case START_NODE: + case APPROVE_USER_NODE: { + List 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 item : BpmnModelConstants.SUPPORT_CONVERT_SIMPLE_FlOW_NODES) { + if (item.isInstance(targetElement)) { + isSupport = true; + break; + } + } + if (!isSupport) { + throw exception(CONVERT_TO_SIMPLE_MODEL_NOT_SUPPORT); } } }