Skip to content

@sud-web/http

管理后台统一 HTTP 请求库,基于 axios 封装,提供 CSRF Token 管理、401 自动刷新、统一错误处理等功能。

特性

  • CSRF Token 自动管理 - 自动从响应头获取并存储 CSRF Token,请求时自动携带
  • 401 自动刷新 - 支持 Token 过期自动刷新,并发请求队列管理
  • 统一错误处理 - 内置错误处理逻辑,支持自定义错误处理
  • TypeScript 支持 - 完整的 TypeScript 类型定义
  • 灵活的 Hooks - 支持自定义请求/响应拦截器

安装

bash
npm install @sud-web/http axios
# 或
pnpm add @sud-web/http axios
# 或
yarn add @sud-web/http axios

注意: axios 是 peer dependency,需要单独安装。

快速开始

基础用法

typescript
import { createAxios } from '@sud-web/http'

// 创建 axios 实例
const http = createAxios({
  axiosConfig: {
    baseURL: 'https://api.example.com',
    timeout: 10000
  }
})

// 使用
const response = await http.get('/api/users')

带错误提示的用法

typescript
import { createAxios } from '@sud-web/http'
import { ElMessage } from 'element-plus'

const http = createAxios({
  axiosConfig: {
    baseURL: 'https://api.example.com'
  },
  hooks: {
    // 自定义错误提示
    showErrorMessage: (msg) => {
      ElMessage.error(msg)
    }
  }
})

带 401 自动刷新的用法

typescript
import { createAxios, RefreshManager } from '@sud-web/http'

// 创建刷新管理器
const refreshManager = new RefreshManager(
  async () => {
    // 刷新 token 的逻辑(使用 cookie,后端自动处理)
    const response = await axios.post('/api/refresh')
    return response
  },
  {
    // 刷新失败的回调
    onRefreshFail: () => {
      // 跳转到登录页
      window.location.href = '/login'
    }
  }
)

// 创建 axios 实例
const http = createAxios({
  axiosConfig: {
    baseURL: 'https://api.example.com'
  },
  refreshManager
})

API 文档

createAxios(options)

创建配置好的 axios 实例。

参数

typescript
interface CreateAxiosOptions {
  hooks?: AxiosHooks           // 自定义 hooks
  refreshManager?: RefreshHandler  // 401 刷新管理器
  axiosConfig?: Record<string, any>  // axios 配置
}

AxiosHooks

typescript
interface AxiosHooks {
  // 显示错误消息
  showErrorMessage?: (msg: string) => void
  
  // 请求拦截器
  onRequest?: (
    config: InternalAxiosRequestConfig
  ) => InternalAxiosRequestConfig | Promise<InternalAxiosRequestConfig>
  
  // 响应拦截器(成功)
  onResponse?: (
    response: AxiosResponse
  ) => any | Promise<any>
  
  // 响应拦截器(错误)
  onResponseError?: (
    error: AxiosError
  ) => any
}

RefreshManager

401 自动刷新管理器。

构造函数

typescript
new RefreshManager(
  refreshFn: () => Promise<any>,
  options?: RefreshManagerOptions
)

参数

  • refreshFn: 刷新 token 的函数,返回 Promise
  • options: 可选配置
    typescript
    interface RefreshManagerOptions {
      onRefreshFail?: () => void  // 刷新失败时的回调
    }

特性

  • 并发请求队列: 多个请求同时 401 时,只会触发一次刷新,其他请求会加入队列等待
  • 防重复刷新: 同一个请求不会重复触发刷新
  • 自动重试: 刷新成功后自动重试原始请求

使用示例

示例 1: 基础配置

typescript
import { createAxios } from '@sud-web/http'

const http = createAxios({
  axiosConfig: {
    baseURL: 'https://api.example.com',
    timeout: 10000,
    headers: {
      'Content-Type': 'application/json'
    }
  }
})

// 发送请求
const data = await http.get('/api/users')

示例 2: 自定义请求拦截器

typescript
import { createAxios } from '@sud-web/http'

const http = createAxios({
  axiosConfig: {
    baseURL: 'https://api.example.com'
  },
  hooks: {
    onRequest: async (config) => {
      // 添加时间戳(token 通过 cookie 自动携带,无需手动添加)
      config.params = {
        ...config.params,
        _t: Date.now()
      }
      
      return config
    }
  }
})

示例 3: 自定义响应处理

typescript
import { createAxios } from '@sud-web/http'

const http = createAxios({
  hooks: {
    // 自定义响应处理
    onResponse: (response) => {
      // 直接返回 response,不走默认的 code 判断
      return response.data
    },
    
    // 自定义错误处理
    onResponseError: (error) => {
      if (error.response?.status === 403) {
        // 处理 403 错误
        console.error('没有权限')
      }
      return Promise.reject(error)
    }
  }
})

完整的认证流程

typescript
import { createAxios, RefreshManager } from '@sud-web/http'
import { ElMessage } from 'element-plus'

// 刷新管理器
const refreshManager = new RefreshManager(
  async () => {
    // 刷新 token(使用 cookie,后端自动处理,无需手动传递和存储)
    const response = await axios.post('/api/auth/refresh')
    return response
  },
  {
    onRefreshFail: () => {
      // 跳转登录
      window.location.href = '/login'
    }
  }
)

// 创建 http 实例
const http = createAxios({
  axiosConfig: {
    baseURL: import.meta.env.VITE_APP_BASE_URL,
    timeout: 10000
  },
  hooks: {
    showErrorMessage: (msg) => {
      ElMessage.error(msg)
    }
    // token 通过 cookie 自动携带,无需在 onRequest 中手动添加
  },
  refreshManager
})

export default http

完整示例(可直接使用)

这是一个完整的、可直接复制到项目中使用的示例:

typescript
import type { AxiosInstance, AxiosRequestConfig } from "axios"
import { createAxios, RefreshManager } from "@sud-web/http"

import { get } from "lodash-es"
import { refreshTokenApi } from "@/api/login"
import { redirectToLogin } from "@sud-web/utils"
import { ElMessage } from "element-plus" // 或使用其他 UI 库

const refreshManager = new RefreshManager(refreshTokenApi, {
  onRefreshFail: () => {
    console.log("[ refreshfail ] >")
    redirectToLogin()
  }
})

function createService() {
  return createAxios({
    refreshManager,
    axiosConfig: {
      timeout: 30000,
    },
    hooks: {
      // 走默认处理才会执行, 成功响应自定义 code 错误提示
      showErrorMessage(msg) {
        ElMessage.error(msg || "接口错误")
      },
      onResponseError(error) {
        console.log("[ error ] >", error)

        const status = get(error, "response.status")
        switch (status) {
          case 400:
            error.message = "请求错误"
            break
          case 403:
            error.message = "拒绝访问"
            break
          case 404:
            error.message = "请求地址出错"
            break
          case 408:
            error.message = "请求超时"
            break
          case 500:
            error.message = "服务器内部错误"
            break
          case 501:
            error.message = "服务未实现"
            break
          case 502:
            error.message = "网关错误"
            break
          case 503:
            error.message = "服务不可用"
            break
          case 504:
            error.message = "网关超时"
            break
          case 505:
            error.message = "HTTP 版本不受支持"
            break
          default:
            break
        }
        ElMessage({ message: error.message, type: "error" })
        return Promise.reject(error)
      }
    }
  })
}

/** 创建请求方法 */
function createRequestFunction(service: AxiosInstance) {
  return function <T>(config: AxiosRequestConfig): Promise<T> {
    return service(config)
  }
}

/** 用于网络请求的实例 */
export const service = createService()
/** 用于网络请求的方法 */
export const request = createRequestFunction(service)

使用方式:

typescript

// 使用封装的 request 方法
import { request } from '@/utils/request'

interface User {
  id: number
  name: string
}

const users = await request<User[]>('/api/users')

注意事项:

  • 需要根据项目实际情况调整 refreshTokenApiredirectToLogin 等工具函数
  • ElMessage 是 element-plus 的消息组件,如果使用其他 UI 库可相应替换(如 vant 的 Toast、naive-ui 的 useMessage 等)
  • 如果使用 Vite,可以取消注释 baseURL 配置

示例 6: 禁用特定接口的自动刷新

typescript
// 某些接口(如refresh接口)不需要自动刷新
const response = await http.post('/api/refresh', {}, {
  config: {
    enableRefresh: false  // 禁用自动刷新
  }
})

默认行为

响应处理

  • 成功响应 (code === 0): 直接返回 response.data
  • 业务错误 (code !== 0):
    • 如果提供了 showErrorMessage,会调用它显示错误消息
    • 返回 Promise.reject(response.data)

CSRF Token

  • 自动从响应头 x-xsrf-token 获取并存储到 localStorage
  • 请求时自动在请求头 X-XSRF-TOKEN 中携带

401 处理

  • 如果提供了 refreshManager,遇到 401 时会自动调用刷新逻辑
  • 刷新成功后自动重试原始请求
  • 并发请求会被加入队列,等待刷新完成后统一处理

注意事项

  1. 认证方式: 推荐使用 Cookie 方式,后端自动处理 token 的存储和携带,前端无需手动管理。使用代理时,Cookie 会自动处理,无需额外配置
  2. CSRF Token 存储: CSRF Token 存储在 localStorage 中,key 为 x-xsrf-token,会自动从响应头获取并存储
  3. 响应格式: 默认期望响应格式为 { code: number, data?: any, msg?: string }
  4. 401 刷新: 只有提供了 refreshManager 才会自动处理 401
  5. 并发请求: 多个请求同时 401 时,只会触发一次刷新,其他请求会等待
  6. TypeScript: 建议使用 TypeScript 以获得完整的类型提示