龙空技术网

工作流引擎设计与实现·条件流程执行

程序猿凯撒 92

前言:

此时同学们对“java开发平台的工作流框架使用的工作流引擎”大体比较关注,兄弟们都需要剖析一些“java开发平台的工作流框架使用的工作流引擎”的相关文章。那么小编在网摘上搜集了一些关于“java开发平台的工作流框架使用的工作流引擎””的相关知识,希望看官们能喜欢,你们一起来学习一下吧!

在流程的简单执行章节中,我们让一条普通的顺序流程从开始节点走向结束节点。那如果是条件流程呢?我们又应该如何处理呢?

流程定义

如上图渲染的流程图,可由以下两种流程定义文件生成。

src/test/resources/leave_02.json

由决策节点的输出边属性来定义表达式,该表达式返回值为true/false

注:以下json并非全部,缺少位置信息。

json复制代码{  "name": "leave",  "displayName": "请假",  "instanceUrl": "leaveForm",  "nodes": [    {      "id": "start",      "type": "snaker:start",      "properties": {},      "text": {        "value": "开始"      }    },    {      "id": "apply",      "type": "snaker:task",      "properties": {},      "text": {        "value": "请假申请"      }    },    {      "id": "approveDept",      "type": "snaker:task",      "x": 740,      "y": 160,      "properties": {},      "text": {        "value": "部门领导审批"      }    },    {      "id": "approveBoss",      "type": "snaker:task",      "properties": {},      "text": {        "value": "公司领导审批"      }    },    {      "id": "2c75eebf-5baf-4cd0-a7b3-05466be13634",      "type": "snaker:decision",      "properties": {}    },    {      "id": "end",      "type": "snaker:end",      "properties": {},      "text": {        "value": "结束"      }    }  ],  "edges": [    {      "id": "3037be41-5682-4344-b94a-9faf5c3e62ba",      "type": "snaker:transition",      "sourceNodeId": "start",      "targetNodeId": "apply",      "properties": {}    },    {      "id": "c79642ae-9f28-4213-8cdf-0e0d6467b1b9",      "type": "snaker:transition",      "sourceNodeId": "apply",      "targetNodeId": "approveDept",      "properties": {}    },    {      "id": "09d9b143-9473-4a0f-8287-9abf6f65baf5",      "type": "snaker:transition",      "sourceNodeId": "approveDept",      "targetNodeId": "2c75eebf-5baf-4cd0-a7b3-05466be13634",      "properties": {}    },    {      "id": "a64348ec-4168-4f36-8a61-15cf12c710b9",      "type": "snaker:transition",      "sourceNodeId": "approveBoss",      "targetNodeId": "end"      "properties": {}    },    {      "id": "517ef2c7-3486-4992-b554-0f538ab91751",      "type": "snaker:transition",      "sourceNodeId": "2c75eebf-5baf-4cd0-a7b3-05466be13634",      "targetNodeId": "end",      "properties": {        "expr": "#f_day<3"      },      "text": {        "value": "请假天数小于3"      }    },    {      "id": "d7ec4166-f3fc-4fd6-a2ac-a6c4d509c4dd",      "type": "snaker:transition",      "sourceNodeId": "2c75eebf-5baf-4cd0-a7b3-05466be13634",      "targetNodeId": "approveBoss",      "properties": {        "expr": "#f_day>=3"      },      "text": {        "value": "请假天数大于等于3"      }    }  ]}

src/test/resources/leave_03.json

由决策节点的expr属性来定义表达式,该表达式返回值为目标节点名称。

注:以下json并非全部,缺少位置信息。

json复制代码{  "name": "leave",  "displayName": "请假",  "instanceUrl": "leaveForm",  "nodes": [    {      "id": "start",      "type": "snaker:start",      "properties": {},      "text": {        "value": "开始"      }    },    {      "id": "apply",      "type": "snaker:task",      "properties": {},      "text": {        "value": "请假申请"      }    },    {      "id": "approveDept",      "type": "snaker:task",      "properties": {},      "text": {        "value": "部门领导审批"      }    },    {      "id": "approveBoss",      "type": "snaker:task",      "properties": {},      "text": {        "value": "公司领导审批"      }    },    {      "id": "2c75eebf-5baf-4cd0-a7b3-05466be13634",      "type": "snaker:decision",      "properties": {        "expr": "#f_day>=3?'approveBoss':'end'"      }    },    {      "id": "end",      "type": "snaker:end",      "properties": {},      "text": {        "value": "结束"      }    }  ],  "edges": [    {      "id": "3037be41-5682-4344-b94a-9faf5c3e62ba",      "type": "snaker:transition",      "sourceNodeId": "start",      "targetNodeId": "apply",      "properties": {}    },    {      "id": "c79642ae-9f28-4213-8cdf-0e0d6467b1b9",      "type": "snaker:transition",      "sourceNodeId": "apply",      "targetNodeId": "approveDept",      "properties": {},    },    {      "id": "09d9b143-9473-4a0f-8287-9abf6f65baf5",      "type": "snaker:transition",      "sourceNodeId": "approveDept",      "targetNodeId": "2c75eebf-5baf-4cd0-a7b3-05466be13634",      "properties": {}    },    {      "id": "a64348ec-4168-4f36-8a61-15cf12c710b9",      "type": "snaker:transition",      "sourceNodeId": "approveBoss",      "targetNodeId": "end",      "properties": {}    },    {      "id": "517ef2c7-3486-4992-b554-0f538ab91751",      "type": "snaker:transition",      "sourceNodeId": "2c75eebf-5baf-4cd0-a7b3-05466be13634",      "targetNodeId": "end",      "properties": {},      "text": {        "value": "请假天数小于3"      }    },    {      "id": "d7ec4166-f3fc-4fd6-a2ac-a6c4d509c4dd",      "type": "snaker:transition",      "sourceNodeId": "2c75eebf-5baf-4cd0-a7b3-05466be13634",      "targetNodeId": "approveBoss",      "text": {        "value": "请假天数大于等于3"      }    }  ]}

src/test/resources/leave_04.json

由决策节点定义的handleClasss属性,实例化决策类,决定下一个节点名称。

注:以下json并非全部,缺少位置信息。

json复制代码{  "name": "leave",  "displayName": "请假",  "instanceUrl": "leaveForm",  "nodes": [    {      "id": "start",      "type": "snaker:start",      "text": {        "value": "开始"      }    },    {      "id": "apply",      "type": "snaker:task",      "properties": {},      "text": {        "value": "请假申请"      }    },    {      "id": "approveDept",      "type": "snaker:task",      "x": 740,      "y": 160,      "properties": {},      "text": {        "value": "部门领导审批"      }    },    {      "id": "approveBoss",      "type": "snaker:task",      "properties": {},      "text": {        "value": "公司领导审批"      }    },    {      "id": "2c75eebf-5baf-4cd0-a7b3-05466be13634",      "type": "snaker:decision",      "properties": {        "handleClass": "com.mldong.flow.LeaveDecisionHandler"      }    },    {      "id": "end",      "type": "snaker:end",      "text": {        "value": "结束"      }    }  ],  "edges": [    {      "id": "3037be41-5682-4344-b94a-9faf5c3e62ba",      "type": "snaker:transition",      "sourceNodeId": "start",      "targetNodeId": "apply"    },    {      "id": "c79642ae-9f28-4213-8cdf-0e0d6467b1b9",      "type": "snaker:transition",      "sourceNodeId": "apply",      "targetNodeId": "approveDept"    },    {      "id": "09d9b143-9473-4a0f-8287-9abf6f65baf5",      "type": "snaker:transition",      "sourceNodeId": "approveDept",      "targetNodeId": "2c75eebf-5baf-4cd0-a7b3-05466be13634"    },    {      "id": "a64348ec-4168-4f36-8a61-15cf12c710b9",      "type": "snaker:transition",      "sourceNodeId": "approveBoss",      "targetNodeId": "end"    },    {      "id": "517ef2c7-3486-4992-b554-0f538ab91751",      "type": "snaker:transition",      "sourceNodeId": "2c75eebf-5baf-4cd0-a7b3-05466be13634",      "targetNodeId": "end",      "text": {        "value": "请假天数小于3"      },    },    {      "id": "d7ec4166-f3fc-4fd6-a2ac-a6c4d509c4dd",      "type": "snaker:transition",      "sourceNodeId": "2c75eebf-5baf-4cd0-a7b3-05466be13634",      "targetNodeId": "approveBoss",      "text": {        "value": "请假天数大于等于3"      }    }  ]}
旧的代码逻辑

新增src/test/java/com/mldong/flow/ExecuteTest.java

共两个方法executeLeave_01和executeLeave_02,两者的执行逻辑都一样,就是解析的流程定义文件不一样。

加载配置解析流程定义文件执行流程

java复制代码package com.mldong.flow;import cn.hutool.core.io.IoUtil;import cn.hutool.core.lang.Dict;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_01() {        new Configuration();        ProcessModel processModel = ModelParser.parse(IoUtil.readBytes(this.getClass().getResourceAsStream("/leave.json")));        Execution execution = new Execution();        execution.setArgs(Dict.create());        processModel.getStart().execute(execution);    }    @Test    public void executeLeave_02() {        new Configuration();        ProcessModel processModel = ModelParser.parse(IoUtil.readBytes(this.getClass().getResourceAsStream("/leave_02.json")));        Execution execution = new Execution();        execution.setArgs(Dict.create());        processModel.getStart().execute(execution);    }}

当执行executeLeave_01方法时,结果如下:

text复制代码model:StartModel,name:start,displayName:开始model:TaskModel,name:apply,displayName:请假申请model:TaskModel,name:approveDept,displayName:部门领导审批model:EndModel,name:end,displayName:结束

当执行executeLeave_02方法时,结果如下:

text复制代码model:StartModel,name:start,displayName:开始model:TaskModel,name:apply,displayName:请假申请model:TaskModel,name:approveDept,displayName:部门领导审批

我们会看到,executeLeave_02的执行是不完整的,因为我们并没有对决策节点进行处理。下面我们要对决策节点进行处理,使其完整的从开始节点走向结束节点。

决策节点分析

从图中看,我们可以得到如下两条路径:

开始->请假申请->部门领导审批->结束开始->请假申请->部门领导审批->公司领导审批->结束

查看流程定义文件leave_02.json,在节点输出边中,我们会看到如下属性:

json复制代码{  "expr": "#f_day<3"}
json复制代码{  "expr": "#f_day>=3"}

查看流程定义文件leave_03.json,在节点属性中,我们会看到如下属性:

json复制代码{  "expr": "#f_day>=3?'approveBoss':'end'"}

查看流程定义文件leave_04.json,在节点属性中,我们会看到如下属性:

json复制代码{  "handleClass": "com.mldong.flow.LeaveDecisionHandler"}

那我们在代码上应该如何实现呢?其实思路很简单,分三种情况判断:

如果决策节点定义有表达式属性:

从节点属性中获取表达式调用表达式引擎,得到下一个节点的节点名称遍历所有输出边,如果输出边目标节点名称和上面找到的下一个节点名称一致,则设置enabled=true调用输出边的execute方法

如果决策节点定义有决策类字段串属性:

从节点属性中获取决策类实例类决策类调用决策类方法,得到下一个节点的节点名称遍历所有输出边,如果输出边目标节点名称和上面找到的下一个节点名称一致,则设置enabled=true调用输出边的execute方法

如果决策节点未定义有表达式属性:

从节点的输出边中获取表达式调用表达式引擎,设置输出边的enabled属性调用输出边的execute方法代码实现

model/DecisionModel.java

java复制代码package com.mldong.flow.engine.model;import cn.hutool.core.convert.Convert;import cn.hutool.core.util.ReflectUtil;import cn.hutool.core.util.StrUtil;import cn.hutool.extra.expression.ExpressionUtil;import com.mldong.flow.engine.core.Execution;import com.mldong.flow.engine.enums.ErrEnum;import com.mldong.flow.engine.ex.JeeFlowException;import engine.DecisionHandler;import lombok.Data;/** * * 决策模型 * @author mldong * @date 2023/4/25 */@Datapublic class DecisionModel extends NodeModel {    private String expr; // 决策表达式    private String handleClass; // 决策处理类    @Override    public void exec(Execution execution) {        // 执行决策节点自定义执行逻辑        boolean isFound = false;        String nextNodeName = null;        if(StrUtil.isNotEmpty(expr)) {            Object obj = ExpressionUtil.eval(expr, execution.getArgs());            nextNodeName = Convert.toStr(obj,"");        } else if(StrUtil.isNotEmpty(handleClass)) {            DecisionHandler decisionHandler = ReflectUtil.newInstance(handleClass);            nextNodeName = decisionHandler.decide(execution);        }        for(TransitionModel transitionModel: getOutputs()){            if (StrUtil.isNotEmpty(transitionModel.getExpr()) && Convert.toBool(ExpressionUtil.eval(transitionModel.getExpr(), execution.getArgs()), false)) {                // 决策节点输出边存在表达式,则使用输出边的表达式,true则执行                isFound = true;                transitionModel.setEnabled(true);                transitionModel.execute(execution);            } else if(transitionModel.getTo().equalsIgnoreCase(nextNodeName)) {                // 找到对应的下一个节点                isFound = true;                transitionModel.setEnabled(true);                transitionModel.execute(execution);            }        }        if(!isFound) {            // 找不到下一个可执行路线            throw new JeeFlowException(ErrEnum.NOT_FOUND_NEXT_NODE);        }    }}

单元测试类改造

src/test/java/com/mldong/flow/ExecuteTest.java

java复制代码package com.mldong.flow;import cn.hutool.core.io.IoUtil;import cn.hutool.core.lang.Dict;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_01() {        new Configuration();        ProcessModel processModel = ModelParser.parse(IoUtil.readBytes(this.getClass().getResourceAsStream("/leave.json")));        Execution execution = new Execution();        execution.setArgs(Dict.create());        processModel.getStart().execute(execution);    }    @Test    public void executeLeave_02() {        new Configuration();        ProcessModel processModel = ModelParser.parse(IoUtil.readBytes(this.getClass().getResourceAsStream("/leave_02.json")));        Execution execution = new Execution();        execution.setArgs(Dict.create());        processModel.getStart().execute(execution);    }    @Test    public void executeLeave_02_1() {        new Configuration();        ProcessModel processModel = ModelParser.parse(IoUtil.readBytes(this.getClass().getResourceAsStream("/leave_02.json")));        Execution execution = new Execution();        execution.setArgs(Dict.create());        execution.getArgs().put("f_day",1);        processModel.getStart().execute(execution);    }    @Test    public void executeLeave_02_2() {        new Configuration();        ProcessModel processModel = ModelParser.parse(IoUtil.readBytes(this.getClass().getResourceAsStream("/leave_02.json")));        Execution execution = new Execution();        execution.setArgs(Dict.create());        execution.getArgs().put("f_day",3);        processModel.getStart().execute(execution);    }    @Test    public void executeLeave_03_1() {        new Configuration();        ProcessModel processModel = ModelParser.parse(IoUtil.readBytes(this.getClass().getResourceAsStream("/leave_03.json")));        Execution execution = new Execution();        execution.setArgs(Dict.create());        execution.getArgs().put("f_day",1);        processModel.getStart().execute(execution);    }    @Test    public void executeLeave_03_2() {        new Configuration();        ProcessModel processModel = ModelParser.parse(IoUtil.readBytes(this.getClass().getResourceAsStream("/leave_03.json")));        Execution execution = new Execution();        execution.setArgs(Dict.create());        execution.getArgs().put("f_day",3);        processModel.getStart().execute(execution);    }    @Test    public void executeLeave_04() {        new Configuration();        ProcessModel processModel = ModelParser.parse(IoUtil.readBytes(this.getClass().getResourceAsStream("/leave_04.json")));        Execution execution = new Execution();        execution.setArgs(Dict.create());        processModel.getStart().execute(execution);    }}
测试验证

当执行executeLeave_02_1方法时,结果如下:

流程定义文件:leave_02.jsonf_day=1

text复制代码model:StartModel,name:start,displayName:开始model:TaskModel,name:apply,displayName:请假申请model:TaskModel,name:approveDept,displayName:部门领导审批model:EndModel,name:end,displayName:结束

当执行executeLeave_02_2方法时,结果如下:

流程定义文件:leave_02.jsonf_day=3

text复制代码model:StartModel,name:start,displayName:开始model:TaskModel,name:apply,displayName:请假申请model:TaskModel,name:approveDept,displayName:部门领导审批model:TaskModel,name:approveBoss,displayName:公司领导审批model:EndModel,name:end,displayName:结束

当执行executeLeave_03_1方法时,结果如下:

流程定义文件:leave_03.jsonf_day=1

text复制代码model:StartModel,name:start,displayName:开始model:TaskModel,name:apply,displayName:请假申请model:TaskModel,name:approveDept,displayName:部门领导审批model:EndModel,name:end,displayName:结束

当执行executeLeave_03_2方法时,结果如下:

流程定义文件:leave_03.jsonf_day=3

text复制代码model:StartModel,name:start,displayName:开始model:TaskModel,name:apply,displayName:请假申请model:TaskModel,name:approveDept,displayName:部门领导审批model:TaskModel,name:approveBoss,displayName:公司领导审批model:EndModel,name:end,displayName:结束

作者:mldong

链接:

标签: #java开发平台的工作流框架使用的工作流引擎