人人都会AI编程

12.3 请求拦截与统一封装

更新时间:2026-07-11

下面以最常用的 axios 为例(即使换成 fetch,思路也完全一样),给出一个可以直接复用的封装方案。


1. 创建统一请求实例

首先创建一个 request.ts 文件,配置基础域名、超时时间、默认请求头等。

import axios from 'axios'

const request = axios.create({
  baseURL: '/api',          // 所有请求统一前缀,如 Tauri IPC 可改为自定义 scheme
  timeout: 10000,           // 10 秒超时
  headers: {
    'Content-Type': 'application/json'
  }
})

export default request

为什么用 /api
如果后端是 Tauri 自带的 Rust 命令,通常不在前端发 HTTP 请求;但若你的应用需要访问远程服务器,可以把 baseURL 设为实际域名,如 https://api.example.com。对于本地开发,也可通过 Vite 代理来避免跨域。


2. 请求拦截器:统一注入 Token

每一次发送请求时,自动贴上登录后的 Token,避免每个接口调用都手动传参。

request.interceptors.request.use(config => {
  const token = localStorage.getItem('auth_token')  // 或来自状态管理
  if (token) {
    config.headers.Authorization = `Bearer ${token}`
  }
  return config
}, error => {
  return Promise.reject(error)
})

延伸:也可以在这里添加请求时间戳、设备 ID、防重放签名字段等。


3. 响应拦截器:统一错误处理

后端返回的格式假设是 { code: 0, data: {...}, message: '' },当 code !== 0 或 HTTP 状态码异常时,统一提示用户。

request.interceptors.response.use(
  response => {
    const res = response.data
    if (res.code !== 0) {
      // 业务错误
      console.error(`[${res.code}] ${res.message}`)
      // 可以按需弹出全局提示(如 Element Plus 的 ElMessage)
      return Promise.reject(new Error(res.message || '业务错误'))
    }
    return res.data   // 只返回 data,调用者不用再解包
  },
  error => {
    // 网络或服务器错误
    let message = '网络异常,请稍后重试'
    if (error.response) {
      const status = error.response.status
      switch (status) {
        case 401:
          message = '登录已过期,请重新登录'
          // 可以触发跳转到登录页
          break
        case 403:
          message = '没有权限访问'
          break
        case 500:
          message = '服务器异常'
          break
      }
    }
    console.error(message)
    return Promise.reject(error)
  }
)

注意:在 Tauri 环境下,如果使用 Rust 后端通过 invoke 调用,上面的 HTTP 拦截器并不适用于 invoke(它走的是 Tauri IPC,不是 HTTP)。对于 invoke,可以另行封装一个统一的 tauriRequest 函数。


4. API 模块封装

有了上面的 request,每个业务接口就可以写成非常简洁的函数。

// api/user.ts
import request from '@/utils/request'

export const login = (params: { username: string; password: string }) => {
  return request.post('/user/login', params)   // 返回 Promise<Token>
}

export const getUserInfo = () => {
  return request.get('/user/info')               // 返回 Promise<UserInfo>
}

在组件中使用时,直接调用这些函数,无需再关心 Token 拼接或错误弹出。

<script setup lang="ts">
import { login } from '@/api/user'

const handleLogin = async () => {
  try {
    const token = await login({ username: 'admin', password: '123456' })
    localStorage.setItem('auth_token', token)
    // 跳转到主页
  } catch (e) {
    // 错误已在拦截器中统一处理,这里可只做必要的事
  }
}
</script>

5. 配合 Tauri 的本地请求

如果你的请求对象是 Tauri 的 Rust 命令(通过 @tauri-apps/apiinvoke),则需要单独封装一个 invokeWithErrorHandler

import { invoke } from '@tauri-apps/api/core'

export async function tauriRequest<T>(cmd: string, args?: Record<string, unknown>): Promise<T> {
  try {
    const result = await invoke<T>(cmd, args)
    return result
  } catch (error) {
    console.error(`Tauri command [${cmd}] failed:`, error)
    // 这里可以根据 error 类型做统一提示
    throw error
  }
}

前后端通信方式虽然不同,但“拦截与封装”的思想一致:集中管理、调用清爽、维护方便。


总结

  • axiosfetch 封装一个实例,一次性配置 baseURL、超时、请求头。
  • 请求拦截器:自动注入 Token、公共参数。
  • 响应拦截器:剥离出 data,统一捕获业务错误和 HTTP 错误。
  • 单独处理 Tauri 的 invoke,确保所有通信都有一致的错误提示。

这样一来,团队成员只需关注业务接口的调用,不用再为“token 怎么传?”“异常怎么弹?”这类细节分心,代码也更易于维护和重构。