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

本文介绍了一个自己编写的简单工作流引擎的实现过程,主要关注于串行流程,包括需求分析、难点讨论、设计思路和具体实现。通过注册机制和上下文语义,实现了工作流引擎与节点的解耦,并提供了流程表示、节点表示和引擎逻辑的详细说明。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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));
   
### 关于 Flowable 工作流引擎 Flowable 是一种开源的工作流和业务流程管理 (BPM) 平台,支持 BPMN 2.0 和 CMMN 1.1 标准。它提供了灵活的 API 来定义、管理和执行复杂的业务逻辑[^1]。 #### 主要特性 - **标准支持**: Flowable 支持 BPMN 2.0 的核心规范以及扩展功能,允许开发者通过图形化工具设计复杂的工作流模型。 - **轻量级架构**: 它可以嵌入到任何 Java 应用程序中运行,并且具有较低的学习曲线。 - **高性能**: 流程实例的数量不会影响其性能表现,适合大规模并发场景下的应用开发需求。 - **集成能力强大**: 可轻松与其他框架和服务进行交互操作,比如 Spring Boot 或者微服务环境中的其他组件。 以下是使用 `flowable-spring-boot-starter` 配置的一个简单例子: ```java @SpringBootApplication @EnableFlowableProcessEngineConfiguration public class DemoApplication { public static void main(String[] args){ SpringApplication.run(DemoApplication.class,args); } } ``` 对于翻译模板处理方面,则可以通过如下方式实现动态加载不同语言资源文件来完成国际化设置: ```javascript // 假设我们有一个默认英文版界面需要转换成中文显示 let template = 'Submit'; if(translations['zh_CN']){ let translatedText = translations['zh_CN'][template]; if(translatedText){ template = translatedText; }else{ console.log(`No translation found for ${template}`); } } else { console.error('Missing Chinese Translation File'); } console.info(template); // 输出应该是提交而不是原来的 Submit 字样 ``` 另外需要注意的是,在实际项目选型过程中也要考虑到技术栈之间的匹配度问题。例如如果正在使用的SpringBoot版本较老(如v1.x系列),那么可能无法直接采用最新版Camunda产品线上的某些高级特性和优化成果因为它们通常依赖更高版本的基础库支持[^4]。 最后提醒一点关于网关节点的概念理解上可能会存在混淆情况。“流对象”这一术语涵盖了三种基本类型的元素:事件(Event), 活动(Activity) 和 网关(Gateway)[^2]. 这些构成了整个业务过程建模的核心构建模块之一.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值