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
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|