前端 API 自动化管理完整封装方案(基于 Axios)

53 阅读3分钟

1. 基础封装

1.1 类型定义 (TypeScript)

types/api.d.ts
export interface APIConfig {
  baseURL: string;
  globalHeaders?: Record<string, string>;
  modules: Record<string, APIModule>;
  timeout?: number;
}

export interface APIModule {
  basePath: string;
  apis: APIEndpoint[];
}

export interface APIEndpoint {
  name: string;
  method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
  url: string;
  mock?: any; // 开发环境 mock 数据
}

export interface APIParams {
  pathParams?: Record<string, string | number>;
  queryParams?: Record<string, any>;
  body?: any;
  headers?: Record<string, string>;
}

export interface APIResponse<T = any> {
  code: number;
  message: string;
  data: T;
  timestamp: number;
}

export type RequestInterceptor = (config: any) => any;
export type ResponseInterceptor = (response: any) => any;
export type ErrorInterceptor = (error: any) => Promise<any>;

1.2 Axios 封装核心类

utils/api-manager.ts
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios';
import { APIConfig, APIModule, APIEndpoint, APIParams, APIResponse, RequestInterceptor, ResponseInterceptor, ErrorInterceptor } from '@/types/api';

class APIManager {
  private axiosInstance: AxiosInstance;
  private config: APIConfig;
  private modules: Record<string, any> = {};
  private requestInterceptors: RequestInterceptor[] = [];
  private responseInterceptors: ResponseInterceptor[] = [];
  private errorInterceptors: ErrorInterceptor[] = [];

  constructor(config: APIConfig) {
    this.config = config;
    this.axiosInstance = axios.create({
      baseURL: config.baseURL,
      timeout: config.timeout || 10000,
      headers: config.globalHeaders || {}
    });

    this.setupInterceptors();
    this.initializeModules();
  }

  private setupInterceptors() {
    // 请求拦截器
    this.axiosInstance.interceptors.request.use(
      (config) => {
        // 执行自定义请求拦截器
        this.requestInterceptors.forEach(interceptor => {
          config = interceptor(config);
        });
        return config;
      },
      (error) => {
        return Promise.reject(error);
      }
    );

    // 响应拦截器
    this.axiosInstance.interceptors.response.use(
      (response) => {
        // 执行自定义响应拦截器
        this.responseInterceptors.forEach(interceptor => {
          response = interceptor(response);
        });
        return response;
      },
      (error) => {
        // 执行错误拦截器
        return this.handleError(error);
      }
    );
  }

  private async handleError(error: AxiosError): Promise<any> {
    let processedError = error;
    for (const interceptor of this.errorInterceptors) {
      try {
        processedError = await interceptor(processedError);
      } catch (e) {
        console.error('Error interceptor failed:', e);
      }
    }
    return Promise.reject(processedError);
  }

  private initializeModules() {
    Object.keys(this.config.modules).forEach(moduleName => {
      this.modules[moduleName] = this.createModule(moduleName);
    });
  }

  private createModule(moduleName: string): any {
    const moduleConfig = this.config.modules[moduleName];
    const module: any = {};

    moduleConfig.apis.forEach(api => {
      module[api.name] = (params: APIParams = {}, options: AxiosRequestConfig = {}) => {
        return this.createAPICall(moduleConfig.basePath, api, params, options);
      };
    });

    return module;
  }

  private async createAPICall(
    basePath: string,
    api: APIEndpoint,
    params: APIParams,
    options: AxiosRequestConfig
  ): Promise<APIResponse> {
    // 开发环境可以使用 mock 数据
    if (process.env.NODE_ENV === 'development' && api.mock) {
      console.warn(`Using mock data for ${api.name}`);
      return Promise.resolve(api.mock);
    }

    let url = `${basePath}${api.url}`;
    
    // 处理路径参数
    if (params.pathParams) {
      Object.keys(params.pathParams).forEach(key => {
        url = url.replace(`:${key}`, encodeURIComponent(params.pathParams![key]));
      });
    }

    // 处理查询参数
    if (params.queryParams) {
      const queryString = new URLSearchParams(params.queryParams).toString();
      url += `?${queryString}`;
    }

    // 合并配置
    const finalConfig: AxiosRequestConfig = {
      method: api.method,
      url,
      headers: {
        ...params.headers,
        ...options.headers
      },
      ...options
    };

    // 处理请求体
    if (['POST', 'PUT', 'PATCH'].includes(api.method) {
      finalConfig.data = params.body;
    }

    try {
      const response = await this.axiosInstance.request(finalConfig);
      return response.data;
    } catch (error) {
      throw error;
    }
  }

  public addRequestInterceptor(interceptor: RequestInterceptor): void {
    this.requestInterceptors.push(interceptor);
  }

  public addResponseInterceptor(interceptor: ResponseInterceptor): void {
    this.responseInterceptors.push(interceptor);
  }

  public addErrorInterceptor(interceptor: ErrorInterceptor): void {
    this.errorInterceptors.push(interceptor);
  }

  public getModule<T = any>(moduleName: string): T {
    if (!this.modules[moduleName]) {
      throw new Error(`Module ${moduleName} not found`);
    }
    return this.modules[moduleName];
  }
}

export default APIManager;

1.3 API 配置示例

config/api-config.ts
import { APIConfig } from '@/types/api';

const apiConfig: APIConfig = {
  baseURL: process.env.VUE_APP_API_BASE_URL || '/api',
  timeout: 15000,
  globalHeaders: {
    'Content-Type': 'application/json'
  },
  modules: {
    user: {
      basePath: '/user',
      apis: [
        {
          name: 'login',
          method: 'POST',
          url: '/login',
          mock: { code: 200, message: 'success', data: { token: 'mock-token' }, timestamp: Date.now() }
        },
        {
          name: 'getUserInfo',
          method: 'GET',
          url: '/info/:id',
          mock: { code: 200, message: 'success', data: { id: 1, name: 'Mock User' }, timestamp: Date.now() }
        },
        {
          name: 'updateUser',
          method: 'PUT',
          url: '/update'
        }
      ]
    },
    product: {
      basePath: '/product',
      apis: [
        {
          name: 'getProductList',
          method: 'GET',
          url: '/list'
        },
        {
          name: 'getProductDetail',
          method: 'GET',
          url: '/detail/:id'
        }
      ]
    }
  }
};

export default apiConfig;

1.4 初始化实例

utils/api.ts
import APIManager from './api-manager';
import apiConfig from '../config/api-config';

// 创建 API 管理器实例
const apiManager = new APIManager(apiConfig);

// 添加全局请求拦截器 - 例如添加认证 token
apiManager.addRequestInterceptor((config) => {
  const token = localStorage.getItem('token');
  if (token) {
    config.headers = config.headers || {};
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

// 添加全局响应拦截器 - 例如处理通用响应格式
apiManager.addResponseInterceptor((response) => {
  if (response.data && response.data.code !== 200) {
    console.error(`API Error: ${response.data.message}`);
  }
  return response;
});

// 添加全局错误拦截器 - 例如处理 401 未授权
apiManager.addErrorInterceptor((error) => {
  if (error.response && error.response.status === 401) {
    // 跳转到登录页
    window.location.href = '/login';
  }
  return Promise.reject(error);
});

export default apiManager;

2. 使用示例

2.1 基本使用

import apiManager from '@/utils/api';

// 获取用户模块
const userModule = apiManager.getModule<{
  login: (params: { body: { username: string; password: string } }) => Promise<APIResponse<{ token: string }>>;
  getUserInfo: (params: { pathParams: { id: number } }) => Promise<APIResponse<{ id: number; name: string }>>;
  updateUser: (params: { body: { id: number; name: string } }) => Promise<APIResponse>;
}>('user');

// 获取产品模块
const productModule = apiManager.getModule<{
  getProductList: (params?: { queryParams?: { page: number; size: number } }) => Promise<APIResponse<any[]>>;
  getProductDetail: (params: { pathParams: { id: number } }) => Promise<APIResponse<any>>;
}>('product');

// 使用示例
async function exampleUsage() {
  try {
    // 用户登录
    const loginRes = await userModule.login({
      body: {
        username: 'admin',
        password: '123456'
      }
    });
    console.log('Login success:', loginRes.data.token);
    
    // 获取用户信息
    const userInfo = await userModule.getUserInfo({
      pathParams: { id: 1 }
    });
    console.log('User info:', userInfo.data);
    
    // 获取产品列表
    const products = await productModule.getProductList({
      queryParams: { page: 1, size: 10 }
    });
    console.log('Product list:', products.data);
    
    // 获取产品详情
    const productDetail = await productModule.getProductDetail({
      pathParams: { id: 123 }
    });
    console.log('Product detail:', productDetail.data);
  } catch (error) {
    console.error('API error:', error);
  }
}

2.2 在 Vue 中使用

import { ref } from 'vue';
import apiManager from '@/utils/api';
import type { APIResponse } from '@/types/api';

export function useApi() {
  const loading = ref(false);
  const error = ref<any>(null);
  
  const userModule = apiManager.getModule('user');
  const productModule = apiManager.getModule('product');
  
  const login = async (username: string, password: string) => {
    loading.value = true;
    error.value = null;
    try {
      const res = await userModule.login({
        body: { username, password }
      });
      return res.data;
    } catch (err) {
      error.value = err;
      throw err;
    } finally {
      loading.value = false;
    }
  };
  
  const getProductList = async (page: number = 1, size: number = 10) => {
    loading.value = true;
    try {
      const res = await productModule.getProductList({
        queryParams: { page, size }
      });
      return res.data;
    } catch (err) {
      error.value = err;
      throw err;
    } finally {
      loading.value = false;
    }
  };
  
  return {
    loading,
    error,
    login,
    getProductList
  };
}

2.3 在 React 中使用

src/hooks/useApi.ts
import { useState } from 'react';
import apiManager from '@/utils/api';
import type { APIResponse } from '@/types/api';

export function useApi() {
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<any>(null);
  
  const userModule = apiManager.getModule('user');
  const productModule = apiManager.getModule('product');
  
  const login = async (username: string, password: string) => {
    setLoading(true);
    setError(null);
    try {
      const res = await userModule.login({
        body: { username, password }
      });
      return res.data;
    } catch (err) {
      setError(err);
      throw err;
    } finally {
      setLoading(false);
    }
  };
  
  const getProductList = async (page: number = 1, size: number = 10) => {
    setLoading(true);
    try {
      const res = await productModule.getProductList({
        queryParams: { page, size }
      });
      return res.data;
    } catch (err) {
      setError(err);
      throw err;
    } finally {
      setLoading(false);
    }
  };
  
  return {
    loading,
    error,
    login,
    getProductList
  };
}