axios请求封装

133 阅读2分钟

记录一下最近项目使用的axios封装方法

完整代码

import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError, InternalAxiosRequestConfig } from 'axios';
interface ApiResponse<T> {
  message: string;
  data: T;
  success: boolean;
}
const NoAuthUrl = ['/users/login', '/users/otp/verify']
// 处理401错误的函数
function handle401Error(): void {
  // 只在浏览器环境中执行
  if (typeof window !== "undefined") {
    // 获取当前路径
    const currentPath = window.location.pathname;

    // 排除登录和注册页面
    const excludePaths = ["/login", "/register"];
    if (!excludePaths.includes(currentPath)) {
      // 清除认证token
      localStorage.removeItem("auth_token");

      // 重定向到登录页面
      window.location.href = "/login";
    }
  }
}

// 请求状态管理
interface RequestState {
  [key: string]: boolean;
}

// 全局loading状态
const loadingState: RequestState = {};

// 创建自定义axios实例
const apiClient: AxiosInstance = axios.create({
  baseURL: process.env.NEXT_PUBLIC_API_URL || "",
  timeout: 10000,
  headers: {
    "Content-Type": "application/json",
  },
});

// 请求拦截器
apiClient.interceptors.request.use(
  (config: InternalAxiosRequestConfig) => {
    // 生成请求唯一标识
    const requestId = generateRequestId(config);

    // 设置loading状态
    loadingState[requestId] = true;

    // 添加认证token
    const token =
      typeof window !== "undefined" ? localStorage.getItem("auth_token") : null;
    if (token && !NoAuthUrl.includes(config.url || '')) {
      config.headers = config.headers || {};
      config.headers.Authorization = `Bearer ${token}`;
    }

    return config;
  },
  (error: AxiosError) => {
    return Promise.reject(error);
  }
);

// 响应拦截器
apiClient.interceptors.response.use(
  (response: AxiosResponse) => {
    // 清除loading状态
    const requestId = generateRequestId(response.config);
    loadingState[requestId] = false;
    return response.data;
  },
  (error: AxiosError) => {
    // 清除loading状态
    if (error.config) {
      const requestId = generateRequestId(error.config);
      loadingState[requestId] = false;
    }

    // 错误处理
    handleApiError(error);

    return Promise.reject(error);
  }
);

// 生成请求唯一标识
function generateRequestId(
  config: AxiosRequestConfig | InternalAxiosRequestConfig
): string {
  const { method, url, params, data } = config;
  return `${method}_${url}_${JSON.stringify(params)}_${JSON.stringify(data)}`;
}

// 检查请求是否正在加载
export function isLoading(
  config: AxiosRequestConfig | InternalAxiosRequestConfig
): boolean {
  const requestId = generateRequestId(config);
  return !!loadingState[requestId];
}

// 错误处理函数
function handleApiError(error: AxiosError): void {
  let errorMessage = "请求失败,请稍后重试";

  if (error.response) {
    // 服务器返回错误状态码
    const status = error.response.status;
    const data = error.response.data as any;

    switch (status) {
      case 400:
        errorMessage = data.error || "请求参数错误";
        break;
      case 401:
        errorMessage = "未授权,请重新登录";
        // 处理401错误,排除登录和注册页面
        handle401Error();
        break;
      case 403:
        errorMessage = "拒绝访问";
        break;
      case 404:
        errorMessage = data.error || "请求的资源不存在";
        break;
      case 500:
        errorMessage = data.error || "服务器错误";
        break;
      default:
        errorMessage = data.error || `请求失败(${status})`;
    }
  } else if (error.request) {
    // 请求发出但没有收到响应
    errorMessage = "网络错误,请检查您的网络连接";
  } else {
    // 请求配置出错
    errorMessage = error.message || "请求配置错误";
  }

  // 显示错误提示
  // 由于flowbite-react不支持直接调用toast,这里只记录到控制台
  // 实际使用时可以通过组件方式或其他toast库显示错误
  console.error("API请求错误:", errorMessage, error);
}

// 通用请求方法
export async function request<T>(
  config: AxiosRequestConfig
): Promise<ApiResponse<T>> {
  try {
    return (await apiClient(config)) as ApiResponse<T>;
  } catch (error) {
    throw error;
  }
}

// 导出封装的请求方法
export default {
  get: <T>(url: string, params?: any, config?: AxiosRequestConfig) => 
    request<T>({ ...config, method: 'get', url, params }),
  
  post: <T>(url: string, data?: any, config?: AxiosRequestConfig) => 
    request<T>({ ...config, method: 'post', url, data }),
  
  put: <T>(url: string, data?: any, config?: AxiosRequestConfig) => 
    request<T>({ ...config, method: 'put', url, data }),
  
  delete: <T>(url: string, config?: AxiosRequestConfig) => 
    request<T>({ ...config, method: 'delete', url }),
  
  // 获取当前loading状态
  isLoading,
};

实现考虑

1. loading状态

使用请求的method,url,data等值作为唯一id存储,页面想要使用的时候,使用jotai的get或者useMemo等方法获取loading状态,给页面的loading组件使用

2. ApiResponse泛型

定义通用ApiResponse泛型,接口请求只需传入主要data类型

3. auth_token

从localStorage中获取登录获取的token,在request中添加Authorization,同时维护一个url数组,过滤注册和登录等接口,避免出现bug

4. 错误处理

主要针对401进行错误处理,进行重定向