做后台开发的同学一定遇到过这类需求:请假审批:员工提交 -> 主管审批 -> 部门经理审批 -> 副总审批,但不同天数的审批链路还不一样合同审批:金额超过 10 万需要额外部门会签,超过 50

做后台开发的同学一定遇到过这类需求:请假审批:员工提交 -> 主管审批 -> 部门经理审批 -> 副总审批,但不同天数的审批链路还不一样合同审批:金额超过 10 万需要额外部门会签,超过 50

做后台开发的同学一定遇到过这类需求:

  • 请假审批:员工提交 -> 主管审批 -> 部门经理审批 -> 副总审批,但不同天数的审批链路还不一样
  • 合同审批:金额超过 10 万需要额外部门会签,超过 50 万需要财务参与
  • 数据抓取:多线程并行爬取多个数据源,再汇总处理

这类需求本质上都是一个有向图的流转问题。如果全靠 if-else 硬编码,代码很快就会变成面条式意大利粉。

Solon Flow是 Solon 生态中的通用流程编排框架,它的核心思路是:用 YAML(或 Java Fluent API)描述流转逻辑,用引擎驱动执行。不需要重依赖、不需要外部数据库、不需要部署服务,一个 jar 包就能跑。

今天这篇文章,我们用最经典的"请假审批流"作为实战案例,从零到一跑通 Solon Flow 的核心能力。

二、5 分钟跑起来:Hello World

2.1 添加依赖

pom.xml中添加:

<dependency> <groupId>org.noear</groupId> <artifactId>solon-flow</artifactId> </dependency>

不需要额外数据库、不需要消息中间件,一个依赖搞定。

2.2 创建流程定义文件

resources/flow/目录下创建demo1.yml

id: "demo1" layout: - { id: "s", type: "start", link: "n1" } - { id: "n1", type: "activity", task: 'System.out.println("hello world!");', link: "e" } - { id: "e", type: "end" }

这是一个最简单的三节点流程:开始 -> 执行打印 -> 结束。

2.3 执行

两种方式,任选其一:

方式一:原生 Java 模式(不需要启动 Solon 容器)

import org.noear.solon.flow.FlowEngine; public class Demo { public static void main(String[] args) { FlowEngine engine = FlowEngine.newInstance(); engine.load("classpath:flow/*"); engine.eval("demo1"); // 控制台输出:hello world! } }

方式二:Solon 容器注解模式

先在应用配置中声明流程文件位置:

# app.yml solon.flow: - "classpath:flow/*.yml"

然后在组件中注入引擎直接使用:

@Component public class DemoCom implements LifecycleBean { @Inject private FlowEngine flowEngine; @Override public void start() throws Throwable { flowEngine.eval("demo1"); } }

运行后控制台输出:hello world!。5 分钟,一个流程就跑通了。

三、核心概念速览:7 种节点 + 引擎 + 上下文

在进入实战之前,先过一遍 Solon Flow 的核心概念。这些概念非常精简,但足够覆盖绝大多数编排场景。

3.1 七种节点类型

Solon Flow 的图由7 种节点构成:

节点类型说明执行任务连接条件多线程可流入可流出
start开始节点---01
activity活动节点(缺省类型)支持--1..n1..n
exclusive排他网关(单选)支持支持-1..n1..n
inclusive包容网关(多选)支持支持-1..n1..n
parallel并行网关(全选)支持-支持1..n1..n
loop循环网关支持--11
end结束节点---1..n0

几个关键点:

  • startend每个图必须有且仅有一个
  • exclusive(排他网关):相当于"单选",只有一条满足条件的连接会流出,没有匹配的用默认(无条件)连接
  • inclusive(包容网关):相当于"多选",所有满足条件的连接都会流出,需要成对使用实现汇聚
  • parallel(并行网关):相当于"全选",所有连接都会流出,内部使用多线程执行,需要成对使用实现汇聚
  • loop(循环网关):遍历集合并循环流出,通过$for$in元数据指定变量

3.2 流程引擎(FlowEngine)

FlowEngine是执行流程的核心入口,主要负责:

  • 加载和解析流程定义(支持 YAML、JSON、Java 硬编码)
  • 驱动流程执行
  • 管理拦截器链
FlowEngine engine = FlowEngine.newInstance(); engine.load("classpath:flow/*.yml"); // 加载 engine.eval("demo1"); // 执行(按 id 匹配) engine.eval("demo1", context); // 执行(带上下文) engine.eval("demo1", 1, context); // 执行(深度控制,单步调试)

3.3 上下文(FlowContext)

FlowContext是流程执行的核心现场,承载了业务数据、控制状态和执行轨迹:

// 创建 FlowContext context = FlowContext.of(); FlowContext context = FlowContext.of("instance-001"); // 传递业务参数 context.put("day", 5); context.put("applicant", "张三"); // 获取参数 int day = context.getAs("day"); // 控制流程 context.stop(); // 停止(可用于中断恢复) context.interrupt(); // 中断当前分支 // 序列化与恢复 String snapshot = context.toJson(); FlowContext restored = FlowContext.fromJson(snapshot); // 执行轨迹 context.lastNodeId(); // 最后执行的节点 ID context.trace(); // 完整执行轨迹

概念不多,但覆盖了编排、执行、控制、恢复四大核心能力。下面进入实战。

四、实战一:50 行 YAML 实现请假审批流(条件分支 exclusive)

4.1 业务规则

一个请假审批的流转规则如下:

员工发起请假 -> 主管审批 -> 天数 < 3天:直接结束(主管批了就行) -> 天数 >= 3天:部门经理审批 -> 天数 < 7天:结束 -> 天数 >= 7天:副总审批 -> 结束

这是一个典型的条件分支场景,用exclusive(排他网关)来实现。

4.2 流程定义

创建resources/flow/leave-approval.yml

id: "leave-approval" title: "请假审批" layout: - { id: "s", type: "start", title: "发起人", meta: { role: "employee" }, link: "n1" } - { id: "n1", type: "activity", title: "主管审批", task: "@tl_approve", link: "g1" } - { id: "g1", type: "exclusive", title: "天数判断", link: [ { nextId: "e", title: "3天以下" }, { nextId: "n2", title: "3天以上", when: "day>=3" } ]} - { id: "n2", type: "activity", title: "部门经理审批", task: "@dm_approve", link: "g2" } - { id: "g2", type: "exclusive", title: "天数判断", link: [ { nextId: "e", title: "7天以下" }, { nextId: "n3", title: "7天以上", when: "day>=7" } ]} - { id: "n3", type: "activity", title: "副总审批", task: "@vp_approve", link: "e" } - { id: "e", type: "end" }

一共 8 个节点(含 start 和 end),YAML 共50 行以内

解释几个关键点:

  • task: "@tl_approve":以@开头的是任务组件引用,对应 Java 中注册的TaskComponent
  • when: "day>=3":连接条件表达式,引擎通过表达式引擎(SnEL)求值,day从上下文中获取
  • 排他网关g1:如果day>=3满足,走n2;否则走默认连接e(没有 when 的连接就是默认)
  • meta: { role: "employee" }:元数据,可以在拦截器或任务中读取,用于权限判断等

4.3 任务组件实现

每个task中的@xxx引用,对应一个 Java 组件:

import org.noear.solon.flow.core.TaskComponent; import org.noear.solon.flow.core.FlowContext; import org.noear.solon.flow.core.Node; // 主管审批 @Component("tl_approve") public class TeamLeaderApprove implements TaskComponent { @Override public void run(FlowContext context, Node node) throws Throwable { String applicant = context.getAs("applicant"); int day = context.getAs("day"); System.out.println("主管审批通过:申请人=" + applicant + ", 天数=" + day); context.put("tl_approved", true); } } // 部门经理审批 @Component("dm_approve") public class DeptManagerApprove implements TaskComponent { @Override public void run(FlowContext context, Node node) throws Throwable { String applicant = context.getAs("applicant"); System.out.println("部门经理审批通过:申请人=" + applicant); context.put("dm_approved", true); } } // 副总审批 @Component("vp_approve") public class VPApprove implements TaskComponent { @Override public void run(FlowContext context, Node node) throws Throwable { String applicant = context.getAs("applicant"); System.out.println("副总审批通过:申请人=" + applicant); context.put("vp_approved", true); } }

4.4 测试运行

public class LeaveApprovalTest { public static void main(String[] args) { FlowEngine engine = FlowEngine.newInstance(); engine.load("classpath:flow/leave-approval.yml"); // 场景1:请假2天 -> 主管审批 -> 结束 FlowContext ctx1 = FlowContext.of() .put("applicant", "张三") .put("day", 2); engine.eval("leave-approval", ctx1); // 输出:主管审批通过:申请人=张三, 天数=2 // 场景2:请假5天 -> 主管 -> 部门经理 -> 结束 FlowContext ctx2 = FlowContext.of() .put("applicant", "李四") .put("day", 5); engine.eval("leave-approval", ctx2); // 输出:主管审批通过... 部门经理审批通过... // 场景3:请假10天 -> 主管 -> 部门经理 -> 副总 -> 结束 FlowContext ctx3 = FlowContext.of() .put("applicant", "王五") .put("day", 10); engine.eval("leave-approval", ctx3); // 输出:主管审批通过... 部门经理审批通过... 副总审批通过... } }

这就是完整的请假审批流。50 行 YAML + 几个任务组件,流转逻辑一目了然。

五、实战二:并行网关 — 同时发送短信和邮件通知

审批通过后,需要同时给申请人和 HR 发送短信通知和邮件通知。这是一个典型的并行执行场景,用parallel(并行网关)来实现。

id: "notify-flow" title: "审批通知" layout: - { id: "s", type: "start", link: "n1" } - { id: "n1", type: "activity", title: "审批完成", task: "@after_approve", link: "g1" } - { id: "g1", type: "parallel", title: "并行发送", link: ["n2", "n3"] } - { id: "n2", type: "activity", title: "发送短信", task: "@send_sms", link: "g2" } - { id: "n3", type: "activity", title: "发送邮件", task: "@send_email", link: "g2" } - { id: "g2", type: "parallel", title: "汇聚结果", link: "e" } - { id: "e", type: "end" }

关键说明: