uniapp token过期的几种常见处理方案

📅 发布时间:2026/7/13 10:11:40 👁️ 浏览次数:
uniapp token过期的几种常见处理方案
引言在现代Web和移动应用中,Token是用于身份认证的重要手段,它帮助服务器识别用户身份并提供相应的权限,然而,Token通常会有有效期,过期后用户将无法继续访问需要身份验证的接口,Token过期的问题,如果处理不当,会严重影响用户体验,特别是在UniApp这种跨平台开发框架中,我们需要确保不同平台的用户在Token过期时能平稳过渡1. 什么是Token过期?Token通常是一个短期有效的字符串,存储在客户端,后端服务器通过验证Token来识别用户身份,然而Token一般会设定一个过期时间,一旦超过这个时间,Token就会失效,常见的错误码如401 Unauthorized 标识Token过期,需要重新登录或刷新Token2.Token过期常见问题用户体验差: Token一旦过期,用户可能会遇到请重新登录的提示,影响流畅性接口调用失败: 如果前端没有妥善处理Token过期,接口调用会缺少有效Token而失败刷新失败: 当尝试刷新Token失败时,用户需要重新登录,这时候的体验可能会比较差那么,如何在UniApp中高效处理Token过期问题呢? 下面我将介绍几种常见的解决方案3. 解决方案方案1: 基础版 - 被动刷新(Token 过期后提示 手动登录)核心逻辑:拦截接口返回的 401 (Token过期),清除本地 Token 并跳转登录页,适合小型项目,无refreshToken 机制。方案2: 无感刷新 双 Token 机制 (自动续期 请求队列)核心逻辑: 当Access Token 失效(后端返回401状态码)时双Token无感刷新如果只给用户一个Token:设短了 (比如2个小时) : 用户用着用着就过期,需要重新登录,体验差设长了 (比如7天) : 一旦Token 泄露,别人能长期盗用,不安全Access Token(访问令牌) : 短有效期(2小时),请求接口Refresh Token(刷新令牌) 仅用于Access Token 过期后的续期操作,有效期长(如604800秒),不参与普通接口请求,技术架构1. Token Store 状态管理使用Pinia 管理Token状态,核心功能包括1.1 过期时间管理登录成功后,计算并存储Token 的过期时间戳:const setTokenInfo (val: IAuthLoginRes) { updateNowTime() tokenInfo.value val // 计算并存储过期时间 const now Date.now() if (isSingleTokenRes(val)) { // 单token模式 const expireTime now val.expiresIn * 1000 uni.setStorageSync(accessTokenExpireTime, expireTime) } else if (isDoubleTokenRes(val)) { // 双token模式 const accessExpireTime now val.accessExpiresIn * 1000 const refreshExpireTime now val.refreshExpiresIn * 1000 uni.setStorageSync(accessTokenExpireTime, accessExpireTime) uni.setStorageSync(refreshTokenExpireTime, refreshExpireTime) } }2.2 过期状态判断通过computed 属性实时判断Token 是否过期://令牌 const isTokenExpired computed(() { if (!tokenInfo.value) { return true } const now nowTime.value const expireTime uni.getStorageSync(accessTokenExpireTime) if (!expireTime) return true return now expireTime }) //刷新令牌 const isRefreshTokenExpired computed(() { if (!isDoubleTokenMode) return true const now nowTime.value const refreshExpireTime uni.getStorageSync(refreshTokenExpireTime) if (!refreshExpireTime) return true return now refreshExpireTime }) 2.3 刷新token 方法typescript const refreshToken async () { if (!isDoubleTokenMode) { console.error(单token模式不支持刷新token) throw new Error(单token模式不支持刷新token) } try { // 安全检查确保refreshToken存在 if (!isDoubleTokenRes(tokenInfo.value) || !tokenInfo.value.refreshToken) { throw new Error(无效的refreshToken) } const refreshToken tokenInfo.value.refreshToken const res await _refreshToken(refreshToken) console.log(刷新token-res: , res) setTokenInfo(res) return res } catch (error) { console.error(刷新token失败:, error) throw error } finally { updateNowTime() } }2.4. HTTP 拦截器 自动添加token在请求拦截器中自动为每个请求添加有效的 Tokenconst httpInterceptor { invoke(options: CustomRequestOptions) { // ... 其他处理逻辑 // 添加 token 请求头标识 const tokenStore useTokenStore() const token tokenStore.updateNowTime().validToken if (token) { options.header.Authorization Bearer ${token} } return options }, }2.5 无感刷新核心逻辑 (401错误)这是整个方案的核心, 在HTTP 响应拦截中实现:步骤1: 获取Refresh Token 并加入请求队列从Pinia的Token 仓库中取出refreshToken :如有refreshToken : 将当前失败的请求封装回调函数,加入taskQueue 队列(回调函数逻辑:用新Token 重新发起原请求,并resolve 结果)若没有refreshToken: 直接跳过刷新,后续会走失败逻辑步骤2: 加锁并发起Token刷新判断条件refreshToken !refreshing有刷新令牌且未在刷新中先设置refreshing true加锁防止其他请求重复触发刷新调用tokenStore.refreshToken()发起刷新请求刷新成功解锁refreshing false提示「token 刷新成功」可注释真正无感遍历taskQueue执行所有回调函数重新发起之前失败的请求最后清空队列taskQueue []。刷新失败如 Refresh Token 过期解锁refreshing false提示「登录已过期请重新登录」清空本地登录态tokenStore.logout()延迟 2 秒跳转登录页清空队列taskQueue []。步骤3: 临时 reject 原请求无论是否触发刷新当前 401 对应的原请求会先return reject(res)—— 因为原请求用的是过期 Token4. 正常响应处理若响应不是 401 错误判断 HTTP 状态码是否在 200-300 之间合法响应再判断业务码是否为成功码ResultEnum.Success0/Success200成功resolve 后端返回的业务数据失败提示错误信息reject 错误数据非 200-300 状态码直接 reject 响应结果。必然失败后续靠队列重新执行请求并 resolve 结果。typescript // 刷新 token 状态管理 let refreshing false // 防止重复刷新 token 标识 let taskQueue: (() void)[] [] // 刷新 token 请求队列 export function httpT(options: CustomRequestOptions) { return new PromiseT((resolve, reject) { uni.request({ ...options, success: async (res) { const responseData res.data as IResponseT const { code } responseData // 检查是否是401错误 const isTokenExpired res.statusCode 401 || code 401 if (isTokenExpired) { const tokenStore useTokenStore() // 单Token模式直接跳转登录 if (!isDoubleTokenMode) { tokenStore.logout() toLoginPage() return reject(res) } /* -------- 无感刷新 token ----------- */ const { refreshToken } tokenStore.tokenInfo as IDoubleTokenRes || {} // 将失败的请求加入队列 if (refreshToken) { taskQueue.push(() { resolve(httpT(options)) }) } // 如果有 refreshToken 且未在刷新中发起刷新 if (refreshToken !refreshing) { refreshing true try { // 刷新 token await tokenStore.refreshToken() refreshing false uni.showToast({ title: token 刷新成功, icon: none, }) // 重新执行队列中的所有请求 taskQueue.forEach(task task()) } catch (refreshErr) { console.error(刷新 token 失败:, refreshErr) refreshing false uni.showToast({ title: 登录已过期请重新登录, icon: none, }) // 清除用户信息并跳转登录页 await tokenStore.logout() setTimeout(() { toLoginPage() }, 2000) } finally { // 清空任务队列 taskQueue [] } } return reject(res) } // 处理正常响应 if (res.statusCode 200 res.statusCode 300) { if (code ! ResultEnum.Success0 code ! ResultEnum.Success200) { uni.showToast({ icon: none, title: responseData.msg || responseData.message || 请求错误, }) return reject(responseData.data) } return resolve(responseData.data) } // 处理其他错误 reject(res) }, fail(err) { uni.showToast({ icon: none, title: 网络错误换个网络试试, }) reject(err) }, }) }) }工作流程详解场景一: 单个请求Token 过期用户操作 - 发起请求 - 携带过期Token服务器返回401检测到401 - 加入请求队列 - 发起刷新Token 请求刷新成功- 更新token - 重新执行队列中的请求返回数据给用户(用户无感知)场景二多个请求同时 Token 过期请求A、B、C 同时发起 → 都携带过期Token都返回 401请求A触发刷新设置 refreshing true请求B加入队列等待请求C加入队列等待刷新成功 → 更新Token队列中的请求A、B、C 依次重新执行所有请求成功返回用户无感知### 场景三Refresh Token 也过期用户操作 → 发起请求 → 携带过期Token服务器返回 401尝试刷新Token → Refresh Token 也过期刷新失败 → 清除本地Token → 提示用户2秒后跳转登录页方案3 : 前端主动预刷新核心逻辑不在401 之后再刷新在 Access Token 快要过期前(比如快过期前1分钟)前端主动静默刷新Token流程登录时记录过期时间定时器 / 每次发请求前判断: 是否快过期是 - 提前刷新Token否 - 正常请求方案 4 : 单Token 后端延长有效期核心逻辑只有一个 Access Token每次请求接口后端自动延长 Token 有效期前端不用管刷新方案 5每次请求都带 Token后端实时判断懒人方案前端不判断过期每次请求都带上 Token\后端发现快过期直接在响应头返回新 Token前端拦截响应自动替换旧 Token