# 小程序 401 静默重新登录 - 前端需求 ## 一、背景 生产环境 JWT 密钥发生过变更,老用户本地缓存的 token 已失效。当前老用户打开小程序后,所有需要登录的接口均返回 401,前端未处理 401,导致页面渲染空白。 **结论**:这是纯前端问题。后端 401 响应规范(HTTP 401 + JSON body),前端补齐 401 处理逻辑后,老用户即可"无感自动重新登录",无需弹窗提醒。 --- ## 二、后端接口约定(已确认) ### 1. 401 响应格式 HTTP 状态码 `401`,响应体(JSON): ```json { "code": 401, "message": "未授权,请先登录", "data": null, "timestamp": 1234567890 } ``` 被踢下线(在其他设备登录)时: ```json { "code": 1304, "message": "您的账号已在其他设备登录,当前会话已失效,请重新登录", "data": null, "timestamp": 1234567890 } ``` **判定依据**:`HTTP status === 401`,或响应体 `code === 401 || code === 1304`。 ### 2. 静默重新登录接口(公开接口,无需鉴权) ``` POST /api/auth/wechat/miniapp/login Content-Type: application/json ``` 请求体: ```json { "jsCode": "wx.login() 返回的 code", "deviceType": "MINIAPP" } ``` 成功响应: ```json { "code": 200, "message": "操作成功", "data": { "accessToken": "...", "refreshToken": "...", "expiresIn": 2592000, "needBindPhone": false, "newUser": false, "userInfo": {} }, "timestamp": 1234567890 } ``` **说明**: - 该接口在公开白名单内,即使当前 token 失效也能正常调用。 - 统一登录按 openid 自动复用老账号,**不会新建账号、不会丢失用户数据**。 - `needBindPhone = true` 时表示需要绑定手机号(老用户一般已绑定,正常返回 `false`)。 - `expiresIn` 单位为秒。 --- ## 三、前端实现方案 在**请求封装层**(axios / request / 拦截器)增加 401 处理,核心逻辑如下。 ### 核心代码(示意) ```js // 全局单例锁,防止多个请求同时 401 时重复触发重登 let refreshing = null; async function handle401(error) { // 1. 若已有重登在进行,等它完成后直接重试 if (refreshing) { await refreshing; return retryRequest(error.config); } refreshing = (async () => { try { // 2. 用 wx.login 获取新的 code(Promise 化) const code = await wxLogin(); // 3. 静默重新登录(该请求不带旧 token,且不进入 401 拦截) const res = await request({ url: '/api/auth/wechat/miniapp/login', method: 'POST', data: { jsCode: code, deviceType: 'MINIAPP' }, skipAuth: true }); if (res.code === 200 && res.data) { // 4. 保存新 token(覆盖本地缓存) saveToken(res.data.accessToken, res.data.refreshToken); // 5. 需要绑定时才走绑定流程 if (res.data.needBindPhone) { await bindPhone(res.data.tempToken); } } else { // 登录接口本身失败,才真正引导用户重登 redirectToLogin(); } } catch (e) { redirectToLogin(); } finally { refreshing = null; } })(); await refreshing; // 6. 用新 token 重试原请求 return retryRequest(error.config); } function wxLogin() { return new Promise((resolve, reject) => { wx.login({ success: (res) => res.code ? resolve(res.code) : reject(new Error('no code')), fail: reject }); }); } ``` ### 拦截器接入(示意) ```js request.interceptors.response.use( (response) => { const res = response.data; if (res.code === 401 || res.code === 1304) { return handle401({ config: response.config }); } return res; }, (error) => { if (error.response && error.response.status === 401) { return handle401({ config: error.config }); } return Promise.reject(error); } ); ``` --- ## 四、必须满足的要点 1. **静默无感**:收到 401 时不弹窗、不跳登录页,直接执行 `wx.login → miniapp/login → 重试原请求`。 2. **防并发**:多个请求同时 401 时,只触发一次重登(使用全局单例 Promise 锁,见上文 `refreshing`)。 3. **兜底提示**:仅当 `miniapp/login` 本身失败(网络异常、code 过期等)时,才提示"登录已失效,请重新进入小程序",并清空本地 token 引导重登。 4. **token 存储**:重新登录成功后,`accessToken` 与 `refreshToken` 需覆盖本地旧值;后续请求自动携带新 token。 5. **公开接口豁免**:`miniapp/login` 请求本身必须标记为跳过鉴权(不携带旧 token、不进入 401 拦截),否则会死循环。 --- ## 五、为什么用静默重登而非弹窗提醒 - 小程序登录是无状态的(`wx.login` 拿 code 即可换 token),用户完全无感知,体验最好。 - 弹窗提醒需要用户手动操作,容易造成用户流失。 - 该方案为一次性:token 换新后,只要密钥不再变更,就不会再次触发。 --- ## 六、验收标准 1. 老用户(本地已缓存旧 token)打开小程序,页面正常渲染,无空白、无弹窗。 2. 老用户刷新页面 / 重复进入,仍能正常使用,无需重新登录。 3. 弱网或多接口并发场景下,不会出现重复登录请求。 4. 服务端断网 / 登录接口异常时,有友好兜底提示,而非白屏。