FastAPI 统一异常响应处理实战:让错误返回也有 code 和 message
上一节我们用泛型实现了统一的成功响应接口 APIResponse,但当时遗留了一个问题:接口一旦抛出异常,返回的仍然是 FastAPI 默认的 detail 结构,和成功响应格式不一致。
本文就来补齐这块短板:通过注册异常处理器,把 HTTP 异常和 Pydantic 参数校验异常统一包装成 code + message 的结构,让前端无论请求成功还是失败,拿到的都是同一种数据格式。
一、先看看问题现场
在 Swagger 文档里随便请求一个接口(比如传入一个不存在的编号 100),返回体是这样的:
{
"detail": "Not Found"
}
只有 detail 字段,没有 code、没有 message。而我们的目标是把它变成:
{
"code": 404,
"message": "具体的错误信息"
}
二、实现思路
FastAPI 提供了异常处理机制,可以拦截错误并自定义返回内容。实际项目中主要面对两类异常:
| 异常类型 | 触发场景 | 处理器 |
|---|---|---|
HTTPException | HTTP 请求层面的错误(如 404) | http_exception_handler |
RequestValidationError | Pydantic 数据校验失败(如字段缺失) | validation_exception_handler |
分别注册这两个处理器,就能覆盖绝大多数错误场景。
三、编写 HTTP 异常处理器
使用装饰器 app.exception_handler 注册,指定异常类型为 HTTPException:
import datetime
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import JSONResponse
from fastapi.exceptions import RequestValidationError
from pydantic import BaseModel
app = FastAPI(
title="学生管理",
description="基于 FastAPI + MySQL 的学生管理系统 API",
version="0.0.1"
)
@app.exception_handler(HTTPException)
def http_exception_handle(request: Request, exc: HTTPException):
"""
把HTTPException错误格式统一成ApiResponse结构
"""
return JSONResponse(
status_code = exc.status_code,
content={
"code": exc.status_code,
"message": exc.detail,
"data": None
}
)
要点说明:
- 函数接收两个参数:
request(请求信息)和exc(异常对象)。 JSONResponse需要两个核心参数:status_code和content。status_code直接取自exc.status_code;message取自exc.detail。- 因为是请求错误,没有业务数据,所以不返回
data字段。
四、编写参数校验异常处理器
Pydantic 校验失败时,exc.errors() 会返回一个错误列表,信息非常冗长。直接丢给前端体验很差,所以我们的策略是:只取第一条错误,提取出错字段位置和错误原因,拼成一句人话。
@app.exception_handler(RequestValidationError)
def validation_exception_handle(request: Request, exc: RequestValidationError):
"""
把pydantic校验失败的格式统一
"""
errors = exc.errors()
first_error = errors[0] if errors else {}
field = " -> ".join(str(loc) for loc in first_error.get("loc", []))
message = first_error.get("msg", "请求参数有误")
return JSONResponse(
status_code = 422,
content={
"code": 422,
"message": f"参数校验失败:{field} - {message}",
"data": None
}
)
关键逻辑拆解:
errors = exc.errors():拿到全部校验错误。first_error = errors[0] if errors else {}:三元表达式,安全取第一条。failed_field:遍历loc字段(它是一个路径列表,如["body", "student_number"]),用" -> "拼接,得到可读的字段位置。message:优先取msg,取不到时兜底为"请求参数有误"。- 状态码固定为
422,与 FastAPI 校验失败时的默认状态码保持一致。
五、测试验证
测试 1:参数缺失
在 Swagger 中只填写部分字段(故意漏掉 student_number),点击 Execute,返回:
{
"code": 422,
"message": "body -> student_number 失败:Field required"
}
返回码 422、字段位置 body -> student_number、错误原因 Field required 都清晰呈现,前端一眼就能定位问题字段。
测试 2:HTTP 异常
为了验证 HTTPException 处理器是否生效,临时在接口中抛出一个异常:
raise HTTPException(status_code=404, detail="测试 HTTP 异常")
再次请求接口,返回体变为:
{
"code": 404,
"message": "测试 HTTP 异常"
}
可以看到,原本的 detail 已经被替换成 code + message,说明 http_exception_handler 已成功生效。
测试完成后,记得把临时抛异常的代码删掉,并清理调试用的 print 语句。
六、小结
至此,统一响应结构全部完成,覆盖了两种场景:
- 请求成功:通过泛型定义
APIResponse统一包装,返回code + message + data。 - 请求失败:注册
HTTPException和RequestValidationError两个异常处理器,分别处理 HTTP 错误与参数校验错误,统一包装成code + message。
这样前端只要请求接口,拿到的永远是同一种数据格式,错误处理逻辑可以集中编写,开发效率显著提升。
小提示:Python 中的
None在 JSON 序列化后表现为null,两者都是"空"的含义,前端解析时无需特殊处理。
想系统学习这门课程?
你已阅读完本文。需要更完整的知识体系,可以继续了解「FastAPI 项目实战」。
查看课程详情