丁烨烨 4 månader sedan
förälder
incheckning
44af284e4d
1 ändrade filer med 159 tillägg och 87 borttagningar
  1. 159 87
      src/utils/request.ts

+ 159 - 87
src/utils/request.ts

@@ -9,7 +9,7 @@ interface RequestConfig {
   timeout?: number
   headers?: Record<string, string>
   signal?: AbortSignal
-  // Next.js 缓存选项(仅服务端有效)
+  // Next.js 缓存选项
   cache?: "force-cache" | "no-store" | "no-cache" | "default"
   next?: {
     revalidate?: number | false
@@ -17,10 +17,8 @@ interface RequestConfig {
   }
 }
 
-// 扩展 GET/DELETE 请求的配置(增加数组参数格式化)
-interface GetDeleteRequestConfig extends RequestConfig {
-  arrayFormat?: "indices" | "repeat" | "comma"
-}
+// 定义可能的错误类型
+// type HttpClientError = Error
 
 // 定义可能的错误类型
 type HttpClientError = Error & {
@@ -28,11 +26,10 @@ type HttpClientError = Error & {
   originalError?: Error // 新增:保留原始错误
   isClientError?: boolean // 新增:标记是否为客户端错误
 }
-
 class ServerHttpClient {
-  private readonly baseURL: string
-  private readonly defaultTimeout: number
-  private readonly defaultHeaders: Record<string, string>
+  private baseURL: string
+  private defaultTimeout: number
+  private defaultHeaders: Record<string, string>
 
   constructor() {
     this.baseURL = process.env.NEXT_PUBLIC_API_BASE ?? "http://localhost:8080"
@@ -107,35 +104,12 @@ class ServerHttpClient {
       return responseData
     } catch (error) {
       clearTimeout(timeoutId) // 确保超时器被清理
-      throw this.handleError(error as Error, isClientRequest)
+      throw this.handleError(error as Error)
     }
   }
 
-  /**
-   * 服务端 GET 请求方法
-   */
-  async serverGet<TResponse = any, TParams extends Record<string, any> | null = null>(
-      url: string,
-      params?: TParams,
-      config?: GetDeleteRequestConfig,
-  ): Promise<ApiResponse<TResponse>> {
-    return this.request<TResponse>("GET", url, { params, config })
-  }
-
-  /**
-   * 服务端 POST 请求方法
-   */
-  async serverPost<TResponse = any, TData extends Record<string, any> | null = null>(
-      url: string,
-      data?: TData,
-      config?: RequestConfig,
-  ): Promise<ApiResponse<TResponse>> {
-    return this.request<TResponse>("POST", url, { data, config })
-  }
-
   /**
    * 客户端 POST 请求方法(新增)
-   * 适配浏览器环境,自动移除服务端缓存配置,优化错误提示
    */
   async clientPost<TResponse = any, TData extends Record<string, any> | null = null>(
       url: string,
@@ -149,115 +123,213 @@ class ServerHttpClient {
     })
   }
 
-  /**
-   * 服务端 PUT 请求方法
-   */
-  async serverPut<TResponse = any, TData extends Record<string, any> | null = null>(
+  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 fullUrl = this.buildUrl(url, params, config?.arrayFormat)
+      const controller = new AbortController()
+      const timeoutId = setTimeout(() => controller.abort(), config?.timeout ?? this.defaultTimeout)
+
+      const response = await fetch(fullUrl, {
+        method: "GET",
+        headers: {
+          ...this.defaultHeaders,
+          ...config?.headers,
+        },
+        signal: config?.signal ?? controller.signal,
+        cache: config?.cache,
+        next: config?.next,
+      })
+
+      clearTimeout(timeoutId)
+
+      if (!response.ok) {
+        throw new Error(`HTTP ${response.status}: ${response.statusText}`)
+      }
+
+      const data: ApiResponse<TResponse> = await response.json()
+      return data
+    } catch (error: any) {
+      throw this.handleError(error)
+    }
+  }
+
+  async serverPost<TResponse = any, TData extends Record<string, any> | null = Record<string, any> | null>(
       url: string,
       data?: TData,
       config?: RequestConfig,
   ): Promise<ApiResponse<TResponse>> {
-    return this.request<TResponse>("PUT", url, { data, config })
+    try {
+      const fullUrl = `${this.baseURL}${url}`
+      const controller = new AbortController()
+      const timeoutId = setTimeout(() => controller.abort(), config?.timeout ?? this.defaultTimeout)
+
+      const response = await fetch(fullUrl, {
+        method: "POST",
+        headers: {
+          ...this.defaultHeaders,
+          ...config?.headers,
+        },
+        body: data ? JSON.stringify(data) : undefined,
+        signal: config?.signal ?? controller.signal,
+        cache: config?.cache,
+        next: config?.next,
+      })
+
+      clearTimeout(timeoutId)
+
+      if (!response.ok) {
+        const errorData = await response.json().catch(() => ({}))
+        throw new Error((errorData as any)?.msg || `HTTP ${response.status}: ${response.statusText}`)
+      }
+
+      const responseData: ApiResponse<TResponse> = await response.json()
+      return responseData
+    } catch (error: any) {
+      throw this.handleError(error)
+    }
   }
 
-  /**
-   * 服务端 DELETE 请求方法
-   */
-  async serverDelete<TResponse = any, TParams extends Record<string, any> | null = null>(
+  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 fullUrl = `${this.baseURL}${url}`
+      const controller = new AbortController()
+      const timeoutId = setTimeout(() => controller.abort(), config?.timeout ?? this.defaultTimeout)
+
+      const response = await fetch(fullUrl, {
+        method: "PUT",
+        headers: {
+          ...this.defaultHeaders,
+          ...config?.headers,
+        },
+        body: data ? JSON.stringify(data) : undefined,
+        signal: config?.signal ?? controller.signal,
+        cache: config?.cache,
+        next: config?.next,
+      })
+
+      clearTimeout(timeoutId)
+
+      if (!response.ok) {
+        const errorData = await response.json().catch(() => ({}))
+        throw new Error((errorData as any)?.msg || `HTTP ${response.status}: ${response.statusText}`)
+      }
+
+      const responseData: ApiResponse<TResponse> = await response.json()
+      return responseData
+    } catch (error: any) {
+      throw this.handleError(error)
+    }
+  }
+
+  async serverDelete<TResponse = any, TParams extends Record<string, any> | null = Record<string, any> | null>(
       url: string,
       params?: TParams,
-      config?: GetDeleteRequestConfig,
+      config?: RequestConfig & { arrayFormat?: "indices" | "repeat" | "comma" },
   ): Promise<ApiResponse<TResponse>> {
-    return this.request<TResponse>("DELETE", url, { params, config })
+    try {
+      const fullUrl = this.buildUrl(url, params, config?.arrayFormat)
+      const controller = new AbortController()
+      const timeoutId = setTimeout(() => controller.abort(), config?.timeout ?? this.defaultTimeout)
+
+      const response = await fetch(fullUrl, {
+        method: "DELETE",
+        headers: {
+          ...this.defaultHeaders,
+          ...config?.headers,
+        },
+        signal: config?.signal ?? controller.signal,
+        cache: config?.cache,
+        next: config?.next,
+      })
+
+      clearTimeout(timeoutId)
+
+      if (!response.ok) {
+        const errorData = await response.json().catch(() => ({}))
+        throw new Error((errorData as any)?.msg || `HTTP ${response.status}: ${response.statusText}`)
+      }
+
+      const responseData: ApiResponse<TResponse> = await response.json()
+      return responseData
+    } catch (error: any) {
+      throw this.handleError(error)
+    }
   }
 
-  /**
-   * 构建带参数的 URL
-   */
   private buildUrl(
       url: string,
       params?: Record<string, any> | null,
       arrayFormat: "indices" | "repeat" | "comma" = "indices",
   ): string {
-    if (!params) return `${this.baseURL}${url}`
+    const fullUrl = `${this.baseURL}${url}`
 
-    const searchParams = new URLSearchParams()
+    if (!params) {
+      return fullUrl
+    }
 
-    for (const [key, value] of Object.entries(params)) {
-      if (value === undefined || value === null) continue
+    const searchParams = new URLSearchParams()
 
+    for (const key in params) {
+      const value = params[key]
       if (Array.isArray(value)) {
         switch (arrayFormat) {
           case "repeat":
-            value.forEach(v => searchParams.append(key, String(v)))
+            value.forEach((v) => searchParams.append(key, String(v)))
             break
           case "comma":
-            searchParams.set(key, value.map(String).join(","))
+            searchParams.set(key, value.map((v) => String(v)).join(","))
             break
           case "indices":
           default:
             value.forEach((v, i) => searchParams.set(`${key}[${i}]`, String(v)))
             break
         }
-      } else {
+      } else if (value !== undefined && value !== null) {
         searchParams.set(key, String(value))
       }
     }
 
     const queryString = searchParams.toString()
-    return queryString ? `${this.baseURL}${url}?${queryString}` : `${this.baseURL}${url}`
+    return queryString ? `${fullUrl}?${queryString}` : fullUrl
   }
 
-  /**
-   * 统一错误处理(区分客户端/服务端错误)
-   */
-  private handleError(error: Error, isClientRequest = false): HttpClientError {
-    const httpError = error as HttpClientError
-    httpError.originalError = { ...error } // 保留原始错误信息
-    httpError.isClientError = isClientRequest // 标记客户端错误
-
+  private handleError(error: any): HttpClientError {
     if (error.name === "AbortError") {
-      httpError.message = isClientRequest
-          ? "请求超时,请检查网络或稍后重试" // 客户端更友好的提示
-          : "请求超时"
-      httpError.statusCode = 408
-    } else if (error instanceof TypeError && error.message.includes("fetch")) {
-      httpError.message = isClientRequest
-          ? "网络连接失败,请检查您的网络设置" // 客户端更友好的提示
-          : "网络连接失败"
-      httpError.statusCode = 0
-    } else if (!httpError.statusCode) {
-      httpError.message = httpError.message || (isClientRequest
-          ? "请求失败,请联系客服"
-          : "未知错误,请联系管理员")
-      httpError.statusCode = 500
+      return new Error("请求超时")
     }
 
-    return httpError
+    if (error instanceof TypeError && error.message.includes("fetch")) {
+      return new Error("网络连接失败")
+    }
+
+    return error instanceof Error ? error : new Error("未知错误")
   }
 
-  /**
-   * 获取客户端配置信息
-   */
   getInstance() {
     return {
       baseURL: this.baseURL,
       timeout: this.defaultTimeout,
-      headers: { ...this.defaultHeaders }, // 返回拷贝,避免外部修改内部配置
+      headers: this.defaultHeaders,
     }
   }
 }
 
-// 创建服务端 HTTP 客户端实例(单例)
+// 创建服务端 HTTP 客户端实例
 const serverHttpClient = new ServerHttpClient()
 
-// 导出请求方法(绑定实例上下文)
 export const serverGet = serverHttpClient.serverGet.bind(serverHttpClient)
 export const serverPost = serverHttpClient.serverPost.bind(serverHttpClient)
-export const clientPost = serverHttpClient.clientPost.bind(serverHttpClient) // 导出新增的clientPost
 export const serverPut = serverHttpClient.serverPut.bind(serverHttpClient)
 export const serverDelete = serverHttpClient.serverDelete.bind(serverHttpClient)
+export const clientPost = serverHttpClient.clientPost.bind(serverHttpClient) // 导出新增的clientPost
 
-// 导出类型和实例
-export type { ApiResponse, RequestConfig, GetDeleteRequestConfig, HttpClientError }
+export type { ApiResponse, RequestConfig, HttpClientError }
 export { serverHttpClient }