1:创建流程
使用 eclipse工作流插件 画图,如下是效果:
也可以直接使用如下的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");
}
执行后任务历史如下图:
3:任务退回
先将任务执行到董事长审批
,董事长因为老婆跟自己的亲弟弟跑了,心情不好,所以请假不批,要退回,如下图。
3.1:原理
每个任务节点都有流出指向和流入指向,如下图:
上图中的1
,对于开始节点就是流出指向,对于直属领导审批
就是流入指向,其他的同理,当启动流程实例,activiti会根据开始节点的流出指向1
,生成直属领导审批
对应的任务,其他的也都是同理,而任务跳转
和这里默认方式生成任务其实是一样的,只不过默认的指向不支持而已,我们可以通过手动干预,让它支持
,比如流程流转到了董事长审批
,要退回,怎么办呢?只需要将董事长审批的默认的流出指向删除,然后增加
指向上一个任务节点的流出指向就可以了,也就是变成下图这样:
这样,当我们完成了董事长审批
的任务后,就会生成部门领导审批
的任务,也就达到了退回的目的
。但是在完成之后,还需要将原始的指向关系恢复回来,后续流程才能正常流转,恢复后变成这样,其实就是原始状态:
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:测试
如下是当前执行到董事长审批
的状态:
执行如下代码回退到部门领导审批
:
@Test
public void jumpTo() {
turnTransition("707502", "usertask2", new HashMap<>());
}
执行后任务正常回退(其实就是我们手动干预指向后的流转而已,和正常流转没有任何区别)
到"部门负责人审批":
3.4:实现任意跳转
上述只是分析了如何回退到上一步,其实回退也是通过指定要流转的节点来实现的,因此,我们指定流转到哪个节点就能流转到哪个节点,也就能实现不同的效果,如撤回
等,下面看几个例子。
- 从“直属领导审批”直接到"董事长审批"
如下的状态:
执行如下代码,直接到"董事长审批":
@Test
public void jumpTo() {
turnTransition("730007", "usertask3", new HashMap<>());
}
执行后状态如下:
- 跳过"董事长审批",直接到结束
如下的状态:
执行如下代码:
@Test
public void jumpTo() {
turnTransition("737506", "endevent2", new HashMap<>());
}
执行后状态如下,可以看到流程直接结束了:
参考文章
Activiti任务退回、任意跳转(任务调度)功能实现及思路(简版)