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

This commit is contained in:
jason 2024-04-05 12:59:37 +08:00
parent d88718071d
commit d9758636b1
6 changed files with 90 additions and 33 deletions

View File

@ -20,8 +20,10 @@ public enum BpmSimpleModelNodeType implements IntArrayValuable {
// TODO @jaosn-1014-2 是前端已经定义好的么感觉未来可以考虑搞成和 BPMN 尽量一致的单词哈类似 usertask 用户审批 // TODO @jaosn-1014-2 是前端已经定义好的么感觉未来可以考虑搞成和 BPMN 尽量一致的单词哈类似 usertask 用户审批
START_EVENT_NODE(0, "开始节点"), START_EVENT_NODE(0, "开始节点"),
APPROVE_USER_NODE (1, "审批人节点"), APPROVE_USER_NODE (1, "审批人节点"),
// 抄送人节点对应 BPMN ScriptTask. 使用ScriptTask 原因好像 ServiceTask 自定义属性不能写入 XML
SCRIPT_TASK_NODE(2, "抄送人节点"),
EXCLUSIVE_GATEWAY_NODE(4, "排他网关"), EXCLUSIVE_GATEWAY_NODE(4, "排他网关"),
END_NODE(-2, "结束节点"); END_EVENT_NODE(-2, "结束节点");
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmSimpleModelNodeType::getType).toArray(); public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmSimpleModelNodeType::getType).toArray();

View File

@ -13,6 +13,7 @@ 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.ArrayList;
@ -20,11 +21,15 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; 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( return NumberUtils.parseInt(userTask.getAttributeValue(
BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY)); BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY));
@ -379,26 +384,27 @@ public class BpmnModelUtils {
Assert.notNull(nodeType, "模型节点类型不支持"); Assert.notNull(nodeType, "模型节点类型不支持");
switch (nodeType) { switch (nodeType) {
case START_EVENT_NODE: case START_EVENT_NODE:
case APPROVE_USER_NODE: { case APPROVE_USER_NODE:
case SCRIPT_TASK_NODE: {
addBpmnSequenceFlowElement(mainProcess, node.getId(), childNode.getId(), null, null); addBpmnSequenceFlowElement(mainProcess, node.getId(), childNode.getId(), null, null);
// 递归调用后续节点 // 递归调用后续节点
addBpmnSequenceFlow(mainProcess, childNode,endId); addBpmnSequenceFlow(mainProcess, childNode, endId);
break; break;
} }
case EXCLUSIVE_GATEWAY_NODE: { case EXCLUSIVE_GATEWAY_NODE: {
String gateWayEndId = ( childNode == null || childNode.getId() == null ) ? BpmnModelConstants.END_EVENT_ID : childNode.getId(); String gateWayEndId = (childNode == null || childNode.getId() == null) ? BpmnModelConstants.END_EVENT_ID : childNode.getId();
List<BpmSimpleModelNodeVO> conditionNodes = node.getConditionNodes(); List<BpmSimpleModelNodeVO> conditionNodes = node.getConditionNodes();
Assert.notEmpty(conditionNodes, "网关节点的条件节点不能为空"); Assert.notEmpty(conditionNodes, "网关节点的条件节点不能为空");
for (int i = 0; i < conditionNodes.size(); i++) { for (int i = 0; i < conditionNodes.size(); i++) {
BpmSimpleModelNodeVO item = conditionNodes.get(i); BpmSimpleModelNodeVO item = conditionNodes.get(i);
BpmSimpleModelNodeVO nextNodeOnCondition = getNextNodeOnCondition(item); BpmSimpleModelNodeVO nextNodeOnCondition = item.getChildNode();
if (nextNodeOnCondition != null && nextNodeOnCondition.getId() != null) { if (nextNodeOnCondition != null && nextNodeOnCondition.getId() != null) {
addBpmnSequenceFlowElement(mainProcess, node.getId(), nextNodeOnCondition.getId(), addBpmnSequenceFlowElement(mainProcess, node.getId(), nextNodeOnCondition.getId(),
String.format("%s_SequenceFlow_%d", node.getId(), i+1), null); String.format("%s_SequenceFlow_%d", node.getId(), i + 1), null);
addBpmnSequenceFlow(mainProcess, nextNodeOnCondition, gateWayEndId); addBpmnSequenceFlow(mainProcess, nextNodeOnCondition, gateWayEndId);
} else { } else {
addBpmnSequenceFlowElement(mainProcess, node.getId(), gateWayEndId, addBpmnSequenceFlowElement(mainProcess, node.getId(), gateWayEndId,
String.format("%s_SequenceFlow_%d", node.getId(), i+1), null); String.format("%s_SequenceFlow_%d", node.getId(), i + 1), null);
} }
} }
// 递归调用后续节点 // 递归调用后续节点
@ -412,10 +418,6 @@ public class BpmnModelUtils {
} }
private static BpmSimpleModelNodeVO getNextNodeOnCondition(BpmSimpleModelNodeVO conditionNode) {
return conditionNode.getChildNode();
}
private static void addBpmnSequenceFlowElement(Process mainProcess, String sourceId, String targetId, String seqFlowId, String conditionExpression) { private static void addBpmnSequenceFlowElement(Process mainProcess, String sourceId, String targetId, String seqFlowId, String conditionExpression) {
SequenceFlow sequenceFlow = new SequenceFlow(sourceId, targetId); SequenceFlow sequenceFlow = new SequenceFlow(sourceId, targetId);
if (StrUtil.isNotEmpty(conditionExpression)) { if (StrUtil.isNotEmpty(conditionExpression)) {
@ -439,7 +441,10 @@ public class BpmnModelUtils {
addBpmnStartEventNode(mainProcess, simpleModelNode); addBpmnStartEventNode(mainProcess, simpleModelNode);
break; break;
case APPROVE_USER_NODE: case APPROVE_USER_NODE:
addBpmnUserTaskEventNode(mainProcess, simpleModelNode); addBpmnUserTaskNode(mainProcess, simpleModelNode);
break;
case SCRIPT_TASK_NODE:
addBpmnScriptTaSskNode(mainProcess, simpleModelNode);
break; break;
case EXCLUSIVE_GATEWAY_NODE: case EXCLUSIVE_GATEWAY_NODE:
addBpmnExclusiveGatewayNode(mainProcess, simpleModelNode); addBpmnExclusiveGatewayNode(mainProcess, simpleModelNode);
@ -467,6 +472,25 @@ public class BpmnModelUtils {
} }
} }
private static void addBpmnScriptTaSskNode(Process mainProcess, BpmSimpleModelNodeVO node) {
ScriptTask scriptTask = new ScriptTask();
scriptTask.setId(node.getId());
scriptTask.setName(node.getName());
scriptTask.setScriptFormat(ScriptingEngines.DEFAULT_SCRIPTING_LANGUAGE);
scriptTask.setScript(BPMN_SIMPLE_COPY_EXECUTION_SCRIPT);
// 添加自定义属性
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) { private static void addBpmnExclusiveGatewayNode(Process mainProcess, BpmSimpleModelNodeVO node) {
Assert.notEmpty(node.getConditionNodes(), "网关节点的条件节点不能为空"); Assert.notEmpty(node.getConditionNodes(), "网关节点的条件节点不能为空");
ExclusiveGateway exclusiveGateway = new ExclusiveGateway(); ExclusiveGateway exclusiveGateway = new ExclusiveGateway();
@ -483,25 +507,21 @@ public class BpmnModelUtils {
mainProcess.addFlowElement(endEvent); mainProcess.addFlowElement(endEvent);
} }
private static void addBpmnUserTaskEventNode(Process mainProcess, BpmSimpleModelNodeVO node) { private static void addBpmnUserTaskNode(Process mainProcess, BpmSimpleModelNodeVO node) {
UserTask userTask = new UserTask(); UserTask userTask = new UserTask();
userTask.setId(node.getId()); userTask.setId(node.getId());
userTask.setName(node.getName()); userTask.setName(node.getName());
Integer candidateStrategy = MapUtil.getInt(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY); addExtensionAttributes(node, userTask);
// 添加自定义属性
addExtensionAttribute(userTask, BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY,
candidateStrategy == null ? null : String.valueOf(candidateStrategy));
addExtensionAttribute(userTask, BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM,
MapUtil.getStr(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_PARAM));
mainProcess.addFlowElement(userTask); mainProcess.addFlowElement(userTask);
} }
private static void addExtensionAttribute(FlowElement element, String namespace, String name, String value) { private static void addExtensionAttributes(FlowElement element, String namespace, String name, String value) {
if (value == null) { if (value == null) {
return; return;
} }
ExtensionAttribute extensionAttribute = new ExtensionAttribute(name, value); ExtensionAttribute extensionAttribute = new ExtensionAttribute(name, value);
extensionAttribute.setNamespace(namespace); extensionAttribute.setNamespace(namespace);
extensionAttribute.setNamespacePrefix(FLOWABLE_EXTENSIONS_PREFIX);
element.addAttribute(extensionAttribute); element.addAttribute(extensionAttribute);
} }

View File

@ -17,9 +17,11 @@ public interface BpmProcessInstanceCopyService {
* 流程实例的抄送 * 流程实例的抄送
* *
* @param userIds 抄送的用户编号 * @param userIds 抄送的用户编号
* @param taskId 流程任务编号 * @param processInstanceId 流程编号
* @param taskId 任务编号
* @param taskName 任务名称
*/ */
void createProcessInstanceCopy(Collection<Long> userIds, String taskId); void createProcessInstanceCopy(Collection<Long> userIds, String processInstanceId, String taskId, String taskName);
/** /**
* 获得抄送的流程的分页 * 获得抄送的流程的分页

View File

@ -1,6 +1,5 @@
package cn.iocoder.yudao.module.bpm.service.task; package cn.iocoder.yudao.module.bpm.service.task;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCopyPageReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCopyPageReqVO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO; import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO;
@ -11,7 +10,6 @@ import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ProcessInstance; import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
@ -47,14 +45,14 @@ public class BpmProcessInstanceCopyServiceImpl implements BpmProcessInstanceCopy
private BpmProcessDefinitionService processDefinitionService; private BpmProcessDefinitionService processDefinitionService;
@Override @Override
public void createProcessInstanceCopy(Collection<Long> userIds, String taskId) { public void createProcessInstanceCopy(Collection<Long> userIds, String processInstanceId, String taskId, String taskName) {
// 1.1 校验任务存在 // 1.1 校验任务存在 暂时去掉这个校验. 因为任务可能仿钉钉快搭的抄送节点(ScriptTask)
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);
} // }
// 1.2 校验流程实例存在 // 1.2 校验流程实例存在
String processInstanceId = task.getProcessInstanceId(); // String processInstanceId = task.getProcessInstanceId();
ProcessInstance processInstance = processInstanceService.getProcessInstance(processInstanceId); ProcessInstance processInstance = processInstanceService.getProcessInstance(processInstanceId);
if (processInstance == null) { if (processInstance == null) {
throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS);
@ -70,7 +68,7 @@ public class BpmProcessInstanceCopyServiceImpl implements BpmProcessInstanceCopy
List<BpmProcessInstanceCopyDO> copyList = convertList(userIds, userId -> new BpmProcessInstanceCopyDO() List<BpmProcessInstanceCopyDO> copyList = convertList(userIds, userId -> new BpmProcessInstanceCopyDO()
.setUserId(userId).setStartUserId(Long.valueOf(processInstance.getStartUserId())) .setUserId(userId).setStartUserId(Long.valueOf(processInstance.getStartUserId()))
.setProcessInstanceId(processInstanceId).setProcessInstanceName(processInstance.getName()) .setProcessInstanceId(processInstanceId).setProcessInstanceName(processInstance.getName())
.setCategory(processDefinition.getCategory()).setTaskId(taskId).setTaskName(task.getName())); .setCategory(processDefinition.getCategory()).setTaskId(taskId).setTaskName(taskName));
processInstanceCopyMapper.insertBatch(copyList); processInstanceCopyMapper.insertBatch(copyList);
} }

View File

@ -0,0 +1,34 @@
package cn.iocoder.yudao.module.bpm.service.task;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker;
import jakarta.annotation.Resource;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.stereotype.Service;
import java.util.Set;
/**
* 仿钉钉快搭各个节点 Service
* @author jason
*/
@Service
public class BpmSimpleNodeService {
@Resource
private BpmTaskCandidateInvoker taskCandidateInvoker;
@Resource
private BpmProcessInstanceCopyService processInstanceCopyService;
/**
* 仿钉钉快搭抄送
* @param execution 执行的任务(ScriptTask)
*/
public Boolean copy(DelegateExecution execution) {
Set<Long> userIds = taskCandidateInvoker.calculateUsers(execution);
FlowElement currentFlowElement = execution.getCurrentFlowElement();
processInstanceCopyService.createProcessInstanceCopy(userIds, execution.getProcessInstanceId(),
currentFlowElement.getId(), currentFlowElement.getName());
return Boolean.TRUE;
}
}

View File

@ -185,7 +185,8 @@ public class BpmTaskServiceImpl implements BpmTaskService {
// 2. 抄送用户 // 2. 抄送用户
if (CollUtil.isNotEmpty(reqVO.getCopyUserIds())) { if (CollUtil.isNotEmpty(reqVO.getCopyUserIds())) {
processInstanceCopyService.createProcessInstanceCopy(reqVO.getCopyUserIds(), reqVO.getId()); processInstanceCopyService.createProcessInstanceCopy(reqVO.getCopyUserIds(), instance.getProcessInstanceId(),
reqVO.getId(), task.getName());
} }
// 情况一被委派的任务不调用 complete 去完成任务 // 情况一被委派的任务不调用 complete 去完成任务