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-streamContent-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、最佳实践