FastAPI 零基础教程(二)- 请求体与响应模型,1天吃透Pydantic无缝衔接

文章目录


前言

上一阶段我们掌握了FastAPI基础接口、路径参数、查询参数、RESTful规范与自动文档,能快速编写基础查询接口。但真实业务中,新增、修改、编辑数据几乎都需要传递复杂JSON数据,单纯的查询参数完全无法满足需求。

这就需要用到FastAPI核心核心能力:请求体 + 响应模型

本阶段最大优势:100%复用你已精通的Pydantic知识。FastAPI的请求校验、数据序列化、字段约束、脱敏返回,全部基于Pydantic v2实现,无需学习新语法,无缝衔接旧知识。

本文耗时1天学完,彻底解决业务核心问题:

  1. 复杂JSON请求参数接收、自动校验、类型转换;

  2. 接口返回数据脱敏、字段过滤、统一格式化;

  3. 路径参数、查询参数、请求体混合传参;

  4. Header、Cookie请求头参数获取;

  5. 生产级DTO分层(入参模型/出参模型分离)。

一、阶段学习目标

  1. 彻底分清查询参数请求体的使用场景与区别;

  2. 熟练使用Pydantic BaseModel定义POST/PUT请求体,掌握字段校验、默认值、必填规则;

  3. 掌握嵌套请求体模型,适配复杂多层JSON参数场景;

  4. 精通response_model响应模型,实现密码脱敏、字段过滤、数据格式化

  5. 掌握三种参数混合使用:路径参数 + 查询参数 + 请求体;

  6. 学会获取Header请求头、Cookie参数;

  7. 搭建生产级统一接口返回体,实现前后端格式统一;

  8. 完成用户新增、编辑、脱敏查询完整实战接口。

二、核心概念:查询参数 vs 请求体

2.1 核心区别(新手必记)

上一阶段的路径参数、查询参数,仅适用于简单少量参数、查询场景 ;而请求体专门用于复杂JSON、大批量数据、新增/修改场景

参数类型 传输位置 适用请求方法 数据格式 业务场景
查询参数 URL ? 后拼接 GET 为主 简单键值对 分页、筛选、搜索、ID查询
请求体 HTTP 请求体 POST/PUT/PATCH 复杂JSON、嵌套结构 新增数据、编辑数据、批量提交

2.2 FastAPI识别规则

这是FastAPI的核心智能规则,无需手动声明:

  1. 普通基础类型(int/str/bool)= 自动识别为查询参数

  2. Pydantic模型类型(继承BaseModel)= 自动识别为请求体

三、核心实战:Pydantic请求体

所有请求体基于 Pydantic BaseModel 定义,你之前学的所有校验规则(长度、正则、邮箱、数值范围)全部通用。

3.1 基础请求体(单层级)

实现用户新增接口,接收JSON请求体,自动完成参数校验。

python 复制代码
from fastapi import FastAPI, status
from pydantic import BaseModel, EmailStr, field_validator

app = FastAPI(title="请求体与响应模型实战")

# 定义请求体模型:用户新增入参
class UserCreate(BaseModel):
    # 必填字段
    username: str
    email: EmailStr
    password: str
    # 可选字段,带默认值
    age: int | None = None
    is_active: bool = True

    # 自定义字段校验(复用Pydantic能力)
    @field_validator("username")
    def check_username(cls, v):
        if len(v) < 3:
            raise ValueError("用户名长度不能小于3位")
        return v

    @field_validator("password")
    def check_password(cls, v):
        if len(v) < 6:
            raise ValueError("密码长度不能小于6位")
        return v

# POST接口:接收JSON请求体
@app.post("/users", summary="新增用户", status_code=status.HTTP_201_CREATED)
def create_user(user: UserCreate):
    # 自动校验参数,非法参数直接返回422错误
    # 直接返回请求数据(后续改造为脱敏响应模型)
    return user

3.2 运行测试

启动项目:uvicorn main:app --reload,访问 http://127.0.0.1:8000/docs 调试:

正确请求体示例:

json 复制代码
{
    "username": "zhangsan",
    "email": "zhangsan@qq.com",
    "password": "123456",
    "age": 22
}

非法场景自动拦截:邮箱格式错误、用户名过短、密码位数不足,无需手写if判断。

3.3 嵌套请求体(复杂业务必备)

真实业务常出现多层嵌套JSON(如用户绑定收货地址),FastAPI+Pydantic完美支持嵌套模型。

python 复制代码
# 嵌套子模型:收货地址
class Address(BaseModel):
    province: str
    city: str
    detail: str

# 父模型:用户新增(嵌套地址)
class UserCreateWithAddr(BaseModel):
    username: str
    email: EmailStr
    password: str
    address: Address  # 嵌套子模型
    age: int | None = None

# 嵌套请求体接口
@app.post("/users/addr", summary="新增用户+绑定地址")
def create_user_with_addr(user: UserCreateWithAddr):
    return user

请求体支持多层嵌套,自动递归校验所有字段,适配复杂业务参数。

四、核心重点:响应模型与数据脱敏(生产必备)

最大生产坑点:直接返回数据库/请求原始数据,会泄露密码、密钥等敏感信息。

解决方案:使用 response_model 响应模型,实现字段过滤、敏感数据脱敏、返回格式统一。

4.1 入参、出参模型分层(生产规范)

严格分层,禁止同一模型既做入参又做出参:

  • UserCreate:新增入参模型(包含密码)

  • UserPublic:返回出参模型(剔除密码、脱敏字段)

4.2 响应模型实战脱敏

python 复制代码
# 响应模型:用户公开信息(脱敏)
class UserPublic(BaseModel):
    id: int
    username: str
    email: EmailStr
    age: int | None = None
    is_active: bool

    # 关键配置:支持ORM/字典对象自动转换
    class Config:
        from_attributes = True

# 模拟数据库
fake_user_db = []

# 改造新增接口,添加响应模型脱敏
@app.post("/users", summary="新增用户(脱敏返回)", response_model=UserPublic, status_code=status.HTTP_201_CREATED)
def create_user(user: UserCreate):
    # 模拟生成用户ID、存入数据库
    new_user = user.model_dump()
    new_user["id"] = len(fake_user_db) + 1
    fake_user_db.append(new_user)
    # 返回完整数据,响应模型自动剔除password字段
    return new_user

核心效果 :前端传入密码,后端存储后,返回数据自动剔除password字段,彻底杜绝敏感信息泄露。

4.3 响应模型进阶配置

4.3.1 exclude_unset:只返回赋值字段

适用于局部更新场景,不返回默认空字段:

python 复制代码
@app.post("/users", response_model=UserPublic, response_model_exclude_unset=True)
4.3.2 手动指定排除/包含字段
python 复制代码
# 仅返回指定字段
@app.get("/users/{user_id}", response_model=UserPublic, response_model_include={"id", "username", "email"})

# 排除指定敏感字段
@app.get("/users/{user_id}", response_model_exclude={"password"})

五、混合传参:三种参数同时使用

真实业务高频场景:路径参数 + 查询参数 + 请求体 混合传参,FastAPI可智能区分、互不冲突。

场景:根据用户ID(路径参数),分页(查询参数)更新用户信息(请求体)

python 复制代码
# 用户更新入参模型
class UserUpdate(BaseModel):
    username: str | None = None
    email: EmailStr | None = None
    age: int | None = None

# 混合传参接口
@app.put("/users/{user_id}", summary="更新用户信息(混合传参)", response_model=UserPublic)
def update_user(
    # 1. 路径参数:用户ID
    user_id: int,
    # 2. 查询参数:分页/附加筛选
    is_validate: bool = True,
    # 3. 请求体:更新数据JSON
    user: UserUpdate
):
    return {
        "user_id": user_id,
        "is_validate": is_validate,
        **user.model_dump(exclude_unset=True)
    }

FastAPI自动识别三类参数,无需手动区分,极大简化开发。

业务常用:获取请求头Token、设备信息、Cookie会话ID,FastAPI原生支持。

python 复制代码
from fastapi import Header, Cookie

@app.get("/headers", summary="获取请求头参数")
def get_headers(
    # 获取自定义请求头
    token: str | None = Header(default=None),
    # 获取浏览器UA
    user_agent: str | None = Header(default=None),
    # 获取Cookie
    session_id: str | None = Cookie(default=None)
):
    return {
        "token": token,
        "user_agent": user_agent,
        "session_id": session_id
    }

常用场景:接口鉴权获取Token、统计客户端设备、会话状态校验。

七、生产级统一响应体封装

原生返回格式杂乱,前端对接繁琐,我们封装泛型统一返回体,适配所有接口,前后端统一规范。

python 复制代码
from typing import Generic, TypeVar, Optional

# 泛型定义
T = TypeVar("T")

# 统一响应模型
class ApiResp(BaseModel, Generic[T]):
    code: int = 200
    msg: str = "请求成功"
    data: Optional[T] = None

# 快捷工具函数
def success_resp(data: T | None = None, msg: str = "请求成功") -> ApiResp[T]:
    return ApiResp(data=data, msg=msg)

def fail_resp(msg: str = "请求失败", code: int = 400) -> ApiResp:
    return ApiResp(code=code, msg=msg)

# 改造接口,使用统一返回体
@app.get("/users/{user_id}", summary="查询用户详情", response_model=ApiResp[UserPublic])
def get_user(user_id: int):
    if user_id > len(fake_user_db):
        return fail_resp(msg="用户不存在", code=404)
    return success_resp(data=fake_user_db[user_id-1])

统一返回格式:{code, msg, data},前端无需适配多种返回结构,生产项目必备。

八、阶段综合实战:完整用户CRUD(脱敏+统一返回)

整合本阶段所有知识点,实现可直接上线的用户新增、查询、更新接口,包含:参数校验、嵌套模型、数据脱敏、统一响应、混合传参。

python 复制代码
from fastapi import FastAPI, Header, Cookie, status
from pydantic import BaseModel, EmailStr, field_validator
from typing import Generic, TypeVar, Optional

# ====================== 1. 统一响应封装 ======================
T = TypeVar("T")
class ApiResp(BaseModel, Generic[T]):
    code: int = 200
    msg: str = "请求成功"
    data: Optional[T] = None

def success_resp(data=None, msg="请求成功"):
    return ApiResp(data=data, msg=msg)

def fail_resp(msg="请求失败", code=400):
    return ApiResp(code=code, msg=msg)

# ====================== 2. 模型分层(入参/出参) ======================
class UserCreate(BaseModel):
    username: str
    email: EmailStr
    password: str
    age: int | None = None

    @field_validator("username")
    def check_username(cls, v):
        if len(v) < 3:
            raise ValueError("用户名至少3位")
        return v

class UserUpdate(BaseModel):
    username: str | None = None
    email: EmailStr | None = None
    age: int | None = None

class UserPublic(BaseModel):
    id: int
    username: str
    email: EmailStr
    age: int | None = None

    class Config:
        from_attributes = True

# ====================== 3. 项目初始化 ======================
app = FastAPI(title="第二阶段综合实战")
fake_db = []

# ====================== 4. 业务接口 ======================
@app.post("/users", summary="新增用户", response_model=ApiResp[UserPublic], status_code=status.HTTP_201_CREATED)
def create_user(user: UserCreate):
    """新增用户,自动脱敏返回"""
    new_user = user.model_dump()
    new_user["id"] = len(fake_db) + 1
    fake_db.append(new_user)
    return success_resp(data=new_user)

@app.get("/users/{user_id}", summary="查询用户", response_model=ApiResp[UserPublic])
def get_user(user_id: int):
    """根据ID查询脱敏用户信息"""
    if user_id < 1 or user_id > len(fake_db):
        return fail_resp("用户不存在", 404)
    return success_resp(data=fake_db[user_id-1])

@app.put("/users/{user_id}", summary="更新用户", response_model=ApiResp[UserPublic])
def update_user(user_id: int, user: UserUpdate):
    """局部更新用户信息"""
    if user_id < 1 or user_id > len(fake_db):
        return fail_resp("用户不存在", 404)
    update_data = user.model_dump(exclude_unset=True)
    fake_db[user_id-1].update(update_data)
    return success_resp(data=fake_db[user_id-1])

九、新手高频避坑指南

  1. GET接口使用请求体:HTTP规范禁止GET携带请求体,查询参数只用查询参数;

  2. 入参出参共用一个模型:必然导致密码、密钥泄露,必须分层DTO;

  3. 忽略from_attributes配置:ORM对象无法转换为响应模型,直接报错;

  4. 不做数据脱敏直接返回原始数据:生产重大安全漏洞;

  5. 混合传参参数名冲突:路径参数、查询参数、请求体参数建议命名区分;

  6. 严格分层模型:Create/Update/Public三类模型各司其职;

  7. 所有生产接口统一响应体,杜绝返回格式混乱;

  8. 复杂参数一律用请求体,简单筛选用查询参数。

十、阶段核心总结

  1. 参数区分:基础类型=查询参数,Pydantic模型=请求体,GET无请求体、POST/PUT核心用请求体;

  2. 请求体核心:完全复用Pydantic校验能力,支持单层/嵌套复杂JSON,自动参数校验;

  3. 响应模型核心:response_model实现字段过滤、敏感数据脱敏、返回格式标准化,生产必备;

  4. 混合传参:FastAPI支持路径+查询+请求体+Header+Cookie多参数共存,智能解析;

  5. 工程规范:入参出参模型分层、统一泛型响应体,贴合企业后端开发标准。