前言:
此时同学们对“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
链接: