欢迎光临
我们一直在努力

第6课:FastAPI|HTTP请求方法|状态码|响应模型与统一返回格式封装

在这里插入图片描述

文章目录

    • 1. 课前导读
      • 本节课学习目标
      • 前置知识
      • 学完能掌握什么
      • 适用人群
    • 2. 核心理论讲解
      • 2.1 HTTP请求方法详解与RESTful设计原则
        • 2.1.1 主要方法对比
        • 2.1.2 FastAPI中各方法的装饰器
      • 2.2 HTTP状态码——与客户端沟通的语言
        • 2.2.1 成功类(2xx)
        • 2.2.2 客户端错误(4xx)
        • 2.2.3 服务器错误(5xx)
        • 2.2.4 FastAPI中返回状态码的方式
      • 2.3 响应模型(Response Model)原理
        • 2.3.1 基本用法
        • 2.3.2 响应模型的底层逻辑
        • 2.3.3 `response_model`与函数返回类型注解的关系
      • 2.4 统一返回格式封装(企业级实践)
        • 2.4.1 为什么需要统一格式?
        • 2.4.2 FastAPI中如何优雅实现?
      • 2.5 与Flask/Django的响应处理对比
    • 3. 环境搭建 & 实操准备
      • 3.1 复用项目环境
      • 3.2 新增代码文件
      • 3.3 安装依赖(无额外需求)
    • 4. 手把手代码实战
      • 4.1 不同HTTP方法的接口实现(模拟商品管理)
      • 4.2 响应模型的高级用法:字段别名、排除空值、嵌套
      • 4.3 自定义状态码与异常处理
      • 4.4 统一返回格式封装(企业级实战)
        • 4.4.1 定义统一响应模型
        • 4.4.2 在接口中使用统一响应格式
        • 4.4.3 实现全局异常拦截统一错误响应(预览)
      • 4.5 使用`response_model_include`和`response_model_exclude`
      • 4.6 响应模型与路径操作函数的返回类型注解配合
      • 4.7 处理文件下载等非JSON响应
      • 4.8 请求方法实践:创建完整CRUD + 统一响应
    • 5. 重点知识点总结
      • HTTP方法速查表
      • 状态码选择最佳实践
      • 响应模型核心语法
      • 统一返回格式优势与实现
      • 易错点与避坑指南
      • 最佳实践
    • 6. 课后作业 & 思考题
      • 实操练习题(必做)
      • 理论思考题
      • 拓展学习方向
    • 7. 本节干货总结
      • 核心考点(面试/自测)
      • 实际工作应用场景
    • 下节课预告
  • 🔗《20节课 FastAPI 从入门到精通》系列课程导航

1. 课前导读

本节课学习目标

在前5课中,我们已经能够创建接口并接收路径参数、查询参数和请求体数据。但一个专业的API不仅需要正确处理数据,还需要遵循HTTP规范——使用恰当的请求方法表达操作意图,返回正确的状态码告知客户端处理结果,并通过统一的响应格式提升前后端协作效率。

本节课将带你掌握FastAPI中关于HTTP协议规范落地的核心实践。学完本节课,你将:

  • 理解HTTP请求方法语义:GET、POST、PUT、PATCH、DELETE等方法的正确使用场景与区别
  • 掌握HTTP状态码:2xx成功、4xx客户端错误、5xx服务器错误的典型用法,以及FastAPI中如何返回特定状态码
  • 学会响应模型:使用response_model参数过滤输出字段、保证类型安全、自动生成文档
  • 封装统一返回格式:实现企业级API常见的{code, message, data}结构,支持泛型
  • 处理异常响应:自定义异常与状态码的映射

前置知识

  • 已完成第5课,熟悉Pydantic模型定义和请求体处理
  • 了解基本的HTTP协议(请求方法、状态码分类)
  • 知道FastAPI的路径操作装饰器(@app.get等)

学完能掌握什么

学完本节课后,你将具备以下能力:

  • 设计符合RESTful规范的API:根据资源操作选择正确的方法和状态码
  • 规范输出响应数据:隐藏敏感字段、统一分页格式
  • 封装可复用的响应工具:减少重复代码,提升项目一致性
  • 理解FastAPI的response_model机制:既能校验输出,又能自动生成文档
  • 为前后端分离项目提供标准接口契约
  • 适用人群

    • 已经掌握参数接收但输出混乱的开发者
    • 需要对接前端或第三方系统的后端工程师
    • 希望API符合行业规范(RESTful)的学习者
    • 准备进入企业级项目开发的学员

    2. 核心理论讲解

    2.1 HTTP请求方法详解与RESTful设计原则

    HTTP协议定义了多种请求方法(也称为HTTP动词),每种方法对应一种资源操作语义。在RESTful API设计中,合理使用方法能显著提升接口的可读性和可维护性。

    2.1.1 主要方法对比
    方法语义幂等性安全性是否支持请求体典型场景
    GET 获取资源 ✅ 是 ✅ 是 ❌ 否 查询列表、获取详情
    POST 创建资源 ❌ 否 ❌ 否 ✅ 是 注册用户、创建订单
    PUT 全量更新 ✅ 是 ❌ 否 ✅ 是 替换整个资源
    PATCH 部分更新 ❌ 否 ❌ 否 ✅ 是 修改用户部分字段
    DELETE 删除资源 ✅ 是 ❌ 否 ❌ 否 删除商品

    关键概念解释:

    • 幂等性:多次执行相同请求,产生的副作用与执行一次相同。例如:DELETE删除一个资源多次,第一次删除成功,后续返回404,但服务器状态不会再变,是幂等的。
    • 安全性:不修改服务器状态(只读)。GET和HEAD是安全的。
    2.1.2 FastAPI中各方法的装饰器

    FastAPI为每个HTTP方法提供了对应的装饰器:

    from fastapi import FastAPI

    app = FastAPI()

    @app.get("/resource")
    def get_resource(): ...

    @app.post("/resource")
    def create_resource(): ...

    @app.put("/resource/{id}")
    def update_resource(id: int): ...

    @app.patch("/resource/{id}")
    def partial_update(id: int): ...

    @app.delete("/resource/{id}")
    def delete_resource(id: int): ...

    与Flask/Django对比:

    • Flask中需要显式指定methods=['GET', 'POST'],而FastAPI通过不同装饰器更清晰地表达意图。
    • Django REST framework使用@action装饰器,略繁琐。

    2.2 HTTP状态码——与客户端沟通的语言

    状态码是服务器告诉客户端请求结果的数字代码,分为五类:

    2.2.1 成功类(2xx)
    状态码名称使用场景
    200 OK 成功 GET、PUT、PATCH 成功返回数据
    201 Created 已创建 POST 创建资源成功,通常在响应头Location中放新资源URL
    204 No Content 无内容 DELETE 成功,或 PUT 更新无需返回数据
    206 Partial Content 部分内容 范围请求(下载大文件断点续传)
    2.2.2 客户端错误(4xx)
    状态码名称使用场景
    400 Bad Request 错误请求 请求体格式错误、参数校验失败(业务层面)
    401 Unauthorized 未授权 未提供认证凭证或凭证无效
    403 Forbidden 禁止访问 已认证但无权限(如普通用户访问管理员接口)
    404 Not Found 未找到 资源不存在
    422 Unprocessable Entity 无法处理 请求体语法正确但语义错误(Pydantic校验失败默认返回422)
    429 Too Many Requests 请求过多 触发限流
    2.2.3 服务器错误(5xx)
    状态码名称使用场景
    500 Internal Server Error 服务器内部错误 未捕获的异常(框架默认)
    502 Bad Gateway 网关错误 代理服务器收到无效响应
    503 Service Unavailable 服务不可用 服务器过载或维护
    2.2.4 FastAPI中返回状态码的方式

    方式1:在装饰器中指定默认状态码

    @app.post("/items", status_code=201)
    def create_item():
    return {"id": 1}

    方式2:手动返回Response对象或JSONResponse

    from fastapi.responses import JSONResponse

    @app.post("/items")
    def create_item():
    return JSONResponse(content={"id": 1}, status_code=201)

    方式3:使用raise HTTPException(用于错误响应)

    from fastapi import HTTPException

    @app.get("/items/{id}")
    def get_item(id: int):
    if id not in items:
    raise HTTPException(status_code=404, detail="Item not found")
    return items[id]

    2.3 响应模型(Response Model)原理

    FastAPI允许你在路径装饰器中使用response_model参数,指定用于输出响应的Pydantic模型。这带来三大好处:

  • 数据过滤:只返回模型中定义的字段,避免敏感信息泄露(例如不返回密码哈希)。
  • 类型转换:确保输出数据符合模型类型(如将datetime转为ISO字符串)。
  • 文档自动生成:OpenAPI schema会包含响应模型的结构。
  • 2.3.1 基本用法

    from pydantic import BaseModel

    class UserOut(BaseModel):
    id: int
    name: str
    # email 字段不在此模型中,所以不会返回

    @app.get("/users/{id}", response_model=UserOut)
    def get_user(id: int):
    # 假设从数据库查到包含 email 字段的字典
    user = {"id": 1, "name": "Alice", "email": "alice@example.com"}
    return user # 实际返回 {"id": 1, "name": "Alice"},email被过滤

    2.3.2 响应模型的底层逻辑

    当使用response_model时,FastAPI会在路径操作函数返回后,将返回值传入该模型进行序列化(model_dump()),然后输出JSON。这意味着:

    • 你可以返回字典、模型实例或其他可转换对象。
    • 如果返回值与模型不匹配(缺少字段或类型错误),FastAPI会抛出500错误(开发阶段应避免)。
    2.3.3 response_model与函数返回类型注解的关系

    推荐同时使用两者:response_model用于运行时过滤和文档,返回类型注解用于编辑器和类型检查。

    @app.get("/user", response_model=UserOut)
    def get_user() > UserOut:
    return user_data

    2.4 统一返回格式封装(企业级实践)

    在实际项目中,为了前端便于统一处理错误和成功响应,通常会封装一个统一的响应格式,例如:

    {
    "code": 0,
    "message": "success",
    "data": {}
    }

    对于错误响应:

    {
    "code": 40001,
    "message": "用户名已存在",
    "data": null
    }

    2.4.1 为什么需要统一格式?
    • 前端统一拦截:无论成功失败,都通过code字段判断,而非依赖HTTP状态码。
    • 业务错误码细化:HTTP状态码有限(如400涵盖多种错误),自定义code可精确定位。
    • 扩展性:可以加入timestamp、request_id等字段便于排查。
    2.4.2 FastAPI中如何优雅实现?

    方案:定义通用的响应模型,并编写辅助函数返回。同时利用FastAPI的response_model可以指定data字段的动态类型。

    2.5 与Flask/Django的响应处理对比

    框架指定状态码响应格式控制响应模型过滤统一封装
    FastAPI 装饰器参数或status_code 可返回任意类型,自动转JSON 内置response_model 自定义辅助函数
    Flask return jsonify(…), 201 需手动jsonify 无内置,需手动过滤 自定义装饰器或函数
    Django return JsonResponse(…, status=201) 类似Flask DRF提供Serializer DRF的Response可封装

    FastAPI的优势在于类型安全与文档自动生成的无缝集成。


    3. 环境搭建 & 实操准备

    3.1 复用项目环境

    继续使用第5课的项目(lesson03_first_project)。确保虚拟环境已激活。

    3.2 新增代码文件

    在app/api/v1/下创建http_methods_demo.py,并在main.py中注册。

    touch app/api/v1/http_methods_demo.py

    main.py中注册:

    from app.api.v1 import http_methods_demo
    app.include_router(http_methods_demo.router, prefix="/api/v1/http", tags=["HTTP方法与响应"])

    3.3 安装依赖(无额外需求)


    4. 手把手代码实战

    4.1 不同HTTP方法的接口实现(模拟商品管理)

    # app/api/v1/http_methods_demo.py
    from fastapi import APIRouter, HTTPException, status, Response
    from pydantic import BaseModel, Field
    from typing import Optional, List, Dict
    from datetime import datetime

    router = APIRouter()

    # ———- 数据模拟 ———-
    fake_products_db: Dict[int, dict] = {}
    counter = 1

    class ProductCreate(BaseModel):
    name: str = Field(..., min_length=1)
    price: float = Field(..., gt=0)
    stock: int = Field(0, ge=0)

    class ProductUpdate(BaseModel):
    name: Optional[str] = Field(None, min_length=1)
    price: Optional[float] = Field(None, gt=0)
    stock: Optional[int] = Field(None, ge=0)

    class ProductOut(BaseModel):
    id: int
    name: str
    price: float
    stock: int
    created_at: datetime

    # ———- 1. GET – 获取列表和详情 ———-
    @router.get("/products", response_model=List[ProductOut])
    async def list_products():
    """GET /products – 获取所有商品列表"""
    return list(fake_products_db.values())

    @router.get("/products/{product_id}", response_model=ProductOut)
    async def get_product(product_id: int):
    """GET /products/{id} – 获取单个商品"""
    product = fake_products_db.get(product_id)
    if not product:
    raise HTTPException(status_code=404, detail="商品不存在")
    return product

    # ———- 2. POST – 创建资源,返回201 ———-
    @router.post("/products", response_model=ProductOut, status_code=status.HTTP_201_CREATED)
    async def create_product(product: ProductCreate):
    """POST /products – 创建新商品"""
    global counter
    now = datetime.now()
    new_product = ProductOut(
    id=counter,
    name=product.name,
    price=product.price,
    stock=product.stock,
    created_at=now
    )
    fake_products_db[counter] = new_product.model_dump()
    counter += 1
    return new_product

    # ———- 3. PUT – 全量更新 ———-
    @router.put("/products/{product_id}", response_model=ProductOut)
    async def update_product_full(product_id: int, product: ProductCreate):
    """PUT /products/{id} – 全量更新(必须提供所有字段)"""
    if product_id not in fake_products_db:
    raise HTTPException(status_code=404, detail="商品不存在")
    existing = fake_products_db[product_id]
    updated = ProductOut(
    id=product_id,
    name=product.name,
    price=product.price,
    stock=product.stock,
    created_at=existing["created_at"] # 保留创建时间
    )
    fake_products_db[product_id] = updated.model_dump()
    return updated

    # ———- 4. PATCH – 部分更新 ———-
    @router.patch("/products/{product_id}", response_model=ProductOut)
    async def update_product_partial(product_id: int, update: ProductUpdate):
    """PATCH /products/{id} – 部分更新(只更新传递的字段)"""
    if product_id not in fake_products_db:
    raise HTTPException(status_code=404, detail="商品不存在")
    current = fake_products_db[product_id]
    # 只更新传入的非None字段
    update_data = update.model_dump(exclude_unset=True)
    current.update(update_data)
    # 重新校验并构建ProductOut(确保类型正确)
    updated = ProductOut(**current)
    fake_products_db[product_id] = updated.model_dump()
    return updated

    # ———- 5. DELETE – 删除资源,返回204 ———-
    @router.delete("/products/{product_id}", status_code=status.HTTP_204_NO_CONTENT)
    async def delete_product(product_id: int):
    """DELETE /products/{id} – 删除商品,返回204 No Content"""
    if product_id not in fake_products_db:
    raise HTTPException(status_code=404, detail="商品不存在")
    del fake_products_db[product_id]
    # 返回204时不应有响应体,FastAPI会自动处理
    return Response(status_code=status.HTTP_204_NO_CONTENT)

    测试方法:

    # 1. POST 创建商品
    curl -X POST http://localhost:8000/api/v1/http/products \\
    -H "Content-Type: application/json" \\
    -d '{"name": "手机", "price": 2999, "stock": 100}'
    # 响应201,带id和created_at

    # 2. GET 列表
    curl http://localhost:8000/api/v1/http/products

    # 3. PUT 全量更新
    curl -X PUT http://localhost:8000/api/v1/http/products/1 \\
    -H "Content-Type: application/json" \\
    -d '{"name": "旗舰手机", "price": 3999, "stock": 50}'

    # 4. PATCH 部分更新
    curl -X PATCH http://localhost:8000/api/v1/http/products/1 \\
    -H "Content-Type: application/json" \\
    -d '{"stock": 80}'

    # 5. DELETE 删除
    curl -X DELETE http://localhost:8000/api/v1/http/products/1 -v
    # 响应状态码应为204

    4.2 响应模型的高级用法:字段别名、排除空值、嵌套

    # ———- 响应模型进阶 ———-
    from pydantic import BaseModel, Field, ConfigDict

    class User(BaseModel):
    id: int
    username: str
    password: str # 敏感字段,不应返回

    class UserPublic(BaseModel):
    id: int
    username: str
    # 使用别名,输出时字段名变为 "user_name"
    # 注意:Field别名会影响序列化,需要配置 model_config 或使用 serialization_alias
    model_config = ConfigDict(populate_by_name=True)

    @router.get("/users/{user_id}", response_model=UserPublic)
    async def get_user(user_id: int):
    # 模拟从数据库取出包含密码的用户数据
    user = User(id=1, username="alice", password="secret")
    return user # 密码不会返回,因为UserPublic中没有password字段

    4.3 自定义状态码与异常处理

    # ———- 自定义异常与状态码 ———-
    from fastapi import HTTPException, Header

    @router.post("/transfer")
    async def transfer_money(
    amount: float,
    from_account: str,
    to_account: str,
    x_request_id: Optional[str] = Header(None)
    ):
    """
    模拟转账接口,演示不同状态码的使用
    """

    if amount <= 0:
    raise HTTPException(status_code=400, detail="金额必须大于0")
    if from_account == to_account:
    raise HTTPException(status_code=400, detail="不能转入同一账户")
    # 模拟余额不足(业务错误,但HTTP状态码仍用400或422)
    if amount > 1000:
    raise HTTPException(status_code=402, detail="余额不足") # 402 Payment Required 非标准但可用
    # 成功返回200
    return {"message": "转账成功", "transfer_id": "T123"}

    4.4 统一返回格式封装(企业级实战)

    4.4.1 定义统一响应模型

    新建app/schemas/response.py(创建目录):

    mkdir -p app/schemas
    touch app/schemas/__init__.py
    touch app/schemas/response.py

    app/schemas/response.py:

    from typing import Generic, TypeVar, Optional
    from pydantic import BaseModel, Field

    T = TypeVar('T')

    class ResponseModel(BaseModel, Generic[T]):
    """
    统一响应格式
    code: 业务状态码,0表示成功,其他为错误码
    message: 提示信息
    data: 实际数据,可为None或任意类型
    """

    code: int = 0
    message: str = "success"
    data: Optional[T] = None

    @classmethod
    def success(cls, data: Optional[T] = None, message: str = "success"):
    return cls(code=0, message=message, data=data)

    @classmethod
    def error(cls, code: int = 1, message: str = "error", data: Optional[T] = None):
    return cls(code=code, message=message, data=data)

    4.4.2 在接口中使用统一响应格式

    在http_methods_demo.py中引入并使用:

    from app.schemas.response import ResponseModel

    @router.get("/unified/products", response_model=ResponseModel[List[ProductOut]])
    async def unified_list_products():
    """使用统一返回格式的商品列表"""
    data = list(fake_products_db.values())
    return ResponseModel.success(data=data)

    @router.post("/unified/products", response_model=ResponseModel[ProductOut], status_code=201)
    async def unified_create_product(product: ProductCreate):
    """创建商品,返回统一格式"""
    # … 创建逻辑省略,假设备有new_product
    # 假设 new_product 是 ProductOut 实例
    return ResponseModel.success(data=new_product, message="创建成功")

    @router.get("/unified/products/{product_id}", response_model=ResponseModel[ProductOut])
    async def unified_get_product(product_id: int):
    product = fake_products_db.get(product_id)
    if not product:
    return ResponseModel.error(code=404, message="商品不存在")
    return ResponseModel.success(data=ProductOut(**product))

    4.4.3 实现全局异常拦截统一错误响应(预览)

    为了所有异常都返回统一格式,可以添加全局异常处理器(第12课详讲),这里简单展示:

    from fastapi import Request
    from fastapi.responses import JSONResponse

    @router.exception_handler(HTTPException)
    async def http_exception_handler(request: Request, exc: HTTPException):
    return JSONResponse(
    status_code=exc.status_code,
    content=ResponseModel.error(code=exc.status_code, message=exc.detail).model_dump()
    )

    但通常不会覆盖FastAPI的默认422等,此部分后续课程深入。

    4.5 使用response_model_include和response_model_exclude

    在复杂场景中,可以动态选择返回字段:

    @router.get("/products/{product_id}/partial", response_model=ProductOut, response_model_include={"id", "name"})
    async def get_product_partial(product_id: int):
    """只返回id和name字段"""
    product = fake_products_db.get(product_id)
    if not product:
    raise HTTPException(404)
    return product

    或者排除字段:

    response_model_exclude={"created_at"}

    4.6 响应模型与路径操作函数的返回类型注解配合

    from typing import Annotated

    @router.get("/example", response_model=ProductOut)
    def example() > ProductOut:
    # 返回类型注解帮助IDE检查
    return {"id": 1, "name": "test", "price": 10, "stock": 5, "created_at": datetime.now()}

    如果返回的字典缺少created_at字段,Pydantic会抛出ValidationError,导致500错误。因此务必保证数据完整性。

    4.7 处理文件下载等非JSON响应

    统一返回格式通常仅适用于JSON API。对于文件下载、图片等,直接返回FileResponse:

    from fastapi.responses import FileResponse

    @router.get("/download/{file_name}")
    async def download_file(file_name: str):
    # 假设文件存在于 static 目录
    return FileResponse(f"static/{file_name}", media_type="application/octet-stream")

    4.8 请求方法实践:创建完整CRUD + 统一响应

    我们将上面的商品管理接口全部改造为统一响应格式,并整合到一个完整示例中。

    # 统一响应版商品管理
    from app.schemas.response import ResponseModel

    @router.get("/v2/products", response_model=ResponseModel[List[ProductOut]])
    async def v2_list_products():
    return ResponseModel.success(data=list(fake_products_db.values()))

    @router.post("/v2/products", response_model=ResponseModel[ProductOut], status_code=201)
    async def v2_create_product(product: ProductCreate):
    global counter
    now = datetime.now()
    new_product = ProductOut(
    id=counter,
    name=product.name,
    price=product.price,
    stock=product.stock,
    created_at=now
    )
    fake_products_db[counter] = new_product.model_dump()
    counter += 1
    return ResponseModel.success(data=new_product, message="创建成功")

    @router.get("/v2/products/{product_id}", response_model=ResponseModel[ProductOut])
    async def v2_get_product(product_id: int):
    product = fake_products_db.get(product_id)
    if not product:
    return ResponseModel.error(code=404, message="商品不存在")
    return ResponseModel.success(data=ProductOut(**product))

    # 类似实现 PUT、PATCH、DELETE,为节省篇幅,省略部分,但思路一致

    测试:

    curl http://localhost:8000/api/v1/http/v2/products
    # 响应: {"code":0,"message":"success","data":[…]}


    5. 重点知识点总结

    HTTP方法速查表

    装饰器典型用途状态码幂等
    @app.get 查询 200
    @app.post 创建 201
    @app.put 全量更新 200 或 204
    @app.patch 部分更新 200
    @app.delete 删除 204

    状态码选择最佳实践

    • 200:成功且有响应体(GET、PUT、PATCH)
    • 201:创建成功,响应体应包含新资源
    • 204:成功但无响应体(DELETE、PUT更新且不返回内容)
    • 400:客户端请求错误(参数缺失、格式错误)
    • 401:未认证
    • 403:已认证但无权限
    • 404:资源不存在
    • 422:请求体语法正确但语义错误(Pydantic默认)
    • 500:服务器内部错误(不应该显式返回,而是让框架处理)

    响应模型核心语法

    @app.get("/path", response_model=SomeModel, status_code=200)
    def func() > SomeModel:
    return data

    统一返回格式优势与实现

    • 定义ResponseModel[T]泛型模型
    • 提供success和error工厂方法
    • 在接口中返回ResponseModel.success(data=…)

    易错点与避坑指南

  • ❌ DELETE返回200但带有响应体:有些前端预期204,不一致可能导致问题。建议统一返回204无内容,或200带{"message":"deleted"}。
  • ❌ 使用response_model但返回的数据包含模型未定义的字段:不会报错,字段自动过滤。但注意如果返回模型实例并且有额外字段,不会被过滤(因为模型实例序列化只包含定义的字段)。
  • ❌ PUT接口忘记处理未传递的字段:PUT要求全量更新,未传递的字段应重置为默认值或null。通常应使用ProductCreate模型(所有字段必选)而非可选模型。
  • ❌ PATCH接口错误地使用model.model_dump()包含默认值:使用exclude_unset=True只获取显式设置的字段。
  • ❌ 返回204时仍然写了响应体:FastAPI会忽略响应体,但最好显式return Response(status_code=204)。
  • ❌ 在统一响应格式中,将HTTP状态码与业务code混淆:例如HTTP返回200但业务code=404表示资源不存在。前端需要同时判断两者,增加了复杂度。建议业务错误时HTTP状态码仍用200,但通过code区分(即完全用业务code)。或者严格遵循HTTP语义:资源不存在返回404,前端统一处理。两种风格各有利弊,本节课采用HTTP状态码200+业务code的“阿里风格”。
  • 最佳实践

  • 遵循HTTP方法语义:GET只读,POST创建,PUT全量,PATCH部分,DELETE删除。
  • 合理使用状态码:不要一律返回200,让客户端能从状态码快速判断成功/失败类型。
  • 始终使用response_model:即使当前没有敏感字段,也为将来扩展预留。
  • 为响应模型设置ConfigDict(extra="forbid"):避免意外返回额外字段。
  • 统一响应格式建议:如果团队内部统一使用业务code,那么HTTP状态码一律用200,错误信息通过code+message传递(便于前端统一拦截)。如果是公开API,建议遵循HTTP语义。
  • 使用response_model_exclude_unset:对于PATCH返回,可以排除未设置的字段。

  • 6. 课后作业 & 思考题

    实操练习题(必做)

  • 基础练习:为第5课的商品管理接口增加响应模型和统一格式

    • 定义ProductOut,排除created_at敏感信息(或保留但只读)
    • 所有接口使用response_model=ResponseModel[T]返回统一格式
  • 进阶练习:实现一个用户管理接口

    • 用户注册(POST):返回201,使用统一格式
    • 用户信息查询(GET):返回200,但密码字段不出现在响应模型中
    • 用户更新(PATCH):支持部分更新,返回200
    • 用户删除(DELETE):返回204
  • 挑战练习:实现文件上传接口(预览)

    • POST /upload 接收文件,返回文件信息(文件名、大小、上传时间),使用统一格式
    • 参考FastAPI File 和 UploadFile(后续课程会讲,可提前自学)
  • 理论思考题

  • 为什么PUT要求幂等而PATCH不要求? 结合实际场景说明。
  • 使用response_model时,如果路径操作函数返回的字典缺少模型中的必选字段,会发生什么?
  • 统一返回格式中,HTTP状态码应该全部使用200吗? 分析两种策略的优缺点。
  • 如何实现动态响应模型(根据请求参数返回不同字段)? (提示:response_model可以是Union或使用Response直接构造)
  • 拓展学习方向

    • RESTful API成熟度模型(Richardson Maturity Model)
    • OpenAPI规范中关于响应状态码和响应模型的定义
    • FastAPI的status模块:查看所有标准状态码常量
    • 自定义响应类:继承JSONResponse实现统一格式自动包装

    7. 本节干货总结

    核心考点(面试/自测)

  • HTTP方法中PUT和PATCH的区别? PUT全量替换,PATCH部分更新。PUT要求幂等。

  • 201和204状态码的区别? 201表示创建成功,通常有响应体(新资源);204表示成功但无响应体。

  • 如何在FastAPI中返回特定的状态码? 装饰器参数status_code,或return JSONResponse(…, status_code=xxx),或raise HTTPException(status_code=xxx)。

  • response_model的作用是什么? 过滤输出字段、类型转换、生成文档。

  • 统一返回格式如何设计? 通常包含code、message、data三个字段,使用泛型支持不同类型data。

  • 实际工作应用场景

    • 场景1:RESTful API设计 按照本节课的方法设计用户、订单、商品等资源接口,前端对接体验良好。

    • 场景2:微服务间调用 使用统一格式封装,方便调用方解析,错误信息标准化。

    • 场景3:自动生成文档 充分利用response_model,让Swagger UI展示精确的响应结构,减少沟通成本。

    • 场景4:敏感数据保护 响应模型中排除密码、身份证号等字段,避免泄露。

    • 场景5:版本升级兼容 通过响应模型可以增加新字段而不破坏旧客户端(只要使用extra="ignore")。


    下节课预告

    本节课我们规范了HTTP方法和响应输出,建立了企业级API的骨架。但到目前为止,参数校验还停留在简单的Field约束层面。实际业务中,我们需要对路径参数、查询参数、Header、Cookie等进行更复杂的校验(如正则、枚举、自定义逻辑)。

    下节课(第7课),我们将深入学习:

    • Query / Path / Header / Cookie参数的高级校验
    • 使用Query、Path、Header、Cookie函数添加约束
    • 别名、废弃参数、示例值等元数据配置
    • 结合依赖注入实现参数预处理

    让你的参数处理能力从“能用”提升到“专业”。


    🔗《20节课 FastAPI 从入门到精通》系列课程导航

    去订阅

    🌟 感谢您耐心阅读到这里! 💡 如果本文对您有所启发欢迎: 👍 点赞📌 收藏 📤 分享给更多需要的伙伴。 🗣️ 期待在评论区看到您的想法, 共同进步。 🔔 关注我,持续获取更多干货内容~ 🤗 我们下篇文章见~

    赞(0)
    未经允许不得转载:171主机测评 » 第6课:FastAPI|HTTP请求方法|状态码|响应模型与统一返回格式封装
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址