仿钉钉流程设计- code review 修改。扩展属性保存在 extensionElement 尝试

This commit is contained in:
jason 2024-04-11 21:02:38 +08:00
parent 1e97ca282b
commit 9456d461f9
6 changed files with 316 additions and 271 deletions

View File

@ -18,20 +18,17 @@ import java.util.Objects;
public enum BpmSimpleModelNodeType implements IntArrayValuable { public enum BpmSimpleModelNodeType implements IntArrayValuable {
// TODO @jaosn-1014-2 是前端已经定义好的么感觉未来可以考虑搞成和 BPMN 尽量一致的单词哈类似 usertask 用户审批 // TODO @jaosn-1014-2 是前端已经定义好的么感觉未来可以考虑搞成和 BPMN 尽量一致的单词哈类似 usertask 用户审批
// TODO @jason_NODE 都删除掉哈 START_EVENT(0, "开始节点"),
START_EVENT_NODE(0, "开始节点"), END_EVENT(-2, "结束节点"), // TODO @jaosn挪到 START_EVENT_NODE
END_EVENT_NODE(-2, "结束节点"), // TODO @jaosn挪到 START_EVENT_NODE
APPROVE_USER_NODE(1, "审批人节点"), // TODO @jaosn是不是这里从 10 开始好点相当于说0-9 给开始和结束10-19 给各种节点20-29 给各种条件TODO @jason改成 USER_TASK 是不是好点呀 USER_TASK(1, "审批人节点"), // TODO @jaosn是不是这里从 10 开始好点相当于说0-9 给开始和结束10-19 给各种节点20-29 给各种条件 TODO 后面改改
// 抄送人节点对应 BPMN ScriptTask. 使用ScriptTask 原因好像 ServiceTask 自定义属性不能写入 XML COPY_TASK(2, "抄送人节点"),
// TODO @jasonServiceTask 自定义 xml有没啥报错信息
SCRIPT_TASK_NODE(2, "抄送人节点"), // TODO @jason是不是改成 COPY_TASK 好一点哈
EXCLUSIVE_GATEWAY_NODE(4, "排他网关"), // TODO @jason是不是改成叫 条件分支 EXCLUSIVE_GATEWAY(4, "排他网关"), // TODO @jason是不是改成叫 条件分支
PARALLEL_GATEWAY_FORK_NODE(5, "并行网关分叉节点"), // TODO @jason是不是一个 并行分支 就可以啦 PARALLEL_GATEWAY_FORK(5, "并行网关分叉节点"), // TODO @jason是不是一个 并行分支 就可以啦 后面是否去掉并行网关只用包容网关
PARALLEL_GATEWAY_JOIN_NODE(6, "并行网关聚合节点"), PARALLEL_GATEWAY_JOIN(6, "并行网关聚合节点"),
INCLUSIVE_GATEWAY_FORK_NODE(7, "包容网关分叉节点"), INCLUSIVE_GATEWAY_FORK(7, "包容网关分叉节点"),
INCLUSIVE_GATEWAY_JOIN_NODE(8, "包容网关聚合节点"), INCLUSIVE_GATEWAY_JOIN(8, "包容网关聚合节点"),
; ;
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmSimpleModelNodeType::getType).toArray(); public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmSimpleModelNodeType::getType).toArray();
@ -39,9 +36,13 @@ public enum BpmSimpleModelNodeType implements IntArrayValuable {
private final Integer type; private final Integer type;
private final String name; private final String name;
public static boolean isGatewayNode(Integer type) { /**
return Objects.equals(EXCLUSIVE_GATEWAY_NODE.getType(), type) || Objects.equals(PARALLEL_GATEWAY_FORK_NODE.getType(), type) * 判断是否为分支节点
|| Objects.equals(INCLUSIVE_GATEWAY_FORK_NODE.getType(), type) ; * @param type 节点类型
*/
public static boolean isBranchNode(Integer type) {
return Objects.equals(EXCLUSIVE_GATEWAY.getType(), type) || Objects.equals(PARALLEL_GATEWAY_FORK.getType(), type)
|| Objects.equals(INCLUSIVE_GATEWAY_FORK.getType(), type) ;
} }
public static BpmSimpleModelNodeType valueOf(Integer type) { public static BpmSimpleModelNodeType valueOf(Integer type) {

View File

@ -1,43 +1,41 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.util; package cn.iocoder.yudao.module.bpm.framework.flowable.core.util;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ArrayUtil; 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.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 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.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.Process; import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*; import org.flowable.bpmn.model.*;
import org.flowable.common.engine.impl.scripting.ScriptingEngines;
import org.flowable.common.engine.impl.util.io.BytesStreamSource; import org.flowable.common.engine.impl.util.io.BytesStreamSource;
import java.util.ArrayList; import java.util.*;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static org.flowable.bpmn.constants.BpmnXMLConstants.*;
/** /**
* 流程模型转操作工具类 * 流程模型转操作工具类
*/ */
public class BpmnModelUtils { public class BpmnModelUtils {
public static final String BPMN_SIMPLE_COPY_EXECUTION_SCRIPT = "#{bpmSimpleNodeService.copy(execution)}";
public static Integer parseCandidateStrategy(FlowElement userTask) { public static Integer parseCandidateStrategy(FlowElement userTask) {
return NumberUtils.parseInt(userTask.getAttributeValue( Integer candidateStrategy = NumberUtils.parseInt(userTask.getAttributeValue(
BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY)); BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY));
// @芋艿 尝试从 ExtensionElement . 后续相关扩展是否都可以 extensionElement 如表单权限 按钮权限
if (candidateStrategy == null) {
ExtensionElement element = CollUtil.getFirst(userTask.getExtensionElements().get(BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY));
candidateStrategy = NumberUtils.parseInt(Optional.ofNullable(element).map(ExtensionElement::getElementText).orElse(null));
}
return candidateStrategy;
} }
public static String parseCandidateParam(FlowElement userTask) { public static String parseCandidateParam(FlowElement userTask) {
return userTask.getAttributeValue( String candidateParam = userTask.getAttributeValue(
BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM); BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM);
if (candidateParam == null) {
ExtensionElement element = CollUtil.getFirst(userTask.getExtensionElements().get(BpmnModelConstants.USER_TASK_CANDIDATE_PARAM));
candidateParam = Optional.ofNullable(element).map(ExtensionElement::getElementText).orElse(null);
}
return candidateParam;
} }
/** /**
@ -339,231 +337,4 @@ public class BpmnModelUtils {
} }
return userTaskList; return userTaskList;
} }
// ========== TODO @jason单独出一个 SimpleModelUtils定位上它是 BPMN 的精简模式 ==========
/**
* 仿钉钉流程设计模型数据结构(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 节点
// 添加 FlowNode
addBpmnFlowNode(mainProcess, simpleModelNode);
// 单独添加 end event 节点
addBpmnEndEventNode(mainProcess);
// 添加节点之间的连线 Sequence Flow
addBpmnSequenceFlow(mainProcess, simpleModelNode, BpmnModelConstants.END_EVENT_ID);
// 自动布局
new BpmnAutoLayout(bpmnModel).execute();
return bpmnModel;
}
private static void addBpmnSequenceFlow(Process mainProcess, BpmSimpleModelNodeVO node, String endId) {
// 节点为 null 退出
if (node == null || node.getId() == null) {
return;
}
BpmSimpleModelNodeVO childNode = node.getChildNode();
// 如果不是网关节点且后续节点为 null. 添加与结束节点的连线
if (!BpmSimpleModelNodeType.isGatewayNode(node.getType()) && (childNode == null || childNode.getId() == null)) {
addBpmnSequenceFlowElement(mainProcess, node.getId(), endId, null, null);
return;
}
BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType());
Assert.notNull(nodeType, "模型节点类型不支持");
// TODO @jason建议是addXXX 都改成 buildXXX构建出一个什么然后返回之后让这个方法添加到自己的结果里
switch (nodeType) {
case START_EVENT_NODE:
case APPROVE_USER_NODE:
case SCRIPT_TASK_NODE:
case PARALLEL_GATEWAY_JOIN_NODE:
case INCLUSIVE_GATEWAY_JOIN_NODE:{
addBpmnSequenceFlowElement(mainProcess, node.getId(), childNode.getId(), null, null);
// 递归调用后续节点
addBpmnSequenceFlow(mainProcess, childNode, endId);
break;
}
case PARALLEL_GATEWAY_FORK_NODE:
case EXCLUSIVE_GATEWAY_NODE:
case INCLUSIVE_GATEWAY_FORK_NODE:{
String gateWayEndId = (childNode == null || childNode.getId() == null) ? BpmnModelConstants.END_EVENT_ID : childNode.getId();
List<BpmSimpleModelNodeVO> conditionNodes = node.getConditionNodes();
Assert.notEmpty(conditionNodes, "网关节点的条件节点不能为空");
for (int i = 0; i < conditionNodes.size(); i++) {
BpmSimpleModelNodeVO item = conditionNodes.get(i);
BpmSimpleModelNodeVO nextNodeOnCondition = item.getChildNode();
if (nextNodeOnCondition != null && nextNodeOnCondition.getId() != null) {
addBpmnSequenceFlowElement(mainProcess, node.getId(), nextNodeOnCondition.getId(),
String.format("%s_SequenceFlow_%d", node.getId(), i + 1), null);
addBpmnSequenceFlow(mainProcess, nextNodeOnCondition, gateWayEndId);
} else {
addBpmnSequenceFlowElement(mainProcess, node.getId(), gateWayEndId,
String.format("%s_SequenceFlow_%d", node.getId(), i + 1), null);
}
}
// 递归调用后续节点
addBpmnSequenceFlow(mainProcess, childNode, endId);
break;
}
default: {
// TODO 其它节点类型的实现
}
}
}
private static void addBpmnSequenceFlowElement(Process mainProcess, String sourceId, String targetId, String seqFlowId, String conditionExpression) {
SequenceFlow sequenceFlow = new SequenceFlow(sourceId, targetId);
if (StrUtil.isNotEmpty(conditionExpression)) {
sequenceFlow.setConditionExpression(conditionExpression);
}
if (StrUtil.isNotEmpty(seqFlowId)) {
sequenceFlow.setId(seqFlowId);
}
mainProcess.addFlowElement(sequenceFlow);
}
private static void addBpmnFlowNode(Process mainProcess, BpmSimpleModelNodeVO simpleModelNode) {
// 节点为 null 退出
if (simpleModelNode == null || simpleModelNode.getId() == null) {
return;
}
BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(simpleModelNode.getType());
Assert.notNull(nodeType, "模型节点类型不支持");
switch (nodeType) {
case START_EVENT_NODE:
addBpmnStartEventNode(mainProcess, simpleModelNode);
break;
case APPROVE_USER_NODE:
addBpmnUserTaskNode(mainProcess, simpleModelNode);
break;
case SCRIPT_TASK_NODE:
addBpmnScriptTaSskNode(mainProcess, simpleModelNode);
break;
case EXCLUSIVE_GATEWAY_NODE:
addBpmnExclusiveGatewayNode(mainProcess, simpleModelNode);
break;
case PARALLEL_GATEWAY_FORK_NODE:
case PARALLEL_GATEWAY_JOIN_NODE:
addBpmnParallelGatewayNode(mainProcess, simpleModelNode);
break;
case INCLUSIVE_GATEWAY_FORK_NODE:
addBpmnInclusiveGatewayNode(mainProcess, simpleModelNode, Boolean.TRUE);
break;
case INCLUSIVE_GATEWAY_JOIN_NODE:
addBpmnInclusiveGatewayNode(mainProcess, simpleModelNode, Boolean.FALSE);
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.getChildNode());
}
}
// chileNode不为空递归添加子节点
if (simpleModelNode.getChildNode() != null) {
addBpmnFlowNode(mainProcess, simpleModelNode.getChildNode());
}
}
private static void addBpmnParallelGatewayNode(Process mainProcess, BpmSimpleModelNodeVO node) {
ParallelGateway parallelGateway = new ParallelGateway();
parallelGateway.setId(node.getId());
mainProcess.addFlowElement(parallelGateway);
}
private static void addBpmnScriptTaSskNode(Process mainProcess, BpmSimpleModelNodeVO node) {
ScriptTask scriptTask = new ScriptTask();
scriptTask.setId(node.getId());
scriptTask.setName(node.getName());
// TODO @jason建议使用 ServiceTask通过 executionListeners 实现
scriptTask.setScriptFormat(ScriptingEngines.DEFAULT_SCRIPTING_LANGUAGE);
scriptTask.setScript(BPMN_SIMPLE_COPY_EXECUTION_SCRIPT);
// 添加自定义属性
// TODO @jason可以使用 ServiceTask ExtensionAttribute
addExtensionAttributes(node, scriptTask);
mainProcess.addFlowElement(scriptTask);
}
private static void addExtensionAttributes(BpmSimpleModelNodeVO node, FlowElement flowElement) {
Integer candidateStrategy = MapUtil.getInt(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY);
addExtensionAttributes(flowElement, BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY,
candidateStrategy == null ? null : String.valueOf(candidateStrategy));
addExtensionAttributes(flowElement, BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM,
MapUtil.getStr(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_PARAM));
}
private static void addBpmnExclusiveGatewayNode(Process mainProcess, BpmSimpleModelNodeVO node) {
Assert.notEmpty(node.getConditionNodes(), "网关节点的条件节点不能为空");
ExclusiveGateway exclusiveGateway = new ExclusiveGateway();
exclusiveGateway.setId(node.getId());
// 网关的最后一个条件为 网关的 default sequence flow
exclusiveGateway.setDefaultFlow(String.format("%s_SequenceFlow_%d", node.getId(), node.getConditionNodes().size()));
mainProcess.addFlowElement(exclusiveGateway);
}
private static void addBpmnInclusiveGatewayNode(Process mainProcess, BpmSimpleModelNodeVO node, Boolean isFork) {
InclusiveGateway inclusiveGateway = new InclusiveGateway();
inclusiveGateway.setId(node.getId());
if (isFork) {
Assert.notEmpty(node.getConditionNodes(), "网关节点的条件节点不能为空");
// 网关的最后一个条件为 网关的 default sequence flow
inclusiveGateway.setDefaultFlow(String.format("%s_SequenceFlow_%d", node.getId(), node.getConditionNodes().size()));
}
mainProcess.addFlowElement(inclusiveGateway);
}
private static void addBpmnEndEventNode(Process mainProcess) {
EndEvent endEvent = new EndEvent();
endEvent.setId(BpmnModelConstants.END_EVENT_ID);
endEvent.setName("结束");
mainProcess.addFlowElement(endEvent);
}
private static void addBpmnUserTaskNode(Process mainProcess, BpmSimpleModelNodeVO node) {
UserTask userTask = new UserTask();
userTask.setId(node.getId());
userTask.setName(node.getName());
addExtensionAttributes(node, userTask);
mainProcess.addFlowElement(userTask);
}
private static void addExtensionAttributes(FlowElement element, String namespace, String name, String value) {
if (value == null) {
return;
}
ExtensionAttribute extensionAttribute = new ExtensionAttribute(name, value);
extensionAttribute.setNamespace(namespace);
extensionAttribute.setNamespacePrefix(FLOWABLE_EXTENSIONS_PREFIX);
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);
}
} }

View File

@ -0,0 +1,270 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.util;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
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.model.*;
import org.flowable.bpmn.model.Process;
import java.util.List;
import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_NAMESPACE;
import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_PREFIX;
/**
* 仿钉钉快搭模型相关的工具方法
*
* @author jason
*/
public class SimpleModelUtils {
public static final String BPMN_SIMPLE_COPY_EXECUTION_SCRIPT = "#{bpmSimpleNodeService.copy(execution)}";
/**
* 仿钉钉流程设计模型数据结构(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 节点
// SimpleModel 构建 FlowNode 并添加到 Main Process
buildAndAddBpmnFlowNode(simpleModelNode, mainProcess);
// 单独构建 end event 节点
buildAndAddBpmnEndEvent(mainProcess);
// 构建并添加节点之间的连线 Sequence Flow
buildAndAddBpmnSequenceFlow(mainProcess, simpleModelNode, BpmnModelConstants.END_EVENT_ID);
// 自动布局
new BpmnAutoLayout(bpmnModel).execute();
return bpmnModel;
}
private static void buildAndAddBpmnSequenceFlow(Process mainProcess, BpmSimpleModelNodeVO node, String targetId) {
// 节点为 null 退出
if (node == null || node.getId() == null) {
return;
}
BpmSimpleModelNodeVO childNode = node.getChildNode();
// 如果不是网关节点且后续节点为 null. 添加与结束节点的连线
if (!BpmSimpleModelNodeType.isBranchNode(node.getType()) && (childNode == null || childNode.getId() == null)) {
SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), targetId, null, null);
mainProcess.addFlowElement(sequenceFlow);
return;
}
BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType());
Assert.notNull(nodeType, "模型节点类型不支持");
switch (nodeType) {
case START_EVENT:
case USER_TASK:
case COPY_TASK:
case PARALLEL_GATEWAY_JOIN:
case INCLUSIVE_GATEWAY_JOIN:{
SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), childNode.getId(), null, null);
mainProcess.addFlowElement(sequenceFlow);
// 递归调用后续节点
buildAndAddBpmnSequenceFlow(mainProcess, childNode, targetId);
break;
}
case PARALLEL_GATEWAY_FORK:
case EXCLUSIVE_GATEWAY:
case INCLUSIVE_GATEWAY_FORK:{
String sequenceFlowTargetId = (childNode == null || childNode.getId() == null) ? BpmnModelConstants.END_EVENT_ID : childNode.getId();
List<BpmSimpleModelNodeVO> conditionNodes = node.getConditionNodes();
Assert.notEmpty(conditionNodes, "网关节点的条件节点不能为空");
for (int i = 0; i < conditionNodes.size(); i++) {
BpmSimpleModelNodeVO item = conditionNodes.get(i);
BpmSimpleModelNodeVO nextNodeOnCondition = item.getChildNode();
if (nextNodeOnCondition != null && nextNodeOnCondition.getId() != null) {
SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), nextNodeOnCondition.getId(),
String.format("%s_SequenceFlow_%d", node.getId(), i + 1), null);
mainProcess.addFlowElement(sequenceFlow);
// 递归调用后续节点
buildAndAddBpmnSequenceFlow(mainProcess, nextNodeOnCondition, sequenceFlowTargetId);
} else {
SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), sequenceFlowTargetId,
String.format("%s_SequenceFlow_%d", node.getId(), i + 1), null);
mainProcess.addFlowElement(sequenceFlow);
}
}
// 递归调用后续节点
buildAndAddBpmnSequenceFlow(mainProcess, childNode, targetId);
break;
}
default: {
// TODO 其它节点类型的实现
}
}
}
private static SequenceFlow buildBpmnSequenceFlow(String sourceId, String targetId, String seqFlowId, String conditionExpression) {
SequenceFlow sequenceFlow = new SequenceFlow(sourceId, targetId);
if (StrUtil.isNotEmpty(conditionExpression)) {
sequenceFlow.setConditionExpression(conditionExpression);
}
if (StrUtil.isNotEmpty(seqFlowId)) {
sequenceFlow.setId(seqFlowId);
}
return sequenceFlow;
}
private static void buildAndAddBpmnFlowNode(BpmSimpleModelNodeVO simpleModelNode, Process mainProcess) {
// 节点为 null 退出
if (simpleModelNode == null || simpleModelNode.getId() == null) {
return;
}
BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(simpleModelNode.getType());
Assert.notNull(nodeType, "模型节点类型不支持");
switch (nodeType) {
case START_EVENT: {
StartEvent startEvent = buildBpmnStartEvent(simpleModelNode);
mainProcess.addFlowElement(startEvent);
break;
}
case USER_TASK: {
UserTask userTask = buildBpmnUserTask(simpleModelNode);
mainProcess.addFlowElement(userTask);
break;
}
case COPY_TASK: {
ServiceTask serviceTask = buildBpmnServiceTask(simpleModelNode);
mainProcess.addFlowElement(serviceTask);
break;
}
case EXCLUSIVE_GATEWAY: {
ExclusiveGateway exclusiveGateway = buildBpmnExclusiveGateway(simpleModelNode);
mainProcess.addFlowElement(exclusiveGateway);
break;
}
case PARALLEL_GATEWAY_FORK:
case PARALLEL_GATEWAY_JOIN: {
ParallelGateway parallelGateway = buildBpmnParallelGateway(simpleModelNode);
mainProcess.addFlowElement(parallelGateway);
break;
}
case INCLUSIVE_GATEWAY_FORK: {
InclusiveGateway inclusiveGateway = buildBpmnInclusiveGateway(simpleModelNode, Boolean.TRUE);
mainProcess.addFlowElement(inclusiveGateway);
break;
}
case INCLUSIVE_GATEWAY_JOIN: {
InclusiveGateway inclusiveGateway = buildBpmnInclusiveGateway(simpleModelNode, Boolean.FALSE);
mainProcess.addFlowElement(inclusiveGateway);
break;
}
default: {
// TODO 其它节点类型的实现
}
}
// 如果不是网关类型的接口 并且chileNode为空退出
if (!BpmSimpleModelNodeType.isBranchNode(simpleModelNode.getType()) && simpleModelNode.getChildNode() == null) {
return;
}
// 如果是网关类型接口. 递归添加条件节点
if (BpmSimpleModelNodeType.isBranchNode(simpleModelNode.getType()) && ArrayUtil.isNotEmpty(simpleModelNode.getConditionNodes())) {
for (BpmSimpleModelNodeVO node : simpleModelNode.getConditionNodes()) {
buildAndAddBpmnFlowNode(node.getChildNode(), mainProcess);
}
}
// chileNode不为空递归添加子节点
if (simpleModelNode.getChildNode() != null) {
buildAndAddBpmnFlowNode(simpleModelNode.getChildNode(), mainProcess);
}
}
private static ParallelGateway buildBpmnParallelGateway(BpmSimpleModelNodeVO node) {
ParallelGateway parallelGateway = new ParallelGateway();
parallelGateway.setId(node.getId());
return parallelGateway;
}
private static ServiceTask buildBpmnServiceTask(BpmSimpleModelNodeVO node) {
ServiceTask serviceTask = new ServiceTask();
serviceTask.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_EXPRESSION);
serviceTask.setImplementation(BPMN_SIMPLE_COPY_EXECUTION_SCRIPT);
serviceTask.setId(node.getId());
serviceTask.setName(node.getName());
// TODO @jason建议使用 ServiceTask通过 executionListeners 实现
// @芋艿 ServiceTask 就可以了吧 不需要 executionListeners
addExtensionElement(node, serviceTask);
return serviceTask;
}
private static void addExtensionElement(BpmSimpleModelNodeVO node, FlowElement flowElement) {
Integer candidateStrategy = MapUtil.getInt(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY);
addExtensionElement(flowElement, FLOWABLE_EXTENSIONS_NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY,
candidateStrategy == null ? null : String.valueOf(candidateStrategy));
addExtensionElement(flowElement, FLOWABLE_EXTENSIONS_NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM,
MapUtil.getStr(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_PARAM));
}
private static ExclusiveGateway buildBpmnExclusiveGateway(BpmSimpleModelNodeVO node) {
Assert.notEmpty(node.getConditionNodes(), "网关节点的条件节点不能为空");
ExclusiveGateway exclusiveGateway = new ExclusiveGateway();
exclusiveGateway.setId(node.getId());
// 网关的最后一个条件为 网关的 default sequence flow
exclusiveGateway.setDefaultFlow(String.format("%s_SequenceFlow_%d", node.getId(), node.getConditionNodes().size()));
return exclusiveGateway;
}
private static InclusiveGateway buildBpmnInclusiveGateway(BpmSimpleModelNodeVO node, Boolean isFork) {
InclusiveGateway inclusiveGateway = new InclusiveGateway();
inclusiveGateway.setId(node.getId());
if (isFork) {
Assert.notEmpty(node.getConditionNodes(), "网关节点的条件节点不能为空");
// 网关的最后一个条件为 网关的 default sequence flow
inclusiveGateway.setDefaultFlow(String.format("%s_SequenceFlow_%d", node.getId(), node.getConditionNodes().size()));
}
return inclusiveGateway;
}
private static void buildAndAddBpmnEndEvent(Process mainProcess) {
EndEvent endEvent = new EndEvent();
endEvent.setId(BpmnModelConstants.END_EVENT_ID);
endEvent.setName("结束");
mainProcess.addFlowElement(endEvent);
}
private static UserTask buildBpmnUserTask(BpmSimpleModelNodeVO node) {
UserTask userTask = new UserTask();
userTask.setId(node.getId());
userTask.setName(node.getName());
addExtensionElement(node, userTask);
return userTask;
}
private static void addExtensionElement(FlowElement element, String namespace, String name, String value) {
if (value == null) {
return;
}
ExtensionElement extensionElement = new ExtensionElement();
extensionElement.setNamespace(namespace);
extensionElement.setNamespacePrefix(FLOWABLE_EXTENSIONS_PREFIX);
extensionElement.setElementText(value);
extensionElement.setName(name);
element.addExtensionElement(extensionElement);
}
private static StartEvent buildBpmnStartEvent(BpmSimpleModelNodeVO node) {
StartEvent startEvent = new StartEvent();
startEvent.setId(node.getId());
startEvent.setName(node.getName());
return startEvent;
}
}

View File

@ -10,6 +10,7 @@ import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimp
import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; 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.enums.BpmnModelConstants;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.flowable.bpmn.model.*; import org.flowable.bpmn.model.*;
import org.flowable.engine.repository.Model; import org.flowable.engine.repository.Model;
@ -22,7 +23,7 @@ import java.util.Map;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 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.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.ErrorCodeConstants.MODEL_NOT_EXISTS;
import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.START_EVENT_NODE; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.START_EVENT;
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_PARAM;
import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY;
@ -56,7 +57,7 @@ public class BpmSimpleModelServiceImpl implements BpmSimpleModelService {
// return Boolean.FALSE; // return Boolean.FALSE;
// } // }
// 1. JSON 转换成 bpmnModel // 1. JSON 转换成 bpmnModel
BpmnModel bpmnModel = BpmnModelUtils.convertSimpleModelToBpmnModel(model.getKey(), model.getName(), reqVO.getSimpleModelBody()); BpmnModel bpmnModel = SimpleModelUtils.convertSimpleModelToBpmnModel(model.getKey(), model.getName(), reqVO.getSimpleModelBody());
// 2.1 保存 Bpmn XML // 2.1 保存 Bpmn XML
bpmModelService.saveModelBpmnXml(model.getId(), StrUtil.utf8Bytes(BpmnModelUtils.getBpmnXml(bpmnModel))); bpmModelService.saveModelBpmnXml(model.getId(), StrUtil.utf8Bytes(BpmnModelUtils.getBpmnXml(bpmnModel)));
// 2.2 保存 JSON 数据 // 2.2 保存 JSON 数据
@ -93,7 +94,7 @@ public class BpmSimpleModelServiceImpl implements BpmSimpleModelService {
return null; return null;
} }
BpmSimpleModelNodeVO rootNode = new BpmSimpleModelNodeVO(); BpmSimpleModelNodeVO rootNode = new BpmSimpleModelNodeVO();
rootNode.setType(START_EVENT_NODE.getType()); rootNode.setType(START_EVENT.getType());
rootNode.setId(startEvent.getId()); rootNode.setId(startEvent.getId());
rootNode.setName(startEvent.getName()); rootNode.setName(startEvent.getName());
recursiveBuildSimpleModelNode(startEvent, rootNode); recursiveBuildSimpleModelNode(startEvent, rootNode);
@ -105,10 +106,10 @@ public class BpmSimpleModelServiceImpl implements BpmSimpleModelService {
Assert.notNull(nodeType, "节点类型不支持"); Assert.notNull(nodeType, "节点类型不支持");
// 校验节点是否支持转仿钉钉的流程模型 // 校验节点是否支持转仿钉钉的流程模型
List<SequenceFlow> outgoingFlows = validateCanConvertSimpleNode(nodeType, currentFlowNode); List<SequenceFlow> outgoingFlows = validateCanConvertSimpleNode(nodeType, currentFlowNode);
if (CollUtil.isEmpty(outgoingFlows) || outgoingFlows.get(0).getTargetFlowElement() == null) { if (CollUtil.isEmpty(outgoingFlows) || CollUtil.getFirst(outgoingFlows).getTargetFlowElement() == null) {
return; return;
} }
FlowElement targetElement = outgoingFlows.get(0).getTargetFlowElement(); FlowElement targetElement = CollUtil.getFirst(outgoingFlows).getTargetFlowElement();
// 如果是 EndEvent 直接退出 // 如果是 EndEvent 直接退出
if (targetElement instanceof EndEvent) { if (targetElement instanceof EndEvent) {
return; return;
@ -123,7 +124,7 @@ public class BpmSimpleModelServiceImpl implements BpmSimpleModelService {
private BpmSimpleModelNodeVO convertUserTaskToSimpleModelNode(UserTask userTask) { private BpmSimpleModelNodeVO convertUserTaskToSimpleModelNode(UserTask userTask) {
BpmSimpleModelNodeVO simpleModelNodeVO = new BpmSimpleModelNodeVO(); BpmSimpleModelNodeVO simpleModelNodeVO = new BpmSimpleModelNodeVO();
simpleModelNodeVO.setType(BpmSimpleModelNodeType.APPROVE_USER_NODE.getType()); simpleModelNodeVO.setType(BpmSimpleModelNodeType.USER_TASK.getType());
simpleModelNodeVO.setName(userTask.getName()); simpleModelNodeVO.setName(userTask.getName());
simpleModelNodeVO.setId(userTask.getId()); simpleModelNodeVO.setId(userTask.getId());
Map<String, Object> attributes = MapUtil.newHashMap(); Map<String, Object> attributes = MapUtil.newHashMap();
@ -137,13 +138,13 @@ public class BpmSimpleModelServiceImpl implements BpmSimpleModelService {
private List<SequenceFlow> validateCanConvertSimpleNode(BpmSimpleModelNodeType nodeType, FlowNode currentFlowNode) { private List<SequenceFlow> validateCanConvertSimpleNode(BpmSimpleModelNodeType nodeType, FlowNode currentFlowNode) {
switch (nodeType) { switch (nodeType) {
case START_EVENT_NODE: case START_EVENT:
case APPROVE_USER_NODE: { case USER_TASK: {
List<SequenceFlow> outgoingFlows = currentFlowNode.getOutgoingFlows(); List<SequenceFlow> outgoingFlows = currentFlowNode.getOutgoingFlows();
if (CollUtil.isNotEmpty(outgoingFlows) && outgoingFlows.size() > 1) { if (CollUtil.isNotEmpty(outgoingFlows) && outgoingFlows.size() > 1) {
throw exception(CONVERT_TO_SIMPLE_MODEL_NOT_SUPPORT); throw exception(CONVERT_TO_SIMPLE_MODEL_NOT_SUPPORT);
} }
validIsSupportFlowNode(outgoingFlows.get(0).getTargetFlowElement()); validIsSupportFlowNode(CollUtil.getFirst(outgoingFlows).getTargetFlowElement());
return outgoingFlows; return outgoingFlows;
} }
default: { default: {

View File

@ -47,7 +47,7 @@ public class BpmProcessInstanceCopyServiceImpl implements BpmProcessInstanceCopy
// TODO @芋艿这里多加了一个 name // TODO @芋艿这里多加了一个 name
@Override @Override
public void createProcessInstanceCopy(Collection<Long> userIds, String processInstanceId, String taskId, String taskName) { public void createProcessInstanceCopy(Collection<Long> userIds, String processInstanceId, String taskId, String taskName) {
// 1.1 校验任务存在 暂时去掉这个校验. 因为任务可能仿钉钉快搭的抄送节点(ScriptTask) TODO jason抄送节点会没有来源的 taskId // 1.1 校验任务存在 暂时去掉这个校验. 因为任务可能仿钉钉快搭的抄送节点(ScriptTask) TODO jason抄送节点会没有来源的 taskId @芋艿 是否校验一下 传递进来的 id 不为空就行
// Task task = taskService.getTask(taskId); // Task task = taskService.getTask(taskId);
// if (ObjectUtil.isNull(task)) { // if (ObjectUtil.isNull(task)) {
// throw exception(ErrorCodeConstants.TASK_NOT_EXISTS); // throw exception(ErrorCodeConstants.TASK_NOT_EXISTS);

View File

@ -9,7 +9,8 @@ import org.springframework.stereotype.Service;
import java.util.Set; import java.util.Set;
/** /**
* 仿钉钉快搭各个节点 Service TODO @jason注释要有空行哈 * 仿钉钉快搭各个节点 Service
*
* @author jason * @author jason
*/ */
@Service @Service
@ -21,7 +22,8 @@ public class BpmSimpleNodeService {
private BpmProcessInstanceCopyService processInstanceCopyService; private BpmProcessInstanceCopyService processInstanceCopyService;
/** /**
* 仿钉钉快搭抄送 TODO @jason注释要有空行哈 * 仿钉钉快搭抄送
*
* @param execution 执行的任务(ScriptTask) * @param execution 执行的任务(ScriptTask)
*/ */
public Boolean copy(DelegateExecution execution) { public Boolean copy(DelegateExecution execution) {