FastAPI 框架 - 高级部分

FastAPI 框架 - 高级部分

6.1.11 数据库集成

简述:数据库是 Web 应用的核心组件。本节介绍 FastAPI 与 MySQL 数据库的集成,包括同步/异步连接池配置、SQLAlchemy ORM 模型定义、CRUD 操作、事务管理和多数据源操作,提供实际项目开发中可直接使用的完整方案。

同步数据库连接池配置

配置 SQLAlchemy 同步引擎与连接池参数,创建 Session 工厂并提供数据库会话的依赖注入函数,适用于传统同步路由。

python 复制代码
# database/connection.py
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, Session, DeclarativeBase
from typing import Generator

DATABASE_URL = "mysql+pymysql://root:password@localhost:3306/mydb?charset=utf8mb4"

engine = create_engine(
    DATABASE_URL,
    pool_size=10,
    max_overflow=20,
    pool_pre_ping=True,
    pool_recycle=1800,
    echo=False
)

SessionLocal = sessionmaker(
    autocommit=False,
    autoflush=False,
    bind=engine
)

class Base(DeclarativeBase):
    pass

def get_db() -> Generator[Session, None, None]:
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

异步数据库连接池配置

配置 SQLAlchemy 异步引擎与 AsyncSession 工厂,支持 async/await 异步路由,适用于高并发 I/O 密集型场景。

python 复制代码
# database/async_connection.py
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
from sqlalchemy.orm import DeclarativeBase
from typing import AsyncGenerator

ASYNC_DATABASE_URL = "mysql+aiomysql://root:password@localhost:3306/mydb?charset=utf8mb4"

async_engine = create_async_engine(
    ASYNC_DATABASE_URL,
    pool_size=10,
    max_overflow=20,
    pool_pre_ping=True,
    pool_recycle=1800,
    echo=False
)

AsyncSessionLocal = async_sessionmaker(
    async_engine,
    class_=AsyncSession,
    expire_on_commit=False
)

async def get_async_db() -> AsyncGenerator[AsyncSession, None]:
    async with AsyncSessionLocal() as session:
        yield session

连接池参数说明

逐项解释 SQLAlchemy 连接池核心参数的含义,并给出开发环境与生产环境的推荐值。

参数 说明 开发环境 生产环境
pool_size 池中最小连接数 5 CPU核心数×2
max_overflow 峰值最大额外连接数 10 20-30
pool_pre_ping 取连接前健康检查 False True
pool_recycle 连接回收时间(秒) 不设置 1800-3600
pool_timeout 等待连接超时(秒) 30 10
echo 打印SQL语句 True False

实战项目结构

展示一个标准 FastAPI 数据库项目的目录结构,按职责分层组织代码(数据库、模型、Schema、CRUD、路由、核心工具)。

复制代码
myproject/
├── app/
│   ├── __init__.py
│   ├── main.py              # FastAPI 主应用入口
│   ├── config.py             # 配置管理
│   ├── database/             # 数据库相关
│   │   ├── __init__.py
│   │   ├── connection.py     # 同步连接池配置
│   │   ├── async_connection.py  # 异步连接池配置
│   │   ├── base.py           # 基础模型类
│   │   └── session.py        # Session 依赖注入
│   ├── models/               # SQLAlchemy 模型
│   │   ├── __init__.py
│   │   ├── user.py
│   │   └── order.py
│   ├── schemas/              # Pydantic 模型
│   │   ├── __init__.py
│   │   ├── user.py
│   │   └── order.py
│   ├── crud/                 # 数据库操作
│   │   ├── __init__.py
│   │   ├── user.py
│   │   └── order.py
│   ├── routers/              # 路由
│   │   ├── __init__.py
│   │   ├── user.py
│   │   └── order.py
│   └── core/                 # 核心工具
│       ├── __init__.py
│       ├── transaction.py    # 事务管理器
│       └── exceptions.py     # 自定义异常
├── tests/
├── requirements.txt
└── .env

数据模型定义

定义 SQLAlchemy ORM 基类(含命名约定)和业务模型(User、Order),包含字段映射、索引、外键和关系配置。

python 复制代码
# database/base.py
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy import MetaData

class Base(DeclarativeBase):
    metadata = MetaData(
        naming_convention={
            "ix": "ix_%(column_0_label)s",
            "uq": "uq_%(table_name)s_%(column_0_name)s",
            "ck": "ck_%(table_name)s_%(constraint_name)s",
            "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
            "pk": "pk_%(table_name)s"
        }
    )
python 复制代码
# models/user.py
from sqlalchemy import Column, Integer, String, Boolean, DateTime, Text
from sqlalchemy.orm import relationship
from datetime import datetime
from database.base import Base

class User(Base):
    __tablename__ = "users"

    id = Column(Integer, primary_key=True, autoincrement=True)
    username = Column(String(50), unique=True, nullable=False, index=True, comment="用户名")
    email = Column(String(100), unique=True, nullable=False, index=True, comment="邮箱")
    hashed_password = Column(String(255), nullable=False, comment="密码哈希")
    full_name = Column(String(100), nullable=True, comment="真实姓名")
    is_active = Column(Boolean, default=True, comment="是否激活")
    is_superuser = Column(Boolean, default=False, comment="是否超级用户")
    created_at = Column(DateTime, default=datetime.now, comment="创建时间")
    updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now, comment="更新时间")

    orders = relationship("Order", back_populates="user", cascade="all, delete-orphan")

    def __repr__(self):
        return f"<User(id={self.id}, username='{self.username}')>"
python 复制代码
# models/order.py
from sqlalchemy import Column, Integer, String, Float, DateTime, ForeignKey, Enum as SQLEnum
from sqlalchemy.orm import relationship
from datetime import datetime
import enum
from database.base import Base

class OrderStatus(str, enum.Enum):
    PENDING = "pending"
    PAID = "paid"
    SHIPPED = "shipped"
    COMPLETED = "completed"
    CANCELLED = "cancelled"

class Order(Base):
    __tablename__ = "orders"

    id = Column(Integer, primary_key=True, autoincrement=True)
    order_no = Column(String(32), unique=True, nullable=False, index=True, comment="订单号")
    user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, comment="用户ID")
    total_amount = Column(Float, nullable=False, default=0.0, comment="订单总额")
    status = Column(SQLEnum(OrderStatus), default=OrderStatus.PENDING, comment="订单状态")
    shipping_address = Column(Text, nullable=True, comment="收货地址")
    created_at = Column(DateTime, default=datetime.now, comment="创建时间")
    updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now, comment="更新时间")

    user = relationship("User", back_populates="orders")

    def __repr__(self):
        return f"<Order(id={self.id}, order_no='{self.order_no}', status='{self.status}')>"

Session 依赖注入

将数据库会话封装为 FastAPI 依赖,路由中通过 Depends(get_db) 自动获取和释放 Session。

python 复制代码
# database/session.py
from typing import Generator
from sqlalchemy.orm import Session
from database.connection import SessionLocal

def get_db() -> Generator[Session, None, None]:
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

CRUD 基础操作

定义 Pydantic 请求/响应 Schema,并实现用户表的增删改查基础函数,作为业务逻辑的数据访问层。

python 复制代码
# schemas/user.py
from pydantic import BaseModel, EmailStr, Field, ConfigDict
from datetime import datetime
from typing import Optional
from enum import Enum

class UserStatus(str, Enum):
    ACTIVE = "active"
    INACTIVE = "inactive"

class UserBase(BaseModel):
    username: str = Field(..., min_length=3, max_length=50, description="用户名")
    email: EmailStr = Field(..., description="邮箱地址")
    full_name: Optional[str] = Field(None, max_length=100, description="真实姓名")

class UserCreate(UserBase):
    password: str = Field(..., min_length=6, max_length=100, description="密码")

class UserUpdate(BaseModel):
    username: Optional[str] = Field(None, min_length=3, max_length=50)
    email: Optional[EmailStr] = None
    full_name: Optional[str] = Field(None, max_length=100)
    is_active: Optional[bool] = None

class UserResponse(UserBase):
    id: int
    is_superuser: bool
    created_at: datetime
    updated_at: datetime

    model_config = ConfigDict(from_attributes=True)
python 复制代码
# crud/user.py
from sqlalchemy.orm import Session
from sqlalchemy import select
from typing import Optional, List
from models.user import User
from schemas.user import UserCreate, UserUpdate

def create_user(db: Session, user_data: UserCreate, hashed_password: str) -> User:
    user = User(
        username=user_data.username,
        email=user_data.email,
        full_name=user_data.full_name,
        hashed_password=hashed_password
    )
    db.add(user)
    db.commit()
    db.refresh(user)
    return user

def get_user_by_id(db: Session, user_id: int) -> Optional[User]:
    return db.query(User).filter(User.id == user_id).first()

def get_user_by_username(db: Session, username: str) -> Optional[User]:
    return db.query(User).filter(User.username == username).first()

def get_users(db: Session, skip: int = 0, limit: int = 10) -> List[User]:
    return db.query(User).offset(skip).limit(limit).all()

def update_user(db: Session, user_id: int, user_data: UserUpdate) -> Optional[User]:
    user = get_user_by_id(db, user_id)
    if not user:
        return None
    update_data = user_data.model_dump(exclude_unset=True)
    for field, value in update_data.items():
        setattr(user, field, value)
    db.commit()
    db.refresh(user)
    return user

def delete_user(db: Session, user_id: int) -> bool:
    user = get_user_by_id(db, user_id)
    if not user:
        return False
    db.delete(user)
    db.commit()
    return True

事务管理

概念:事务是一组数据库操作的集合,这些操作要么全部成功(提交),要么全部失败(回滚)。在涉及多表操作或需要保证数据一致性的场景中,事务管理是必不可少的。

事务基础模式

使用 try/commit/rollback 手动管理事务,失败时自动回滚。

python 复制代码
# crud/transaction.py
from sqlalchemy.orm import Session
from models.user import User
from models.order import Order, OrderStatus
from typing import Dict, Any

def create_user_with_initial_order(
    db: Session,
    username: str,
    email: str,
    order_data: Dict[str, Any]
) -> User:
    try:
        user = User(username=username, email=email, hashed_password="hashed_password")
        db.add(user)
        db.flush()

        order = Order(
            order_no=order_data["order_no"],
            user_id=user.id,
            total_amount=order_data["total_amount"],
            status=OrderStatus.PENDING
        )
        db.add(order)
        db.commit()
        db.refresh(user)
        return user
    except Exception:
        db.rollback()
        raise
上下文管理器事务(推荐)

使用 @contextmanager 封装事务的提交与回滚逻辑,with 块正常退出自动提交,异常自动回滚。

python 复制代码
# core/transaction.py
from contextlib import contextmanager
from typing import Generator
from sqlalchemy.orm import Session
import logging

logger = logging.getLogger("app.transaction")

@contextmanager
def transaction_scope(db: Session) -> Generator[Session, None, None]:
    try:
        yield db
        db.commit()
    except Exception as e:
        db.rollback()
        logger.error(f"事务回滚: {str(e)}")
        raise

@contextmanager
def nested_transaction(db: Session):
    savepoint = db.begin_nested()
    try:
        yield savepoint
        savepoint.commit()
    except Exception:
        savepoint.rollback()
        raise
悲观锁 vs 乐观锁

对比两种并发控制策略:悲观锁通过 SELECT ... FOR UPDATE 加行锁;乐观锁通过版本号字段在更新时检测冲突。

python 复制代码
from sqlalchemy.orm import Session
from models.product import Product

class ProductCRUD:

    @staticmethod
    def update_price_pessimistic(db: Session, product_id: int, new_price: float) -> Product:
        product = db.query(Product).filter(Product.id == product_id).with_for_update().first()
        if not product:
            raise Exception("商品不存在")
        product.price = new_price
        db.commit()
        db.refresh(product)
        return product

    @staticmethod
    def update_price_optimistic(db: Session, product_id: int, new_price: float, expected_version: int) -> bool:
        result = db.query(Product).filter(
            Product.id == product_id,
            Product.version == expected_version
        ).update({"price": new_price, "version": Product.version + 1})
        if result == 0:
            return False
        db.commit()
        return True

多数据源操作

概念:多数据源是指在一个应用中连接多个不同的数据库。常见场景包括主从复制(写操作走主库,读操作走从库)。

主从数据源配置

使用 DatabaseManager 管理器分别创建主库(写)和从库(读)的引擎与 Session 工厂。

python 复制代码
# database/multi_source.py
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, Session
from typing import Dict

class DatabaseManager:
    def __init__(self):
        self._engines: Dict[str, any] = {}
        self._sessions: Dict[str, sessionmaker] = {}
        self._init_databases()

    def _init_databases(self):
        self._engines["master"] = create_engine(
            "mysql+pymysql://root:master_password@localhost:3306/app_master?charset=utf8mb4",
            pool_size=10, max_overflow=20, pool_pre_ping=True, pool_recycle=1800
        )
        self._engines["slave"] = create_engine(
            "mysql+pymysql://root:slave_password@localhost:3307/app_slave?charset=utf8mb4",
            pool_size=20, max_overflow=30, pool_pre_ping=True, pool_recycle=1800
        )
        self._sessions["master"] = sessionmaker(autocommit=False, autoflush=False, bind=self._engines["master"])
        self._sessions["slave"] = sessionmaker(autocommit=False, autoflush=False, bind=self._engines["slave"])

    def get_master_session(self) -> Session:
        return self._sessions["master"]()

    def get_slave_session(self) -> Session:
        return self._sessions["slave"]()

    def close_all(self):
        for engine in self._engines.values():
            engine.dispose()
读写分离依赖注入

将读操作路由到从库、写操作路由到主库,通过 FastAPI 依赖注入实现透明的读写分离。

python 复制代码
# database/rw_session.py
from fastapi import Depends, Request
from database.multi_source import DatabaseManager

db_manager = DatabaseManager()

def get_read_db(request: Request):
    db = db_manager.get_slave_session()
    try:
        yield db
    finally:
        db.close()

def get_write_db(request: Request):
    db = db_manager.get_master_session()
    try:
        yield db
    finally:
        db.close()

@app.get("/users")
def list_users(db: Session = Depends(get_read_db)):
    return db.query(User).all()

@app.post("/users")
def create_user(user: UserCreate, db: Session = Depends(get_write_db)):
    new_user = User(**user.model_dump())
    db.add(new_user)
    db.commit()
    db.refresh(new_user)
    return new_user

Redis 集成

概念:Redis 是高性能内存数据库,常用于缓存、会话存储、分布式锁和限流。FastAPI 中通过 redis-py 客户端连接 Redis。

Redis 连接配置

使用 redis-py 客户端连接 Redis 服务,配置连接池大小、超时和重试策略。

python 复制代码
# database/redis_connection.py
import redis

REDIS_URL = "redis://localhost:6379/0"

redis_client = redis.from_url(
    REDIS_URL,
    decode_responses=True,
    max_connections=50,
    socket_timeout=5,
    socket_connect_timeout=5,
    retry_on_timeout=True
)

def get_redis() -> redis.Redis:
    return redis_client
缓存操作

封装 Redis 缓存的设置、获取、删除和按模式批量失效方法。

python 复制代码
# core/cache.py
import json
from typing import Optional, Any
from database.redis_connection import redis_client

def set_cache(key: str, value: Any, expire_seconds: int = 300):
    redis_client.setex(key, expire_seconds, json.dumps(value, ensure_ascii=False))

def get_cache(key: str) -> Optional[Any]:
    data = redis_client.get(key)
    if data:
        return json.loads(data)
    return None

def delete_cache(key: str):
    redis_client.delete(key)

def invalidate_cache(pattern: str):
    for key in redis_client.scan_iter(match=pattern):
        redis_client.delete(key)
分布式锁

基于 Redis 的 SET NX EX 命令实现分布式锁,使用 Lua 脚本保证解锁的原子性。

python 复制代码
# core/distributed_lock.py
import uuid
import time
from contextlib import contextmanager
from database.redis_connection import redis_client

@contextmanager
def distributed_lock(lock_name: str, timeout: int = 10, retry_times: int = 3):
    lock_key = f"lock:{lock_name}"
    lock_value = str(uuid.uuid4())
    acquired = False

    for attempt in range(retry_times):
        acquired = redis_client.set(lock_key, lock_value, nx=True, ex=timeout)
        if acquired:
            break
        time.sleep(0.1 * (attempt + 1))

    if not acquired:
        raise Exception(f"获取锁失败: {lock_name}")

    try:
        yield lock_value
    finally:
        script = """
        if redis.call("get", KEYS[1]) == ARGV[1] then
            return redis.call("del", KEYS[1])
        else
            return 0
        end
        """
        redis_client.eval(script, 1, lock_key, lock_value)
Redis 数据结构常用操作速查

快速查阅 Redis 五种核心数据结构及发布/订阅的常用命令示例。

python 复制代码
# String
redis_client.set("key", "value")
redis_client.get("key")
redis_client.setex("key", 3600, "value")
redis_client.incr("counter")

# Hash
redis_client.hset("user:1", mapping={"name": "Alice", "age": "25"})
redis_client.hget("user:1", "name")
redis_client.hgetall("user:1")

# List
redis_client.lpush("queue", "task1", "task2")
redis_client.rpop("queue")
redis_client.lrange("queue", 0, -1)

# Set
redis_client.sadd("tags", "python", "fastapi")
redis_client.smembers("tags")

# Sorted Set
redis_client.zadd("ranking", {"alice": 100, "bob": 85})
redis_client.zrevrange("ranking", 0, 2, withscores=True)

# 发布/订阅
pubsub = redis_client.pubsub()
pubsub.subscribe("notifications")
redis_client.publish("notification", "hello")

6.1.12 认证与安全

简述:在实际项目中,认证与权限控制是保障系统安全的基础。本章节详细介绍基于 JWT 的用户认证登录,以及基于 RBAC(Role-Based Access Control)的权限管理体系,并补充 OAuth2 标准流程。

概念:认证与授权

概念 说明
认证(Authentication) 验证用户身份,确认"你是谁"
授权(Authorization) 验证用户权限,确认"你能做什么"
JWT JSON Web Token,安全的 token 格式
RBAC Role-Based Access Control,通过角色管理权限

概念:RBAC 权限模型

RBAC 的核心思想是将权限 授予角色 ,再将角色 分配给用户

复制代码
用户 ──属于──> 角色 ──拥有──> 权限
                          │
                       操作资源

安装依赖

bash 复制代码
pip install python-jose[cryptography] passlib[bcrypt] pycryptodome

安全工具模块

密码哈希与 JWT 工具函数。

python 复制代码
# core/security.py
from passlib.context import CryptContext
from jose import jwt, JWTError
from datetime import datetime, timedelta, timezone
from typing import Optional
import logging

SECRET_KEY = "your-secret-key-change-in-production"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 1440

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
logger = logging.getLogger("app.security")

def hash_password(password: str) -> str:
    return pwd_context.hash(password)

def verify_password(plain_password: str, hashed_password: str) -> bool:
    return pwd_context.verify(plain_password, hashed_password)

def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
    to_encode = data.copy()
    expire = datetime.now(timezone.utc) + (expires_delta or timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES))
    to_encode.update({"exp": expire, "iat": datetime.now(timezone.utc)})
    return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)

def decode_access_token(token: str) -> Optional[dict]:
    try:
        return jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
    except JWTError as e:
        logger.warning(f"JWT decode error: {e}")
        return None

认证依赖注入

从请求头提取 Bearer Token,解码 JWT 获取用户对象。

python 复制代码
# core/dependencies.py
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from sqlalchemy.orm import Session
from database.session import get_db
from models.user import User
from core.security import decode_access_token

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login")

async def get_current_user(
    token: str = Depends(oauth2_scheme),
    db: Session = Depends(get_db)
) -> User:
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="认证失败,请重新登录",
        headers={"WWW-Authenticate": "Bearer"},
    )
    payload = decode_access_token(token)
    if payload is None:
        raise credentials_exception
    user_id = payload.get("sub")
    if user_id is None:
        raise credentials_exception
    user = db.query(User).filter(User.id == int(user_id)).first()
    if user is None or not user.is_active:
        raise credentials_exception
    return user

RBAC 权限检查

实现基于权限代码的检查器,支持"全部满足"和"满足其一"两种模式。

python 复制代码
# core/rbac.py
from typing import List
from fastapi import HTTPException, status, Depends
from models.user import User
from core.dependencies import get_current_user

class PermissionChecker:
    def __init__(self, *permission_codes: str, require_all: bool = True):
        self.permission_codes = permission_codes
        self.require_all = require_all

    async def __call__(self, current_user: User = Depends(get_current_user)) -> User:
        if current_user.is_superuser:
            return current_user
        user_permissions = current_user.get_all_permissions()
        if "*" in user_permissions:
            return current_user
        if self.require_all:
            missing = [p for p in self.permission_codes if p not in user_permissions]
            if missing:
                raise HTTPException(status_code=403, detail=f"缺少权限: {', '.join(missing)}")
        else:
            if not any(p in user_permissions for p in self.permission_codes):
                raise HTTPException(status_code=403, detail=f"需要权限之一: {', '.join(self.permission_codes)}")
        return current_user

def require_permissions(*permission_codes: str):
    return PermissionChecker(*permission_codes, require_all=True)

def require_any_permission(*permission_codes: str):
    return PermissionChecker(*permission_codes, require_all=False)

OAuth2 密码流程(完整实现)

使用 FastAPI 内置的 OAuth2PasswordRequestForm 实现标准 OAuth2 密码授权流程,自动在 Swagger UI 中显示授权按钮。

python 复制代码
# routers/auth.py
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm
from sqlalchemy.orm import Session
from database.session import get_db
from models.user import User
from schemas.auth import TokenResponse
from core.security import verify_password, create_access_token, decode_access_token, hash_password

router = APIRouter(prefix="/auth", tags=["认证"])

@router.post("/login", response_model=TokenResponse)
async def login(
    form_data: OAuth2PasswordRequestForm = Depends(),
    db: Session = Depends(get_db)
):
    user = db.query(User).filter(User.username == form_data.username).first()
    if not user or not verify_password(form_data.password, user.hashed_password):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="用户名或密码错误",
            headers={"WWW-Authenticate": "Bearer"},
        )
    if not user.is_active:
        raise HTTPException(status_code=403, detail="用户已被禁用")

    access_token = create_access_token(
        data={"sub": str(user.id), "username": user.username, "type": "access"}
    )
    return TokenResponse(
        access_token=access_token,
        token_type="bearer",
        expires_in=86400,
        user_id=user.id,
        username=user.username
    )

@router.post("/register")
async def register(
    username: str = Form(...),
    password: str = Form(..., min_length=6),
    email: str = Form(...),
    db: Session = Depends(get_db)
):
    existing = db.query(User).filter((User.username == username) | (User.email == email)).first()
    if existing:
        raise HTTPException(status_code=400, detail="用户名或邮箱已存在")
    user = User(username=username, email=email, hashed_password=hash_password(password))
    db.add(user)
    db.commit()
    db.refresh(user)
    return {"id": user.id, "username": user.username}

提示 :使用 OAuth2PasswordRequestForm 后,Swagger UI(/docs)会自动显示 "Authorize" 按钮,用户可以直接在文档中测试需要认证的接口。form_data 包含 usernamepasswordscopegrant_type 等标准 OAuth2 字段。

API Key 认证

除 JWT 外,通过请求头 X-API-Key 实现简单的 API Key 认证方式,适用于服务间调用或第三方接入。

python 复制代码
from fastapi import APIKeyHeader, Security, HTTPException, status

api_key_header = APIKeyHeader(name="X-API-Key")

async def verify_api_key(api_key: str = Security(api_key_header)):
    if api_key != "my-secret-api-key":
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="无效的 API Key")
    return api_key

@app.get("/api-key-protected")
async def api_key_protected(api_key: str = Depends(verify_api_key)):
    return {"message": "API Key 验证通过"}

6.1.13 文件上传与下载

简述 :FastAPI 通过 UploadFileStreamingResponse 提供方便的文件处理功能,支持单文件/多文件上传、文件类型验证、大文件流式下载等场景。

文件上传

使用 UploadFile 实现单文件上传、多文件上传和带类型/大小验证的上传功能。

python 复制代码
from fastapi import FastAPI, File, UploadFile, HTTPException
from typing import List
import shutil
import os

app = FastAPI()

@app.post("/upload")
async def upload_file(file: UploadFile = File(...)):
    file_path = f"uploads/{file.filename}"
    os.makedirs("uploads", exist_ok=True)
    with open(file_path, "wb") as buffer:
        shutil.copyfileobj(file.file, buffer)
    return {"filename": file.filename, "content_type": file.content_type}

@app.post("/upload-multiple")
async def upload_multiple_files(files: List[UploadFile] = File(...)):
    uploaded = []
    for file in files:
        file_path = f"uploads/{file.filename}"
        with open(file_path, "wb") as buffer:
            shutil.copyfileobj(file.file, buffer)
        uploaded.append({"filename": file.filename})
    return {"uploaded": uploaded}

@app.post("/upload-with-validation")
async def upload_with_validation(file: UploadFile = File(...), description: str = ""):
    allowed_types = ["image/jpeg", "image/png", "image/gif"]
    if file.content_type not in allowed_types:
        raise HTTPException(status_code=400, detail=f"不支持的文件类型: {file.content_type}")
    contents = await file.read()
    if len(contents) > 5 * 1024 * 1024:
        raise HTTPException(status_code=400, detail="文件大小超过5MB")
    file_path = f"uploads/{file.filename}"
    with open(file_path, "wb") as buffer:
        buffer.write(contents)
    return {"filename": file.filename, "size": len(contents), "description": description}

文件下载

使用 FileResponse 返回静态文件、StreamingResponse 实现大文件流式下载和动态内容生成下载。

python 复制代码
from fastapi.responses import StreamingResponse, FileResponse
import io

@app.get("/download/{filename}")
async def download_file(filename: str):
    file_path = f"uploads/{filename}"
    if not os.path.exists(file_path):
        raise HTTPException(status_code=404, detail="文件不存在")
    return FileResponse(file_path)

@app.get("/download-stream/{filename}")
async def download_stream(filename: str):
    def iterfile():
        with open(f"uploads/{filename}", "rb") as f:
            while chunk := f.read(8192):
                yield chunk
    return StreamingResponse(
        iterfile(),
        media_type="application/octet-stream",
        headers={"Content-Disposition": f"attachment; filename={filename}"}
    )

@app.get("/download-csv")
async def download_csv():
    csv_content = "id,name,email\n1,Alice,alice@example.com\n2,Bob,bob@example.com"
    return StreamingResponse(
        io.BytesIO(csv_content.encode()),
        media_type="text/csv",
        headers={"Content-Disposition": "attachment; filename=data.csv"}
    )

6.1.14 WebSocket

简述:WebSocket 是一种全双工通信协议,允许服务器主动向客户端推送数据。FastAPI 内置 WebSocket 支持,适合实时聊天、通知推送、数据监控等场景。

WebSocket 基础

使用 FastAPI 内置 WebSocket 支持实现全双工通信,包含连接管理器(连接/断开/广播)和测试页面。

python 复制代码
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from typing import List, Dict

app = FastAPI()

class ConnectionManager:
    def __init__(self):
        self.active_connections: List[WebSocket] = []

    async def connect(self, websocket: WebSocket):
        await websocket.accept()
        self.active_connections.append(websocket)

    def disconnect(self, websocket: WebSocket):
        self.active_connections.remove(websocket)

    async def send_personal_message(self, message: str, websocket: WebSocket):
        await websocket.send_text(message)

    async def broadcast(self, message: str):
        for connection in self.active_connections:
            await connection.send_text(message)

manager = ConnectionManager()

@app.websocket("/ws/{client_id}")
async def websocket_endpoint(websocket: WebSocket, client_id: int):
    await manager.connect(websocket)
    try:
        while True:
            data = await websocket.receive_text()
            await manager.send_personal_message(f"你发送了: {data}", websocket)
            await manager.broadcast(f"客户端 {client_id} 说: {data}")
    except WebSocketDisconnect:
        manager.disconnect(websocket)
        await manager.broadcast(f"客户端 {client_id} 离开了")

WebSocket 认证

在 WebSocket 连接时验证用户身份,支持查询参数 Token 和子协议两种认证方式。

python 复制代码
from fastapi import WebSocket, Query, status

async def verify_ws_token(token: str) -> Optional[dict]:
    payload = decode_access_token(token)
    if not payload:
        return None
    return payload

@app.websocket("/ws/auth/{client_id}")
async def websocket_with_auth(
    websocket: WebSocket,
    client_id: int,
    token: Optional[str] = Query(None)
):
    if not token:
        await websocket.close(code=status.WS_1008_POLICY_VIOLATION)
        return

    payload = await verify_ws_token(token)
    if not payload:
        await websocket.close(code=status.WS_1008_POLICY_VIOLATION)
        return

    await websocket.accept()
    try:
        while True:
            data = await websocket.receive_text()
            await websocket.send_text(f"认证用户 {payload.get('username')} 消息: {data}")
    except WebSocketDisconnect:
        pass

房间管理与心跳机制

实现基于房间的消息推送和定时心跳检测,构建生产级 WebSocket 应用。

python 复制代码
import asyncio
from typing import Dict, Set
from datetime import datetime

class RoomManager:
    def __init__(self):
        self.rooms: Dict[str, Set[WebSocket]] = {}
        self.heartbeat_interval = 30

    async def join_room(self, room_id: str, websocket: WebSocket):
        if room_id not in self.rooms:
            self.rooms[room_id] = set()
        self.rooms[room_id].add(websocket)

    def leave_room(self, room_id: str, websocket: WebSocket):
        if room_id in self.rooms:
            self.rooms[room_id].discard(websocket)
            if not self.rooms[room_id]:
                del self.rooms[room_id]

    async def broadcast_to_room(self, room_id: str, message: str):
        if room_id in self.rooms:
            for ws in self.rooms[room_id]:
                try:
                    await ws.send_text(message)
                except Exception:
                    self.leave_room(room_id, ws)

room_manager = RoomManager()

@app.websocket("/ws/room/{room_id}/{user_id}")
async def websocket_room(websocket: WebSocket, room_id: str, user_id: int):
    await websocket.accept()
    await room_manager.join_room(room_id, websocket)

    try:
        while True:
            data = await websocket.receive_text()
            await room_manager.broadcast_to_room(
                room_id,
                f"[{user_id}] {data}"
            )
    except WebSocketDisconnect:
        room_manager.leave_room(room_id, websocket)
        await room_manager.broadcast_to_room(
            room_id,
            f"用户 {user_id} 离开了房间"
        )

6.1.15 SSE(Server-Sent Events)服务端推送

简述:SSE 是一种基于 HTTP 的服务器推送技术,服务器可以单向向客户端推送数据。与 WebSocket 的全双工通信不同,SSE 是单向的(服务器→客户端),但更简单、基于标准 HTTP、天然支持断线重连。适用于 AI 流式输出、实时通知、日志推送等场景。

概念:SSE vs WebSocket

对比项 SSE WebSocket
通信方向 单向(服务器→客户端) 双向
协议 标准 HTTP 独立协议(ws://)
断线重连 浏览器自动重连 需手动实现
数据格式 纯文本 文本/二进制
浏览器支持 所有现代浏览器 所有现代浏览器
适用场景 AI 流式输出、通知推送 聊天、实时协作

基础 SSE 实现

使用 StreamingResponse 配合 text/event-stream Content-Type 实现 SSE 推送。

python 复制代码
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import asyncio
import json
from datetime import datetime

app = FastAPI()

async def event_generator():
    count = 0
    while True:
        count += 1
        data = json.dumps({
            "count": count,
            "timestamp": datetime.now().isoformat(),
            "message": f"第 {count} 条消息"
        }, ensure_ascii=False)
        yield f"data: {data}\n\n"
        await asyncio.sleep(1)

@app.get("/sse/events")
async def sse_events():
    return StreamingResponse(
        event_generator(),
        media_type="text/event-stream",
        headers={
            "Cache-Control": "no-cache",
            "Connection": "keep-alive",
            "X-Accel-Buffering": "no"
        }
    )

前端接收

javascript 复制代码
const eventSource = new EventSource("/sse/events");
eventSource.onmessage = (event) => {
    const data = JSON.parse(event.data);
    console.log(data.message);
};
eventSource.onerror = () => {
    console.log("连接断开,自动重连...");
};

AI 流式输出(ChatGPT 风格)

模拟 AI 模型的流式文本输出,逐字/逐句推送,实现打字机效果。

python 复制代码
@app.get("/sse/chat")
async def sse_chat(prompt: str = "你好"):
    async def chat_stream():
        response_text = f"收到你的问题:{prompt}。这是一个模拟的AI流式回复,逐字输出展示打字机效果。"
        for char in response_text:
            data = json.dumps({"content": char}, ensure_ascii=False)
            yield f"data: {data}\n\n"
            await asyncio.sleep(0.05)
        yield f"data: [DONE]\n\n"

    return StreamingResponse(
        chat_stream(),
        media_type="text/event-stream",
        headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"}
    )

SSE 事件类型与 ID

使用 event 字段指定事件类型、id 字段设置事件 ID,客户端可按类型监听和从断点续传。

python 复制代码
@app.get("/sse/notifications")
async def sse_notifications():
    async def notification_stream():
        notifications = [
            {"event": "order", "id": "1", "data": {"order_id": 1001, "status": "已发货"}},
            {"event": "system", "id": "2", "data": {"message": "系统维护通知"}},
            {"event": "order", "id": "3", "data": {"order_id": 1002, "status": "已完成"}},
        ]
        for notif in notifications:
            yield f"event: {notif['event']}\n"
            yield f"id: {notif['id']}\n"
            yield f"data: {json.dumps(notif['data'], ensure_ascii=False)}\n\n"
            await asyncio.sleep(2)

    return StreamingResponse(
        notification_stream(),
        media_type="text/event-stream",
        headers={"Cache-Control": "no-cache"}
    )

前端按类型监听

javascript 复制代码
const es = new EventSource("/sse/notifications");
es.addEventListener("order", (event) => {
    console.log("订单通知:", JSON.parse(event.data));
});
es.addEventListener("system", (event) => {
    console.log("系统通知:", JSON.parse(event.data));
});

6.1.16 限流与防刷

简述:限流是保护 API 不被恶意请求压垮的重要手段。本节介绍使用 slowapi 实现接口级限流,以及基于 Redis 的分布式限流方案。

概念:限流策略

策略 说明 适用场景
固定窗口 固定时间窗口内限制请求数 简单场景
滑动窗口 平滑的时间窗口,更精确 通用场景
令牌桶 匀速生成令牌,允许突发 API 网关
漏桶 匀速处理请求 流量整形

slowapi 集成(推荐)

slowapi 是 FastAPI 最常用的限流库,基于 limits 库实现,支持多种后端存储(内存/Redis)。

bash 复制代码
pip install slowapi
python 复制代码
# main.py
from fastapi import FastAPI, Request
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded
from slowapi.middleware import SlowAPIMiddleware

limiter = Limiter(key_func=get_remote_address)
app = FastAPI()
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
app.add_middleware(SlowAPIMiddleware)

@app.get("/unlimited")
async def unlimited():
    return {"message": "不限流"}

@app.get("/limited")
@limiter.limit("10/minute")
async def limited(request: Request):
    return {"message": "限流接口,每分钟10次"}

@app.post("/login")
@limiter.limit("5/minute")
async def login(request: Request):
    return {"message": "登录限流,每分钟5次"}

@app.get("/api/data")
@limiter.limit("100/hour")
async def api_data(request: Request):
    return {"message": "数据接口,每小时100次"}

多级限流策略

对同一接口设置多个限流规则,如同时限制每秒和每分钟的请求量。

python 复制代码
@app.get("/multi-limit")
@limiter.limit("5/second")
@limiter.limit("100/minute")
@limiter.limit("1000/hour")
async def multi_limit(request: Request):
    return {"message": "多级限流"}

基于 Redis 的分布式限流

使用 Redis 实现滑动窗口限流,支持分布式部署场景,多个 worker 共享限流状态。

python 复制代码
# core/rate_limiter.py
from fastapi import Request, HTTPException
from database.redis_connection import redis_client

def sliding_window_limit(
    request: Request,
    max_requests: int = 100,
    window_seconds: int = 60,
    key_prefix: str = "ratelimit"
):
    client_ip = request.client.host if request.client else "unknown"
    key = f"{key_prefix}:{client_ip}"

    current = redis_client.incr(key)
    if current == 1:
        redis_client.expire(key, window_seconds)

    if current > max_requests:
        raise HTTPException(
            status_code=429,
            detail="请求过于频繁,请稍后再试",
            headers={
                "X-RateLimit-Limit": str(max_requests),
                "X-RateLimit-Remaining": str(max(0, max_requests - current)),
                "Retry-After": str(redis_client.ttl(key))
            }
        )
    return True

6.1.17 分页与排序

简述:分页和排序是列表查询接口的基本需求。本节封装通用的分页参数和响应模型,支持灵活的排序字段指定,可直接在项目中复用。

分页参数模型

定义 PaginationParams(页码/每页数量)和泛型 PaginatedResponse(含总数/总页数),封装通用的分页请求与响应结构。

python 复制代码
# schemas/pagination.py
from pydantic import BaseModel, Field
from typing import TypeVar, Generic, List

class PaginationParams(BaseModel):
    page: int = Field(1, ge=1, description="页码,从1开始")
    page_size: int = Field(10, ge=1, le=100, description="每页数量")

    @property
    def offset(self) -> int:
        return (self.page - 1) * self.page_size

    @property
    def limit(self) -> int:
        return self.page_size

T = TypeVar("T")

class PaginatedResponse(BaseModel, Generic[T]):
    items: List[T]
    total: int
    page: int
    page_size: int
    total_pages: int

    @classmethod
    def create(cls, items: List[T], total: int, params: PaginationParams):
        total_pages = (total + params.page_size - 1) // params.page_size
        return cls(items=items, total=total, page=params.page, page_size=params.page_size, total_pages=total_pages)

分页查询示例

在路由中组合使用分页参数和排序参数,配合白名单校验排序字段。

python 复制代码
# routers/users.py
from fastapi import APIRouter, Depends, Query
from sqlalchemy.orm import Session
from sqlalchemy import select, func
from database.session import get_db
from models.user import User
from schemas.user import UserResponse
from schemas.pagination import PaginationParams, PaginatedResponse

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

ALLOWED_SORT_FIELDS = {"id", "username", "created_at"}

@router.get("/", response_model=PaginatedResponse[UserResponse])
async def list_users(
    pagination: PaginationParams = Depends(),
    sort_by: Optional[str] = Query(None),
    sort_order: Optional[str] = Query("asc"),
    db: Session = Depends(get_db)
):
    query = select(User)
    if sort_by and sort_by in ALLOWED_SORT_FIELDS:
        sort_column = getattr(User, sort_by)
        if sort_order == "desc":
            sort_column = sort_column.desc()
        query = query.order_by(sort_column)
    total = db.scalar(select(func.count()).select_from(query.subquery()))
    items = db.scalars(query.offset(pagination.offset).limit(pagination.limit)).all()
    return PaginatedResponse.create(items=items, total=total, params=pagination)

6.1.18 Alembic 数据库迁移

简述:Alembic 是 SQLAlchemy 的数据库迁移工具,用于管理数据库 Schema 的版本变更(建表、加字段、改索引等)。通过自动生成迁移脚本和版本管理,实现数据库结构的可控演进,是生产环境必备工具。

概念:为什么需要数据库迁移

在开发过程中,数据库表结构会不断变化(新增字段、修改索引等)。直接手动修改数据库存在以下问题:

  • 多人协作时无法同步变更
  • 生产环境与开发环境结构不一致
  • 无法回滚到之前的版本
  • 缺乏变更历史记录

Alembic 通过迁移脚本记录每次 Schema 变更,支持自动生成、版本管理和回滚。

安装与初始化

bash 复制代码
pip install alembic

cd myproject
alembic init alembic

项目结构

复制代码
myproject/
├── alembic/
│   ├── versions/        # 迁移脚本目录
│   ├── env.py           # Alembic 环境配置
│   └── script.py.mako   # 迁移脚本模板
├── alembic.ini          # Alembic 配置文件
├── app/
│   ├── models/
│   │   ├── user.py
│   │   └── order.py
│   └── database/
│       └── base.py

配置 Alembic

修改 alembic/env.pyalembic.ini,关联 SQLAlchemy 模型和数据库连接。

ini 复制代码
# alembic.ini - 修改数据库连接
sqlalchemy.url = mysql+pymysql://root:password@localhost:3306/mydb?charset=utf8mb4
python 复制代码
# alembic/env.py - 关键修改
from app.database.base import Base
from app.models import user, order  # 导入所有模型
target_metadata = Base.metadata

# 将 target_metadata 替换原来的值
# target_metadata = None  # 删除这行

常用命令

bash 复制代码
# 自动生成迁移脚本(对比模型与数据库差异)
alembic revision --autogenerate -m "add user table"

# 执行迁移(升级到最新版本)
alembic upgrade head

# 回滚一个版本
alembic downgrade -1

# 回滚到指定版本
alembic downgrade <revision_id>

# 查看当前版本
alembic current

# 查看迁移历史
alembic history

# 查看待执行的迁移
alembic show head

# 重置到初始状态
alembic downgrade base

# 生成空迁移脚本(手动编写SQL)
alembic revision -m "custom migration"

迁移脚本示例

Alembic 自动生成的迁移脚本包含 upgrade()downgrade() 两个方法。

python 复制代码
# alembic/versions/001_add_user_table.py
"""add user table

Revision ID: 001
Revises:
Create Date: 2024-01-01 00:00:00.000000
"""
from alembic import op
import sqlalchemy as sa

revision = '001'
down_revision = None
branch_labels = None
depends_on = None

def upgrade() -> None:
    op.create_table(
        'users',
        sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
        sa.Column('username', sa.String(50), nullable=False),
        sa.Column('email', sa.String(100), nullable=False),
        sa.Column('hashed_password', sa.String(255), nullable=False),
        sa.Column('is_active', sa.Boolean(), server_default='1'),
        sa.Column('created_at', sa.DateTime(), server_default=sa.func.now()),
        sa.PrimaryKeyConstraint('id'),
        sa.UniqueConstraint('username'),
        sa.UniqueConstraint('email'),
    )
    op.create_index('ix_users_username', 'users', ['username'])
    op.create_index('ix_users_email', 'users', ['email'])

def downgrade() -> None:
    op.drop_index('ix_users_email', table_name='users')
    op.drop_index('ix_users_username', table_name='users')
    op.drop_table('users')

手动迁移:添加字段

当自动生成无法满足需求时,手动编写迁移脚本。

python 复制代码
# alembic/versions/002_add_user_avatar.py
"""add user avatar column

Revision ID: 002
Revises: 001
"""
from alembic import op
import sqlalchemy as sa

revision = '002'
down_revision = '001'

def upgrade() -> None:
    op.add_column('users', sa.Column('avatar_url', sa.String(255), nullable=True, comment='头像URL'))
    op.add_column('users', sa.Column('bio', sa.Text(), nullable=True, comment='个人简介'))

def downgrade() -> None:
    op.drop_column('users', 'bio')
    op.drop_column('users', 'avatar_url')

多环境迁移管理

在开发、测试、生产环境中安全地执行迁移。

bash 复制代码
# 开发环境
alembic upgrade head

# 生产环境(先查看待执行的SQL,不实际执行)
alembic upgrade head --sql

# 生产环境(执行前先备份)
alembic upgrade head

# 指定配置文件(不同环境使用不同配置)
alembic -c alembic_production.ini upgrade head

提示 :生产环境执行迁移前,务必先使用 --sql 参数查看将要执行的 SQL 语句,确认无误后再执行。同时建议在执行前备份数据库。


6.1.19 子应用挂载与自定义路由类

简述app.mount() 可以将独立的 ASGI 应用挂载到主应用的指定路径下,实现模块隔离;自定义 APIRoute 类可以在路由层统一添加逻辑(如日志、认证、请求计时),减少重复代码。

子应用挂载(Mount)

使用 app.mount() 挂载子应用,子应用有独立的中间件和路由,与主应用隔离。

python 复制代码
from fastapi import FastAPI

app = FastAPI(title="主应用")

# 子应用1:管理后台
admin_app = FastAPI(title="管理后台")

@admin_app.get("/dashboard")
async def admin_dashboard():
    return {"app": "admin", "page": "dashboard"}

@admin_app.get("/users")
async def admin_users():
    return {"app": "admin", "users": []}

# 子应用2:API服务
api_app = FastAPI(title="API服务")

@api_app.get("/items")
async def api_items():
    return {"app": "api", "items": []}

# 挂载子应用
app.mount("/admin", admin_app)
app.mount("/api", api_app)

# 访问路径:
# /admin/dashboard  → admin_app
# /admin/users      → admin_app
# /api/items        → api_app

提示mount() 挂载的子应用是独立的 ASGI 应用,无法共享主应用的依赖注入和中间件。如果需要共享依赖,应使用 include_router() 而非 mount()

自定义路由类(APIRoute)

继承 APIRoute 实现通用逻辑,所有使用该路由类的路由自动应用,无需在每个路由中重复编写。

python 复制代码
import time
import logging
from fastapi import FastAPI, Request, Response
from fastapi.routing import APIRoute
from typing import Callable

logger = logging.getLogger("app")

class TimedRoute(APIRoute):
    """自动记录请求耗时的路由类"""

    def get_route_handler(self) -> Callable:
        original_route_handler = super().get_route_handler()

        async def custom_route_handler(request: Request) -> Response:
            start = time.time()
            response = await original_route_handler(request)
            duration = time.time() - start
            response.headers["X-Process-Time"] = f"{duration:.4f}"
            logger.info(f"{request.method} {request.url.path} - {duration:.4f}s")
            return response

        return custom_route_handler

class AuthenticatedRoute(APIRoute):
    """需要认证的路由类"""

    def get_route_handler(self) -> Callable:
        original_route_handler = super().get_route_handler()

        async def custom_route_handler(request: Request) -> Response:
            token = request.headers.get("Authorization")
            if not token:
                return JSONResponse(
                    status_code=401,
                    content={"detail": "未提供认证信息"}
                )
            return await original_route_handler(request)

        return custom_route_handler

# 使用自定义路由类
app = FastAPI()

# 方式1:在 APIRouter 上使用
from fastapi import APIRouter
router = APIRouter(route_class=TimedRoute)

@router.get("/timed")
async def timed_endpoint():
    return {"message": "此接口自动记录耗时"}

# 方式2:在整个应用上使用
app = FastAPI(route_class=TimedRoute)

# 方式3:特定路由使用
@router.get("/protected", route_class=AuthenticatedRoute)
async def protected_endpoint():
    return {"message": "需要认证"}

6.1.20 API 版本控制

简述:API 版本控制是管理接口变更的策略,确保旧版本客户端在 API 升级时仍可正常使用。FastAPI 没有内置版本控制,但可以通过 URL 前缀、请求头等方式灵活实现。

URL 前缀版本(推荐)

通过不同的 URL 前缀区分 API 版本(如 /api/v1//api/v2/),最直观、最常用的方式。

python 复制代码
from fastapi import FastAPI, APIRouter

app = FastAPI()

# V1 路由
v1_router = APIRouter(prefix="/users")

@v1_router.get("/")
async def v1_list_users():
    return {"version": "v1", "users": [{"id": 1, "name": "Alice"}]}

@v1_router.get("/{user_id}")
async def v1_get_user(user_id: int):
    return {"version": "v1", "id": user_id, "name": "Alice"}

# V2 路由(返回更多字段)
v2_router = APIRouter(prefix="/users")

@v2_router.get("/")
async def v2_list_users():
    return {"version": "v2", "users": [{"id": 1, "name": "Alice", "email": "alice@example.com", "role": "admin"}]}

@v2_router.get("/{user_id}")
async def v2_get_user(user_id: int):
    return {"version": "v2", "id": user_id, "name": "Alice", "email": "alice@example.com", "role": "admin"}

# 注册路由
app.include_router(v1_router, prefix="/api/v1", tags=["V1 - 用户"])
app.include_router(v2_router, prefix="/api/v2", tags=["V2 - 用户"])

# 访问路径:
# GET /api/v1/users/
# GET /api/v2/users/

项目结构组织

按版本号组织路由文件,便于维护多版本 API。

复制代码
app/
├── routers/
│   ├── __init__.py
│   ├── v1/
│   │   ├── __init__.py
│   │   ├── users.py
│   │   └── orders.py
│   └── v2/
│       ├── __init__.py
│       ├── users.py
│       └── orders.py
├── schemas/
│   ├── v1/
│   │   └── user.py
│   └── v2/
│       └── user.py
└── main.py
python 复制代码
# main.py
from fastapi import FastAPI
from routers.v1 import users as v1_users, orders as v1_orders
from routers.v2 import users as v2_users

app = FastAPI()

app.include_router(v1_users.router, prefix="/api/v1", tags=["V1"])
app.include_router(v1_orders.router, prefix="/api/v1", tags=["V1"])
app.include_router(v2_users.router, prefix="/api/v2", tags=["V2"])

请求头版本控制

通过自定义请求头(如 X-API-Version)区分版本,URL 保持不变。

python 复制代码
from fastapi import FastAPI, Request, Header
from typing import Optional

app = FastAPI()

@app.get("/users")
async def get_users(api_version: Optional[str] = Header("v1", alias="X-API-Version")):
    if api_version == "v2":
        return {"version": "v2", "users": [{"id": 1, "name": "Alice", "email": "a@b.com"}]}
    return {"version": "v1", "users": [{"id": 1, "name": "Alice"}]}

版本弃用标记

使用 deprecated=True 标记即将废弃的 API 版本,在 Swagger UI 中显示删除线。

python 复制代码
@v1_router.get("/", deprecated=True)
async def v1_list_users():
    return {"version": "v1", "deprecated": True, "message": "请使用 /api/v2/users/"}

📖 前置知识第6步-1-1-FastAPI框架-基础部分路由、Pydantic、依赖注入、中间件、异常处理
📖 下一步:[FastAPI框架-部署与实战) 部署、测试、日志监控、健康检查、Celery、最佳实践

相关推荐
!沧海@一粟!2 小时前
Linux高并发内核优化
linux·运维·oracle
kaico20182 小时前
FastAPI 框架 - 部署与实战
fastapi
llilay3 小时前
企业级FastAPI后端模板搭建(二)整合路由Router
开发语言·python·fastapi
我叫张小白。3 小时前
Redis的缓存雪崩、击穿、穿透和解决方案
数据结构·redis·fastapi·缓存穿透·缓存击穿·雪崩·热点key问题
念恒123063 小时前
MySQL事务(下)---MySQL InnoDB MVCC 与 Read View:从隐藏列、Undo Log 到 RR 与 RC 的本质区别
数据库·mysql·oracle
anew___4 小时前
《数据库原理》精要解读(五)—— 数据库完整性:守护数据的真实与逻辑
数据库·oracle
TheRouter14 小时前
AI Agent 记忆体系建设实战:短期、长期与工作记忆的工程实现
数据库·人工智能·oracle
JAVA面经实录91715 小时前
Hibernate面试题库
数据库·oracle·hibernate
jnrjian19 小时前
CDB 中某个PDB的datafile 丢失 没有备份过也可恢复 需要来回切换CDB PDB
oracle