跳到主要内容

FastAPI 统一异常响应处理实战:让错误返回也有 code 和 message

上一节我们用泛型实现了统一的成功响应接口 APIResponse,但当时遗留了一个问题:接口一旦抛出异常,返回的仍然是 FastAPI 默认的 detail 结构,和成功响应格式不一致。

本文就来补齐这块短板:通过注册异常处理器,把 HTTP 异常和 Pydantic 参数校验异常统一包装成 code + message 的结构,让前端无论请求成功还是失败,拿到的都是同一种数据格式。

一、先看看问题现场​

在 Swagger 文档里随便请求一个接口(比如传入一个不存在的编号 100),返回体是这样的:

{
"detail": "Not Found"
}

只有 detail 字段,没有 code、没有 message。而我们的目标是把它变成:

{
"code": 404,
"message": "具体的错误信息"
}

二、实现思路​

FastAPI 提供了异常处理机制,可以拦截错误并自定义返回内容。实际项目中主要面对两类异常:

异常类型触发场景处理器
HTTPExceptionHTTP 请求层面的错误(如 404)http_exception_handler
RequestValidationErrorPydantic 数据校验失败(如字段缺失)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
}
)

关键逻辑拆解:

  1. errors = exc.errors():拿到全部校验错误。
  2. first_error = errors[0] if errors else {}:三元表达式,安全取第一条。
  3. failed_field:遍历 loc 字段(它是一个路径列表,如 ["body", "student_number"]),用 " -> " 拼接,得到可读的字段位置。
  4. message:优先取 msg,取不到时兜底为 "请求参数有误"。
  5. 状态码固定为 422,与 FastAPI 校验失败时的默认状态码保持一致。

五、测试验证​

测试 1:参数缺失​

在 Swagger 中只填写部分字段(故意漏掉 student_number),点击 Execute,返回:

{
"code": 422,
"message": "body -> student_number 失败:Field required"
}
参数校验失败时返回 code 422 与字段缺失提示

返回码 422、字段位置 body -> student_number、错误原因 Field required 都清晰呈现,前端一眼就能定位问题字段。

测试 2:HTTP 异常​

为了验证 HTTPException 处理器是否生效,临时在接口中抛出一个异常:

raise HTTPException(status_code=404, detail="测试 HTTP 异常")

再次请求接口,返回体变为:

{
"code": 404,
"message": "测试 HTTP 异常"
}
抛出 HTTPException 后返回 code 与 message 结构

可以看到,原本的 detail 已经被替换成 code + message,说明 http_exception_handler 已成功生效。

测试完成后,记得把临时抛异常的代码删掉,并清理调试用的 print 语句。

六、小结​

至此,统一响应结构全部完成,覆盖了两种场景:

  • 请求成功:通过泛型定义 APIResponse 统一包装,返回 code + message + data。
  • 请求失败:注册 HTTPException 和 RequestValidationError 两个异常处理器,分别处理 HTTP 错误与参数校验错误,统一包装成 code + message。

这样前端只要请求接口,拿到的永远是同一种数据格式,错误处理逻辑可以集中编写,开发效率显著提升。

小提示:Python 中的 None 在 JSON 序列化后表现为 null,两者都是"空"的含义,前端解析时无需特殊处理。

课程推荐

想系统学习这门课程?

你已阅读完本文。需要更完整的知识体系,可以继续了解「FastAPI 项目实战」。

查看课程详情