说说在 jBPM 工作流中如何实现【回退】功能

回退,指的是用户主动回退到当前任务的上一流程节点(上一步骤)。

想象这样一种场景,当前用户接收任务后,发现这个任务不该由他办理或者这个任务存在严重的业务问题,这时就需要回退给上一步的办理者重新办理。

解决方案如下:

  1. 识别 “需要具有回退能力” 的任务。
  2. 为上述任务设计处理回退逻辑的监听器。
  3. 回退监听器接收一个参数,用于指定回退目的地的活动 ID。之所以这样设计是因为,回退的出发地与目的地可能相隔着多个活动。这个参数也可以设计为流程变量,这样可以在流程运行时动态算出。
  4. 回退监听器动态创建一条转移路径,指向回退目的地的活动。
  5. 定义【回退任务】API。
  6. 如果回退操作造成业务 “损失”,那么就必须在【回退任务】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;
    }
});

发表评论

电子邮件地址不会被公开。 必填项已用*标注