How Activiti implements the fallback of the process

1 Overview

edit

Process rollback has always been an old problem, and there has been no good solution. This article will detail the solution to process rollback. First, let's analyze different process approval situations, and implement process rollback processing on the corresponding nodes, as well as the rollback processing that should be provided. Of course, we do not mean rollback by drawing a line on the process node. to the desired node.

When rolling back, two situations need to be addressed:

 

  •     fallback to originator
  •     Go back to the previous step and step back

Because of falling back to any node, Activiti's own api is not supported. We can only achieve free jump by extending activiti's api to achieve the fallback to any node, but there are exceptions. When rolling back, you need to pay attention, otherwise, when activiti jumps, the data is prone to problems, mainly when jumping outside the concurrent node branch (as shown in the figure below, when nodes B and D return to node A ), the instance ID of its execution will change. Therefore, it is necessary to pay attention to some restrictions on the process jump in this case.

1.jpg

 

Then we need to roll back to any node in the current approval task, and how to expand when free jump is realized. The following is the implementation method of extending activiti to realize free jump:

 

/**
  * Delete the node after the node and point to the new node.
  * @param actDefId Process definition ID
  * @param nodeId Process node ID
  * @param aryDestination the node to jump to
  * @return Map<String,Object> Returns a collection of nodes and nodes that need to be restored.
  */
 @SuppressWarnings("unchecked")
 private Map<String,Object>  prepare(String actDefId,String nodeId,String[] aryDestination){
  Map<String,Object> map=new HashMap<String, Object>();
  
  // modify the process definition
  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){
   
  }
  
  /**
   * Solve the problem of process termination caused by selecting free jump to point to the sync node.
   * Delete the flow that points to itself in the target node.
   */
  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){
    //create a connection
    ActivityImpl destAct = processDefinition.findActivity (dest);
    TransitionImpl transitionImpl = curAct.createOutgoingTransition();
    transitionImpl.setDestination(destAct);
   }
  }
  
  map.put("activity", curAct);
  
  
  return map;
  
 }
 
 /**
  * Clear the temporary node and add it back to the original node.
  * @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);
 }

 /**
  * By specifying the target node, the task jump is realized
  * @param taskId task ID
  * @param destNodeIds the destination node ID to jump to
  * @param vars Process variables
  */
 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{
   //recover
   restore(activityMap);
  }
 }

 If we need to jump, we need to know what node is the previous step when we go back to the previous step. How to get the node whose fallback is only through the process, which cannot meet the needs of the business, because sometimes we need to fall back to a node for processing, and then we need to return to the original node in the next step, as shown in the figure above. On node E, when rolling back, E needs to go back to D or C, and then go back to B after completion. In this case, we can require that E must go to node G1 and execute it downward. This kind of fallback will be humanized, and it will also ensure that the signals and parameters of the process instance are normal in the subsequent execution process. At this time, we are required to have a complete record of each node ID that the process instance passes through. Data, and through the following data, you can quickly find out which node should the current node roll back to when it rolls back, and who is the executive of this node at that time.

 

2. How to record the execution of the process

In order to better record the tree nodes that the process passes through, we use a tree structure to store the process nodes that pass through when the process instance is executed, as shown in the figure above, and the tree diagram of its execution is shown in the following figure:

3.png

We need to find the parent node where it returns to the previous step at each node. This requires an algorithm. For example, when backing up at B or D, we let him back to A, and when C is back, we let him back to B. If We're back at E and we need to get him back to G1. The implementation of this algorithm is not complicated. With the execution tree data of this tree type, everything becomes very simple. However, it should be noted that when we roll back, we need to record which node he rolled back from. If the user finishes processing, he can be asked to go back directly to the original rollback node, or he can go back again according to the process definition. approval. If the execution reaches E, let him roll back and re-approve, the tree diagram of its execution is as follows:

5.png

 

Pay attention to G1, there is a pointer to E. When it is completed, you can let him jump to E. This is when the task is completed, you can find which task node it should jump to.

 

3. The extension table records the execution path of the process

/*==============================================================*/
/* Table: BPM_RU_PATH                                           */
/*==============================================================*/
CREATE TABLE BPM_RU_PATH
(
   PATH_ID_             VARCHAR(64) NOT NULL,
   INST_ID_             VARCHAR(64) NOT NULL COMMENT '流程实例ID',
   ACT_DEF_ID_          VARCHAR(64) NOT NULL COMMENT 'Act定义ID',
   ACT_INST_ID_         VARCHAR(64) NOT NULL COMMENT 'Act实例ID',
   SOL_ID_              VARCHAR(64) NOT NULL COMMENT '解决方案ID',
   NODE_ID_             VARCHAR(255) NOT NULL COMMENT '节点ID',
   NODE_NAME_           VARCHAR(255) COMMENT '节点名称',
   NODE_TYPE_           VARCHAR(50) COMMENT '节点类型',
   START_TIME_          DATETIME NOT NULL COMMENT '开始时间',
   END_TIME_            DATETIME COMMENT '结束时间',
   DURATION_            INT COMMENT '持续时长',
   DURATION_VAL_        INT COMMENT '有效审批时长',
   ASSIGNEE_            VARCHAR(64) COMMENT '处理人ID',
   TO_USER_ID_          VARCHAR(64) COMMENT '代理人ID',
   IS_MULTIPLE_         VARCHAR(20) COMMENT '是否为多实例',
   EXECUTION_ID_        VARCHAR(64) COMMENT '活动执行ID',
   USER_IDS_            VARCHAR(300) COMMENT '原执行人IDS',
   PARENT_ID_           VARCHAR(64) COMMENT '父ID',
   LEVEL_               INT COMMENT '层次',
   OUT_TRAN_ID_         VARCHAR(255) COMMENT '跳出路线ID',
   TOKEN_               VARCHAR(255) COMMENT '路线令牌',
   JUMP_TYPE_           VARCHAR(50) COMMENT '跳到该节点的方式
            正常跳转
            自由跳转
            回退跳转',
   NEXT_JUMP_TYPE_      VARCHAR(50) COMMENT '下一步跳转方式',
   OPINION_             VARCHAR(500) COMMENT '审批意见',
   REF_PATH_ID_         VARCHAR(64) COMMENT '引用路径ID
            当回退时,重新生成的结点,需要记录引用的回退节点,方便新生成的路径再次回退。',
   TENANT_ID_           VARCHAR(64) COMMENT '租用机构ID',
   CREATE_BY_           VARCHAR(64) COMMENT '创建人ID',
   CREATE_TIME_         DATETIME COMMENT '创建时间',
   UPDATE_BY_           VARCHAR(64) COMMENT '更新人ID',
   UPDATE_TIME_         DATETIME COMMENT '更新时间',
   PRIMARY KEY (PATH_ID_)
);

ALTER TABLE BPM_RU_PATH COMMENT '流程实例运行路线';

 

4.如何创建执行路径

有了上面的表结构后,如何让activiti在执行的过程中,往上面的表加上我们需要的数据,这时我们就需要利用activiti的全局事件监听器,具体的实现请参考我的 【全局事件监听处理】。

 

<bean id="globalEventListener" class="com.redxun.bpm.activiti.listener.GlobalEventListener">
   <property name="handlers">
  <map>
   <entry key="TASK_CREATED" value="taskCreateListener"/>
   <entry key="TASK_COMPLETED" value="taskCompleteListener"/>
   <entry key="TASK_ASSIGNED" value="taskAssignedListener"/>
   <entry key="PROCESS_COMPLETED" value="processCompleteListener"/>
   <entry key="ACTIVITY_STARTED" value="activityStartedListener"/>
   <entry key="ACTIVITY_COMPLETED" value="activityCompletedListener"/>
   <entry key="ACTIVITY_SIGNALED" value="activitySignaledListener"/>
   <entry key="PROCESS_STARTED" value="processStartEventListener"/>
  </map>
 </property>
  </bean>

 其中Activiti提供了两个不错的事件监听,一个是执行实体创建事件ACTIVITY_STARTED,一个实体完成的事件ACTIVITY_COMPLETED。我们分别在这两个事件上加上bpm_ru_path表的记录创建与更新即可。在其回退的时候,通过算法找到其需要回退的节点,然后通过上文提供的自由跳转方法,即可以实现流程的回退。

 

package com.redxun.bpm.activiti.listener;

import java.util.Date;
import java.util.Map;

import javax.annotation.Resource;

import org.activiti.engine.RuntimeService;
import org.activiti.engine.delegate.event.ActivitiEvent;
import org.activiti.engine.delegate.event.impl.ActivitiActivityEventImpl;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.redxun.bpm.activiti.entity.ActNodeDef;
import com.redxun.bpm.activiti.service.ActRepService;
import com.redxun.bpm.activiti.util.ProcessHandleHelper;
import com.redxun.bpm.core.entity.BpmInst;
import com.redxun.bpm.core.entity.BpmRuPath;
import com.redxun.bpm.core.entity.IExecutionCmd;
import com.redxun.bpm.core.entity.ProcessNextCmd;
import com.redxun.bpm.core.entity.ProcessStartCmd;
import com.redxun.bpm.core.entity.config.ActivityConfig;
import com.redxun.bpm.core.entity.config.BpmEventConfig;
import com.redxun.bpm.core.manager.BpmInstManager;
import com.redxun.bpm.core.manager.BpmNodeSetManager;
import com.redxun.bpm.core.manager.BpmRuPathManager;
import com.redxun.bpm.enums.TaskEventType;
import com.redxun.core.constants.MBoolean;
import com.redxun.core.script.GroovyEngine;
import com.redxun.saweb.util.IdUtil;

/**
 * 活动节点开始时的监听器
 * @author keitch
 *
 */
public class ActivityStartedListener implements EventHandler{
 
 private Log logger=LogFactory.getLog(ActivityStartedListener.class);
 
 @Resource BpmRuPathManager bpmRuPathManager;
 
 @Resource BpmInstManager bpmInstManager;
 
 @Resource ActRepService actRepService;
 
 @Resource RuntimeService runtimeService;
 
 @Resource BpmNodeSetManager bpmNodeSetManager;
 
 @Resource GroovyEngine groovyEngine;
 
 /**
  * 执行脚本事件
  * @param eventImpl
  */
 public void executeEventScript(ActivitiActivityEventImpl eventImpl){
  String solId=(String)runtimeService.getVariable(eventImpl.getExecutionId(), "solId");
  //处理事件
  ActivityConfig actConfig=bpmNodeSetManager.getActivityConfig(solId, eventImpl.getActivityId());
  if(actConfig.getEvents().size()>0){
   BpmEventConfig bpmEventConfig=null;
   for(BpmEventConfig eventConfig:actConfig.getEvents()){
    if(TaskEventType.ACTIVITY_STARTED.name().equals(eventConfig.getEventKey())){
     bpmEventConfig=eventConfig;
     break;
    }
   }
   //执行脚本
   if(bpmEventConfig!=null && StringUtils.isNotEmpty(bpmEventConfig.getScript())){
    logger.debug("==================execute the ActivityStartedListener  complete listener:"+bpmEventConfig.getScript());
    Map<String,Object> vars=runtimeService.getVariables(eventImpl.getExecutionId());
    vars.put("executionId",eventImpl.getExecutionId());
    groovyEngine.executeScripts(bpmEventConfig.getScript(),vars);
   }
  }
 }
 
 /**
  * 创建执行路径的数据,用于流程图的追踪,流程回退及执行等
  */
 @Override
 public void handle(ActivitiEvent event) {
  logger.debug("enter the event ActivityStartedListener handler is .....============");
  ActivitiActivityEventImpl eventImpl=(ActivitiActivityEventImpl)event;
  
  String activityId=eventImpl.getActivityId();
  String entityName=eventImpl.getActivityName();
  
  logger.debug("entity:"+activityId + " entityName:"+entityName);
  
  IExecutionCmd cmd=ProcessHandleHelper.getProcessCmd();
  ActNodeDef actNodeDef=actRepService.getActNodeDef(eventImpl.getProcessDefinitionId(), activityId);
  //判断一些并行的网关的结束点,防止其生成多条记录
  if(eventImpl.getActivityType().indexOf("Gateway")!=-1 || StringUtils.isNotEmpty(actNodeDef.getMultiInstance())){
   BpmRuPath ruPath= bpmRuPathManager.getFarestPath(eventImpl.getProcessInstanceId(),eventImpl.getActivityId());
   if(ruPath!=null){
    cmd.setNodeId(activityId);
    if("userTask".equals(eventImpl.getActivityType())){
     //获得会签人员列表,并且进行会签人员设置
     if(StringUtils.isEmpty(ruPath.getUserIds())){
      String userIds=(String)runtimeService.getVariable(eventImpl.getExecutionId(), "signUserIds_"+activityId);
      ruPath.setUserIds(userIds);
      bpmRuPathManager.update(ruPath);
     }
    }
    return;
   }
  }
  
  //创建执行路径
  BpmRuPath path=new BpmRuPath();
  path.setPathId(IdUtil.getId());
  path.setActDefId(eventImpl.getProcessDefinitionId());
  path.setActInstId(eventImpl.getProcessInstanceId());
  path.setExecutionId(eventImpl.getExecutionId());
  path.setNodeName(actNodeDef.getNodeName());
  path.setNodeId(activityId);
  path.setNodeType(actNodeDef.getNodeType());
  path.setStartTime(new Date());
  
  if(cmd instanceof ProcessStartCmd){//若为启动时,需要从线程中获得
   ProcessStartCmd startCmd=(ProcessStartCmd)cmd;
   path.setInstId(startCmd.getBpmInstId());
   path.setSolId(startCmd.getSolId());
  }else{
   BpmInst bpmInst=bpmInstManager.getByActInstId(eventImpl.getProcessInstanceId());
   path.setInstId(bpmInst.getInstId());
   path.setSolId(bpmInst.getSolId());
   path.setNextJumpType(((ProcessNextCmd)cmd).getNextJumpType());
  }
  //是否为多实例
  if(StringUtils.isNotEmpty(actNodeDef.getMultiInstance())){
   path.setIsMultiple(MBoolean.YES.name());
  }else{
   path.setIsMultiple(MBoolean.NO.name());
  }  
  
  //记录跳转的原节点,并且把跳转记录挂至该节点上
  BpmRuPath parentPath=null;
  if(cmd!=null && StringUtils.isNotEmpty(cmd.getNodeId())){
   parentPath=bpmRuPathManager.getFarestPath(eventImpl.getProcessInstanceId(),cmd.getNodeId());
  }
  if(parentPath!=null){
   path.setParentId(parentPath.getPathId());
   path.setLevel(parentPath.getLevel()+1);
  }else{
   path.setLevel(1);
   path.setParentId("0");
  }
  
  //是否由回退时产生的,若是需要记录回退时的流程ID
  BpmRuPath bpmRuPath=ProcessHandleHelper.getBackPath();
  if(bpmRuPath!=null){
   path.setRefPathId(bpmRuPath.getPathId());
  }
  //当从开始启动时,进入两次,这时需要记录其父ID
  //或在任务节点后的非任务点时,传递其父Id
  if(!"userTask".equals(actNodeDef.getNodeType())){
   cmd.setNodeId(eventImpl.getActivityId());
  }
  
  bpmRuPathManager.create(path);
  
 }
}

 完成的事件处理

package com.redxun.bpm.activiti.listener;

import java.util.Date;
import java.util.Map;

import javax.annotation.Resource;

import org.activiti.engine.RuntimeService;
import org.activiti.engine.delegate.event.ActivitiEvent;
import org.activiti.engine.delegate.event.impl.ActivitiActivityEventImpl;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.redxun.bpm.activiti.util.ProcessHandleHelper;
import com.redxun.bpm.core.entity.BpmRuPath;
import com.redxun.bpm.core.entity.IExecutionCmd;
import com.redxun.bpm.core.entity.ProcessNextCmd;
import com.redxun.bpm.core.entity.config.ActivityConfig;
import com.redxun.bpm.core.entity.config.BpmEventConfig;
import com.redxun.bpm.core.manager.BpmNodeSetManager;
import com.redxun.bpm.core.manager.BpmRuPathManager;
import com.redxun.bpm.enums.TaskEventType;
import com.redxun.bpm.enums.TaskOptionType;
import com.redxun.core.script.GroovyEngine;
import com.redxun.saweb.context.ContextUtil;

/**
 * 活动节点结束时的监听器
 * @author keitch
 *
 */
public class ActivityCompletedListener implements EventHandler{
 
 private Log logger=LogFactory.getLog(ActivityCompletedListener.class);
 
 @Resource BpmRuPathManager bpmRuPathManager;
 
 @Resource BpmNodeSetManager bpmNodeSetManager;
 
 @Resource GroovyEngine groovyEngine;
 
 @Resource RuntimeService runtimeService;
 
 /**
  * 执行脚本事件
  * @param eventImpl
  */
 public void executeEventScript(ActivitiActivityEventImpl eventImpl){
  String solId=(String)runtimeService.getVariable(eventImpl.getExecutionId(), "solId");
  //处理事件
  ActivityConfig actConfig=bpmNodeSetManager.getActivityConfig(solId, eventImpl.getActivityId());
  if(actConfig.getEvents().size()>0){
   BpmEventConfig bpmEventConfig=null;
   for(BpmEventConfig eventConfig:actConfig.getEvents()){
    if(TaskEventType.ACTIVITY_COMPLETED.name().equals(eventConfig.getEventKey())){
     bpmEventConfig=eventConfig;
     break;
    }
   }
   //执行脚本
   if(bpmEventConfig!=null && StringUtils.isNotEmpty(bpmEventConfig.getScript())){
    logger.debug("==================execute the ActivityCompletedListener  complete listener:"+bpmEventConfig.getScript());
    Map<String,Object> vars=runtimeService.getVariables(eventImpl.getExecutionId());
    vars.put("executionId",eventImpl.getExecutionId());
    groovyEngine.executeScripts(bpmEventConfig.getScript(),vars);
   }
  }
 }
 
 @Override
 public void handle(ActivitiEvent event) {
  logger.debug("enter the event ActivityCompletedListener handler is .....============");
  ActivitiActivityEventImpl eventImpl=(ActivitiActivityEventImpl)event;
  //执行配置的事件脚本
  executeEventScript(eventImpl);
  
  IExecutionCmd cmd=ProcessHandleHelper.getProcessCmd();
  BpmRuPath ruPath=bpmRuPathManager.getFarestPath(eventImpl.getProcessInstanceId(),eventImpl.getActivityId() );
  
  if(ruPath!=null){
   ruPath.setAssignee(ContextUtil.getCurrentUserId());
   //TODO 设置代理人,表示代理谁来执行
   //ruPath.setAgnentUserId(aValue);
   if(cmd instanceof ProcessNextCmd){
    ruPath.setToUserId(((ProcessNextCmd)cmd).getAgentToUserId());
   }
   
   ruPath.setEndTime(new Date());
   Long duration=ruPath.getEndTime().getTime()-ruPath.getStartTime().getTime();
   ruPath.setDuration(duration.intValue());
   //TODO,结合工作日历计算有效时间
   ruPath.setDurationVal(duration.intValue());
   if(cmd!=null && "userTask".equals(ruPath.getNodeType())){
    if(StringUtils.isNotBlank(cmd.getJumpType())){
     ruPath.setJumpType(cmd.getJumpType());
     ruPath.setOpinion(cmd.getOpinion());
    }else{
     ruPath.setJumpType(TaskOptionType.AGREE.name());
     ruPath.setOpinion("同意");
    }
   }
   //更新其数据
   bpmRuPathManager.update(ruPath);
  }
 }
}

 

5.流程回退处理

有了以上的执行数据,流程的回退,就可以通过算法找到其需要回退的流程节点,从而可以实现流程的回退处理,注意以下的获得当前任务的回退节点Id,然后指定这个节点Id为执行完成后,需要跳转至这个节点上。
注意这部分代码 BpmRuPath bpmRuPath = getBackNodeId(task.getProcessInstanceId(), task.getTaskDefinitionKey());

/**
  * 任务往下跳转
  * 
  * @param taskId
  * @param jsonData
  * @param vars
  * @throws Exception
  */
 public void doNext(ProcessNextCmd cmd) throws Exception {
  
  boolean isSetBackPath = false;
  try {
   TaskEntity task = (TaskEntity) taskService.createTaskQuery().taskId(cmd.getTaskId()).singleResult();
   
   UserTaskConfig userTaskConfig=bpmNodeSetManager.getTaskConfig(task.getSolId(), task.getTaskDefinitionKey());
     
   //String processInstanceId = task.getProcessInstanceId();
   // 加上executionId,用来记录执行的路径
   cmd.setNodeId(task.getTaskDefinitionKey());
   // 加上线程变量
   ProcessHandleHelper.setProcessCmd(cmd);
   BpmInst bpmInst = bpmInstManager.getByActInstId(task.getProcessInstanceId());
   BpmFormInst bpmFormInst = bpmFormInstManager.get(bpmInst.getFormInstId());
   try {
    String newJson = JSONUtil.copyJsons(bpmFormInst.getJsonData(), cmd.getJsonData());
    bpmFormInst.setJsonData(newJson);
    bpmFormInstManager.saveOrUpdate(bpmFormInst);
   } catch (Exception ex) {
    logger.error(ex.getCause());
   }
   Map<String, Object> vars = handleTaskVars(task, cmd.getJsonData());
   // 加上外围传过来的变量
   if (cmd.getVars() != null) {
    vars.putAll(cmd.getVars());
   }
   // 若为回退,则处理回退的操作
   if (TaskOptionType.BACK.name().equals(cmd.getJumpType())) {
    BpmRuPath bpmRuPath = getBackNodeId(task.getProcessInstanceId(), task.getTaskDefinitionKey());
    // 没有找到回退的节点,提示用户
    if (bpmRuPath == null) {
     ProcessHandleHelper.getProcessMessage().getErrorMsges().add("本环节不能回退!没有找到上一步的回退审批环节!");
     return;
    } else {// 设置回退的节点
     cmd.setDestNodeId(bpmRuPath.getNodeId());
     ProcessHandleHelper.setBackPath(bpmRuPath);
     isSetBackPath = true;
    }
   } else if (TaskOptionType.BACK_TO_STARTOR.name().equals(cmd.getJumpType())) {// 回退至发起人
    ActNodeDef afterNode = actRepService.getNodeAfterStart(task.getProcessDefinitionId());
    if (afterNode == null) {
     ProcessHandleHelper.getProcessMessage().getErrorMsges().add("没有找到发起人所在的审批环节!");
     return;
    } else {
     cmd.setDestNodeId(afterNode.getNodeId());
    }
   } else {
    // 查找是否为原路返回的模式,即当前任务是否由回退处理的
    BpmRuPath ruPath = bpmRuPathManager.getFarestPath(task.getProcessInstanceId(), task.getTaskDefinitionKey());
    if (ruPath != null && "".equals(ruPath.getNextJumpType())) {
     BpmRuPath toNodePath = bpmRuPathManager.get(ruPath.getParentId());
     if (toNodePath != null) {
      cmd.setDestNodeId(toNodePath.getNodeId());
     }
    }
   }
   
   //加上前置处理
   if(StringUtils.isNotEmpty(userTaskConfig.getPreHandle())){
    Object preBean=AppBeanUtil.getBean(userTaskConfig.getPreHandle());
    if(preBean instanceof TaskPreHandler){
     TaskPreHandler handler=(TaskPreHandler)preBean;
     handler.taskPreHandle(cmd, task, bpmInst.getBusKey());
    }   
   }
   // 以下为任务的跳转处理
   if (StringUtils.isNotEmpty(cmd.getDestNodeId())) {// 进行指定节点的跳转
    actTaskService.completeTask(cmd.getTaskId(), new String[] { cmd.getDestNodeId() }, vars);
   } else {// 正常跳转
    taskService.complete(cmd.getTaskId(), vars);
   }
   
   //加上后置处理
   if(StringUtils.isNotEmpty(userTaskConfig.getAfterHandle())){
    Object preBean = AppBeanUtil.getBean (userTaskConfig.getAfterHandle ());
    if (preBean instanceof TaskAfterHandler) {
     TaskAfterHandler trades = (TaskAfterHandler) preBean;
     handler.taskAfterHandle(cmd, task.getTaskDefinitionKey(), bpmInst.getBusKey());
    }
   }
  } catch (Exception e) {
   e.printStackTrace ();
   logger.error(e.getCause());
   throw e;
  } finally {
   ProcessHandleHelper.clearProcessCmd();
   if (isSetBackPath) {
    ProcessHandleHelper.clearBackPath();
   }
  }
 }

 The specific implementation effect can refer to the following online example,

http://www.redxun.cn:8020/saweb/index.do,

user:admin

pwd:1

 

http://redxun.iteye.com/blog/2406509

 

You need to open the back button in the node configuration of the process solution, as shown in the following figure:

6.png

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326849616&siteId=291194637