一个系列搞懂自研工作流(四)-重要节点如何实现

2,152 阅读4分钟

写在最前

之前三篇文章已经基本上将工作流引擎的原理阐述清楚了,接下来我们深入实现细节,来看一下各个节点如何实现。

节点总览

以节点类型来分,节点可分为开始节点、结束节点、任务节点、Decision节点、Fork节点、Join节点。

  • 开始节点和结束节点作为流程的开始和结束关卡出现,主要是控制流程的开始和结束

  • 任务节点可理解为需要人为介入的节点,通过用户的审批意见进行下一节点的选择。

  • Decision节点可理解为决策节点,目标出线可以有多个,只有一条符合要求的连线在节点决策时被选择。

  • Fork和join节点通常一起使用,fork节点分出可同时进行的流程,在fork节点中汇聚。

Start、End节点实现

作为流程的开始和结束,这两个节点的职责相对简单。Start节点只需要开始流程并且执行连线节点的动作即可,而End节点只需要结束整个流程即可。

开始节点


@Override
protected void exec(Execution execution) {
    // 执行连线动作
    runOutTransition(execution);
}

runOutTransition实现可以看上一篇文章的介绍,在此不再赘述。runOutTransition是节点如何到连线节点的过程,需要理解这一过程才能更好设计节点流转。 流程引擎介绍runOutTransition

结束节点


@Override
protected void exec(Execution execution) {
   
    doExecution(new EndProcessHandler(), execution);
}

handler中做的动作就是将流程的状态改为结束状态,可根据业务需要进行其他结束操作的动作。

Task节点

任务节点,即需要人工介入的节点,需要指定人进行审批通过或驳回的决议来决定节点流转,所以会有停留和完成状态。创建任务等待审核的过程其实就可以理解为流程因任务挂起了,点击审核通过或者驳回就是触发任务节点。所以从代码的实现上,可根据是否有taskId来判断是任务创建还是完成任务,进而进行对应的动作。


@Override
protected void exec(Execution execution) {
    Task task = execution.getTask();

    if (canCreateNewTask(task)) {
        doExecution(new TaskCreateHandler(), execution);
    }

    // 检测任务是否完成
    doExecution(new TaskCheckCompleteHandler(), execution);
    if (execution.getTask().isComplete()) {
        // 后继执行下一个节点
        runOutTransition(execution);
    }
}

TaskCreateHandler就是在数据库中创建任务

TaskCheckCompleteHandler 该Handler就是检测任务是否完成(或签、会签完成的不同逻辑),如果完成进行连线节点动作runOutTransition

Decision节点

决策节点,只需要定义好决策策略,根据运行参数和表达式来判断进行哪一个连线的动作。如图,decision的语义就是要么Task1能被执行,要么Task2能被执行,只有一条连线能被执行,节点执行取决于Decision节点的表达式和运行参数共同作用的结果。

代码实现


@Override
protected void exec(Execution execution) {
    if (ObjectUtil.isEmpty(expr)) {
        throw new SmartFlowException("表达式解析器为空,请检查配置.");
    }

    boolean isFound = false;

    String next = decisionHandler.decide(expr, execution);
    if (ObjectUtil.isNotEmpty(next)) {
        for (LineModel line : getOutputs()) {
            // 找到后继节点,并继续往后执行
            if (line.getId().equalsIgnoreCase(next)) {
                line.setEnabled(true);
                line.execute(execution);
                isFound = true;
            }
        }
    }
}

expr为定义的表达式,如#day>2?'line3':'line4'。这里的表达式可自行选择,决定于你的decisionHandler用的是什么判断策略,此处使用的是EL表达式。

Fork、Join节点

  • Fork节点逻辑相对简单,直接按顺序执行线节点逻辑即可。
  • Join节点动作原理上就是 记录触发节点的次数与来源连线的size做equal判断,来判断join节点是否可以进入下一个节点。

如图,fork-join的语义就是Task1和Task2都会被执行,在join节点只有两个任务都完成了汇聚进join节点才能进入下一个节点。

代码实现

@Override
protected void exec(Execution execution) {
    // 分流节点,直接找到连线,继续往下执行
    runOutTransition(execution);
}

fork节点就是执行runOutTransition

join节点就是完成节点动作(创建一条记录),然后判断数据库中count数和入线的size是否相等,相等就说明join完成进入下一节点,否则结束等待下一次被触发。

写在最后

至此所有节点实现都已经完成,接下来需要做的就是一些扩展点的实现。在具体实现之前感觉工作流引擎十分复杂,再具体实际开发后,通过整体设计将复杂的流程分解为各个简单小步骤,最终化繁为简,发现原来每个节点的实现也并没有这么困难。