blob: 623ee78666c77b4e9d950ea08aeeda6d57ab0228 [file] [log] [blame]
type RequestOptions = {
method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
headers?: Record<string, string>;
body?: unknown;
queryParams?: Record<string, string | number>;
};
type Interceptor = {
request?: (config: RequestInit) => RequestInit;
requestError?: (error: unknown) => Promise<never>; // 错误拦截器应始终抛出
response?: (response: Response) => Response | Promise<Response>;
// 错误拦截器必须抛出或返回与请求相同类型的结果
responseError?: <T>(error: unknown) => Promise<T>;
};
export class HttpClient {
private static instance: HttpClient;
private baseUrl: string;
private interceptors: Interceptor[] = [];
private constructor(baseUrl: string) {
this.baseUrl = baseUrl;
}
public static getInstance(baseUrl: string): HttpClient {
if (!HttpClient.instance) {
HttpClient.instance = new HttpClient(baseUrl);
}
return HttpClient.instance;
}
// 添加拦截器
addInterceptor(interceptor: Interceptor) {
this.interceptors.push(interceptor);
return this; // 支持链式调用
}
async request<T>(endpoint: string, options: RequestOptions = {}): Promise<T> {
const {
method = 'GET',
headers = {},
body,
queryParams = {}
} = options;
const url = new URL(`${this.baseUrl}${endpoint}`);
Object.entries(queryParams).forEach(([key, value]) => {
url.searchParams.append(key, String(value));
});
let config: RequestInit = {
method,
headers: {
'Content-Type': 'application/json',
...headers
}
};
if (body) {
config.body = JSON.stringify(body);
}
// 请求拦截器处理
for (const interceptor of this.interceptors) {
if (interceptor.request) {
config = interceptor.request(config);
}
}
try {
let response = await fetch(url.toString(), config);
// 响应拦截器处理
for (const interceptor of this.interceptors) {
if (interceptor.response) {
response = await interceptor.response(response);
}
}
if (!response.ok) {
const errorData = await response.json();
throw new Error(`HTTP error! Status: ${response.status}, Message: ${errorData.message || 'Unknown error'}`);
}
return await response.json();
} catch (error) {
// 改进的错误拦截器处理
let lastError = error;
for (const interceptor of this.interceptors) {
if (interceptor.responseError) {
try {
// 错误拦截器必须返回Promise<T>或抛出
return await interceptor.responseError<T>(lastError);
} catch (e) {
// 保存最新错误并继续下一个拦截器
lastError = e;
console.error('Interceptor error:', e);
}
}
}
// 如果所有拦截器都未能处理错误,则抛出最后一个错误
throw lastError;
}
}
get<T>(endpoint: string, queryParams?: Record<string, string | number>) {
return this.request<T>(endpoint, { queryParams });
}
post<T>(endpoint: string, body?: unknown, headers?: Record<string, string>) {
return this.request<T>(endpoint, { method: 'POST', body, headers });
}
put<T>(endpoint: string, body?: unknown, headers?: Record<string, string>) {
return this.request<T>(endpoint, { method: 'PUT', body, headers });
}
delete<T>(endpoint: string, headers?: Record<string, string>) {
return this.request<T>(endpoint, { method: 'DELETE', headers });
}
}
const client = HttpClient.getInstance('http://localhost:8080/');
// 添加Token注入拦截器
client.addInterceptor({
request: (config) => {
const token = localStorage.getItem('token'); // 从存储中获取token
if (token) {
config.headers = {
...config.headers,
'Authorization': `Bearer ${token}`
};
}
return config;
},
responseError: async (error) => {
if (error instanceof Error && error.message.includes('401')) {
console.log('Token expired, redirecting to login...');
// 示例:重定向到登录页
window.location.href = '/login';
// 必须返回Promise<never>或抛出
return new Promise(() => {}); // 永远不解决的Promise
}
throw error;
}
});
export default client as HttpClient;