四条线与一条密文:访客邀请全链路复盘与审查捡出的存量 bug
导读
把访客邀请的四条线(提交生效、出码、权限下发、通行回流)完整摊开,回答”前端拿到二维码时权限到底下发了没有”;同日审查同事的短信 mock 提交,捡出一个更让人意外的存量 bug——真实审批短信的收件手机号,可能从来都是一串加密后的乱码。
前言
9 月 3 日访客邀请的异步链路联调跑通后,9 月 4 日做两件事:把整个功能的全链路文档补齐(含外部平台交互),以及审查短信 mock 提交。文档是给自己和后来人看的”作战地图”,审查则意外地让一条潜伏已久的存量 bug 浮出水面。
一、四条线,互不阻塞
访客邀请看起来是”一个按钮”,实际是四条独立的线:
这个结构里最重要的认知是:前端拿到二维码时,权限下发可能还没发生;下发失败也不影响业务已生效。三条线解耦意味着每条线的失败模式可以独立设计——下发挂了调度器会退避重试,出码只依赖主表数据,提交事务里只做最快的落库。
图 1:四条线互不阻塞——二维码秒出,权限下发由调度器兜底
二、出码线:单访客单码,以及一个静默的域名坑
出码接口 getVisitorQrCodes 有两道闸门:仅 source=4(访客邀请),且仅邀约人本人(userId 比对),其余一律返回空数组。恒返回一条门岗码,qrContent 拼的是核验 H5 的地址,参数带证件号和手机号明文——这是既有核验页的契约,后端不出图,图片由前端渲染。
这里埋着一个值得警惕的坑:H5 域名按环境配置,测试环境漏配会静默生成指向生产地址的二维码——扫出来能开,核验的是生产数据。配置类错误的可怕之处就在于它不报错。
三、下发线状态机:每个格子都要有出路
调度器每分钟领到期任务(上限 20 条),状态机比前一天记录的更完整:
几个设计细节值得单独看:领取用乐观 UPDATE(抢不到就跳过,天然防多实例重复领取);PROCESSING 超过 10 分钟自动转 UNKNOWN_MANUAL(防执行中僵死);人工重试只接受 FAILED_FINAL/UNKNOWN_MANUAL——SUCCESS 是终态,因为星链侧重复授权有副作用,”不确定结果”的任务不允许自动重复下发。幂等靠的是 appointment_id 唯一索引 + INSERT IGNORE,代价(已接受并记录):同一预约二次审批不重下发。
四、审查捡出的存量 bug:短信发给了”密文”
同事提交了短信 mock(测试全绿:through 53 / integrate-iot 5),审查 mock 的用法时发现它调用了 getVisitorPhoneStr()——一个解密 getter。顺着这条线往回看老代码,看到了不该看的东西:
成因链条清晰:入口加密 → 后续所有读取都是密文 → 短信收件号变成 f1875d04... 这样的乱码 → 发送异常被 try-catch 吞掉,只在日志留一句”短信发送失败”。结论很扎心:所有来源的访客审批短信大概率从未真实发出过。

图 2:审查 mock 的新代码,反而照出了老代码的存量 bug
但审查结论写的是”先记录、暂不改“,改前先过业务确认:问一句”访客审批通过短信有人收到过吗”——如果从来没人收到,bug 实锤且没人依赖;如果有人说收到过,说明链路另有蹊跷,先查清再动。修一个”看起来明显的 bug”之前,先确认它是不是真的没人依赖——这是审查纪律里最容易被忽略的一条。
五、经验总结
- 全链路文档的价值在于把”线的边界”画出来:哪条同步、哪条异步、哪条默认关,谁阻塞谁一目了然;
- 状态机设计检查表:每个状态都要有出路,终态要回答”谁还能动它”,超时僵死要有自动出口;
- 配置类错误不报错——测试环境漏配域名会静默指向生产,这类坑只能靠清单和 review 拦;
- 审查别人代码时顺手对照”新老同款逻辑”——mock 的正确用法照出了老代码的密文 bug;
- 发现存量 bug 先记录、找业务确认影响面,再动手——改动一个没人依赖的错误行为也可能制造意外。
结语
这一天没有写多少新代码,但产出的两样东西都比代码长寿:一张四条线的作战地图,和一份带确认动作的待改清单。文档和审查都是慢功夫,省的是将来某个深夜的排查时间。