流程回退一直以來是個老舊的難題,也一直沒有好的解決方法,本文就來詳述流程回退的解決辦法。首先我們來分析一下不同的流程審批情況,并在對應(yīng)的節(jié)點上實現(xiàn)流程的回退處理,以及應(yīng)該提供的回退處理,當(dāng)然我們說的回退不是指通過在流程節(jié)點上畫一條線回退到想回的節(jié)點上。
創(chuàng)新互聯(lián)專注于網(wǎng)站建設(shè),為客戶提供網(wǎng)站設(shè)計制作、做網(wǎng)站、網(wǎng)頁設(shè)計開發(fā)服務(wù),多年建網(wǎng)站服務(wù)經(jīng)驗,各類網(wǎng)站都可以開發(fā),高端網(wǎng)站設(shè)計,公司官網(wǎng),公司展示網(wǎng)站,網(wǎng)站設(shè)計,建網(wǎng)站費(fèi)用,建網(wǎng)站多少錢,價格優(yōu)惠,收費(fèi)合理。
回退時,需要解決兩種情況:
回退到發(fā)起人
回退到上一步及逐步回退
因為回退至任一節(jié)點上,Activiti本身的api是不支持的,我們只能通過擴(kuò)展activiti的的api,以實現(xiàn)自由跳轉(zhuǎn)才達(dá)到回退至任一節(jié)點上,但有情況是例外的,回退的時候,需要注意,否則activiti在跳轉(zhuǎn)的時候,數(shù)據(jù)是容易出問題的,主要是在并發(fā)的節(jié)點分支里跳到外面時(如下圖所示,B、D節(jié)點回到A節(jié)點時),其執(zhí)行的實例Id會變化,因此,需要注意對這種情況下的流程跳轉(zhuǎn)作一些限制。
那么我們需要在當(dāng)前審批的任務(wù)上,需要進(jìn)行回退到任何一個節(jié)點,實現(xiàn)自由跳轉(zhuǎn)時,如何擴(kuò)展,如下為我們擴(kuò)展activiti來實現(xiàn)自由跳轉(zhuǎn)的實現(xiàn)方式:
/** * 將節(jié)點之后的節(jié)點刪除然后指向新的節(jié)點。 * @param actDefId 流程定義ID * @param nodeId 流程節(jié)點ID * @param aryDestination 需要跳轉(zhuǎn)的節(jié)點 * @return Map<String,Object> 返回節(jié)點和需要恢復(fù)節(jié)點的集合。 */@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){ } /** * 解決通過選擇自由跳轉(zhuǎn)指向同步節(jié)點導(dǎo)致的流程終止的問題。 * 在目標(biāo)節(jié)點中刪除指向自己的流轉(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)建一個連接 ActivityImpl destAct= processDefinition.findActivity(dest); TransitionImpl transitionImpl = curAct.createOutgoingTransition(); transitionImpl.setDestination(destAct); } } map.put("activity", curAct); return map; }/** * 將臨時節(jié)點清除掉,加回原來的節(jié)點。 * @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); }/** * 通過指定目標(biāo)節(jié)點,實現(xiàn)任務(wù)的跳轉(zhuǎn) * @param taskId 任務(wù)ID * @param destNodeIds 跳至的目標(biāo)節(jié)點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),就需要知道回退上一步時,其上一步是什么節(jié)點。如何僅是通過流程獲得其回退的節(jié)點,這是達(dá)不到業(yè)務(wù)的需求的,因為有時我們需要回退到某個節(jié)點處理后,下一步需要回到原來的節(jié)點上,如我們在上圖E節(jié)點上,回退時,E回退需要回到D或C上,完成后再回到B,這種情況下我們可以要求E必須需要去到G1節(jié)點上,往下執(zhí)行。這種回退就會顯得人性化,同時也保證流程實例在后續(xù)的執(zhí)行過程中,其信號及各參數(shù)是正常的,這時就要求我們需要有一個完整記錄流程實例執(zhí)行經(jīng)過的各個節(jié)點ID的數(shù)據(jù),并且通過以下的數(shù)據(jù)可以快速找到當(dāng)前節(jié)點回退時,應(yīng)該回退到哪一個節(jié)點上,并且當(dāng)時這個節(jié)點的執(zhí)行人員是誰。
為了更好記錄流程經(jīng)過的樹節(jié)點,我們采用了一個樹結(jié)構(gòu)來存儲流程實例執(zhí)行時,經(jīng)過的流程節(jié)點,如上圖所示,其執(zhí)行的樹型圖所示所示:
我們需要在各個節(jié)點那里可以找到其退回至上一步環(huán)節(jié)的父節(jié)點那里,這需要一個算法,如在B或D那里回退,我們讓他退回A,在C回退我們讓他回到B,若我們在E位置回退,我們需要讓他回到G1那里。這個算法的實現(xiàn)不算復(fù)雜,有這個樹型的執(zhí)行樹數(shù)據(jù)后,一切變得很簡單。但要注意一點,我們在回退時,需要記錄他是從哪個節(jié)點回退過來的,若用戶處理完成后,可以要求他直接回到原回退的節(jié)點去,也可以按流程定義重新走一次審批。假如執(zhí)行到E,讓他回退時并且重新審批,其執(zhí)行的樹圖如下所示:
注意G1,那里有指向E,當(dāng)完成時,可以讓他來跳到E上,這就是任務(wù)完成后,可以找到它應(yīng)該跳至哪一個任務(wù)節(jié)點上。
/*==============================================================*//* 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 '節(jié)點ID', NODE_NAME_ VARCHAR(255) COMMENT '節(jié)點名稱', NODE_TYPE_ VARCHAR(50) COMMENT '節(jié)點類型', START_TIME_ DATETIME NOT NULL COMMENT '開始時間', END_TIME_ DATETIME COMMENT '結(jié)束時間', DURATION_ INT COMMENT '持續(xù)時長', 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 '活動執(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é)點的方式 正常跳轉(zhuǎn) 自由跳轉(zhuǎn) 回退跳轉(zhuǎn)', NEXT_JUMP_TYPE_ VARCHAR(50) COMMENT '下一步跳轉(zhuǎn)方式', OPINION_ VARCHAR(500) COMMENT '審批意見', REF_PATH_ID_ VARCHAR(64) COMMENT '引用路徑ID 當(dāng)回退時,重新生成的結(jié)點,需要記錄引用的回退節(jié)點,方便新生成的路徑再次回退。', TENANT_ID_ VARCHAR(64) COMMENT '租用機(jī)構(gòu)ID', CREATE_BY_ VARCHAR(64) COMMENT '創(chuàng)建人ID', CREATE_TIME_ DATETIME COMMENT '創(chuàng)建時間', UPDATE_BY_ VARCHAR(64) COMMENT '更新人ID', UPDATE_TIME_ DATETIME COMMENT '更新時間', PRIMARY KEY (PATH_ID_) );
ALTER TABLE BPM_RU_PATH COMMENT ‘流程實例運(yùn)行路線’;
有了面的表結(jié)構(gòu)后,如何讓activiti在執(zhí)行的過程中,往上面的表加上我們需要的數(shù)據(jù),這時我們就需要利用activiti的全局事件監(jiān)聽器,具體的實現(xiàn)請參考我的全局事件監(jiān)聽處理。
<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提供了兩個不錯的事件監(jiān)聽,一個是執(zhí)行實體創(chuàng)建事件ACTIVITY_STARTED,一個實體完成的事件ACTIVITY_COMPLETED。我們分別在這兩個事件上加上bpm_ru_path表的記錄創(chuàng)建與更新即可。在其回退的時候,通過算法找到其需要回退的節(jié)點,然后通過上文提供的自由跳轉(zhuǎn)方法,即可以實現(xiàn)流程的回退。
有了以上的執(zhí)行數(shù)據(jù),流程的回退,就可以通過算法找到其需要回退的流程節(jié)點,從而可以實現(xiàn)流程的回退處理,注意以下的獲得當(dāng)前任務(wù)的回退節(jié)點Id,然后指定這個節(jié)點Id為執(zhí)行完成后,需要跳轉(zhuǎn)至這個節(jié)點上。
注意這部分代碼 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,用來記錄執(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()); // 加上外圍傳過來的變量 if (cmd.getVars() != null) { vars.putAll(cmd.getVars()); } // 若為回退,則處理回退的操作 if (TaskOptionType.BACK.name().equals(cmd.getJumpType())) { BpmRuPath bpmRuPath = getBackNodeId(task.getProcessInstanceId(), task.getTaskDefinitionKey()); // 沒有找到回退的節(jié)點,提示用戶 if (bpmRuPath == null) { ProcessHandleHelper.getProcessMessage().getErrorMsges().add("本環(huán)節(jié)不能回退!沒有找到上一步的回退審批環(huán)節(jié)!"); return; } else {// 設(shè)置回退的節(jié)點 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("沒有找到發(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é)點的跳轉(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(); } } }
具體的實現(xiàn)效果可以參考如下在線示例,
需要在流程解決方案的節(jié)點配置中,打開回退按鈕,如下圖所示:
了解咨詢QQ:1361783075
分享名稱:Activiti如何實現(xiàn)流程的回退
URL分享:http://jinyejixie.com/article44/johphe.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站設(shè)計公司、搜索引擎優(yōu)化、動態(tài)網(wǎng)站、網(wǎng)站營銷、品牌網(wǎng)站制作、ChatGPT
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)