访客邀请上线记:从星链直连到持久化异步任务链路
导读
访客邀请功能要求”提交即生效”:免审批、访客自动进通行用户、权限自动下发到魔点门禁。真正的工作量不在接口本身,而在把”审批通过后直连星链下发权限”改成一条可重试、可追踪、可人工兜底的持久化异步任务链路。本篇记录接口契约、合并冲突的坑,以及一次状态机走完一整圈的全链路模拟联调。
前言
9 月 3 日的主题是访客邀请。被访人在访客登记页点”访客邀请”,填访客姓名、手机号、证件号、拜访时间和车牌,提交即生效——没有审批流。后端落在 through-service,难点不在表单字段,而在权限下发:访客的通行权限要推到魔点/星链平台,这一步直接决定访客能不能刷开门禁。
一、接口契约:免审批,提交即生效
POST /through/visitorAppointment/invite,几个值得注意的契约设计:
- 服务端强制覆盖:
source=4(访客邀请)、staffId/userId=当前登录人——前端传了也会被覆盖,身份类字段永远不信客户端; - 手机号服务端强制非空,证件号码按身份证校验码规则校验;
- 提交即生效:后端自动审核通过,访客自动进通行用户、车辆自动建档,列表立即可见(来源列显示”访客邀请”)。
二、为什么把直连改成持久化异步任务
原有设计是审批通过后直接同步调星链接口下发权限。这有两个老毛病:请求线程被外部平台拖住,失败了就丢了——权限没下发,访客站在门禁前干瞪眼。
改造后的链路:

图 1:直连是”请求线程扛一切”,异步链路是”落库即返回,调度器兜底”
设计的几个关键点:INSERT IGNORE + 唯一索引保证同一预约不会重复入队;SUCCESS 是终态,人工重试接口只接受 FAILED_FINAL / UNKNOWN_MANUAL;退避按 1/5/15/30/60 分钟拉开,不给下游平台添堵。语义变化也需求方拍板记录在案:同一预约二次审批不再重下发——只影响氚云改期场景,访客邀请一次性提交无此问题。
三、合并的坑:自动合并成功 ≠ 语义正确
本次合并 访客邀请 分支与 adapter 分支,冲突只有 2 个文件,但其中一个埋了雷:adapter 侧删除 UserClient 声明的 hunk 被 git 自动采纳,而我方的部门回填代码还在调用它——自动合并出了编译必炸的结果。

图 2:过滤器拒绝路径取并集没问题;删声明的 hunk 必须全文件核对引用
教训一句话:跨分支删声明的合并,必须全文件核对引用。git 只保证文本不冲突,不保证语义正确。另一个文件 InternalApiDenyFilter 就是正面例子——双方新增的拒绝路径取并集,各四条两条,相安无事。
四、全链路模拟:状态机走完一整圈
联调分两层。层 1 只起主工程,验证合并不破坏业务:提交 invite 后主表 source=4、modian_permission_task 恰一条 PENDING 且与提交同秒同事务、门岗码正常、enabled=false 时 dispatch 原样返回——全过。
层 2 起 bridge + mock 星链(node 起在 19099,恒回 code=0,请求落日志当断言点),然后故意让任务失败一次:
| 步骤 | 结果 |
|---|---|
| 预约园区改成未映射 → dispatch | FAILED_FINAL + PARK_NOT_MAPPED ✅(顺带证明 JWT 换 token/验签链路通) |
| 人工重试接口 | 任务回 PENDING ✅ |
| 改成已映射园区 → dispatch | SUCCESS,provider_record_id 回填 mock 返回值 ✅ |
状态机走完 PENDING → FAILED_FINAL →(人工重试)→ PENDING → SUCCESS 一整圈。失败路径是被真实走过的,不是只看代码觉得它会工作——这是这次联调最值得保留的动作。
五、经验总结
- 身份类字段(操作人、来源)永远服务端覆盖,不信客户端;
- 外部平台调用默认会挂:持久化任务 + 退避 + 人工兜底是标配,不是过度设计;
- 终态设计要回答”谁还能动它”:SUCCESS 不收人工重试,就是幂等的最后一道闸;
- git 自动合并成功后,对删声明的 hunk 做全文件引用核对;
- 联调主动制造一次失败,让状态机的每一段都被真实踩过。
结语
表单接口半天就能写完,让访客”提交即生效、权限必达”的是背后那条异步任务链路。把失败当成常态设计进去,状态机的每个格子都真实走一遍——上线那晚才能睡得着。