跳到主要内容

7.2 API 错误契约

错误契约决定前端如何展示错误、后端如何记录问题、测试如何断言失败路径。没有统一契约时,前端会被迫解析各种不一致的错误格式。

学习目标

  • 能区分校验错误、认证错误、权限错误、资源不存在和服务器错误。
  • 能设计统一错误响应结构。
  • 能在 ASP.NET Core 中集中处理异常。
  • 能让前端按错误类型展示不同提示。

应用场景

  • 表单字段校验失败,需要展示字段级提示。
  • 用户未登录或 Token 过期。
  • 用户访问了没有权限的资源。
  • 后端发生未预期异常,需要给用户友好提示并记录日志。

推荐错误结构

{
"code": "todo.title_required",
"message": "标题不能为空",
"traceId": "0HMS...",
"errors": {
"title": ["标题不能为空"]
}
}

状态码约定

状态码场景前端处理
400请求格式或业务校验失败展示表单或业务提示
401未登录或登录过期跳转登录或刷新令牌
403已登录但无权限展示无权限页面
404资源不存在展示空状态或不存在提示
500服务端未知错误展示通用错误并保留 traceId

ASP.NET Core 示例

app.UseExceptionHandler(errorApp =>
{
errorApp.Run(async context =>
{
var traceId = context.TraceIdentifier;
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
context.Response.ContentType = "application/json";

await context.Response.WriteAsJsonAsync(new ApiError(
"server.unexpected_error",
"服务暂时不可用,请稍后再试",
traceId));
});
});

public sealed record ApiError(string Code, string Message, string TraceId);

前端处理示例

export type ApiError = {
code: string;
message: string;
traceId: string;
errors?: Record<string, string[]>;
};

export async function parseApiError(response: Response): Promise<ApiError> {
try {
return (await response.json()) as ApiError;
} catch {
return {
code: 'client.unexpected_response',
message: '服务响应格式异常',
traceId: '',
};
}
}

重点难点

  • 错误 code 应稳定,message 可以面向用户调整。
  • 生产环境不要把异常堆栈返回给前端。
  • traceId 是联动前端报错和后端日志的关键。
  • 校验错误应尽量包含字段级信息,方便表单展示。

常见误区

误区推荐做法
所有失败都返回 200用状态码表达失败类型
前端靠字符串匹配错误使用稳定 code 判断错误
直接返回异常详情记录日志,返回友好错误和 traceId

练习

  • 为创建任务接口设计标题为空的错误响应。
  • 前端根据 401 自动跳转登录页。
  • 在日志中记录错误 codetraceId