介绍
回退
操作是指,将流程退回到上一个节点,基本思路是通过审批历史服务HistoryService
找到审批审批的上一节点,然后跟通用拒绝操作类似,将流程拨回到该节点,要注意的一个问题是,如果碰到并行审批,在并行线上回退应该回退到哪里呢?
如图,如果审批顺序为主管审批->上级领导审批->董事长审批
,这时候总监审批执行回退操作,应该回退到哪个节点呢,显然不是董事长,因为这是两个并行互不干扰的审批,正常应该回退到主管审批这里,所以回退操作应该是基于execution的回退。如果你对ecxecution不了解,你可以查看之前的文章SpringBoot Activiti6系列教程(六)-Execution说明
因此,方案就是找到该execution的上个审批节点将流程回退到该节点。
实现
同样,我们可以编写一个command的类实现回退
FlowToPreNodeCmd
import com.definesys.mpaas.common.exception.MpaasBusinessException;
import org.activiti.bpmn.model.FlowElement;
import org.activiti.bpmn.model.FlowNode;
import org.activiti.bpmn.model.SequenceFlow;
import org.activiti.engine.HistoryService;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.impl.interceptor.Command;
import org.activiti.engine.impl.interceptor.CommandContext;
import org.activiti.engine.impl.persistence.entity.ExecutionEntity;
import org.activiti.engine.task.Task;
import java.util.List;
/**
* @Copyright: Shanghai Definesys Company.All rights reserved.
* @Description:
* @author: jianfeng.zheng
* @since: 2019/9/24 6:11 PM
* @history: 1.2019/9/24 created by jianfeng.zheng
*/
public class FlowToPreNodeCmd implements Command<String> {
private Task task;
public FlowToPreNodeCmd(Task task) {
this.task = task;
}
@Override
public String execute(CommandContext context) {
FlowElement element = this.getPreNode(this.task, context);
if (element == null) {
throw new MpaasBusinessException("该节点不能进行退回");
}
SequenceFlow flow = this.findAcessSequenceFlow((FlowNode) element);
ExecutionEntity executionEntity = context.getExecutionEntityManager().findById(task.getExecutionId());
executionEntity.setCurrentFlowElement(flow);
context.getAgenda().planTakeOutgoingSequenceFlowsOperation(executionEntity, true);
return executionEntity.getId();
}
private FlowElement getPreNode(Task task, CommandContext context) {
HistoryService historyService = context.getProcessEngineConfiguration().getHistoryService();
List<HistoricActivityInstance> items = historyService.createHistoricActivityInstanceQuery()
.executionId(task.getExecutionId())
.activityType("userTask")
.orderByHistoricActivityInstanceStartTime()
.desc()
.list();
if (items == null || items.size() == 0) {
throw new MpaasBusinessException("未找到上一节点");
}
String currentAct = task.getTaskDefinitionKey();
String preAct = null;
for (int i = 0; i < items.size(); ++i) {
HistoricActivityInstance item = items.get(i);
if (currentAct.equals(item.getActivityId())) {
continue;
}
preAct = item.getActivityId();
break;
}
if (preAct == null) {
return null;
}
RepositoryService repositoryService = context.getProcessEngineConfiguration().getRepositoryService();
org.activiti.bpmn.model.Process process = repositoryService.getBpmnModel(task.getProcessDefinitionId()).getMainProcess();
FlowElement node = process.getFlowElement(preAct);
return node;
}
private SequenceFlow findAcessSequenceFlow(FlowNode node) {
List<SequenceFlow> flows = node.getIncomingFlows();
if (flows == null || flows.size() == 0) {
throw new MpaasBusinessException("上一节点找不到入口");
}
//找没有加条件的连线
for (SequenceFlow flow : flows) {
if (flow.getConditionExpression() == null) {
return flow;
}
}
//如果都没有选择第一条
return flows.get(0);
}
}
有两个地方需要注意
- 基于execution来进行查找,代码里面条件为
.executionId(task.getExecutionId())
- activiti重新执行一个流需要有一条“线”,有限考虑没有加条件的线,保证流程能够流进我们希望的节点
上面仅仅只是流程回退的操作,要实现回退效果,还需要删除变量和当前的task。
@Override
public TaskResponse backwardTask(BPMContextInfo context, TaskResponse request, String user) {
Task task = taskService.createTaskQuery().taskId(request.getTaskId()).singleResult();
managementService.executeCommand(new ExecutionVariableDeleteCmd(task.getExecutionId()));
managementService.executeCommand(new FlowToPreNodeCmd(task));
managementService.executeCommand(new TaskDeleteCmd(request.getTaskId()));
return this.taskResponse(task,task.getProcessInstanceId());
}
这三个步骤顺序不能改变,第一步需要删除execution变量以免对新节点造成影响,最后一步才删除task,因为计算上一节点需要用到task,如果提前删除会造成task找不到。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。