You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
5.9 KiB
5.9 KiB
前端统一错误响应格式适配
日期: 2026-01-27
类型: 功能增强
影响范围: API 客户端、类型定义
变更概述
适配后端统一错误响应格式(RFC-135),更新前端 API 客户端和类型定义,确保前后端响应格式一致。
问题背景
后端实现了统一响应格式:
{
success: boolean;
code: number;
message: string;
data: T | null;
timestamp: string;
}
但前端类型定义和响应拦截器仍使用旧格式,缺少 success 和 timestamp 字段。
解决方案
1. 更新 API 响应类型定义
文件: client/src/types/api.ts
// 旧格式
export interface ApiResponse<T> {
code: number;
message: string;
data: T;
}
// 新格式
export interface ApiResponse<T> {
success: boolean; // ✅ 新增
code: number;
message: string;
data: T | null; // ✅ 支持 null
timestamp: string; // ✅ 新增
}
2. 更新响应拦截器
文件: client/src/services/api/client.ts
成功响应处理
// 优先检查新格式(success 字段)
if (res && typeof res === 'object' && typeof res.success === 'boolean') {
if (res.success) {
return res.data;
}
// 业务错误
const message = res.message || 'Unknown Error';
const error = new Error(message);
(error as any).code = res.code;
(error as any).data = res.data;
return Promise.reject(error);
}
// 兼容旧格式(code 字段)
if (res && typeof res === 'object' && typeof res.code === 'number') {
if (res.code === 200) {
return res.data;
}
// ...
}
错误响应处理
// 处理统一错误响应格式
const errorData = error.response?.data as any;
if (errorData && typeof errorData.success === 'boolean' && !errorData.success) {
const message = errorData.message || 'Unknown Error';
const customError = new Error(message);
(customError as any).code = errorData.code;
(customError as any).data = errorData.data;
(customError as any).timestamp = errorData.timestamp;
// 401/403 认证错误特殊处理
if (errorData.code === 401 || errorData.code === 403) {
useAppStore.getState().setUser(null);
localStorage.removeItem('token');
window.location.href = '/login';
}
return Promise.reject(customError);
}
兼容性
向后兼容
响应拦截器同时支持新旧两种格式:
- 新格式(优先):检查
success字段 - 旧格式(兼容):检查
code === 200
这确保了在后端逐步迁移期间,前端不会出现问题。
错误对象增强
错误对象现在包含更多信息:
interface CustomError extends Error {
code: number; // HTTP 状态码
data: any; // 错误详情
timestamp?: string; // 错误时间戳
}
测试场景
1. 成功响应
// 后端返回
{
"success": true,
"code": 200,
"message": "Success",
"data": { "userId": "123" },
"timestamp": "2026-01-27T08:00:00Z"
}
// 前端接收
{ "userId": "123" } // 自动提取 data
2. 业务错误(400)
// 后端返回
{
"success": false,
"code": 400,
"message": "验证码错误",
"data": null,
"timestamp": "2026-01-27T08:00:00Z"
}
// 前端捕获
catch (error) {
console.error(error.message); // "验证码错误"
console.error(error.code); // 400
}
3. 参数验证错误(422)
// 后端返回
{
"success": false,
"code": 422,
"message": "body -> code: Field required",
"data": { "errors": [...] },
"timestamp": "2026-01-27T08:00:00Z"
}
// 前端捕获
catch (error) {
console.error(error.message); // "body -> code: Field required"
console.error(error.data); // { "errors": [...] }
}
4. 认证错误(401)
// 后端返回
{
"success": false,
"code": 401,
"message": "认证失败",
"data": null,
"timestamp": "2026-01-27T08:00:00Z"
}
// 前端行为
// 1. 清除用户状态
// 2. 删除 token
// 3. 跳转登录页
使用示例
API 调用
import apiClient from '@/services/api/client';
// 成功场景
try {
const user = await apiClient.get('/api/v1/users/me');
console.log(user); // 直接获取 data
} catch (error) {
console.error(error.message); // 统一错误消息
console.error(error.code); // HTTP 状态码
}
// 错误场景
try {
await apiClient.post('/api/v1/auth/login/phone', {
phone: '18046315592',
code: '000000'
});
} catch (error) {
// error.message: "验证码错误"
// error.code: 400
showToast(error.message);
}
类型安全
import type { ApiResponse } from '@/types/api';
// 完整响应类型
const response: ApiResponse<User> = {
success: true,
code: 200,
message: 'Success',
data: { userId: '123', username: 'test' },
timestamp: '2026-01-27T08:00:00Z'
};
// data 可以为 null
const errorResponse: ApiResponse<null> = {
success: false,
code: 400,
message: 'Error',
data: null,
timestamp: '2026-01-27T08:00:00Z'
};
影响范围
受影响文件
client/src/types/api.ts- 类型定义client/src/services/api/client.ts- 响应拦截器
不受影响
- 所有使用
apiClient的业务代码无需修改 - 响应拦截器自动处理格式转换
- 错误处理逻辑保持不变
相关文档
部署说明
无需特殊部署步骤,前端构建后即可生效。
后续优化
- 添加响应时间监控(利用
timestamp字段) - 实现错误重试机制(基于
code判断) - 统一错误提示组件(显示
message) - 添加错误追踪(记录
timestamp和code)