Fork me on GitHub

Activiti(5)--任意节点跳转

1. 任意节点跳转以及原理

  • 常规节点跳转
    • 跳转到目标节点
    • 跳转到目标节点的入线
    • 跳转到目标节点的上一个节点并触发连线的条件计算
  • 多实例节点的跳转
    • 普通节点跳转到多实例节点
    • 多实例节点跳转到普通节点

比如总经理审批节点跳转到请假申请节点:

image

思路:

  1. 可以获取总经理审批节点对应的任务 ID, 实例 ID, 执行实例 ID
  2. 可以通过 planContiuneProcessInCompensation 方法让当前执行的实例按照我们预期的效果流转
  3. 我们可以将当前执行实例中的 currentFlowElement 字段设置为 请假申请 节点 XML 中的 id 值;
  4. 因为执行实例运转之后, 当前的任务节点并没有被删除, 所以需要手工删除;
  5. 历史表跳转之前的任务节点也不会被完成, 需要手工进行完成.

1.1. 实现方案

  • 获取 ActivitiEngineAgenda
  • commandContext.getExecutionEntityManager() 获取 ExecutionEntityManager
  • commandContext.getTaskEntityManager() 获取 TaskEntityManager
  • 设置执行实例的运行节点
  • 触发执行实例运转
  • 设置删除当前的任务节点
  • 更新历史实例表以及历史任务表, 当前的任务节点为完成状态
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@AllArgsConstructor
public class JumpCmd implements Command<Void> {

private String taskId;
private String targetNodeId;

@Override
public Void execute(CommandContext commandContext) {
ActivitiEngineAgenda agenda = commandContext.getAgenda();
TaskEntityManager taskEntityManager = commandContext.getTaskEntityManager();
TaskEntity taskEntity = taskEntityManager.findById(taskId);
// 执行实例 id
String executionId = taskEntity.getExecutionId();
String processDefinitionId = taskEntity.getProcessDefinitionId();
ExecutionEntityManager executionEntityManager = commandContext.getExecutionEntityManager();
HistoryManager historyManager = commandContext.getHistoryManager();
// 执行实例对象
ExecutionEntity executionEntity = executionEntityManager.findById(executionId);
Process process = ProcessDefinitionUtil.getProcess(processDefinitionId);
FlowElement flowElement = process.getFlowElement(targetNodeId);
if (flowElement == null) {
throw new RuntimeException("目标节点不存在");
}
// 将历史活动表更新
historyManager.recordActivityEnd(executionEntity, "jump");
// 设置当前流程
executionEntity.setCurrentFlowElement(flowElement);
// 跳转, 触发执行实例运转
agenda.planContinueProcessInCompensation(executionEntity);
// 从runtime 表中删除当前任务
taskEntityManager.delete(taskId);
// 将历史任务表更新, 历史任务标记为完成
historyManager.recordTaskEnd(taskId, "jump");
return null;
}
}

1.2. 测试场景

假设有如下流程定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?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: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="my-process">

<startEvent id="start"/>
<sequenceFlow id="flow1" sourceRef="start" targetRef="userTask1"/>
<userTask id="userTask1" name="userTask1" activiti:assignee="destiny1"/>
<sequenceFlow id="flow2" sourceRef="userTask1" targetRef="userTask2"/>
<userTask id="userTask2" name="userTask2" activiti:assignee="destiny2"/>
<sequenceFlow id="flow3" sourceRef="userTask2" targetRef="end"/>
<endEvent id="end"/>

</process>

</definitions>

结构如下:

start -> userTask1 -> userTask2 -> end

使用如下的执行顺序:

  1. startProcessInstance
  2. complete 任务 userTask1
  3. 执行 ManagementService().executeCommand(new JumpCmd("7502", "userTask1"))
  4. complete 任务 userTask1
  5. 执行 ManagementService().executeCommand(new JumpCmd("7502", "userTask1"))

然后查询 ACT_HI_ACTINST 表, 能够看到如下执行轨迹:

ID_ PROC_DEF_ID_ PROC_INST_ID_ EXECUTION_ID_ ACT_ID_ TASK_ID_ CALL_PROC_INST_ID_ ACT_NAME_ ACT_NAME_ ASSIGNEE_ START_TIME_ END_TIME_ DURATION_ DELETE_REASON_ TENANT_ID_
6 my-process:1:3 4 5 start startEvent 2019-03-04 00:37:54.328 2019-03-04 00:37:54.330 2 “”
7 my-process:1:3 4 5 userTask1 8 userTask1 userTask destiny1 2019-03-04 00:37:54.331 2019-03-04 00:38:19.920 25589 “”
2501 my-process:1:3 4 5 userTask2 2502 userTask2 userTask destiny2 2019-03-04 00:38:19.933 2019-03-04 00:38:44.834 24901 jump “”
5001 my-process:1:3 4 5 userTask1 5002 userTask1 userTask destiny1 2019-03-04 00:38:44.848 2019-03-04 00:39:02.232 17384 “”
7501 my-process:1:3 4 5 userTask2 7502 userTask2 userTask destiny2 2019-03-04 00:39:02.245 2019-03-04 00:39:19.718 17473 jump “”
10001 my-process:1:3 4 5 userTask1 10002 userTask1 userTask destiny1 2019-03-04 00:39:19.743 “”

可以看到我们已经完成了简单条件下的跳转

1.3. 关于自由跳转时历史活动不更新问题的解决

之前遇到一个错误的写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@AllArgsConstructor
public class JumpCmd implements Command<Void> {

private String taskId;
private String targetNodeId;

@Override
public Void execute(CommandContext commandContext) {
ActivitiEngineAgenda agenda = commandContext.getAgenda();
TaskEntityManager taskEntityManager = commandContext.getTaskEntityManager();
TaskEntity taskEntity = taskEntityManager.findById(taskId);
// 执行实例 id
String executionId = taskEntity.getExecutionId();
String processDefinitionId = taskEntity.getProcessDefinitionId();
ExecutionEntityManager executionEntityManager = commandContext.getExecutionEntityManager();
HistoryManager historyManager = commandContext.getHistoryManager();
// 执行实例对象
ExecutionEntity executionEntity = executionEntityManager.findById(executionId);
Process process = ProcessDefinitionUtil.getProcess(processDefinitionId);
FlowElement flowElement = process.getFlowElement(targetNodeId);
if (flowElement == null) {
throw new RuntimeException("目标节点不存在");
}
// 设置当前流程
executionEntity.setCurrentFlowElement(flowElement);
// 跳转, 触发执行实例运转
agenda.planContinueProcessInCompensation(executionEntity);
// 从runtime 表中删除当前任务
taskEntityManager.delete(taskId);
// 将历史活动表更新
historyManager.recordActivityEnd(executionEntity, "jump");
// 将历史任务表更新, 历史任务标记为完成
historyManager.recordTaskEnd(taskId, "jump");
return null;
}
}

与上文中正确写法的不同之处在于数据库更新操作的顺序. 但仅仅更换顺序也会到导致历史表中数据无法正确结束.

问题的原因在于第 25 行的时候已经为执行实例执行了 setCurrentFlowElement() 操作, 设置了最新的流程元素, 所以如果在后面再执行对 ACT_HI_ACTINST 的更新, 就无法找到正确的 FlowElement. 所以需要将 historyManager.recordActivityEnd(executionEntity, "jump"); 放在第 25 行之前

2. 跳转到目标节点的入线

假设有如下流程定义:

image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?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: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="my-process">
<startEvent id="start"/>
<userTask id="userTask1" name="userTask1" activiti:assignee="destiny1"/>
<userTask id="userTask2" name="userTask2" activiti:assignee="destiny2"/>
<userTask id="userTask3" name="userTask3" activiti:assignee="destiny3"/>
<endEvent id="end1"/>
<endEvent id="end2"/>
<sequenceFlow sourceRef="start" targetRef="userTask1"/>
<sequenceFlow sourceRef="userTask1" targetRef="userTask2">
<conditionExpression>${condition==1}</conditionExpression>
</sequenceFlow>
<sequenceFlow sourceRef="userTask1" targetRef="userTask3">
<conditionExpression>${condition==2}</conditionExpression>
</sequenceFlow>
<sequenceFlow sourceRef="userTask2" targetRef="end1"/>
<sequenceFlow sourceRef="userTask3" targetRef="end2"/>
</process>
</definitions>

将原先的跳转命令类稍作修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
@AllArgsConstructor
public class SequenceFlowJumpCmd implements Command<Void> {

private String taskId;
private String targetNodeId;

@Override
public Void execute(CommandContext commandContext) {
ActivitiEngineAgenda agenda = commandContext.getAgenda();
TaskEntityManager taskEntityManager = commandContext.getTaskEntityManager();
TaskEntity taskEntity = taskEntityManager.findById(taskId);
// 执行实例 id
String executionId = taskEntity.getExecutionId();
String processDefinitionId = taskEntity.getProcessDefinitionId();
ExecutionEntityManager executionEntityManager = commandContext.getExecutionEntityManager();
HistoryManager historyManager = commandContext.getHistoryManager();
// 执行实例对象
ExecutionEntity executionEntity = executionEntityManager.findById(executionId);
Process process = ProcessDefinitionUtil.getProcess(processDefinitionId);
FlowElement flowElement = process.getFlowElement(targetNodeId);
if (flowElement == null) {
throw new RuntimeException("目标节点不存在");
}
SequenceFlow sequenceFlow = null;
if (flowElement instanceof FlowNode) {
FlowNode flowNode = (FlowNode) flowElement;
// 找到所有的入线, 并取其中唯一的一条
List<SequenceFlow> incomingFlows = flowNode.getIncomingFlows();
sequenceFlow = incomingFlows.get(0);
}
if (sequenceFlow == null) {
throw new RuntimeException("目标连线不存在");
}
// 将历史活动表更新
historyManager.recordActivityEnd(executionEntity, "jump");
// 设置当前流程
executionEntity.setCurrentFlowElement(sequenceFlow);
// 触发执行实例运转, 第二个参数为是否参与计算
agenda.planTakeOutgoingSequenceFlowsOperation(executionEntity, true);
// 从runtime 表中删除当前任务
taskEntityManager.delete(taskId);
// 将历史任务表更新, 历史任务标记为完成
historyManager.recordTaskEnd(taskId, "jump");
return null;
}
}
  • 通过连线触发实例的时候, 连线上的条件不会参与计算
  • 只有实例经过节点, 网关的时候, 连线上的条件才会经过计算

3. 跳转到目标节点入线连接的节点

因为实例是从节点离开, 因此后面的连线是可以参与运算的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
@AllArgsConstructor
public class SequenceFlowSourceJumpCmd implements Command<Void> {

private String taskId;
private String targetNodeId;
private Map<String, Object> condition;

@Override
public Void execute(CommandContext commandContext) {
ActivitiEngineAgenda agenda = commandContext.getAgenda();
TaskEntityManager taskEntityManager = commandContext.getTaskEntityManager();
TaskEntity taskEntity = taskEntityManager.findById(taskId);
// 执行实例 id
String executionId = taskEntity.getExecutionId();
String processDefinitionId = taskEntity.getProcessDefinitionId();
ExecutionEntityManager executionEntityManager = commandContext.getExecutionEntityManager();
HistoryManager historyManager = commandContext.getHistoryManager();
// 执行实例对象
ExecutionEntity executionEntity = executionEntityManager.findById(executionId);
Process process = ProcessDefinitionUtil.getProcess(processDefinitionId);
FlowElement flowElement = process.getFlowElement(targetNodeId);
if (flowElement == null) {
throw new RuntimeException("目标节点不存在");
}
SequenceFlow sequenceFlow = null;
if (flowElement instanceof FlowNode) {
FlowNode flowNode = (FlowNode) flowElement;
// 找到所有的入线, 并取其中唯一的一条
List<SequenceFlow> incomingFlows = flowNode.getIncomingFlows();
sequenceFlow = incomingFlows.get(0);
}
if (sequenceFlow == null) {
throw new RuntimeException("目标连线不存在");
}
FlowElement sourceFlowElement = sequenceFlow.getSourceFlowElement();
executionEntity.setVariables(condition);
// 将历史活动表更新
historyManager.recordActivityEnd(executionEntity, "jump");
// 设置当前流程
executionEntity.setCurrentFlowElement(sourceFlowElement);
// 触发执行实例运转, 第二个参数为是否参与计算
agenda.planTakeOutgoingSequenceFlowsOperation(executionEntity, true);
// 从runtime 表中删除当前任务
taskEntityManager.delete(taskId);
// 将历史任务表更新, 历史任务标记为完成
historyManager.recordTaskEnd(taskId, "jump");
return null;
}
}

>