但 AntFlowCore(也叫 AntFlow.NET)这个项目,还是让我在翻开源码后停不下来。
原因很简单:它没有选择"翻译"Java 版 AntFlow,而是用 .NET 生态的工具链重新解了同一道题。 而且解题方式,相当有意思。
这个项目 2024 年 11 月起步,到现在的完成度——19 种流程操作、22 个事件钩子接口、9 个表单生命周期方法、11 个分层项目、Apache 2.0 开源——说实话,不像一个人的业余项目。
今天就来拆开看看,里面到底装了什么。
https://github.com/mrtylerzhou/AntFlow.net
一、先说背景:为什么是钉钉审批流
AntFlowCore 的灵感来自钉钉的审批流设计——国内最被低估的产品之一。
用过钉钉审批的人都知道,它的强大不在于"画流程图",而在于对中国企业组织架构的理解。直属领导审批、HRBP 会签、按职级跳转、跨部门加签……这些在欧美 BPMN 标准里不存在的需求,在钉钉里是标配。
原版 AntFlow 是 Java 实现的,2024 年 5 月启动。而 .NET 版本(AntFlowCore)半年后跟进,瞄准 .NET 10 和 C# 13,定位很明确:让 .NET 生态的企业级应用也能用上这套审批流引擎。
二、架构全景:11 个项目的分层逻辑
AntFlowCore 的解决方案被拆成了 11 个项目,这不是为了看起来"专业",而是有清晰的依赖方向:
AntFlowCore.Web ← 入口,Minimal API 宿主
↓
AntFlowCore.Api ← REST 控制器
↓
AntFlowCore.AspNetCore ← DI 注册、中间件、FreeSql 配置
↓
├── AntFlowCore.Engine ← 业务服务、工厂、处理器链
│ ↓
│ AntFlowCore.Bpmn ← BPMN 引擎核心:服务、适配器、监听器
│ ↓
│ AntFlowCore.Base ← 领域模型:实体、BPMN 模型、枚举、DTO
│
├── AntFlowCore.Persist ← FreeSql 仓储实现
├── AntFlowCore.Persist.api ← 仓储接口(68 个)
├── AntFlowCore.Abstraction ← 服务接口、AOP、格式化器
└── AntFlowCore.Abstraction.Orm ← 仓储抽象基类
注:解决方案中还有
AntFlowCore.Engine.Orm,无源码文件,仅用于 NuGet 打包。
依赖方向很干净:Web → Api → AspNetCore → (Engine → Bpmn → Base) + (Persist → Persist.api → Abstraction.Orm)。没有循环依赖,没有跨层调用。
AntFlowCore.Base 是地基,60 多个数据库实体、80 多个 VO、50 多个枚举类型,定义了系统的每个角落。Bpmn 是引擎核心,负责流程的创建、执行、流转。Engine 是业务层,把引擎能力包装成可用服务。Persist 用 FreeSql 做了 68 个仓储接口和实现。
这种分层不是教科书式的"为了分层而分层",而是每个层都有明确的职责边界和可替换性。比如你可以把 FreeSql 换成 EF Core,只需要重写 Persist 层,上面的引擎和业务逻辑完全不用动。
三、核心技术亮点
3.1 Natasha 动态工厂:让编译器替你写代码
这是整个项目最值得细说的部分。
在传统的策略模式实现中,工厂方法通常长这样:
publicclassAdaptorFactory : IAdaptorFactory
{
public IProcessOperationAdaptor GetProcessOperationAdaptor(string type)
{
return type switch
{
"Submit" => _submitAdaptor,
"Agree" => _agreeAdaptor,
"DisAgree" => _disagreeAdaptor,
"Transfer" => _transferAdaptor,
"Delegate" => _delegateAdaptor,
"BackToModify" => _backToModifyAdaptor,
// ... 19 种操作,19 个 case
_ => thrownew InvalidOperationException($"Unknown type: {type}")
};
}
}
每加一个新的适配器,就要在工厂里加一行 case。19 种操作 × 5 个工厂方法 = 近百行 switch-case。而且这些代码纯粹是样板,没有任何业务逻辑。
AntFlowCore 的作者选择了一个不同的解法:用 Natasha 在启动时动态编译出这个工厂类。
具体做法分三步:
第一步,在接口方法上打标记:
publicinterfaceIAdaptorFactory
{
[SpfService(typeof(IProcessOperationAdaptor))]
IProcessOperationAdaptor GetProcessOperationAdaptor(string bizObjectCode);
[AutoParse]
IEnumerable<T> AutoParseAdaptors<T>(string tag);
}
第二步,启动时通过反射读取这些标记,生成 C# 源码字符串。
第三步,用 Natasha 的 AssemblyCSharpBuilder 编译并实例化:
var builder = new AssemblyCSharpBuilder();
builder.AddCode(generatedSource);
var assembly = builder.Compile();
var factory = (IAdaptorFactory)assembly.CreateInstance("GeneratedAdaptorFactory");
最终运行时拿到的是一个编译好的、类型安全的工厂类,性能跟手写的 switch-case 一模一样,但代码是自动生成的。
好代码不写在 switch-case 里,而是让编译器替你写。
这个模式的价值不在于"省了几行代码",而在于它建立了一个零维护成本的扩展机制:新增适配器只需要实现接口并注册到 DI,工厂自动感知,不需要改任何已有代码。开闭原则,落地得相当漂亮。
3.2 Rougamo.Fody AOP 事务:告别手动 Begin/Commit/Rollback
工作流引擎对事务的要求极高——一个审批动作涉及创建任务、更新执行上下文、写入历史记录、发送通知,任何一步失败都要全部回滚。
传统的写法:
publicasync Task DoSomething()
{
usingvar uow = _freeSql.CreateUnitOfWork();
try
{
// 业务逻辑...
uow.Commit();
}
catch
{
uow.Rollback();
throw;
}
}
每个需要事务的方法都要写一遍。AntFlowCore 的做法是用 Rougamo.Fody 做 IL 编织,把事务逻辑抽成一个 TransactionalAttribute:
[Transactional]
publicasync Task DoSomething()
{
// 只写业务逻辑,事务自动管理
}
编译时,Fody 会把方法的 IL 织入成类似这样的结构:进入方法时开始 UnitOfWork,正常退出时 Commit,抛出异常时 Rollback。还支持异步方法。
这是 .NET 生态一个被低估的用法。AOP 在 Java 的 Spring 里是标配,但在 .NET 里很多人还停留在"写个拦截器"的阶段。Rougamo.Fody 的 IL 编织方案,性能比动态代理更好——因为它发生在编译时,运行时零开销。
3.3 19 种流程操作:策略模式的教科书级应用
一个成熟的工作流引擎,核心能力体现在"审批过程中能做什么"。AntFlowCore 定义了 19 种流程操作,每种都是一个独立的 IProcessOperationAdaptor 实现:
所有操作统一走 ButtonsOperation 入口,通过策略模式分发到对应的适配器。API 层只需要一个端点:
[HttpPost("process/buttonsOperation")]
public Result<BusinessDataVo> ButtonsOperation(
[FromServices] IHttpContextAccessor accessor,
[FromQuery] string formCode)
{
var values = accessor.HttpContext!.ReadRawBodyAsString();
return _processApprovalService.ButtonsOperation(values, formCode);
}
一个端点,19 种行为。这也是为什么中国企业的审批场景能被覆盖得这么完整——每种操作都是一个独立的扩展点,而不是硬编码在引擎里的固定流程。
3.4 模板方法模式:表单操作的生命周期
除了流程操作,表单数据处理也是一个核心问题。AntFlowCore 用 IFormOperationAdaptor<T> 接口定义了 9 个生命周期方法:
publicinterfaceIFormOperationAdaptor<T>
{
voidPreviewSetCondition(T vo); // 预览时设置条件
voidLaunchParameters(T vo); // 启动参数
voidOnInitData(T vo); // 初始化数据
voidOnQueryData(T vo); // 查询数据
voidOnSubmitData(T vo); // 提交数据
voidOnConsentData(T vo); // 同意时处理
voidOnBackToModifyData(T vo); // 打回修改时处理
voidOnCancellationData(T vo); // 撤销时处理
voidOnFinishData(BusinessDataVo vo); // 流程完成时处理
}
这是一个经典的模板方法模式。每种表单类型实现这个接口,引擎在流程的不同阶段自动调用对应的方法。
开发者添加一个新表单类型只需要做两件事:
[DIYFormServiceAnno(SvcName = "LeaveApply", Desc = "请假申请")]
publicclassLeaveApplyFlowService : IFormOperationAdaptor<LeaveApplyVo>
{
publicvoidOnSubmitData(LeaveApplyVo vo) { /* 创建请假记录 */ }
publicvoidOnConsentData(LeaveApplyVo vo) { /* 更新请假状态 */ }
}
然后注册到 DI。不需要修改引擎代码,不需要改路由,不需要改任何已有文件。FormFactory 通过 [DIYFormServiceAnno] 属性自动发现并路由到正确的适配器。
四、工作流能力的深度
评估一个工作流引擎,不能只看它支持多少种节点类型,更要看它在审批过程中能做什么。
AntFlowCore 的审批能力清单:
顺序会签
审批人依次审批,前一个不同意后一个看不到 并行会签
所有审批人同时审批,需要全部通过 或签
:多个审批人中任意一人通过即可 加签/加批
审批过程中动态增加审批人 转办
把审批任务转给他人处理 委托
将任务委托给他人代办 打回至任意节点
不是只能打回到上一个节点,可以指定任意历史节点 撤回
提交后可以撤回 快速前进
跳过某些节点 减签
审批过程中动态移除审批人 未来节点修改
流程还没走到某个节点时,就可以提前修改那个节点的审批人
其中"未来节点修改"和"打回至任意节点"是比较少见的功能,尤其适合中国企业里常见的"流程走到一半领导说换个人"、"这个申请打回到申请人重新填"等场景。
人员解析策略也很丰富:直属领导、HRBP、角色、职级、循环、业务表关联、自选、发起人、自定义外部接口。基本覆盖了中国企业组织架构的各种变体。
五、一些容易被忽略的细节
5.1 15 个自定义 JSON Converter
AntFlowCore.Base/conf/json/ 目录下藏着 15 个自定义的 System.Text.Json.JsonConverter。
为什么需要这些?因为前端传过来的数据并不总是类型一致的。布尔值有时是 true/false,有时是 0/1;字符串有时是单个值,有时是数组。与其在业务代码里到处做类型判断,不如在反序列化阶段统一处理。
这是防御性编程的正确姿势:在系统边界做校验和转换,内部代码保持干净。
5.2 View Object 扁平列表模式
BPMN 模型通常用树或图结构表示,但 AntFlowCore 在运行时用一个扁平的 BpmnConfCommonElementVo 对象列表,通过 FlowFrom / FlowTo 字段链接节点。BpmnNodeFormatService(557 行)负责将设计器的树形结构转换为这个扁平格式,后续的流转逻辑只需要在列表中按链接查找即可。
5.3 多租户内置支持
每个实体都有 TenantId 字段,MultiTenantIdHolder 通过 ThreadLocalContainer 存储当前租户信息,在 HTTP 中间件中设置。不需要在业务代码中显式传递租户 ID。
六、客观评价:优点和不足
优点
架构干净。 11 个项目的依赖方向清晰,没有循环依赖,没有跨层调用。这种分层不是一开始就规划好的,而是随着功能增加不断重构出来的。
扩展性设计到位。 新增表单类型、条件判断器、通知渠道、流程操作,都是"实现接口 + 注册 DI"两步完成,不需要修改已有代码。开闭原则不是口号,是落地的设计。
技术选型有想法。 Natasha 动态工厂和 Rougamo.Fody AOP 都不是 .NET 生态的主流选择,但在这个场景下用得很恰当。说明作者不是在堆技术,而是在解决实际问题时选择了合适的工具。
对中国企业场景的理解深刻。 顺序会签、加签、打回至任意节点、未来节点修改——这些功能不是从 BPMN 规范翻译过来的,而是从钉钉的实际产品需求反推出来的。
不足
没有自动化测试。 整个解决方案找不到一个测试项目。对于一个工作流引擎来说,这是硬伤。19 种流程操作、22 个事件钩子、9 个生命周期方法——没有测试覆盖,重构和升级时谁来保证正确性?
文档多但缺少入门指南。 项目里有 15+ 份技术文档,内容很详细,但缺少"从零到一跑起来"的入门教程。对于想快速上手的人来说,学习曲线偏陡。
数据库脚本齐全但缺乏迁移工具。 提供了 MySQL、Oracle、PostgreSQL、SQL Server 的初始化脚本,但没有集成数据库版本管理工具。生产环境升级时的数据库变更需要手动处理。
示例代码可以更多。 只有一个 ThirdPartyAccountApplyFlowService 示例,对于一个面向开发者的引擎来说,一两个示例不太够。
七、Java 版 vs .NET 版:不是翻译,是重新解题
很多人会问:.NET 版跟 Java 版有什么区别?只是语法翻译吗?
不是。 两者的核心业务流程和前端设计器是相同的,但实现方式上有明显差异:
Java 版用 Spring 的依赖注入和 AOP,.NET 版用原生的 DI + Rougamo.Fody Java 版用 MyBatis/JPA,.NET 版用 FreeSql .NET 版独有的 Natasha 动态工厂是 Java 版没有的设计 .NET 版直接面向 .NET 10 / C# 13,用到了最新语言特性
换句话说,.NET 版不是对 Java 版的逐行翻译,而是用 .NET 生态的工具链重新实现了同一套业务能力。 这比直接移植要有价值得多——因为它真正融入了 .NET 的技术栈,开发者可以用 .NET 的方式去理解和扩展它。
八、适合什么场景?能学到什么?
适用场景
如果你的项目需要审批流功能,AntFlowCore 适合以下场景:
企业内部系统
OA、ERP、CRM 中需要审批流程的模块 低代码/无代码平台
作为底层工作流引擎嵌入 需要钉钉风格审批流的项目
顺序会签、加签、转办等中国式审批 多租户 SaaS 产品
内置多租户支持
不太适合的场景:
需要严格 BPMN 2.0 兼容的项目(这是类 BPMN,不是标准实现) 需要与 Java 生态微服务深度集成的场景 对自动化测试有强制要求的企业(需要自己补测试)
学习价值
不管你是否会在项目中使用它,AntFlowCore 的源码都值得翻一翻:
Natasha 动态代码生成
一个"让编译器替你写样板代码"的完整实现 策略模式的大规模应用
19 种操作的统一入口和分发机制 AOP 事务的 IL 编织实现
比动态代理性能更好的 .NET AOP 方案 BPMN 引擎的实现思路
流程定义、执行上下文、任务流转 自定义 JSON Converter 的防御性设计
在系统边界处理类型不一致
写在最后
开源社区从来不缺工作流引擎,但缺的是认真对待工程细节的引擎。
AntFlowCore 最值得认可的不是它实现了多少功能,而是它在每个技术决策上展现出的克制和品味:用 Natasha 消除样板代码而不是堆砌 switch-case,用 AOP 管理事务而不是在每个方法里写 try-catch,用 15 个 JSON Converter 在前端边界做防御而不是在业务逻辑里到处判断类型。
移植不是翻译,而是用新语言的工具重新解题。 这一点,AntFlowCore 做到了。
当然,没有自动化测试是实实在在的短板。但考虑到这个项目 2024 年 11 月才起步,作为一个个人主导的开源项目,能做到现在的完成度,已经值得关注。
如果你正在为 .NET 项目选型工作流引擎,或者单纯想看看一个认真做的 .NET 开源项目长什么样,不妨去翻翻它的源码。
项目地址:
https://github.com/mrtylerzhou/AntFlow.net