轻松聘
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

10 KiB

微信登录接入方案(P1)

状态:方案文档 · 不涉及代码改写 · 真实后端就绪后按路线图替换 mock 即可 适用范围:有职 App 微信授权登录(三分支状态机) 关联文件:src/lib/wechat-sdk.ts、src/hooks/useWechatLogin.ts、src/api/api.ts、src/types/wechat-auth.ts、components/onboarding/screens/P1Login.tsx、components/onboarding/screens/P2PhoneCode.tsx


0. 一句话结论

前端三分支状态机与页面流转已全部联通,当前唯一阻塞点是:微信原生 SDK 未接入(Expo Go 无法跑原生模块)、以及后端两个接口仍是 mock。 接入真实微信 = 替换 1 个函数 (requestWechatAuthCode) + 替换 2 个接口函数体 (fetchWechatLoginResult / bindWechatAndRegister) + 申请微信开放平台移动应用 + 用 EAS Build 生成自定义客户端。 上层 hook、页面、类型零改动。


1. 现状盘点(已实现 vs 待接入)

层 文件 现状 接入动作
类型 src/types/wechat-auth.ts ✅ 已完整定义 无需改动
状态机 src/hooks/useWechatLogin.ts ✅ 8 态判别联合,已联通 无需改动
页面流转 P1Login.tsx → P2PhoneCode.tsx → P3 ✅ 已联通 无需改动
绑定 hook src/hooks/useWechatBindPhone.ts ✅ 已联通 无需改动
登录态 src/context/AuthContext.tsx ✅ login(token, expiresAt) / isTokenValid 已就绪 无需改动
SDK 适配层 src/lib/wechat-sdk.ts ⚠️ mock:返回 mock_wx_code_* 替换 requestWechatAuthCode
后端接口 src/api/api.ts ⚠️ mock:fetchWechatLoginResult / bindWechatAndRegister 替换函数体
微信开放平台 —— ❌ 未申请移动应用 申请 AppID + Universal Links
构建产物 EAS ❌ 无自定义客户端 eas build --profile development

已验证的关键事实(直接取自源码,非推测):

  • useWechatLogin 的 start() 逻辑:isTokenValid 为真 → alreadyLoggedIn(免授权直接落地);否则 authenticating → requestWechatAuthCode() → verifying → fetchWechatLoginResult(code),按 result.status 分流 active / new / deleted。
  • new 分支:setState({ kind: 'needBindPhone', ticket: { token: result.token, openid: result.openid } }) —— 临时凭证已下发并透传。
  • P1Login.tsx 在 needBindPhone 时把 wechatTicket 写入 useOnboardingFlow 的 wechatTicket 字段并跳 P2。
  • P2PhoneCode.tsx 检测到 wechatTicket 后调用 bindWechatAndRegister({ token, openid, phone, code }) 完成注册。
  • deleted 分支:不写登录态,仅提示不可登录,无复活出口(符合账号注销即不可逆的硬约束)。

2. 接入目标与成功标准

目标:让真实微信用户能走通 new / active / deleted 三条链路,且:

  1. 用户第一次用微信登录 → 跳手机号+验证码 → 注册落地;
  2. 老用户再次微信登录 → 直接进主界面(本地 token 有效时甚至免授权);
  3. 处于注销审核期的账号 → 登录页明确提示「该账号正在申请注销中,无法登录」,无复活入口。

成功标准(验收口径):

  • requestWechatAuthCode() 真正调起微信并回传一次性 code(非 mock)。
  • 后端 /api/auth/wechat 用 code 换 openid → 判定 new/active/deleted → 首响下发可复用临时凭证。
  • bind-phone 用临时凭证(非 code)完成 openid 与手机号绑定注册。
  • iOS 能在微信授权后通过 Universal Links 正确回调 App;Android 能通过包名+签名回调。
  • 三分支在自定义客户端(非 Expo Go)上全部走通。
  • mock 辅助 MOCK_WECHAT_STATUS / setMockWechatAccountStatus 已删除,页面无任何 mock 分支残留。

3. 两个硬约束(设计已遵守,接后端不可破坏)

  1. 微信 code 一次性(5 分钟有效):用户授权后原 code 已被首次请求消耗,绑定手机号时拿不到第二个 code。因此后端必须在首次响应里下发 WechatPendingTicket(含 token + openid),前端在 P2 回传票据而非 code。
    • 类型已建模:WechatPendingTicket { token: string; openid: string }(src/types/wechat-auth.ts:29)。
  2. 注销即不可登录、不可复活:deleted 分支前端不写登录态、不提供任何复活入口;后端需保证审核期内账号返回 deleted 且拒绝任何登录/复活请求。

4. 接入路线图(按执行顺序)

Step 1 · 微信开放平台申请移动应用(前置,阻塞项)

  • 登录微信开放平台 → 创建移动应用,拿到 AppID / AppSecret。
  • iOS:登记 Universal Links(微信授权回调硬性要求)。需准备一个 HTTPS 域名 + 后端托管 apple-app-site-association。
  • Android:登记 应用包名(com.youzhi.app,见 app.config.js)与应用签名(发布/调试签名 SHA256)。
  • ⚠️ 此步产生的 AppID、Universal Links 域名需回填到代码与 EAS 配置(见 Step 3/4)。

Step 2 · 安装微信 SDK(Expo 插件优先)

npx expo install react-native-wechat-lib
# 或选用维护更活跃的 expo 兼容插件(以官方 docs 为准)
  • 原生模块需在 ios/ android/ 原生壳中注册(Expo 前置预构建会处理 config plugin 注入,无需手改原生代码)。

Step 3 · 生成 EAS 自定义开发客户端(Expo Go 跑不了原生模块)

  • 当前 eas.json 已有 development profile(developmentClient: true)。
  • 执行:
    eas build --profile development --platform ios
    eas build --profile development --platform android
    
  • 安装产物到真机/模拟器。后续所有真机验证都在此客户端中进行,Expo Go 无法调起微信。

Step 4 · 替换 SDK 适配层(唯一改动点 · 前端)

文件:src/lib/wechat-sdk.ts

  • 顶部 import WeChat from 'react-native-wechat-lib';
  • isWechatAvailable() 改为 return WeChat.isWXAppInstalled();
  • requestWechatAuthCode() 替换为:
    const res = await WeChat.sendAuthRequest('snsapi_userinfo', 'wechat_auth_state');
    return res.code;
    
  • app.config.js 的 extra 中补 wechatAppId,在 SDK 初始化时读取(或走 expo-config-plugin 注入原生 Info.plist / AndroidManifest)。
  • 此文件改动不影响任何上层调用方(签名不变)。

Step 5 · 实现后端两个接口

接口 方法 职责 关键返回
/api/auth/wechat POST code → access_token + openid → 查库判定状态 WechatLoginResult:new 时下发明文 WechatPendingTicket;active 下发正式 token + expiresAt + profile + role;deleted 仅 openid
/api/auth/wechat/bind-phone POST 用临时凭证 + 手机号 + 验证码完成注册,关联 openid WechatSession:token + expiresAt + profile + role
  • 后端须保证:code 一次性;临时凭证有独立 TTL 且可校验来源;deleted 账号拒绝登录。
  • WechatLoginResult / WechatSession 字段契约见 src/types/wechat-auth.ts,前后端以此对齐。

Step 6 · 替换 API 层函数体(后端就绪后)

文件:src/api/api.ts

  • fetchWechatLoginResult(code):删掉 MOCK_WECHAT_STATUS 分支,改为 fetch(...),请求 /api/auth/wechat,返回 WechatLoginResult。函数签名不变。
  • bindWechatAndRegister(payload):改为请求 /api/auth/wechat/bind-phone,返回 WechatSession。签名不变。
  • 保留 JSDoc 的 @method / @url / @param / @returns 注释。

Step 7 · 清除 mock 残留

  • 删除 src/api/api.ts 中的 let MOCK_WECHAT_STATUS、setMockWechatAccountStatus() 及其导出(同步从 src/api/index.ts 移除导出)。
  • 删除 P1Login.tsx / 测试代码中调用 setMockWechatAccountStatus 的分支(若有)。

Step 8 · 回调与票据闭环验证

  • iOS Universal Links:确认微信开放平台登记的 Universal Links 能打开 App,且 useWechatLogin 未被重复触发(alreadyLoggedIn 短路逻辑生效)。
  • Android 包名/签名:确认微信回调能正确唤起 App 并继续 verifying。
  • 验证 needBindPhone → wechatTicket 透传 → P2 调用 bindWechatAndRegister 全程用的是临时凭证 token,而非已失效的 code。

5. 手动验收路径(可复现)

  1. 在自定义客户端(非 Expo Go)上安装并登录。
  2. active 分支:用老微信账号登录 → 期望直接进主界面;杀进程重进(本地 token 有效)→ 期望 alreadyLoggedIn 免授权落地。
  3. new 分支:用全新微信账号登录 → 期望跳 P2 手机号+验证码 → 注册成功进 P3 选身份。
  4. deleted 分支:用处于注销审核期的账号登录 → 期望登录页提示「该账号正在申请注销中,无法登录」,无复活入口。
  5. 断网 / 取消授权 / 未装微信 → 期望 error 态并显示对应提示。
  6. 回归 tsc --noEmit 与 eslint . 均 0 错误、0 警告。

6. 风险与待确认项

项 风险/阻塞 处置
AppID / Universal Links 需用户到微信开放平台申请,非代码可解 Step 1 前置,未拿到前无法真机验证
react-native-wechat-lib 维护状态 库可能滞后于新架构 接入时以 Expo 官方 docs 选型为准(AGENTS.md 要求先读 docs.expo.dev/versions/v57.0.0/)
Universal Links 配置 iOS 回调硬性要求,易踩坑 后端托管 apple-app-site-association + 真机验证
自定义客户端构建 首次 EAS Build 耗时较长 Step 3 提前跑,避免阻塞联调
临时凭证安全 code 一次性,凭证必须独立加密 + 短 TTL 后端实现时强制

7. 一句话给执行者

别动 hook、页面、类型。 只动两处:src/lib/wechat-sdk.ts(换真 SDK 调用)和 src/api/api.ts(换真接口),配齐微信开放平台移动应用 + EAS 自定义客户端,三分支即可真机走通。