请求参数验证(Validation) —— 这是 FastAPI 最强大的特性之一。

FastAPI 基于 Pydantic,提供了多种方式对输入数据进行校验。下面我用 同一个业务案例,分别用 4 种典型验证方式 实现,并对比它们的适用场景、优缺点和特点


🎯 业务案例:用户注册 API

要求:

  • username:字符串,3~20 字符,仅允许字母、数字、下划线
  • password:字符串,至少 8 位,包含字母和数字
  • age:整数,13~120 岁
  • email:合法邮箱格式(可选)

我们将用以下 4 种方式实现验证:

  1. 基础类型提示 + 默认值
  2. 使用 Query, Path, Body 添加简单约束
  3. 使用 Pydantic Field + 正则/范围校验
  4. 自定义校验器(@validator@field_validator

✅ 方式 1:基础类型提示(最简,但能力有限)

from fastapi import FastAPI
from typing import Optional

app = FastAPI()

@app.post("/register1/")
def register1(
    username: str,
    password: str,
    age: int,
    email: Optional[str] = None
):
    return {"msg": "Registered!"}

🔍 特点:

  • ✅ 自动校验基本类型(str, int
  • 无法校验长度、格式、范围
  • ❌ 密码可以是 "1",用户名可以是 "a"
  • ❌ 邮箱可以是 "not-an-email"
💡 适合快速原型,不适合生产环境

✅ 方式 2:使用 FastAPI 的 Query / Body 添加简单约束

from fastapi import FastAPI, Body
from typing import Optional

app = FastAPI()

@app.post("/register2/")
def register2(
    username: str = Body(..., min_length=3, max_length=20),
    password: str = Body(..., min_length=8),
    age: int = Body(..., ge=13, le=120),  # ge=≥, le=≤
    email: Optional[str] = Body(None)
):
    return {"msg": "Registered!"}

🔍 特点:

  • ✅ 支持长度、数值范围校验
  • ✅ 错误时自动返回 422 + 详细信息
  • 无法校验复杂规则(如“密码必须含字母和数字”)
  • 正则表达式不支持Body 不支持 regex
  • ❌ 参数一多,函数签名变得冗长
💡 适合简单字段级约束,但逻辑复杂时不够用。

✅ 方式 3:使用 Pydantic Field(推荐!结构清晰)

from fastapi import FastAPI
from pydantic import BaseModel, Field
from typing import Optional
import re

class UserRegister(BaseModel):
    username: str = Field(..., min_length=3, max_length=20, pattern=r"^[a-zA-Z0-9_]+$")
    password: str = Field(..., min_length=8)
    age: int = Field(..., ge=13, le=120)
    email: Optional[str] = Field(None)

app = FastAPI()

@app.post("/register3/")
def register3(user: UserRegister):
    return {"msg": "Registered!", "user": user}
⚠️ 注意:pattern 在 Pydantic v2 中替代了旧版的 regex

🔍 特点:

  • 支持正则表达式pattern
  • ✅ 所有校验集中在一个模型中,结构清晰
  • ✅ 自动生成 OpenAPI 文档中的格式说明
  • ❌ 仍无法实现“密码必须同时含字母和数字”这类跨字符逻辑
💡 这是大多数场景的最佳选择

✅ 方式 4:自定义校验器(处理复杂逻辑)

使用 @field_validator(Pydantic v2 推荐)实现高级校验:

from fastapi import FastAPI
from pydantic import BaseModel, Field, field_validator
from typing import Optional
import re

class UserRegister(BaseModel):
    username: str = Field(..., min_length=3, max_length=20, pattern=r"^[a-zA-Z0-9_]+$")
    password: str = Field(..., min_length=8)
    age: int = Field(..., ge=13, le=120)
    email: Optional[str] = Field(None)

    @field_validator("password")
    @classmethod
    def validate_password(cls, v: str) -> str:
        if not re.search(r"[A-Za-z]", v):
            raise ValueError("Password must contain at least one letter")
        if not re.search(r"\d", v):
            raise ValueError("Password must contain at least one digit")
        return v

    @field_validator("email")
    @classmethod
    def validate_email(cls, v: str | None) -> str | None:
        if v is not None:
            if not re.match(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$", v):
                raise ValueError("Invalid email format")
        return v

app = FastAPI()

@app.post("/register4/")
def register4(user: UserRegister):
    return {"msg": "Registered!", "user": user}

🔍 特点:

  • ✅ 可实现任意复杂校验逻辑
  • ✅ 校验失败时返回友好错误信息
  • ✅ 保持模型为中心的设计
  • ⚠️ 需要写额外代码,但可测试、可复用
💡 适合安全敏感场景(如密码、身份证、手机号校验)。

📊 四种方式对比总结

特性方式1:基础类型方式2:Body/Query方式3:Field方式4:自定义校验器
类型校验
长度/范围
正则表达式✅(pattern✅(任意逻辑)
复杂业务规则
代码可读性低(参数臃肿)高(模型集中)中(需理解装饰器)
OpenAPI 文档支持基础部分完整(含 pattern)校验逻辑不显示
适用场景快速原型简单 API推荐:大多数项目密码、身份证、金融等强校验

✅ 最佳实践建议

  1. 优先使用 BaseModel + Field(方式3)—— 结构清晰,功能强大。
  2. 复杂规则用 @field_validator(方式4)—— 不要试图用正则解决所有问题。
  3. 避免在路由函数中写校验逻辑—— 违反关注点分离。
  4. 利用 FastAPI 自动生成的 /docs 测试校验效果

🧪 测试你的 API

访问 http://localhost:8000/docs,尝试以下无效输入:

  • username: "ab" → 太短
  • password: "12345678" → 缺少字母
  • age: 10 → 小于 13
  • email: "bad-email" → 格式错误

你会看到 FastAPI 返回清晰的 422 错误,例如:

{
  "detail": [
    {
      "type": "value_error",
      "loc": ["body", "password"],
      "msg": "Password must contain at least one letter",
      "input": "12345678"
    }
  ]
}