Skip to content

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": "入门"
  }
}

错误时同样用这个结构,只改 codemessage

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 过滤