import axios, { AxiosError, type AxiosInstance, type AxiosRequestConfig, type AxiosResponse, type InternalAxiosRequestConfig, } from 'axios' import { handleKicked, isKickedPayload } from '../auth/kick-handler.ts' export interface LegacyApiSuccess { success: true msg?: string data?: T } export interface LegacyApiFailure { success: false error?: string } export type ApiResponse = LegacyApiSuccess | LegacyApiFailure export interface JavaApiResponse { success: boolean message: string data: T | null } export type RequestOptions = AxiosRequestConfig export function extractErrorMessage(error: unknown) { if (error instanceof AxiosError) { const data = error.response?.data if (data && typeof data === 'object') { for (const field of ['error', 'message', 'msg'] as const) { if (field in data && typeof (data as Record)[field] === 'string') { const value = ((data as Record)[field]).trim() if (value) { return value } } } } if (typeof data === 'string' && data.trim()) { return data } return error.message || '请求失败' } return error instanceof Error ? error.message : '请求失败' } /** 从 axios 错误对象里取响应体(兼容真实 AxiosError 与测试桩)。 */ function responsePayloadOf(error: unknown): unknown { return (error as { response?: { data?: unknown } } | null | undefined)?.response?.data } /** 响应成功分支:Java 业务错误是 HTTP 200 + body.code,互踢下线必须在这里也判。 */ export function onResponseFulfilled(response: AxiosResponse): AxiosResponse { if (isKickedPayload(response.data)) { handleKicked() } return response } /** 响应失败分支:折叠为 Error 之前先判互踢下线(HTTP 401 等状态码路径)。 */ export function onResponseRejected(error: unknown): Promise { if (isKickedPayload(responsePayloadOf(error))) { handleKicked() } return Promise.reject(new Error(extractErrorMessage(error))) } function createHttpClient(): AxiosInstance { const instance = axios.create({ withCredentials: true, timeout: 30000, headers: { 'X-Requested-With': 'XMLHttpRequest', }, }) instance.interceptors.request.use((config: InternalAxiosRequestConfig) => { // 携带本地登录令牌:桌面端同源 cookie 仍可用;dev(5173) 无 cookie 时靠 Bearer 过 Java 鉴权 try { if (config.url && config.url.indexOf('/newApi') === 0 && typeof window !== 'undefined') { const token = window.localStorage.getItem('aiimage_auth_token') || '' if (token) { config.headers.Authorization = `Bearer ${token}` } } } catch { /* 忽略读取令牌异常 */ } return config }) instance.interceptors.response.use(onResponseFulfilled, onResponseRejected) return instance } export const http = createHttpClient() export async function request(config: RequestOptions): Promise { const response = await http.request, D>(config) return response.data } export function get(url: string, config?: RequestOptions) { return request({ url, method: 'GET', ...config, }) } export function post(url: string, data?: D, config?: RequestOptions) { return request({ url, method: 'POST', data, ...config, }) } export function put(url: string, data?: D, config?: RequestOptions) { return request({ url, method: 'PUT', data, ...config, }) } export function del(url: string, config?: RequestOptions) { return request({ url, method: 'DELETE', ...config, }) } export const requestJson = request export const requestGetJson = get export const requestPostJson = post export const requestPutJson = put export const requestDeleteJson = del export async function unwrapJavaResponse(promise: Promise>) { const response = await promise if (!response.success) { throw new Error(response.message || '请求失败') } return response.data as T }