fix(auth): 微信 unionid 账号打通逻辑修复(老账号自动关联 + 查找顺序优化 + 冲突保护) #1738

Merged
xiaoxia merged 3 commits from fix/wechat-unionid-account-linking into main 2026-09-06 14:48:51 +08:00
Owner

背景

小程序刚绑定到微信开放平台(企业主体)。绑定前小程序登录已创建过无 unionid 的老账号;绑定后这些老账号无法与 Web 扫码(同一微信用户、同 unionid)账号自动关联,导致同一人两端落到不同 user_id。

根因

WechatSyncUseCase.execute 存在两个缺陷:

  1. openid 命中老账号后不补写 unionid:老账号 wechat_unionid 为空,即使本次请求带了 unionid 也不会回填,之后每次登录都只走 openid 分支,永远关联不上。
  2. 查找顺序 openid 优先:跨端场景(Web 端已有 unionid 账号,小程序端是新 openid)虽然能走 unionid 分支绑定 openid,但 openid 优先会让「同 openid 不同账号态」的处理顺序不清晰;且 unionid/openid 分属两个账号时无任何保护,直接覆盖 openid 会把两个账号错误串联。

修改点(packages/application/auth/wechat_sync_use_case.py)

  1. 查找顺序改为先 unionid 后 openid:unionid 是跨应用识别同一微信用户的稳定标识,优先匹配。
  2. openid 命中老账号 + 请求带 unionid + 老账号 unionid 为空 → 补写 unionid 并保存(绑定开放平台前存量账号自动关联)。
  3. unionid 命中但 openid 不同 → 把新 openid 绑定到该用户(沿用原跨端绑定语义,仅当该 openid 不属于任何其他账号时)。
  4. 冲突去重保护(三种冲突均拒绝写入并返回 conflict 错误,交数据修复人工合并,不静默串联账号):
    • unionid 与 openid 命中两个不同账号;
    • 补写 unionid 时发现该 unionid 已被其他账号占用;
    • unionid 命中账号,但请求 openid 已属于第三个账号(不抢占 openid)。
  5. 同一用户双命中时不产生多余 save。

测试

  • 更新 tests/unit/test_wechat_sync_use_case.py:20 个用例,覆盖 openid 登录、老账号补写 unionid、unionid 跨端绑定新 openid、三类冲突拒绝、新用户创建、用户名冲突、session 保存等。
  • 本地 20/20 通过。

配套数据修复

staging 存量重复账号(应赐恩 Web 账号 30c14e1… 与小程序账号 5e20fdb7…)将通过数据修复脚本合并,与本 PR 独立。

⚠️ 仅部署 staging 验证,不动生产。

## 背景 小程序刚绑定到微信开放平台(企业主体)。绑定前小程序登录已创建过**无 unionid 的老账号**;绑定后这些老账号无法与 Web 扫码(同一微信用户、同 unionid)账号自动关联,导致同一人两端落到不同 user_id。 ## 根因 `WechatSyncUseCase.execute` 存在两个缺陷: 1. **openid 命中老账号后不补写 unionid**:老账号 `wechat_unionid` 为空,即使本次请求带了 unionid 也不会回填,之后每次登录都只走 openid 分支,永远关联不上。 2. **查找顺序 openid 优先**:跨端场景(Web 端已有 unionid 账号,小程序端是新 openid)虽然能走 unionid 分支绑定 openid,但 openid 优先会让「同 openid 不同账号态」的处理顺序不清晰;且 unionid/openid 分属两个账号时无任何保护,直接覆盖 openid 会把两个账号错误串联。 ## 修改点(packages/application/auth/wechat_sync_use_case.py) 1. **查找顺序改为先 unionid 后 openid**:unionid 是跨应用识别同一微信用户的稳定标识,优先匹配。 2. **openid 命中老账号 + 请求带 unionid + 老账号 unionid 为空 → 补写 unionid 并保存**(绑定开放平台前存量账号自动关联)。 3. **unionid 命中但 openid 不同 → 把新 openid 绑定到该用户**(沿用原跨端绑定语义,仅当该 openid 不属于任何其他账号时)。 4. **冲突去重保护**(三种冲突均拒绝写入并返回 conflict 错误,交数据修复人工合并,不静默串联账号): - unionid 与 openid 命中两个不同账号; - 补写 unionid 时发现该 unionid 已被其他账号占用; - unionid 命中账号,但请求 openid 已属于第三个账号(不抢占 openid)。 5. 同一用户双命中时不产生多余 save。 ## 测试 - 更新 `tests/unit/test_wechat_sync_use_case.py`:20 个用例,覆盖 openid 登录、**老账号补写 unionid**、unionid 跨端绑定新 openid、三类冲突拒绝、新用户创建、用户名冲突、session 保存等。 - 本地 20/20 通过。 ## 配套数据修复 staging 存量重复账号(应赐恩 Web 账号 30c14e1… 与小程序账号 5e20fdb7…)将通过数据修复脚本合并,与本 PR 独立。 ⚠️ 仅部署 staging 验证,不动生产。
xiaoxia added 2 commits 2026-09-06 14:32:46 +08:00
test(auth): 补充unionid补写/跨端绑定/冲突拒绝场景测试
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 4s
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 37s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 36s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m6s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m46s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 1m49s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 1m50s
CI/CD Pipeline / Validate - Style (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Security (pull_request) Has been cancelled
CI/CD Pipeline / Build Production API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been cancelled
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
AI Code Review / AI Code Review (pull_request) Has been cancelled
PR Automation / Auto Approve on CI Green (pull_request) Has been cancelled
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been cancelled
0554b4b1dd

🚀 预览环境已部署

项目 详情
PR号 #1738
预览链接 https://pr-1738.preview.xiaoxiajianji.com
API环境 staging

💡 预览环境使用 staging API 数据,请勿在预览环境中操作重要数据。

🔄 每次提交新代码后预览环境会自动更新。

🗑️ PR 关闭或合并后,预览环境会自动清理。

🚀 **预览环境已部署** | 项目 | 详情 | |------|------| | PR号 | #1738 | | 预览链接 | [https://pr-1738.preview.xiaoxiajianji.com](https://pr-1738.preview.xiaoxiajianji.com) | | API环境 | staging | > 💡 预览环境使用 staging API 数据,请勿在预览环境中操作重要数据。 > > 🔄 每次提交新代码后预览环境会自动更新。 > > 🗑️ PR 关闭或合并后,预览环境会自动清理。
auto-approve-bot added 1 commit 2026-09-06 14:35:47 +08:00
style: auto-format with black + isort + prettier [skip ci-format-check]
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 6s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 6s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 30s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 29s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 55s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 1m35s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 1m37s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m38s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 2m4s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m49s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
AI Code Review / AI Code Review (pull_request) Successful in 3m56s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 4m18s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 1s
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 18s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 29s
711d03a409
Collaborator

【阻塞级判定】

  • 是否存在阻塞级问题:否
  • 阻塞级问题数量:0 个

📊 审查概览

  • 整体评价:通过
  • 建议级问题数量:1 个

🔴 阻塞级问题(必须修复)

💡 改进建议(不阻塞合并)

  1. [wechat_sync_use_case.py:145] 冗余的数据库查询
    • 具体内容:在 elif openid_user 分支中,再次调用 self.user_repository.find_by_wechat_unionid(request.unionid) 是冗余的。
    • 原因:代码逻辑是先执行 unionid_user = self.user_repository.find_by_wechat_unionid(request.unionid)(第117行)。只有当 unionid_user 为空时,才会进入 elif openid_user 分支。因此,在第145行再次查询 unionid 必然返回 None(除非在极短时间内发生了数据变更,但此时应依赖数据库唯一索引约束而非应用层查询)。
    • 修改建议:删除第145-147行的冲突检查逻辑,因为如果 unionid 已被占用,逻辑会在第122行的 if unionid_user and openid_user 分支被拦截并报错。

良好实践

  • 冲突处理严谨:代码非常清晰地处理了 unionid 和 openid 分属不同账号的冲突情况(第122-127行),避免了账号被错误关联的风险。
  • 存量数据兼容:第137-147行逻辑很好地支持了“仅 openid 的老账号补写 unionid”的场景,保证了业务平滑迁移。
  • 测试覆盖全面:单元测试新增了多种冲突场景和边界条件(如 TestWechatSyncConflicts),覆盖了核心业务逻辑,质量较高。

格式检查通过 | 逻辑审查通过 | 性能无明显问题


🤖 由 AI 代码审查机器人自动生成 | 2026-09-06 06:39:46 | 模型:

### 【阻塞级判定】 - 是否存在阻塞级问题:否 - 阻塞级问题数量:0 个 ### 📊 审查概览 - 整体评价:通过 - 建议级问题数量:1 个 ### 🔴 阻塞级问题(必须修复) 无 ### 💡 改进建议(不阻塞合并) 1. **[wechat_sync_use_case.py:145] 冗余的数据库查询** - 具体内容:在 `elif openid_user` 分支中,再次调用 `self.user_repository.find_by_wechat_unionid(request.unionid)` 是冗余的。 - 原因:代码逻辑是先执行 `unionid_user = self.user_repository.find_by_wechat_unionid(request.unionid)`(第117行)。只有当 `unionid_user` 为空时,才会进入 `elif openid_user` 分支。因此,在第145行再次查询 unionid 必然返回 None(除非在极短时间内发生了数据变更,但此时应依赖数据库唯一索引约束而非应用层查询)。 - 修改建议:删除第145-147行的冲突检查逻辑,因为如果 unionid 已被占用,逻辑会在第122行的 `if unionid_user and openid_user` 分支被拦截并报错。 ### ✅ 良好实践 - **冲突处理严谨**:代码非常清晰地处理了 unionid 和 openid 分属不同账号的冲突情况(第122-127行),避免了账号被错误关联的风险。 - **存量数据兼容**:第137-147行逻辑很好地支持了“仅 openid 的老账号补写 unionid”的场景,保证了业务平滑迁移。 - **测试覆盖全面**:单元测试新增了多种冲突场景和边界条件(如 `TestWechatSyncConflicts`),覆盖了核心业务逻辑,质量较高。 --- ✅ 格式检查通过 | ✅ 逻辑审查通过 | ✅ 性能无明显问题 --- <sub>🤖 由 AI 代码审查机器人自动生成 | 2026-09-06 06:39:46 | 模型: </sub> <!-- AI_CODE_REVIEW_AUTO_COMMENT -->
xiaoxia merged commit 58d57033ea into main 2026-09-06 14:48:51 +08:00

🗑️ 预览环境已清理

PR #1738 已关闭或合并,对应的预览环境已被清理。

如有需要,可以重新打开 PR 来重新生成预览环境。

🗑️ **预览环境已清理** PR #1738 已关闭或合并,对应的预览环境已被清理。 > 如有需要,可以重新打开 PR 来重新生成预览环境。
Sign in to join this conversation.