回退,指的是用户主动回退到当前任务的上一流程节点(上一步骤)。
想象这样一种场景,当前用户接收任务后,发现这个任务不该由他办理或者这个任务存在严重的业务问题,这时就需要回退给上一步的办理者重新办理。
解决方案如下:
识别 “需要具有回退能力” 的任务。
为上述任务设计处理回退逻辑的监听器。
回退监听器接收一个参数,用于指定回退目的地的活动 ID。之所以这样设计是因为,回退的出发地与目的地可能相隔着多个活动。这个参数也可以设计为流程变量,这样可以在流程运行时动态算出。
回退监听器动态创建一条转移路径,指向回退目的地的活动。
定义【回退任务】API。
如果回退操作造成业务 “损失”,那么就必须在【回退任务】API 中予以补偿;如果需要清除 “历史痕迹”,那么在【回退任务】API 还需要删除相关的历史记录。
假设有这样一个流程:
jPDL:
<?xml version="1.0" encoding="UTF-8"?><process name="Rollback" xmlns="http://jbpm.org/4.4/jpdl"> <start g="207,195,48,48" name="start1"> <transition to="申请"/> </start> <task assignee="Jack" g="290,191,92,52" name="申请"> <transition to="审批"/> </task> <task assignee="Deniro" g="420,189,92,52" name="审批"> <!-- 领导审批活动,定义具有退回【申请】活动的监听器--> <on event="start"> <event-listener class="net.deniro.jbpm.test.RollbackListener"> <field name="rollbackTo"> <string value="申请"/> </field> </event-listener> </on> <transition to="end1"/> </task> <end g="550,191,48,48" name="end1"/></process>
RollbackListener 是定义的回退监听器,它会根据注入的 rollbackTo 参数值来动态生成一条通向回退目的地的转移路径。它定义如下:
ublic class RollbackListener implements org.jbpm.api.listener.EventListener { static Logger logger = Logger.getLogger(RollbackListener.class); private static ProcessEngine processEngine = Configuration.getProcessEngine(); /** * 退回的目的地 */ private String rollbackTo; /** * 增加退回路径 * * @param execution * @throws Exception */ @Override public void notify(EventListenerExecution execution) throws Exception { //获取流程定义对象 ProcessInstance processInstance = execution.getProcessInstance(); String processDefinitionId = processInstance.getProcessDefinitionId(); ProcessDefinitionImpl processDefinition = (ProcessDefinitionImpl) processEngine .getRepositoryService().createProcessDefinitionQuery() .processDefinitionId(processDefinitionId).uniqueResult(); //获取退回目的地活动的定义对象 ActivityImpl toActivityImpl = processDefinition.findActivity(rollbackTo); if (toActivityImpl == null) {//退回目的地不存在(流程定义错误),则记录日志 String msg = "在此 " + processDefinitionId + "流程中不存在 " + rollbackTo+ "活动"; logger.error(msg); throw new Exception(msg); } //获取当前活动的定义对象 ActivityImpl fromActivityImpl=((ExecutionImpl)execution).getActivity(); //建立退回路径 TransitionImpl transition=fromActivityImpl.createOutgoingTransition(); transition.setName(fromActivityImpl.getName()+" 回退 "+rollbackTo); transition.setDestination(toActivityImpl); } }
最后,编写回退任务 API:
public class TaskRollbackService { /** * 流程引擎 */ private static final ProcessEngine processEngine= Configuration.getProcessEngine(); /** * 任务服务 */ private static final TaskService taskService=processEngine.getTaskService(); /** * 退回任务 * @param taskId 当前任务 ID * @param rollbackToActName 要退回的活动名称 */ public void completeTaskRollback(String taskId,String rollbackToActName){ Task task=taskService.getTask(taskId); taskService.completeTask(task.getId(),task.getActivityName()+" 回退 " + ""+rollbackToActName);//完成任务 //清除历史痕迹或补偿业务损失 } }
这里使用流程引擎与任务服务来实现回退;如果使用 jBPM4 的命令模式,则调用会更加优雅,而且还会受到事务的控制。
单元测试:
//发起流程实例ProcessInstance processInstance = executionService.startProcessInstanceByKey("Rollback");String instanceId = processInstance.getId();//实例 ID//正常办理第一个任务Task applyTask = taskService.findPersonalTasks("Jack").get(0); taskService.completeTask(applyTask.getId());//断言已到达第二个任务processInstance = executionService.findProcessInstanceById(instanceId); assertTrue(processInstance.isActive("审批"));//假设在【审批】活动中未通过,则执行【回退】操作Task auditTask = taskService.findPersonalTasks("Deniro").get(0); TaskRollbackService rollbackService = new TaskRollbackService(); rollbackService.completeTaskRollback(auditTask.getId(), "申请");//断言当前已在【申请】活动processInstance = executionService.findProcessInstanceById(instanceId); assertTrue(processInstance.isActive("申请"));//重新【申请】Task applyTask2 = taskService.findPersonalTasks("Jack").get(0); taskService.completeTask(applyTask2.getId());//【审批】通过Task auditTask2 = taskService.findPersonalTasks("Deniro").get(0); taskService.completeTask(auditTask2.getId());//断言流程结束assertProcessInstanceEnded(processInstance);//断言流程实例已成为历史HistoryProcessInstance historyProcessInstance = historyService .createHistoryProcessInstanceQuery().processInstanceId(instanceId) .uniqueResult(); assertNotNull(historyProcessInstance);
这里还有一些需要优化的地方:
1、TaskRollbackService 的 completeTaskRollback(String taskId,String rollbackToActName),定义了两个参数;其实 rollbackToActName,即需要回退的活动名称已经在流程定义的回退监听器中设定过了,所以其实这里可以省略。注意:这种省略适用于只存在一条回退转移路径的情况。如果存在多条回退转移路径,就需要调用者明确指定具体的回退目的地参数啦O(∩_∩)O~
2、使用 jBPM4 的命令模式来实现 completeTaskRollback 逻辑,会受到事务控制,这对于清除历史痕迹与实现业务补偿的场景来说很重要。自定义命令代码模板如下:
//使用 Configuration.getProcessEngine() 的 execute() 方法来执行自定义命令Configuration.getProcessEngine().execute(new Command<Void>() { @Override public Void execute(Environment environment) throws Exception { //通过 Environment 对象,可以取得任意的流程引擎服务类 HistoryService historyService = environment.get(HistoryService.class); ... return null; } });
作者:deniro
链接:https://www.jianshu.com/p/0b3f071e9ce6
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。