Commit d9758636b191a4b7d0201b8d3943c15c5b7b220a

Authored by jason
1 parent d8871807

仿钉钉流程设计-抄送节点实现

yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java
@@ -20,8 +20,10 @@ public enum BpmSimpleModelNodeType implements IntArrayValuable { @@ -20,8 +20,10 @@ public enum BpmSimpleModelNodeType implements IntArrayValuable {
20 // TODO @jaosn:-1、0、1、4、-2 是前端已经定义好的么?感觉未来可以考虑搞成和 BPMN 尽量一致的单词哈;类似 usertask 用户审批; 20 // TODO @jaosn:-1、0、1、4、-2 是前端已经定义好的么?感觉未来可以考虑搞成和 BPMN 尽量一致的单词哈;类似 usertask 用户审批;
21 START_EVENT_NODE(0, "开始节点"), 21 START_EVENT_NODE(0, "开始节点"),
22 APPROVE_USER_NODE (1, "审批人节点"), 22 APPROVE_USER_NODE (1, "审批人节点"),
  23 + // 抄送人节点、对应 BPMN 的 ScriptTask. 使用ScriptTask 原因。好像 ServiceTask 自定义属性不能写入 XML
  24 + SCRIPT_TASK_NODE(2, "抄送人节点"),
23 EXCLUSIVE_GATEWAY_NODE(4, "排他网关"), 25 EXCLUSIVE_GATEWAY_NODE(4, "排他网关"),
24 - END_NODE(-2, "结束节点"); 26 + END_EVENT_NODE(-2, "结束节点");
25 27
26 public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmSimpleModelNodeType::getType).toArray(); 28 public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmSimpleModelNodeType::getType).toArray();
27 29
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java
@@ -13,6 +13,7 @@ import org.flowable.bpmn.BpmnAutoLayout; @@ -13,6 +13,7 @@ import org.flowable.bpmn.BpmnAutoLayout;
13 import org.flowable.bpmn.converter.BpmnXMLConverter; 13 import org.flowable.bpmn.converter.BpmnXMLConverter;
14 import org.flowable.bpmn.model.Process; 14 import org.flowable.bpmn.model.Process;
15 import org.flowable.bpmn.model.*; 15 import org.flowable.bpmn.model.*;
  16 +import org.flowable.common.engine.impl.scripting.ScriptingEngines;
16 import org.flowable.common.engine.impl.util.io.BytesStreamSource; 17 import org.flowable.common.engine.impl.util.io.BytesStreamSource;
17 18
18 import java.util.ArrayList; 19 import java.util.ArrayList;
@@ -20,11 +21,15 @@ import java.util.HashSet; @@ -20,11 +21,15 @@ import java.util.HashSet;
20 import java.util.List; 21 import java.util.List;
21 import java.util.Set; 22 import java.util.Set;
22 23
  24 +import static org.flowable.bpmn.constants.BpmnXMLConstants.*;
  25 +
23 /** 26 /**
24 * 流程模型转操作工具类 27 * 流程模型转操作工具类
25 */ 28 */
26 public class BpmnModelUtils { 29 public class BpmnModelUtils {
27 30
  31 + public static final String BPMN_SIMPLE_COPY_EXECUTION_SCRIPT = "#{bpmSimpleNodeService.copy(execution)}";
  32 +
28 public static Integer parseCandidateStrategy(FlowElement userTask) { 33 public static Integer parseCandidateStrategy(FlowElement userTask) {
29 return NumberUtils.parseInt(userTask.getAttributeValue( 34 return NumberUtils.parseInt(userTask.getAttributeValue(
30 BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY)); 35 BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY));
@@ -379,26 +384,27 @@ public class BpmnModelUtils { @@ -379,26 +384,27 @@ public class BpmnModelUtils {
379 Assert.notNull(nodeType, "模型节点类型不支持"); 384 Assert.notNull(nodeType, "模型节点类型不支持");
380 switch (nodeType) { 385 switch (nodeType) {
381 case START_EVENT_NODE: 386 case START_EVENT_NODE:
382 - case APPROVE_USER_NODE: { 387 + case APPROVE_USER_NODE:
  388 + case SCRIPT_TASK_NODE: {
383 addBpmnSequenceFlowElement(mainProcess, node.getId(), childNode.getId(), null, null); 389 addBpmnSequenceFlowElement(mainProcess, node.getId(), childNode.getId(), null, null);
384 // 递归调用后续节点 390 // 递归调用后续节点
385 - addBpmnSequenceFlow(mainProcess, childNode,endId); 391 + addBpmnSequenceFlow(mainProcess, childNode, endId);
386 break; 392 break;
387 } 393 }
388 case EXCLUSIVE_GATEWAY_NODE: { 394 case EXCLUSIVE_GATEWAY_NODE: {
389 - String gateWayEndId = ( childNode == null || childNode.getId() == null ) ? BpmnModelConstants.END_EVENT_ID : childNode.getId(); 395 + String gateWayEndId = (childNode == null || childNode.getId() == null) ? BpmnModelConstants.END_EVENT_ID : childNode.getId();
390 List<BpmSimpleModelNodeVO> conditionNodes = node.getConditionNodes(); 396 List<BpmSimpleModelNodeVO> conditionNodes = node.getConditionNodes();
391 Assert.notEmpty(conditionNodes, "网关节点的条件节点不能为空"); 397 Assert.notEmpty(conditionNodes, "网关节点的条件节点不能为空");
392 for (int i = 0; i < conditionNodes.size(); i++) { 398 for (int i = 0; i < conditionNodes.size(); i++) {
393 BpmSimpleModelNodeVO item = conditionNodes.get(i); 399 BpmSimpleModelNodeVO item = conditionNodes.get(i);
394 - BpmSimpleModelNodeVO nextNodeOnCondition = getNextNodeOnCondition(item); 400 + BpmSimpleModelNodeVO nextNodeOnCondition = item.getChildNode();
395 if (nextNodeOnCondition != null && nextNodeOnCondition.getId() != null) { 401 if (nextNodeOnCondition != null && nextNodeOnCondition.getId() != null) {
396 addBpmnSequenceFlowElement(mainProcess, node.getId(), nextNodeOnCondition.getId(), 402 addBpmnSequenceFlowElement(mainProcess, node.getId(), nextNodeOnCondition.getId(),
397 - String.format("%s_SequenceFlow_%d", node.getId(), i+1), null); 403 + String.format("%s_SequenceFlow_%d", node.getId(), i + 1), null);
398 addBpmnSequenceFlow(mainProcess, nextNodeOnCondition, gateWayEndId); 404 addBpmnSequenceFlow(mainProcess, nextNodeOnCondition, gateWayEndId);
399 } else { 405 } else {
400 addBpmnSequenceFlowElement(mainProcess, node.getId(), gateWayEndId, 406 addBpmnSequenceFlowElement(mainProcess, node.getId(), gateWayEndId,
401 - String.format("%s_SequenceFlow_%d", node.getId(), i+1), null); 407 + String.format("%s_SequenceFlow_%d", node.getId(), i + 1), null);
402 } 408 }
403 } 409 }
404 // 递归调用后续节点 410 // 递归调用后续节点
@@ -412,10 +418,6 @@ public class BpmnModelUtils { @@ -412,10 +418,6 @@ public class BpmnModelUtils {
412 418
413 } 419 }
414 420
415 - private static BpmSimpleModelNodeVO getNextNodeOnCondition(BpmSimpleModelNodeVO conditionNode) {  
416 - return conditionNode.getChildNode();  
417 - }  
418 -  
419 private static void addBpmnSequenceFlowElement(Process mainProcess, String sourceId, String targetId, String seqFlowId, String conditionExpression) { 421 private static void addBpmnSequenceFlowElement(Process mainProcess, String sourceId, String targetId, String seqFlowId, String conditionExpression) {
420 SequenceFlow sequenceFlow = new SequenceFlow(sourceId, targetId); 422 SequenceFlow sequenceFlow = new SequenceFlow(sourceId, targetId);
421 if (StrUtil.isNotEmpty(conditionExpression)) { 423 if (StrUtil.isNotEmpty(conditionExpression)) {
@@ -439,7 +441,10 @@ public class BpmnModelUtils { @@ -439,7 +441,10 @@ public class BpmnModelUtils {
439 addBpmnStartEventNode(mainProcess, simpleModelNode); 441 addBpmnStartEventNode(mainProcess, simpleModelNode);
440 break; 442 break;
441 case APPROVE_USER_NODE: 443 case APPROVE_USER_NODE:
442 - addBpmnUserTaskEventNode(mainProcess, simpleModelNode); 444 + addBpmnUserTaskNode(mainProcess, simpleModelNode);
  445 + break;
  446 + case SCRIPT_TASK_NODE:
  447 + addBpmnScriptTaSskNode(mainProcess, simpleModelNode);
443 break; 448 break;
444 case EXCLUSIVE_GATEWAY_NODE: 449 case EXCLUSIVE_GATEWAY_NODE:
445 addBpmnExclusiveGatewayNode(mainProcess, simpleModelNode); 450 addBpmnExclusiveGatewayNode(mainProcess, simpleModelNode);
@@ -467,6 +472,25 @@ public class BpmnModelUtils { @@ -467,6 +472,25 @@ public class BpmnModelUtils {
467 } 472 }
468 } 473 }
469 474
  475 + private static void addBpmnScriptTaSskNode(Process mainProcess, BpmSimpleModelNodeVO node) {
  476 + ScriptTask scriptTask = new ScriptTask();
  477 + scriptTask.setId(node.getId());
  478 + scriptTask.setName(node.getName());
  479 + scriptTask.setScriptFormat(ScriptingEngines.DEFAULT_SCRIPTING_LANGUAGE);
  480 + scriptTask.setScript(BPMN_SIMPLE_COPY_EXECUTION_SCRIPT);
  481 + // 添加自定义属性
  482 + addExtensionAttributes(node, scriptTask);
  483 + mainProcess.addFlowElement(scriptTask);
  484 + }
  485 +
  486 + private static void addExtensionAttributes(BpmSimpleModelNodeVO node, FlowElement flowElement) {
  487 + Integer candidateStrategy = MapUtil.getInt(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY);
  488 + addExtensionAttributes(flowElement, BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY,
  489 + candidateStrategy == null ? null : String.valueOf(candidateStrategy));
  490 + addExtensionAttributes(flowElement, BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM,
  491 + MapUtil.getStr(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_PARAM));
  492 + }
  493 +
470 private static void addBpmnExclusiveGatewayNode(Process mainProcess, BpmSimpleModelNodeVO node) { 494 private static void addBpmnExclusiveGatewayNode(Process mainProcess, BpmSimpleModelNodeVO node) {
471 Assert.notEmpty(node.getConditionNodes(), "网关节点的条件节点不能为空"); 495 Assert.notEmpty(node.getConditionNodes(), "网关节点的条件节点不能为空");
472 ExclusiveGateway exclusiveGateway = new ExclusiveGateway(); 496 ExclusiveGateway exclusiveGateway = new ExclusiveGateway();
@@ -483,25 +507,21 @@ public class BpmnModelUtils { @@ -483,25 +507,21 @@ public class BpmnModelUtils {
483 mainProcess.addFlowElement(endEvent); 507 mainProcess.addFlowElement(endEvent);
484 } 508 }
485 509
486 - private static void addBpmnUserTaskEventNode(Process mainProcess, BpmSimpleModelNodeVO node) { 510 + private static void addBpmnUserTaskNode(Process mainProcess, BpmSimpleModelNodeVO node) {
487 UserTask userTask = new UserTask(); 511 UserTask userTask = new UserTask();
488 userTask.setId(node.getId()); 512 userTask.setId(node.getId());
489 userTask.setName(node.getName()); 513 userTask.setName(node.getName());
490 - Integer candidateStrategy = MapUtil.getInt(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY);  
491 - // 添加自定义属性  
492 - addExtensionAttribute(userTask, BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY,  
493 - candidateStrategy == null ? null : String.valueOf(candidateStrategy));  
494 - addExtensionAttribute(userTask, BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM,  
495 - MapUtil.getStr(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_PARAM)); 514 + addExtensionAttributes(node, userTask);
496 mainProcess.addFlowElement(userTask); 515 mainProcess.addFlowElement(userTask);
497 } 516 }
498 517
499 - private static void addExtensionAttribute(FlowElement element, String namespace, String name, String value) { 518 + private static void addExtensionAttributes(FlowElement element, String namespace, String name, String value) {
500 if (value == null) { 519 if (value == null) {
501 return; 520 return;
502 } 521 }
503 ExtensionAttribute extensionAttribute = new ExtensionAttribute(name, value); 522 ExtensionAttribute extensionAttribute = new ExtensionAttribute(name, value);
504 extensionAttribute.setNamespace(namespace); 523 extensionAttribute.setNamespace(namespace);
  524 + extensionAttribute.setNamespacePrefix(FLOWABLE_EXTENSIONS_PREFIX);
505 element.addAttribute(extensionAttribute); 525 element.addAttribute(extensionAttribute);
506 } 526 }
507 527
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyService.java
@@ -17,9 +17,11 @@ public interface BpmProcessInstanceCopyService { @@ -17,9 +17,11 @@ public interface BpmProcessInstanceCopyService {
17 * 流程实例的抄送 17 * 流程实例的抄送
18 * 18 *
19 * @param userIds 抄送的用户编号 19 * @param userIds 抄送的用户编号
20 - * @param taskId 流程任务编号 20 + * @param processInstanceId 流程编号
  21 + * @param taskId 任务编号
  22 + * @param taskName 任务名称
21 */ 23 */
22 - void createProcessInstanceCopy(Collection<Long> userIds, String taskId); 24 + void createProcessInstanceCopy(Collection<Long> userIds, String processInstanceId, String taskId, String taskName);
23 25
24 /** 26 /**
25 * 获得抄送的流程的分页 27 * 获得抄送的流程的分页
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java
1 package cn.iocoder.yudao.module.bpm.service.task; 1 package cn.iocoder.yudao.module.bpm.service.task;
2 2
3 -import cn.hutool.core.util.ObjectUtil;  
4 import cn.iocoder.yudao.framework.common.pojo.PageResult; 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCopyPageReqVO; 4 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCopyPageReqVO;
6 import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO; 5 import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO;
@@ -11,7 +10,6 @@ import jakarta.annotation.Resource; @@ -11,7 +10,6 @@ import jakarta.annotation.Resource;
11 import lombok.extern.slf4j.Slf4j; 10 import lombok.extern.slf4j.Slf4j;
12 import org.flowable.engine.repository.ProcessDefinition; 11 import org.flowable.engine.repository.ProcessDefinition;
13 import org.flowable.engine.runtime.ProcessInstance; 12 import org.flowable.engine.runtime.ProcessInstance;
14 -import org.flowable.task.api.Task;  
15 import org.springframework.context.annotation.Lazy; 13 import org.springframework.context.annotation.Lazy;
16 import org.springframework.stereotype.Service; 14 import org.springframework.stereotype.Service;
17 import org.springframework.validation.annotation.Validated; 15 import org.springframework.validation.annotation.Validated;
@@ -47,14 +45,14 @@ public class BpmProcessInstanceCopyServiceImpl implements BpmProcessInstanceCopy @@ -47,14 +45,14 @@ public class BpmProcessInstanceCopyServiceImpl implements BpmProcessInstanceCopy
47 private BpmProcessDefinitionService processDefinitionService; 45 private BpmProcessDefinitionService processDefinitionService;
48 46
49 @Override 47 @Override
50 - public void createProcessInstanceCopy(Collection<Long> userIds, String taskId) {  
51 - // 1.1 校验任务存在  
52 - Task task = taskService.getTask(taskId);  
53 - if (ObjectUtil.isNull(task)) {  
54 - throw exception(ErrorCodeConstants.TASK_NOT_EXISTS);  
55 - } 48 + public void createProcessInstanceCopy(Collection<Long> userIds, String processInstanceId, String taskId, String taskName) {
  49 + // 1.1 校验任务存在 暂时去掉这个校验. 因为任务可能仿钉钉快搭的抄送节点(ScriptTask)
  50 +// Task task = taskService.getTask(taskId);
  51 +// if (ObjectUtil.isNull(task)) {
  52 +// throw exception(ErrorCodeConstants.TASK_NOT_EXISTS);
  53 +// }
56 // 1.2 校验流程实例存在 54 // 1.2 校验流程实例存在
57 - String processInstanceId = task.getProcessInstanceId(); 55 +// String processInstanceId = task.getProcessInstanceId();
58 ProcessInstance processInstance = processInstanceService.getProcessInstance(processInstanceId); 56 ProcessInstance processInstance = processInstanceService.getProcessInstance(processInstanceId);
59 if (processInstance == null) { 57 if (processInstance == null) {
60 throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); 58 throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS);
@@ -70,7 +68,7 @@ public class BpmProcessInstanceCopyServiceImpl implements BpmProcessInstanceCopy @@ -70,7 +68,7 @@ public class BpmProcessInstanceCopyServiceImpl implements BpmProcessInstanceCopy
70 List<BpmProcessInstanceCopyDO> copyList = convertList(userIds, userId -> new BpmProcessInstanceCopyDO() 68 List<BpmProcessInstanceCopyDO> copyList = convertList(userIds, userId -> new BpmProcessInstanceCopyDO()
71 .setUserId(userId).setStartUserId(Long.valueOf(processInstance.getStartUserId())) 69 .setUserId(userId).setStartUserId(Long.valueOf(processInstance.getStartUserId()))
72 .setProcessInstanceId(processInstanceId).setProcessInstanceName(processInstance.getName()) 70 .setProcessInstanceId(processInstanceId).setProcessInstanceName(processInstance.getName())
73 - .setCategory(processDefinition.getCategory()).setTaskId(taskId).setTaskName(task.getName())); 71 + .setCategory(processDefinition.getCategory()).setTaskId(taskId).setTaskName(taskName));
74 processInstanceCopyMapper.insertBatch(copyList); 72 processInstanceCopyMapper.insertBatch(copyList);
75 } 73 }
76 74
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmSimpleNodeService.java 0 → 100644
  1 +package cn.iocoder.yudao.module.bpm.service.task;
  2 +
  3 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker;
  4 +import jakarta.annotation.Resource;
  5 +import org.flowable.bpmn.model.FlowElement;
  6 +import org.flowable.engine.delegate.DelegateExecution;
  7 +import org.springframework.stereotype.Service;
  8 +
  9 +import java.util.Set;
  10 +
  11 +/**
  12 + * 仿钉钉快搭各个节点 Service
  13 + * @author jason
  14 + */
  15 +@Service
  16 +public class BpmSimpleNodeService {
  17 +
  18 + @Resource
  19 + private BpmTaskCandidateInvoker taskCandidateInvoker;
  20 + @Resource
  21 + private BpmProcessInstanceCopyService processInstanceCopyService;
  22 +
  23 + /**
  24 + * 仿钉钉快搭抄送
  25 + * @param execution 执行的任务(ScriptTask)
  26 + */
  27 + public Boolean copy(DelegateExecution execution) {
  28 + Set<Long> userIds = taskCandidateInvoker.calculateUsers(execution);
  29 + FlowElement currentFlowElement = execution.getCurrentFlowElement();
  30 + processInstanceCopyService.createProcessInstanceCopy(userIds, execution.getProcessInstanceId(),
  31 + currentFlowElement.getId(), currentFlowElement.getName());
  32 + return Boolean.TRUE;
  33 + }
  34 +}
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java
@@ -185,7 +185,8 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -185,7 +185,8 @@ public class BpmTaskServiceImpl implements BpmTaskService {
185 185
186 // 2. 抄送用户 186 // 2. 抄送用户
187 if (CollUtil.isNotEmpty(reqVO.getCopyUserIds())) { 187 if (CollUtil.isNotEmpty(reqVO.getCopyUserIds())) {
188 - processInstanceCopyService.createProcessInstanceCopy(reqVO.getCopyUserIds(), reqVO.getId()); 188 + processInstanceCopyService.createProcessInstanceCopy(reqVO.getCopyUserIds(), instance.getProcessInstanceId(),
  189 + reqVO.getId(), task.getName());
189 } 190 }
190 191
191 // 情况一:被委派的任务,不调用 complete 去完成任务 192 // 情况一:被委派的任务,不调用 complete 去完成任务