|
@@ -0,0 +1,196 @@
|
|
|
|
|
+import axios, {
|
|
|
|
|
+ type AxiosError,
|
|
|
|
|
+ type AxiosInstance,
|
|
|
|
|
+ type AxiosRequestConfig,
|
|
|
|
|
+ type AxiosResponse,
|
|
|
|
|
+ type InternalAxiosRequestConfig,
|
|
|
|
|
+} from "axios"
|
|
|
|
|
+
|
|
|
|
|
+// 定义通用的 API 响应接口
|
|
|
|
|
+interface ApiResponse<T = any> {
|
|
|
|
|
+ data: T
|
|
|
|
|
+ code: number
|
|
|
|
|
+ msg: string
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 扩展 Axios 请求配置
|
|
|
|
|
+interface RequestConfig extends AxiosRequestConfig {
|
|
|
|
|
+ timeout?: number
|
|
|
|
|
+ headers?: Record<string, string>
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 定义可能的错误类型
|
|
|
|
|
+type HttpClientError = AxiosError | Error
|
|
|
|
|
+
|
|
|
|
|
+class ServerHttpClient {
|
|
|
|
|
+ private instance: AxiosInstance
|
|
|
|
|
+
|
|
|
|
|
+ constructor() {
|
|
|
|
|
+ // 创建axios实例
|
|
|
|
|
+ this.instance = axios.create({
|
|
|
|
|
+ baseURL: process.env.NEXT_PUBLIC_API_BASE ?? "http://localhost:8080",
|
|
|
|
|
+ timeout: 10000,
|
|
|
|
|
+ headers: {
|
|
|
|
|
+ "Content-Type": "application/json",
|
|
|
|
|
+ },
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ this.setupRequestInterceptors()
|
|
|
|
|
+ this.setupResponseInterceptors()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private setupRequestInterceptors() {
|
|
|
|
|
+ this.instance.interceptors.request.use(
|
|
|
|
|
+ (config: InternalAxiosRequestConfig) => {
|
|
|
|
|
+ return config
|
|
|
|
|
+ },
|
|
|
|
|
+ (error: any) => {
|
|
|
|
|
+ return Promise.reject(error)
|
|
|
|
|
+ },
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private setupResponseInterceptors() {
|
|
|
|
|
+ this.instance.interceptors.response.use(
|
|
|
|
|
+ (response: AxiosResponse<ApiResponse<any>>) => {
|
|
|
|
|
+ return response
|
|
|
|
|
+ },
|
|
|
|
|
+ (error: AxiosError) => {
|
|
|
|
|
+ // 服务端只返回错误,不做 UI 提示
|
|
|
|
|
+ return Promise.reject(error)
|
|
|
|
|
+ },
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // GET 请求
|
|
|
|
|
+ async serverGet<TResponse = any, TParams extends Record<string, any> | null = Record<string, any> | null>(
|
|
|
|
|
+ url: string,
|
|
|
|
|
+ params?: TParams,
|
|
|
|
|
+ config?: RequestConfig & { arrayFormat?: "indices" | "repeat" | "comma" },
|
|
|
|
|
+ ): Promise<ApiResponse<TResponse>> {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await this.instance.get<ApiResponse<TResponse>>(url, {
|
|
|
|
|
+ params,
|
|
|
|
|
+ paramsSerializer: (p) => {
|
|
|
|
|
+ const parts: string[] = []
|
|
|
|
|
+ for (const key in p) {
|
|
|
|
|
+ const value = (p as any)[key]
|
|
|
|
|
+ if (Array.isArray(value)) {
|
|
|
|
|
+ switch (config?.arrayFormat ?? "indices") {
|
|
|
|
|
+ case "repeat":
|
|
|
|
|
+ value.forEach((v) => parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(v)}`))
|
|
|
|
|
+ break
|
|
|
|
|
+ case "comma":
|
|
|
|
|
+ parts.push(`${encodeURIComponent(key)}=${value.map((v) => encodeURIComponent(v)).join(",")}`)
|
|
|
|
|
+ break
|
|
|
|
|
+ case "indices":
|
|
|
|
|
+ default:
|
|
|
|
|
+ value.forEach((v, i) => parts.push(`${encodeURIComponent(key)}[${i}]=${encodeURIComponent(v)}`))
|
|
|
|
|
+ break
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (value !== undefined && value !== null) {
|
|
|
|
|
+ parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return parts.join("&")
|
|
|
|
|
+ },
|
|
|
|
|
+ ...config,
|
|
|
|
|
+ })
|
|
|
|
|
+ return response.data
|
|
|
|
|
+ } catch (error: any) {
|
|
|
|
|
+ throw this.handleError(error)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // POST 请求
|
|
|
|
|
+ async serverPost<TResponse = any, TData extends Record<string, any> | null = Record<string, any> | null>(
|
|
|
|
|
+ url: string,
|
|
|
|
|
+ data?: TData,
|
|
|
|
|
+ config?: RequestConfig,
|
|
|
|
|
+ ): Promise<ApiResponse<TResponse>> {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await this.instance.post<ApiResponse<TResponse>>(url, JSON.stringify(data), config)
|
|
|
|
|
+ return response.data
|
|
|
|
|
+ } catch (error: any) {
|
|
|
|
|
+ throw this.handleError(error)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // PUT 请求
|
|
|
|
|
+ async serverPut<TResponse = any, TData extends Record<string, any> | null = Record<string, any> | null>(
|
|
|
|
|
+ url: string,
|
|
|
|
|
+ data?: TData,
|
|
|
|
|
+ config?: RequestConfig,
|
|
|
|
|
+ ): Promise<ApiResponse<TResponse>> {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await this.instance.put<ApiResponse<TResponse>>(url, JSON.stringify(data), config)
|
|
|
|
|
+ return response.data
|
|
|
|
|
+ } catch (error: any) {
|
|
|
|
|
+ throw this.handleError(error)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // DELETE 请求
|
|
|
|
|
+ async serverDelete<TResponse = any, TParams extends Record<string, any> | null = Record<string, any> | null>(
|
|
|
|
|
+ url: string,
|
|
|
|
|
+ params?: TParams,
|
|
|
|
|
+ config?: RequestConfig & { arrayFormat?: "indices" | "repeat" | "comma" },
|
|
|
|
|
+ ): Promise<ApiResponse<TResponse>> {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await this.instance.delete<ApiResponse<TResponse>>(url, {
|
|
|
|
|
+ params,
|
|
|
|
|
+ paramsSerializer: (p) => {
|
|
|
|
|
+ const parts: string[] = []
|
|
|
|
|
+ for (const key in p) {
|
|
|
|
|
+ const value = (p as any)[key]
|
|
|
|
|
+ if (Array.isArray(value)) {
|
|
|
|
|
+ switch (config?.arrayFormat ?? "indices") {
|
|
|
|
|
+ case "repeat":
|
|
|
|
|
+ value.forEach((v) => parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(v)}`))
|
|
|
|
|
+ break
|
|
|
|
|
+ case "comma":
|
|
|
|
|
+ parts.push(`${encodeURIComponent(key)}=${value.map((v) => encodeURIComponent(v)).join(",")}`)
|
|
|
|
|
+ break
|
|
|
|
|
+ case "indices":
|
|
|
|
|
+ default:
|
|
|
|
|
+ value.forEach((v, i) => parts.push(`${encodeURIComponent(key)}[${i}]=${encodeURIComponent(v)}`))
|
|
|
|
|
+ break
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (value !== undefined && value !== null) {
|
|
|
|
|
+ parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return parts.join("&")
|
|
|
|
|
+ },
|
|
|
|
|
+ ...config,
|
|
|
|
|
+ })
|
|
|
|
|
+ return response.data
|
|
|
|
|
+ } catch (error: any) {
|
|
|
|
|
+ throw this.handleError(error)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 错误处理
|
|
|
|
|
+ private handleError(error: any): HttpClientError {
|
|
|
|
|
+ if (axios.isAxiosError(error)) {
|
|
|
|
|
+ const message = (error.response?.data as any)?.msg || error.message || "请求失败"
|
|
|
|
|
+ return new Error(message)
|
|
|
|
|
+ }
|
|
|
|
|
+ return error instanceof Error ? error : new Error("未知错误")
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ getInstance(): AxiosInstance {
|
|
|
|
|
+ return this.instance
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 创建服务端 HTTP 客户端实例
|
|
|
|
|
+const serverHttpClient = new ServerHttpClient()
|
|
|
|
|
+
|
|
|
|
|
+export const serverGet = serverHttpClient.serverGet.bind(serverHttpClient)
|
|
|
|
|
+export const serverPost = serverHttpClient.serverPost.bind(serverHttpClient)
|
|
|
|
|
+export const serverPut = serverHttpClient.serverPut.bind(serverHttpClient)
|
|
|
|
|
+export const serverDelete = serverHttpClient.serverDelete.bind(serverHttpClient)
|
|
|
|
|
+
|
|
|
|
|
+export type { ApiResponse, RequestConfig, HttpClientError }
|
|
|
|
|
+export { serverHttpClient }
|