惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Google DeepMind News
Google DeepMind News
爱范儿
爱范儿
J
Java Code Geeks
L
LangChain Blog
V
V2EX
大猫的无限游戏
大猫的无限游戏
S
SegmentFault 最新的问题
博客园 - Franky
Microsoft Azure Blog
Microsoft Azure Blog
Jina AI
Jina AI
Blog — PlanetScale
Blog — PlanetScale
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
The Cloudflare Blog
博客园 - 司徒正美
B
Blog
G
Google Developers Blog
Stack Overflow Blog
Stack Overflow Blog
罗磊的独立博客
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Apple Machine Learning Research
Apple Machine Learning Research
Engineering at Meta
Engineering at Meta
MyScale Blog
MyScale Blog
有赞技术团队
有赞技术团队
Hugging Face - Blog
Hugging Face - Blog

博客园_首页

Linux实操--组管理、权限管理和定时任务 Java + EasyExcel 实现单个接口导出多个Excel Mem0 源码解析系列(二):提示词工程的深度剖析 Openclaw TaskFlow究竟是什么?和普通Skill技能有什么区别 博文阅读密码验证 - 博客园 嘉立创开源:应该是全网MicroPython教程最多的开发板 Hermes Agent 集成实践:从协议到生产 2026年AI编程工具横评:Cursor、Codex、Claude Code、Zed、Windsurf Java程序员必看的RAG入门教程 2026 AI效率神器:Superpowers + Claude Code 保姆级教程 本地大模型部署全攻略:从 0 到 1 玩转 Ollama 【从0到1构建一个ClaudeAgent】内存管理-上下文压缩 .NET 高级开发 | 设计、实现一个事件总线框架 电子小白入门之NE555 3. WorkBuddy:隐藏玩法,一键召唤专家,让 AI 以"专家身份"给你干活 和AI一起搞事情#3:Claude Teammate 游戏开发翻车实录 【OpenClaw】通过 Nanobot 源码学习架构---(7)Memory C# .NET 周刊|2026年3月3期 我在 Debian 11 上把 K8s 单机搭起来了,过程没你想的那么顺(/opt 目录版) 深度学习进阶(七)Data-efficient Image Transformer CLI+Skill搭建浏览器AI自动化框架,告别一切重复枯燥任务 告别Token账单无底洞:OpenClaw本地部署,重塑企业数据主权的唯一解 FastAPI+Vue:文件分片上传+秒传+断点续传,这坑我帮你踩平了! SBTI 爆火后,我做了个程序员版的 CBTI。。已开源 + 附开发过程 多模态检索开始进入工程期:用 Sentence Transformers 搭建可落地的 Multimodal RAG 100多行代码实现一个最简单的Agent(用ReAct) Claude Code 通关手册(八):推荐 5 个 Hooks,代码质量提升 3 倍 老板:“有人截图了!”。安全部门:“收到,马上查暗水印!” - why技术 技术之外,皆是人间 C#/.NET/.NET Core技术前沿周刊 | 第 69 期(2026年4.01-4.12)
Solon Flow 实战:用 50 行 YAML 实现一个请假审批流(含中断...
带刺的坐椅 · 2026-05-19 · via 博客园_首页

一、为什么需要流程编排?

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

  • 请假审批:员工提交 -> 主管审批 -> 部门经理审批 -> 副总审批,但不同天数的审批链路还不一样
  • 合同审批:金额超过 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 开始节点 - - - 0 1
activity 活动节点(缺省类型) 支持 - - 1..n 1..n
exclusive 排他网关(单选) 支持 支持 - 1..n 1..n
inclusive 包容网关(多选) 支持 支持 - 1..n 1..n
parallel 并行网关(全选) 支持 - 支持 1..n 1..n
loop 循环网关 支持 - - 1 1
end 结束节点 - - - 1..n 0

几个关键点:

  • 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" }

关键说明:

  • g1 是并行网关的 分叉端:所有流出连接都会同时执行(多线程)
  • g2 是并行网关的 汇聚端:等待所有流入连接到齐后,才继续往后流转
  • parallel 必须成对使用(分叉 + 汇聚)

任务组件:

@Component("send_sms")
public class SendSms implements TaskComponent {
    @Override
    public void run(FlowContext context, Node node) throws Throwable {
        String applicant = context.getAs("applicant");
        System.out.println("[短信] 通知 " + applicant + ":您的请假申请已审批通过");
    }
}

@Component("send_email")
public class SendEmail implements TaskComponent {
    @Override
    public void run(FlowContext context, Node node) throws Throwable {
        String applicant = context.getAs("applicant");
        System.out.println("[邮件] 通知 " + applicant + ":您的请假申请已审批通过");
    }
}

运行后,短信和邮件两个任务会 并行执行(parallel 节点内部使用多线程),执行完毕后在 g2 汇聚,然后继续往后。

六、实战三:中断与恢复 — 审批到一半要等领导出差回来

实际业务中,审批往往不是一次完成的。比如副总出差了,流程要暂停,等副总回来再继续。这就是 Solon Flow 的 中断与恢复 能力。

6.1 核心机制

  • 在任务组件中调用 context.stop(),流程会停在当前节点
  • 通过 context.toJson() 将整个上下文序列化为 JSON 快照
  • 需要恢复时,通过 FlowContext.fromJson() 还原上下文
  • 再次调用 engine.eval(),引擎会 自动从上次停止的节点继续执行

6.2 修改副总审批组件,加入中断逻辑

@Component("vp_approve")
public class VPApprove implements TaskComponent {
    @Override
    public void run(FlowContext context, Node node) throws Throwable {
        String applicant = context.getAs("applicant");
        boolean vpAvailable = context.getAs("vpAvailable"); // 副总是否在岗

        if (!vpAvailable) {
            System.out.println("副总出差中,流程暂停等待。申请人=" + applicant);
            context.stop(); // 中断流程
            return;
        }

        System.out.println("副总审批通过:申请人=" + applicant);
        context.put("vp_approved", true);
    }
}

6.3 执行与恢复

public class InterruptResumeTest {
    public static void main(String[] args) {
        FlowEngine engine = FlowEngine.newInstance();
        engine.load("classpath:flow/leave-approval.yml");

        // ===== 第一阶段:提交请假,副总出差,流程暂停 =====
        FlowContext context = FlowContext.of("leave-001")
                .put("applicant", "王五")
                .put("day", 10)
                .put("vpAvailable", false);  // 副总不在

        engine.eval("leave-approval", context);
        // 输出:主管审批通过... 部门经理审批通过... 副总出差中,流程暂停等待

        System.out.println("流程是否已停止: " + context.isStopped());  // true
        System.out.println("最后执行节点: " + context.lastNodeId());    // n3

        // 持久化快照到数据库(实际项目中)
        String snapshot = context.toJson();
        // db.save("leave-001", snapshot);

        // ===== 第二阶段:副总回来了,恢复流程 =====
        // 从数据库加载快照
        // String snapshot = db.load("leave-001");
        FlowContext restored = FlowContext.fromJson(snapshot);
        restored.put("vpAvailable", true);  // 更新状态:副总回来了

        engine.eval("leave-approval", restored);
        // 输出:副总审批通过:申请人=王五
    }
}

这个能力非常强大。上下文的序列化和恢复是 快照级别 的,意味着:

  • 可以跨线程恢复(异步回调场景)
  • 可以跨时间恢复(长流程场景)
  • 可以跨服务器恢复(分布式场景,配合数据库持久化快照)

非常适合审批流、AI 对话流等长流程场景。

七、实战四:循环遍历 — 批量审批

假设部门经理需要一次性审批团队所有待处理的请假单,可以用 loop(循环网关) 实现:

id: "batch-approve"
title: "批量审批"
layout:
  - { id: "s", type: "start", link: "g1" }
  - { id: "g1", type: "loop", meta: { "$for": "item", "$in": "pendingList" }, link: "n1" }
  - { id: "n1", type: "activity", title: "逐条审批", task: "@batch_item_approve", link: "g2" }
  - { id: "g2", type: "loop", link: "e" }
  - { id: "e", type: "end" }

关键说明:

  • 第一个 loop 节点 g1 是循环的 流出端:通过 $in 从上下文中获取集合 pendingList,用 $for 声明循环变量名 item
  • 循环每取出一个元素,推入上下文变量 item,后续节点可直接使用
  • 第二个 loop 节点 g2 是循环的 汇聚端:等待遍历结束后才往后流转
  • loop 必须成对使用

任务组件:

@Component("batch_item_approve")
public class BatchItemApprove implements TaskComponent {
    @Override
    public void run(FlowContext context, Node node) throws Throwable {
        // 每次循环,item 变量自动推入上下文
        Map<String, Object> item = context.getAs("item");
        System.out.println("批量审批处理:" + item.get("applicant") + " - " + item.get("day") + "天");
    }
}

执行:

FlowContext context = FlowContext.of()
        .put("pendingList", Arrays.asList(
            Map.of("applicant", "张三", "day", 1),
            Map.of("applicant", "李四", "day", 4),
            Map.of("applicant", "王五", "day", 8)
        ));
engine.eval("batch-approve", context);
// 输出:
// 批量审批处理:张三 - 1天
// 批量审批处理:李四 - 4天
// 批量审批处理:王五 - 8天

八、实战五:Graph 编码模式 — 用 Java Fluent API 定义流程

除了 YAML,Solon Flow 还支持用纯 Java 代码定义流程。这在需要动态构建流程、或者流程需要根据参数动态调整的场景下非常有用。

import org.noear.solon.flow.core.Graph;

Graph graph = Graph.create("leave", spec -> {
    spec.addStart("s").title("发起人").linkAdd("n1");

    spec.addActivity("n1").title("主管审批").task("@tl_approve").linkAdd("g1");

    spec.addExclusive("g1")
            .linkAdd("e", l -> l.title("3天以下"))
            .linkAdd("n2", l -> l.title("3天以上").when("day>=3"));

    spec.addActivity("n2").title("部门经理审批").task("@dm_approve").linkAdd("g2");

    spec.addExclusive("g2")
            .linkAdd("e", l -> l.title("7天以下"))
            .linkAdd("n3", l -> l.title("7天以上").when("day>=7"));

    spec.addActivity("n3").title("副总审批").task("@vp_approve").linkAdd("e");

    spec.addEnd("e");
});

// 直接执行
FlowEngine engine = FlowEngine.newInstance();
engine.eval(graph);

Fluent API 的方法名与 YAML 中的节点类型完全对应:

  • spec.addStart(id) / spec.addEnd(id) / spec.addActivity(id) / spec.addExclusive(id) ...
  • .title() 设置标题
  • .task() 设置任务
  • .linkAdd(nextId) 添加流出连接(无参 lambda 可设置连接标题和条件)
  • .when() 设置连接条件表达式

此外,Graph 还支持复制和修改已有图:

// 复制并修改为新图
Graph graphNew = Graph.copy(graph, spec -> {
    spec.removeNode("n3");  // 移除副总审批节点
    spec.getNode("n2").linkRemove("n3").linkAdd("e"); // n2 直接连到 end
});

这种能力非常适合做 流程模板:先定义基础流程,再根据租户/业务线动态裁剪。

两种模式也可以互相转换:

// Graph -> YAML(反向序列化)
String yaml = graph.toYaml();

// Graph -> JSON
String json = graph.toJson();

// Graph -> PlantUML 状态图文本(v3.9.5+)
String plantuml = graph.toPlantuml();

九、高级能力:拦截器、事件总线、单步调试

9.1 拦截器(AOP 能力)

Solon Flow 提供了 FlowInterceptor 拦截器接口,可以在不修改流程定义和任务组件的前提下,对流程执行进行切面增强。

常见的使用场景:

场景 实现方式
性能监控 doIntercept 中统计总耗时
执行审计 onNodeStart 记录节点 ID 和上下文参数
安全校验 doIntercept 中校验必要 Token
参数补全 流程开始前向上下文注入全局配置
@Component
public class FlowAuditInterceptor implements FlowInterceptor {
    static Logger log = LoggerFactory.getLogger(FlowAuditInterceptor.class);

    @Override
    public void doIntercept(FlowInvocation inv) throws Throwable {
        long start = System.currentTimeMillis();
        try {
            inv.invoke();  // 执行流程
        } catch (Throwable ex) {
            log.error("流程执行异常: " + inv.getStartNode().getGraph().getId(), ex);
            throw ex;
        } finally {
            long elapsed = System.currentTimeMillis() - start;
            log.info("流程 {} 执行耗时: {}ms", inv.getStartNode().getGraph().getId(), elapsed);
        }
    }

    @Override
    public void onNodeStart(FlowContext context, Node node) {
        log.info("节点开始: id={}, title={}", node.getId(), node.getTitle());
    }

    @Override
    public void onNodeEnd(FlowContext context, Node node) {
        log.info("节点完成: id={}, title={}", node.getId(), node.getTitle());
    }
}

拦截器可以通过 Solon 容器自动注册(@Component),也可以手动注册:

engine.addInterceptor(new FlowAuditInterceptor());

9.2 事件总线(解耦利器)

Solon Flow 内置了基于 DamiBus 的事件总线,支持在流程执行中进行 上下文级别(实例级别) 的事件通信。

三种模式:

模式 方法 说明
发送 send 异步/同步广播,不要求答复
请求 call 发送并等待答复(类似 RPC)
stream 发送并持续接收数据流

在流程中触发事件:

@Component("notify_task")
public class NotifyTask implements TaskComponent {
    @Override
    public void run(FlowContext context, Node node) throws Throwable {
        // 发送事件(广播)
        context.eventBus().send("leave.approved", context.getAs("applicant"));
    }
}

在外部订阅事件:

FlowContext context = FlowContext.of();
context.eventBus().<String>listen("leave.approved", event -> {
    System.out.println("收到审批通过通知: " + event.getPayload());
    // 在这里处理:发企微通知、更新统计报表、推送消息队列等
});

engine.eval("leave-approval", context);

通过事件总线,流程定义只管"发信号",具体的业务处理(发通知、写日志、更新缓存)由外部监听器处理,实现了极度解耦。

9.3 单步前进(调试模式)

Solon Flow 支持深度控制,可以逐步执行流程中的每一个节点,类似调试器的单步执行:

FlowEngine engine = FlowEngine.newInstance();
engine.load("classpath:flow/leave-approval.yml");

FlowContext context = FlowContext.of()
        .put("applicant", "张三")
        .put("day", 2);

// 每次执行一步(深度=1)
engine.eval("leave-approval", 1, context);
System.out.println("当前节点: " + context.lastRecord().getTitle()); // 发起人

engine.eval("leave-approval", 1, context);
System.out.println("当前节点: " + context.lastRecord().getTitle()); // 主管审批

engine.eval("leave-approval", 1, context);
System.out.println("当前节点: " + context.lastRecord().getTitle()); // 天数判断

这个能力非常适合:

  • 流程调试:开发阶段逐步排查问题
  • 流程演示:向产品/业务逐步展示流转过程
  • 交互式流程:每个节点执行后等待用户确认再继续

十、可视化设计器

对于不熟悉 YAML 的业务人员,Solon Flow 提供了 可视化设计器,可以通过拖拽的方式设计流程图,然后导出为 YAML/JSON。

设计器特点:

  • 拖拽式节点编排,所见即所得
  • 支持 7 种节点类型的可视化配置
  • 可设置节点属性(标题、任务、条件、元数据等)
  • 导出为 YAML/JSON/Map 格式,与代码模式无缝衔接

有了设计器,流程定义可以由业务人员直接参与维护,开发人员只需要关注任务组件的实现,实现真正的职责分离。

十一、包容网关补充 — 条件会签场景

除了排他网关(单选)和并行网关(全选),还有一种 包容网关(inclusive),相当于"多选"——满足条件的连接都会流出。

以合同审批为例,金额不同需要不同部门会签:

id: "contract-approval"
title: "合同审批(条件会签)"
layout:
  - { id: "s", type: "start", title: "发起人", meta: { role: "employee" }, link: "n1" }
  - { id: "n1", type: "activity", title: "主管批", meta: { role: "tl" }, link: "g1" }
  - { id: "g1", type: "inclusive", title: "会签", link: [
      { nextId: "n2", title: "10万以上", when: "amount>=100000" },
      { nextId: "n3", title: "50万以上", when: "amount>=500000" },
      { nextId: "n4", title: "90万以上", when: "amount>=900000" }
    ]}
  - { id: "n2", type: "activity", title: "本部门经理批", task: "@dm_approve", link: "g2" }
  - { id: "n3", type: "activity", title: "生产部经理批", task: "@prod_approve", link: "g2" }
  - { id: "n4", type: "activity", title: "财务部经理批", task: "@fin_approve", link: "g2" }
  - { id: "g2", type: "inclusive", link: "e" }
  - { id: "e", type: "end" }

如果合同金额 80 万,则 g1 会同时流出 n2(10万以上)和 n3(50万以上),两个部门并行会签。g2 作为汇聚端,等待所有满足条件的流入到齐后才继续。

十二、总结与进阶

12.1 本文覆盖的核心能力

能力 对应特性 实战章节
条件分支 exclusive 排他网关 实战一:请假审批
并行执行 parallel 并行网关 实战二:并行通知
中断恢复 context.stop() + toJson/fromJson 实战三:中断恢复
循环遍历 loop 循环节点 + $for/$in 实战四:批量审批
Fluent API Graph.create() 硬编码 实战五:Graph 编码
条件会签 inclusive 包容网关 十一章:合同审批
AOP 扩展 FlowInterceptor 拦截器 高级能力
事件解耦 DamiBus 事件总线 高级能力
单步调试 eval(id, depth, context) 高级能力
可视化设计 可视化设计器 第十章

12.2 Solon Flow 的适用场景

  • 无状态流:计算编排、规则决策、数据抓取、AI Agent 工具调用链
  • 有状态流:审批流、长流程、需要中断恢复的业务场景
  • 混合场景:审批流 + 自动化任务编排的混合流程

12.3 进阶方向

如果你已经在项目中使用 Solon Flow,还可以关注以下进阶主题:

  • solon-flow-workflow:在 solon-flow 基础上封装了工作流服务,提供任务认领(claimTask)、任务查找(findTask)、状态控制(StateController)、状态持久化(StateRepository)等开箱即用的工作流能力
  • 引擎驱动定制:通过自定义 FlowDriver 实现不同的执行策略
  • 元数据扩展:通过 meta 字段实现节点级别的自定义扩展(权限、超时、重试等)
  • Solon AI Flow:将 Solon Flow 与 AI 能力结合,实现 ReAct Agent、AI 工具链等场景
  • PlantUML 导出:通过 graph.toPlantuml() 生成状态图文档,方便技术评审

12.4 项目信息


写在最后:流程编排的本质是"把流转逻辑从业务代码中抽离出来"。Solon Flow 用最轻量的方式做到了这一点——一个依赖、一个 YAML 文件、几行 Java 代码,就能跑起一个完整的审批流。如果你正在寻找一个轻量、灵活、不绑定外部服务的流程编排方案,值得试试。