-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapi.ts
More file actions
186 lines (163 loc) · 5.37 KB
/
Copy pathapi.ts
File metadata and controls
186 lines (163 loc) · 5.37 KB
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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
import axios from 'axios';
import { getUuid } from './auth';
import toast from 'react-hot-toast';
import type { NamingResponse, HistoryItem, RestEndpointResponse } from '../types';
const api = axios.create({
baseURL: '/api',
timeout: 60000,
});
const pickFirstString = (value: unknown): string | undefined => {
if (typeof value === 'string') {
const v = value.trim();
return v ? v : undefined;
}
return undefined;
};
const extractApiMessage = (data: unknown): string | undefined => {
const direct = pickFirstString(data);
if (direct) return direct;
if (!data || typeof data !== 'object') return undefined;
const obj = data as Record<string, unknown>;
const candidates = [
obj.message,
obj.msg,
obj.error,
obj.detail,
obj.title,
];
for (const c of candidates) {
const s = pickFirstString(c);
if (s) return s;
}
const errors = obj.errors;
if (Array.isArray(errors)) {
const parts = errors
.map((e) => (typeof e === 'string' ? e.trim() : ''))
.filter(Boolean);
if (parts.length) return parts.join(';');
}
return undefined;
};
api.interceptors.request.use((config) => {
const uuid = getUuid();
if (uuid) {
config.headers['X-User-UUID'] = uuid;
}
return config;
}, (error) => {
return Promise.reject(error);
});
api.interceptors.response.use((response) => {
// ── 统一业务错误处理 ──
// 后端始终返回 HTTP 200,实际状态码在响应体 code 字段中
const body = response.data
if (body && typeof body === 'object' && 'code' in body) {
const code = body.code as number
// code 为 0 或 200 视为成功,其他值视为业务错误
if (code !== 0 && code !== 200) {
const apiMessage = extractApiMessage(body)
const message = apiMessage
?? (body as Record<string, unknown>).message
?? (body as Record<string, unknown>).msg
?? `请求失败(错误码 ${code})`
const msgStr = String(message)
toast.error(msgStr, { id: `biz_error_${code}` })
// 封装为带响应数据的错误,方便调用方按需读取
const err = new Error(msgStr) as Error & { code: number; data: unknown }
err.code = code
err.data = body
return Promise.reject(err)
}
}
return response
}, (error) => {
// ── HTTP 层错误处理(网络异常、超时、真实 HTTP 错误等)──
let message = '请求失败,请稍后重试'
if (axios.isAxiosError(error)) {
const status = error.response?.status;
const apiMessage = extractApiMessage(error.response?.data);
if (apiMessage) {
message = apiMessage;
} else if (status) {
switch (status) {
case 400:
message = '请求参数有误';
break;
case 401:
message = '未授权或登录已过期';
break;
case 403:
message = '无权限访问';
break;
case 404:
message = '接口不存在';
break;
case 429:
message = '请求过于频繁,请稍后再试';
break;
case 500:
case 502:
case 503:
case 504:
message = '服务异常,请稍后重试';
break;
default:
message = `请求失败(${status})`;
}
} else if (error.code === 'ECONNABORTED') {
message = '请求超时,请稍后再试';
} else if (error.request) {
message = '网络异常,请检查网络连接';
} else if (error.message) {
message = error.message;
}
} else if (error?.message) {
message = error.message;
}
toast.error(message, { id: `api_error_${message}` });
return Promise.reject(error);
});
export const generateNaming = async (description: string, lang?: string): Promise<NamingResponse> => {
const response = await api.post<NamingResponse>('/naming/generate', { description, lang });
return response.data;
};
export const getHistory = async (page = 1, size = 20): Promise<{ code: number, data: HistoryItem[] }> => {
const response = await api.get('/user/history', {
params: { page, size }
});
// Adapter for backend pagination structure and field naming
const backendData = response.data;
type BackendHistoryItem = {
id: number;
inputText?: string;
createTime?: string;
createdAt?: string;
time?: string;
};
if (backendData.data && typeof backendData.data === 'object' && 'content' in backendData.data) {
const content = (backendData.data as { content?: unknown }).content;
if (!Array.isArray(content)) return response.data;
return {
code: backendData.code,
data: (content as BackendHistoryItem[]).map((item) => ({
id: item.id,
description: item.inputText ?? '',
createdAt: item.createTime || item.createdAt || item.time
}))
};
}
return response.data;
};
export const addFavorite = async (variableName: string, reason: string): Promise<void> => {
await api.post('/user/favorites', { variableName, reason });
};
export const removeFavorite = async (variableName: string): Promise<void> => {
await api.delete('/user/favorites', {
params: { variableName }
});
};
export const generateRestNaming = async (description: string, lang?: string): Promise<RestEndpointResponse> => {
const response = await api.post<RestEndpointResponse>('/naming/rest', { description, lang });
return response.data;
};
export default api;