06 错误处理
本章目标:学会向客户端返回规范的错误信息,以及全局统一处理异常。
1. HTTPException:主动返回错误
业务中遇到「用户不存在」「余额不足」等情况时,抛出 HTTPException:
from fastapi import FastAPI, HTTPException, status
app = FastAPI()
fake_db = {1: {"id": 1, "name": "小明"}}
@app.get("/users/{user_id}")
def get_user(user_id: int):
if user_id not in fake_db:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="用户不存在",
)
return fake_db[user_id]
访问 /users/999 会收到:
HTTP 404
{"detail": "用户不存在"}
要点:
- 是
raise不是return!抛出异常后函数立即结束 detail可以是字符串,也可以是字典/列表(会原样放进响应)- 在函数的任何深度抛出都有效(包括被调用的子函数里),FastAPI 会接住它
2. 常见业务错误示例
# 参数合法但业务上不允许 → 400
if user.balance < price:
raise HTTPException(status_code=400, detail="余额不足")
# 未登录 → 401
raise HTTPException(status_code=401, detail="请先登录")
# 登录了但没权限 → 403
raise HTTPException(status_code=403, detail="无权访问该资源")
# 资源不存在 → 404
raise HTTPException(status_code=404, detail="文章不存在")
# 资源冲突(如用户名已被占用)→ 409
raise HTTPException(status_code=409, detail="用户名已存在")
分不清用哪个时的口诀:没登录 401,没权限 403,找不到 404,其他客户端问题 400。
3. 理解 422:FastAPI 的自动校验错误
当请求数据不符合你声明的类型/模型时,FastAPI 自动返回 422,格式固定:
{
"detail": [
{
"type": "int_parsing",
"loc": ["path", "user_id"],
"msg": "Input should be a valid integer...",
"input": "abc"
}
]
}
loc:错误位置,["path", "user_id"]表示路径参数 user_id,["body", "email"]表示请求体的 email 字段msg:人类可读的原因
你不需要为 422 写任何代码,但要学会读懂它——前端联调时对方问「为什么报 422」,你要能看 loc 和 msg 快速定位。
4. 自定义异常 + 全局异常处理器
项目变大后,到处写 raise HTTPException(404, "xx不存在") 很啰嗦。更优雅的方式是:定义业务异常类,用异常处理器统一转成 HTTP 响应。
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
app = FastAPI()
# 1. 定义业务异常
class NotFoundError(Exception):
def __init__(self, resource: str):
self.resource = resource
# 2. 注册处理器:所有地方抛出的 NotFoundError 都会走到这里
@app.exception_handler(NotFoundError)
def not_found_handler(request: Request, exc: NotFoundError):
return JSONResponse(
status_code=404,
content={"detail": f"{exc.resource}不存在"},
)
# 3. 业务代码里只管抛,不关心 HTTP 细节
@app.get("/articles/{article_id}")
def get_article(article_id: int):
article = None # 假装查了数据库没查到
if article is None:
raise NotFoundError("文章")
return article
好处:业务代码和 HTTP 细节解耦,错误响应格式全项目统一。
5. 兜底:处理所有未捕获的异常
代码有 bug 抛了意料之外的异常时,FastAPI 默认返回 500 和 Internal Server Error。你可以加一个兜底处理器,统一格式并记录日志:
import logging
logger = logging.getLogger("app")
@app.exception_handler(Exception)
def unhandled_exception_handler(request: Request, exc: Exception):
# 关键:把异常记录下来,否则线上出问题无从排查
logger.exception(f"未处理异常: {request.method} {request.url}")
return JSONResponse(
status_code=500,
content={"detail": "服务器内部错误,请稍后重试"},
)
开发阶段建议先不加这个——让异常直接在终端打印完整堆栈,更方便调试。上线前再加上。
6. 统一响应格式(可选风格)
有些团队喜欢所有接口都返回统一包装:
{"code": 0, "message": "ok", "data": {...}}
FastAPI 不强制任何风格。本教程采用更贴近 HTTP 语义的风格(用状态码区分成败、错误放 detail),这也是 FastAPI 社区的主流做法。你入职后跟随团队规范即可,机制都是本章讲的这些。
本章小结
- 业务错误用
raise HTTPException(status_code=..., detail=...) - 422 是自动校验错误,学会读
loc定位问题 - 大项目用「自定义异常 +
@app.exception_handler」统一错误处理 - 生产环境要有兜底异常处理器并记录日志
下一章:07-依赖注入 —— FastAPI 最重要的机制,也是连接数据库的关键。