177 lines
6.0 KiB
TypeScript
177 lines
6.0 KiB
TypeScript
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
|
||
})
|
||
}
|