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 包含 username、password、scope、grant_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 通过 UploadFile 和 StreamingResponse 提供方便的文件处理功能,支持单文件/多文件上传、文件类型验证、大文件流式下载等场景。

文件上传

使用 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.py 和 alembic.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、最佳实践

相关推荐
杨云龙UP19 小时前
Oracle 19c 单机到单机 Active Data Guard 搭建与巡检操作手册
数据库·oracle·adg·操作指南·data guard·dg搭建
我科绝伦(Huanhuan Zhou)1 天前
oracle坏块导致备份不了
数据库·oracle·ffmpeg
杨云龙UP1 天前
Oracle 19c RAC到RAC Active Data Guard标准搭建与巡检指南
运维·服务器·数据库·oracle·adg·data guard·rac到rac
许彰午1 天前
06-Oracle卸载与重装完整流程
数据库·oracle
我的xiaodoujiao1 天前
Django 基础知识详细图文教程 13-Django 模型定义与使用 3
数据库·后端·python·测试工具·oracle·django
我科绝伦(Huanhuan Zhou)1 天前
oracle RAC共享存储换盘操作指南
数据库·oracle
wangbing11251 天前
重建oracle服务
数据库·oracle
shaibdoio1 天前
瞬维AI落地经验:AI Agent工具调用准确率怎么提
数据库·人工智能·oracle
jnrjian2 天前
Oracle Inlist 和exists的最佳执行计划
oracle
爱写代码的倒霉蛋2 天前
FastAPI的接口实现流程讲解
fastapi