中文字幕第五页-中文字幕第页-中文字幕韩国-中文字幕最新-国产尤物二区三区在线观看-国产尤物福利视频一区二区

Activiti如何實(shí)現(xiàn)流程的回退

1.概述

流程回退一直以來(lái)是個(gè)老舊的難題,也一直沒(méi)有好的解決方法,本文就來(lái)詳述流程回退的解決辦法。首先我們來(lái)分析一下不同的流程審批情況,并在對(duì)應(yīng)的節(jié)點(diǎn)上實(shí)現(xiàn)流程的回退處理,以及應(yīng)該提供的回退處理,當(dāng)然我們說(shuō)的回退不是指通過(guò)在流程節(jié)點(diǎn)上畫(huà)一條線回退到想回的節(jié)點(diǎn)上。

創(chuàng)新互聯(lián)專(zhuān)注于網(wǎng)站建設(shè),為客戶提供網(wǎng)站設(shè)計(jì)制作、做網(wǎng)站、網(wǎng)頁(yè)設(shè)計(jì)開(kāi)發(fā)服務(wù),多年建網(wǎng)站服務(wù)經(jīng)驗(yàn),各類(lèi)網(wǎng)站都可以開(kāi)發(fā),高端網(wǎng)站設(shè)計(jì),公司官網(wǎng),公司展示網(wǎng)站,網(wǎng)站設(shè)計(jì),建網(wǎng)站費(fèi)用,建網(wǎng)站多少錢(qián),價(jià)格優(yōu)惠,收費(fèi)合理。


回退時(shí),需要解決兩種情況:

  •     回退到發(fā)起人

  •     回退到上一步及逐步回退

因?yàn)榛赝酥寥我还?jié)點(diǎn)上,Activiti本身的api是不支持的,我們只能通過(guò)擴(kuò)展activiti的的api,以實(shí)現(xiàn)自由跳轉(zhuǎn)才達(dá)到回退至任一節(jié)點(diǎn)上,但有情況是例外的,回退的時(shí)候,需要注意,否則activiti在跳轉(zhuǎn)的時(shí)候,數(shù)據(jù)是容易出問(wèn)題的,主要是在并發(fā)的節(jié)點(diǎn)分支里跳到外面時(shí)(如下圖所示,B、D節(jié)點(diǎn)回到A節(jié)點(diǎn)時(shí)),其執(zhí)行的實(shí)例Id會(huì)變化,因此,需要注意對(duì)這種情況下的流程跳轉(zhuǎn)作一些限制。

Activiti如何實(shí)現(xiàn)流程的回退

那么我們需要在當(dāng)前審批的任務(wù)上,需要進(jìn)行回退到任何一個(gè)節(jié)點(diǎn),實(shí)現(xiàn)自由跳轉(zhuǎn)時(shí),如何擴(kuò)展,如下為我們擴(kuò)展activiti來(lái)實(shí)現(xiàn)自由跳轉(zhuǎn)的實(shí)現(xiàn)方式:

/**
  * 將節(jié)點(diǎn)之后的節(jié)點(diǎn)刪除然后指向新的節(jié)點(diǎn)。 
  * @param actDefId   流程定義ID
  * @param nodeId   流程節(jié)點(diǎn)ID
  * @param aryDestination 需要跳轉(zhuǎn)的節(jié)點(diǎn)
  * @return Map<String,Object> 返回節(jié)點(diǎn)和需要恢復(fù)節(jié)點(diǎn)的集合。
  */@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){
  
 }
 
 /**
   * 解決通過(guò)選擇自由跳轉(zhuǎn)指向同步節(jié)點(diǎn)導(dǎo)致的流程終止的問(wèn)題。
   * 在目標(biāo)節(jié)點(diǎn)中刪除指向自己的流轉(zhuǎn)。
   */ 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){
   //創(chuàng)建一個(gè)連接   ActivityImpl destAct= processDefinition.findActivity(dest);
    TransitionImpl transitionImpl = curAct.createOutgoingTransition();
    transitionImpl.setDestination(destAct);
  }
 }
 
  map.put("activity", curAct);
 
 
 return map;
 
}/**
  * 將臨時(shí)節(jié)點(diǎn)清除掉,加回原來(lái)的節(jié)點(diǎn)。
  * @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);
}/**
  * 通過(guò)指定目標(biāo)節(jié)點(diǎn),實(shí)現(xiàn)任務(wù)的跳轉(zhuǎn)
  * @param taskId 任務(wù)ID
  * @param destNodeIds 跳至的目標(biāo)節(jié)點(diǎn)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{
  //恢復(fù)  restore(activityMap);
 }
}

 

若我們需要進(jìn)行跳轉(zhuǎn),就需要知道回退上一步時(shí),其上一步是什么節(jié)點(diǎn)。如何僅是通過(guò)流程獲得其回退的節(jié)點(diǎn),這是達(dá)不到業(yè)務(wù)的需求的,因?yàn)橛袝r(shí)我們需要回退到某個(gè)節(jié)點(diǎn)處理后,下一步需要回到原來(lái)的節(jié)點(diǎn)上,如我們?cè)谏蠄DE節(jié)點(diǎn)上,回退時(shí),E回退需要回到D或C上,完成后再回到B,這種情況下我們可以要求E必須需要去到G1節(jié)點(diǎn)上,往下執(zhí)行。這種回退就會(huì)顯得人性化,同時(shí)也保證流程實(shí)例在后續(xù)的執(zhí)行過(guò)程中,其信號(hào)及各參數(shù)是正常的,這時(shí)就要求我們需要有一個(gè)完整記錄流程實(shí)例執(zhí)行經(jīng)過(guò)的各個(gè)節(jié)點(diǎn)ID的數(shù)據(jù),并且通過(guò)以下的數(shù)據(jù)可以快速找到當(dāng)前節(jié)點(diǎn)回退時(shí),應(yīng)該回退到哪一個(gè)節(jié)點(diǎn)上,并且當(dāng)時(shí)這個(gè)節(jié)點(diǎn)的執(zhí)行人員是誰(shuí)。

2.如何記錄流程的執(zhí)行過(guò)程

為了更好記錄流程經(jīng)過(guò)的樹(shù)節(jié)點(diǎn),我們采用了一個(gè)樹(shù)結(jié)構(gòu)來(lái)存儲(chǔ)流程實(shí)例執(zhí)行時(shí),經(jīng)過(guò)的流程節(jié)點(diǎn),如上圖所示,其執(zhí)行的樹(shù)型圖所示所示:

Activiti如何實(shí)現(xiàn)流程的回退

我們需要在各個(gè)節(jié)點(diǎn)那里可以找到其退回至上一步環(huán)節(jié)的父節(jié)點(diǎn)那里,這需要一個(gè)算法,如在B或D那里回退,我們讓他退回A,在C回退我們讓他回到B,若我們?cè)贓位置回退,我們需要讓他回到G1那里。這個(gè)算法的實(shí)現(xiàn)不算復(fù)雜,有這個(gè)樹(shù)型的執(zhí)行樹(shù)數(shù)據(jù)后,一切變得很簡(jiǎn)單。但要注意一點(diǎn),我們?cè)诨赝藭r(shí),需要記錄他是從哪個(gè)節(jié)點(diǎn)回退過(guò)來(lái)的,若用戶處理完成后,可以要求他直接回到原回退的節(jié)點(diǎn)去,也可以按流程定義重新走一次審批。假如執(zhí)行到E,讓他回退時(shí)并且重新審批,其執(zhí)行的樹(shù)圖如下所示:

Activiti如何實(shí)現(xiàn)流程的回退

注意G1,那里有指向E,當(dāng)完成時(shí),可以讓他來(lái)跳到E上,這就是任務(wù)完成后,可以找到它應(yīng)該跳至哪一個(gè)任務(wù)節(jié)點(diǎn)上。

3.擴(kuò)展表記錄流程的執(zhí)行的路徑

/*==============================================================*//* Table: BPM_RU_PATH                                           *//*==============================================================*/CREATE TABLE BPM_RU_PATH
(
   PATH_ID_             VARCHAR(64) NOT NULL,
   INST_ID_             VARCHAR(64) NOT NULL COMMENT '流程實(shí)例ID',
   ACT_DEF_ID_          VARCHAR(64) NOT NULL COMMENT 'Act定義ID',
   ACT_INST_ID_         VARCHAR(64) NOT NULL COMMENT 'Act實(shí)例ID',
   SOL_ID_              VARCHAR(64) NOT NULL COMMENT '解決方案ID',
   NODE_ID_             VARCHAR(255) NOT NULL COMMENT '節(jié)點(diǎn)ID',
   NODE_NAME_           VARCHAR(255) COMMENT '節(jié)點(diǎn)名稱(chēng)',
   NODE_TYPE_           VARCHAR(50) COMMENT '節(jié)點(diǎn)類(lèi)型',
   START_TIME_          DATETIME NOT NULL COMMENT '開(kāi)始時(shí)間',
   END_TIME_            DATETIME COMMENT '結(jié)束時(shí)間',
   DURATION_            INT COMMENT '持續(xù)時(shí)長(zhǎng)',
   DURATION_VAL_        INT COMMENT '有效審批時(shí)長(zhǎng)',
   ASSIGNEE_            VARCHAR(64) COMMENT '處理人ID',
   TO_USER_ID_          VARCHAR(64) COMMENT '代理人ID',
   IS_MULTIPLE_         VARCHAR(20) COMMENT '是否為多實(shí)例',
   EXECUTION_ID_        VARCHAR(64) COMMENT '活動(dòng)執(zhí)行ID',
   USER_IDS_            VARCHAR(300) COMMENT '原執(zhí)行人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 '跳到該節(jié)點(diǎn)的方式
            正常跳轉(zhuǎn)
            自由跳轉(zhuǎn)
            回退跳轉(zhuǎn)',
   NEXT_JUMP_TYPE_      VARCHAR(50) COMMENT '下一步跳轉(zhuǎn)方式',
   OPINION_             VARCHAR(500) COMMENT '審批意見(jiàn)',
   REF_PATH_ID_         VARCHAR(64) COMMENT '引用路徑ID
            當(dāng)回退時(shí),重新生成的結(jié)點(diǎn),需要記錄引用的回退節(jié)點(diǎn),方便新生成的路徑再次回退。',
   TENANT_ID_           VARCHAR(64) COMMENT '租用機(jī)構(gòu)ID',
   CREATE_BY_           VARCHAR(64) COMMENT '創(chuàng)建人ID',
   CREATE_TIME_         DATETIME COMMENT '創(chuàng)建時(shí)間',
   UPDATE_BY_           VARCHAR(64) COMMENT '更新人ID',
   UPDATE_TIME_         DATETIME COMMENT '更新時(shí)間',
  PRIMARY KEY (PATH_ID_)
);

ALTER TABLE BPM_RU_PATH COMMENT ‘流程實(shí)例運(yùn)行路線’;

4.如何創(chuàng)建執(zhí)行路徑

有了面的表結(jié)構(gòu)后,如何讓activiti在執(zhí)行的過(guò)程中,往上面的表加上我們需要的數(shù)據(jù),這時(shí)我們就需要利用activiti的全局事件監(jiān)聽(tīng)器,具體的實(shí)現(xiàn)請(qǐng)參考我的全局事件監(jiān)聽(tīng)處理。

<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提供了兩個(gè)不錯(cuò)的事件監(jiān)聽(tīng),一個(gè)是執(zhí)行實(shí)體創(chuàng)建事件ACTIVITY_STARTED,一個(gè)實(shí)體完成的事件ACTIVITY_COMPLETED。我們分別在這兩個(gè)事件上加上bpm_ru_path表的記錄創(chuàng)建與更新即可。在其回退的時(shí)候,通過(guò)算法找到其需要回退的節(jié)點(diǎn),然后通過(guò)上文提供的自由跳轉(zhuǎn)方法,即可以實(shí)現(xiàn)流程的回退。

5.流程回退處理

有了以上的執(zhí)行數(shù)據(jù),流程的回退,就可以通過(guò)算法找到其需要回退的流程節(jié)點(diǎn),從而可以實(shí)現(xiàn)流程的回退處理,注意以下的獲得當(dāng)前任務(wù)的回退節(jié)點(diǎn)Id,然后指定這個(gè)節(jié)點(diǎn)Id為執(zhí)行完成后,需要跳轉(zhuǎn)至這個(gè)節(jié)點(diǎn)上。
注意這部分代碼 BpmRuPath bpmRuPath = getBackNodeId(task.getProcessInstanceId(), task.getTaskDefinitionKey());

/**
  * 任務(wù)往下跳轉(zhuǎn)
  * 
  * @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,用來(lái)記錄執(zhí)行的路徑  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());
  // 加上外圍傳過(guò)來(lái)的變量  if (cmd.getVars() != null) {
    vars.putAll(cmd.getVars());
  }
  // 若為回退,則處理回退的操作  if (TaskOptionType.BACK.name().equals(cmd.getJumpType())) {
    BpmRuPath bpmRuPath = getBackNodeId(task.getProcessInstanceId(), task.getTaskDefinitionKey());
   // 沒(méi)有找到回退的節(jié)點(diǎn),提示用戶   if (bpmRuPath == null) {
     ProcessHandleHelper.getProcessMessage().getErrorMsges().add("本環(huán)節(jié)不能回退!沒(méi)有找到上一步的回退審批環(huán)節(jié)!");
    return;
   } else {// 設(shè)置回退的節(jié)點(diǎn)    cmd.setDestNodeId(bpmRuPath.getNodeId());
     ProcessHandleHelper.setBackPath(bpmRuPath);
     isSetBackPath = true;
   }
  } else if (TaskOptionType.BACK_TO_STARTOR.name().equals(cmd.getJumpType())) {// 回退至發(fā)起人   ActNodeDef afterNode = actRepService.getNodeAfterStart(task.getProcessDefinitionId());
   if (afterNode == null) {
     ProcessHandleHelper.getProcessMessage().getErrorMsges().add("沒(méi)有找到發(fā)起人所在的審批環(huán)節(jié)!");
    return;
   } else {
     cmd.setDestNodeId(afterNode.getNodeId());
   }
  } else {
   // 查找是否為原路返回的模式,即當(dāng)前任務(wù)是否由回退處理的   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());
   }   
  }
  // 以下為任務(wù)的跳轉(zhuǎn)處理  if (StringUtils.isNotEmpty(cmd.getDestNodeId())) {// 進(jìn)行指定節(jié)點(diǎn)的跳轉(zhuǎn)   actTaskService.completeTask(cmd.getTaskId(), new String[] { cmd.getDestNodeId() }, vars);
  } else {// 正常跳轉(zhuǎn)   taskService.complete(cmd.getTaskId(), vars);
  }
  
  //加上后置處理  if(StringUtils.isNotEmpty(userTaskConfig.getAfterHandle())){
    Object preBean=AppBeanUtil.getBean(userTaskConfig.getAfterHandle());
   if(preBean instanceof TaskAfterHandler){
     TaskAfterHandler handler=(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();
  }
 }
}

 

具體的實(shí)現(xiàn)效果可以參考如下在線示例,

需要在流程解決方案的節(jié)點(diǎn)配置中,打開(kāi)回退按鈕,如下圖所示:

Activiti如何實(shí)現(xiàn)流程的回退

了解咨詢QQ:1361783075

分享名稱(chēng):Activiti如何實(shí)現(xiàn)流程的回退
URL分享:http://m.2m8n56k.cn/article44/johphe.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站設(shè)計(jì)公司、搜索引擎優(yōu)化、動(dòng)態(tài)網(wǎng)站、網(wǎng)站營(yíng)銷(xiāo)、品牌網(wǎng)站制作、ChatGPT

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:[email protected]。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

網(wǎng)站建設(shè)網(wǎng)站維護(hù)公司
主站蜘蛛池模板: 精品无码久久久久久国产 | 日韩精品特黄毛片免费看 | 亚洲一区高清 | 国产精品99在线观看 | 国产精品久久影院 | 99精品视频在线观看 | 国产99视频精品草莓免视看 | 国产精品一一在线观看 | 成人a毛片高清视频 | 大量愉拍情侣在线视频 | 亚洲精品国产精品精 | 国产亚洲人成网站在线观看不卡 | 亚洲性综合| 久久国产精品1区2区3区网页 | 99福利网| 另类综合视频 | 色内内免费视频播放 | 性夜黄a爽爽免费视频国产 性夜影院爽黄a爽免费看网站 | 日韩免费a级在线观看 | 国内成人免费视频 | 久久精品在 | 亚洲欧洲一区二区 | 男人天堂手机在线 | 91久久香蕉国产线看观看软件 | 国产步兵社区视频在线观看 | 中文乱码字幕午夜无线观看 | 亚洲精品字幕一区二区三区 | 999热精品这里在线观看 | 日韩欧一级毛片在线播无遮挡 | 久久一本 | 欧美成人高清免费大片观看 | 国产一久久香蕉国产线看观看 | 亚洲欧美成人影院 | 久久久亚洲天堂 | 亚洲午夜片 | 久久影院在线 | 欧美va在线播放免费观看 | 久久久网站亚洲第一 | 国产精品美女久久福利网站 | 精品成人免费一区二区在线播放 | 国产在线a|