针对多后端地址且拦截器需求不同的场景,通过面向对象编程中的继承和多态来组织代码确实是一个清晰且可维护性高的方案。无论是 TypeScript 还是 JavaScript,核心思想都是定义一个基础的请求类,然后为每个特定的后端服务创建专属的子类,在子类中实现各自独特的拦截逻辑。 以下是具体的封装方案和代码示例。
💡 设计思路概述
- 基础类 (BaseRequest):负责公共配置(如默认超时时间)和基础方法(如
get,post)。它声明了拦截器的“骨架”或默认行为。 - 子类 (例如 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 版本提供了完整的类型提示,减少了运行时错误。
- 灵活配置:即使在子类固化后,在创建实例时仍可传入临时配置覆盖默认值,提供了灵活性。
最佳实践建议:
- 统一导出:在一个文件(如
http-services.js/ts)中创建并导出所有服务实例,方便统一管理。 - 错误处理:在拦截器的
onResponseError或onRequestError中,根据不同的后端需求进行差异化的错误处理(如提示消息、跳转登录页等)。 - 默认配置:在基类中设置合理的全局默认配置(如超时时间)。
这种基于继承的封装模式清晰地将不同后端的请求隔离开,是管理多后端地址项目的有效策略。希望这些示例能帮助你更好地组织你的项目代码!如果你对某个细节有进一步的疑问,我们可以继续探讨。