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

177 lines
6.0 KiB
TypeScript
Raw Normal View History

2025-11-13 08:38:25 +00:00
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
}
2025-11-24 03:47:20 +00:00
config.headers["Accept-Language"] = "en";
2025-11-13 08:38:25 +00:00
// 获取设备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
})
}