深入理解 FastAPI:Python高性能API框架的完整指南

深入理解 FastAPI

现代Python高性能API框架的完整指南

目录

  • [1. FastAPI概述与核心特性](#1. FastAPI概述与核心特性)
  • [2. 异步编程原理](#2. 异步编程原理)
  • [3. Pydantic数据验证](#3. Pydantic数据验证)
  • [4. 依赖注入系统](#4. 依赖注入系统)
  • [5. 中间件与生命周期](#5. 中间件与生命周期)
  • [6. 认证与安全](#6. 认证与安全)
  • [7. 数据库集成](#7. 数据库集成)
  • [8. 后台任务与WebSocket](#8. 后台任务与WebSocket)
  • [9. 测试策略](#9. 测试策略)
  • [10. 生产部署与性能优化](#10. 生产部署与性能优化)

1. FastAPI概述与核心特性

1.1 什么是FastAPI

FastAPI是一个现代、高性能的Python Web框架,专门用于构建API。它诞生于2018年,由Sebastián Ramírez创建,目标是解决Python Web开发中长期存在的几个痛点:

传统框架的问题

  • Flask:简单灵活,但缺乏数据验证、类型提示支持,需要大量第三方库
  • Django REST Framework:功能强大但过于重量级,学习曲线陡峭
  • 性能瓶颈:传统同步框架在高并发场景下表现不佳

FastAPI的解决方案

FastAPI站在巨人的肩膀上,它不是从零开始,而是巧妙地组合了两个优秀的库:

  1. Starlette:提供Web框架的核心能力(路由、中间件、WebSocket等)
  2. Pydantic:提供数据验证和序列化能力

这种设计哲学意味着FastAPI本身的代码量很小,但功能极其强大。当你使用FastAPI时,实际上是在使用这两个经过生产验证的成熟库。

核心优势

特性 说明 对比传统框架
极高性能 与NodeJS、Go相当 比Flask快10-100倍
开发效率 开发速度提升200-300% 自动文档、自动验证
减少Bug 类型提示减少约40%的人为错误 编译时发现问题
标准化 基于OpenAPI和JSON Schema 无需手写API文档
学习曲线 只需了解Python类型注解 无需学习DSL

1.2 安装与环境配置

FastAPI提供了多种安装方式,根据你的需求选择:

bash 复制代码
# 方式1:标准安装(推荐,包含所有常用依赖)
# 包含:uvicorn、httpx、jinja2、python-multipart等
pip install "fastapi[standard]"

# 方式2:最小安装(只有核心功能)
# 适合:对依赖有严格控制的环境
pip install fastapi

# 方式3:单独安装ASGI服务器
# 如果你选择了最小安装,需要单独安装服务器
pip install uvicorn[standard]

为什么需要ASGI服务器?

FastAPI本身只是一个框架,它定义了如何处理请求,但不包含网络服务器功能。ASGI(Asynchronous Server Gateway Interface)是Python异步Web应用的标准接口,Uvicorn是最流行的ASGI服务器实现。

这种分离设计的好处是:

  • 可以选择不同的ASGI服务器(Uvicorn、Hypercorn、Daphne)
  • 框架专注于业务逻辑,服务器专注于网络处理
  • 方便在不同环境(开发/生产)使用不同配置

版本要求与兼容性

  • Python 3.8+(推荐3.10+,可以使用 | 语法替代 Union
  • FastAPI 0.100+(2024-2025年推荐0.115+)

1.3 第一个FastAPI应用

让我们从一个完整但简单的例子开始,逐步理解FastAPI的核心概念:

python 复制代码
# main.py
from fastapi import FastAPI

# 创建FastAPI应用实例
# 这个实例是整个应用的入口点,所有路由、中间件都注册在这里
app = FastAPI(
    title="My API",              # API标题,显示在文档中
    description="这是一个示例API",  # API描述
    version="1.0.0",             # API版本
    docs_url="/docs",            # Swagger文档路径(默认)
    redoc_url="/redoc"           # ReDoc文档路径(默认)
)

# 最简单的路由:GET请求,返回JSON
@app.get("/")
async def root():
    """
    根路径端点

    这个函数会在访问 / 时被调用
    返回的字典会自动序列化为JSON
    """
    return {"message": "Hello World"}

# 带路径参数的路由
@app.get("/items/{item_id}")
async def read_item(item_id: int, q: str | None = None):
    """
    获取单个物品

    Args:
        item_id: 路径参数,自动转换为int类型
        q: 查询参数,可选,默认为None

    FastAPI会自动:
    1. 验证item_id是否为有效整数
    2. 如果验证失败,返回清晰的错误信息
    3. 将参数传递给函数
    """
    return {"item_id": item_id, "q": q}

运行应用

bash 复制代码
# 开发模式:启用自动重载,代码修改后自动重启
uvicorn main:app --reload

# 这个命令的含义:
# main - Python模块名(main.py)
# app - FastAPI实例的变量名
# --reload - 监视文件变化,自动重启

理解路由装饰器

python 复制代码
@app.get("/items/{item_id}")

这一行代码做了很多事情:

  1. @app.get - 注册一个处理GET请求的路由
  2. "/items/{item_id}" - URL路径模板,{item_id} 是路径参数
  3. 装饰器将函数与路径绑定,FastAPI自动处理请求分发

自动生成的文档

启动应用后,访问以下地址:

这些文档完全自动生成,无需任何额外配置。FastAPI通过分析你的代码(类型注解、docstring)来生成文档。

1.4 请求参数的多种来源

在Web开发中,客户端数据可以通过多种方式传递。FastAPI提供了统一且类型安全的方式来处理所有这些来源:

python 复制代码
from fastapi import FastAPI, Query, Path, Body, Header, Cookie
from pydantic import BaseModel
from typing import Annotated

app = FastAPI()

# ==================== 1. 路径参数 ====================
# 路径参数是URL的一部分,用于标识特定资源
# 例如:/users/123 中的 123 就是用户ID

@app.get("/users/{user_id}")
async def get_user(
    user_id: Annotated[int, Path(
        title="用户ID",
        description="要查询的用户的唯一标识符",
        ge=1,           # 大于等于1
        example=42      # 文档中的示例值
    )]
):
    """
    Path参数的特点:
    - 必须存在(否则URL不匹配)
    - 通常用于资源标识
    - 支持类型验证和约束
    """
    return {"user_id": user_id}


# ==================== 2. 查询参数 ====================
# 查询参数在URL的?后面,用于过滤、分页、搜索等
# 例如:/items/?skip=0&limit=10&q=phone

@app.get("/items/")
async def list_items(
    # 必选参数:没有默认值
    category: str,

    # 可选参数:有默认值
    skip: int = 0,

    # 带验证的参数:使用Query添加约束
    limit: Annotated[int, Query(
        le=100,          # 最大100
        description="返回结果数量限制"
    )] = 10,

    # 带长度验证的字符串参数
    q: Annotated[str | None, Query(
        min_length=3,    # 最短3个字符
        max_length=50,   # 最长50个字符
        pattern="^[a-zA-Z0-9]+$"  # 只允许字母数字
    )] = None
):
    """
    Query参数的特点:
    - 用于可选的筛选条件
    - 可以有默认值
    - 支持复杂的验证规则
    - 非必须(除非没有默认值)
    """
    return {
        "category": category,
        "skip": skip,
        "limit": limit,
        "q": q
    }


# ==================== 3. 请求体 ====================
# 请求体用于发送复杂的结构化数据,通常是JSON格式
# 常见于POST、PUT、PATCH请求

class Item(BaseModel):
    """
    Pydantic模型定义请求体结构

    这不仅仅是数据容器,它还提供:
    - 自动JSON解析
    - 类型验证
    - 自动文档生成
    - IDE智能提示
    """
    name: str
    price: float
    description: str | None = None
    tax: float | None = None

@app.post("/items/")
async def create_item(item: Item):
    """
    创建新物品

    FastAPI看到参数类型是Pydantic模型,
    会自动从请求体读取JSON并验证
    """
    # item 已经是验证过的 Item 实例
    item_dict = item.model_dump()
    if item.tax:
        item_dict["price_with_tax"] = item.price + item.tax
    return item_dict


# ==================== 4. 请求头 ====================
# HTTP头部通常用于元数据:认证、内容类型、追踪ID等

@app.get("/headers/")
async def read_headers(
    # Header会自动处理HTTP头部的命名转换
    # X-Token 在Python中变成 x_token
    user_agent: Annotated[str | None, Header()] = None,
    x_request_id: Annotated[str | None, Header()] = None,
    authorization: Annotated[str | None, Header()] = None
):
    """
    Header参数的特点:
    - 自动转换下划线为连字符(x_token -> X-Token)
    - 大小写不敏感
    - 常用于认证、追踪、内容协商
    """
    return {
        "User-Agent": user_agent,
        "X-Request-ID": x_request_id,
        "Authorization": authorization[:20] + "..." if authorization else None
    }


# ==================== 5. Cookie ====================
# Cookie用于客户端状态存储,如会话ID、用户偏好等

@app.get("/cookies/")
async def read_cookies(
    session_id: Annotated[str | None, Cookie()] = None,
    preferences: Annotated[str | None, Cookie()] = None
):
    """
    Cookie参数的特点:
    - 由浏览器自动发送
    - 常用于会话管理
    - 注意安全性(HttpOnly、Secure、SameSite)
    """
    return {
        "session_id": session_id,
        "preferences": preferences
    }

Annotated类型的设计哲学

你可能注意到我们使用了 Annotated[int, Query(...)] 这种语法。这是Python 3.9引入的类型注解增强,FastAPI充分利用了这一特性:

python 复制代码
# 传统方式(仍然支持,但不推荐)
def old_style(q: str = Query(default=None, min_length=3)):
    pass

# 现代方式(推荐)
def new_style(q: Annotated[str | None, Query(min_length=3)] = None):
    pass

使用Annotated的好处:

  1. 类型和元数据分离 :类型检查工具只看到 str | None
  2. 更清晰的默认值:默认值在最后,一目了然
  3. 可复用:可以创建类型别名复用验证规则
python 复制代码
# 创建可复用的类型别名
PositiveInt = Annotated[int, Query(gt=0)]
SearchQuery = Annotated[str | None, Query(min_length=3, max_length=50)]

# 在多个路由中复用
@app.get("/products/")
async def list_products(page: PositiveInt = 1, q: SearchQuery = None):
    pass

@app.get("/orders/")
async def list_orders(page: PositiveInt = 1, q: SearchQuery = None):
    pass

2. 异步编程原理

2.1 为什么需要异步?

要理解异步编程的价值,我们需要先理解Web服务器面临的挑战:

传统同步模型的问题

想象一个餐厅只有一个服务员。同步模型下,这个服务员:

  1. 接待客人A,记录点单
  2. 去厨房等待A的菜做好
  3. 把菜端给A
  4. 然后才能服务客人B

如果做一道菜需要10分钟,这个服务员一小时只能服务6位客人。

异步模型的解决方案

聪明的服务员会这样做:

  1. 接待客人A,记录点单,提交给厨房
  2. 不等待,立即去接待客人B
  3. 记录B的点单,提交厨房
  4. 继续接待C、D、E...
  5. 当厨房通知某道菜好了,再去端菜

这就是异步编程的核心思想:不要傻等,去做别的事

python 复制代码
import asyncio
import httpx
from fastapi import FastAPI
import time

app = FastAPI()

# ==================== 同步方式(阻塞) ====================
@app.get("/sync")
def sync_endpoint():
    """
    同步请求示例

    问题:
    - 这个函数执行期间,整个worker都在等待
    - 如果外部API响应需要2秒,这个worker 2秒内无法处理其他请求
    - 在高并发场景下,这会导致严重的性能问题
    """
    import requests
    # 发起HTTP请求,线程阻塞等待响应
    response = requests.get("https://api.example.com/data")
    return response.json()


# ==================== 异步方式(非阻塞) ====================
@app.get("/async")
async def async_endpoint():
    """
    异步请求示例

    优势:
    - await期间,事件循环可以处理其他请求
    - 单个worker可以同时处理数百个并发请求
    - 资源利用率大幅提升
    """
    async with httpx.AsyncClient() as client:
        # await表示:发起请求后,让出控制权,等响应回来再继续
        response = await client.get("https://api.example.com/data")
        return response.json()

2.2 深入理解 async/await

asyncawait 是Python异步编程的核心关键字,理解它们的本质很重要:

async def - 定义协程函数

python 复制代码
async def my_coroutine():
    """
    async def 定义了一个协程函数

    调用这个函数不会立即执行函数体,
    而是返回一个协程对象,需要被await或事件循环调度
    """
    return "Hello"

# 直接调用不会执行函数体
coro = my_coroutine()  # 返回协程对象 <coroutine object my_coroutine at 0x...>

# 必须await才会真正执行
result = await my_coroutine()  # 返回 "Hello"

await - 等待异步操作完成

python 复制代码
async def fetch_data(url: str) -> dict:
    """模拟异步数据获取"""
    await asyncio.sleep(1)  # 模拟I/O操作,这1秒内可以处理其他请求
    return {"url": url, "data": "some data"}

async def process_data(data: dict) -> dict:
    """模拟异步数据处理"""
    await asyncio.sleep(0.5)  # 模拟I/O操作
    return {"processed": True, **data}

串行 vs 并行执行

理解这两种模式的区别对写出高效的异步代码至关重要:

python 复制代码
# ==================== 串行执行 ====================
@app.get("/serial")
async def serial_execution():
    """
    串行执行:总耗时 = 1秒 + 1秒 = 2秒

    适用场景:
    - 第二个操作依赖第一个的结果
    - 需要保证执行顺序
    """
    start = time.time()

    # 先执行第一个,等待完成
    data1 = await fetch_data("url1")  # 等待1秒
    # 再执行第二个
    data2 = await fetch_data("url2")  # 再等待1秒

    print(f"串行耗时: {time.time() - start:.2f}秒")  # 约2秒
    return [data1, data2]


# ==================== 并行执行 ====================
@app.get("/parallel")
async def parallel_execution():
    """
    并行执行:总耗时 = max(1秒, 1秒) = 1秒

    适用场景:
    - 多个独立的I/O操作
    - 操作之间没有依赖关系
    """
    start = time.time()

    # asyncio.gather 同时启动多个协程
    # 它们会并发执行,等待最慢的那个完成
    data1, data2 = await asyncio.gather(
        fetch_data("url1"),  # 启动
        fetch_data("url2")   # 同时启动
    )

    print(f"并行耗时: {time.time() - start:.2f}秒")  # 约1秒
    return [data1, data2]


# ==================== TaskGroup(Python 3.11+) ====================
@app.get("/taskgroup")
async def taskgroup_execution():
    """
    TaskGroup是Python 3.11引入的更现代的并发控制方式

    相比asyncio.gather的优势:
    - 更好的异常处理:一个任务失败会取消所有其他任务
    - 结构化并发:任务有明确的生命周期边界
    - 代码更清晰
    """
    start = time.time()

    async with asyncio.TaskGroup() as tg:
        # 创建任务,立即开始执行
        task1 = tg.create_task(fetch_data("url1"))
        task2 = tg.create_task(fetch_data("url2"))

    # 退出上下文管理器时,所有任务都已完成
    print(f"TaskGroup耗时: {time.time() - start:.2f}秒")
    return [task1.result(), task2.result()]

2.3 何时使用 async def vs def

这是FastAPI新手最常见的困惑之一。简单的判断标准:

使用 async def

  • 函数内部有 await 调用
  • 进行异步I/O操作(async数据库、async HTTP客户端)

使用普通 def

  • 纯CPU计算
  • 使用同步库(如 requests、同步数据库驱动)
python 复制代码
from fastapi import FastAPI
import time

app = FastAPI()

# ==================== 场景1:异步I/O操作 ====================
@app.get("/io-bound")
async def io_bound():
    """
    使用 async def:适合I/O密集型操作

    I/O操作的特点是大部分时间在等待:
    - 等待数据库响应
    - 等待HTTP请求响应
    - 等待文件读写完成
    """
    async with httpx.AsyncClient() as client:
        # 这个await期间,事件循环可以处理其他请求
        response = await client.get("https://api.example.com/data")
        return response.json()


# ==================== 场景2:CPU密集型操作 ====================
@app.get("/cpu-bound")
def cpu_bound():
    """
    使用普通 def:适合CPU密集型操作

    FastAPI的智能处理:
    - 普通函数会在线程池中运行
    - 不会阻塞事件循环
    - 但要注意:线程池大小有限(默认40个线程)
    """
    # 模拟CPU密集计算
    result = sum(i * i for i in range(1000000))
    return {"result": result}


# ==================== 反面教材:阻塞事件循环 ====================
@app.get("/bad-example")
async def bad_example():
    """
    ❌ 错误示范:在async函数中使用阻塞操作

    问题:
    - time.sleep是同步阻塞调用
    - 在async函数中,它会阻塞整个事件循环
    - 所有其他请求都要等这5秒过去
    """
    time.sleep(5)  # 千万别这样做!
    return {"status": "done"}


# ==================== 正确处理:使用run_in_executor ====================
@app.get("/good-example")
async def good_example():
    """
    ✓ 正确示范:将阻塞操作放到线程池

    如果必须使用同步阻塞函数(比如某些遗留库),
    使用run_in_executor将其放到线程池执行
    """
    loop = asyncio.get_event_loop()
    # 在线程池中运行阻塞操作,不会阻塞事件循环
    await loop.run_in_executor(None, time.sleep, 5)
    return {"status": "done"}

2.4 异步上下文管理器与应用生命周期

现代FastAPI应用通常需要管理各种资源:数据库连接池、HTTP客户端、缓存连接等。这些资源应该在应用启动时创建,关闭时清理。

python 复制代码
from contextlib import asynccontextmanager
from fastapi import FastAPI
import httpx
from sqlalchemy.ext.asyncio import create_async_engine

@asynccontextmanager
async def lifespan(app: FastAPI):
    """
    应用生命周期管理器

    这是FastAPI推荐的资源管理方式(替代了旧的on_event装饰器)

    设计模式:
    - yield之前的代码在应用启动时执行(初始化)
    - yield之后的代码在应用关闭时执行(清理)
    - 通过app.state存储共享资源
    """

    # ===== 启动时执行 =====
    print("🚀 应用启动中...")

    # 1. 初始化数据库连接池
    # 为什么要在启动时创建?
    # - 连接池创建需要时间,不应该让第一个请求等待
    # - 连接池应该被所有请求共享,而不是每个请求创建
    app.state.db_engine = create_async_engine(
        "postgresql+asyncpg://user:pass@localhost/db",
        pool_size=10,      # 池中保持的连接数
        max_overflow=20,   # 超出pool_size时可以额外创建的连接数
        pool_recycle=3600  # 连接1小时后回收,避免数据库超时断开
    )

    # 2. 初始化HTTP客户端
    # 为什么要复用客户端?
    # - HTTP连接建立需要TCP握手、可能的TLS握手,开销大
    # - 复用客户端可以使用HTTP连接池,大幅减少延迟
    app.state.http_client = httpx.AsyncClient(
        timeout=30.0,
        limits=httpx.Limits(
            max_keepalive_connections=20,
            max_connections=100
        )
    )

    # 3. 初始化Redis连接
    import redis.asyncio as aioredis
    app.state.redis = await aioredis.from_url(
        "redis://localhost",
        encoding="utf-8",
        decode_responses=True
    )

    print("✅ 所有服务初始化完成!")

    yield  # 应用运行中,处理请求

    # ===== 关闭时执行 =====
    print("🛑 应用关闭中...")

    # 优雅关闭所有连接
    # 为什么需要优雅关闭?
    # - 确保正在进行的数据库事务能够完成
    # - 释放系统资源(文件描述符、内存)
    # - 避免连接泄漏导致数据库连接池耗尽

    await app.state.db_engine.dispose()
    await app.state.http_client.aclose()
    await app.state.redis.close()

    print("👋 应用已安全关闭")

# 创建应用,传入生命周期管理器
app = FastAPI(lifespan=lifespan)

@app.get("/external-api")
async def call_external():
    """
    使用共享的HTTP客户端

    好处:
    - 复用连接池,减少连接建立开销
    - 统一的超时配置
    - 应用关闭时自动清理
    """
    client = app.state.http_client
    response = await client.get("https://api.github.com/zen")
    return {"github_zen": response.text}

@app.get("/cache/{key}")
async def get_cache(key: str):
    """使用共享的Redis连接"""
    value = await app.state.redis.get(key)
    return {"key": key, "value": value}

3. Pydantic数据验证

3.1 为什么需要数据验证?

在Web开发中,永远不要信任客户端数据。用户可能:

  • 输入错误的数据类型(字符串代替数字)
  • 输入不符合业务规则的数据(负数价格、空用户名)
  • 恶意输入(SQL注入、XSS攻击)

传统方式需要大量手动验证代码:

python 复制代码
# 传统方式:繁琐且容易遗漏
def create_user_old(data: dict):
    if "username" not in data:
        raise ValueError("缺少用户名")
    if not isinstance(data["username"], str):
        raise ValueError("用户名必须是字符串")
    if len(data["username"]) < 3:
        raise ValueError("用户名至少3个字符")
    if len(data["username"]) > 50:
        raise ValueError("用户名最多50个字符")
    # ... 还有email、password等字段
    # 代码冗长,容易出错,维护困难

Pydantic通过声明式的方式解决这个问题:

python 复制代码
from pydantic import BaseModel, Field, field_validator, model_validator
from datetime import datetime
from enum import Enum
from typing import Self

# 使用枚举限制有效值
class UserRole(str, Enum):
    """
    用户角色枚举

    继承str的好处:
    - JSON序列化时自动转为字符串
    - 可以直接与字符串比较
    """
    ADMIN = "admin"
    USER = "user"
    GUEST = "guest"


class UserBase(BaseModel):
    """
    用户基础模型

    BaseModel的魔力:
    - 自动从JSON/dict解析数据
    - 自动类型转换("123" -> 123)
    - 自动验证约束
    - 生成JSON Schema(用于API文档)
    """

    # Field提供更详细的验证规则
    username: str = Field(
        ...,              # ... 表示必填字段
        min_length=3,     # 最短3个字符
        max_length=50,    # 最长50个字符
        examples=["john_doe"]  # 文档示例
    )

    # 使用正则表达式验证email格式
    email: str = Field(
        ...,
        pattern=r'^[\w\.-]+@[\w\.-]+\.\w+$',
        examples=["user@example.com"]
    )

    # 枚举类型,只接受定义的值
    role: UserRole = UserRole.USER


class UserCreate(UserBase):
    """
    创建用户时的请求模型

    继承UserBase,额外添加password字段
    这种分层设计很常见:
    - UserBase: 共享的基础字段
    - UserCreate: 创建时的输入(包含密码)
    - UserResponse: 返回给客户端的数据(不包含密码)
    """

    password: str = Field(..., min_length=8)

    @field_validator('password')
    @classmethod
    def validate_password_strength(cls, v: str) -> str:
        """
        自定义字段验证器

        验证密码强度:
        - 必须包含大写字母
        - 必须包含数字
        """
        if not any(c.isupper() for c in v):
            raise ValueError('密码必须包含至少一个大写字母')
        if not any(c.isdigit() for c in v):
            raise ValueError('密码必须包含至少一个数字')
        return v


class UserResponse(UserBase):
    """
    返回给客户端的用户模型

    注意:不包含password字段
    这是一个安全最佳实践:永远不要将密码返回给客户端
    """
    id: int
    created_at: datetime

    model_config = {
        "from_attributes": True  # 允许从ORM模型创建
    }

3.2 高级验证技巧

Pydantic v2提供了强大的验证能力,让我们看几个实际场景:

python 复制代码
from pydantic import BaseModel, Field, field_validator, model_validator
from typing import Self
from decimal import Decimal

class Order(BaseModel):
    """
    订单模型 - 展示多种验证技巧
    """
    product_id: int
    quantity: int = Field(gt=0, description="购买数量,必须大于0")
    unit_price: Decimal = Field(gt=0, decimal_places=2)
    discount: float = Field(ge=0, le=1, default=0, description="折扣率,0-1之间")
    total: Decimal | None = None  # 计算字段,允许为空

    @field_validator('quantity')
    @classmethod
    def validate_quantity(cls, v: int) -> int:
        """
        单字段验证器

        使用场景:
        - 检查库存(需要查询数据库)
        - 业务规则限制
        """
        if v > 1000:
            raise ValueError('单次订单数量不能超过1000')
        return v

    @model_validator(mode='after')
    def calculate_total(self) -> Self:
        """
        模型验证器 - 在所有字段验证完成后执行

        mode='after' 表示在字段验证后执行
        mode='before' 表示在字段验证前执行(用于预处理原始数据)

        这里用于计算派生字段
        """
        if self.total is None:
            self.total = self.quantity * self.unit_price * Decimal(1 - self.discount)
        return self


class DateRange(BaseModel):
    """
    日期范围 - 展示跨字段验证
    """
    start_date: datetime
    end_date: datetime

    @model_validator(mode='after')
    def validate_date_range(self) -> Self:
        """
        验证结束日期必须晚于开始日期

        这种跨字段验证只能用model_validator实现
        """
        if self.end_date <= self.start_date:
            raise ValueError('结束日期必须晚于开始日期')
        return self


class BatchRequest(BaseModel):
    """
    批量请求 - 展示列表验证
    """
    orders: list[Order]

    @model_validator(mode='after')
    def validate_batch(self) -> Self:
        """验证批量操作的约束"""
        if len(self.orders) > 100:
            raise ValueError('批量订单不能超过100个')

        # 检查是否有重复的product_id
        product_ids = [o.product_id for o in self.orders]
        if len(product_ids) != len(set(product_ids)):
            raise ValueError('批量订单中存在重复的产品')

        return self

3.3 泛型响应模型

在实际项目中,API响应通常有统一的格式。使用泛型可以创建可复用的响应包装器:

python 复制代码
from pydantic import BaseModel, Field
from typing import Generic, TypeVar
from datetime import datetime

# 定义类型变量
T = TypeVar('T')

class Response(BaseModel, Generic[T]):
    """
    通用响应包装器

    为什么需要统一的响应格式?
    - 前端可以用统一的逻辑处理所有响应
    - 便于错误处理和日志记录
    - API文档更加一致
    """
    code: int = Field(default=200, description="业务状态码")
    message: str = Field(default="success", description="状态消息")
    data: T | None = Field(default=None, description="响应数据")
    timestamp: datetime = Field(default_factory=datetime.now)

    @classmethod
    def success(cls, data: T, message: str = "success") -> "Response[T]":
        """便捷方法:创建成功响应"""
        return cls(code=200, message=message, data=data)

    @classmethod
    def error(cls, code: int, message: str) -> "Response[None]":
        """便捷方法:创建错误响应"""
        return cls(code=code, message=message, data=None)


class PaginatedResponse(BaseModel, Generic[T]):
    """
    分页响应包装器

    分页是API设计中最常见的需求之一
    """
    items: list[T]
    total: int = Field(description="总记录数")
    page: int = Field(description="当前页码")
    page_size: int = Field(description="每页大小")
    pages: int = Field(description="总页数")
    has_next: bool = Field(description="是否有下一页")
    has_prev: bool = Field(description="是否有上一页")

    @classmethod
    def create(
        cls,
        items: list[T],
        total: int,
        page: int,
        page_size: int
    ) -> "PaginatedResponse[T]":
        """
        工厂方法:根据参数计算分页信息

        为什么用工厂方法?
        - 封装计算逻辑
        - 确保字段一致性
        - 减少调用方的重复代码
        """
        pages = (total + page_size - 1) // page_size  # 向上取整
        return cls(
            items=items,
            total=total,
            page=page,
            page_size=page_size,
            pages=pages,
            has_next=page < pages,
            has_prev=page > 1
        )


# 具体的业务模型
class User(BaseModel):
    id: int
    username: str
    email: str

class Article(BaseModel):
    id: int
    title: str
    content: str


# 在路由中使用泛型响应
@app.get("/users", response_model=Response[PaginatedResponse[User]])
async def list_users(page: int = 1, page_size: int = 10):
    """
    获取用户列表

    response_model的作用:
    - 自动序列化响应
    - 过滤多余字段(如密码)
    - 生成OpenAPI文档
    """
    # 模拟数据
    total = 100
    users = [User(id=i, username=f"user_{i}", email=f"user{i}@example.com")
             for i in range((page-1)*page_size, min(page*page_size, total))]

    paginated = PaginatedResponse.create(
        items=users,
        total=total,
        page=page,
        page_size=page_size
    )

    return Response.success(paginated)

3.4 配置管理

Pydantic Settings是管理应用配置的最佳实践:

python 复制代码
from pydantic_settings import BaseSettings, SettingsConfigDict
from pydantic import Field, SecretStr
from functools import lru_cache

class Settings(BaseSettings):
    """
    应用配置类

    设计原则(12-Factor App):
    - 配置与代码分离
    - 从环境变量读取敏感信息
    - 有合理的默认值
    """

    # ===== 应用基础配置 =====
    app_name: str = "My FastAPI App"
    debug: bool = False
    environment: str = Field(default="development", pattern="^(development|staging|production)$")

    # ===== 数据库配置 =====
    database_url: str = Field(
        ...,  # 必填
        description="数据库连接字符串",
        examples=["postgresql://user:pass@localhost/db"]
    )
    database_pool_size: int = Field(default=5, ge=1, le=50)
    database_max_overflow: int = Field(default=10, ge=0)

    # ===== Redis配置 =====
    redis_url: str = "redis://localhost:6379/0"
    redis_max_connections: int = 10

    # ===== JWT配置 =====
    # 使用SecretStr存储敏感信息
    # 它不会在日志、repr中显示实际值
    secret_key: SecretStr
    algorithm: str = "HS256"
    access_token_expire_minutes: int = Field(default=30, ge=1)
    refresh_token_expire_days: int = Field(default=7, ge=1)

    # ===== 外部服务配置 =====
    openai_api_key: SecretStr | None = None
    sentry_dsn: str | None = None

    # ===== 配置来源设置 =====
    model_config = SettingsConfigDict(
        env_file=".env",           # 从.env文件读取
        env_file_encoding="utf-8",
        case_sensitive=False,      # 环境变量名不区分大小写
        extra="ignore"             # 忽略.env中多余的变量
    )


@lru_cache
def get_settings() -> Settings:
    """
    获取配置单例

    为什么用lru_cache?
    - 配置只需要加载一次
    - 避免重复读取环境变量和.env文件
    - 保证整个应用使用同一个配置实例
    """
    return Settings()


# 在FastAPI中使用配置
from fastapi import Depends

@app.get("/config")
async def get_config(settings: Settings = Depends(get_settings)):
    """
    显示(安全的)配置信息

    注意:
    - 不要暴露敏感信息
    - 生产环境应该禁用这个端点
    """
    if settings.environment == "production":
        return {"error": "Not available in production"}

    return {
        "app_name": settings.app_name,
        "environment": settings.environment,
        "debug": settings.debug,
        # SecretStr的get_secret_value()方法获取实际值
        # 但这里我们只显示是否已配置
        "openai_configured": settings.openai_api_key is not None
    }

4. 依赖注入系统

4.1 什么是依赖注入?

依赖注入(Dependency Injection,DI)是软件设计中的一个重要模式。核心思想是:不要在函数内部创建依赖,而是从外部注入

没有依赖注入的代码

python 复制代码
# 紧耦合的代码
def get_user_orders(user_id: int):
    # 函数内部直接创建数据库连接
    # 问题:
    # 1. 难以测试(无法模拟数据库)
    # 2. 难以复用(连接配置写死)
    # 3. 资源管理困难(连接何时关闭?)
    db = DatabaseConnection("postgresql://...")
    orders = db.query(f"SELECT * FROM orders WHERE user_id = {user_id}")
    db.close()
    return orders

使用依赖注入的代码

python 复制代码
# 松耦合的代码
def get_user_orders(user_id: int, db: DatabaseConnection):
    # 数据库连接从外部传入
    # 优势:
    # 1. 可测试(传入Mock数据库)
    # 2. 可复用(不关心连接来源)
    # 3. 关注点分离
    orders = db.query(f"SELECT * FROM orders WHERE user_id = {user_id}")
    return orders

FastAPI的依赖注入系统让这一切变得简单优雅:

python 复制代码
from fastapi import FastAPI, Depends, HTTPException, status
from typing import Annotated

app = FastAPI()

# ===== 定义依赖 =====
async def common_parameters(
    q: str | None = None,
    skip: int = 0,
    limit: int = 100
):
    """
    公共查询参数依赖

    这是一个简单的依赖函数,返回一个字典
    FastAPI会:
    1. 分析函数签名,获取参数
    2. 从请求中提取参数值
    3. 调用函数获取依赖值
    4. 将结果注入到路由函数
    """
    return {"q": q, "skip": skip, "limit": limit}


# 创建类型别名,提高可读性
CommonParams = Annotated[dict, Depends(common_parameters)]


@app.get("/items/")
async def read_items(commons: CommonParams):
    """
    这里的commons会自动获得common_parameters的返回值

    执行流程:
    1. 请求到达 /items/?q=test&skip=10
    2. FastAPI调用common_parameters(q="test", skip=10, limit=100)
    3. 返回值 {"q": "test", "skip": 10, "limit": 100}
    4. 这个字典作为commons参数传给read_items
    """
    return {"items": [...], "params": commons}


@app.get("/users/")
async def read_users(commons: CommonParams):
    """
    同样的依赖可以在多个路由中复用
    这就是依赖注入的威力:代码复用 + 关注点分离
    """
    return {"users": [...], "params": commons}

4.2 类作为依赖

当依赖逻辑复杂时,使用类可以提供更好的组织和封装:

python 复制代码
from fastapi import Depends, Query
from typing import Annotated

class Pagination:
    """
    分页参数依赖类

    使用类的优势:
    - 可以有实例属性,存储计算后的值
    - 可以有方法,提供额外功能
    - 更好的IDE支持和类型提示
    """

    def __init__(
        self,
        page: int = Query(1, ge=1, description="页码,从1开始"),
        page_size: int = Query(10, ge=1, le=100, description="每页数量")
    ):
        """
        初始化方法就像普通依赖函数一样工作

        FastAPI会:
        1. 分析__init__的参数
        2. 从请求中提取page和page_size
        3. 创建Pagination实例
        """
        self.page = page
        self.page_size = page_size
        # 计算派生值
        self.skip = (page - 1) * page_size
        self.limit = page_size

    def paginate(self, query):
        """便捷方法:应用分页到SQLAlchemy查询"""
        return query.offset(self.skip).limit(self.limit)


@app.get("/items/")
async def list_items(
    # Depends()不带参数时,FastAPI会实例化Pagination类
    pagination: Annotated[Pagination, Depends()]
):
    """
    pagination是Pagination类的实例
    可以访问其属性和方法
    """
    return {
        "page": pagination.page,
        "page_size": pagination.page_size,
        "skip": pagination.skip,
        "limit": pagination.limit
    }

4.3 依赖链:构建复杂的依赖关系

FastAPI的依赖可以组成链式结构,解决复杂的依赖关系:

python 复制代码
from fastapi import Depends, HTTPException, status
from typing import Annotated

# ===== 第一层:基础设施依赖 =====
async def get_db():
    """
    数据库会话依赖

    使用yield的依赖(上下文依赖):
    - yield之前的代码在请求处理前执行
    - yield的值作为依赖注入
    - yield之后的代码在请求处理后执行(无论成功还是异常)
    """
    db = SessionLocal()
    try:
        yield db
        await db.commit()  # 成功则提交
    except Exception:
        await db.rollback()  # 异常则回滚
        raise
    finally:
        await db.close()  # 无论如何都关闭连接


# ===== 第二层:认证依赖 =====
async def get_current_user(
    token: str = Depends(oauth2_scheme),  # 从请求头获取token
    db: Session = Depends(get_db)          # 需要数据库来验证用户
) -> User:
    """
    获取当前登录用户

    这是一个组合依赖:
    - 依赖oauth2_scheme获取token
    - 依赖get_db获取数据库会话

    执行顺序:
    1. oauth2_scheme从请求头提取token
    2. get_db创建数据库会话
    3. 本函数验证token,查询用户
    """
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="无法验证凭据",
        headers={"WWW-Authenticate": "Bearer"},
    )

    # 验证token
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        user_id = payload.get("sub")
        if user_id is None:
            raise credentials_exception
    except JWTError:
        raise credentials_exception

    # 查询用户
    user = await db.get(User, user_id)
    if user is None:
        raise credentials_exception

    return user


# ===== 第三层:权限检查依赖 =====
async def get_current_active_user(
    current_user: Annotated[User, Depends(get_current_user)]
) -> User:
    """
    获取活跃用户

    在get_current_user的基础上,额外检查用户是否被禁用
    """
    if not current_user.is_active:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="用户已被禁用"
        )
    return current_user


# ===== 第四层:角色检查依赖 =====
def require_role(required_role: str):
    """
    角色检查依赖工厂

    这是一个高阶函数,返回一个依赖
    允许参数化依赖行为
    """
    async def role_checker(
        current_user: Annotated[User, Depends(get_current_active_user)]
    ) -> User:
        if current_user.role != required_role:
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail=f"需要 {required_role} 角色"
            )
        return current_user

    return role_checker


# ===== 在路由中使用 =====

@app.get("/users/me")
async def read_current_user(
    user: Annotated[User, Depends(get_current_active_user)]
):
    """任何登录用户都可以访问"""
    return user

@app.get("/admin/users")
async def admin_list_users(
    admin: Annotated[User, Depends(require_role("admin"))],
    db: Annotated[Session, Depends(get_db)]
):
    """
    只有管理员可以访问

    依赖执行顺序:
    1. oauth2_scheme提取token
    2. get_db创建数据库会话
    3. get_current_user验证token,获取用户
    4. get_current_active_user检查用户状态
    5. require_role("admin")检查角色
    6. 最后执行路由函数
    """
    users = await db.query(User).all()
    return users

4.4 路由级别依赖

有时候你需要给一组路由添加相同的依赖,FastAPI支持在路由器级别声明依赖:

python 复制代码
from fastapi import APIRouter, Depends

# 创建一个需要认证的路由器
authenticated_router = APIRouter(
    prefix="/protected",
    tags=["Protected"],
    # 这里的依赖会应用到这个路由器下的所有路由
    dependencies=[Depends(get_current_active_user)]
)

@authenticated_router.get("/resource1")
async def get_resource1():
    """自动需要认证"""
    return {"resource": "1"}

@authenticated_router.get("/resource2")
async def get_resource2():
    """自动需要认证"""
    return {"resource": "2"}


# 创建一个管理员路由器
admin_router = APIRouter(
    prefix="/admin",
    tags=["Admin"],
    dependencies=[Depends(require_role("admin"))]
)

@admin_router.get("/dashboard")
async def admin_dashboard():
    """只有管理员可以访问"""
    return {"status": "admin area"}


# 将路由器注册到主应用
app.include_router(authenticated_router)
app.include_router(admin_router)

5. 中间件与生命周期

5.1 理解中间件

中间件是处理请求/响应的"层",它在路由函数执行前后运行,适合实现横切关注点(Cross-Cutting Concerns)。

中间件的执行流程

复制代码
请求 → 中间件A(前) → 中间件B(前) → 路由函数 → 中间件B(后) → 中间件A(后) → 响应

像洋葱一样,后添加的中间件在内层。

python 复制代码
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
import time
import uuid
import logging

app = FastAPI()
logger = logging.getLogger(__name__)

# ===== 自定义中间件 =====
@app.middleware("http")
async def request_middleware(request: Request, call_next):
    """
    请求处理中间件

    这个中间件做三件事:
    1. 生成请求ID(用于追踪)
    2. 记录请求开始
    3. 计算处理时间
    """
    # 生成唯一请求ID
    request_id = request.headers.get("X-Request-ID", str(uuid.uuid4()))

    # 记录请求开始时间
    start_time = time.time()

    # 将request_id存入request.state,供路由函数使用
    request.state.request_id = request_id

    logger.info(f"[{request_id}] 开始处理: {request.method} {request.url.path}")

    try:
        # 调用下一个中间件或路由
        # 这是关键!不调用call_next请求就不会被处理
        response = await call_next(request)

        # 计算处理时间
        process_time = time.time() - start_time

        # 在响应头中添加有用信息
        response.headers["X-Request-ID"] = request_id
        response.headers["X-Process-Time"] = f"{process_time:.4f}"

        logger.info(
            f"[{request_id}] 处理完成: {response.status_code} "
            f"耗时 {process_time:.4f}s"
        )

        return response

    except Exception as e:
        # 记录异常
        process_time = time.time() - start_time
        logger.error(
            f"[{request_id}] 处理异常: {str(e)} "
            f"耗时 {process_time:.4f}s"
        )
        raise


# ===== CORS中间件 =====
# CORS(跨源资源共享)是Web安全的重要机制
app.add_middleware(
    CORSMiddleware,
    # 允许的源
    # 开发环境可以用["*"],生产环境应该明确指定
    allow_origins=[
        "http://localhost:3000",     # 本地前端
        "https://myapp.com",         # 生产前端
    ],
    # 是否允许携带凭据(cookies、Authorization头)
    allow_credentials=True,
    # 允许的HTTP方法
    allow_methods=["*"],
    # 允许的请求头
    allow_headers=["*"],
    # 预检请求的缓存时间
    max_age=600,
)

5.2 高级中间件:限流

限流是保护API的重要手段,防止滥用和DDoS攻击:

python 复制代码
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
from starlette.responses import Response, JSONResponse
from collections import defaultdict
import time
import asyncio

class RateLimitMiddleware(BaseHTTPMiddleware):
    """
    速率限制中间件

    实现滑动窗口限流算法:
    - 记录每个IP的请求时间戳
    - 只保留时间窗口内的记录
    - 超过限制返回429

    注意:这是简化实现,生产环境应该使用Redis
    """

    def __init__(
        self,
        app,
        requests_per_minute: int = 60,
        window_size: int = 60
    ):
        super().__init__(app)
        self.requests_per_minute = requests_per_minute
        self.window_size = window_size
        # 存储请求记录:{ip: [timestamp1, timestamp2, ...]}
        self.requests: dict[str, list[float]] = defaultdict(list)
        # 用于清理过期记录
        self._cleanup_lock = asyncio.Lock()

    async def dispatch(self, request: Request, call_next) -> Response:
        # 获取客户端IP
        # 注意:如果在反向代理后面,应该从X-Forwarded-For获取
        client_ip = request.client.host
        forwarded_for = request.headers.get("X-Forwarded-For")
        if forwarded_for:
            client_ip = forwarded_for.split(",")[0].strip()

        current_time = time.time()

        # 清理过期记录(避免内存泄漏)
        async with self._cleanup_lock:
            if client_ip in self.requests:
                # 只保留窗口内的请求
                self.requests[client_ip] = [
                    t for t in self.requests[client_ip]
                    if current_time - t < self.window_size
                ]

        # 检查是否超过限制
        recent_requests = self.requests[client_ip]
        if len(recent_requests) >= self.requests_per_minute:
            # 计算重试时间
            oldest_request = min(recent_requests)
            retry_after = int(self.window_size - (current_time - oldest_request))

            return JSONResponse(
                status_code=429,
                content={
                    "error": "Too Many Requests",
                    "detail": f"超过速率限制({self.requests_per_minute}次/分钟)",
                    "retry_after": retry_after
                },
                headers={"Retry-After": str(retry_after)}
            )

        # 记录本次请求
        self.requests[client_ip].append(current_time)

        # 处理请求
        response = await call_next(request)

        # 添加限流信息到响应头
        response.headers["X-RateLimit-Limit"] = str(self.requests_per_minute)
        response.headers["X-RateLimit-Remaining"] = str(
            self.requests_per_minute - len(self.requests[client_ip])
        )

        return response


# 添加中间件
app.add_middleware(RateLimitMiddleware, requests_per_minute=100)

5.3 中间件 vs 依赖注入:如何选择?

这是一个常见的设计决策。两者的区别:

特性 中间件 依赖注入
执行范围 所有请求 指定路由
访问响应 ✓ 可以 ✗ 不能
返回早期响应 ✓ 可以 ✓ 可以(抛异常)
修改响应头 ✓ 可以 ✗ 不能
测试便利性 中等 优秀(易于mock)
典型用途 日志、CORS、限流 认证、数据库、参数验证

选择建议

python 复制代码
# 使用中间件:需要处理所有请求,或需要修改响应
@app.middleware("http")
async def add_security_headers(request: Request, call_next):
    response = await call_next(request)
    response.headers["X-Content-Type-Options"] = "nosniff"
    response.headers["X-Frame-Options"] = "DENY"
    return response

# 使用依赖注入:只需要处理特定路由,或需要复杂的依赖关系
@app.get("/protected")
async def protected_route(user: User = Depends(get_current_user)):
    return {"user": user.username}

6. 认证与安全

6.1 理解OAuth2

OAuth2是现代Web认证的标准协议。FastAPI内置了完整的OAuth2支持。

OAuth2 Password Flow的工作原理

复制代码
1. 用户提交用户名和密码
2. 服务器验证凭据
3. 验证成功,服务器返回JWT token
4. 后续请求携带token
5. 服务器验证token,获取用户身份
python 复制代码
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import JWTError, jwt
from passlib.context import CryptContext
from datetime import datetime, timedelta
from typing import Annotated
from pydantic import BaseModel

# ===== 配置 =====
SECRET_KEY = "your-secret-key-keep-it-secret"  # 生产环境使用环境变量
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

# ===== 密码哈希 =====
# 使用bcrypt算法,这是目前推荐的密码哈希算法
# 特点:慢(故意的,防止暴力破解)、自动加盐
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

# ===== OAuth2配置 =====
# tokenUrl是获取token的路径,用于Swagger UI的认证表单
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

app = FastAPI()


# ===== 数据模型 =====
class Token(BaseModel):
    """Token响应模型"""
    access_token: str
    token_type: str
    expires_in: int  # token有效期(秒)

class TokenData(BaseModel):
    """Token解析后的数据"""
    user_id: int | None = None
    username: str | None = None

class User(BaseModel):
    """用户模型"""
    id: int
    username: str
    email: str
    is_active: bool = True

class UserInDB(User):
    """数据库中的用户(包含密码哈希)"""
    hashed_password: str


# ===== 密码工具函数 =====
def verify_password(plain_password: str, hashed_password: str) -> bool:
    """
    验证密码

    为什么不直接比较?
    - 密码存储的是哈希值,不是明文
    - 需要用相同的算法处理输入的密码,再比较哈希
    """
    return pwd_context.verify(plain_password, hashed_password)

def get_password_hash(password: str) -> str:
    """
    生成密码哈希

    bcrypt的特点:
    - 自动生成随机盐
    - 盐存储在哈希结果中
    - 每次哈希结果不同(因为盐不同),但验证时都能通过
    """
    return pwd_context.hash(password)


# ===== JWT工具函数 =====
def create_access_token(
    data: dict,
    expires_delta: timedelta | None = None
) -> str:
    """
    创建JWT访问令牌

    JWT结构:header.payload.signature
    - header: 算法和类型
    - payload: 用户数据(不要放敏感信息!)
    - signature: 签名,防止篡改
    """
    to_encode = data.copy()

    # 设置过期时间
    expire = datetime.utcnow() + (expires_delta or timedelta(minutes=15))
    to_encode.update({
        "exp": expire,
        "iat": datetime.utcnow(),  # 签发时间
        "type": "access"           # 令牌类型
    })

    # 生成JWT
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt


# ===== 用户认证依赖 =====
async def get_current_user(
    token: Annotated[str, Depends(oauth2_scheme)]
) -> User:
    """
    从token获取当前用户

    这是核心认证逻辑,被其他需要认证的路由依赖
    """
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="无法验证凭据",
        headers={"WWW-Authenticate": "Bearer"},
    )

    try:
        # 解码JWT
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])

        # 提取用户信息
        username: str = payload.get("sub")
        user_id: int = payload.get("user_id")

        if username is None:
            raise credentials_exception

        token_data = TokenData(username=username, user_id=user_id)

    except JWTError as e:
        # JWT解码失败(过期、签名无效等)
        raise credentials_exception

    # 从数据库获取用户(这里简化为模拟)
    user = get_user_from_db(token_data.user_id)
    if user is None:
        raise credentials_exception

    return user


# ===== 登录端点 =====
@app.post("/token", response_model=Token)
async def login(
    form_data: Annotated[OAuth2PasswordRequestForm, Depends()]
):
    """
    用户登录,获取访问令牌

    OAuth2PasswordRequestForm是标准的OAuth2密码流表单:
    - username: 用户名
    - password: 密码
    - scope: 权限范围(可选)
    """
    # 1. 验证用户凭据
    user = authenticate_user(form_data.username, form_data.password)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="用户名或密码错误",
            headers={"WWW-Authenticate": "Bearer"},
        )

    # 2. 创建访问令牌
    access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    access_token = create_access_token(
        data={
            "sub": user.username,
            "user_id": user.id
        },
        expires_delta=access_token_expires
    )

    # 3. 返回令牌
    return Token(
        access_token=access_token,
        token_type="bearer",
        expires_in=ACCESS_TOKEN_EXPIRE_MINUTES * 60
    )


# ===== 受保护的端点 =====
@app.get("/users/me", response_model=User)
async def read_users_me(
    current_user: Annotated[User, Depends(get_current_user)]
):
    """
    获取当前登录用户信息

    由于依赖get_current_user,未认证的请求会返回401
    """
    return current_user

6.2 基于角色的访问控制(RBAC)

实际应用通常需要更细粒度的权限控制:

python 复制代码
from enum import Enum
from functools import wraps

class Permission(str, Enum):
    """权限枚举"""
    READ = "read"
    WRITE = "write"
    DELETE = "delete"
    ADMIN = "admin"

class Role(str, Enum):
    """角色枚举"""
    VIEWER = "viewer"
    EDITOR = "editor"
    ADMIN = "admin"

# 角色-权限映射
ROLE_PERMISSIONS = {
    Role.VIEWER: [Permission.READ],
    Role.EDITOR: [Permission.READ, Permission.WRITE],
    Role.ADMIN: [Permission.READ, Permission.WRITE, Permission.DELETE, Permission.ADMIN],
}


class User(BaseModel):
    id: int
    username: str
    role: Role


def require_permissions(*required_permissions: Permission):
    """
    权限检查依赖工厂

    使用方式:
    @app.delete("/items/{id}")
    async def delete_item(
        id: int,
        user: User = Depends(require_permissions(Permission.DELETE))
    ):
        ...
    """
    async def permission_checker(
        current_user: Annotated[User, Depends(get_current_user)]
    ) -> User:
        # 获取用户的所有权限
        user_permissions = ROLE_PERMISSIONS.get(current_user.role, [])

        # 检查是否拥有所需权限
        for perm in required_permissions:
            if perm not in user_permissions:
                raise HTTPException(
                    status_code=status.HTTP_403_FORBIDDEN,
                    detail=f"需要 {perm.value} 权限"
                )

        return current_user

    return permission_checker


# 使用示例
@app.get("/articles")
async def list_articles(
    user: User = Depends(require_permissions(Permission.READ))
):
    """任何角色都可以读取"""
    return {"articles": [...]}

@app.post("/articles")
async def create_article(
    article: Article,
    user: User = Depends(require_permissions(Permission.WRITE))
):
    """需要写入权限"""
    return {"created": article}

@app.delete("/articles/{id}")
async def delete_article(
    id: int,
    user: User = Depends(require_permissions(Permission.DELETE))
):
    """需要删除权限(只有管理员)"""
    return {"deleted": id}

7. 数据库集成

7.1 SQLAlchemy异步集成

现代FastAPI应用推荐使用异步数据库操作。SQLAlchemy 2.0完全支持async/await:

python 复制代码
from sqlalchemy.ext.asyncio import (
    create_async_engine,
    AsyncSession,
    async_sessionmaker
)
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from sqlalchemy import String, ForeignKey, select
from datetime import datetime
from typing import AsyncGenerator

# ===== 数据库配置 =====
# 异步PostgreSQL连接需要使用asyncpg驱动
DATABASE_URL = "postgresql+asyncpg://user:password@localhost/dbname"

# 创建异步引擎
engine = create_async_engine(
    DATABASE_URL,
    echo=True,           # 开发环境打印SQL,生产环境关闭
    pool_size=10,        # 连接池大小
    max_overflow=20,     # 超出pool_size时可额外创建的连接数
    pool_recycle=3600,   # 连接回收时间,防止数据库断开空闲连接
    pool_pre_ping=True,  # 使用前检查连接是否有效
)

# 创建会话工厂
async_session = async_sessionmaker(
    engine,
    class_=AsyncSession,
    expire_on_commit=False  # 提交后不过期对象,避免额外查询
)


# ===== 模型定义 =====
class Base(DeclarativeBase):
    """所有模型的基类"""
    pass


class User(Base):
    """
    用户模型

    SQLAlchemy 2.0使用Mapped和mapped_column进行类型安全的定义
    """
    __tablename__ = "users"

    # 主键
    id: Mapped[int] = mapped_column(primary_key=True)

    # 唯一索引列
    username: Mapped[str] = mapped_column(String(50), unique=True, index=True)
    email: Mapped[str] = mapped_column(String(100), unique=True)

    # 普通列
    hashed_password: Mapped[str] = mapped_column(String(255))
    is_active: Mapped[bool] = mapped_column(default=True)

    # 带默认值的时间戳
    created_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)
    updated_at: Mapped[datetime | None] = mapped_column(
        default=None,
        onupdate=datetime.utcnow
    )


class Article(Base):
    """文章模型"""
    __tablename__ = "articles"

    id: Mapped[int] = mapped_column(primary_key=True)
    title: Mapped[str] = mapped_column(String(200))
    content: Mapped[str] = mapped_column()  # TEXT类型

    # 外键关联
    author_id: Mapped[int] = mapped_column(ForeignKey("users.id"))

    created_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)


# ===== 数据库会话依赖 =====
async def get_db() -> AsyncGenerator[AsyncSession, None]:
    """
    数据库会话依赖

    使用async with确保会话正确关闭
    使用try/except处理异常时的回滚
    """
    async with async_session() as session:
        try:
            yield session
            await session.commit()
        except Exception:
            await session.rollback()
            raise

7.2 CRUD操作模式

组织良好的CRUD操作可以提高代码复用性和可维护性:

python 复制代码
from sqlalchemy import select, update, delete
from sqlalchemy.ext.asyncio import AsyncSession
from typing import TypeVar, Generic
from pydantic import BaseModel

# 泛型类型
ModelType = TypeVar("ModelType", bound=Base)
CreateSchemaType = TypeVar("CreateSchemaType", bound=BaseModel)
UpdateSchemaType = TypeVar("UpdateSchemaType", bound=BaseModel)


class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]):
    """
    通用CRUD基类

    提供标准的增删改查操作,子类可以继承并扩展
    """

    def __init__(self, model: type[ModelType]):
        self.model = model

    async def get(
        self,
        db: AsyncSession,
        id: int
    ) -> ModelType | None:
        """根据ID获取单个记录"""
        result = await db.execute(
            select(self.model).where(self.model.id == id)
        )
        return result.scalar_one_or_none()

    async def get_multi(
        self,
        db: AsyncSession,
        *,
        skip: int = 0,
        limit: int = 100
    ) -> list[ModelType]:
        """获取多条记录,支持分页"""
        result = await db.execute(
            select(self.model)
            .offset(skip)
            .limit(limit)
            .order_by(self.model.id.desc())
        )
        return list(result.scalars().all())

    async def create(
        self,
        db: AsyncSession,
        *,
        obj_in: CreateSchemaType
    ) -> ModelType:
        """创建新记录"""
        # 将Pydantic模型转换为字典
        obj_in_data = obj_in.model_dump()
        # 创建数据库模型实例
        db_obj = self.model(**obj_in_data)
        db.add(db_obj)
        # flush将对象持久化到数据库,但不提交事务
        await db.flush()
        # refresh获取数据库生成的值(如自增ID)
        await db.refresh(db_obj)
        return db_obj

    async def update(
        self,
        db: AsyncSession,
        *,
        db_obj: ModelType,
        obj_in: UpdateSchemaType | dict
    ) -> ModelType:
        """更新记录"""
        # 处理输入数据
        if isinstance(obj_in, dict):
            update_data = obj_in
        else:
            update_data = obj_in.model_dump(exclude_unset=True)

        # 更新对象属性
        for field, value in update_data.items():
            if hasattr(db_obj, field):
                setattr(db_obj, field, value)

        db.add(db_obj)
        await db.flush()
        await db.refresh(db_obj)
        return db_obj

    async def delete(
        self,
        db: AsyncSession,
        *,
        id: int
    ) -> bool:
        """删除记录"""
        result = await db.execute(
            delete(self.model).where(self.model.id == id)
        )
        return result.rowcount > 0


# ===== 用户CRUD =====
class UserCreate(BaseModel):
    username: str
    email: str
    password: str

class UserUpdate(BaseModel):
    username: str | None = None
    email: str | None = None
    is_active: bool | None = None


class CRUDUser(CRUDBase[User, UserCreate, UserUpdate]):
    """
    用户CRUD操作

    继承通用CRUD,添加用户特定的操作
    """

    async def get_by_email(
        self,
        db: AsyncSession,
        email: str
    ) -> User | None:
        """根据邮箱获取用户"""
        result = await db.execute(
            select(User).where(User.email == email)
        )
        return result.scalar_one_or_none()

    async def get_by_username(
        self,
        db: AsyncSession,
        username: str
    ) -> User | None:
        """根据用户名获取用户"""
        result = await db.execute(
            select(User).where(User.username == username)
        )
        return result.scalar_one_or_none()

    async def create(
        self,
        db: AsyncSession,
        *,
        obj_in: UserCreate
    ) -> User:
        """创建用户(密码需要哈希)"""
        db_obj = User(
            username=obj_in.username,
            email=obj_in.email,
            hashed_password=get_password_hash(obj_in.password)
        )
        db.add(db_obj)
        await db.flush()
        await db.refresh(db_obj)
        return db_obj

    async def authenticate(
        self,
        db: AsyncSession,
        *,
        username: str,
        password: str
    ) -> User | None:
        """验证用户凭据"""
        user = await self.get_by_username(db, username)
        if not user:
            return None
        if not verify_password(password, user.hashed_password):
            return None
        return user


# 创建CRUD实例
user_crud = CRUDUser(User)


# ===== 在路由中使用 =====
@app.post("/users/", response_model=UserResponse)
async def create_user(
    user_in: UserCreate,
    db: Annotated[AsyncSession, Depends(get_db)]
):
    """创建新用户"""
    # 检查用户名是否已存在
    existing = await user_crud.get_by_username(db, user_in.username)
    if existing:
        raise HTTPException(
            status_code=400,
            detail="用户名已被使用"
        )

    # 检查邮箱是否已存在
    existing = await user_crud.get_by_email(db, user_in.email)
    if existing:
        raise HTTPException(
            status_code=400,
            detail="邮箱已被使用"
        )

    # 创建用户
    user = await user_crud.create(db, obj_in=user_in)
    return user

8. 后台任务与WebSocket

8.1 后台任务

有些操作不需要让用户等待,比如发送邮件、生成报告等。FastAPI提供了简单的后台任务机制:

python 复制代码
from fastapi import BackgroundTasks
from typing import Annotated

def write_log(message: str):
    """
    写日志的后台任务

    这个函数会在响应发送后执行
    即使它执行很慢,也不会影响响应时间
    """
    with open("log.txt", "a") as f:
        f.write(f"{datetime.now()}: {message}\n")

async def send_email_async(email: str, subject: str, body: str):
    """
    异步发送邮件

    后台任务也可以是async函数
    """
    # 模拟发送邮件
    await asyncio.sleep(2)
    print(f"邮件已发送到 {email}")


@app.post("/users/")
async def create_user(
    user: UserCreate,
    background_tasks: BackgroundTasks,
    db: Annotated[AsyncSession, Depends(get_db)]
):
    """
    创建用户并发送欢迎邮件

    邮件发送在后台进行,用户无需等待
    """
    # 创建用户
    new_user = await user_crud.create(db, obj_in=user)

    # 添加后台任务
    # 注意:这只是添加到队列,不会立即执行
    background_tasks.add_task(
        send_email_async,
        email=user.email,
        subject="欢迎加入!",
        body=f"你好 {user.username},欢迎使用我们的服务!"
    )

    # 添加多个后台任务
    background_tasks.add_task(write_log, f"新用户注册: {user.username}")

    # 立即返回响应,后台任务稍后执行
    return new_user

后台任务的限制

后台任务适合简单、快速的操作。对于以下情况,应该使用专门的任务队列(如Celery):

  • 执行时间很长(几分钟甚至几小时)
  • 需要重试机制
  • 需要任务状态追踪
  • 需要在多个服务器间分布执行

8.2 WebSocket实时通信

WebSocket提供了全双工通信能力,适合聊天、通知、实时数据等场景:

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

class ConnectionManager:
    """
    WebSocket连接管理器

    职责:
    - 维护活跃连接列表
    - 管理连接的加入和断开
    - 提供广播和定向发送能力
    """

    def __init__(self):
        # 按房间组织连接
        self.active_connections: Dict[str, List[WebSocket]] = {}
        # 连接到用户的映射
        self.connection_users: Dict[WebSocket, dict] = {}

    async def connect(
        self,
        websocket: WebSocket,
        room: str,
        user_info: dict
    ):
        """
        建立连接

        连接建立流程:
        1. 接受WebSocket握手
        2. 将连接添加到房间
        3. 记录用户信息
        """
        await websocket.accept()

        if room not in self.active_connections:
            self.active_connections[room] = []

        self.active_connections[room].append(websocket)
        self.connection_users[websocket] = user_info

        # 广播加入消息
        await self.broadcast_to_room(
            room,
            {
                "type": "system",
                "message": f"{user_info['username']} 加入了房间"
            },
            exclude=websocket
        )

    def disconnect(self, websocket: WebSocket, room: str):
        """断开连接"""
        if room in self.active_connections:
            self.active_connections[room].remove(websocket)
            if not self.active_connections[room]:
                del self.active_connections[room]

        user_info = self.connection_users.pop(websocket, {})
        return user_info

    async def send_personal(self, websocket: WebSocket, data: dict):
        """发送私人消息"""
        await websocket.send_json(data)

    async def broadcast_to_room(
        self,
        room: str,
        data: dict,
        exclude: WebSocket | None = None
    ):
        """
        广播消息到房间内所有连接

        exclude参数用于排除发送者自己
        """
        for connection in self.active_connections.get(room, []):
            if connection != exclude:
                try:
                    await connection.send_json(data)
                except:
                    # 连接可能已断开
                    pass

    def get_room_users(self, room: str) -> List[str]:
        """获取房间内的用户列表"""
        users = []
        for conn in self.active_connections.get(room, []):
            if conn in self.connection_users:
                users.append(self.connection_users[conn]['username'])
        return users


manager = ConnectionManager()


@app.websocket("/ws/chat/{room}")
async def websocket_endpoint(
    websocket: WebSocket,
    room: str,
    token: str  # 通过查询参数传递token
):
    """
    聊天室WebSocket端点

    连接URL示例: ws://localhost:8000/ws/chat/general?token=xxx
    """
    # 验证token
    try:
        user = await verify_token(token)
    except Exception:
        await websocket.close(code=4001, reason="认证失败")
        return

    user_info = {"username": user.username, "id": user.id}

    # 建立连接
    await manager.connect(websocket, room, user_info)

    # 发送房间信息
    await manager.send_personal(websocket, {
        "type": "room_info",
        "room": room,
        "users": manager.get_room_users(room)
    })

    try:
        while True:
            # 接收消息
            data = await websocket.receive_json()

            # 处理不同类型的消息
            msg_type = data.get("type", "message")

            if msg_type == "message":
                # 广播聊天消息
                await manager.broadcast_to_room(room, {
                    "type": "message",
                    "username": user.username,
                    "content": data.get("content", ""),
                    "timestamp": datetime.now().isoformat()
                })

            elif msg_type == "typing":
                # 广播正在输入状态
                await manager.broadcast_to_room(room, {
                    "type": "typing",
                    "username": user.username
                }, exclude=websocket)

    except WebSocketDisconnect:
        # 处理断开连接
        user_info = manager.disconnect(websocket, room)
        await manager.broadcast_to_room(room, {
            "type": "system",
            "message": f"{user_info.get('username', 'Unknown')} 离开了房间"
        })

9. 测试策略

9.1 测试的重要性

好的测试是高质量软件的基石。FastAPI的设计让测试变得非常简单:

python 复制代码
# test_main.py
from fastapi.testclient import TestClient
from main import app

# TestClient使用requests库,提供同步的测试接口
client = TestClient(app)


class TestUserAPI:
    """用户API测试"""

    def test_create_user_success(self):
        """测试成功创建用户"""
        response = client.post(
            "/users/",
            json={
                "username": "testuser",
                "email": "test@example.com",
                "password": "TestPass123"
            }
        )

        assert response.status_code == 200
        data = response.json()
        assert data["username"] == "testuser"
        assert data["email"] == "test@example.com"
        assert "password" not in data  # 确保密码不返回
        assert "id" in data  # 确保返回了ID

    def test_create_user_invalid_email(self):
        """测试邮箱格式无效"""
        response = client.post(
            "/users/",
            json={
                "username": "testuser",
                "email": "invalid-email",  # 无效邮箱
                "password": "TestPass123"
            }
        )

        assert response.status_code == 422  # 验证错误
        error = response.json()
        assert "detail" in error

    def test_create_user_weak_password(self):
        """测试弱密码"""
        response = client.post(
            "/users/",
            json={
                "username": "testuser",
                "email": "test@example.com",
                "password": "weak"  # 太短
            }
        )

        assert response.status_code == 422

    def test_get_user_not_found(self):
        """测试用户不存在"""
        response = client.get("/users/99999")
        assert response.status_code == 404


class TestAuthentication:
    """认证测试"""

    def test_login_success(self):
        """测试登录成功"""
        # 先创建用户
        client.post("/users/", json={
            "username": "loginuser",
            "email": "login@example.com",
            "password": "TestPass123"
        })

        # 登录
        response = client.post(
            "/token",
            data={  # OAuth2使用form data
                "username": "loginuser",
                "password": "TestPass123"
            }
        )

        assert response.status_code == 200
        data = response.json()
        assert "access_token" in data
        assert data["token_type"] == "bearer"

    def test_login_wrong_password(self):
        """测试密码错误"""
        response = client.post(
            "/token",
            data={
                "username": "loginuser",
                "password": "WrongPassword"
            }
        )

        assert response.status_code == 401

    def test_protected_route_without_token(self):
        """测试未认证访问受保护路由"""
        response = client.get("/users/me")
        assert response.status_code == 401

    def test_protected_route_with_token(self):
        """测试认证后访问受保护路由"""
        # 登录获取token
        login_response = client.post(
            "/token",
            data={"username": "loginuser", "password": "TestPass123"}
        )
        token = login_response.json()["access_token"]

        # 使用token访问
        response = client.get(
            "/users/me",
            headers={"Authorization": f"Bearer {token}"}
        )

        assert response.status_code == 200
        assert response.json()["username"] == "loginuser"

9.2 依赖注入覆盖

测试时经常需要替换真实依赖,比如使用测试数据库:

python 复制代码
import pytest
from fastapi.testclient import TestClient
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.pool import StaticPool

from main import app, get_db, Base

# ===== 测试数据库配置 =====
# 使用内存SQLite,每次测试都是干净的
TEST_DATABASE_URL = "sqlite:///:memory:"

engine = create_engine(
    TEST_DATABASE_URL,
    connect_args={"check_same_thread": False},
    poolclass=StaticPool  # 保持单一连接
)

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


@pytest.fixture(scope="function")
def db_session():
    """
    数据库会话fixture

    每个测试函数都会:
    1. 创建全新的表
    2. 获得独立的数据库会话
    3. 测试结束后删除所有表
    """
    # 创建表
    Base.metadata.create_all(bind=engine)

    session = TestingSessionLocal()
    try:
        yield session
    finally:
        session.close()
        # 清理
        Base.metadata.drop_all(bind=engine)


@pytest.fixture(scope="function")
def client(db_session):
    """
    测试客户端fixture

    覆盖get_db依赖,使用测试数据库
    """
    def override_get_db():
        try:
            yield db_session
        finally:
            pass

    # 覆盖依赖
    app.dependency_overrides[get_db] = override_get_db

    with TestClient(app) as c:
        yield c

    # 清理覆盖
    app.dependency_overrides.clear()


def test_create_and_get_user(client):
    """
    集成测试:创建用户并获取

    使用覆盖后的测试数据库
    """
    # 创建
    response = client.post("/users/", json={
        "username": "newuser",
        "email": "new@example.com",
        "password": "Pass123456"
    })
    assert response.status_code == 200
    user_id = response.json()["id"]

    # 获取
    response = client.get(f"/users/{user_id}")
    assert response.status_code == 200
    assert response.json()["username"] == "newuser"

10. 生产部署与性能优化

10.1 部署架构

生产环境的典型部署架构:

复制代码
                    ┌─────────────────┐
                    │   Nginx/Caddy   │  反向代理、SSL终止、静态文件
                    └────────┬────────┘
                             │
              ┌──────────────┼──────────────┐
              │              │              │
        ┌─────┴─────┐  ┌─────┴─────┐  ┌─────┴─────┐
        │ Gunicorn  │  │ Gunicorn  │  │ Gunicorn  │  进程管理
        │  Worker   │  │  Worker   │  │  Worker   │
        └─────┬─────┘  └─────┬─────┘  └─────┬─────┘
              │              │              │
        ┌─────┴─────┐  ┌─────┴─────┐  ┌─────┴─────┐
        │  Uvicorn  │  │  Uvicorn  │  │  Uvicorn  │  ASGI服务器
        │ (FastAPI) │  │ (FastAPI) │  │ (FastAPI) │
        └───────────┘  └───────────┘  └───────────┘

Uvicorn配置

python 复制代码
# run.py
import uvicorn
from main import app

if __name__ == "__main__":
    uvicorn.run(
        "main:app",
        host="0.0.0.0",
        port=8000,
        workers=4,              # 工作进程数
        loop="uvloop",          # 更快的事件循环
        http="httptools",       # 更快的HTTP解析器
        log_level="info",
        access_log=True,
        reload=False,           # 生产环境禁用
        proxy_headers=True,     # 信任代理头
    )

Gunicorn + Uvicorn

bash 复制代码
# gunicorn.conf.py
bind = "0.0.0.0:8000"
workers = 4                    # 通常为 CPU核心数 * 2 + 1
worker_class = "uvicorn.workers.UvicornWorker"
timeout = 120                  # 请求超时
keepalive = 5                  # Keep-alive超时
max_requests = 1000            # Worker处理多少请求后重启
max_requests_jitter = 50       # 添加随机性,避免所有worker同时重启

# 启动命令
# gunicorn main:app -c gunicorn.conf.py

10.2 Docker部署

dockerfile 复制代码
# Dockerfile
FROM python:3.11-slim

# 设置工作目录
WORKDIR /app

# 安装系统依赖
RUN apt-get update && apt-get install -y \
    gcc \
    libpq-dev \
    && rm -rf /var/lib/apt/lists/*

# 复制依赖文件
COPY requirements.txt .

# 安装Python依赖
RUN pip install --no-cache-dir -r requirements.txt

# 复制应用代码
COPY . .

# 创建非root用户(安全最佳实践)
RUN useradd -m appuser && chown -R appuser:appuser /app
USER appuser

# 暴露端口
EXPOSE 8000

# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:8000/health || exit 1

# 启动命令
CMD ["gunicorn", "main:app", \
     "--workers", "4", \
     "--worker-class", "uvicorn.workers.UvicornWorker", \
     "--bind", "0.0.0.0:8000", \
     "--access-logfile", "-", \
     "--error-logfile", "-"]

10.3 性能优化

python 复制代码
from fastapi import FastAPI
from fastapi.responses import ORJSONResponse

# 1. 使用更快的JSON序列化器
# orjson比标准json快3-10倍
app = FastAPI(default_response_class=ORJSONResponse)

# 2. 启用Gzip压缩
from fastapi.middleware.gzip import GZipMiddleware
app.add_middleware(GZipMiddleware, minimum_size=1000)

# 3. 数据库连接池优化
engine = create_async_engine(
    DATABASE_URL,
    pool_size=20,           # 根据并发需求调整
    max_overflow=10,        # 突发流量时额外的连接
    pool_timeout=30,        # 等待可用连接的超时时间
    pool_recycle=1800,      # 连接回收时间(防止数据库断开)
    pool_pre_ping=True,     # 使用前检查连接有效性
)

# 4. 响应缓存(使用Redis)
from fastapi_cache import FastAPICache
from fastapi_cache.backends.redis import RedisBackend
from fastapi_cache.decorator import cache

@app.on_event("startup")
async def startup():
    redis = await aioredis.from_url("redis://localhost")
    FastAPICache.init(RedisBackend(redis), prefix="api-cache")

@app.get("/expensive-data")
@cache(expire=60)  # 缓存60秒
async def get_expensive_data():
    # 耗时的计算或查询
    return {"data": result}

# 5. 批量操作代替循环
@app.post("/users/batch")
async def create_users_batch(
    users: list[UserCreate],
    db: AsyncSession = Depends(get_db)
):
    # 批量插入
    db_users = [User(**u.model_dump()) for u in users]
    db.add_all(db_users)
    await db.commit()
    return {"created": len(db_users)}

总结

核心要点回顾

  1. 异步编程:理解async/await的本质,正确区分I/O密集和CPU密集操作

  2. 类型系统:充分利用Python类型注解和Pydantic,让代码更安全、文档更完善

  3. 依赖注入:这是FastAPI的核心设计,用于代码复用、测试便利、关注点分离

  4. 中间件:处理横切关注点,如日志、CORS、限流

  5. 安全认证:实现OAuth2、JWT,保护API安全

  6. 数据库:使用异步SQLAlchemy,正确管理连接和事务

  7. 测试:编写全面的测试,使用依赖覆盖隔离外部依赖

  8. 部署:使用Docker容器化,Gunicorn管理进程,注意性能优化

最佳实践清单

  • 使用类型注解和Pydantic模型验证数据
  • 正确区分async def和def的使用场景
  • 使用依赖注入管理数据库、认证等
  • 实现统一的异常处理和响应格式
  • 编写测试覆盖核心功能
  • 使用环境变量管理配置,不要硬编码敏感信息
  • 实现日志和监控
  • 使用Docker容器化部署
  • 配置HTTPS和安全响应头
  • 实现速率限制保护API

参考资源

相关推荐
发哥来了2 小时前
【AI视频创作】【评测】【核心能力与成本效益】
大数据·人工智能
Sarvartha2 小时前
LangChain 入门核心知识学习笔记
笔记·学习·langchain
chinesegf2 小时前
Ubuntu 安装 Python 虚拟环境:常见问题与解决指南
linux·python·ubuntu
醉舞经阁半卷书12 小时前
Python机器学习常用库快速精通
人工智能·python·深度学习·机器学习·数据挖掘·数据分析·scikit-learn
hoiii1872 小时前
16APSK/32APSK调制解调MATLAB仿真实现
开发语言·matlab·fpga开发
feifeigo1233 小时前
基于MATLAB的情感语音模板培训与识别实现方案
开发语言·matlab
JH30733 小时前
Java Spring中@AllArgsConstructor注解引发的依赖注入异常解决
java·开发语言·spring
码农水水3 小时前
米哈游Java面试被问:机器学习模型的在线服务和A/B测试
java·开发语言·数据库·spring boot·后端·机器学习·word
产品何同学3 小时前
在线问诊医疗APP如何设计?2套原型拆解与AI生成原型图实战
人工智能·产品经理·健康医疗·在线问诊·app原型·ai生成原型图·医疗app