diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java new file mode 100644 index 000000000..357804fec --- /dev/null +++ b/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; + } +} 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 new file mode 100644 index 000000000..f6fb33e16 --- /dev/null +++ b/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 saveSimpleModel(@Valid @RequestBody BpmSimpleModelSaveReqVO reqVO) { + return success(bpmSimpleModelService.saveSimpleModel(reqVO)); + } +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/simple/BpmSimpleModelNodeVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/simple/BpmSimpleModelNodeVO.java new file mode 100644 index 000000000..19152bd2a --- /dev/null +++ b/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 conditionNodes; + + @Schema(description = "节点的属性") + private Map attributes; +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/simple/BpmSimpleModelSaveReqVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/simple/BpmSimpleModelSaveReqVO.java new file mode 100644 index 000000000..881adc89f --- /dev/null +++ b/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; +} 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 3eb6981ef..5283baa25 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 @@ -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"; + } 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 bcf82d731..b66ef6a97 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 @@ -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 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); + } + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java index e1acce064..2e0927e7b 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java +++ b/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); + /** * 修改流程模型 * diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java index eb392ace2..b85b5e066 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java +++ b/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)); } /** 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 new file mode 100644 index 000000000..1761ba3ff --- /dev/null +++ b/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); +} 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 new file mode 100644 index 000000000..8273ca176 --- /dev/null +++ b/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; + } + } +}