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