澳五机器人 Mud.Feishu 3.0 把飞书 .NET SDK 改造为支持 Native AOT 的全过程
讲的是 Mud.Feishu 3.0 把飞书 .NET SDK 改造为支持 Native AOT 的全过程。核心挑战和方案非常清晰。
---
为什么飞书 SDK 改造 AOT 难
AOT 对应用友好,对类库不友好。两条硬约束[citation:web:70.3]:
| 约束 | 触发警告 | 含义 |
|:----|:---------|:----|
| 运行期不能动态生成代码 | IL3050(RequiresDynamicCode) | System.Text.Json 默认用反射构建序列化元数据 |
| 裁剪后不能引用被裁掉的成员 | IL2026(RequiresUnreferencedCode) | 裁剪器不知道哪些 DTO 会被反射用到 |
飞书 SDK 恰好是这两条的集中地——32 个业务模块、200+ 接口、3000+ 个 DTO,全都靠 System.Text.Json
的反射默认实现序列化。在 JIT 模式下毫无问题,一进 Native AOT,裁剪器就把这些反射调用全标记为警告,严
重的运行时直接抛 NotSupportedException[citation:web:70.3]。
---
改造方案:六项架构决策
决策一:全链路源生成,全局管控
AOT 开关收敛到根目录一个 Directory.Build.props,所有工程统一继承[citation:web:70.3]:
<IsAotCompatible>true</IsAotCompatible>
<EnableAotAnalyzer>true</EnableAotAnalyzer>
<TrimMode>full</TrimMode>
AOT 能力只在 net8.0+ 生效,netstandard2.0 / net6.0 走反射路径。一套代码,条件编译隔离。
决策二:每个模块自动生成 JsonSerializable 上下文
不是手动写——用脚手架(JsonContextScaffolder)为每个业务模块自动生成源生成上下文[citation:web:70.3]:
当前规模:
· 33 个 DataModels 模块 Context
· 1 个响应包装 FeishuApiResultJsonContext
· 10 个 EventCallback 域 Context
· 累计 2915 个 [JsonSerializable] 登记
· 覆盖 3000+ 个类型
决策三:一条可合并、幂等的解析器链
生成的 Context 是散的,需要一个中枢串起来——FeishuJsonDefaults.ConfigureUserResolver[citation:web:70.3]:
链首:源生成 Context(AOT 安全路径)
↓
链中:用户自定义 Context(通过 ConfigureUserResolver 注入)
↓
链尾:反射兜底(确保跨 TFM 行为一致)
链尾的反射兜底是关键取舍:真 AOT(PublishAot)下本来也序列化不了自定义类型,所以兜底不削弱 AOT 保证;但没有兜底的话 net8+ 会抛异常,而 net6 却正常——同一个调用两种行为,不能接受。
决策四:模块自治装配
33 个 Context 没道理让用户手写。每个模块提供 Configure*Resolver() 入口,SDK 启动时自动接线,业务开发者全程不用手写一行 Context[citation:web:70.3]。
决策五:AOT 安全的序列化入口
JsonSerializer.Serialize<T>(value, options) 带着反射告警标注,AOT 下不能直接调。提供一个语义等价的替代入口 FeishuJsonAot——先用 GetTypeInfo 解析出 JsonTypeInfo,再走非泛型重载,零反射[citation:web:70.3]。
决策六:配置绑定也走源生成
ConfigurationBinder.Bind 的反射实现同样触发 IL2026/IL3050,也要改成源生成。
---
门禁:防回归
架构做到 AOT 友好还不够,还得用工程手段防止回归[citation:web:70.3]:
严格模式门禁
AotStrictMode=true 把 IL2026/IL3050 等 12 个告警全部升级为编译错误。
--no-incremental 封死假绿
这里藏着一个工程坑:MSBuild 的增量编译只比较时间戳,紧跟在上一步构建之后做严格模式构建,编译器会判定"已是最新"直接跳过,输出恒 [OK]。加上 --no-incremental 之后,立刻暴露了 Mud.Feishu.WebSocket 的 4 条违规[citation:web:70.3]。
真机 AOT 验证工程
单独建一个 Demos/Mud.Feishu.AotVerification,用 PublishAot=true 做 win-x64 / linux-x64 双 RID 发布,跑 11 项冒烟测试。关键 4 条链路必须全部跑通[citation:web:70.3]:
✅ 源生成 JSON
✅ protobuf
✅ WebSocket 协议
✅ EventCallback 事件
---
收益与你的技术背景
| 指标 | 改前(JIT) | 改后(AOT) |
|:----|:----------:|:----------:|
| 容器冷启动 | ~800ms | ~50ms |
| 常驻内存 | ~80MB | ~20MB |
| 发布产物 | 多文件 + 运行时 | 4MB 单文件 |
这和你之前在 FHE 线程池里做的"用自研池换掉 OpenMP"是同一个工程哲学——把运行期的灵活性前置为编译期的确定性。只不过 FHE 线程池前置的是线程调度决策,飞书 SDK 前置的是序列化元数据决策。两者的共同点是:在编译期确定的事情,运行期就不用猜了。
