简介:工作流开发过程中,因为国内的需求变态原因,会有流程图节点任意跳转的需求
一.普通的流程跳转设计
因为普通流程只需要根据连线进行节点跳转就行直接贴代码:
//获取流程定义
Process process = repositoryService.getBpmnModel(processDefinitionId).getMainProcess();
//获取目标节点定义
FlowNode targetNode = (FlowNode) process.getFlowElement(flowElementId);
List<Execution> executionList = runtimeService.createExecutionQuery().parentId(taskList.get(0).getProcessInstanceId()).list();
Execution newExecution = executionList.remove(0);
//流程执行到来源节点
managementService.executeCommand(new SetNodeAndGoCmd(targetNode, newExecution.getId()));
if (CollectionUtil.isNotEmpty(executionList)) {
List<String> ids = executionList.stream().map(Execution::getId).collect(Collectors.toList());
managementService.executeCommand(new DeleteExecutionCmd(ids));
}
//删除当前运行任务
for (Task currentTask : taskList) {
managementService.executeCommand(new DeleteTaskCmd(currentTask.getId()));
}
@Slf4j
public class SetNodeAndGoCmd implements Command<Void> {
private FlowNode flowElement;
private String executionId;
private String executionName;
private String startUserId;
public SetNodeAndGoCmd(FlowNode flowElement, String executionId) {
this.flowElement = flowElement;
this.executionId = executionId;
}
public SetNodeAndGoCmd(FlowNode flowElement, String executionId, String executionName, String startUserId) {
this.flowElement = flowElement;
this.executionId = executionId;
this.executionName = executionName;
this.startUserId = startUserId;
}
@Override
public Void execute(CommandContext commandContext) {
//获取目标节点的来源连线
List<SequenceFlow> flows = flowElement.getIncomingFlows();
if (flows == null || flows.size() < 1) {
throw new ActivitiException("回退错误,目标节点没有来源连线");
}
//随便流程当前的主线,时当前执行计划为,从连线流转到目标节点,实现跳转
ExecutionEntity executionEntity = commandContext.getExecutionEntityManager().findById(executionId);
executionEntity.setCurrentFlowElement(flows.get(0));
if (StrUtil.isNotEmpty(executionName)) {
executionEntity.setName(executionName);
}
if (StrUtil.isNotEmpty(startUserId)) {
executionEntity.setStartUserId(startUserId);
}
log.info("SetNodeAndGoCmd执行节点跳转:processInstanceId:{},来源:{},出线:{}", executionEntity.getProcessInstanceId(), flows.get(0).getSourceRef(), flows.get(0).getTargetRef());
commandContext.getAgenda().planTakeOutgoingSequenceFlowsOperation(executionEntity, true);
return null;
}
}
@Slf4j
public class DeleteExecutionCmd implements Command<Void>, Serializable {
private List<String> executionIds;
public DeleteExecutionCmd(List<String> executionIds) {
super();
this.executionIds = executionIds;
}
/**
* @param
* @param
* @return
*/
@Override
public Void execute(CommandContext commandContext) {
//删除当前任务,来源任务
ExecutionEntityManager executionEntityManager = (ExecutionEntityManagerImpl) commandContext.getExecutionEntityManager();
for (String id : executionIds) {
executionEntityManager.delete(id);
}
return null;
}
}
@Slf4j
public class DeleteTaskCmd extends NeedsActiveTaskCmd<String> {
public DeleteTaskCmd(String taskId) {
super(taskId);
}
/**
* 这里继承了NeedsActiveTaskCmd,主要时很多跳转业务场景下,要求不能时挂起任务。可以直接继承Command即可
*
* @param commandContext
* @param currentTask
* @return
*/
@Override
public String execute(CommandContext commandContext, TaskEntity currentTask) {
log.info("DeleteTaskCmd执行任务删除:ID:{},name:{}", currentTask.getId(), currentTask.getName());
//获取所需服务
TaskEntityManagerImpl taskEntityManager = (TaskEntityManagerImpl) commandContext.getTaskEntityManager();
//获取当前任务的来源任务及来源节点信息
ExecutionEntity executionEntity = currentTask.getExecution();
//删除当前任务,来源任务
taskEntityManager.deleteTask(currentTask, "", false, false);
HistoricTaskInstanceEntityManager historicTaskInstanceEntityManager = (HistoricTaskInstanceEntityManagerImpl) commandContext.getHistoricTaskInstanceEntityManager();
historicTaskInstanceEntityManager.delete(currentTask.getId());
return executionEntity == null ? "" : executionEntity.getId();
}
二.并行网关跳转
因为并行网关每条支线execution都会创建,所以按照上面的方式直接跳转到其中一个节点是不可以的,并行网关不完整。有两个解决方案:
1.获取跳转节点所在的并行网关,或者网关开始的outgoing出线,每一条出现都新增一条任务和execution记录,这样网关就可以继续执行汇聚
2.业务限制网关内的节点不允许驳回,我们采用第二种方案
方案二要解决的问题:如果判断一个节点是否被并行网关包围?
代码如下:
这边代码可以优化,因为递归会有很多重复节点被扫描,达不到O(n)时间复杂度,如果用备忘录去剪枝优化需要注意一个问题,stack是全局的,如果已经判断的节点直接返回会导致stack数据不对,要做更多的判断,因为我们流程图不会有几百个节点,没有必要去优化
@Override
public Map<String, Boolean> isSurroundedWithParallelGateway(String processDefinitionId) {
Map<String, Boolean> results = MapUtil.newHashMap(16);
BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
Process process = bpmnModel.getProcesses().get(0);
StartEvent startEvent = process.findFlowElementsOfType(org.activiti.bpmn.model.StartEvent.class).get(0);
//从开始节点开始遍历流程图,如果遇到并行网关入栈,遍历所有节点入栈直到遇到并行网关出栈,并切进行判断是否存在目标节点
//时间复杂度O(n)只需要遍历一遍,空间复杂度lgO(n)
recursionParallelGateway(startEvent, new Stack<>(), results);
return results;
}
private void recursionParallelGateway(FlowNode currentNode, Stack<FlowNode> stack, Map<String, Boolean> result) {
if (currentNode instanceof EndEvent) {
return;
}
List<SequenceFlow> outComing = currentNode.getOutgoingFlows();
if (CollectionUtil.isEmpty(outComing)) {
return;
}
//并行网关开始
if (currentNode.getIncomingFlows().size() == 1 && currentNode instanceof ParallelGateway) {
for (int i = 0; i < currentNode.getOutgoingFlows().size(); i++) {
stack.push(currentNode);
}
}
if (currentNode instanceof ParallelGateway) {
log.info("并行网关参数:{},{},{}", currentNode.getId(), currentNode.getIncomingFlows().size(), currentNode.getOutgoingFlows());
}
//并行网关结束
if (currentNode.getIncomingFlows().size() > 1 && currentNode instanceof ParallelGateway && !stack.isEmpty()) {
stack.pop();
}
if (currentNode instanceof UserTask) {
if (!stack.isEmpty()) {
result.put(currentNode.getId(), true);
} else {
result.put(currentNode.getId(), false);
}
}
for (SequenceFlow flow : outComing) {
FlowNode flowElement = (FlowNode) flow.getTargetFlowElement();
recursionParallelGateway(flowElement, stack, result);
}
}