自己写一个简单的工作流引擎V1

1.需求

市面上常见的工作流组件一般都是前端通过拖拉拽配置流程图,后端流程引擎解析流程配置,这里我们手写一个简单的流程引擎,先实现串行流程,例如下:
在这里插入图片描述
小明提交了一个申请单,然后经过经理审批,审批结束后,不管通过还是不通过,都会经过第三步把结果发送给小明

2.难点分析

  • 每个节点审批时间是不确定的,工作流引擎主动式调取下一个节点的逻辑并不适合当前场景
  • 节点类型不是固定的后续会增多,工作流引擎与节点类型判断的逻辑不能写死
  • 审批时需要传递一些基本信息,如审批人、审批时间等,这些信息如何传递

3.设计

  • 采用注册机制,把节点类型及其自有逻辑注册进工作流引擎,以便能够扩展更多节点,使得工作流引擎与节点解耦
  • 工作流引擎增加被动式驱动逻辑,使得能够通过外部来使工作流引擎执行下一个节点
  • 增加上下文语义,作为全局变量来使用,使得数据能够流经各个节点

4.实现

流程的表示

流程配置好后一般会生成xml或者json格式的文件,这里我们使用xml表示流程

<definitions>
    <process id="process_2" name="简单审批例子">
        <startEvent id="startEvent_1">
            <outgoing>flow_1</outgoing>
        </startEvent>
        <sequenceFlow id="flow_1" sourceRef="startEvent_1" targetRef="approvalApply_1" />
        <approvalApply id="approvalApply_1" name="提交申请单">
            <incoming>flow_1</incoming>
            <outgoing>flow_2</outgoing>
        </approvalApply>
        <sequenceFlow id="flow_2" sourceRef="approvalApply_1" targetRef="approval_1" />
        <approval id="approval_1" name="审批">
            <incoming>flow_2</incoming>
            <outgoing>flow_3</outgoing>
        </approval>
        <sequenceFlow id="flow_3" sourceRef="approval_1" targetRef="notify_1"/>
        <notify id="notify_1" name="结果邮件通知">
            <incoming>flow_3</incoming>
            <outgoing>flow_4</outgoing>
        </notify>
        <sequenceFlow id="flow_4" sourceRef="notify_1" targetRef="endEvent_1"/>
        <endEvent id="endEvent_1">
            <incoming>flow_4</incoming>
        </endEvent>
    </process>
</definitions>

  1. process表示一个流程
  2. startEvent表示开始节点,endEvent表示结束节点
  3. approvalApply、approval、notify分别表示提交申请单、审批、邮件通知节点
  4. sequenceFlow表示连线,从sourceRef开始,指向targetRef
节点的表示

outgoing表示出边,即节点执行完毕后,应该从那个边出去。
incoming表示入边,即从哪个边进入到本节点。
一个节点只有outgoing而没有incoming,如:startEvent;也可以 只有入边而没有出边,如:endEvent;也可以既有入边也有出边,如:approvalApply、approval、notify。

流程引擎的逻辑

基于上述XML,流程引擎的运行逻辑如下

  1. 找到process
  2. 找到开始节点(startEvent)
  3. 找到startEvent的outgoing边(sequenceFlow)
  4. 找到该边(sequenceFlow)指向的节点(targetRef=approvalApply_1)
  5. 执行approvalApply_1节点自身的逻辑
  6. 找到该节点的outgoing边(sequenceFlow)重复3-5,直到遇到结束节点(endEvent),流程结束
上代码

定义流程

public class PeProcess {
    private String id;
    public PeNode start;

    public PeProcess(String id, PeNode start) {
        this.id = id;
        this.start = start;
    }

    public PeNode peNodeWithID(String peNodeID) {
        PeNode node = this.start.out.to;
        this.start = this.start.out.to;
        return node;
    }
}

定义节点

public class PeNode {
    public String id;

    public String type;
    public PeEdge in;
    public PeEdge out;

    public PeNode(String id) {
        this.id = id;
    }

}

定义边

public class PeEdge {
    private String id;
    public PeNode from;
    public PeNode to;

    public PeEdge(String id) {
        this.id = id;
    }
}

接下来,构建流程图,代码如下:

import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import java.util.HashMap;
import java.util.Map;

public class XmlPeProcessBuilder {
    private String xmlStr;
    private final Map<String, PeNode> id2PeNode = new HashMap<>();
    private final Map<String, PeEdge> id2PeEdge = new HashMap<>();

    public XmlPeProcessBuilder(String xmlStr) {
        this.xmlStr = xmlStr;
    }

    public PeProcess build() throws Exception {
        //strToNode : 把一段xml转换为org.w3c.dom.Node
        Node definations = XmlUtil.strToNode(xmlStr);
        //childByName : 找到definations子节点中nodeName为process的那个Node
        Node process = XmlUtil.childByName(definations, "process");
        NodeList childNodes = process.getChildNodes();

        for (int j = 0; j < childNodes.getLength(); j++) {
            Node node = childNodes.item(j);
            //#text node should be skip
            if (node.getNodeType() == Node.TEXT_NODE) continue;

            if ("sequenceFlow".equals(node.getNodeName()))
                buildPeEdge(node);
            else
                buildPeNode(node);
        }
        Map.Entry<String, PeNode> startEventEntry = id2PeNode.entrySet().stream().filter(entry -> "startEvent".equals(entry.getValue().type)).findFirst().get();
        return new PeProcess(startEventEntry.getKey(), startEventEntry.getValue());
    }

    private void buildPeEdge(Node node) {
        //attributeValue : 找到node节点上属性为id的值
        PeEdge peEdge = id2PeEdge.computeIfAbsent(XmlUtil.attributeValue(node, "id"), id -> new PeEdge(id));
        pe
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
假设我们要求v1到其他各个点的最短路径,图如下: ![Dijkstra算法示例图](https://cdn.luogu.com.cn/upload/image_hosting/e2u9r9hc.png) 我们按照Dijkstra算法的步骤来求解: 1. 初始化:将v1的最短路径长度dist[v1]设为0,其余顶点的最短路径长度dist[v]设为正无穷。 | 顶点 | dist值 | | --- | --- | | v1 | 0 | | v2 | ∞ | | v3 | ∞ | | v4 | ∞ | | v5 | ∞ | 2. 确定已知最短路径的顶点:从所有未确定最短路径的顶点中选取dist值最小的顶点v1,将其标记为已确定最短路径。 3. 更新邻接顶点的最短路径长度: - 对于v2,dist[v1]+w(v1,v2)=10<∞,更新dist[v2]=10; - 对于v3,dist[v1]+w(v1,v3)=3<∞,更新dist[v3]=3; - 对于v4,v1和v4之间没有边,不需要更新; - 对于v5,v1和v5之间没有边,不需要更新。 | 顶点 | dist值 | | --- | --- | | v1 | 0 | | v2 | 10 | | v3 | 3 | | v4 | ∞ | | v5 | ∞ | 4. 重复步骤2和3,选择dist值最小的未确定最短路径的顶点v3,将其标记为已确定最短路径。 5. 更新邻接顶点的最短路径长度: - 对于v2,v3和v2之间没有边,不需要更新; - 对于v4,v3和v4之间没有边,不需要更新; - 对于v5,v3和v5之间没有边,不需要更新。 | 顶点 | dist值 | | --- | --- | | v1 | 0 | | v2 | 10 | | v3 | 3 | | v4 | ∞ | | v5 | ∞ | 6. 重复步骤2和3,选择dist值最小的未确定最短路径的顶点v2,将其标记为已确定最短路径。 7. 更新邻接顶点的最短路径长度: - 对于v4,dist[v2]+w(v2,v4)=20<∞,更新dist[v4]=20; - 对于v5,v2和v5之间没有边,不需要更新。 | 顶点 | dist值 | | --- | --- | | v1 | 0 | | v2 | 10 | | v3 | 3 | | v4 | 20 | | v5 | ∞ | 8. 重复步骤2和3,选择dist值最小的未确定最短路径的顶点v5,将其标记为已确定最短路径。 9. 更新邻接顶点的最短路径长度: - 对于v4,v5和v4之间没有边,不需要更新。 | 顶点 | dist值 | | --- | --- | | v1 | 0 | | v2 | 10 | | v3 | 3 | | v4 | 20 | | v5 | ∞ | 10. 重复步骤2和3,选择dist值最小的未确定最短路径的顶点v4,将其标记为已确定最短路径。 11. 更新邻接顶点的最短路径长度: - 对于v5,v4和v5之间没有边,不需要更新。 | 顶点 | dist值 | | --- | --- | | v1 | 0 | | v2 | 10 | | v3 | 3 | | v4 | 20 | | v5 | ∞ | 12. 重复步骤2和3,选择dist值最小的未确定最短路径的顶点v5,将其标记为已确定最短路径。 13. 更新邻接顶点的最短路径长度: - 无法更新。 | 顶点 | dist值 | | --- | --- | | v1 | 0 | | v2 | 10 | | v3 | 3 | | v4 | 20 | | v5 | ∞ | 14. 最终得到v1到各顶点的最短路径长度dist。 | 顶点 | dist值 | | --- | --- | | v1 | 0 | | v2 | 10 | | v3 | 3 | | v4 | 20 | | v5 | ∞ |
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值