做后台开发的同学一定遇到过这类需求请假审批员工提交 - 主管审批 - 部门经理审批 - 副总审批但不同天数的审批链路还不一样合同审批金额超过 10 万需要额外部门会签超过 50 万需要财务参与数据抓取多线程并行爬取多个数据源再汇总处理这类需求本质上都是一个有向图的流转问题。如果全靠 if-else 硬编码代码很快就会变成面条式意大利粉。Solon Flow是 Solon 生态中的通用流程编排框架它的核心思路是用 YAML或 Java Fluent API描述流转逻辑用引擎驱动执行。不需要重依赖、不需要外部数据库、不需要部署服务一个 jar 包就能跑。今天这篇文章我们用最经典的请假审批流作为实战案例从零到一跑通 Solon Flow 的核心能力。二、5 分钟跑起来Hello World2.1 添加依赖在pom.xml中添加dependency groupIdorg.noear/groupId artifactIdsolon-flow/artifactId /dependency不需要额外数据库、不需要消息中间件一个依赖搞定。2.2 创建流程定义文件在resources/flow/目录下创建demo1.ymlid: 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开始节点---01activity活动节点缺省类型支持--1..n1..nexclusive排他网关单选支持支持-1..n1..ninclusive包容网关多选支持支持-1..n1..nparallel并行网关全选支持-支持1..n1..nloop循环网关支持--11end结束节点---1..n0几个关键点start和end每个图必须有且仅有一个exclusive排他网关相当于单选只有一条满足条件的连接会流出没有匹配的用默认无条件连接inclusive包容网关相当于多选所有满足条件的连接都会流出需要成对使用实现汇聚parallel并行网关相当于全选所有连接都会流出内部使用多线程执行需要成对使用实现汇聚loop循环网关遍历集合并循环流出通过$for和$in元数据指定变量3.2 流程引擎FlowEngineFlowEngine是执行流程的核心入口主要负责加载和解析流程定义支持 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 上下文FlowContextFlowContext是流程执行的核心现场承载了业务数据、控制状态和执行轨迹// 创建 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 实现请假审批流条件分支 exclusive4.1 业务规则一个请假审批的流转规则如下员工发起请假 - 主管审批 - 天数 3天直接结束主管批了就行 - 天数 3天部门经理审批 - 天数 7天结束 - 天数 7天副总审批 - 结束这是一个典型的条件分支场景用exclusive排他网关来实现。4.2 流程定义创建resources/flow/leave-approval.ymlid: 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: day3 } ]} - { 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: day7 } ]} - { id: n3, type: activity, title: 副总审批, task: vp_approve, link: e } - { id: e, type: end }一共 8 个节点含 start 和 endYAML 共50 行以内。解释几个关键点task: tl_approve以开头的是任务组件引用对应 Java 中注册的TaskComponentwhen: day3连接条件表达式引擎通过表达式引擎SnEL求值day从上下文中获取排他网关g1如果day3满足走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 }关键说明
做后台开发的同学一定遇到过这类需求:请假审批:员工提交 -> 主管审批 -> 部门经理审批 -> 副总审批,但不同天数的审批链路还不一样合同审批:金额超过 10 万需要额外部门会签,超过 50
做后台开发的同学一定遇到过这类需求请假审批员工提交 - 主管审批 - 部门经理审批 - 副总审批但不同天数的审批链路还不一样合同审批金额超过 10 万需要额外部门会签超过 50 万需要财务参与数据抓取多线程并行爬取多个数据源再汇总处理这类需求本质上都是一个有向图的流转问题。如果全靠 if-else 硬编码代码很快就会变成面条式意大利粉。Solon Flow是 Solon 生态中的通用流程编排框架它的核心思路是用 YAML或 Java Fluent API描述流转逻辑用引擎驱动执行。不需要重依赖、不需要外部数据库、不需要部署服务一个 jar 包就能跑。今天这篇文章我们用最经典的请假审批流作为实战案例从零到一跑通 Solon Flow 的核心能力。二、5 分钟跑起来Hello World2.1 添加依赖在pom.xml中添加dependency groupIdorg.noear/groupId artifactIdsolon-flow/artifactId /dependency不需要额外数据库、不需要消息中间件一个依赖搞定。2.2 创建流程定义文件在resources/flow/目录下创建demo1.ymlid: 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开始节点---01activity活动节点缺省类型支持--1..n1..nexclusive排他网关单选支持支持-1..n1..ninclusive包容网关多选支持支持-1..n1..nparallel并行网关全选支持-支持1..n1..nloop循环网关支持--11end结束节点---1..n0几个关键点start和end每个图必须有且仅有一个exclusive排他网关相当于单选只有一条满足条件的连接会流出没有匹配的用默认无条件连接inclusive包容网关相当于多选所有满足条件的连接都会流出需要成对使用实现汇聚parallel并行网关相当于全选所有连接都会流出内部使用多线程执行需要成对使用实现汇聚loop循环网关遍历集合并循环流出通过$for和$in元数据指定变量3.2 流程引擎FlowEngineFlowEngine是执行流程的核心入口主要负责加载和解析流程定义支持 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 上下文FlowContextFlowContext是流程执行的核心现场承载了业务数据、控制状态和执行轨迹// 创建 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 实现请假审批流条件分支 exclusive4.1 业务规则一个请假审批的流转规则如下员工发起请假 - 主管审批 - 天数 3天直接结束主管批了就行 - 天数 3天部门经理审批 - 天数 7天结束 - 天数 7天副总审批 - 结束这是一个典型的条件分支场景用exclusive排他网关来实现。4.2 流程定义创建resources/flow/leave-approval.ymlid: 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: day3 } ]} - { 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: day7 } ]} - { id: n3, type: activity, title: 副总审批, task: vp_approve, link: e } - { id: e, type: end }一共 8 个节点含 start 和 endYAML 共50 行以内。解释几个关键点task: tl_approve以开头的是任务组件引用对应 Java 中注册的TaskComponentwhen: day3连接条件表达式引擎通过表达式引擎SnEL求值day从上下文中获取排他网关g1如果day3满足走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 }关键说明