说说在 jBPM 工作流中如何实现【撤销】功能

撤销(Withdraw):针对当前用户已办理的任务(历史任务),重置回待办状态。

撤销一般发生在以下场景中:当用户办理完任务后,发现办理的任务存在业务错误或者觉得下一节点办理人需要重新选择,那么这时就需要撤销任务啦 O(∩_∩)O哈哈~

注意:如果下一节点办理人已完成任务或者流程可能已经流转了若干节点(甚至走了分支),那么这时执行【撤销】操作,就需要对业务进行很大的补偿。是否必要这么做,需要权衡。

假设,只能【撤销】到上一步,解决方案如下:

  1. 为【撤销】到上一步的活动定义一个【撤销监听器】,动态生成当前活动到上一步活动的转移路径。
  2. 定义【撤销命令】,在命令中结束当前活动的所有任务(清除当前活动的历史痕迹,如果需要的话),然后转移到上一步活动。之所以使用命令模式,是因为这里可能涉及多处的写数据库操作,所以利用命令模式来实现事务的控制。
  3. 如果【撤销】操作引发业务损失,那么需要在【撤销命令】中进行补偿;如果业务损失很严重(比如下一步活动已办理),那么可以拒绝【撤销】操作。

流程定义:

注意:虚线的【撤销】路径是动态生成的。

jPDL:

<?xml version="1.0" encoding="UTF-8"?>

<process name="Withdraw" xmlns="http://jbpm.org/4.4/jpdl">
    <start name="start1" g="124,211,48,48">
        <transition to="申请"/>
    </start>
    <end name="end1" g="487,213,48,48"/>
    <task name="申请" g="213,207,92,52" assignee="Deniro">
        <transition to="审核">
            <!-- 设置撤销监听器-->
            <event-listener class="net.deniro.jbpm.WithdrawListener"/>
        </transition>
    </task>
    <task name="审核" g="343,209,92,52" assignee="Jack">
        <transition to="end1"/>
    </task>
</process>

在【审核】的转移路径上设置了撤销监听器,监听器定义如下:

public class WithdrawListener implements EventListener {

    /**
     * 动态创建【撤销】路径
     *
     * @param execution
     * @throws Exception
     */
    @Override
    public void notify(EventListenerExecution execution) throws Exception {
        TransitionImpl transition = ((ExecutionImpl) execution).getTransition();

        //【撤销】操作的源活动
        ActivityImpl from = transition.getDestination();

        //【撤销】操作的目标活动
        ActivityImpl to = transition.getSource();

        //合理性判断
        if (from == null || to == null) {
            throw new Exception("无法【撤销】");
        }

        //动态创建转移路径
        TransitionImpl withdrawTransition = from.createOutgoingTransition();
        withdrawTransition.setName(from.getName() + " 撤销到 " + to.getName());
        withdrawTransition.setDestination(to);
    }
}

通过构造函数,传入流程实例与撤销目标活动名称。

撤销命令:

public class WithdrawCommand implements Command<Void> {

    /**
     * 流程实例 ID
     */
    private String processId;

    /**
     * 目标活动名称
     */
    private String targetActivityName;

    /**
     * @param processId
     * @param targetActivityName
     */
    public WithdrawCommand(String processId, String targetActivityName) {
        this.processId = processId;
        this.targetActivityName = targetActivityName;
    }

    @Override
    public Void execute(Environment environment) throws Exception {

        /**
         * 获取当前活动名称
         */
        ExecutionService executionService = environment.get(ExecutionService.class);
        Execution execution = executionService.findExecutionById(processId);
        Set<String> activityNames = execution.findActiveActivityNames();
        //合理性判断
        if (activityNames == null || activityNames.isEmpty()) {
            throw new Exception("无法获取当前活动名称");
        }
        if (activityNames.size() > 1) {
            throw new Exception("存在多个当前活动");
        }
        String currentActivityName = activityNames.iterator().next();//当前活动名称

        /**
         * 执行撤销任务
         */
        String transitionName = currentActivityName + " 撤销到 " + targetActivityName;//转移路径名称
        TaskService taskService = environment.get(TaskService.class);
        List<Task> tasks = taskService.createTaskQuery().processInstanceId(processId)
                .activityName(currentActivityName).list();//获取当前活动的任务
        for (Task task : tasks) {//撤销任务
            try {
                taskService.completeTask(task.getId(), transitionName);
            } catch (Exception e) {//说明当前活动已非撤销操作的目标活动咯,可能已经流转了多个节点
                throw new Exception("路径:" + transitionName + "不存在");
            }
        }

        /**
         * 需要的话,清除历史(删除历史活动实例和历史任务)
         */
        HistoryService historyService = environment.get(HistoryService.class);
        HistoryActivityInstanceImpl historyActivityInstance = (HistoryActivityInstanceImpl)
                historyService.createHistoryActivityInstanceQuery().activityName
                        (currentActivityName).executionId(processId).uniqueResult();
        //使用 Hibernate 的 Session 执行删除操作
        Session session = environment.get(Session.class);
        session.delete(historyActivityInstance);
        return null;
    }
}

该命令执行以下步骤:

  1. 获取当前活动名称。
  2. 执行撤销任务。
  3. 清除历史。

单元测试:

//发起流程实例
ProcessInstance processInstance=executionService.startProcessInstanceByKey("Withdraw");
String instanceId=processInstance.getId();//实例 ID

//完成【申请】任务
Task applyTask=taskService.findPersonalTasks("Deniro").get(0);
taskService.completeTask(applyTask.getId());

//断言到【审核】任务
processInstance=executionService.findProcessInstanceById(instanceId);
assertTrue(processInstance.isActive("审核"));

//执行【撤销】操作
Configuration.getProcessEngine().execute(new WithdrawCommand(instanceId,"申请"));

//断言已清除历史
List<HistoryTask> historyTasks=historyService.createHistoryTaskQuery().assignee
        ("Jack").executionId(instanceId).list();
assertEquals(0,historyTasks.size());//办理人 Jack 已无历史任务
List<HistoryActivityInstance> historyActivityInstances=historyService
        .createHistoryActivityInstanceQuery().activityName("审核").list();
assertEquals(0,historyActivityInstances.size());//已无【审核】活动的历史信息

//断言到【申请】任务
processInstance=executionService.findProcessInstanceById(instanceId);
assertTrue(processInstance.isActive("申请"));

//断言【申请】任务被分配给了【Deniro】
List<Task> applyTasks=taskService.findPersonalTasks("Deniro");
assertEquals(1,applyTasks.size());

//完成【申请】任务
taskService.completeTask(applyTasks.get(0).getId());

//完成【审核】任务
Task auditTask=taskService.findPersonalTasks("Jack").get(0);
taskService.completeTask(auditTask.getId());

//断言流程已结束
assertProcessInstanceEnded(instanceId);

是不是很简单呀 O(∩_∩)O哈哈~

猜你喜欢

转载自blog.csdn.net/deniro_li/article/details/80500629