| 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; |