昨晚出现了一个线上问题,对我自己是一次很扎实的提醒:很多“看起来是配置差异”的问题,深层其实是接口契约没有真正冻结。
为了方便理解,本文把具体系统抽象成三层:适配层(jzadapter)、平台层(wwrpabase)和开放平台业务层。事情的起点很直接:这三层刚一起发版到生产。
发完不久,接口返回里 robot_id、group_id、contact_id 的形态就出现了明显差异:开发环境看起来是包装过的 ID,生产环境回的是底层纯数字 SN。这个现象如果只停在列表接口,还不算最糟;真正麻烦的是,客户端会拿这些 ID 去调其它接口,链路一长就会出现语义割裂。
这个问题是怎么被我钉死的
我第一步没有去改代码,先把现场压实。
同一个 key 在生产和开发的配置如下:
1 | config_tool -f GetStr -k system_state |
生产:
1 | {"base_channel":3,"default_dev_type":1} |
开发:
1 | {"base_channel":1,"bns_key":"...","default_dev_type":1} |
这时候我和 AI 的分工很清楚:我负责提出“该验证什么”,AI 负责帮我快速扫代码事实。
我们很快确认了几件关键事实:
system_state是全局配置,不带 corp 维度。base_channel=3走 WEG 路径时,转换器本身是直通返回。base_channel=1走 BNS 路径时,才会使用bns_key做 AES 编解码。
换句话说,生产和开发 robot_sn 不一样,根因并不在某张业务表,核心是“编解码路径”不同。
最关键的判断:生产不能改 base_channel
这一步我问自己说:
如果根因是 base_channel,那是不是把生产也改成 1 就结束了?
很显然,我们是不能这么做滴。
base_channel 在适配层是全局态,它影响的不止一个接口,也不止开放平台业务层。这个开关一动,潜在影响面会覆盖整条底层编解码链路。所以:生产不能改这个配置。
到这里,问题就变成了一个架构题:
在不动生产 base_channel 的前提下,怎么把开放平台对外 ID 语义稳定下来。
架构抉择:我为什么选了平台层收口
当时桌面上有几条路。
第一条路是在开放平台业务层自己做一层映射或 AES。实现速度看起来快,长期风险很高,后面会形成双真相源。
第二条路是开放平台业务层直接连适配层。能解决一部分问题,分层会开始走样,后面回调链路和其它服务复用也会更难。
第三条路是“开放平台业务层 -> 平台层”,把 ID 编解码契约收口到平台层,业务层只保留一个统一入口。这个方案符合我们原本的依赖方向,也更适合把策略做成系统能力。
我最后拍板第三条。
同时我和 AI 一起把另一个关键点定了:
HLOPEN_USE_LEGACY_ID 这个租户特性开关,只能在平台层的 ID 格式化能力内部判定,开放平台业务层不直接读 feature。
这个决定看起来像细节,实际是防止策略漂移的核心。只要判定点分散,后面同步接口、异步结果、事件回调就会慢慢长成三套语义。
我和 AI 在“自动化”上争论过一轮
过程中我追问了一个问题:
既然定了统一 ID 编排层,那是不是应该把所有同步响应、异步结果、回调回传都自动化接进去?
AI 一开始给了一个方向:
用反射自动扫描字段,或者通过拦截器 + 注册表,让转换“自动发生”。
但我很快把这个方向否掉了。原因很现实:
- 字段语义不可穷举。今天叫
robot_id,明天可能叫sender_uid、target_sn,靠字段名猜语义注定会漏。 - 字段方向不稳定。同一个字段在入参是“外部 -> 内部”,在出参是“内部 -> 外部”,自动机制很难判断上下文。
- 序列化字符串替换不可靠。异步结果里很多是 JSON 字符串,盲替会把非 ID 数字一起改掉。
- 排障代价高。隐式转换一旦出错,代码里找不到明确调用点,问题会变成“看不见的 bug”。
后来方案被收敛成两种:
- 拦截器 + 中心注册表:集中声明哪些接口字段要转换。
- 显式逐点改造 + 极简 helper:在赋值现场直接调用
idcodec。
我最终坚持第二种。核心不是“省体力”,而是降低心智负担。
在我看来,中心注册表 + 拦截器是典型过度设计:每次加新功能或改接口,都要回头检查注册中心有没有补齐,维护路径会被人为拉长。 在这个地方,我的原则很简单:心智负担一定要小,代码大道至简,能在现场显式完成的,就不要绕到隐式机制里。
显式改造的好处是,代码天然可审计、可 grep、可 review。比如从:
1 | rsp.RobotId = robotSn |
变成:
1 | rsp.RobotId = idcodec.FormatRobot(ctx, corpID, appID, robotSn) |
每个转换点都写在现场,不需要额外心智去记“还要不要去某个中心注册表补一行”。对后续维护者来说,这比“聪明的自动化”更可靠。
我怎么把“讨论”压成可执行工程
我这次最大的体感是:只靠聊天很难收口复杂改造。
所以我直接用三层文档把它钉住:
spec.md:定义必须满足的约束和验收标准。plan.md:把状态变换拆成 Phase,并写清不变量。tasks.md:把每一步要改的文件、命令、门禁检查全部落成执行清单。
这套方法帮我解决了两个问题。
一个问题是“记忆漂移”。这类跨服务改造会持续几天,换会话窗口、换上下文都很常见。没有冻结文档,团队和 AI 都会慢慢偏航。
另一个问题是“做完以后怎么证明做完”。我们把 grep 门禁、编译门禁、结构门禁都写进了 tasks.md,执行结果能直接对照。
真正落代码时,我和 AI 的协作方式
代码层面改动很大,覆盖适配层、平台层、开放平台业务层三条线。
核心动作包括:
- 在平台层协议增加批量
resolve/format能力。 - 在适配层协议补齐反向能力,保证包装 ID 入参双读可用。
- 在平台层新增统一
open_id_codec模块,收口resolve/format逻辑和特性开关判定。 - 在开放平台业务层新增
idcodec模块,把同步接口、异步任务、回调链路统一接入。
迁移量真正大的地方在业务层异步任务和 cb_* 回调链。这个阶段 AI 的价值非常高:它能快速定位调用点、批量替换、补 helper、跑编译。
同时我也很清楚,关键裁决必须是我来做:
- 哪些旧路径可以删,哪些必须暂时保留。
- 错误语义怎么映射,才能跟现有对外错误码兼容。
- 哪些变更是“功能收口”,哪些其实是在偷改业务语义。
我在这个阶段一直坚持一件事:AI 可以快,但不能越权替我做架构判断。
代码完成后,我做了哪些“看起来慢”的事
代码过编译只是中段,不是终点。
这次我把后置治理也做完了:
- 补全关键函数注释,尤其是业务层
idcodec和平台层open_id_codec。 - 在平台层补 Prometheus 指标:
id_resolve_hit_mode_totalid_format_mode_totalid_codec_error_totalid_codec_latency
- 更新
constitution.md,把“统一 ID 编排层”的硬约束写进宪法文档。 - 更新 roadmap/progress,确保后续会话知道当前系统事实。
- 把业务层 README 从“改造记录”重写成“统一 ID 编排层说明”。
这些动作短期看不性感,长期会省掉大量返工。后来的人读文档就能知道:这里的策略边界是什么,为什么这么定,新增接口该走哪条路。
我在生产项目里的 AI 协作分工
这是我平时与 AI 协作的一种方式。AI 最稳的角色有三个:
- 代码考古与事实扫描器
- 批量迁移执行器
- 文档草案生成器
我自己主要负责三件事:
- 边界和风险判断
- ADR 级别的方案取舍
- 验收口径和最终签字
AI 负责提速,我负责边界、取舍和验收。
写在最后
这次从“生产返回纯数字 SN”到“统一 ID 编排层”落地,对我来说不是一次普通修 bug。
它更像一次完整的工程演练:
现场定位、根因确认、边界约束、架构裁决、Spec/Plan/Tasks、跨服务改造、门禁验收、文档收口,整个链条都跑了一遍。
如果你也在做类似的跨服务治理改造,我自己的经验是:
先把系统事实钉死,再把方案写成可执行清单,最后才是代码。这个顺序会慢一点,心里会稳很多。
当然,目前还没做完整测试,这篇主要是一次过程分享和总结。整个过程从问题出现、分析定位、方案收敛到代码改完,实际投入大约三个小时。
这也是 AI 协作最神奇的地方:交付速度是真的上来了。
我想说的是,如果完全手工做这次改造,到底是一天还是三天,我现在真不敢拍胸口。
因为我已经很久没有在没有 AI 的情况下,从头到尾手撸一个完整功能了。
印象里从 24 年开始,我脑子里装的基本就是系统架构边界、微服务拆分和高并发链路这些问题。
你要是突然问我“数组和切片的区别”,我想我的脑袋可能会短路,因为它早就不在我的注意力中心了。
我现在还在上海,明天要继续进行现场适配/交付工作,累瘫了…
附:和 AI 工作截图
分享两张和 AI 一起工作的截图。

