一个项目有多个后端地址,每个后端地址的请求拦截器和响应拦截器都不一样,该怎么封装【进阶版】

94 阅读4分钟

针对多后端地址且拦截器需求不同的场景,通过面向对象编程中的​​继承​​和​​多态​​来组织代码确实是一个清晰且可维护性高的方案。无论是 TypeScript 还是 JavaScript,核心思想都是定义一个基础的请求类,然后为每个特定的后端服务创建专属的子类,在子类中实现各自独特的拦截逻辑。 以下是具体的封装方案和代码示例。

💡 设计思路概述

  1. ​基础类 (BaseRequest)​​:负责公共配置(如默认超时时间)和基础方法(如 get, post)。它声明了拦截器的“骨架”或默认行为。
  2. ​子类 (例如 ServiceARequest, ServiceBRequest)​​:继承基础类。每个子类对应一个特定的后端地址(baseURL),并在自己的构造函数中设置该地址和各自独有的拦截器逻辑。

这样,当你在项目的不同模块中使用 serviceA.request()serviceB.request()时,它们会自动指向正确的后端地址并应用相应的拦截器。

使用 TypeScript 实现

在 TypeScript 中,我们可以利用​​抽象类​​和​​泛型​​来构建一个类型安全、约束力强的结构。

// 1. 定义拦截器接口和配置类型
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios';

// 定义拦截器接口,子类可以选择性地实现这些方法
export interface Interceptors {
  onRequest?(config: InternalAxiosRequestConfig): InternalAxiosRequestConfig | Promise<InternalAxiosRequestConfig>;
  onRequestError?(error: any): any;
  onResponse?(response: AxiosResponse): AxiosResponse | Promise<AxiosResponse>;
  onResponseError?(error: any): any;
}

// 扩展 AxiosRequestConfig,允许传入自定义拦截器
export interface RequestConfig extends AxiosRequestConfig {
  interceptors?: Interceptors;
}

// 2. 创建抽象基类
export abstract class BaseRequest {
  protected instance: AxiosInstance;
  protected abstract getBaseURL(): string; // 抽象方法,强制子类提供 baseURL

  constructor(config: RequestConfig = {}) {
    // 创建 Axios 实例,合并传入的配置和基类的基本配置
    this.instance = axios.create({
      timeout: 10000,
      ...config, // 用户自定义配置优先
      baseURL: this.getBaseURL(), // 使用子类提供的 baseURL
    });

    this.setupInterceptors(config.interceptors);
  }

  // 设置拦截器
  private setupInterceptors(interceptors?: Interceptors) {
    // 实例级别的请求拦截器
    this.instance.interceptors.request.use(
      (config: InternalAxiosRequestConfig) => {
        // 先执行子类自定义的请求拦截器
        if (interceptors?.onRequest) {
          return interceptors.onRequest(config);
        }
        return config;
      },
      (error) => {
        // 请求错误处理
        if (interceptors?.onRequestError) {
          return interceptors.onRequestError(error);
        }
        return Promise.reject(error);
      }
    );

    // 实例级别的响应拦截器
    this.instance.interceptors.response.use(
      (response: AxiosResponse) => {
        // 先执行子类自定义的响应拦截器
        if (interceptors?.onResponse) {
          return interceptors.onResponse(response);
        }
        return response;
      },
      (error) => {
        // 响应错误处理
        if (interceptors?.onResponseError) {
          return interceptors.onResponseError(error);
        }
        return Promise.reject(error);
      }
    );
  }

  // 公共请求方法
  public request<T = any>(config: RequestConfig): Promise<T> {
    return this.instance.request(config);
  }

  public get<T = any>(url: string, config?: RequestConfig): Promise<T> {
    return this.request({ ...config, method: 'GET', url });
  }

  public post<T = any>(url: string, data?: any, config?: RequestConfig): Promise<T> {
    return this.request({ ...config, method: 'POST', url, data });
  }

  // 可以继续封装 put, delete 等方法...
}
// 3. 创建针对不同后端的子类
import { BaseRequest, RequestConfig } from './base-request';

// 服务 A 的请求类
class ServiceARequest extends BaseRequest {
  protected getBaseURL(): string {
    return 'https://api.service-a.com/v1'; // 后端 A 的地址
  }

  // 可以在此类的构造函数中注入服务 A 特有的拦截器
  constructor(config: RequestConfig = {}) {
    const serviceAInterceptors: Interceptors = {
      onRequest: (config) => {
        console.log('Service A 请求拦截器: 添加 A 服务的特定认证');
        const token = localStorage.getItem('token_a');
        if (token) {
          config.headers.Authorization = `Bearer ${token}`;
        }
        return config;
      },
      onResponse: (response) => {
        console.log('Service A 响应拦截器: 处理 A 服务的特定数据格式');
        // 假设服务 A 的数据包裹在 result 字段里
        return { ...response, data: response.data.result };
      },
      onResponseError: (error) => {
        console.error('Service A 响应错误:', error);
        // 可以在这里处理服务 A 特定的错误码,如 401 跳转到 A 的登录页
        if (error.response?.status === 401) {
          window.location.href = '/login-a';
        }
        return Promise.reject(error);
      },
    };

    super({
      ...config,
      interceptors: { ...serviceAInterceptors, ...config.interceptors }, // 合并拦截器
    });
  }
}

// 服务 B 的请求类
class ServiceBRequest extends BaseRequest {
  protected getBaseURL(): string {
    return 'https://api.service-b.com/v2'; // 后端 B 的地址
  }

  constructor(config: RequestConfig = {}) {
    const serviceBInterceptors: Interceptors = {
      onRequest: (config) => {
        console.log('Service B 请求拦截器: 添加 B 服务的 API Key');
        config.headers['X-API-Key'] = 'your-service-b-api-key';
        return config;
      },
      onResponseError: (error) => {
        console.error('Service B 响应错误:', error);
        // 服务 B 的特定错误处理,例如弹窗提示
        if (error.response?.status === 403) {
          alert('您没有权限访问服务 B 的资源');
        }
        return Promise.reject(error);
      },
    };

    super({
      ...config,
      interceptors: { ...serviceBInterceptors, ...config.interceptors },
    });
  }
}

// 4. 创建实例并导出
export const serviceA = new ServiceARequest();
export const serviceB = new ServiceBRequest();
// 5. 在项目中使用
// 在需要调用服务 A 的模块中
import { serviceA } from './http-services';

async function fetchDataFromServiceA() {
  try {
    const data = await serviceA.get('/users');
    console.log('Service A 返回的数据:', data);
  } catch (error) {
    console.error('从 Service A 获取数据失败:', error);
  }
}

// 在需要调用服务 B 的模块中
import { serviceB } from './http-services';

async function postDataToServiceB() {
  try {
    const result = await serviceB.post('/orders', { productId: 123 });
    console.log('提交到 Service B 成功:', result);
  } catch (error) {
    console.error('提交到 Service B 失败:', error);
  }
}

使用现代 JavaScript (ES6+) 实现

对于 JavaScript 项目,思路完全一致,只是不需要类型注解和抽象类语法。我们可以使用普通的类和继承。

// base-request.js
import axios from 'axios';

export class BaseRequest {
  constructor(config = {}) {
    // 子类必须通过 getBaseURL 方法提供 baseURL
    if (!config.baseURL) {
      config.baseURL = this.getBaseURL ? this.getBaseURL() : '';
    }

    this.instance = axios.create({
      timeout: 10000,
      ...config,
    });

    this.setupInterceptors(config.interceptors);
  }

  setupInterceptors(interceptors = {}) {
    // 请求拦截器
    this.instance.interceptors.request.use(
      (config) => (interceptors.onRequest ? interceptors.onRequest(config) : config),
      (error) => (interceptors.onRequestError ? interceptors.onRequestError(error) : Promise.reject(error))
    );

    // 响应拦截器
    this.instance.interceptors.response.use(
      (response) => (interceptors.onResponse ? interceptors.onResponse(response) : response),
      (error) => (interceptors.onResponseError ? interceptors.onResponseError(error) : Promise.reject(error))
    );
  }

  request(config) {
    return this.instance.request(config);
  }

  get(url, config = {}) {
    return this.request({ ...config, method: 'GET', url });
  }

  post(url, data, config = {}) {
    return this.request({ ...config, method: 'POST', url, data });
  }

  // ... 其他方法
}
// http-services.js
import { BaseRequest } from './base-request';

class ServiceARequest extends BaseRequest {
  // 提供基类需要的 baseURL
  getBaseURL() {
    return 'https://api.service-a.com/v1';
  }

  constructor(config = {}) {
    const serviceAInterceptors = {
      onRequest: (config) => {
        const token = localStorage.getItem('token_a');
        if (token) {
          config.headers.Authorization = `Bearer ${token}`;
        }
        return config;
      },
      onResponse: (response) => {
        // 处理服务 A 的数据格式
        return { ...response, data: response.data.result };
      },
    };

    super({ ...config, interceptors: { ...serviceAInterceptors, ...config.interceptors } });
  }
}

class ServiceBRequest extends BaseRequest {
  getBaseURL() {
    return 'https://api.service-b.com/v2';
  }

  constructor(config = {}) {
    const serviceBInterceptors = {
      onRequest: (config) => {
        config.headers['X-API-Key'] = 'your-service-b-api-key';
        return config;
      },
    };

    super({ ...config, interceptors: { ...serviceBInterceptors, ...config.interceptors } });
  }
}

export const serviceA = new ServiceARequest();
export const serviceB = new ServiceBRequest();

✨ 方案优势与最佳实践

  • ​高内聚低耦合​​:每个后端的配置和逻辑封装在各自的类中,职责清晰,修改互不影响。
  • ​易于扩展​​:新增一个后端服务,只需创建一个新的子类即可。
  • ​类型安全 (TS)​​:TypeScript 版本提供了完整的类型提示,减少了运行时错误。
  • ​灵活配置​​:即使在子类固化后,在创建实例时仍可传入临时配置覆盖默认值,提供了灵活性。

​最佳实践建议​​:

  1. ​统一导出​​:在一个文件(如 http-services.js/ts)中创建并导出所有服务实例,方便统一管理。
  2. ​错误处理​​:在拦截器的 onResponseErroronRequestError中,根据不同的后端需求进行差异化的错误处理(如提示消息、跳转登录页等)。
  3. ​默认配置​​:在基类中设置合理的全局默认配置(如超时时间)。

这种基于继承的封装模式清晰地将不同后端的请求隔离开,是管理多后端地址项目的有效策略。希望这些示例能帮助你更好地组织你的项目代码!如果你对某个细节有进一步的疑问,我们可以继续探讨。