likes
comments
collection
share

工作流引擎设计与实现·流程的简单执行

作者站长头像
站长
· 阅读数 13

前言

上文中已经对模型进行了高度的抽象,本文将对模型行为进行进一步说明。这里以提问的方式开场:

如何让流程从开始节点按箭头指向走到结束节点?

StartModel(start)->TaskModel(apply)->TaskModel(deptApprove)->EndModel(end)

工作流引擎设计与实现·流程的简单执行

执行过程分析

对象图:

工作流引擎设计与实现·流程的简单执行

时序图:

工作流引擎设计与实现·流程的简单执行

执行过程说明:

  1. 开始节点调用输出边t1的执行方法
  2. t1输出边调用请假申请节点的执行方法
  3. 请假节点调用输出边t2的执行方法
  4. t2输出边调用部门领导审批的执行方法
  5. 部门领导审批调用输出边t3的执行方法
  6. t3调用结束节点的执行方法

代码实现步骤

节点模型的execute方法增加打印当前对象的编码和名称。

public abstract class NodeModel extends BaseModel implements Action {
    private String layout;// 布局属性(x,y,w,h)
    // 输入边集合
    private List<TransitionModel> inputs = new ArrayList<TransitionModel>();
    // 输出边集合
    private List<TransitionModel> outputs = new ArrayList<TransitionModel>();
    private String preInterceptors; // 节点前置拦截器
    private String postInterceptors; // 节点后置拦截器
    
    /**
    * 由子类自定义执行方法
    * @param execution
    */
    abstract void exec(Execution execution);
    @Override
    public void execute(Execution execution) {
        // 1. 调用前置拦截器
        // 2. 调用子类的exec方法
        // 3. 调用后置拦截器
        System.out.println(StrUtil.format("model:{},name:{},displayName:{}", this.getClass().getSimpleName(), getName(),getDisplayName()));
        exec(execution);
    }
}

流程模型增加获取开始节点方法

public class ProcessModel extends BaseModel {
    private String type; // 流程定义分类
    private String instanceUrl; // 启动实例要填写的表单key
    private String expireTime; // 期待完成时间变量key
    private String instanceNoClass; // 实例编号生成器实现类
    // 流程定义的所有节点
    private List<NodeModel> nodes = new ArrayList<NodeModel>();
    // 流程定义的所有任务节点
    private List<TaskModel> tasks = new ArrayList<TaskModel>();

    /**
     * 获取开始节点
     * @return
     */
    public StartModel getStart() {
        StartModel startModel = null;
        for (int i = 0; i < nodes.size(); i++) {
            NodeModel nodeModel = nodes.get(i);
            if(nodeModel instanceof StartModel) {
                startModel = (StartModel) nodeModel;
                break;
            }
        }
        return startModel;
    }
}

流程模型拿到开始节点对象并调用执行方法

processModel.getStart().execute(new Execution());

此时只打印开始节点信息

model:StartModel,name:start,displayName:开始

节点模型对象的execute方法增加遍历调用下一个节点执行方法的

public abstract class NodeModel extends BaseModel implements Action {
    private String layout;// 布局属性(x,y,w,h)
    // 输入边集合
    private List<TransitionModel> inputs = new ArrayList<TransitionModel>();
    // 输出边集合
    private List<TransitionModel> outputs = new ArrayList<TransitionModel>();
    private String preInterceptors; // 节点前置拦截器
    private String postInterceptors; // 节点后置拦截器
    
    /**
    * 由子类自定义执行方法
    * @param execution
    */
    abstract void exec(Execution execution);
    @Override
    public void execute(Execution execution) {
        // 1. 调用前置拦截器
        // 2. 调用子类的exec方法
        // 3. 调用后置拦截器
        System.out.println(StrUtil.format("model:{},name:{},displayName:{}", this.getClass().getSimpleName(), getName(),getDisplayName()));
        outputs.forEach(tr->{
            tr.getTarget().execute(execution);
        });
        exec(execution);
    }
}

结果:

model:StartModel,name:start,displayName:开始
model:TaskModel,name:apply,displayName:请假申请
model:TaskModel,name:deptApprove,displayName:部门领导审批
model:EndModel,name:end,displayName:结束

为了突显边的作用,我们可以实现边的执行方法:

public class TransitionModel extends BaseModel implements Action {
    private NodeModel source; // 边源节点引用
    private NodeModel target; // 边目标节点引用
    private String to; // 目标节点名称
    private String expr; // 边表达式
    private String g; // 边点坐标集合(x1,y1;x2,y2,x3,y3……)开始、拐角、结束
    private boolean enabled; // 是否可执行
    @Override
    public void execute(Execution execution) {
        if(!enabled) return;
        target.execute(execution);
    }
}

然后改造节点模型增加runOutTransition方法

public abstract class NodeModel extends BaseModel implements Action {
    private String layout;// 布局属性(x,y,w,h)
    // 输入边集合
    private List<TransitionModel> inputs = new ArrayList<TransitionModel>();
    // 输出边集合
    private List<TransitionModel> outputs = new ArrayList<TransitionModel>();
    private String preInterceptors; // 节点前置拦截器
    private String postInterceptors; // 节点后置拦截器
    
    /**
    * 由子类自定义执行方法
    * @param execution
    */
    abstract void exec(Execution execution);
    @Override
    public void execute(Execution execution) {
        // 1. 调用前置拦截器
        // 2. 调用子类的exec方法
        // 3. 调用后置拦截器
        System.out.println(StrUtil.format("model:{},name:{},displayName:{}", this.getClass().getSimpleName(), getName(),getDisplayName()));
        // 执行输出边
        runOutTransition(execution);
        exec(execution);
    }
    /**
     * 执行输出边
     */
    protected void runOutTransition(Execution execution) {
        outputs.forEach(tr->{
            tr.setEnabled(true);
            tr.execute(execution);
        });
    }

}

最终效果为:

model:StartModel,name:start,displayName:开始
model:TaskModel,name:apply,displayName:请假申请
model:TaskModel,name:deptApprove,displayName:部门领导审批
model:EndModel,name:end,displayName:结束

如何让流程产生阻塞?

上面的例子执行过程太顺利了,真实的工作流场景会存在一些阻塞任务,产生阻塞的意思是,即调用节点执行方法,如果条件不满足,依然不能驱动流程往下一个节点进行。那我们如何使用程序去模拟这一过程呢?

首先改造节点模型

并不是每个节点的执行方式都一样,我们需要对不同节点进行不同的输出处理,所以这里

  • 暂时去掉原来节点模型的打印语句和调用执行边的方法
  • 重写toString()方法
public abstract class NodeModel extends BaseModel implements Action {
    private String layout;// 布局属性(x,y,w,h)
    // 输入边集合
    private List<TransitionModel> inputs = new ArrayList<TransitionModel>();
    // 输出边集合
    private List<TransitionModel> outputs = new ArrayList<TransitionModel>();
    private String preInterceptors; // 节点前置拦截器
    private String postInterceptors; // 节点后置拦截器
    
    /**
    * 由子类自定义执行方法
    * @param execution
    */
    abstract void exec(Execution execution);
    @Override
    public void execute(Execution execution) {
        // 1. 调用前置拦截器
        // 2. 调用子类的exec方法
        // 3. 调用后置拦截器
        exec(execution);
    }
    /**
     * 执行输出边
     */
    protected void runOutTransition(Execution execution) {
        outputs.forEach(tr->{
            tr.setEnabled(true);
            tr.execute(execution);
        });
    }
    @Override
    public String toString() {
        return StrUtil.format("model:{},name:{},displayName:{},time:{}", this.getClass().getSimpleName(), getName(),getDisplayName(), DateUtil.dateSecond());
    }
}

实现开始节点的exec方法

开始节点的exec主要执行如下逻辑:

  • 输出super.toString()
  • 调用runOutTransition
public class StartModel extends NodeModel {
    @Override
    void exec(Execution execution) {
        System.out.println(super.toString());
        runOutTransition(execution);
    }
}

实现结束节点的exec方法

结束节点是没有输出边的,所以只输出super.toString()

public class EndModel extends NodeModel {
    @Override
    public void exec(Execution execution) {
        System.out.println(super.toString());
    }
}

实现任务节点的exec方法

任务节点的比较特殊,我们可以做如下处理让其产生临时的阻塞:

public class TaskModel extends NodeModel {
    private String form; // 表单标识
    private String assignee; // 参与人
    private String assignmentHandler; // 参与人处理类
    private TaskTypeEnum taskType; // 任务类型(主办/协办)
    private TaskPerformTypeEnum performType; // 参与类型(普通参与/会签参与)
    private String reminderTime; // 提醒时间
    private String reminderRepeat; // 重复提醒间隔
    private String expireTime; // 期待任务完成时间变量key
    private String autoExecute; // 到期是否自动执行Y/N
    private String callback; // 自动执行回调类
    private Dict ext = Dict.create(); // 自定义扩展属性
    @Override
    public void exec(Execution execution) {
        // 执行任务节点自定义执行逻辑
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(super.toString());
        runOutTransition(execution);
    }
}

此时打印结果如下:

model:StartModel,name:start,displayName:开始,time:2023-04-25 22:27:45
model:TaskModel,name:apply,displayName:请假申请,time:2023-04-25 22:27:48
model:TaskModel,name:deptApprove,displayName:部门领导审批,time:2023-04-25 22:27:51
model:EndModel,name:end,displayName:结束,time:2023-04-25 22:27:51

加入组织

知识星球

相关源码

mldong-flow-demo-02

流程设计器

在线体验