当前位置: 首页 > news >正文

做后台开发的同学一定遇到过这类需求:请假审批:员工提交 -> 主管审批 -> 部门经理审批 -> 副总审批,但不同天数的审批链路还不一样合同审批:金额超过 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" }

关键说明:

http://www.jsqmd.com/news/1078304/

相关文章:

  • .NET开发者集成YOLO目标检测:yolodotnet实战指南
  • 西宁砂石料能送到周边县城吗
  • Momentum1
  • Electron 跨平台移植实战:从 Windows 到 macOS 的适配与 DMG 打包全记录
  • 千问新用户专属878554 无门槛8元通用立减券领取到了!
  • 指标管理的最高境界:让 AI 帮你发现和定义指标
  • (毕业必看)亲测靠谱的一键生成论文工具,毕业党收藏备用
  • 大语言模型推理加速:SPEQ位共享量化技术解析
  • WorkBuddy 上手实战:打造一个可用的本地 AI 工作台
  • 2026深度实测|TRAE与Cursor中文vibe coding迭代能力全对比
  • 工业级机器学习加速实践:从数据到API的72分钟落地指南
  • 8月秋招投递前,应届生AI作品集怎么做才能让简历更具体?
  • Java Web应用安全审计实战:从漏洞挖掘到权限提升的完整攻防路径
  • Vue 2 vs Vue 3:核心特性与差异全解析
  • 2026论文写作工具红黑榜:AI论文写作工具怎么选?看完少走弯路
  • i.MX Graphics SDK跨平台图形开发:从环境配置到项目构建全解析
  • LLM提示安全实战:Prompt注入防御与企业级RAG脱敏方案
  • Python编程资源合集
  • DRAM、NAND Flash、HBM 未来发展前景
  • 痛点还原:手动推导
  • UE5.6 GAS学习笔记(2)-->GA篇 [1.触发流程]
  • RPA引擎源码解析:Python状态机与规则引擎设计
  • 动图魔方技术拆解 09:FrameProcessor 如何统一裁剪、滤镜、字幕和输出参数
  • 遗传算法第二部分:选择压力、交叉算子与自适应变异机制解析
  • 容器云入门学习心得:基于 Docker 实现 Web 应用容器化部署实践
  • Appium跨界Windows桌面自动化测试:统一技术栈实战指南
  • 5分钟搞定FanControl中文设置:Windows风扇控制彻底汉化指南
  • 【2026免费喝奶茶攻略】【领千问8元无门槛券】
  • software framwork 2026.06.25
  • Qwen-VL-2512+Gradio三分钟搭建AI海报工坊