在用Camunda流程引擎做業務開發時,因為實際業務需求,都會提出一些不同的需求(也就是功能),其中【取回】、【撤銷】需求是最為常見的。
取回:流程發起后,因流程發起人后來發現其中提交的信息不對,可信息又需要重新填寫,對這種問題有2種處理方式:
1)告訴下一節點的審核人,讓他駁回重新填寫。這種只適應下一個節點是審核的用戶節點流程,不適用所用流程圖(該功能這里就不討論)
2)由流程發起人主動取回來,重新填寫。可適用所有的流程圖
撤銷:流程發起后,發起人后來覺得沒必要發起這樣的流程,這個時候就想取消發起的流程
以上對【取回】、【撤銷】的理解,其中有一個必要的條件是:發起人
OK,既然理解了,下面就看此功能應該如何實現。
一、取回
測試流程圖:
此流程圖的XML文件:
<?xml version="1.0" encoding="UTF-8"?> <bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_0gaphv8" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="4.11.1" modeler:executionPlatform="Camunda Platform" modeler:executionPlatformVersion="7.15.0"> <bpmn:process id="Process_0cwop9s" name="配制了中斷事件節點的並行流程" isExecutable="true"> <bpmn:startEvent id="StartEvent_1"> <bpmn:outgoing>Flow_02in6iu</bpmn:outgoing> </bpmn:startEvent> <bpmn:sequenceFlow id="Flow_02in6iu" sourceRef="StartEvent_1" targetRef="Activity_0z8xsuw" /> <bpmn:sequenceFlow id="Flow_0lz52r3" sourceRef="Activity_0z8xsuw" targetRef="Gateway_052dhcu" /> <bpmn:userTask id="Activity_0z8xsuw" name="任務" camunda:assignee="${assignee}"> <bpmn:incoming>Flow_02in6iu</bpmn:incoming> <bpmn:outgoing>Flow_0lz52r3</bpmn:outgoing> </bpmn:userTask> <bpmn:parallelGateway id="Gateway_052dhcu"> <bpmn:incoming>Flow_0lz52r3</bpmn:incoming> <bpmn:outgoing>Flow_1kl4krz</bpmn:outgoing> <bpmn:outgoing>Flow_14hjfsn</bpmn:outgoing> </bpmn:parallelGateway> <bpmn:userTask id="Activity_044h6ho" name="任務2" camunda:assignee="${assignee}"> <bpmn:incoming>Flow_1kl4krz</bpmn:incoming> <bpmn:outgoing>Flow_1c9dvbg</bpmn:outgoing> </bpmn:userTask> <bpmn:endEvent id="Event_09jul9g"> <bpmn:incoming>Flow_1c9dvbg</bpmn:incoming> </bpmn:endEvent> <bpmn:sequenceFlow id="Flow_1c9dvbg" sourceRef="Activity_044h6ho" targetRef="Event_09jul9g" /> <bpmn:sequenceFlow id="Flow_1kl4krz" sourceRef="Gateway_052dhcu" targetRef="Activity_044h6ho" /> <bpmn:sequenceFlow id="Flow_14hjfsn" sourceRef="Gateway_052dhcu" targetRef="Activity_1tkc8qi" /> <bpmn:userTask id="Activity_1tkc8qi" name="任務三" camunda:assignee="${assignee}"> <bpmn:incoming>Flow_14hjfsn</bpmn:incoming> <bpmn:outgoing>Flow_1t02lwf</bpmn:outgoing> </bpmn:userTask> <bpmn:endEvent id="Event_0t9d637"> <bpmn:incoming>Flow_1t02lwf</bpmn:incoming> </bpmn:endEvent> <bpmn:sequenceFlow id="Flow_1t02lwf" sourceRef="Activity_1tkc8qi" targetRef="Event_0t9d637" /> </bpmn:process> <bpmndi:BPMNDiagram id="BPMNDiagram_1"> <bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_0cwop9s"> <bpmndi:BPMNEdge id="Flow_02in6iu_di" bpmnElement="Flow_02in6iu"> <di:waypoint x="215" y="227" /> <di:waypoint x="320" y="227" /> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge id="Flow_0lz52r3_di" bpmnElement="Flow_0lz52r3"> <di:waypoint x="420" y="227" /> <di:waypoint x="495" y="227" /> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge id="Flow_1c9dvbg_di" bpmnElement="Flow_1c9dvbg"> <di:waypoint x="580" y="120" /> <di:waypoint x="642" y="120" /> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge id="Flow_1kl4krz_di" bpmnElement="Flow_1kl4krz"> <di:waypoint x="520" y="202" /> <di:waypoint x="520" y="160" /> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge id="Flow_14hjfsn_di" bpmnElement="Flow_14hjfsn"> <di:waypoint x="545" y="227" /> <di:waypoint x="600" y="227" /> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge id="Flow_1t02lwf_di" bpmnElement="Flow_1t02lwf"> <di:waypoint x="700" y="227" /> <di:waypoint x="762" y="227" /> </bpmndi:BPMNEdge> <bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1"> <dc:Bounds x="179" y="209" width="36" height="36" /> </bpmndi:BPMNShape> <bpmndi:BPMNShape id="Activity_0yvqzhz_di" bpmnElement="Activity_0z8xsuw"> <dc:Bounds x="320" y="187" width="100" height="80" /> </bpmndi:BPMNShape> <bpmndi:BPMNShape id="Gateway_1nj20k7_di" bpmnElement="Gateway_052dhcu"> <dc:Bounds x="495" y="202" width="50" height="50" /> </bpmndi:BPMNShape> <bpmndi:BPMNShape id="Activity_14sqxzc_di" bpmnElement="Activity_044h6ho"> <dc:Bounds x="480" y="80" width="100" height="80" /> </bpmndi:BPMNShape> <bpmndi:BPMNShape id="Event_09jul9g_di" bpmnElement="Event_09jul9g"> <dc:Bounds x="642" y="102" width="36" height="36" /> </bpmndi:BPMNShape> <bpmndi:BPMNShape id="Activity_0zbig2w_di" bpmnElement="Activity_1tkc8qi"> <dc:Bounds x="600" y="187" width="100" height="80" /> </bpmndi:BPMNShape> <bpmndi:BPMNShape id="Event_0t9d637_di" bpmnElement="Event_0t9d637"> <dc:Bounds x="762" y="209" width="36" height="36" /> </bpmndi:BPMNShape> </bpmndi:BPMNPlane> </bpmndi:BPMNDiagram> </bpmn:definitions>
流程現在已經發起,運行第一個任務后,產生了2個並行任務,任務2和任務三:
現在調用【取回】接口,看實際情況:
接口調用成功了,那此功能的代碼應如何寫呢?
要寫此功能代碼,首先來整理下思路,也就是此功能需要做那那些事。
- 驗證。 具體要驗證什么呢?包含當前流程實例狀態、當前執行人是否為流程發起人、驗證當前活動實例
- 取消產生的任務 查找此流程產生的任務並取消
- 創建流程第1個USER TASK 任務 刪除所有已經產生的任務,並重新創建流程第一個USE TASK 任務
理解了此功能需要做什么事情,那接下捊代碼就好了:
控制層:
@ApiOperation("取回: 參數procId 為流程實例ID") @GetMapping("/fetchBack") @ApiImplicitParam(name = "procId", value = "流程實例ID", required = true) public ResponseStatusDto<Void> fetchBack (String procId, @CurrentUserInfo UserInfo userInfo){ if( StringUtils.isEmpty(procId)){ return ResponseStatusDto.failure("取回失敗,流程實例ID不正確"); } int type = workflowTaskService.fetchBack(procId, userInfo); return type == 0 ? ResponseStatusDto.success() : ResponseStatusDto.failure(FetchBackFailEnum.getMsgByCode(type)); }
服務層:
protected int checkProcessInstanceState(String procId){ ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(procId).singleResult(); if(ObjectUtils.isEmpty(processInstance)){ return 5; } if(processInstance.isEnded()) { return 6; } return 0; } protected int checkProcessStarter(String procId, UserInfo userInfo){ ProcInstEntity procInstEntity = workflowTaskMapper.queryProcInstEntityById(procId); if(ObjectUtils.isEmpty(procInstEntity) || !procInstEntity.getStartUserId().equals(userInfo.getUserId())){ return 8; } return 0; } protected String getInstanceIdForActivity(ActivityInstance activityInstance, String activityId) { ActivityInstance instance = getChildInstanceForActivity(activityInstance, activityId); if (instance != null) { return instance.getId(); } return null; } protected ActivityInstance getChildInstanceForActivity(ActivityInstance activityInstance, String activityId) { if (activityId.equals(activityInstance.getActivityId())) { return activityInstance; } for (ActivityInstance childInstance : activityInstance.getChildActivityInstances()) { ActivityInstance instance = getChildInstanceForActivity(childInstance, activityId); if (instance != null) { return instance; } } return null; } @Override @Transactional(rollbackFor = Exception.class) public int fetchBack(String procId, UserInfo userInfo) { try { // 判斷當前流程的狀態 int state = checkProcessInstanceState(procId); if( state != 0 ){ return state; } // 判斷是否有任務 List<Task> taskList = taskService.createTaskQuery().processInstanceId(procId).list(); if (ObjectUtils.isEmpty(taskList)) { return 1; } // 判斷當前執行人是否為流程發起人 state = checkProcessStarter(procId, userInfo); if( state != 0 ){ return state; } String processInstanceId = taskList.get(0).getProcessInstanceId(); ActivityInstance activityInstance = runtimeService.getActivityInstance(processInstanceId); // 判斷當前實例是否為空 if(ObjectUtils.isEmpty(activityInstance) || ObjectUtils.isEmpty(activityInstance.getChildActivityInstances())){ return 7; } // 判斷是否處於第一個用戶任務節點 List<HistoricActivityInstance> historicActivityInstanceList = historyService.createHistoricActivityInstanceQuery() .processInstanceId(processInstanceId) .activityType("userTask") .unfinished().orderByHistoricActivityInstanceStartTime() .asc().list(); if (ObjectUtils.isEmpty(historicActivityInstanceList) || historicActivityInstanceList.get(0).getActivityId().equals(activityInstance.getChildActivityInstances()[0].getActivityId())) { return 3; } String toActId = historicActivityInstanceList.get(0).getActivityId(); String assignee = historicActivityInstanceList.get(0).getAssignee(); //設置當前處理人 Map<String, Object> taskVariable = new HashMap<>(); taskVariable.put("assignee", assignee); runtimeService.createProcessInstanceModification(processInstanceId) //關閉相關任務 .cancelActivityInstance(getInstanceIdForActivity(activityInstance, taskList.get(0).getTaskDefinitionKey())) .setAnnotation("進行了取回到節點操作") //啟動目標活動節點 .startBeforeActivity(toActId) //流程的可變參數賦值 .setVariables(taskVariable) .execute(); // 刪除任務表其它任務 List<String> taskIdList = new ArrayList<>(); List<String> actIdList = new ArrayList<>(); for (int i = 1; i < taskList.size(); i++) { taskIdList.add(taskList.get(i).getId()); actIdList.add(getInstanceIdForActivity(activityInstance, taskList.get(i).getTaskDefinitionKey())); } // 特別說明:以上所有方法都是調用流程提供的方法完成的功能。可后來發現對於並行的任務,只能取消其中一個,另外的任務取消不了,所以只能自己操作表,去刪除、更新數據狀態 if(ObjectUtils.isNotEmpty(taskIdList) && ObjectUtils.isNotEmpty(actIdList)){ // 刪除ACT_RU_EXECUTION 表中的實例 workflowTaskMapper.deleteTaskByIdArray(taskIdList); // 刪除ACT_RU_EXECUTION數據 workflowTaskMapper.deleteExecutionByProcInstIdAndActInstIdArray(processInstanceId, actIdList); // 更新ACT_HI_TASKINST表 workflowTaskMapper.updateHiTaskInstByIdArray(taskIdList, DateUtils.getCurrentTime()); // 更新ACT_HI_ACTINST數據 workflowTaskMapper.updateHiActInstById(actIdList, DateUtils.getCurrentTime()); } return 0; }catch (Exception e){ log.error("取回異常:{}", e.getMessage()); return 4; } }
數據操作層:
@Delete(" <script> " + " delete from ACT_RU_TASK where ID_ in " + " <foreach collection = 'taskIdLit' item = 't' separator = ',' open = '(' close = ')' > " + " #{t}" + " </foreach> " + " </script> ") int deleteTaskByIdArray(List<String> taskIdLit); @Update(" <script> " + " update ACT_HI_TASKINST set " + " END_TIME_ = #{endTime}, DELETE_REASON_ = 'deleted' " + " where ID_ in " + " <foreach collection = 'taskIdLit' item = 't' separator = ',' open = '(' close = ')' > " + " #{t}" + " </foreach> " + " </script> ") int updateHiTaskInstByIdArray(@Param("taskIdLit")List<String> taskIdLit, @Param("endTime") String endTime); @Update(" <script> " + " update ACT_HI_ACTINST set END_TIME_ = #{endTime} where ID_ in" + " <foreach collection = 'actInstIdList' item = 't' separator = ',' open = '(' close = ')' > " + " #{t}" + " </foreach> " + " </script> ") int updateHiActInstById(@Param("actInstIdList")List<String> actInstIdList, @Param("endTime")String endTime); @Delete(" <script> " + " delete from ACT_RU_EXECUTION where PROC_INST_ID_ = #{procInstId} AND " + " ACT_INST_ID_ in " + " <foreach collection = 'actInstIdList' item = 't' separator = ',' open = '(' close = ')' > " + " #{t}" + " </foreach> " + " </script> ") int deleteExecutionByProcInstIdAndActInstIdArray(@Param("procInstId")String procInstId , @Param("actInstIdList")List<String> actInstIdList);
二、撤銷
流程圖還是繼續沿用上面的流程圖。
要寫此功能代碼,首先來整理下思路,此功能需要做那那些事。
-
驗證。 具體要驗證什么呢?包含當前流程實例狀態、當前執行人是否為流程發起人、驗證當前活動實例、
-
取消產生的任務 查找此流程產生的任務並取消
-
刪除此流程實例
理解了此功能需要做什么事情,那接下捊代碼就好了:
控制層:
@ApiOperation("撤回: 參數procId 為流程實例ID") @GetMapping("/withDraw") @ApiImplicitParam(name = "procId", value = "流程實例ID", required = true) public ResponseStatusDto<Void> withDraw (String procId, @CurrentUserInfo UserInfo userInfo){ if(StringUtils.isEmpty(procId)){ return ResponseStatusDto.failure("撤回失敗,參數不正確"); } int type = workflowTaskService.withDraw(procId, userInfo); return type == 0 ? ResponseStatusDto.success() : ResponseStatusDto.failure(WithDrawEnum.getMsgByCode(type)); }
服務層:
@Override public int withDraw(String procId, UserInfo userInfo) { int state = checkProcessInstanceState(procId); if( state != 0 ){ return state; } List<Task> taskList = taskService.createTaskQuery().processInstanceId(procId).list(); if(CollectionUtils.isEmpty(taskList)){ return 1; } // 判斷當前執行人是否為流程發起人 state = checkProcessStarter(procId, userInfo); if( state != 0 ){ return state; } Task task = taskList.get(0); List<HistoricActivityInstance> historicActivityInstanceList = historyService.createHistoricActivityInstanceQuery() .processInstanceId(task.getProcessInstanceId()) .activityType("userTask") .finished().orderByHistoricActivityInstanceEndTime() .asc().list(); if(historicActivityInstanceList == null || historicActivityInstanceList.isEmpty()){ return 2; } ActivityInstance activityInstance = runtimeService.getActivityInstance(task.getProcessInstanceId()); String toActId = historicActivityInstanceList.get(0).getActivityId(); String assignee = historicActivityInstanceList.get(0).getAssignee(); Map<String, Object> taskVariable = new HashMap<>(); //設置當前處理人 taskVariable.put("assignee", assignee); runtimeService.createProcessInstanceModification(task.getProcessInstanceId()) //關閉相關任務 .cancelActivityInstance(getInstanceIdForActivity(activityInstance, task.getTaskDefinitionKey())) .setAnnotation("進行了撤回到節點操作") //啟動目標活動節點 .startBeforeActivity(toActId) //流程的可變參數賦值 .setVariables(taskVariable) .execute(); runtimeService.deleteProcessInstance(task.getProcessInstanceId(), String.format("%s 用戶執行了撤回操作", userInfo.getUserId())); return 0; }
接口調用圖: