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
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 三条链路,且:
- 用户第一次用微信登录 → 跳手机号+验证码 → 注册落地;
- 老用户再次微信登录 → 直接进主界面(本地 token 有效时甚至免授权);
- 处于注销审核期的账号 → 登录页明确提示「该账号正在申请注销中,无法登录」,无复活入口。
成功标准(验收口径):
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. 两个硬约束(设计已遵守,接后端不可破坏)
- 微信 code 一次性(5 分钟有效):用户授权后原 code 已被首次请求消耗,绑定手机号时拿不到第二个 code。因此后端必须在首次响应里下发
WechatPendingTicket(含token+openid),前端在P2回传票据而非 code。- 类型已建模:
WechatPendingTicket { token: string; openid: string }(src/types/wechat-auth.ts:29)。
- 类型已建模:
- 注销即不可登录、不可复活:
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已有developmentprofile(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. 手动验收路径(可复现)
- 在自定义客户端(非 Expo Go)上安装并登录。
- active 分支:用老微信账号登录 → 期望直接进主界面;杀进程重进(本地 token 有效)→ 期望
alreadyLoggedIn免授权落地。 - new 分支:用全新微信账号登录 → 期望跳
P2手机号+验证码 → 注册成功进P3选身份。 - deleted 分支:用处于注销审核期的账号登录 → 期望登录页提示「该账号正在申请注销中,无法登录」,无复活入口。
- 断网 / 取消授权 / 未装微信 → 期望
error态并显示对应提示。 - 回归
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 自定义客户端,三分支即可真机走通。