昨晚出现了一个线上问题,对我自己是一次很扎实的提醒:很多“看起来是配置差异”的问题,深层其实是接口契约没有真正冻结。

为了方便理解,本文把具体系统抽象成三层:适配层(jzadapter)、平台层(wwrpabase)和开放平台业务层。事情的起点很直接:这三层刚一起发版到生产。

发完不久,接口返回里 robot_idgroup_idcontact_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_uidtarget_sn,靠字段名猜语义注定会漏。
  • 字段方向不稳定。同一个字段在入参是“外部 -> 内部”,在出参是“内部 -> 外部”,自动机制很难判断上下文。
  • 序列化字符串替换不可靠。异步结果里很多是 JSON 字符串,盲替会把非 ID 数字一起改掉。
  • 排障代价高。隐式转换一旦出错,代码里找不到明确调用点,问题会变成“看不见的 bug”。

后来方案被收敛成两种:

  1. 拦截器 + 中心注册表:集中声明哪些接口字段要转换。
  2. 显式逐点改造 + 极简 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_total
    • id_format_mode_total
    • id_codec_error_total
    • id_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 一起工作的截图。

和 AI 工作截图 2

和 AI 工作截图 1