import { RequestConfig } from './types'
import { ApiErrorCode, ApiError, ErrorHandler } from './error-handler'
import { appConfig } from '@/app/config/index'
* HTTP客户端类 - 负责处理所有HTTP请求
* 使用单例模式确保全局只有一个实例
*/
class HttpClient {
private static instance: HttpClient
private defaultHeaders: HeadersInit
private interceptors: {
request: Array<(req: RequestConfig) => RequestConfig>
response: Array<(res: Response) => Response>
}
private constructor() {
this.defaultHeaders = {
'Content-Type': 'application/json',
Accept: 'application/json',
}
this.interceptors = {
request: [
req => {
const authToken = appConfig.auth.token
console.log('Auth Token:', authToken ? '存在' : '不存在', authToken)
if (authToken) {
return {
...req,
headers: {
...req.headers,
Authorization: `Bearer ${authToken}`,
},
}
}
return req
},
],
response: [],
}
}
public static getInstance(): HttpClient {
if (!HttpClient.instance) {
HttpClient.instance = new HttpClient()
}
return HttpClient.instance
}
* 添加请求拦截器
*/
public addRequestInterceptor(interceptor: (req: RequestConfig) => RequestConfig) {
this.interceptors.request.push(interceptor)
}
* 添加响应拦截器
*/
public addResponseInterceptor(interceptor: (res: Response) => Response) {
this.interceptors.response.push(interceptor)
}
* 发起HTTP请求
*/
public async request<T>(requestConfig: RequestConfig): Promise<T> {
let processedConfig = { ...requestConfig }
for (const interceptor of this.interceptors.request) {
processedConfig = interceptor(processedConfig)
}
const url = `${appConfig.api.baseUrl}${processedConfig.url}`
const isFormData = processedConfig.data instanceof FormData
const baseHeaders: Record<string, string> = { ...this.defaultHeaders } as Record<string, string>
if (isFormData) {
delete baseHeaders['Content-Type']
}
const options: RequestInit = {
method: processedConfig.method || 'GET',
headers: {
...baseHeaders,
...processedConfig.headers,
},
body: isFormData
? processedConfig.data
: processedConfig.data
? JSON.stringify(processedConfig.data)
: undefined,
}
console.log('Final Request Options:', {
method: options.method,
url: url,
headers: options.headers,
body: options.body,
})
const requestTimeout = processedConfig.timeout
const configTimeout = appConfig.api.timeout
const DEFAULT_TIMEOUT = 120000
const timeout = requestTimeout || configTimeout || DEFAULT_TIMEOUT
const controller = new AbortController()
const timeoutId = setTimeout(() => {
controller.abort(new Error(`Request timed out after ${timeout}ms`))
}, timeout)
try {
const fetchPromise = fetch(url, {
...options,
signal: controller.signal,
})
let response = await fetchPromise
clearTimeout(timeoutId)
for (const interceptor of this.interceptors.response) {
response = interceptor(response)
}
console.log('HTTP Response:', {
status: response.status,
statusText: response.statusText,
headers: Object.fromEntries(response.headers.entries()),
})
if (!response.ok) {
const contentType = response.headers.get('content-type')
let errorData: any
if (contentType && contentType.includes('application/json')) {
errorData = await response.json()
} else {
errorData = await response.text()
}
console.error('Error Response:', errorData)
throw new ApiError(
`HTTP Error: ${response.status} ${response.statusText}`,
response.status === 401 ? ApiErrorCode.AUTH_ERROR : ApiErrorCode.SERVER_ERROR,
response.status,
errorData
)
}
const contentType = response.headers.get('content-type')
let responseData: any
if (contentType && contentType.includes('application/json')) {
responseData = await response.json()
console.log('Response Data:', responseData)
return responseData as T
} else {
responseData = await response.text()
console.log('Response Text:', responseData)
return responseData as unknown as T
}
} catch (error: unknown) {
clearTimeout(timeoutId)
console.error('原始错误:', error)
console.error('错误类型:', error instanceof Error ? error.constructor.name : 'unknown')
console.error('错误消息:', error instanceof Error ? error.message : 'unknown')
const handledError = ErrorHandler.handle(
error as Error,
`HTTP request to ${processedConfig.url}`
)
throw handledError
}
}
* GET请求
*/
public async get<T>(
url: string,
requestConfig?: Omit<RequestConfig, 'url' | 'method'>
): Promise<T> {
return this.request<T>({ ...requestConfig, url, method: 'GET' })
}
* POST请求
*/
public async post<T>(
url: string,
data?: any,
requestConfig?: Omit<RequestConfig, 'url' | 'method'>
): Promise<T> {
return this.request<T>({ ...requestConfig, url, method: 'POST', data })
}
* PUT请求
*/
public async put<T>(
url: string,
data?: any,
requestConfig?: Omit<RequestConfig, 'url' | 'method'>
): Promise<T> {
return this.request<T>({ ...requestConfig, url, method: 'PUT', data })
}
* DELETE请求
*/
public async delete<T>(
url: string,
requestConfig?: Omit<RequestConfig, 'url' | 'method'>
): Promise<T> {
return this.request<T>({ ...requestConfig, url, method: 'DELETE' })
}
* PATCH请求
*/
public async patch<T>(
url: string,
data?: any,
requestConfig?: Omit<RequestConfig, 'url' | 'method'>
): Promise<T> {
return this.request<T>({ ...requestConfig, url, method: 'PATCH', data })
}
}
export const httpClient = HttpClient.getInstance()