crush-level-web/src/lib/http/create-http-client.ts

177 lines
6.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import axios, { type AxiosInstance, type CreateAxiosDefaults, type AxiosRequestConfig } from "axios"
import { tokenManager } from "@/lib/auth/token"
import { toast } from "sonner"
import type { ApiResponse } from "@/types/api"
import { API_STATUS, ApiError } from "@/types/api"
import {
type EncryptionConfig,
DEFAULT_ENCRYPTION_CONFIG,
encryptData,
shouldEncryptRequest
} from "@/lib/encryption/encryption"
// 扩展 AxiosRequestConfig 以支持 ignoreError 选项
export interface ExtendedAxiosRequestConfig extends AxiosRequestConfig {
ignoreError?: boolean
}
// 扩展 AxiosInstance 类型以支持带有 ignoreError 的请求
export interface ExtendedAxiosInstance extends AxiosInstance {
post<T = any, R = T, D = any>(url: string, data?: D, config?: ExtendedAxiosRequestConfig): Promise<R>
get<T = any, R = T>(url: string, config?: ExtendedAxiosRequestConfig): Promise<R>
put<T = any, R = T, D = any>(url: string, data?: D, config?: ExtendedAxiosRequestConfig): Promise<R>
delete<T = any, R = T>(url: string, config?: ExtendedAxiosRequestConfig): Promise<R>
patch<T = any, R = T, D = any>(url: string, data?: D, config?: ExtendedAxiosRequestConfig): Promise<R>
}
export interface CreateHttpClientConfig extends CreateAxiosDefaults {
serviceName: string
baseURL?: string
cookieString?: string // 用于服务端渲染时传递cookie
showErrorToast?: boolean // 是否自动显示错误提示默认为true
encryption?: EncryptionConfig // 加密配置
}
const endpoints = {
frog: process.env.NEXT_PUBLIC_FROG_API_URL,
bear: process.env.NEXT_PUBLIC_BEAR_API_URL,
lion: process.env.NEXT_PUBLIC_LION_API_URL,
shark: process.env.NEXT_PUBLIC_SHARK_API_URL,
cow: process.env.NEXT_PUBLIC_COW_API_URL,
pigeon: process.env.NEXT_PUBLIC_PIGEON_API_URL,
}
export function createHttpClient(config: CreateHttpClientConfig): ExtendedAxiosInstance {
const { serviceName, baseURL, cookieString, showErrorToast = true, encryption = DEFAULT_ENCRYPTION_CONFIG, ...axiosConfig } = config
// 从环境变量获取服务地址,如果没有则使用默认地址
const serviceBaseURL = endpoints[serviceName as keyof typeof endpoints] ||
baseURL ||
`http://localhost:3000/api/${serviceName}`
const instance = axios.create({
baseURL: serviceBaseURL,
timeout: 120000,
headers: {
"Content-Type": "application/json",
"Platform": "web",
"Versionnum": "100",
},
...axiosConfig
})
// 请求拦截器
instance.interceptors.request.use(
(config) => {
// 获取token - 支持服务端和客户端
const token = tokenManager.getToken(cookieString)
if (token) {
// Java后端使用AUTH_TK字段接收token
config.headers["AUTH_TK"] = token
}
config.headers["Accept-Language"] = "en";
// 获取设备ID - 支持服务端和客户端
const deviceId = tokenManager.getDeviceId(cookieString)
if (deviceId) {
// Java后端使用AUTH_DID字段接收设备ID
config.headers["AUTH_DID"] = deviceId
}
// 添加服务标识
config.headers["X-Service"] = serviceName
// 服务端渲染时传递cookie
if (typeof window === "undefined" && cookieString) {
config.headers.Cookie = cookieString
}
// 加密处理
if (encryption.enabled && token && shouldEncryptRequest(config.data, encryption)) {
try {
console.log('config.data', config.data)
// 加密请求数据
const encryptedData = encryptData(config.data, token, encryption)
config.data = encryptedData
// // 添加加密标识头
// config.headers[encryption.encryptHeader] = 'true'
// 设置内容类型为JSON
config.headers['Content-Type'] = 'application/json'
} catch (error) {
console.error('Request encryption failed:', error)
// 加密失败时继续使用原始数据
}
}
return config
},
(error) => {
return Promise.reject(error)
}
)
// 响应拦截器
instance.interceptors.response.use(
(response) => {
const apiResponse = response.data as ApiResponse
// 检查业务状态
if (apiResponse.status === API_STATUS.OK) {
// 成功返回content内容
return apiResponse.content
} else {
// 检查是否忽略错误
const ignoreError = (response.config as ExtendedAxiosRequestConfig)?.ignoreError
// 业务错误创建ApiError并抛出
const apiError = new ApiError(
apiResponse.errorCode,
apiResponse.errorMsg,
apiResponse.traceId,
!!ignoreError
)
// 错误提示由providers.tsx中的全局错误处理统一管理
// 这里不再直接显示toast避免重复弹窗
return Promise.reject(apiError)
}
},
(error) => {
// 检查是否忽略错误
const ignoreError = (error.config as ExtendedAxiosRequestConfig)?.ignoreError
// 网络错误或其他HTTP错误
let errorMessage = 'Network exception, please try again later'
let errorCode = 'NETWORK_ERROR'
// 创建标准化的错误对象
const traceId = error.response?.headers?.['x-trace-id'] || 'unknown'
const apiError = new ApiError(errorCode, errorMessage, traceId, !!ignoreError)
return Promise.reject(apiError)
}
)
return instance as ExtendedAxiosInstance
}
// 为服务端渲染创建带cookie的HTTP客户端
export function createServerHttpClient(serviceName: string, cookieString: string): ExtendedAxiosInstance {
return createHttpClient({
serviceName,
cookieString,
showErrorToast: false // 服务端不显示toast
})
}
// 创建不显示错误提示的HTTP客户端用于静默请求
export function createSilentHttpClient(serviceName: string): ExtendedAxiosInstance {
return createHttpClient({
serviceName,
showErrorToast: false
})
}