activiti学习之回退实现

1:创建流程

使用 eclipse工作流插件 画图,如下是效果:

activiti学习之回退实现

也可以直接使用如下的xml:

<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/test">
    <process id="jumpTo" name="jumpTo" isExecutable="true">
        <startEvent id="startevent1" name="Start"></startEvent>
        <userTask id="usertask1" name="直属领导审批" activiti:assignee="小赵">
            <extensionElements>
                <modeler:initiator-can-complete xmlns:modeler="http://activiti.com/modeler">false</modeler:initiator-can-complete>
            </extensionElements>
        </userTask>
        <sequenceFlow id="flow1" sourceRef="startevent1" targetRef="usertask1"></sequenceFlow>
        <userTask id="usertask2" name="部门领导审批" activiti:assignee="小马">
            <extensionElements>
                <modeler:initiator-can-complete xmlns:modeler="http://activiti.com/modeler">false</modeler:initiator-can-complete>
            </extensionElements>
        </userTask>
        <sequenceFlow id="flow2" sourceRef="usertask1" targetRef="usertask2"></sequenceFlow>
        <userTask id="usertask3" name="董事长审批" activiti:assignee="小朱">
            <extensionElements>
                <modeler:initiator-can-complete xmlns:modeler="http://activiti.com/modeler">false</modeler:initiator-can-complete>
            </extensionElements>
        </userTask>
        <sequenceFlow id="flow3" sourceRef="usertask2" targetRef="usertask3"></sequenceFlow>
        <endEvent id="endevent2" name="End"></endEvent>
        <sequenceFlow id="flow5" sourceRef="usertask3" targetRef="endevent2"></sequenceFlow>
    </process>
    <bpmndi:BPMNDiagram id="BPMNDiagram_jumpTo">
        <bpmndi:BPMNPlane bpmnElement="jumpTo" id="BPMNPlane_jumpTo">
            <bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
                <omgdc:Bounds height="35.0" width="35.0" x="110.0" y="240.0"></omgdc:Bounds>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="usertask1" id="BPMNShape_usertask1">
                <omgdc:Bounds height="55.0" width="105.0" x="280.0" y="230.0"></omgdc:Bounds>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="usertask2" id="BPMNShape_usertask2">
                <omgdc:Bounds height="55.0" width="105.0" x="490.0" y="230.0"></omgdc:Bounds>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="usertask3" id="BPMNShape_usertask3">
                <omgdc:Bounds height="55.0" width="105.0" x="690.0" y="230.0"></omgdc:Bounds>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="endevent2" id="BPMNShape_endevent2">
                <omgdc:Bounds height="35.0" width="35.0" x="900.0" y="240.0"></omgdc:Bounds>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
                <omgdi:waypoint x="145.0" y="257.0"></omgdi:waypoint>
                <omgdi:waypoint x="280.0" y="257.0"></omgdi:waypoint>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
                <omgdi:waypoint x="385.0" y="257.0"></omgdi:waypoint>
                <omgdi:waypoint x="490.0" y="257.0"></omgdi:waypoint>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3">
                <omgdi:waypoint x="595.0" y="257.0"></omgdi:waypoint>
                <omgdi:waypoint x="690.0" y="257.0"></omgdi:waypoint>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="flow5" id="BPMNEdge_flow5">
                <omgdi:waypoint x="795.0" y="257.0"></omgdi:waypoint>
                <omgdi:waypoint x="900.0" y="257.0"></omgdi:waypoint>
            </bpmndi:BPMNEdge>
        </bpmndi:BPMNPlane>
    </bpmndi:BPMNDiagram>
</definitions>

2:测试流程可用

不涉及基础环境准备,关于基础环境可以参考 activiti学习之基础环境搭建 这篇文章。

2.1:部署流程定义

/*
 * 部署流程定义
 */
@Test
public void deploy() {
    Deployment deployment = repositoryService.createDeployment() // 创建部署
            .addClasspathResource("com/jh/activiti/jumpTo.bpmn20.xml") // 加载流程资源文件
            .name("测试任务退回") // 流程名称
            .deploy(); // 部署
    System.out.println("流程部署ID:" + deployment.getId());
    System.out.println("流程部署Name:" + deployment.getName());
}

2.2:启动流程实例

/**
 * 启动流程实例
 */
@Test
public void start() {
    // 不会在act_ru_task表中生成数据,即不会生成任务,但是会在act_ru_execution表中生成数据,可以通过queryExecution查询
    // , 通过completeTask执行
    ProcessInstance pi = runtimeService.startProcessInstanceByKey("jumpTo"); // 流程定义表的KEY字段值
    System.out.println("流程实例ID:" + pi.getId());
    System.out.println("流程定义ID:" + pi.getProcessDefinitionId());
}

2.3:测试任务执行

@Test
public void complete() {
    // 直属领导审批
//        taskService.complete("677507");
    // 部门领导审批
//        taskService.complete("680007");
    // 董事长审批
    taskService.complete("682502");
}

执行后任务历史如下图:

activiti学习之回退实现

3:任务退回

先将任务执行到董事长审批,董事长因为老婆跟自己的亲弟弟跑了,心情不好,所以请假不批,要退回,如下图。

activiti学习之回退实现

3.1:原理

每个任务节点都有流出指向和流入指向,如下图:

activiti学习之回退实现

上图中的1,对于开始节点就是流出指向,对于直属领导审批就是流入指向,其他的同理,当启动流程实例,activiti会根据开始节点的流出指向1,生成直属领导审批对应的任务,其他的也都是同理,而任务跳转
和这里默认方式生成任务其实是一样的,只不过默认的指向不支持而已,我们可以通过手动干预,让它支持,比如流程流转到了董事长审批,要退回,怎么办呢?只需要将董事长审批的默认的流出指向删除,然后增加
指向上一个任务节点的流出指向就可以了,也就是变成下图这样:

activiti学习之回退实现

这样,当我们完成了董事长审批的任务后,就会生成部门领导审批的任务,也就达到了退回的目的。但是在完成之后,还需要将原始的指向关系恢复回来,后续流程才能正常流转,恢复后变成这样,其实就是原始状态:

activiti学习之回退实现

3.2:程序

/**
 * 流程转向操作
 *
 * @param taskId     当前任务ID
 * @param activityId 目标节点任务ID
 * @param variables  流程变量
 * @throws Exception
 */
public static void turnTransition(String taskId, String activityId, Map<String, Object> variables) {
    // 当前节点(任务对应的动态活动节点)
    ActivityImpl currActivity = findActivitiImpl(taskId, null);
    // 清空当前流向
    List<PvmTransition> oriPvmTransitionList = clearTransition(currActivity);

    // 创建新流向
    TransitionImpl newTransition = currActivity.createOutgoingTransition();
    // 目标节点
    ActivityImpl pointActivity = findActivitiImpl(taskId, activityId);
    // 设置新流向的目标节点
    newTransition.setDestination(pointActivity);

    // 执行转向任务(这里完成任务,因为活动的指向已经变了,因此会生成新指向节点对应的任务,也就实现了回退的效果)
    taskService.complete(taskId, variables);
    // 删除目标节点新流入
    pointActivity.getIncomingTransitions().remove(newTransition);

    // 还原以前流向
    restoreTransition(currActivity, oriPvmTransitionList);
}

/**
 * 清空指定活动节点现有流向,且将清空的现有流向返回
 *
 * @param activityImpl 活动节点
 * @return 节点流向集合
 */
public static List<PvmTransition> clearTransition(ActivityImpl activityImpl) {
    // 存储当前节点所有流向临时变量(存储老的从任务节点出去的线,备份,以便后续恢复)
    List<PvmTransition> oriPvmTransitionList = new ArrayList<>();
    // 获取当前节点所有流向,存储到临时变量,然后清空(获取所有出去的线,activiti内部通过该线来确定生成下一个任务)
    List<PvmTransition> pvmTransitionList = activityImpl.getOutgoingTransitions();
    for (PvmTransition pvmTransition : pvmTransitionList) {
        oriPvmTransitionList.add(pvmTransition);
    }
    pvmTransitionList.clear();

    return oriPvmTransitionList;
}

/**
 * 根据任务ID和节点ID获取活动节点 <br>
 *
 * @param taskId
 * @param activityId 活动节点ID <br>
 *                   如果为null或"",则默认查询当前活动节点 <br>
 *                   如果为"end",则查询结束节点 <br>
 * @return
 * @throws Exception
 */
public static ActivityImpl findActivitiImpl(String taskId, String activityId) {
    // 取得流程定义,对应的是act_re_procdef的数据
    ProcessDefinitionEntity processDefinition = getProcDefEntityByTaskId(taskId);
    // 获取当前活动节点ID
    if (StringUtils.isEmpty(activityId)) {
        activityId = getTaskById(taskId).getTaskDefinitionKey(); // 获取当前任务对应的节点的ID,即活动ID,也即用户任务节点的ID,如<userTask id="usertask1">
    }
    // 根据流程定义,获取该流程实例的结束节点
    ProcessDefinitionImpl processDefinitionImpl = (ProcessDefinitionImpl) getReadOnlyProcessDefinitionByProcDefId(processDefinition.getId());
    if (activityId.toUpperCase().equals("END")) {
        for (ActivityImpl activityImpl : processDefinitionImpl.getActivities()) {
            List<PvmTransition> pvmTransitionList = activityImpl.getOutgoingTransitions();
            if (pvmTransitionList.isEmpty()) {
                return activityImpl;
            }
        }
    }
    // 根据节点ID,获取对应的活动节点,活动节点可以认为是任务执行过程中的动态节点信息,如包含了本节点出去的线outgoingTransition,和指向自己的线incomingTransition,这些都是运行时的动态信息
    ActivityImpl activityImpl = processDefinitionImpl.findActivity(activityId);
    // 此处改动,无法获取到对应的活动节点,所以此处采用迂回的方式,如果前面可以取到则跳过,如果没有取到则自己取
    if (activityImpl == null) {
        List<ActivityImpl> activities = processDefinitionImpl.getActivities();
        for (ActivityImpl actImpl : activities) {
            if (actImpl.getId().equals(activityId)) {
                activityImpl = actImpl;
                break;
            }
        }
    }
    return activityImpl;
}

private static Task getTaskById(String taskId) {
    Task currTask = taskService.createTaskQuery().taskId(taskId).singleResult();
    return currTask;
}

private static ProcessDefinitionEntity getProcDefEntityByTaskId(String taskId) {
//        Task currTask = taskService.createTaskQuery().taskId(taskId).singleResult();
    Task currTask = getTaskById(taskId);
    // 获取流程定义,对应的表示act_re_procdef
    ProcessDefinitionEntity pde = (ProcessDefinitionEntity) ((RepositoryServiceImpl) repositoryService)
            .getDeployedProcessDefinition(currTask.getProcessDefinitionId());
    return pde;
}

public static ReadOnlyProcessDefinition getReadOnlyProcessDefinitionByProcDefId(String procDefId) {
    return ((RepositoryServiceImpl) repositoryService).getDeployedProcessDefinition(procDefId);
}

/**
 * 清空指定节点现有流向,且将新流向接入
 *
 * @param activityImpl         活动节点
 * @param oriPvmTransitionList 新流向节点集合
 */
public static void restoreTransition(ActivityImpl activityImpl, List<PvmTransition> oriPvmTransitionList) {
    // 清空现有流向
    List<PvmTransition> pvmTransitionList = activityImpl.getOutgoingTransitions();
    pvmTransitionList.clear();
    // 还原以前流向
    for (PvmTransition pvmTransition : oriPvmTransitionList) {
        pvmTransitionList.add(pvmTransition);
    }
}

3.3:测试

如下是当前执行到董事长审批的状态:

activiti学习之回退实现

执行如下代码回退到部门领导审批:

@Test
public void jumpTo() {
    turnTransition("707502", "usertask2", new HashMap<>());
}

执行后任务正常回退(其实就是我们手动干预指向后的流转而已,和正常流转没有任何区别)到"部门负责人审批":

activiti学习之回退实现

3.4:实现任意跳转

上述只是分析了如何回退到上一步,其实回退也是通过指定要流转的节点来实现的,因此,我们指定流转到哪个节点就能流转到哪个节点,也就能实现不同的效果,如撤回等,下面看几个例子。

  • 从“直属领导审批”直接到"董事长审批"

如下的状态:

activiti学习之回退实现

执行如下代码,直接到"董事长审批":

@Test
public void jumpTo() {
    turnTransition("730007", "usertask3", new HashMap<>());
}

执行后状态如下:

activiti学习之回退实现

  • 跳过"董事长审批",直接到结束

如下的状态:

activiti学习之回退实现

执行如下代码:

@Test
public void jumpTo() {
    turnTransition("737506", "endevent2", new HashMap<>());
}

执行后状态如下,可以看到流程直接结束了:

activiti学习之回退实现

参考文章

Activiti任务退回、任意跳转(任务调度)功能实现及思路(简版)

上一篇:flowable实战(四)flowable任务实例管理接口


下一篇:springboot整合flowable简单的demo