在使用 FastAPI 构建接口时,是否常遇到这些令人困扰的难题?
- 各接口返回格式五花八门,前端对接时难以理解?
- 抛出异常直接暴露刺眼的 500 页面,连开发者自己都难以定位错误根源?
- 希望统一处理日志、错误和响应结构,结果代码散乱如沙?
别着急,本文将手把手带你落地一套成熟的解决方案:统一响应结构 + 全局异常处理。让你的接口既规范又省心,后期维护时也能少掉几根头发。

为什么要统一响应格式?
统一返回格式的优势不言而喻:
- 前端开发人员再也不必对着千奇百怪的 JSON 结构反复猜测;
- 后期维护定位问题时,无需翻遍整个项目代码;
- 扩展能力强,轻松适配小程序、移动 App、Web 端等多种客户端场景。
推荐一套通用的标准格式,示例如下:
{
"code": 0,
"message": "OK",
"data": {
"id": 1,
"name": "Alice"
}
}
每个字段的含义清晰明确:code 代表业务状态码,message 为提示信息,data 才是真正的业务数据。前端拿到这个结构几乎无需思考即可解析。
响应模型封装
接下来,我们利用 Pydantic 泛型定义一个通用的响应模型。这样一来,无论 data 中存储的是用户信息、订单列表还是分页数据,都能通过同一套结构轻松搞定。
from pydantic.generics import GenericModel
from typing import Generic, TypeVar, Optional
T = TypeVar("T")
class Response(GenericModel, Generic[T]):
code: int = 0
message: str = "OK"
data: Optional[T] = None
使用方法非常直观:只需在路由的 response_model 中声明 Response[UserOut],返回时直接 Response(data=user) 即可。接口文档将自动展示完整的数据结构,前端也能获得稳定的类型提示。
@app.get("/user/{user_id}", response_model=Response[UserOut])
async def get_user(user_id: int):
user = await User.get(id=user_id)
return Response(data=user)
全局异常处理
FastAPI 默认的错误处理过于简单粗暴:异常直接抛 HTTP 500 和系统 Traceback,不适合直接暴露给客户端。我们需要自定义业务异常并注册全局处理器。
首先定义一个业务异常类:
class BusinessException(Exception):
def __init__(self, code: int = 4001, message: str = "业务异常"):
self.code = code
self.message = message
然后注册两个异常处理器:一个专门用于处理业务异常,另一个兜底处理所有意料之外的系统异常。
from fastapi.responses import JSONResponse
from fastapi import Request, FastAPI
def register_exceptions(app: FastAPI):
@app.exception_handler(BusinessException)
async def business_exception_handler(request: Request, exc: BusinessException):
return JSONResponse(
status_code=200,
content={
"code": exc.code,
"message": exc.message,
"data": None
}
)
@app.exception_handler(Exception)
async def general_exception_handler(request: Request, exc: Exception):
return JSONResponse(
status_code=500,
content={
"code": 5000,
"message": "系统异常",
"data": None
}
)
请注意,业务异常我们依然返回 200 状态码,仅通过内部的 code 和 message 来区分具体错误——这样前端可用同一套逻辑处理正常响应和错误响应。而真正的系统异常则保留 500 状态码,便于运维监控。
在应用启动时调用一次 register_exceptions(app) 即可。
异常使用示例
借助这套机制,在业务代码中抛出异常变得非常优雅:
@app.get("/users/{user_id}", response_model=Response[UserOut])
async def get_user(user_id: int):
user = await User.get_or_none(id=user_id)
if not user:
raise BusinessException(code=4040, message="用户不存在")
return Response(data=user)
如果用户不存在,前端会收到一个结构清晰的 JSON:{"code": 4040, "message": "用户不存在", "data": null},一目了然。
项目推荐结构
为了保持代码整洁且易于维护,建议按以下目录组织项目:
app/
├── main.py # 启动入口
├── api/
│ └── user.py # 路由模块
├── core/
│ ├── response.py # 响应模型
│ ├── exception.py # 异常类与注册器
下面给出核心文件的完整代码:
response.py
from pydantic.generics import GenericModel
from typing import Generic, TypeVar, Optional
T = TypeVar("T")
class Response(GenericModel, Generic[T]):
code: int = 0
message: str = "OK"
data: Optional[T] = None
exception.py
from fastapi import Request, FastAPI
from fastapi.responses import JSONResponse
class BusinessException(Exception):
def __init__(self, code: int = 4001, message: str = "业务异常"):
self.code = code
self.message = message
def register_exceptions(app: FastAPI):
@app.exception_handler(BusinessException)
async def business_handler(request: Request, exc: BusinessException):
return JSONResponse(
status_code=200,
content={"code": exc.code, "message": exc.message, "data": None}
)
@app.exception_handler(Exception)
async def global_handler(request: Request, exc: Exception):
return JSONResponse(
status_code=500,
content={"code": 5000, "message": "系统错误", "data": None}
)
main.py
from fastapi import FastAPI
from app.core.exception import register_exceptions
from app.api import user
app = FastAPI()
register_exceptions(app)
app.include_router(user.router)
总结
- 统一响应结构,让接口风格一致,前后端协作效率显著提升。
- 异常统一处理,既避免敏感信息泄露,又增强系统健壮性。
- 借助泛型封装响应模型,优雅实用,长期维护极其省心。
