# 微信登录接入方案(P1) > ⚠️ **入口已摘除(2026-09-15,产品决定)** > 丁总决定前端不做 QQ / 微信登录,现阶段登录方式只保留「手机号 + 短信验证码」与「手机号 + 密码」,以后另加青核登录。 > **代码与页面完整保留待启用**:`P2W`(扫码)/`B1`(授权)页面、`useWechatLogin`、`useWechatBindPhone`、`wechat-sdk.ts`、`types/wechat-auth.ts`、本文件所述三个接口函数**均未删除**,仅从 `P1Login` 与 `P2PhoneCode` 摘掉了入口。 > 恢复入口 = 在 `P1Login.tsx` 重新挂上微信按钮与 `useWechatLogin` 状态机 + 在 `P2PhoneCode.tsx` 恢复微信圆标;`app/register.tsx` 的 `P2W` / `B1` 两个 case 一直都在。 > 本文以下内容描述的是**代码现状与未来接入路线**,不代表当前有可用入口。 > 状态:方案文档 · 不涉及代码改写 · 真实后端就绪后按路线图替换 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、页面、类型**零改动**。 > 注:摘除入口后,`P1Login.tsx` 不再引用 `useWechatLogin`,上表「页面流转」一行的起点需改为 `P2W`;恢复入口时按现状挂回即可。 --- ## 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 插件优先) ```bash 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`)。 - 执行: ```bash 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()` 替换为: ```ts 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 自定义客户端,三分支即可真机走通。