FastAPI新手避坑|登录接口+全局异常处理,从懵圈到吃透
前言:作为FastAPI新手,最近开发登录功能时,踩了一堆关于“全局异常处理”的坑——不知道全局异常到底有啥用、没它行不行,甚至写了异常处理器,还疑惑“我只抛了个HTTPException,它怎么就能拿到状态码和提示?”。今天把这些新手必懵的问题,结合我自己的实战代码,一次性讲透,全程接地气,不搞虚的,新手直接对号入座!
一、先交代背景:我正在做啥?
目前在写一个简单的“随机事项查询”项目,核心功能:用户注册+登录,登录后根据分类(play/study/eat等)查询随机事项。技术栈:FastAPI + SQLAlchemy 2.0(Mapped正规写法) + 密码加密,全程异步开发。
就是在写登录、注册接口时,被“全局异常处理”搞懵了,才有了下面这些疑问和总结,完全贴合新手实战场景,不是纯理论!
二、新手最懵的3个问题(我全踩过)
问题1:没有全局异常处理器,不能抛HTTPException吗?
这是我最开始的核心疑问!答案:完全可以抛!
举个例子,我写登录接口时,判断用户名不存在,直接抛异常:
raise HTTPException(status_code=400, detail="用户名不存在")
没有全局异常处理器的情况下,程序不会崩,但是前端收到的返回格式是FastAPI默认的,特别简陋:
{"detail": "用户名不存在" }
没有code、没有message,前端对接起来特别麻烦,而且每个接口报错,都要手动拼返回格式,重复代码超多!
问题2:全局异常处理器到底干了啥?能修复代码bug吗?
我之前一直以为,有了全局异常,代码就不会报错了——大错特错!
重点强调:全局异常处理器不修复任何bug!
它的核心作用只有2个,特别简单:
-
拦截你手动抛的HTTPException,把默认的简陋格式,改成咱们想要的统一格式(比如code+message);
-
拦截程序意外崩溃(比如变量未定义、数据库连不上),不把后端的报错堆栈暴露给前端,包装成友好的500错误。
举个通俗的例子:全局异常就像一个“错误包装工”,不管是你主动抛的错,还是程序不小心崩了,它都能把错误打包成统一的“快递”(JSON格式)发给前端,不让前端看到杂乱的“原材料”(默认报错格式、报错堆栈)。
问题3:我只抛了HTTPException(400, detail="xxx"),异常处理器怎么拿到status_code?
这是最细节、最懵的一个点!我当时写这段代码时,盯着exc.status_code看了半天,不知道它从哪来的:
async def http_exception_handler(request: Request, exc: HTTPException):
return JSONResponse(
status_code=exc.status_code, # 这个exc.status_code从哪来?
content={"code": exc.status_code, "message": exc.detail},
)
答案:FastAPI自动帮你把参数装进去了!
当你写raise HTTPException(400, detail="用户名不存在")时,FastAPI底层会自动创建一个exc对象,把你传的400和“用户名不存在”,分别赋值给exc.status_code和exc.detail。
你看不见这个过程,但exc对象是真实存在的,所以异常处理器里,直接用exc.status_code、exc.detail就能拿到值,不用你手动传任何参数!
简单说:你负责“抛错”(告诉程序错在哪、错的状态码),FastAPI负责“打包”(把错误信息装进exc),全局异常处理器负责“美化”(把exc里的信息,改成统一格式返回)。
三、实战代码:从无全局异常,到有全局异常的完整对比
结合我自己的登录、注册接口,给大家看最直观的对比,新手可以直接复制用。
先准备基础依赖(必装)
密码加密需要用到passlib和bcrypt,先安装:
pip install passlib[bcrypt]
1. 无全局异常:登录接口(繁琐、格式乱)
没有全局异常时,每个接口都要手动拼错误返回,不然前端拿到的格式不统一:
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from config.things import get_async_db
from crud.numbers import get_number_by_username
from schemas.numbers import NumberCreate
from utils.security import verify_password
router = APIRouter(prefix="/numbers", tags=["numbers"])
# 登录接口(无全局异常,手动拼错误返回)
@router.post("/login")
async def login_number_endpoint(
number_data: NumberCreate,
db: AsyncSession = Depends(get_async_db)
):
# 查找用户
db_number = await get_number_by_username(db, number_data.username)
# 手动判错,手动拼返回格式
if not db_number:
return {"code": 400, "message": "用户名不存在", "data": None}
if not verify_password(number_data.password, db_number.password):
return {"code": 400, "message": "用户名或密码错误", "data": None}
# 登录成功返回
return {"code": 200, "message": "Login successful", "user_id": db_number.id}
缺点:每个接口都要写return错误,重复代码多,万一忘了写,返回格式就乱了。
2. 有全局异常:登录接口(简洁、格式统一)
第一步:写全局异常处理器(新建exception.py)
from fastapi import Request, HTTPException
from fastapi.responses import JSONResponse
# 处理主动抛出的HTTPException(比如用户名不存在、密码错误)
async def http_exception_handler(request: Request, exc: HTTPException):
return JSONResponse(
status_code=exc.status_code,
content={"code": exc.status_code, "message": exc.detail},
)
# 兜底处理所有未知异常(比如代码bug、数据库报错)
async def global_exception_handler(request: Request, exc: Exception):
return JSONResponse(
content={"code": 500, "message": "服务器内部错误", "data": None},
status_code=500
)
第二步:在main.py注册全局异常
from fastapi import FastAPI
from exception import http_exception_handler, global_exception_handler
from fastapi import HTTPException
app = FastAPI()
# 注册全局异常,全局生效
app.add_exception_handler(HTTPException, http_exception_handler)
app.add_exception_handler(Exception, global_exception_handler)
第三步:简化登录接口(不用手动拼错误返回)
@router.post("/login")
async def login_number_endpoint(
number_data: NumberCreate,
db: AsyncSession = Depends(get_async_db)
):
db_number = await get_number_by_username(db, number_data.username)
# 直接抛异常,不用手动return
if not db_number:
raise HTTPException(status_code=400, detail="用户名不存在")
if not verify_password(number_data.password, db_number.password):
raise HTTPException(status_code=400, detail="用户名或密码错误")
return {"code": 200, "message": "Login successful", "user_id": db_number.id}
效果:抛异常后,全局异常处理器自动拦截,前端收到统一格式:
{
"code": 400,
"message": "用户名不存在"
}
再也不用手动拼错误返回,代码简洁太多!
四、新手必记的核心总结(避坑关键)
-
有无全局异常,都能使用raise HTTPException,区别只是返回格式不同;
-
全局异常不修复bug,只统一错误返回格式、隐藏服务器内部报错;
-
exc.status_code、exc.detail不是凭空来的,是FastAPI自动把你抛异常时传的参数装进去的;
-
密码一定要加密存储(用本文的bcrypt算法),禁止明文存数据库,这是企业级规范;
-
全局异常处理器必须在main.py注册,否则不生效!
五、最后说两句
作为新手,刚开始接触FastAPI的全局异常,真的很容易懵——不知道它的作用、不知道参数从哪来、不知道怎么用。但只要结合实际代码,多试几次,就会发现它特别简单,核心就是“统一格式、简化代码”。
本文所有代码,都是我自己实战用过的,没有任何多余的理论,新手可以直接复制到自己的项目里,替换成自己的数据库配置和字段,就能直接用。后续还会更新登录权限控制、前端对接等内容,感兴趣的可以关注~
如果有和我一样的新手,对全局异常、登录接口还有疑问,欢迎在评论区交流,一起避坑、一起进步!
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)