回退,指的是用户主动回退到当前任务的上一流程节点(上一步骤)。
想象这样一种场景,当前用户接收任务后,发现这个任务不该由他办理或者这个任务存在严重的业务问题,这时就需要回退给上一步的办理者重新办理。
解决方案如下:
- 识别 “需要具有回退能力” 的任务。
- 为上述任务设计处理回退逻辑的监听器。
- 回退监听器接收一个参数,用于指定回退目的地的活动 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;
}
});