mirror of
https://github.com/fawney19/Aether.git
synced 2026-01-03 00:02:28 +08:00
feat(frontend): 新增 API 错误类型定义,优化错误处理
- 新增 api-error.ts 定义标准错误类型和工具函数 - 重构 error.ts 和 errorParser.ts 使用新类型 - 更新 api client 的类型定义
This commit is contained in:
@@ -59,7 +59,7 @@ export const adminApi = {
|
|||||||
is_active?: boolean
|
is_active?: boolean
|
||||||
}): Promise<AdminApiKeysResponse> {
|
}): Promise<AdminApiKeysResponse> {
|
||||||
const response = await apiClient.get<AdminApiKeysResponse>('/api/admin/api-keys', {
|
const response = await apiClient.get<AdminApiKeysResponse>('/api/admin/api-keys', {
|
||||||
params: params
|
params
|
||||||
})
|
})
|
||||||
return response.data
|
return response.data
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ class ApiClient {
|
|||||||
await this.refreshPromise
|
await this.refreshPromise
|
||||||
originalRequest.headers.Authorization = `Bearer ${this.getToken()}`
|
originalRequest.headers.Authorization = `Bearer ${this.getToken()}`
|
||||||
return this.client.request(originalRequest)
|
return this.client.request(originalRequest)
|
||||||
} catch (refreshError) {
|
} catch {
|
||||||
return Promise.reject(error)
|
return Promise.reject(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
83
frontend/src/types/api-error.ts
Normal file
83
frontend/src/types/api-error.ts
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
/**
|
||||||
|
* API 错误类型定义
|
||||||
|
* 用于替代各处的 any 类型,提供类型安全的错误处理
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { AxiosError } from 'axios'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 后端标准错误响应格式
|
||||||
|
*/
|
||||||
|
export interface ApiErrorResponse {
|
||||||
|
error?: {
|
||||||
|
type?: string
|
||||||
|
message?: string
|
||||||
|
}
|
||||||
|
detail?: string
|
||||||
|
message?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API 错误类型
|
||||||
|
* 封装 Axios 错误,提供类型安全的错误处理
|
||||||
|
*/
|
||||||
|
export interface ApiError extends AxiosError<ApiErrorResponse> {
|
||||||
|
response?: AxiosError<ApiErrorResponse>['response'] & {
|
||||||
|
data?: ApiErrorResponse
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 类型守卫:检查是否为 API 错误
|
||||||
|
*/
|
||||||
|
export function isApiError(error: unknown): error is ApiError {
|
||||||
|
return (
|
||||||
|
typeof error === 'object' &&
|
||||||
|
error !== null &&
|
||||||
|
'response' in error
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从错误对象中安全提取错误消息
|
||||||
|
*/
|
||||||
|
export function getErrorMessage(error: unknown, defaultMessage = '操作失败'): string {
|
||||||
|
if (!error) return defaultMessage
|
||||||
|
|
||||||
|
if (isApiError(error)) {
|
||||||
|
// 优先从标准错误格式提取
|
||||||
|
if (error.response?.data?.error?.message) {
|
||||||
|
return error.response.data.error.message
|
||||||
|
}
|
||||||
|
// FastAPI 标准 detail 字段
|
||||||
|
if (error.response?.data?.detail) {
|
||||||
|
return error.response.data.detail
|
||||||
|
}
|
||||||
|
// 通用 message 字段
|
||||||
|
if (error.response?.data?.message) {
|
||||||
|
return error.response.data.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error 实例
|
||||||
|
if (error instanceof Error) {
|
||||||
|
return error.message
|
||||||
|
}
|
||||||
|
|
||||||
|
// 字符串错误
|
||||||
|
if (typeof error === 'string') {
|
||||||
|
return error
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从错误对象中安全提取 HTTP 状态码
|
||||||
|
*/
|
||||||
|
export function getErrorStatus(error: unknown): number | undefined {
|
||||||
|
if (isApiError(error)) {
|
||||||
|
return error.response?.status
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
@@ -2,19 +2,10 @@
|
|||||||
* 从后端响应中提取错误消息
|
* 从后端响应中提取错误消息
|
||||||
* 后端统一返回格式: {"error": {"type": "...", "message": "..."}}
|
* 后端统一返回格式: {"error": {"type": "...", "message": "..."}}
|
||||||
*/
|
*/
|
||||||
export function extractErrorMessage(error: any, defaultMessage = '操作失败'): string {
|
import { type ApiError, isApiError, getErrorMessage as _getErrorMessage } from '@/types/api-error'
|
||||||
// 优先从响应中提取错误消息
|
|
||||||
if (error.response?.data?.error?.message) {
|
|
||||||
return error.response.data.error.message
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果是网络错误或其他异常
|
export function extractErrorMessage(error: unknown, defaultMessage = '操作失败'): string {
|
||||||
if (error.message) {
|
return _getErrorMessage(error, defaultMessage)
|
||||||
return error.message
|
|
||||||
}
|
|
||||||
|
|
||||||
// 返回默认消息
|
|
||||||
return defaultMessage
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -38,8 +29,8 @@ export type ErrorType = typeof ErrorType[keyof typeof ErrorType]
|
|||||||
/**
|
/**
|
||||||
* 从后端响应中提取错误类型
|
* 从后端响应中提取错误类型
|
||||||
*/
|
*/
|
||||||
export function extractErrorType(error: any): ErrorType | null {
|
export function extractErrorType(error: unknown): ErrorType | null {
|
||||||
if (error.response?.data?.error?.type) {
|
if (isApiError(error) && error.response?.data?.error?.type) {
|
||||||
return error.response.data.error.type as ErrorType
|
return error.response.data.error.type as ErrorType
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
@@ -48,6 +39,10 @@ export function extractErrorType(error: any): ErrorType | null {
|
|||||||
/**
|
/**
|
||||||
* 检查是否为特定类型的错误
|
* 检查是否为特定类型的错误
|
||||||
*/
|
*/
|
||||||
export function isErrorType(error: any, type: ErrorType): boolean {
|
export function isErrorType(error: unknown, type: ErrorType): boolean {
|
||||||
return extractErrorType(error) === type
|
return extractErrorType(error) === type
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 重新导出类型
|
||||||
|
export type { ApiError }
|
||||||
|
export { isApiError }
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
* 解析 API 错误响应,提取友好的错误信息
|
* 解析 API 错误响应,提取友好的错误信息
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { isApiError } from '@/types/api-error'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pydantic 验证错误项
|
* Pydantic 验证错误项
|
||||||
*/
|
*/
|
||||||
@@ -9,7 +11,7 @@ interface ValidationError {
|
|||||||
loc: (string | number)[]
|
loc: (string | number)[]
|
||||||
msg: string
|
msg: string
|
||||||
type: string
|
type: string
|
||||||
ctx?: Record<string, any>
|
ctx?: Record<string, unknown>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -138,11 +140,14 @@ function formatValidationError(error: ValidationError): string {
|
|||||||
* @param defaultMessage 默认错误信息
|
* @param defaultMessage 默认错误信息
|
||||||
* @returns 格式化的错误信息
|
* @returns 格式化的错误信息
|
||||||
*/
|
*/
|
||||||
export function parseApiError(err: any, defaultMessage: string = '操作失败'): string {
|
export function parseApiError(err: unknown, defaultMessage: string = '操作失败'): string {
|
||||||
if (!err) return defaultMessage
|
if (!err) return defaultMessage
|
||||||
|
|
||||||
// 处理网络错误
|
// 处理网络错误
|
||||||
if (!err.response) {
|
if (!isApiError(err) || !err.response) {
|
||||||
|
if (err instanceof Error) {
|
||||||
|
return err.message || defaultMessage
|
||||||
|
}
|
||||||
return '无法连接到服务器,请检查网络连接'
|
return '无法连接到服务器,请检查网络连接'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,8 +174,8 @@ export function parseApiError(err: any, defaultMessage: string = '操作失败')
|
|||||||
// 3. 处理对象错误
|
// 3. 处理对象错误
|
||||||
if (typeof detail === 'object') {
|
if (typeof detail === 'object') {
|
||||||
// 可能是自定义错误对象
|
// 可能是自定义错误对象
|
||||||
if (detail.message) {
|
if ((detail as Record<string, unknown>).message) {
|
||||||
return detail.message
|
return String((detail as Record<string, unknown>).message)
|
||||||
}
|
}
|
||||||
// 尝试 JSON 序列化
|
// 尝试 JSON 序列化
|
||||||
try {
|
try {
|
||||||
@@ -186,7 +191,7 @@ export function parseApiError(err: any, defaultMessage: string = '操作失败')
|
|||||||
/**
|
/**
|
||||||
* 解析并提取第一个错误信息(用于简短提示)
|
* 解析并提取第一个错误信息(用于简短提示)
|
||||||
*/
|
*/
|
||||||
export function parseApiErrorShort(err: any, defaultMessage: string = '操作失败'): string {
|
export function parseApiErrorShort(err: unknown, defaultMessage: string = '操作失败'): string {
|
||||||
const fullError = parseApiError(err, defaultMessage)
|
const fullError = parseApiError(err, defaultMessage)
|
||||||
|
|
||||||
// 如果有多行错误,只取第一行
|
// 如果有多行错误,只取第一行
|
||||||
|
|||||||
Reference in New Issue
Block a user