492 lines
13 KiB
TypeScript
492 lines
13 KiB
TypeScript
import logger from "@egg/logger"
|
||
import { Logger } from "winston"
|
||
|
||
interface NetRequestParams {
|
||
url: string
|
||
method: string
|
||
queryParams?: any
|
||
payload?: any
|
||
additionalHeaders?: Record<string, string>
|
||
}
|
||
|
||
interface NetErrorDetail {
|
||
response: Response | null
|
||
code: number
|
||
message: string
|
||
data?: any
|
||
}
|
||
|
||
export class NetError extends Error {
|
||
public response: Response | null
|
||
public code: number
|
||
public message: string
|
||
public data?: any
|
||
|
||
constructor({ response, code, message, data }: NetErrorDetail) {
|
||
super(message)
|
||
this.response = response
|
||
this.code = code
|
||
this.message = message
|
||
this.data = data
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 网络工具类,提供发送HTTP请求的方法。
|
||
*/
|
||
class NetToolBase {
|
||
protected prefix: string
|
||
protected headers: Record<string, string>
|
||
protected getHeaders: () => Promise<Record<string, string>>
|
||
protected logger: Logger
|
||
protected requestId: string
|
||
|
||
/**
|
||
* 创建一个网络工具类实例。
|
||
*
|
||
* @param {Object} params - 构造函数参数。
|
||
* @param {string} [params.prefix] - URL前缀。
|
||
* @param {Record<string, string>} [params.headers] - 默认请求头。
|
||
* @param {Function} [params.getHeaders] - 获取请求头的方法。
|
||
* @param {string} [params.requestId] - 请求ID。
|
||
*/
|
||
constructor({
|
||
prefix,
|
||
headers,
|
||
getHeaders,
|
||
requestId,
|
||
}: {
|
||
prefix?: string
|
||
headers?: Record<string, string>
|
||
getHeaders?: () => Promise<Record<string, string>>
|
||
requestId?: string
|
||
} = {}) {
|
||
this.prefix = prefix || ""
|
||
this.headers = headers || {}
|
||
this.getHeaders = getHeaders || (async () => ({}))
|
||
this.requestId = requestId || ""
|
||
this.logger = logger.child({ requestId })
|
||
}
|
||
|
||
/**
|
||
* 创建一个网络工具类实例。
|
||
*
|
||
* @param {Object} params - 构造函数参数。
|
||
* @param {string} [params.prefix] - URL前缀。
|
||
* @param {Record<string, string>} [params.headers] - 默认请求头。
|
||
* @param {Function} [params.getHeaders] - 获取请求头的方法。
|
||
* @param {string} [params.requestId] - 请求ID。
|
||
* @returns 一个网络工具类实例。
|
||
*/
|
||
child({
|
||
prefix,
|
||
headers,
|
||
getHeaders,
|
||
requestId,
|
||
}: {
|
||
prefix?: string
|
||
headers?: Record<string, string>
|
||
getHeaders?: () => Promise<Record<string, string>>
|
||
requestId?: string
|
||
} = {}) {
|
||
return new NetToolBase({
|
||
prefix: prefix || this.prefix,
|
||
headers: headers || this.headers,
|
||
getHeaders: getHeaders || this.getHeaders,
|
||
requestId: requestId || this.requestId,
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 记录响应详情并返回响应日志对象。
|
||
* @param {string} url - 请求地址。
|
||
* @param {string} method - 请求使用的HTTP方法。
|
||
* @param {Record<string, string>} headers - 请求头。
|
||
* @param {any} requestBody - 请求体。
|
||
* @param {any} responseBody - 响应体。
|
||
* @param {number} requestTime - 请求时间。
|
||
* @param {Response} [response] - 响应对象。
|
||
* @returns 响应日志对象。
|
||
*/
|
||
private logResponse(
|
||
url: string,
|
||
method: string,
|
||
headers: Record<string, string>,
|
||
requestBody: any,
|
||
responseBody: any,
|
||
requestTime: number,
|
||
response?: Response
|
||
) {
|
||
const responseLog = {
|
||
ok: response?.ok,
|
||
status: response?.status,
|
||
statusText: response?.statusText,
|
||
url,
|
||
method: method,
|
||
requestHeaders: headers,
|
||
responseHeaders: response?.headers,
|
||
requestBody,
|
||
responseBody,
|
||
requestTime,
|
||
responseTime: new Date().getTime(),
|
||
}
|
||
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<T = any>({
|
||
url,
|
||
method,
|
||
queryParams,
|
||
payload,
|
||
additionalHeaders,
|
||
}: NetRequestParams): Promise<T> {
|
||
// 拼接完整的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 || {}),
|
||
} as Record<string, string>
|
||
|
||
// 设置请求Header
|
||
if (!(payload instanceof FormData)) {
|
||
headers["Content-Type"] = "application/json"
|
||
}
|
||
|
||
// 处理请求数据
|
||
const body = payload instanceof FormData ? payload : JSON.stringify(payload)
|
||
|
||
// 获取响应数据
|
||
let resData: any = null
|
||
let resText: string = ""
|
||
let res: Response
|
||
// 开始计时
|
||
const requestTime = new Date().getTime()
|
||
|
||
try {
|
||
res = await fetch(fullUrl, {
|
||
method,
|
||
body,
|
||
headers,
|
||
})
|
||
try {
|
||
resText = await res.text()
|
||
resData = JSON.parse(resText)
|
||
} catch {
|
||
/* empty */
|
||
}
|
||
} catch (error: any) {
|
||
// 网络请求异常,如请求超时、DNS解析失败、CORS请求被阻止等
|
||
this.logResponse(
|
||
fullUrl,
|
||
method,
|
||
headers,
|
||
payload,
|
||
resData || resText,
|
||
requestTime
|
||
)
|
||
throw new NetError({
|
||
response: null,
|
||
code: 1,
|
||
message: error.message || "网络请求异常",
|
||
})
|
||
}
|
||
// 记录响应
|
||
this.logResponse(
|
||
fullUrl,
|
||
method,
|
||
headers,
|
||
payload,
|
||
resData || resText,
|
||
requestTime,
|
||
res
|
||
)
|
||
// http 错误码异常
|
||
if (!res.ok) {
|
||
throw new NetError({
|
||
response: res,
|
||
code: resData.code || res.status,
|
||
message: resData.message || resData.msg || resText || res.statusText,
|
||
data: resData.data,
|
||
})
|
||
}
|
||
// http 错误码正常,但解析异常
|
||
if (!resData) {
|
||
throw new NetError({
|
||
response: res,
|
||
code: 1,
|
||
message: "解析响应数据异常",
|
||
})
|
||
}
|
||
// 响应数据异常
|
||
if ("code" in resData && resData.code !== 0) {
|
||
throw new NetError({
|
||
response: res,
|
||
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<T = any>(
|
||
url: string,
|
||
queryParams?: any,
|
||
additionalHeaders?: any
|
||
): Promise<T> {
|
||
return this.request({ url, method: "get", queryParams, additionalHeaders })
|
||
}
|
||
|
||
/**
|
||
* 发送POST请求并返回一个解析为响应数据的Promise。
|
||
*
|
||
* @param url - 要发送请求的URL。
|
||
* @param payload - 请求的有效负载数据。
|
||
* @param queryParams - 要包含在URL中的查询参数。
|
||
* @param additionalHeaders - 要包含在请求中的附加头。
|
||
* @returns 一个解析为响应数据的Promise。
|
||
*/
|
||
protected post<T = any>(
|
||
url: string,
|
||
payload?: any,
|
||
queryParams?: any,
|
||
additionalHeaders?: any
|
||
): Promise<T> {
|
||
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<T = any>(
|
||
url: string,
|
||
payload: any,
|
||
queryParams?: any,
|
||
additionalHeaders?: any
|
||
): Promise<T> {
|
||
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<T = any>(
|
||
url: string,
|
||
payload: any,
|
||
queryParams?: any,
|
||
additionalHeaders?: any
|
||
): Promise<T> {
|
||
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<T = any>(
|
||
url: string,
|
||
payload: any,
|
||
queryParams?: any,
|
||
additionalHeaders?: any
|
||
): Promise<T> {
|
||
return this.request({
|
||
url,
|
||
method: "patch",
|
||
payload,
|
||
queryParams,
|
||
additionalHeaders,
|
||
})
|
||
}
|
||
}
|
||
|
||
class NetTool extends NetToolBase {
|
||
public request<T = any>({
|
||
url,
|
||
method,
|
||
queryParams,
|
||
payload,
|
||
additionalHeaders,
|
||
}: NetRequestParams): Promise<T> {
|
||
return super.request<T>({
|
||
url,
|
||
method,
|
||
queryParams,
|
||
payload,
|
||
additionalHeaders,
|
||
})
|
||
}
|
||
public get<T = any>(
|
||
url: string,
|
||
queryParams?: any,
|
||
additionalHeaders?: any
|
||
): Promise<T> {
|
||
return super.get<T>(url, queryParams, additionalHeaders)
|
||
}
|
||
public post<T = any>(
|
||
url: string,
|
||
payload?: any,
|
||
queryParams?: any,
|
||
additionalHeaders?: any
|
||
): Promise<T> {
|
||
return super.post<T>(url, payload, queryParams, additionalHeaders)
|
||
}
|
||
public put<T = any>(
|
||
url: string,
|
||
payload: any,
|
||
queryParams?: any,
|
||
additionalHeaders?: any
|
||
): Promise<T> {
|
||
return super.put<T>(url, payload, queryParams, additionalHeaders)
|
||
}
|
||
public del<T = any>(
|
||
url: string,
|
||
payload: any,
|
||
queryParams?: any,
|
||
additionalHeaders?: any
|
||
): Promise<T> {
|
||
return super.del<T>(url, payload, queryParams, additionalHeaders)
|
||
}
|
||
public patch<T = any>(
|
||
url: string,
|
||
payload: any,
|
||
queryParams?: any,
|
||
additionalHeaders?: any
|
||
): Promise<T> {
|
||
return super.patch<T>(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,
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 创建一个表示200 OK的健康检查响应对象。
|
||
*
|
||
* @param message - 响应消息。
|
||
* @returns 一个表示200 OK的健康检查响应对象。
|
||
*/
|
||
healthCheck(message = "success") {
|
||
return Response.json({ code: 0, message })
|
||
}
|
||
}
|
||
|
||
export { NetTool, NetToolBase }
|