在流程的简略履行章节中,咱们让一条普通的顺序流程从开端节点走向完毕节点。那假如是条件流程呢?咱们又应该怎么处理呢?

流程界说

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

如上图烘托的流程图,可由以下两种流程界说文件生成。

src/test/resources/leave_02.json

由决议计划节点的输出边特点来界说表达式,该表达式返回值为true/false

注:以下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并非悉数,缺少方位信息。

{
  "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并非悉数,缺少方位信息。

{
  "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,两者的履行逻辑都一样,便是解析的流程界说文件不一样。

  • 加载配置
  • 解析流程界说文件
  • 履行流程
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办法时,成果如下:

model:StartModel,name:start,displayName:开端
model:TaskModel,name:apply,displayName:请假请求
model:TaskModel,name:approveDept,displayName:部分领导批阅
model:EndModel,name:end,displayName:完毕

当履行executeLeave_02办法时,成果如下:

model:StartModel,name:start,displayName:开端
model:TaskModel,name:apply,displayName:请假请求
model:TaskModel,name:approveDept,displayName:部分领导批阅

咱们会看到,executeLeave_02的履行是不完好的,由于咱们并没有对决议计划节点进行处理。下面咱们要对决议计划节点进行处理,使其完好的从开端节点走向完毕节点。

决议计划节点剖析

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

  • 开端->请假请求->部分领导批阅->完毕
  • 开端->请假请求->部分领导批阅->公司领导批阅->完毕

检查流程界说文件leave_02.json,在节点输出边中,咱们会看到如下特点:

{
  "expr": "#f_day<3"
}
{
  "expr": "#f_day>=3"
}

检查流程界说文件leave_03.json,在节点特点中,咱们会看到如下特点:

{
  "expr": "#f_day>=3?'approveBoss':'end'"
}

检查流程界说文件leave_04.json,在节点特点中,咱们会看到如下特点:

{
  "handleClass": "com.mldong.flow.LeaveDecisionHandler"
}

那咱们在代码上应该怎么实现呢?其实思路很简略,分三种状况判断:

假如决议计划节点界说有表达式特点:

  • 从节点特点中获取表达式
  • 调用表达式引擎,得到下一个节点的节点称号
  • 遍历一切输出边,假如输出边方针节点称号和上面找到的下一个节点称号共同,则设置enabled=true
  • 调用输出边的execute办法

假如决议计划节点界说有决议计划类字段串特点:

  • 从节点特点中获取决议计划类
  • 实例类决议计划类
  • 调用决议计划类办法,得到下一个节点的节点称号
  • 遍历一切输出边,假如输出边方针节点称号和上面找到的下一个节点称号共同,则设置enabled=true
  • 调用输出边的execute办法

假如决议计划节点未界说有表达式特点:

  • 从节点的输出边中获取表达式
  • 调用表达式引擎,设置输出边的enabled特点
  • 调用输出边的execute办法

代码实现

model/DecisionModel.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
 */
@Data
public 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

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.json
  • f_day=1
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.json
  • f_day=3
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.json
  • f_day=1
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.json
  • f_day=3
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-flow-demo-04

流程设计器

在线体验