4. 请求体与Pydantic模型

任务说明

本案例要求使用 FastAPI 与 Pydantic 完成一个最小可用的用户注册接口:客户端通过 POST /users 提交 JSON 请求体,服务端使用 Pydantic 模型自动完成字段类型校验、必填校验与约束校验,校验通过后返回创建成功的用户信息(含服务端生成的 id)。

核心实现

# main.py
from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel, EmailStr, Field
from typing import Optional

app = FastAPI()

class UserCreate(BaseModel):
    """请求体模型:定义客户端必须提交的字段及校验规则"""
    username: str = Field(..., min_length=3, max_length=20, description="用户名,3-20个字符")
    email: EmailStr = Field(..., description="合法邮箱地址")
    age: Optional[int] = Field(None, ge=18, le=100, description="年龄,18-100之间(可选)")

class UserResponse(BaseModel):
    """响应模型:返回给客户端的数据结构"""
    id: int
    username: str
    email: EmailStr
    age: Optional[int] = None

# 模拟数据库存储(仅用于演示,实际项目应使用真实数据库)
users_db = {}
user_id_counter = 1

@app.post("/users", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
async def create_user(user: UserCreate):
    """
    接收请求体,Pydantic 自动完成类型转换与校验。
    校验失败时 FastAPI 自动返回 422 错误及详细原因。
    """
    global user_id_counter

    # 检查用户名是否已存在(业务校验)
    if any(u["username"] == user.username for u in users_db.values()):
        raise HTTPException(status_code=400, detail="用户名已存在")

    # 生成新用户数据
    new_user = {
        "id": user_id_counter,
        "username": user.username,
        "email": user.email,
        "age": user.age,
    }
    users_db[user_id_counter] = new_user
    user_id_counter += 1

    return new_user

代码说明

上述代码中,UserCreate 模型定义了请求体结构:username 为必填字符串且长度限制在 3~20 字符;email 使用 EmailStr 类型自动验证邮箱格式;age 为可选整数,若提供则必须在 18~100 之间。FastAPI 在路由处理函数中将 user: UserCreate 作为参数声明,即自动从请求 JSON 中解析并校验数据。校验失败时,FastAPI 自动返回 422 Unprocessable Entity,响应体中包含具体错误字段与原因。校验通过后,函数内部执行用户名唯一性检查(业务规则),通过后生成新用户记录并返回。

接口报文结构

POST /users 请求体 (JSON) {"username": "alice", "email": "alice@example.com", "age": 25} Content-Type: application/json Pydantic 自动校验规则 username: 必填, 3 ≤ len ≤ 20 email: 必须匹配邮箱格式 age: 可选, 18 ≤ age ≤ 100 校验失败 → 自动返回 422 + 错误详情 201 Created 响应 {"id": 1, "username": "alice", "email": "alice@example.com", "age": 25} 422 Unprocessable Entity {"detail": [{"loc": ["body", "email"], "msg": "value is not a valid email address", "type": "value_error"}]}

运行与验证

  1. 安装依赖:pip install fastapi uvicorn pydantic[email]
  2. 启动服务:uvicorn main:app --reload --port 8000
  3. 发送合法请求(预期返回 201 与用户数据):
curl -X POST http://127.0.0.1:8000/users \
  -H "Content-Type: application/json" \
  -d '{"username": "alice", "email": "alice@example.com", "age": 25}'

预期输出(与代码逻辑一致):{"id":1,"username":"alice","email":"alice@example.com","age":25}

  1. 发送非法请求(邮箱格式错误,预期返回 422):
curl -X POST http://127.0.0.1:8000/users \
  -H "Content-Type: application/json" \
  -d '{"username": "alice", "email": "not-an-email", "age": 25}'

预期输出:HTTP 422,响应体包含 email 字段的校验错误详情。

判定标准

  • 合法请求返回 201 状态码,响应体包含服务端生成的 id 字段;
  • 缺少必填字段、邮箱格式错误、用户名长度不足或 age 超出范围时,均返回 422 且错误信息指明具体字段;
  • 重复提交相同用户名返回 400 与错误提示“用户名已存在”。