1.概述
流程回退一直以来是个老旧的难题,也一直没有好的解决方法,本文就来详述流程回退的解决办法。首先我们来分析一下不同的流程审批情况,并在对应的节点上实现流程的回退处理,以及应该提供的回退处理,当然我们说的回退不是指通过在流程节点上画一条线回退到想回的节点上。
回退时,需要解决两种情况:
- 回退到发起人
- 回退到上一步及逐步回退
因为回退至任一节点上,Activiti本身的api是不支持的,我们只能通过扩展activiti的的api,以实现自由跳转才达到回退至任一节点上,但有情况是例外的,回退的时候,需要注意,否则activiti在跳转的时候,数据是容易出问题的,主要是在并发的节点分支里跳到外面时(如下图所示,B、D节点回到A节点时),其执行的实例Id会变化,因此,需要注意对这种情况下的流程跳转作一些限制。
那么我们需要在当前审批的任务上,需要进行回退到任何一个节点,实现自由跳转时,如何扩展,如下为我们扩展activiti来实现自由跳转的实现方式:
/**
* 将节点之后的节点删除然后指向新的节点。
* @param actDefId 流程定义ID
* @param nodeId 流程节点ID
* @param aryDestination 需要跳转的节点
* @return Map<String,Object> 返回节点和需要恢复节点的集合。
*/
@SuppressWarnings("unchecked")
private Map<String,Object> prepare(String actDefId,String nodeId,String[] aryDestination){
Map<String,Object> map=new HashMap<String, Object>();
//修改流程定义
ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity)repositoryService.getProcessDefinition(actDefId);
ActivityImpl curAct= processDefinition.findActivity(nodeId);
List<PvmTransition> outTrans= curAct.getOutgoingTransitions();
try{
List<PvmTransition> cloneOutTrans=(List<PvmTransition>) FileUtil.cloneObject(outTrans);
map.put("outTrans", cloneOutTrans);
}
catch(Exception ex){
}
/**
* 解决通过选择自由跳转指向同步节点导致的流程终止的问题。
* 在目标节点中删除指向自己的流转。
*/
for(Iterator<PvmTransition> it=outTrans.iterator();it.hasNext();){
PvmTransition transition=it.next();
PvmActivity activity= transition.getDestination();
List<PvmTransition> inTrans= activity.getIncomingTransitions();
for(Iterator<PvmTransition> itIn=inTrans.iterator();itIn.hasNext();){
PvmTransition inTransition=itIn.next();
if(inTransition.getSource().getId().equals(curAct.getId())){
itIn.remove();
}
}
}
curAct.getOutgoingTransitions().clear();
if(aryDestination!=null && aryDestination.length>0){
for(String dest:aryDestination){
//创建一个连接
ActivityImpl destAct= processDefinition.findActivity(dest);
TransitionImpl transitionImpl = curAct.createOutgoingTransition();
transitionImpl.setDestination(destAct);
}
}
map.put("activity", curAct);
return map;
}
/**
* 将临时节点清除掉,加回原来的节点。
* @param map
* void
*/
@SuppressWarnings("unchecked")
private void restore(Map<String,Object> map){
ActivityImpl curAct=(ActivityImpl) map.get("activity");
List<PvmTransition> outTrans=(List<PvmTransition>) map.get("outTrans");
curAct.getOutgoingTransitions().clear();
curAct.getOutgoingTransitions().addAll(outTrans);
}
/**
* 通过指定目标节点,实现任务的跳转
* @param taskId 任务ID
* @param destNodeIds 跳至的目标节点ID
* @param vars 流程变量
*/
public synchronized void completeTask(String taskId,String[] destNodeIds,Map<String,Object> vars) {
TaskEntity task=(TaskEntity)taskService.createTaskQuery().taskId(taskId).singleResult();
String curNodeId=task.getTaskDefinitionKey();
String actDefId=task.getProcessDefinitionId();
Map<String,Object> activityMap= prepare(actDefId, curNodeId, destNodeIds);
try{
taskService.complete(taskId);
}
catch(Exception ex){
throw new RuntimeException(ex);
}
finally{
//恢复
restore(activityMap);
}
}
若我们需要进行跳转,就需要知道回退上一步时,其上一步是什么节点。如何仅是通过流程获得其回退的节点,这是达不到业务的需求的,因为有时我们需要回退到某个节点处理后,下一步需要回到原来的节点上,如我们在上图E节点上,回退时,E回退需要回到D或C上,完成后再回到B,这种情况下我们可以要求E必须需要去到G1节点上,往下执行。这种回退就会显得人性化,同时也保证流程实例在后续的执行过程中,其信号及各参数是正常的,这时就要求我们需要有一个完整记录流程实例执行经过的各个节点ID的数据,并且通过以下的数据可以快速找到当前节点回退时,应该回退到哪一个节点上,并且当时这个节点的执行人员是谁。