前面讲到的条件流程执行是通过调整决策节点的执行逻辑来实现的,那分支与合并流程呢?我们又应该如何去处理其执行逻辑呢?下面我们来看一条存在有分支与合并的简单流程。如下图: 请假申请->部门领导审批(多领导并行)->公司领导审批
流程定义
src/test/resources/leave_06.json
注:以下json并非全部,缺少位置信息。
{
"name": "leave",
"displayName": "请假",
"instanceUrl": "leaveForm",
"nodes": [
{
"id": "start",
"type": "snaker:start",
"properties": {
"width": "120",
"height": "80"
},
"text": {
"value": "开始"
}
},
{
"id": "apply",
"type": "snaker:task",
"properties": {
"assignee": "approve.operator",
"taskType": "Major",
"performType": "ANY",
"autoExecute": "N",
"width": "120",
"height": "80",
"field": {
"userKey": "1"
}
},
"text": {
"value": "请假申请"
}
},
{
"id": "approveDept_a1",
"type": "snaker:task",
"properties": {
"assignmentHandler": "com.mldong.config.FlowAssignmentHandler",
"taskType": "Major",
"performType": "ANY",
"autoExecute": "N",
"width": 120,
"height": 80,
"field": {}
},
"text": {
"value": "部门领导审批A1"
}
},
{
"id": "approveBoss",
"type": "snaker:task",
"x": 1400,
"y": 340,
"properties": {
"assignmentHandler": "com.mldong.config.FlowAssignmentHandler",
"taskType": "Major",
"performType": "ANY",
"autoExecute": "N",
"width": "120",
"height": "80"
},
"text": {
"value": "公司领导审批"
}
},
{
"id": "end",
"type": "snaker:end",
"properties": {
"width": "120",
"height": "80"
},
"text": {
"value": "结束"
}
},
{
"id": "be5429b2-b9f6-4ae7-ae3c-dbc2c4dae947",
"type": "snaker:fork",
"properties": {}
},
{
"id": "approveDept_a2",
"type": "snaker:task",
"properties": {
"height": 80,
"width": 120,
"field": {}
},
"text": {
"value": "部门领导审批A2"
}
},
{
"id": "approveDept_b1",
"type": "snaker:task",
"x": 1020,
"y": 160,
"properties": {
"height": 80,
"width": 120,
"field": {},
"taskType": "Major",
"performType": "ANY"
},
"text": {
"value": "部门领导审批B1"
}
},
{
"id": "approveDept_b2",
"type": "snaker:task",
"x": 1020,
"y": 500,
"properties": {
"height": 80,
"width": 120,
"field": {}
},
"text": {
"value": "部门领导审批B2"
}
},
{
"id": "a4fccb9c-0146-4b20-929f-fed681a37173",
"type": "snaker:join",
"properties": {}
}
],
"edges": [
{
"id": "3037be41-5682-4344-b94a-9faf5c3e62ba",
"type": "snaker:transition",
"sourceNodeId": "start",
"targetNodeId": "apply",
"properties": {}
},
{
"id": "0ea009ea-d48e-4d69-95bf-de64fc0fc84b",
"type": "snaker:transition",
"sourceNodeId": "apply",
"targetNodeId": "be5429b2-b9f6-4ae7-ae3c-dbc2c4dae947",
"properties": {}
},
{
"id": "88854f27-0781-4dd0-8dfa-d44f27550519",
"type": "snaker:transition",
"sourceNodeId": "be5429b2-b9f6-4ae7-ae3c-dbc2c4dae947",
"targetNodeId": "approveDept_a1",
"properties": {}
},
{
"id": "4aa89dd3-90b5-415d-9853-2b25507e7e3f",
"type": "snaker:transition",
"sourceNodeId": "be5429b2-b9f6-4ae7-ae3c-dbc2c4dae947",
"targetNodeId": "approveDept_a2",
"properties": {}
},
{
"id": "7620a4e0-d147-4ca4-a140-a14003c0d9cd",
"type": "snaker:transition",
"sourceNodeId": "approveDept_a1",
"targetNodeId": "approveDept_b1",
"properties": {}
},
{
"id": "4f1b1351-e9fb-42f2-a2a5-e820a71d41c6",
"type": "snaker:transition",
"sourceNodeId": "approveDept_a2",
"targetNodeId": "approveDept_b2",
"properties": {}
},
{
"id": "a1dee2c8-c505-4a8d-abae-c1f0710dd70a",
"type": "snaker:transition",
"sourceNodeId": "approveDept_b1",
"targetNodeId": "a4fccb9c-0146-4b20-929f-fed681a37173",
"properties": {}
},
{
"id": "f132dfe3-fd3a-4c88-b8f7-7c89fba602fe",
"type": "snaker:transition",
"sourceNodeId": "approveDept_b2",
"targetNodeId": "a4fccb9c-0146-4b20-929f-fed681a37173",
"properties": {}
},
{
"id": "11e10610-576b-4965-a16b-23712ddb9d94",
"type": "snaker:transition",
"sourceNodeId": "a4fccb9c-0146-4b20-929f-fed681a37173",
"targetNodeId": "approveBoss",
"properties": {}
},
{
"id": "1575853c-878b-49e6-bfd8-db21c986bd0b",
"type": "snaker:transition",
"sourceNodeId": "approveBoss",
"targetNodeId": "end",
"properties": {}
}
]
}
旧的代码逻辑
JoinModel.java
和ForkModel.java
调用输入变迁方法runOutTransition(...)
package com.mldong.flow.engine.model;
import com.mldong.flow.engine.core.Execution;
/**
*
* 分支模型
* @author mldong
* @date 2023/4/25
*/
public class ForkModel extends NodeModel {
@Override
public void exec(Execution execution) {
// 执行分支节点自定义执行逻辑
runOutTransition(execution);
}
}
package com.mldong.flow.engine.model;
import com.mldong.flow.engine.core.Execution;
import lombok.Data;
/**
*
* 合并模型
* @author mldong
* @date 2023/4/25
*/
@Data
public class JoinModel extends NodeModel {
@Override
public void exec(Execution execution) {
// 执行合并节点自定义执行逻辑
runOutTransition(execution);
}
}
修改src/test/java/com/mldong/flow/ExecuteTest.java
方法executeLeave_06,执行逻辑和之前的一样,就是解析的流程定义文件修改为leave_06.json。
- 加载配置
- 解析流程定义文件
- 执行流程
package com.mldong.flow;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.Dict;
import com.mldong.flow.engine.MockExecuteTask;
import com.mldong.flow.engine.cfg.Configuration;
import com.mldong.flow.engine.core.Execution;
import com.mldong.flow.engine.model.ProcessModel;
import com.mldong.flow.engine.parser.ModelParser;
import org.junit.Test;
/**
*
* 执行测试
* @author mldong
* @date 2023/5/1
*/
public class ExecuteTest {
@Test
public void executeLeave_06() {
new Configuration();
// 将流程定义文件解析成流程模型
ProcessModel processModel = ModelParser.parse(IoUtil.readBytes(this.getClass().getResourceAsStream("/leave_06.json")));
// 构造执行参数
Execution execution = new Execution();
// 设置当前流程模型
execution.setProcessModel(processModel);
// 设置扩展属性
execution.setArgs(Dict.create());
// 拿到开始节点调用执行方法
processModel.getStart().execute(execution);
// 执行模拟执行任务方法
new MockExecuteTask(execution).run();
}
}
执行结果如下:
调用模型节点执行方法:model:StartModel,name:start,displayName:开始
创建任务:apply,请假申请
设置任务状态为已完成:apply,请假申请
调用模型节点执行方法:model:TaskModel,name:apply,displayName:请假申请
创建任务:approveDept_a1,部门领导审批A1
创建任务:approveDept_a2,部门领导审批A2
设置任务状态为已完成:approveDept_a1,部门领导审批A1
调用模型节点执行方法:model:TaskModel,name:approveDept_a1,displayName:部门领导审批A1
创建任务:approveDept_b1,部门领导审批B1
设置任务状态为已完成:approveDept_a2,部门领导审批A2
调用模型节点执行方法:model:TaskModel,name:approveDept_a2,displayName:部门领导审批A2
创建任务:approveDept_b2,部门领导审批B2
设置任务状态为已完成:approveDept_b1,部门领导审批B1
调用模型节点执行方法:model:TaskModel,name:approveDept_b1,displayName:部门领导审批B1
创建任务:approveBoss,公司领导审批
设置任务状态为已完成:approveDept_b2,部门领导审批B2
调用模型节点执行方法:model:TaskModel,name:approveDept_b2,displayName:部门领导审批B2
创建任务:approveBoss,公司领导审批
设置任务状态为已完成:approveBoss,公司领导审批
调用模型节点执行方法:model:TaskModel,name:approveBoss,displayName:公司领导审批
调用模型节点执行方法:model:EndModel,name:end,displayName:结束
设置任务状态为已完成:approveBoss,公司领导审批
调用模型节点执行方法:model:TaskModel,name:approveBoss,displayName:公司领导审批
调用模型节点执行方法:model:EndModel,name:end,displayName:结束
我们会看到,executeLeave_06的执行也出现两个公司领导审批和结束,因为我们对分支节点和合并节点进行处理是默认的处理方式,即直接调用runOutTransition(...)
方法。下面我们要重新调整节点的处理方式,使其完整且正确的从开始节点走向结束节点。
分支与合并节点分析
分支节点
分支节点和开始节点一样,没有什么特殊执行步骤,只需要调用输出边执行方法,驱动流程往下一个节点执行。
public class ForkModel extends NodeModel {
@Override
public void exec(Execution execution) {
// 执行分支节点自定义执行逻辑
runOutTransition(execution);
}
}
合并节点
合并节点需要判断是否达到合并条件,如果达到合并条件,则可以继续调用输出边执行方法,驱动流程往下一个节点执行。
执行流程图
执行流程说明
- 是否达到合并条件,主要判断前面节点产生的任务是否完成
- 如果达到合并条件,则调用输出边执行方法
- 如果未达到合并条件,则继续阻塞,不往前进行
如下,我们可以通过给Execution对象增加是否可合并属性来判断是否调用输出边执行方法
public class JoinModel extends NodeModel {
@Override
public void exec(Execution execution) {
// 执行合并节点自定义执行逻辑
if(execution.isMerged()) {
runOutTransition(execution);
}
}
}
当然,仅上面一步还是不足以完成触发的,因为isMerged永远为false,下面我们画类图重新组织代码。
类图
虽然判断是否达到合并条件可直接在JoinModel的exec中实现,但是为了通用性,我们可以按如下方式来组织代码。即在上文的基础上增加MergeBranchHandler实现类,通过实现类去修改Execution 的isMerged属性。
代码实现
流程执行参数core/Execution.java增加isMerged属性
package com.mldong.flow.engine.core;
import cn.hutool.core.lang.Dict;
import com.mldong.flow.engine.entity.ProcessTask;
import com.mldong.flow.engine.enums.TaskStateEnum;
import com.mldong.flow.engine.model.ProcessModel;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
*
* 执行对象参数
* @author mldong
* @date 2023/4/25
*/
@Data
public class Execution {
// 流程实例ID
private String processInstanceId;
// 当前流程任务ID
private String processTaskId;
// 执行对象扩展参数
private Dict args;
// 当前流程模型
private ProcessModel processModel;
// 当前任务
private ProcessTask processTask;
// 所有任务集合
private List<ProcessTask> processTaskList = new ArrayList<>();
// 是否可合并
private boolean isMerged;
/**
* 添加任务到任务集合
* @param processTask
*/
public void addTask(ProcessTask processTask) {
this.processTaskList.add(processTask);
}
/**
* 获取正在进行中的任务列表
* @return
*/
public List<ProcessTask> getDoingTaskList() {
return this.processTaskList.stream().filter(item->{
return TaskStateEnum.DOING.getCode().equals(item.getTaskState());
}).collect(Collectors.toList());
}
}
增加合并分支操作的处理器,handlers/impl/MergeBranchHandler.java
package com.mldong.flow.engine.handlers.impl;
import com.mldong.flow.engine.core.Execution;
import com.mldong.flow.engine.handlers.IHandler;
import com.mldong.flow.engine.model.JoinModel;
/**
* 合并分支操作的处理器
* @author mldong
* @date 2023/5/21
*/
public class MergeBranchHandler implements IHandler {
private JoinModel joinModel;
public MergeBranchHandler(JoinModel joinModel) {
this.joinModel = joinModel;
}
@Override
public void handle(Execution execution) {
// 判断是否存在正在执行的任务,存在则不允许合并
execution.setMerged(execution.getDoingTaskList().isEmpty());
}
}
model/JoinModel.java合并节点类增加调用分支处理类方法fire(new MergeBranchHandler(this),execution)
package com.mldong.flow.engine.model;
import com.mldong.flow.engine.core.Execution;
import com.mldong.flow.engine.handlers.impl.MergeBranchHandler;
import lombok.Data;
/**
*
* 合并模型
* @author mldong
* @date 2023/4/25
*/
@Data
public class JoinModel extends NodeModel {
@Override
public void exec(Execution execution) {
// 执行合并节点自定义执行逻辑
fire(new MergeBranchHandler(this),execution);
if(execution.isMerged()) {
runOutTransition(execution);
}
}
}
调用测试类,最终执行结果如下:
调用模型节点执行方法:model:StartModel,name:start,displayName:开始
创建任务:apply,请假申请
设置任务状态为已完成:apply,请假申请
调用模型节点执行方法:model:TaskModel,name:apply,displayName:请假申请
创建任务:approveDept_a1,部门领导审批A1
创建任务:approveDept_a2,部门领导审批A2
设置任务状态为已完成:approveDept_a1,部门领导审批A1
调用模型节点执行方法:model:TaskModel,name:approveDept_a1,displayName:部门领导审批A1
创建任务:approveDept_b1,部门领导审批B1
设置任务状态为已完成:approveDept_a2,部门领导审批A2
调用模型节点执行方法:model:TaskModel,name:approveDept_a2,displayName:部门领导审批A2
创建任务:approveDept_b2,部门领导审批B2
设置任务状态为已完成:approveDept_b1,部门领导审批B1
调用模型节点执行方法:model:TaskModel,name:approveDept_b1,displayName:部门领导审批B1
设置任务状态为已完成:approveDept_b2,部门领导审批B2
调用模型节点执行方法:model:TaskModel,name:approveDept_b2,displayName:部门领导审批B2
创建任务:approveBoss,公司领导审批
设置任务状态为已完成:approveBoss,公司领导审批
调用模型节点执行方法:model:TaskModel,name:approveBoss,displayName:公司领导审批
调用模型节点执行方法:model:EndModel,name:end,displayName:结束
小结
本文通过调整合并节点的合并处理逻辑来实现分支与合并流程,关键点其实就是判断是否能合并。从执行结果中我们可以看出,当我们完成approveDept_b1节点任务时,其会触发一次合并处理,此时任务approveDept_b2未完成,所以不能合并。当执行完approveDept_b2时,会再一次触发合并处理,此时所有任务都已完成,所以允许合并,即允许流程往下一个节点行进。因为这里只是简单模拟,真实的场景approveDept_b1和approveDept_b2是不分先后的,但是这并不影响判断。最终只要是最后一个任务完成,才会真正的触发合并节合并动作,允许流程往下一个节点行进。
加入组织
请在微信中打开: 《立东和他的朋友们》
注:最近发现有些CSDN账号将作者的工作流系列文章搬运过去然后标为原创(猜测是用程序自动抓取的)。为了增加一些限制,作者决定把该系列文章更新至《立东和他的朋友们》这个圈子上,对此感兴趣的小伙伴可以扫描二维码加入,谢谢大家的支持!