在用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; }
接口调用图: