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

引言

在现代Web和移动应用中,Token是用于身份认证的重要手段,它帮助服务器识别用户身份并提供相应的权限,然而,Token通常会有有效期,过期后用户将无法继续访问需要身份验证的接口,Token过期的问题,如果处理不当,会严重影响用户体验,特别是在UniApp这种跨平台开发框架中,我们需要确保不同平台的用户在Token过期时能平稳过渡

1. 什么是Token过期?

Token通常是一个短期有效的字符串,存储在客户端,后端服务器通过验证Token来识别用户身份,然而Token一般会设定一个过期时间,一旦超过这个时间,Token就会失效,常见的错误码如401 Unauthorized 标识Token过期,需要重新登录或刷新Token

2.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 的过期时间戳:

javascript 复制代码
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 是否过期:

javascript 复制代码
//令牌 
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 方法
javascript 复制代码
``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

在请求拦截器中,自动为每个请求添加有效的 Token:

javascript 复制代码
const 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() 发起刷新请求:

  • 刷新成功:
    1. 解锁(refreshing = false);
    2. 提示「token 刷新成功」(可注释,真正无感);
    3. 遍历 taskQueue 执行所有回调函数(重新发起之前失败的请求);
    4. 最后清空队列(taskQueue = [])。
  • 刷新失败(如 Refresh Token 过期):
    1. 解锁(refreshing = false);
    2. 提示「登录已过期,请重新登录」;
    3. 清空本地登录态(tokenStore.logout());
    4. 延迟 2 秒跳转登录页;
    5. 清空队列(taskQueue = [])。
步骤3: 临时 reject 原请求

无论是否触发刷新,当前 401 对应的原请求会先 return reject(res) ------ 因为原请求用的是过期 Token,

4. 正常响应处理

若响应不是 401 错误:

  • 判断 HTTP 状态码是否在 200-300 之间(合法响应);
  • 再判断业务码是否为成功码(ResultEnum.Success0/Success200):
    • 成功:resolve 后端返回的业务数据;
    • 失败:提示错误信息,reject 错误数据;
  • 非 200-300 状态码:直接 reject 响应结果。

必然失败,后续靠队列重新执行请求并 resolve 结果。

javascript 复制代码
```typescript
// 刷新 token 状态管理
let refreshing = false // 防止重复刷新 token 标识
let taskQueue: (() => void)[] = [] // 刷新 token 请求队列

export function http<T>(options: CustomRequestOptions) {
  return new Promise<T>((resolve, reject) => {
    uni.request({
      ...options,
      success: async (res) => {
        const responseData = res.data as IResponse<T>
        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(http<T>(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

流程

  1. 登录时记录过期时间
  2. 定时器 / 每次发请求前判断: 是否快过期
  3. 是 -> 提前刷新Token
  4. 否 -> 正常请求

方案 4 : 单Token + 后端延长有效期

核心逻辑

  • 只有一个 Access Token
  • 每次请求接口,后端自动延长 Token 有效期
  • 前端不用管刷新

方案 5:每次请求都带 Token,后端实时判断(懒人方案)

  • 前端不判断过期
  • 每次请求都带上 Token\
  • 后端发现快过期,直接在响应头返回新 Token
  • 前端拦截响应,自动替换旧 Token
相关推荐
2501_915918412 小时前
iOS App HTTPS 抓包工具,代理抓包和数据线直连 iPhone 抓包的流程
android·ios·小程序·https·uni-app·iphone·webview
anyup_前端梦工厂2 小时前
开源半年,每月 8K+ 下载,uView Pro 让跨端应用开发提效 10 倍
前端·uni-app·开源
一只程序熊3 小时前
uniapp 通过通道$on或者$once调用函数内的showModal弹窗,但是没有反应
uni-app
一渊之隔3 小时前
uniapp封装 SQLite数据库操作接口
数据库·uni-app
天蓝色的鱼鱼2 天前
从“死了么”到“我在”:用uniCloud开发一款温暖人心的App
前端·uni-app
小徐_23332 天前
uni-app 组件库 Wot UI 的 AI 友好型编程指南
前端·uni-app
CHB2 天前
uni-app x 蒸汽模式 性能测试基准报告 Benchmark
uni-app·harmonyos
anyup2 天前
月销 8000+,uView Pro 让 uni-app 跨端开发提速 10 倍
前端·uni-app·开源
willow6 天前
uniapp实战
uni-app