UserAppBinding 一对一 → 一对多拆表
爸爸提的 bug:用户在两个 app 都登录,但只有最后一个能 push。
问题复现
⚠ 当前 binding 是 openid PK,后写覆盖前者
T0
用户在 App A 扫码登录
binding = A
T1
A 给用户推消息
✓ 200 ok
T2
用户在 App B 扫码登录
binding = B(覆盖)
T3
A 想给同一用户再推
403 not_bound_to_app
用户**实际同时是 A 和 B 的客户**(都登录过),但 binding 模型只能记一个 → A 失去推送权, 虽然用户从未离开 A 站。
根因 — 一个表干两件事
① fanout 路由(被动消息)
问题
用户发"你好" → 网关该转给谁?
合理模型
一对一(只能转给一家)
✓ 当前 binding 满足
② 主动 push 越权
问题
A 能给"也是 A 客户"的用户推吗?
合理模型
一对多(用户可属于多 app)
✗ 一对一模型把多 app 客户压成一个
两个职责对模型的要求**直接冲突**——塞一张表必然有一个出错。当前出错的是 push 越权。
解法 — 拆成两表,各司其职
UserAppBinding
保留 · 不动
openid
PK
appName
String
boundAt
DateTime
一对一,**最后扫者赢**。
职责
:决定被动消息 fanout 给谁。
不变
:fanout 路由继续读这表,行为完全不变。
UserAppGrant
新增
openid
String
appName
String
grantedAt
DateTime
source
oauth | qrcode | subscribe | admin
@@id
[openid, appName]
**一对多**,只 add 不删(除非 admin revoke)。
职责
:授权矩阵——这用户给过哪些 app 接触权。
写入
:OAuth/扫码/subscribe 任一动作 → INSERT IGNORE。
两表如何协作 · 数据流
WRITES
OAuth callback
用户在 X 站扫码登录
SCAN/subscribe 事件
扫永久码 / 关注
admin Users tab
手动改 binding
UserAppBinding
UPSERT(openid)
最后写者赢 · 一对一
UserAppGrant
INSERT IGNORE
只 add 不删 · 一对多
READS
fanout 路由
被动消息派给谁
/internal/wx-push
主动推越权检查
/internal/wx-userinfo
查身份越权检查
UserAppBinding(被动路由,一对一)
UserAppGrant(主动越权,一对多)
3 个 internal API 越权改向
POST /internal/wx-push
UserAppBinding
→
UserAppGrant
调用方在 grant 表 → 允许 push。admin 旁路保留。
GET /internal/wx-userinfo
UserAppBinding
→
UserAppGrant
同上,改查 grant 表防 PII 探测仍然有效。
fanout(被动消息路由)
UserAppBinding(不变)
仍然一对一,最新登录的 app 收消息。逻辑零改动。
admin Users tab
两表都展示 + 可改
显示 binding 主属 app + grant 列表(可 revoke)。
改造前后对照
场景
现在
改后
用户在 A 登录,A 推消息
200
推送成功
200
推送成功
用户又去 B 登录,B 推消息
200
推送成功
200
推送成功
A 再推同一用户(B 登录后)
403
not_bound_to_app
200
推送成功
用户发消息回来
→ B(最新登录)
→ B(不变)
从未登录过的业务方推该用户
403
403
仍越权
用户取消关注公众号
微信侧返 errcode 43004
微信侧返 errcode 43004(不变)
迁移成本
代码改动
schema 加 UserAppGrant 表 + db push
OAuth callback / SCAN / subscribe 三处加 grant INSERT IGNORE
wx-push & wx-userinfo 越权检查改查 grant
admin Users tab 显示 grant 列表 + revoke 按钮
data migration:把现有 binding 行回填进 grant
不改的
fanout 路由代码(读 UserAppBinding 不变)
HMAC 签名/token/qrcode/menu/config 接口
业务方接入文档(只新增"用户可属多 app"说明)
所有对外 URL/响应结构
派 CC 实施?
一次任务搞定:加表 / 三处写入 / 两接口越权切换 / 数据迁移 / Users tab 加 grant 列表 / selftest。
~30 min