import { Logger } from "winston" import loggerIns from "../log" interface NetRequestParams { url: string method: string queryParams?: any payload?: any additionalHeaders?: any } interface NetErrorDetail { httpStatus: number code: number message: string } export class NetError extends Error { public code: number public message: string public httpStatus: number constructor({ code, message, httpStatus }: NetErrorDetail) { super(message) this.code = code this.message = message this.httpStatus = httpStatus } } /** * 网络工具类,提供发送HTTP请求的方法。 */ class NetToolBase { protected prefix: string protected headers: any protected getHeaders: () => any protected logger: Logger protected requestId: string /** * 创建一个网络工具类实例。 * * @param {Object} params - 构造函数参数。 * @param {string} [params.prefix] - URL前缀。 * @param {any} [params.headers] - 默认请求头。 * @param {Function} [params.getHeaders] - 获取请求头的方法。 * @param {string} [params.requestId] - 请求ID。 */ constructor({ prefix, headers, getHeaders, requestId, }: { prefix?: string headers?: any getHeaders?: () => any requestId?: string } = {}) { this.prefix = prefix || "" this.headers = headers || {} this.getHeaders = getHeaders || (() => ({})) this.requestId = requestId || "" this.logger = loggerIns.child({ requestId }) } /** * 记录响应详情并返回响应日志对象。 * @param response - 响应对象。 * @param method - 请求使用的HTTP方法。 * @param headers - 请求头。 * @param requestBody - 请求体。 * @param responseBody - 响应体。 * @returns 响应日志对象。 */ private logResponse( response: Response, method: string, headers: any, requestBody: any, responseBody: any ) { const responseLog = { ok: response.ok, status: response.status, statusText: response.statusText, url: response.url, method: method, requestHeaders: headers, responseHeaders: response.headers, requestBody, responseBody, } this.logger.http(JSON.stringify(responseLog, null, 2)) return responseLog } /** * 发送网络请求并返回一个解析为响应数据的Promise。 * @param url - 要发送请求的URL。 * @param method - 请求使用的HTTP方法。 * @param queryParams - 要包含在URL中的查询参数。 * @param payload - 请求的有效负载数据。 * @param additionalHeaders - 要包含在请求中的附加头。 * @returns 一个解析为响应数据的Promise。 * @throws 如果网络响应不成功或存在解析错误,则抛出错误。 */ protected async request({ url, method, queryParams, payload, additionalHeaders, }: NetRequestParams): Promise { // 拼接完整的URL let fullUrl = `${this.prefix}${url}` if (queryParams) { if (typeof queryParams === "string") { fullUrl = `${fullUrl}?${queryParams}` } else { const queryString = new URLSearchParams(queryParams).toString() if (queryString) fullUrl = `${fullUrl}?${queryString}` } } // 设置请求头 const headers = { ...this.headers, ...(await this.getHeaders()), ...additionalHeaders, } // 设置请求Header if (!(payload instanceof FormData)) { headers["Content-Type"] = "application/json" } // 处理请求数据 const body = payload instanceof FormData ? payload : JSON.stringify(payload) // 发送请求 const res = await fetch(fullUrl, { method, body, headers, }) // 获取响应数据 let resData: any = null let resText: string = "" try { resText = await res.text() resData = JSON.parse(resText) } catch { /* empty */ } // 记录响应 this.logResponse(res, method, headers, payload, resData || resText) if (!res.ok) { if (resData?.message || resData?.msg) { throw new NetError({ httpStatus: res.status, code: resData?.code, message: resData?.message || resData?.msg, }) } throw new NetError({ httpStatus: res.status, code: res.status, message: resText || "网络响应异常", }) } // http 错误码正常,但解析异常 if (!resData) { throw new NetError({ httpStatus: res.status, code: 1, message: "解析响应数据异常", }) } // 响应数据异常 if ("code" in resData && resData.code !== 0) { throw new NetError({ httpStatus: res.status, code: resData.code, message: resData.message || resData.msg || "网络请求失败", }) } return resData as T } /** * 发送GET请求并返回一个解析为响应数据的Promise。 * * @param url - 要发送请求的URL。 * @param queryParams - 要包含在URL中的查询参数。 * @param additionalHeaders - 要包含在请求中的附加头。 * @returns 一个解析为响应数据的Promise。 */ protected get( url: string, queryParams?: any, additionalHeaders?: any ): Promise { return this.request({ url, method: "get", queryParams, additionalHeaders }) } /** * 发送POST请求并返回一个解析为响应数据的Promise。 * * @param url - 要发送请求的URL。 * @param payload - 请求的有效负载数据。 * @param queryParams - 要包含在URL中的查询参数。 * @param additionalHeaders - 要包含在请求中的附加头。 * @returns 一个解析为响应数据的Promise。 */ protected post( url: string, payload?: any, queryParams?: any, additionalHeaders?: any ): Promise { return this.request({ url, method: "post", payload, queryParams, additionalHeaders, }) } /** * 发送PUT请求并返回一个解析为响应数据的Promise。 * * @param url - 要发送请求的URL。 * @param payload - 请求的有效负载数据。 * @param queryParams - 要包含在URL中的查询参数。 * @param additionalHeaders - 要包含在请求中的附加头。 * @returns 一个解析为响应数据的Promise。 */ protected put( url: string, payload: any, queryParams?: any, additionalHeaders?: any ): Promise { return this.request({ url, method: "put", payload, queryParams, additionalHeaders, }) } /** * 发送DELETE请求并返回一个解析为响应数据的Promise。 * * @param url - 要发送请求的URL。 * @param payload - 请求的有效负载数据。 * @param queryParams - 要包含在URL中的查询参数。 * @param additionalHeaders - 要包含在请求中的附加头。 * @returns 一个解析为响应数据的Promise。 */ protected del( url: string, payload: any, queryParams?: any, additionalHeaders?: any ): Promise { return this.request({ url, method: "delete", payload, queryParams, additionalHeaders, }) } /** * 发送PATCH请求并返回一个解析为响应数据的Promise。 * * @param url - 要发送请求的URL。 * @param payload - 请求的有效负载数据。 * @param queryParams - 要包含在URL中的查询参数。 * @param additionalHeaders - 要包含在请求中的附加头。 * @returns 一个解析为响应数据的Promise。 */ protected patch( url: string, payload: any, queryParams?: any, additionalHeaders?: any ): Promise { return this.request({ url, method: "patch", payload, queryParams, additionalHeaders, }) } } class NetTool extends NetToolBase { public request({ url, method, queryParams, payload, additionalHeaders, }: NetRequestParams): Promise { return super.request({ url, method, queryParams, payload, additionalHeaders, }) } public get( url: string, queryParams?: any, additionalHeaders?: any ): Promise { return super.get(url, queryParams, additionalHeaders) } public post( url: string, payload?: any, queryParams?: any, additionalHeaders?: any ): Promise { return super.post(url, payload, queryParams, additionalHeaders) } public put( url: string, payload: any, queryParams?: any, additionalHeaders?: any ): Promise { return super.put(url, payload, queryParams, additionalHeaders) } public del( url: string, payload: any, queryParams?: any, additionalHeaders?: any ): Promise { return super.del(url, payload, queryParams, additionalHeaders) } public patch( url: string, payload: any, queryParams?: any, additionalHeaders?: any ): Promise { return super.patch(url, payload, queryParams, additionalHeaders) } /** * 创建一个表示400 Bad Request的响应对象。 * * @param message - 错误消息。 * @returns 一个表示400 Bad Request的响应对象。 */ badRequest(message: string) { this.logger.error(`return a bad request response: ${message}`) return Response.json( { code: 400, message, requestId: this.requestId }, { status: 400 } ) } /** * 创建一个表示404 Not Found的响应对象。 * * @param message - 错误消息。 * @returns 一个表示404 Not Found的响应对象。 */ notFound(message: string) { this.logger.error(`return a not found response: ${message}`) return Response.json( { code: 404, message, requestId: this.requestId }, { status: 404 } ) } /** * 创建一个表示500 Internal Server Error的响应对象。 * * @param message - 错误消息。 * @param data - 错误数据。 * @returns 一个表示500 Internal Server Error的响应对象。 */ serverError(message: string, data?: any) { this.logger.error(`return a server error response: ${message}`) return Response.json( { code: 500, message, data, requestId: this.requestId }, { status: 500 } ) } /** * 创建一个表示200 OK的响应对象。 * * @param data - 响应数据。 * @returns 一个表示200 OK的响应对象。 */ ok(data?: any) { this.logger.info(`return a ok response: ${JSON.stringify(data)}`) return Response.json({ code: 0, message: "success", data, requestId: this.requestId, }) } } export { NetToolBase } export default NetTool