导读:本期聚焦于陆星河创作的《Vue 项目中如何用 Axios 拦截器统一处理请求头、Token 刷新与错误提示?》,敬请观看详情。不少开发者在处理接口鉴权时习惯在每个请求中手动添加 Token,或者在收到 401 错误后直接跳转登录页让用户重新输入账号密码。这种做法在小型项目中尚可应付,但随着业务复杂度上升,频繁的 Token 过期和重复登录会严重影响用户体验。Axios 提供的拦截器机制能够优雅地解决这些问题。通过请求拦截器注入动态请求头,响应拦截器捕获状态码并触发无感刷新,再结合统一的错误提示组件,可以构建一套健壮的前端 HTTP 通信层。本文将围绕 Vue 项目中 Axios 拦截器的实际应用展开,详细讲解请求头注入、Token 无感刷新队列以及全局错误处理的完整实现方案。

在前后端分离的架构中,前端几乎所有的数据交互都依赖 HTTP 请求。当项目接口数量达到一定规模后,如果每个请求都手动编写 Token 注入逻辑和错误处理代码,不仅维护成本极高,还容易遗漏边界场景。Axios 拦截器提供了一种在请求发送前和响应返回后插入自定义逻辑的能力,使得我们可以将公共逻辑集中管理。本文将以 Vue 项目为背景,从请求头注入、Token 无感刷新到统一错误提示,完整拆解拦截器的工程化实践。

Vue 项目中如何用 Axios 拦截器统一处理请求头、Token 刷新与错误提示?

一、Axios 拦截器基础概念与请求头注入

Axios 拦截器分为请求拦截器和响应拦截器两类。请求拦截器在请求发出之前执行,适合在这里统一添加请求头、设置 Token、附加时间戳防止缓存等操作。响应拦截器在收到服务器响应之后执行,适合统一处理错误码、过滤数据、触发 Token 刷新等逻辑。两者通过 axios.interceptors.request.use()axios.interceptors.response.use() 进行注册。

在 Vue 项目中,推荐创建一个独立的 Axios 实例而不是直接使用默认实例。这样做的好处是可以为不同业务模块配置不同的 baseURL、超时时间等参数,同时保持拦截器逻辑的统一管理。比如你可以为需要鉴权的接口创建一个实例,为公开接口创建另一个实例,互不干扰。

请求头注入的核心思路是:从本地存储中读取 Token,如果 Token 存在,就将其添加到请求头的 Authorization 字段中。除了 Token 之外,还可以根据业务需要注入诸如时间戳、签名、设备标识等自定义头信息。下面是一个完整的请求拦截器示例:

import axios from 'axios'
import store from '@/store'

// 创建独立实例
const service = axios.create({
  baseURL: process.env.VUE_APP_BASE_API,
  timeout: 15000
})

// 请求拦截器:统一注入请求头
service.interceptors.request.use(
  config => {
    // 从 Vuex 或本地存储中获取 Token
    const token = store.getters.token || localStorage.getItem('access_token')
    if (token) {
      // 标准的 Bearer Token 格式
      config.headers['Authorization'] = 'Bearer ' + token
    }
    // 添加时间戳,防止 IE 浏览器缓存 GET 请求
    if (config.method === 'get') {
      config.params = {
        ...config.params,
        _t: Date.now()
      }
    }
    // 可以根据业务需要添加自定义请求头
    config.headers['X-Request-Id'] = generateRequestId()
    return config
  },
  error => {
    // 请求发送前的错误处理
    console.error('请求拦截器错误:', error)
    return Promise.reject(error)
  }
)

上面的代码中,请求拦截器做了三件事:注入 Authorization 头、为 GET 请求添加时间戳防缓存、附加请求追踪 ID。其中 Token 的获取优先从 Vuex 读取,因为 Vuex 中的状态是响应式的,当 Token 刷新后所有后续请求都能立即拿到最新值。需要注意的是,config.headers 在不同版本的 Axios 中可能是一个普通对象或 AxiosHeaders 实例,赋值方式略有差异,建议根据实际使用的版本调整写法。

二、Token 过期自动刷新机制实现

在实际业务中,Access Token 通常有较短的有效期,短则几十分钟,长则数小时。当 Token 过期时,后端会返回 401 状态码。传统的处理方式是直接清除本地 Token 并跳转登录页,但这种方式会打断用户的操作流程,体验较差。更优雅的方案是在 Token 过期时自动使用 Refresh Token 获取新的访问令牌,然后重新发起失败的请求,整个过程对用户完全透明,这就是所谓的无感刷新。

实现无感刷新需要解决几个关键问题。第一,如何判断 Token 是否过期,通常通过响应状态码 401 来判定。第二,如何避免并发请求同时触发多次刷新,这是最容易踩坑的地方。第三,刷新失败后如何降级处理,比如 Refresh Token 也过期了,此时应该跳转登录页。第四,刷新成功后如何将队列中挂起的请求依次重发。

对于并发请求问题,核心思路是引入一个标志变量 isRefreshing 和一个请求队列数组。当第一个请求收到 401 响应时,设置 isRefreshing 为 true 并发起刷新请求,同时将后续收到 401 的请求通过 Promise 挂起到队列中。刷新成功后,取出队列中的请求依次重发;刷新失败则清空队列并跳转登录页。下面是完整的实现代码:

let isRefreshing = false
let requestQueue = []

// 响应拦截器:处理 Token 刷新
service.interceptors.response.use(
  response => {
    // 对响应数据做处理,直接返回 data 字段
    return response.data
  },
  async error => {
    const originalRequest = error.config
    
    // 判断是否为 401 且未标记过重试
    if (error.response && error.response.status === 401 && !originalRequest._retry) {
      // 如果已经在刷新中,将请求加入队列等待
      if (isRefreshing) {
        return new Promise((resolve, reject) => {
          requestQueue.push({ resolve, reject, config: originalRequest })
        })
      }
      
      // 标记为正在刷新
      isRefreshing = true
      originalRequest._retry = true
      
      try {
        // 调用刷新 Token 接口
        const refreshToken = localStorage.getItem('refresh_token')
        const res = await axios.post('/auth/refresh', { refreshToken })
        const newToken = res.data.accessToken
        
        // 更新本地存储和 Vuex 中的 Token
        localStorage.setItem('access_token', newToken)
        store.commit('SET_TOKEN', newToken)
        
        // 重发队列中挂起的请求
        requestQueue.forEach(item => {
          item.config.headers['Authorization'] = 'Bearer ' + newToken
          item.resolve(service(item.config))
        })
        requestQueue = []
        
        // 重发当前失败的请求
        originalRequest.headers['Authorization'] = 'Bearer ' + newToken
        return service(originalRequest)
      } catch (refreshError) {
        // 刷新失败,清空队列并跳转登录页
        requestQueue.forEach(item => {
          item.reject(refreshError)
        })
        requestQueue = []
        
        localStorage.removeItem('access_token')
        localStorage.removeItem('refresh_token')
        store.commit('CLEAR_TOKEN')
        
        // 跳转登录页,并记录来源路径
        router.push({
          path: '/login',
          query: { redirect: router.currentRoute.fullPath }
        })
        return Promise.reject(refreshError)
      } finally {
        isRefreshing = false
      }
    }
    
    return Promise.reject(error)
  }
)

这段代码有几个细节值得注意。首先,originalRequest._retry 标记用于防止同一个请求在刷新失败后被无限重试。其次,队列中存储的是 Promise 的 resolve 和 reject 函数,这样当刷新成功后可以直接 resolve 对应的请求,调用方无感知。最后,刷新接口使用的是原始的 axios.post 而非 service 实例,这是为了避免刷新请求本身也经过拦截器导致死循环。如果后端的 Refresh Token 也有过期时间,可以在刷新前先检查时间戳提前刷新,避免在请求过程中遇到 401。

三、统一错误处理与用户提示方案

后端接口返回的数据格式通常包含业务状态码和错误信息。即使 HTTP 状态码是 200,业务层面也可能存在错误,比如参数校验失败、权限不足、资源不存在等。因此,统一错误处理需要同时覆盖 HTTP 层错误和业务层错误两个维度。HTTP 层错误通过响应拦截器的 error 回调捕获,可以根据 status 码进行分类处理。业务层错误则需要在响应拦截器的成功回调中检查返回数据中的业务状态码字段。

在用户提示方面,Vue 项目中通常会结合 Element Plus 或 Ant Design Vue 的消息组件来实现。对于不同级别的错误,应当采用不同的提示策略:参数校验类错误用 warning 类型提示,系统级错误用 error 类型提示,网络断开或服务器不可达时可以用全局遮罩组件提示并支持重试。下面是一个结合 Element Plus 的统一错误处理方案:

import { ElMessage, ElMessageBox } from 'element-plus'

// 业务状态码与提示信息的映射表
const errorCodeMap = {
  400: '请求参数错误',
  401: '登录状态已过期,请重新登录',
  403: '没有访问权限',
  404: '请求的资源不存在',
  429: '请求过于频繁,请稍后再试',
  500: '服务器内部错误',
  502: '网关错误',
  503: '服务暂时不可用',
  504: '网关超时'
}

// 统一错误处理函数
function handleError(error) {
  let message = '未知错误'
  
  if (error.response) {
    // 服务器返回了响应,但状态码非 2xx
    const status = error.response.status
    const data = error.response.data
    
    // 优先使用后端返回的具体错误信息
    if (data && data.message) {
      message = data.message
    } else if (errorCodeMap[status]) {
      message = errorCodeMap[status]
    }
    
    // 401 已在 Token 刷新逻辑中处理,这里跳过
    if (status !== 401) {
      if (status >= 500) {
        ElMessage.error(message)
      } else if (status === 403) {
        ElMessage.warning(message)
      } else {
        ElMessage.error(message)
      }
    }
  } else if (error.request) {
    // 请求已发出但没有收到响应,通常是网络问题
    message = '网络异常,请检查网络连接'
    ElMessage.error(message)
  } else {
    // 请求配置阶段的错误
    message = error.message || '请求发送失败'
    ElMessage.error(message)
  }
  
  return Promise.reject(error)
}

// 在响应拦截器中接入统一错误处理
service.interceptors.response.use(
  response => {
    const res = response.data
    // 假设后端约定 code 为 0 表示成功
    if (res.code !== 0) {
      ElMessage.error(res.message || '业务处理失败')
      return Promise.reject(new Error(res.message || 'Error'))
    }
    return res
  },
  error => {
    return handleError(error)
  }
)

上述方案将错误处理逻辑封装为独立的 handleError 函数,职责清晰且易于扩展。当后端返回的错误信息中包含具体字段校验提示时,优先展示后端信息而非前端预设文案,这样能提供更精准的反馈。对于 5xx 系列的服务器错误,可以考虑加入自动重试机制,但需要设置最大重试次数避免无限循环。此外,在开发环境下可以将完整错误信息打印到控制台,方便调试排查,而在生产环境只展示用户可读的提示文案。

综合来看,通过请求拦截器注入 Token 和自定义头、响应拦截器实现无感刷新、统一错误处理函数管理用户提示,这三层拦截器机制构成了 Vue 项目中 HTTP 通信的完整基础设施。在实际项目中,还可以根据需要扩展请求重试、请求取消、接口缓存、接口防抖等功能,进一步提升前端应用的健壮性和用户体验。关键在于将公共逻辑收敛到拦截器层,让业务代码只关注数据本身,这也是前端工程化的核心目标之一。

VueAxios拦截器Token刷新修改时间:2026-08-25 10:13:47

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。