1. 程式人生 > 實用技巧 >activiti已結束子流程退回

activiti已結束子流程退回

使用者需求

有客戶反映一個子流程審批時將審批通過看成了退回修改,想讓其退回到上一步。
區域性流程圖如下圖

這是並行閘道器下的一個分支流程,該訂單在另一個分支中已經走了多步,當前分支的情況是:在審批這個節點本來要選擇退回待修改結果誤選了審批通過以致該該子流程直接結束。我在網上搜了體面的流程退回,但有流程不能已結束的要求,我也搜到一篇手動退回一個結束的流程的文章(文末連結),就按照該文章的思路嘗試將已結束的流程退回到子流程審批的節點。
先檢視流程相關表的執行情況(具體流程可能會有不同情況),在備份庫中,手動在子流程審批節點推進到結束節點,檢視sql執行。

insert into ACT_HI_VARINST (ID_, PROC_INST_ID_, EXECUTION_ID_, TASK_ID_, NAME_, REV_, VAR_TYPE_, BYTEARRAY_ID_, DOUBLE_, LONG_ , TEXT_, TEXT2_, CREATE_TIME_, LAST_UPDATED_TIME_) values
('1895008', '1892897', '1892903', '1892991',... update ACT_HI_TASKINST set PROC_DEF_ID_ = 'Process_1:62:572507', EXECUTION_ID_ = '1892903',... WHERE ID_ = '1892991'; update ACT_RU_EXECUTION set REV_ = 5, BUSINESS_KEY_ = null, PROC_DEF_ID_ = 'Process_1:62:572507', ACT_ID_ = 'EndEvent_1vt7vcc', IS_ACTIVE_ =
false, IS_CONCURRENT_ = false, IS_SCOPE_ = false, IS_EVENT_SCOPE_ = false, IS_MI_ROOT_ = false, PARENT_ID_ = '1892897', ... WHERE ID_ = '1892903' and REV_ = 4; select * FROM ACT_RU_VARIABLE WHERE TASK_ID_ = '1892991' and NAME_= 'result'; select * FROM ACT_RU_VARIABLE WHERE EXECUTION_ID_ = '
1892903' and NAME_= 'result' and TASK_ID_ is null; delete FROM ACT_RU_IDENTITYLINK WHERE ID_ = '1892992'; delete FROM ACT_RU_TASK WHERE ID_ = '1892991' and REV_ = 4; delete FROM ACT_RU_EXECUTION WHERE ID_ = '1892903' and REV_ = 5;
#等等

語句非常多,一部分任務表,節點表和詳情表的修改操作忽略了,值得關注的是插入了一條歷史變量表,即判斷審批通過的變數;更新了歷史任務表;更新了當前execution,當前節點變成EndEvent,即結束節點。後面又刪除了執行時的人員表,任務表和execution。根據參考文章,手動恢復ACT_RU_TASK,ACT_RU_EXECUTION,ACT_RU_IDENTITYLINK和ACT_RU_VARIABLE。涉及到審批相關變數result的ACT_RU_VARIABLE表的兩個查詢語句,第一據查詢 WHERE TASK_ID_ = '1892991' and NAME_= 'result';這個task_id是指子流程審批任務的id,第二句execution_id是該分支的execution,以及task_id為空,這裡需要注意,如果手動退回後報無法解析expression表示式的錯,可能是查詢變數時沒有找到,這裡如果不恢復ACT_RU_VARIABLE表查詢的也可能為全域性變數result。
後面回退遇到問題時也可以結合執行的sql找出問題,這裡不必贅述。

程式碼實現回退邏輯

/**
* phone為審批人的assignee值,nodeName是子流程審批節點名
*/
@Transactional
public void revoke(String businessId, String phone, String nodeName) {
        // 1.找到taskId,下面選取符合的assignee可以直接在query中篩選同businessId
        List<HistoricTaskInstance> htiList = historyService.createHistoricTaskInstanceQuery()
                .processInstanceBusinessKey(businessId)
                .orderByTaskCreateTime()
                .desc()
                .list();
        String myTaskId = null;
        HistoricTaskInstance myTask = null;
        for(HistoricTaskInstance hti : htiList) {
            if(phone.equals(hti.getAssignee()) && nodeName.equals(hti.getName())) {
                myTaskId = hti.getId();
                // 選取最後一個符合條件的task,即為最近的task
                myTask = hti;
                break;
            }
        }
        if(null==myTaskId) {
            throw new MyException(101,"該任務非當前使用者提交,無法撤回");
        }
        
/* 關於實體類,自己建立太麻煩,我直接使用activiti自帶的類,格式一般是“xxxEntityImpl”比如execution的實體用   ExecutionEntityImpl,具體的欄位資訊可以跟正規流程的一一對照。這裡有個坑,ExecutionEntityImpl物件的isScope屬性初始化為true,必須修改為false,因為一個流程*例項只有一個主execution的isScope欄位為true,其他execution的必須為false,如果設定錯了,手動退回後只能正常推進一次節點,然後該execution會被異常地刪除,導致流轉異常。
execution的acti_id欄位指當前正在執行的節點id,ExecutionEntityImpl物件通過setcurrentFlowElement的方式賦值,需要從流程定義中獲取。
推進時流程的exection物件會根據這條記錄獲取,物件包含自身execution,主execution和活動節點activity物件
*/
        // 2.建立execution
        // 獲取當前節點物件,塞到executionEntity中
        FlowElement flowElement = repositoryService.getBpmnModel(myTask.getProcessDefinitionId()).getMainProcess().getFlowElement(myTask.getTaskDefinitionKey());
        ExecutionEntityImpl executionEntity = new ExecutionEntityImpl();
        executionEntity.setId(myTask.getExecutionId());
        executionEntity.setProcessInstanceId(myTask.getProcessInstanceId());
        executionEntity.setProcessDefinitionId(myTask.getProcessDefinitionId());
        executionEntity.setParentId(myTask.getProcessInstanceId());
        executionEntity.setRootProcessInstanceId(myTask.getProcessInstanceId());
        executionEntity.setCurrentFlowElement(flowElement);
        executionEntity.setStartTime(new Date());
        // 子execution分支的is_scope的值為0,主分支的為1。這裡一定要設定,否則只能暫時退回,再走一個節點該execution就會被刪除
        executionEntity.setScope(false);   
        executionEntity.setActive(true);
        
/* mapper物件的sql可以寫對應表的增刪改查,我拿的是activiti中自身的mapperxml檔案的對應語句(原始碼activiti-engine-6.0.0的db.mapping包下),稍作修改
*/
        int ie = activitiMapper.insertExecution(executionEntity);
        if ( ie == 1) {
            logger.error("新添execution成功,id={}",myTask.getExecutionId());
        }
/* task的assignee欄位其他的ru_task沒有,我也沒有加
*/

        // 3.insert task into ACT_RU_TASK
        TaskEntityImpl runTask = new TaskEntityImpl();
        runTask.setId(myTask.getId());
        runTask.setName(myTask.getName());
        runTask.setPriority(myTask.getPriority());
        //runTask.setAssignee(myTask.getAssignee());
        runTask.setCreateTime(new Date());
        runTask.setExecutionId(myTask.getExecutionId());
        runTask.setProcessInstanceId(myTask.getProcessInstanceId());
        runTask.setProcessDefinitionId(myTask.getProcessDefinitionId());
        runTask.setTaskDefinitionKey(myTask.getTaskDefinitionKey());
        int it = activitiMapper.insertTask(runTask);
        if( it == 1) {
            logger.error("新添task成功,id={}",myTask.getId());
        }
 /* identityLink只需要新增該任務的一條記錄,用taskId篩選即可
 */
        // 4.insert task identitylink into ACT_RUN_IDENTITYLINK
        List<HistoricIdentityLinkEntityImpl> historicIdentityLinkEntities = activitiMapper.selectHistoricIdentityLinksByProcessInstanceAndTaskId(myTask.getProcessInstanceId(),myTask.getId());
        for ( HistoricIdentityLinkEntity e: historicIdentityLinkEntities) {
            if (e.getTaskId() != null && e.getTaskId().equals(myTaskId)) {
                IdentityLinkEntityImpl identityLink = new IdentityLinkEntityImpl();
                identityLink.setId(e.getId());
                identityLink.setType(e.getType());
                identityLink.setUserId(e.getUserId());
                identityLink.setGroupId(e.getGroupId());
                identityLink.setTaskId(e.getTaskId());
                identityLink.setProcessInstanceId(e.getProcessInstanceId());
                activitiMapper.insertIdentityLink(identityLink);
                
            }
        }
/* 這裡的variable根據情況加一條審批相關的變數,可能不用加;加了話需要注意子流程審批用到的變數是什麼語句查詢的,有executionId和taskId的條件話就按條件給欄位賦值。推節點時如果查不到會報無法解析表示式異常
*/
        // 5.insert variables into ACT_RU_VARIABLE   
        List<HistoricVariableInstance> historicVariableInstances = processEngine.getHistoryService().createHistoricVariableInstanceQuery().executionId(myTask.getExecutionId()).list();
        List<VariableInstanceEntity> variables = new ArrayList<>();
        for (HistoricVariableInstance e : historicVariableInstances) {
            if ("result".equals(e.getVariableName())) {
                VariableInstanceEntityImpl v = new VariableInstanceEntityImpl();
                v.setName(e.getVariableName());
                v.setId(e.getId());
                v.setTypeName(e.getVariableTypeName());
                v.setExecutionId(myTask.getExecutionId());
                v.setProcessInstanceId(e.getProcessInstanceId());
                v.setTextValue(String.valueOf(e.getValue()));
                variables.add(v);
                break;
            }
        }
        int ih = activitiMapper.bulkInsertVariableInstance(variables);

        // 6.更改業務表,修改業務相關的邏輯
  
    }

因為理解不了原始碼,對流程的實際流轉過程認識有限,就只能通過多次嘗試,觀察相關表的記錄變更檢視執行情況:檢視act_ru_exection表,business_key篩選該訂單,然後根據該例項proc_inst_id檢視task/variable/identityLink表,根據taskid檢視variable/identitylink的該任務的記錄。在修改了不少細節,檢查了是否對後續子流程和其他子流程有影響後,成功回退了流程。

相關連結

https://blog.csdn.net/jiajane/article/details/103901187 -【Activiti】activiti 手動回退一個結束的流程
感謝這位博主的思路和邏輯