FastAPI 全栈后端(二):路由与数据模型

创作者: Yardon | GitHub: github.com/YardonYan | 版本: v1.0 |



RESTful 路由设计原则

RESTful API 的设计核心是把 URL 当作资源,HTTP 动词表示操作

复制代码
GET    /api/users          # 获取用户列表
POST   /api/users          # 创建用户
GET    /api/users/123      # 获取单个用户
PUT    /api/users/123      # 更新用户(全量)
PATCH  /api/users/123      # 更新用户(部分字段)
DELETE /api/users/123      # 删除用户

URL 用名词复数(users),动词用 HTTP method,不要在 URL 里写 getUser 之类的东西。


路径参数与查询参数

python 复制代码
from fastapi import FastAPI, Query
from enum import Enum

app = FastAPI()

class StatusEnum(str, Enum):
    active = "active"
    inactive = "inactive"

@app.get("/users/{user_id}")
async def get_user(
    user_id: int,                           # 路径参数
    include_posts: bool = False,            # 查询参数 ?include_posts=true
    status: StatusEnum = StatusEnum.active, # 枚举参数
    page: int = Query(1, ge=1),            # 页码,最小1
    size: int = Query(20, ge=1, le=100),   # 每页条数,1-100
    q: str | None = Query(None, min_length=2, description="搜索关键词"),
):
    """获取用户详情"""
    skip = (page - 1) * size
    # ... 数据库查询
    return {"user_id": user_id, "page": page, "q": q}

Query 装饰器提供了参数校验和文档描述。ge(greater or equal)、le(less or equal)确保传入值在合法范围内。

路径参数 vs 查询参数:如何选?

路径参数 (/users/123) 查询参数 (?page=2)
表示"哪个资源" 表示"怎么筛选/排序"
通常是唯一标识 可选,用于过滤/分页
必填 可选(有默认值)

请求体:Pydantic 模型进阶

python 复制代码
from pydantic import BaseModel, Field, EmailStr, field_validator
from datetime import date
from typing import Optional

class UserCreate(BaseModel):
    """创建用户时的请求体"""
    username: str = Field(..., min_length=3, max_length=50, description="用户名")
    email: EmailStr                                          # 自动验证邮箱格式
    password: str = Field(..., min_length=8)
    bio: Optional[str] = Field(None, max_length=200)
    birth_date: Optional[date] = None

    @field_validator("username")
    @classmethod
    def username_no_special_chars(cls, v: str) -> str:
        if not v.replace("_", "").replace("-", "").isalnum():
            raise ValueError("用户名只能包含字母、数字、下划线和连字符")
        return v.strip()

    @field_validator("password")
    @classmethod
    def password_strong_enough(cls, v: str) -> str:
        if not any(c.isdigit() for c in v):
            raise ValueError("密码必须包含至少一个数字")
        return v


@app.post("/users", status_code=201)
async def create_user(user: UserCreate):
    # user 已经被验证、转换完毕
    # 这里 user.username 已经被 strip 过了,email 格式已验证
    return {"id": 1234, "username": user.username}

@field_validator 可以编写复杂的自定义校验逻辑。比传统手写 if/else 清晰一百倍。


响应模型:控制输出

python 复制代码
from pydantic import BaseModel

class UserResponse(BaseModel):
    """对外输出的用户模型------绝不含 password"""
    id: int
    username: str
    email: str
    bio: str | None = None

    model_config = {"from_attributes": True}  # 允许从 ORM 对象转换

@app.get("/users/{user_id}", response_model=UserResponse)
async def get_user(user_id: int):
    user = await db.query(...)
    return user  # FastAPI 自动过滤掉 password 等敏感字段

response_model 就是你的安全检查门------数据库返回了 20 个字段,但前端只能看到你声明的 5 个。


错误处理与状态码

python 复制代码
from fastapi import HTTPException

@app.get("/users/{user_id}")
async def get_user(user_id: int):
    user = await find_user(user_id)
    if not user:
        raise HTTPException(status_code=404, detail=f"用户 {user_id} 不存在")
    return user

# 全局异常处理器
from fastapi import Request
from fastapi.responses import JSONResponse

@app.exception_handler(ValueError)
async def value_error_handler(request: Request, exc: ValueError):
    return JSONResponse(status_code=400, content={"detail": str(exc)})

路由分组与 API 版本管理

随着应用变大,main.py 里塞满 50 个路由函数会变成噩梦。FastAPI 提供了 APIRouter 来分组:

python 复制代码
# routers/users.py
from fastapi import APIRouter

router = APIRouter(prefix="/api/v1/users", tags=["用户管理"])

@router.get("/")
async def list_users(): ...

@router.post("/")
async def create_user(): ...

@router.get("/{user_id}")
async def get_user(user_id: int): ...
python 复制代码
# main.py
from routers import users, posts, auth

app = FastAPI()
app.include_router(users.router)
app.include_router(posts.router)
app.include_router(auth.router)

文件上传与静态文件

python 复制代码
from fastapi import UploadFile, File

@app.post("/upload")
async def upload_file(file: UploadFile = File(...)):
    # 不把整个文件读入内存------适合大文件
    contents = await file.read()
    with open(f"uploads/{file.filename}", "wb") as f:
        f.write(contents)
    return {"filename": file.filename, "size": len(contents)}

本章小结

概念 要点
路径参数 /users/{id} 表示资源,需精确匹配
查询参数 ?page=2 表示过滤/分页,用 Query() 校验
Pydantic 请求体验证、响应过滤、字段级校验链
APIRouter 按模块拆分路由,方便扩展

下一章:数据库与 ORM------用 SQLAlchemy 把数据持久化。


📌 创作者: Yardon | 🏠 个人网站: GlimmerAI.top

📖 本章是「FastAPI 全栈后端 」系列的第 2 章。下一章:数据库与ORM

🌟 欢迎大家来观看!

相关推荐
逻辑君2 小时前
Foresight研究报告【20260023】
人工智能·深度学习·机器学习·数学建模
雪隐2 小时前
AI股票小助手07-TA-Lib 技术指标计算实战
人工智能·后端
Litluecat2 小时前
配合多角色提示语,学习AI漫剧(刚开始学)
人工智能·学习·机器学习·ai·提示词·漫剧
北京耐用通信2 小时前
耐达讯自动化工业网关:极简组态实现 Modbus 转 PROFINET 稳定通讯
人工智能·物联网·网络协议·自动化·信息与通信
katttt_2 小时前
新视角随笔:私域 AI 落地,解锁小微经营的长效竞争力
人工智能
架构源启2 小时前
Spring AI进阶系列(17)- 未来展望与职业发展:Java 工程师迈向 AI 工程化与智能体架构的路线图
java·人工智能·spring
lichenyang4532 小时前
ArkTS 严格类型系统:我答错 2 道题后才真正搞懂的几条规则
前端
Cosolar2 小时前
深入理解 LangChain Callback 机制:从入门到实战
人工智能·后端·面试
小小小小宇2 小时前
定高、不定高、瀑布流虚拟列表
前端