File size: 3,778 Bytes
98277cb 41f6d7f 98277cb | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | import axios, { AxiosError, type AxiosRequestConfig } from 'axios'
export type ApiError = {
status: number
message: string
data?: unknown
}
export type ApiResponse<T> = {
code: number
msg: string
data?: T | null
}
const defaultBaseUrl = import.meta.env.PROD ? '/api/v1' : 'http://localhost:8000/api/v1'
const baseURL = import.meta.env.VITE_API_BASE_URL || defaultBaseUrl
export const http = axios.create({
baseURL,
timeout: 30000,
})
function normalizePath(path: string) {
if (path.startsWith('http://') || path.startsWith('https://')) return path
return path.replace(/^\/+/, '')
}
function extractWrappedErrorMessage(data: unknown) {
if (!data || typeof data !== 'object') return undefined
const obj = data as Record<string, unknown>
const msg = obj.msg
const code = obj.code
if (typeof msg === 'string' && typeof code === 'number') return `API ${code}: ${msg}`
if (typeof msg === 'string') return msg
return undefined
}
function toApiError(error: unknown): ApiError {
if (axios.isAxiosError(error)) {
const axiosError = error as AxiosError
const status = axiosError.response?.status ?? 0
const wrappedMsg = extractWrappedErrorMessage(axiosError.response?.data)
const message =
wrappedMsg ||
(typeof axiosError.response?.data === 'string'
? axiosError.response.data
: axiosError.message)
return {
status,
message,
data: axiosError.response?.data,
}
}
return {
status: 0,
message: error instanceof Error ? error.message : String(error),
}
}
export async function apiGet<T>(path: string, config?: AxiosRequestConfig) {
try {
const res = await http.get<T>(normalizePath(path), config)
return res.data
} catch (error) {
throw toApiError(error)
}
}
export function unwrapApiResponse<T>(value: unknown) {
if (!value || typeof value !== 'object') return { ok: false as const, error: '响应不是对象' }
const obj = value as Record<string, unknown>
if (typeof obj.code !== 'number' || typeof obj.msg !== 'string') {
return { ok: false as const, error: '响应缺少 code/msg 字段' }
}
const code = obj.code
const msg = obj.msg
const data = obj.data as T | null | undefined
if (code !== 200) {
return { ok: false as const, error: `API ${code}: ${msg}`, code, msg, data }
}
return { ok: true as const, data }
}
export async function apiGetWrapped<T>(path: string, config?: AxiosRequestConfig) {
const raw = await apiGet<unknown>(path, config)
const parsed = unwrapApiResponse<T>(raw)
if (parsed.ok) return parsed.data as T
if (parsed.error === '响应缺少 code/msg 字段') return raw as T
const apiError: ApiError = {
status: 0,
message: parsed.error,
data: raw,
}
throw apiError
}
export async function apiPost<TRes, TReq>(
path: string,
data: TReq,
config?: AxiosRequestConfig,
) {
try {
const res = await http.post<TRes>(normalizePath(path), data, config)
return res.data
} catch (error) {
throw toApiError(error)
}
}
export async function apiPostWrapped<TRes, TReq>(
path: string,
data: TReq,
config?: AxiosRequestConfig,
) {
const raw = await apiPost<unknown, TReq>(path, data, config)
const parsed = unwrapApiResponse<TRes>(raw)
if (parsed.ok) return parsed.data as TRes
if (parsed.error === '响应缺少 code/msg 字段') return raw as TRes
const apiError: ApiError = {
status: 0,
message: parsed.error,
data: raw,
}
throw apiError
}
export async function apiGetText(path: string, config?: AxiosRequestConfig) {
try {
const res = await http.get<string>(normalizePath(path), {
...config,
responseType: 'text',
})
return res.data
} catch (error) {
throw toApiError(error)
}
}
|