Appearance
27|FastAPI 响应模型与状态码
接口返回的数据需要统一的结构和类型,前端才能可靠地解析。FastAPI 的 response_model 用 Pydantic 模型约束返回数据的形状,自动过滤多余字段、校验类型、生成文档。状态码则告诉客户端请求的处理结果:成功、参数错误、未授权、服务器内部错误。
一、response_model
python
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class ArticleOut(BaseModel):
id: int
title: str
status: str
# 内存数据(模拟数据库)
articles_db = [
{"id": 1, "title": "入门", "status": "published", "deleted": False},
]
@app.get("/articles/{id}", response_model=ArticleOut)
async def get_article(id: int):
article = articles_db[0]
return article返回的字典包含 deleted 字段,但 ArticleOut 中没有定义这个字段——FastAPI 会自动过滤掉。这保证了接口契约的稳定性:即使内部数据结构变化,返回给客户端的数据形状始终一致。
排除字段
python
class ArticleInternal(BaseModel):
id: int
title: str
content: str
author_email: str # 敏感字段
class ArticlePublic(BaseModel):
id: int
title: str
content: str
@app.get("/articles/{id}", response_model=ArticlePublic)
async def get_article(id: int):
article = get_from_db(id) # 返回 ArticleInternal 形状的数据
return article # author_email 被自动过滤二、自定义状态码
成功响应的状态码
python
from fastapi import status
@app.post("/articles", status_code=status.HTTP_201_CREATED)
async def create_article():
return {"id": 2, "title": "新文章"}| 状态码 | 含义 | 使用场景 |
|---|---|---|
| 200 OK | 成功 | GET、PUT、PATCH 的默认状态码 |
| 201 Created | 创建成功 | POST 创建资源 |
| 204 No Content | 成功但无返回体 | DELETE 删除成功 |
错误状态码
python
from fastapi import HTTPException
@app.get("/articles/{id}")
async def get_article(id: int):
article = find_article(id)
if article is None:
raise HTTPException(status_code=404, detail="文章不存在")
return article| 状态码 | 含义 | 使用场景 |
|---|---|---|
| 400 Bad Request | 请求参数错误 | 参数校验失败 |
| 401 Unauthorized | 未认证 | 缺少 Token |
| 403 Forbidden | 无权限 | Token 有效但权限不足 |
| 404 Not Found | 资源不存在 | 查询不到指定 ID 的数据 |
| 422 Unprocessable Entity | 语义错误 | Pydantic 校验失败(FastAPI 自动返回) |
| 500 Internal Server Error | 服务器内部错误 | 未捕获的异常 |
三、统一响应格式
生产环境通常要求所有接口返回统一的包装结构:
python
from typing import Generic, TypeVar
from pydantic import BaseModel
from pydantic.generics import GenericModel
T = TypeVar("T")
class ResponseWrapper(GenericModel, Generic[T]):
code: int = 0
message: str = "success"
data: T
class ArticleOut(BaseModel):
id: int
title: str
@app.get("/articles/{id}", response_model=ResponseWrapper[ArticleOut])
async def get_article(id: int):
article = {"id": id, "title": "入门"}
return ResponseWrapper(data=article)返回:
json
{
"code": 0,
"message": "success",
"data": {
"id": 1,
"title": "入门"
}
}错误时同样用这个结构,只改 code 和 message:
python
@app.exception_handler(HTTPException)
async def http_exception_handler(request, exc):
return JSONResponse(
status_code=exc.status_code,
content={"code": exc.status_code, "message": exc.detail, "data": None},
)四、Response 对象
需要控制响应头或返回非 JSON 数据时,用 Response:
python
from fastapi import Response
@app.get("/export")
async def export_csv():
csv_content = "id,title\n1,hello\n"
return Response(
content=csv_content,
media_type="text/csv",
headers={"Content-Disposition": "attachment; filename=data.csv"},
)五、常见错误
response_model 过滤了需要的字段
python
class UserOut(BaseModel):
id: int
name: str
# 错误:email 被过滤,前端需要但拿不到
@app.get("/users/{id}", response_model=UserOut)
async def get_user(id: int):
return {"id": 1, "name": "admin", "email": "admin@example.com"}
# 正确:响应模型包含所有需要返回的字段
class UserOut(BaseModel):
id: int
name: str
email: str状态码使用不当
python
# 错误:查询不到数据返回 400(400 是请求参数格式错误,不是资源不存在)
@app.get("/articles/{id}")
async def get_article(id: int):
article = find(id)
if not article:
raise HTTPException(status_code=400, detail="文章不存在")
# 正确:资源不存在用 404
raise HTTPException(status_code=404, detail="文章不存在")手动构造 JSONResponse 导致类型丢失
python
# 错误:手动返回 dict,绕过 response_model 校验和过滤
@app.get("/articles/{id}", response_model=ArticleOut)
async def get_article(id: int):
from fastapi.responses import JSONResponse
return JSONResponse(content={"id": id, "title": "x", "extra": "y"})
# 正确:直接返回数据,让 FastAPI 处理序列化
return {"id": id, "title": "x", "extra": "y"} # extra 会被 response_model 过滤