1. 概述
流程回退一直以来是个老旧的难题,也一直没有好的解决方法,本文就来详述流程回退的解决办法。首先我们来分析一下不同的流程审批情况,并在对应的节点上实现流程的回退处理,以及应该提供的回退处理,当然我们说的回退不是指通过在流程节点上画一条线回退到想回的节点上。
2. 环境
SpringBoot2.X + activiti6
3. 准备测试
3.1 设计流程图
简单的流程回退小测试
场景描述:执行完任务A,到任务B时回退到任务A重新执行。
3.2 Bpmn文件
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" xmlns:tns="http://www.activiti.org/test" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" expressionLanguage="http://www.w3.org/1999/XPath" id="m1597282397003" name="" targetNamespace="http://www.activiti.org/test" typeLanguage="http://www.w3.org/2001/XMLSchema">
<process id="myProcess_1" isClosed="false" isExecutable="true" processType="None">
<startEvent id="_2" name="开始"/>
<userTask activiti:assignee="zhangsan" activiti:exclusive="true" id="_3" name="原始任务A"/>
<userTask activiti:assignee="lisi" activiti:exclusive="true" id="_4" name="原始任务B"/>
<endEvent id="_5" name="结束"/>
<sequenceFlow id="_6" sourceRef="_2" targetRef="_3"/>
<sequenceFlow id="_7" sourceRef="_3" targetRef="_4"/>
<sequenceFlow id="_8" sourceRef="_4" targetRef="_5"/>
</process>
<bpmndi:BPMNDiagram documentation="background=#000000;count=1;horizontalcount=1;orientation=0;width=842.4;height=1195.2;imageableWidth=832.4;imageableHeight=1185.2;imageableX=5.0;imageableY=5.0" id="Diagram-_1" name="New Diagram">
<bpmndi:BPMNPlane bpmnElement="myProcess_1">
<bpmndi:BPMNShape bpmnElement="_2" id="Shape-_2">
<omgdc:Bounds height="32.0" width="32.0" x="-5.0" y="325.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_3" id="Shape-_3">
<omgdc:Bounds height="55.0" width="85.0" x="140.0" y="310.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="55.0" width="85.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_4" id="Shape-_4">
<omgdc:Bounds height="55.0" width="85.0" x="350.0" y="310.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="55.0" width="85.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_5" id="Shape-_5">
<omgdc:Bounds height="32.0" width="32.0" x="545.0" y="320.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="_6" id="BPMNEdge__6" sourceElement="_2" targetElement="_3">
<omgdi:waypoint x="27.0" y="341.0"/>
<omgdi:waypoint x="140.0" y="337.5"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="_7" id="BPMNEdge__7" sourceElement="_3" targetElement="_4">
<omgdi:waypoint x="225.0" y="337.5"/>
<omgdi:waypoint x="350.0" y="337.5"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="_8" id="BPMNEdge__8" sourceElement="_4" targetElement="_5">
<omgdi:waypoint x="435.0" y="337.5"/>
<omgdi:waypoint x="545.0" y="336.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
3.3 删除任务自定义
package com.yb.utils;
import org.activiti.engine.impl.cmd.NeedsActiveTaskCmd;
import org.activiti.engine.impl.interceptor.CommandContext;
import org.activiti.engine.impl.persistence.entity.ExecutionEntity;
import org.activiti.engine.impl.persistence.entity.TaskEntity;
import org.activiti.engine.impl.persistence.entity.TaskEntityManagerImpl;
/**
* @author [email protected]
* @version 1.0
* @date 2020/8/13
* 删除当前运行时任务命令,并返回当前任务的执行对象id,
* 这里继承了NeedsActiveTaskCmd,主要是很多跳转业务场景下,要求不能时挂起任务。可以直接继承Command即可
*/
public class DeleteTaskCommand extends NeedsActiveTaskCmd<String> {
public DeleteTaskCommand(String taskId){
super(taskId);
}
@Override
public String execute(CommandContext commandContext, TaskEntity currentTask){
//获取所需服务
TaskEntityManagerImpl taskEntityManager = (TaskEntityManagerImpl)commandContext.getTaskEntityManager();
//获取当前任务的来源任务及来源节点信息
ExecutionEntity executionEntity = currentTask.getExecution();
//删除当前任务,来源任务
taskEntityManager.deleteTask(currentTask, "jumpReason", false, false);
return executionEntity.getId();
}
@Override
public String getSuspendedTaskException() {
return "挂起的任务不能跳转";
}
}
3.4 任务跳转自定义
package com.yb.utils;
import org.activiti.bpmn.model.FlowNode;
import org.activiti.bpmn.model.SequenceFlow;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.impl.interceptor.Command;
import org.activiti.engine.impl.interceptor.CommandContext;
import org.activiti.engine.impl.persistence.entity.ExecutionEntity;
import java.util.List;
/**
* @author [email protected]
* @version 1.0
* @date 2020/8/13
* 根据提供节点和执行对象id,进行跳转命令
*/
public class JumpCommand implements Command<Void> {
private FlowNode flowElement;
private String executionId;
public JumpCommand(FlowNode flowElement, String executionId){
this.flowElement = flowElement;
this.executionId = executionId;
}
@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));
commandContext.getAgenda().planTakeOutgoingSequenceFlowsOperation(executionEntity, true);
return null;
}
}
3.5 测试类
package com.yb;
import com.yb.utils.DeleteTaskCommand;
import com.yb.utils.JumpCommand;
import org.activiti.bpmn.model.FlowNode;
import org.activiti.bpmn.model.Process;
import org.activiti.engine.*;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
import java.util.List;
/**
* @author [email protected]
* @version 1.0
* @date 2020/8/5
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class ActivitiJpaTest07 {
@Resource
private RepositoryService repositoryService;
@Resource
private RuntimeService runtimeService;
@Resource
private TaskService taskService;
@Resource
private HistoryService historyService;
@Resource
private ManagementService managementService;
/**
* 部署流程,测试流程回退
*/
@Test
public void repositoryDeploy(){
Deployment deploy = repositoryService.createDeployment()
.addClasspathResource("processes/BackOff.bpmn")
.addClasspathResource("processes/BackOff.png")
.name("测试流程回退-1")
.deploy();
System.out.println("部署ID:"+deploy.getId());
System.out.println("部署名称"+deploy.getName());
}
/**
* 发布流程
*/
@Test
public void runtimeRelease(){
ProcessInstance pi = runtimeService.startProcessInstanceByKey("myProcess_1");
System.out.println("流程实例ID:"+pi.getId());
System.out.println("流程定义ID:"+pi.getProcessDefinitionId());
}
/**
* 查询及完成任务
*/
@Test
public void taskQueryComplete(){
List<Task> list = taskService.createTaskQuery()
.taskAssignee("zhangsan")
.list();
for (Task task : list) {
System.out.println("--------------------------------------------");
System.out.println("任务ID:" + task.getId());
System.out.println("任务名称:" + task.getName());
System.out.println("任务创建时间:" + task.getCreateTime());
System.out.println("任务委派人:" + task.getAssignee());
System.out.println("流程实例ID:" + task.getProcessInstanceId());
taskService.complete(task.getId());
}
}
/**
* 流程回退
*/
@Test
public void taskBack(){
Task task = taskService.createTaskQuery()
.taskId("30002")
.singleResult();
Process process = repositoryService.getBpmnModel(task.getProcessDefinitionId()).getMainProcess();
FlowNode flowNode = (FlowNode) process.getFlowElement("_3");
String s = managementService.executeCommand(new DeleteTaskCommand(task.getId()));
managementService.executeCommand(new JumpCommand(flowNode,s));
}
}
4. 测试结果
首先完成任务A
数据库:
任务A完成后,现在待办任务是任务B
进行流程回退,回退到任务A。
数据库:
删除当前任务,回退到上一个节点。从任务B回退到任务A,初步达到流程的初步回退,不包括子流程,并行