JavaScript 封装无感 token 刷新

accessToken过期后使用refreshToken调用刷新 token 接口去获取新的accessToken

js 复制代码
// tokenManager.js
class TokenManager {
  constructor() {
    // 用于存储刷新token的promise,防止并发刷新
    this.promise = null
  }

  /**
   * 设置访问token
   * @param {string} token
   */
  setToken(token) {
    localStorage.setItem('accessToken', token)
  }

  /**
   * 获取刷新token
   * @returns {string|null}
   */
  getRefreshToken() {
    return localStorage.getItem('refreshToken')
  }

  /**
   * 移除所有token信息
   */
  removeToken() {
    localStorage.removeItem('accessToken')
    localStorage.removeItem('refreshToken')
    localStorage.removeItem('name')
  }

  /**
   * 调用接口刷新token
   * @returns {Promise<Response>}
   */
  async refreshAccessToken() {
    // 这里是刷新token的接口
    const response = await fetch('/api/v3/Logon/getRefreshUserToker', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-Refresh-Token': 'true',
      },
      body: JSON.stringify({
        refreshToken: this.getRefreshToken(),
        name: localStorage.getItem('name'),
      }),
    })
    return response.json()
  }

  /**
   * 获取token,如果正在刷新则返回同一个promise
   * @returns {Promise<boolean>} 刷新成功返回true,失败返回false
   */
  refreshToken() {
    if (this.promise) return this.promise

    this.promise = new Promise(resolve => {
      this.refreshAccessToken()
        .then(res => {
          if (res.code === 0) {
            this.setToken(res.accessToken)
            resolve(true)
          } else {
            this.removeToken()
            resolve(false)
          }
        })
        .catch(err => {
          console.error('Refresh token failed', err)
          this.removeToken()
          resolve(false)
        })
    })

    this.promise.finally(() => {
      this.promise = null
    })
    return this.promise
  }
}

const tokener = new TokenManager()

export default tokener

在响应拦截中使用刷新token

js 复制代码
// request.js
import tokener from './tokenManager.js'

/**
 * 请求封装
 * @param {string} url
 * @param {RequestInit} options
 * @returns {Promise<any>}
 */
const request = async (url, options = {}) => {
  // 合并默认配置
  const config = {
    ...options,
    headers: {
      'Content-Type': 'application/json',
      ...options.headers,
    },
  }

  // 添加token
  const token = localStorage.getItem('accessToken')
  if (token) {
    config.headers['Authorization'] = `Bearer ${token}`
  }

  try {
    const response = await fetch(url, config)

    // 拦截 401 未授权,且不是刷新token的请求
    if (response.status === 401 && !config.headers['X-Refresh-Token']) {
      // 尝试刷新token
      const isRefreshSuccess = await tokener.refreshToken()

      if (isRefreshSuccess) {
        // 刷新成功,重试原请求
        // 更新header中的token
        const newToken = localStorage.getItem('accessToken')
        config.headers['Authorization'] = `Bearer ${newToken}`

        // 重新发起请求
        return fetch(url, config).then(res => res.json())
      } else {
        // 刷新失败,抛出错误或跳转登录
        throw new Error('Token refresh failed')
        // window.location.href = '/login'
        // 可以记录当前页面,重新登陆后跳转到当前页面
        // localStorage.setItem('redirectUrl', window.location.href)
      }
    }

    return response.json()
  } catch (error) {
    console.error('Request failed:', error)
    throw error
  }
}

export default request
相关推荐
用户新39 分钟前
V8引擎 精品漫游指南--Ignition篇(下 一) 动态执行前的事情
前端·javascript
@PHARAOH2 小时前
WHAT - GitLens vs Fork
前端
yqcoder2 小时前
前端性能优化:如何减少重绘与重排?
前端·性能优化
洋子3 小时前
Yank Note 系列 13 - 让 AI Agent 进入笔记工作流
前端·人工智能
wenzhangli75 小时前
Ooder A2UI 核心架构深度解析:WEB 拦截层的设计与实现
前端·架构
前端百草阁6 小时前
【前端性能优化全链路指南】从开发编写到构建运行的多维度实践
前端·性能优化
神探小白牙6 小时前
eCharts 多系列柱状图增加背景图
javascript·ecmascript·echarts
女生也可以敲代码6 小时前
AI时代下的50道前端开发面试题:从基础到大模型应用
前端·面试
ZhengEnCi6 小时前
M5-markconv自定义CSS样式指南 📝
前端·css·python
IT_陈寒6 小时前
SpringBoot自动配置的坑差点让我加班到天亮
前端·人工智能·后端