FastAPI 进阶三部曲:中间件、依赖注入与 ORM 实战

一、引言

FastAPI 作为当前 Python 领域最炙手可热的异步 Web 框架,凭借其高性能、自动生成 API 文档以及对异步的原生支持,赢得了大量开发者的青睐。然而,仅仅掌握路由和请求响应的基础用法,远不足以构建一个健壮的企业级应用。中间件(Middleware)依赖注入(Dependency Injection)ORM(对象关系映射) 正是进阶路上必须跨越的三座大山。

本文将基于 FastAPI 官方最佳实践,结合 SQLAlchemy 2.0 异步特性,从零搭建一个完整的图书管理 API,涵盖统一日志处理、用户认证依赖、以及完整的数据库增删改查、分页、聚合查询等核心功能。所有代码均可在实际项目中直接复用。

二、中间件(Middleware)------ 为所有请求添加"守门员"

2.1 什么是中间件?

中间件是一个在每次请求进入 FastAPI 应用时都会被执行的函数。它在请求到达路径操作函数之前运行,并且在响应返回给客户端之前再执行一次。你可以把它想象成一个"守门员",对所有进出请求进行统一处理。

2.2 中间件的典型应用场景

  • 记录每个请求的耗时和日志

  • 统一身份认证(虽然更推荐用依赖注入,但中间件也可做全局鉴权)

  • 跨域资源共享(CORS)配置

  • 统一添加或修改响应头(如 X-Request-ID

  • 性能监控和指标收集

2.3 如何定义一个中间件?

使用 @app.middleware("http") 装饰器,函数签名为 async def middleware(request: Request, call_next)。内部通过 await call_next(request) 调用下一个中间件或最终的路由处理函数,并获取响应对象。

复制代码
from fastapi import FastAPI, Request
import time

app = FastAPI()

@app.middleware("http")
async def log_requests(request: Request, call_next):
    # 请求前处理
    start_time = time.time()
    print(f"收到请求: {request.method} {request.url.path}")

    # 执行后续逻辑(路由处理函数或下一个中间件)
    response = await call_next(request)

    # 响应后处理
    process_time = time.time() - start_time
    response.headers["X-Process-Time"] = str(process_time)
    print(f"请求处理完成,耗时: {process_time:.4f}秒")

    return response

2.4 多个中间件的执行顺序

顺序是"自下而上" ------ 即先注册的后执行(类似洋葱模型)。例如:

复制代码
@app.middleware("http")
async def middleware_a(request, call_next):
    print("A 开始")
    response = await call_next(request)
    print("A 结束")
    return response

@app.middleware("http")
async def middleware_b(request, call_next):
    print("B 开始")
    response = await call_next(request)
    print("B 结束")
    return response

2.4.1核心结论

FastAPI 中后注册的中间件会先执行前置逻辑 ,收尾逻辑则逆序执行。 你代码里先定义 middleware_a、再定义 middleware_b

  1. 注册顺序:AB

  2. 执行打印顺序:

    B 开始
    A 开始

    路由接口业务逻辑执行

    A 结束
    B 结束

2.4.2 原理说明

FastAPI 会把多个 HTTP 中间件层层嵌套包装

复制代码
B(middleware_a(路由函数))

调用 call_next(request) 就是进入内层中间件 / 路由:

  1. 先走外层 B 前置:B 开始
  2. 进入内层 A 前置:A 开始
  3. 走到真正接口视图函数
  4. 接口返回后,先退出 A 后置:A 结束
  5. 最后退出 B 后置:B 结束

2.4.3 直观流程图

复制代码
请求进来
   ↓
middleware_b 【前置】打印 B 开始
   ↓ call_next()
middleware_a 【前置】打印 A 开始
   ↓ call_next()
路由处理函数执行业务
   ↑ 响应返回
middleware_a 【后置】打印 A 结束
   ↑ 回到上一层
middleware_b 【后置】打印 B 结束
   ↓
返回响应给客户端

三、依赖注入(Dependency Injection)------ 告别重复代码

3.1 依赖注入系统的作用

依赖注入是一种设计模式,FastAPI 内置了极其优雅的实现。它的核心思想是:将可复用的逻辑(如数据库会话、用户身份校验、配置对象)抽取为依赖项,由框架自动调用并注入到路径操作函数中

主要优势:

  • 代码复用:一次编写,多处使用

  • 解耦:业务逻辑与基础设施(数据库、缓存等)分离

  • 易于测试:可以轻松用模拟对象替换真实依赖进行单元测试

3.2 使用步骤

  1. 创建一个可调用对象(函数或类)作为依赖项

  2. 在路径操作函数参数中,使用 Depends(依赖项) 声明

  3. FastAPI 自动解析并注入依赖项的结果

3.3 实战:用户认证依赖项

假设我们有多个接口需要验证 JWT Token,并返回当前用户信息。

复制代码
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

# 模拟用户数据
fake_users_db = {
    "admin": {"username": "admin", "role": "superadmin"}
}

async def get_current_user(token: str = Depends(oauth2_scheme)):
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="无效的认证凭据",
        headers={"WWW-Authenticate": "Bearer"},
    )
    try:
        payload = jwt.decode(token, "SECRET_KEY", algorithms=["HS256"])
        username: str = payload.get("sub")
        if username is None:
            raise credentials_exception
    except JWTError:
        raise credentials_exception
    user = fake_users_db.get(username)
    if user is None:
        raise credentials_exception
    return user

# 在路由中使用
@app.get("/users/me")
async def read_users_me(current_user: dict = Depends(get_current_user)):
    return current_user

这样一来,所有需要认证的接口只需声明 current_user = Depends(get_current_user) 即可,完全避免了在每个函数里重复编写解析 Token 的代码。

四、ORM(对象关系映射)------ 用面向对象的方式操作数据库

4.1 ORM 简介

ORM 将数据库表映射为 Python 类,将表中的行映射为类的实例(对象)。开发者通过操作对象和方法,即可完成增删改查,无需手写 SQL。在 FastAPI 异步生态中,SQLAlchemy 2.0 是绝对的首选,它原生支持 async/await,性能强大,企业应用广泛。

ORM 带来的好处:

  • 减少重复的 SQL 拼接代码

  • 代码更简洁、可读性更强

  • 自动管理数据库连接和事务

  • 有效防止 SQL 注入攻击

4.2 环境准备与引擎创建

首先安装依赖:

复制代码
pip install fastapi uvicorn sqlalchemy[asyncio] aiomysql

创建异步数据库引擎(以 MySQL 为例):

复制代码
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker, declarative_base
from sqlalchemy import Column, Integer, String, DateTime, func
from datetime import datetime

ASYNC_DATABASE_URL = "mysql+aiomysql://root:123456@localhost:3306/fastapi_test?charset=utf8"

async_engine = create_async_engine(
    ASYNC_DATABASE_URL,
    echo=True,           # 打印 SQL 日志,便于调试
    pool_size=10,        # 连接池保持的持久连接数
    max_overflow=20      # 允许额外创建的连接数
)

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

4.3 定义模型基类和模型类

我们定义一个基类,包含所有表共有的 create_timeupdate_time 字段,然后为图书表创建模型。

复制代码
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from sqlalchemy import String, DateTime, func

class Base(DeclarativeBase):
    create_time: Mapped[datetime] = mapped_column(
        DateTime, insert_default=func.now(), default=datetime.now, comment="创建时间"
    )
    update_time: Mapped[datetime] = mapped_column(
        DateTime, 
        insert_default=func.now(), 
        onupdate=func.now(), 
        default=datetime.now, 
        comment="修改时间"
    )

class Book(Base):
    __tablename__ = "book"
    id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
    bookname: Mapped[str] = mapped_column(String(255), nullable=False)
    author: Mapped[str] = mapped_column(String(255), nullable=False)
    price: Mapped[float] = mapped_column(nullable=True)   # 价格字段,为后续查询演示扩展

4.4 创建数据库表(应用启动时)

利用 FastAPI 的 startup 事件,在应用启动时自动建表。

复制代码
async def create_tables():
    async with async_engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)

@app.on_event("startup")
async def startup_event():
    await create_tables()

4.5 获取数据库会话的依赖项

每次请求需要独立的事务会话,我们将其封装为依赖项,并自动管理会话的关闭。

4.5.1异步会话工厂 AsyncSessionLocal

复制代码
AsyncSessionLocal = async_sessionmaker(
    bind=async_engine,       # 绑定全局异步数据库引擎(提前初始化的async数据库连接引擎)
    class_=AsyncSession,     # 指定使用SQLAlchemy异步会话类AsyncSession
    expire_on_commit=False   # commit之后,模型对象不会过期,不用重复查库;方便查询后继续使用ORM对象
)
  • async_sessionmaker:SQLAlchemy 异步版会话工厂,用来批量创建数据库会话
  • expire_on_commit=False常用场景:查询拿到数据 commit 后,依然能正常读取模型属性,不会报lazy loading过期报错

4.5.2依赖函数 get_database(最关键)

这是 FastAPI 依赖项,每来一次 HTTP 请求,就执行一次,生成全新独立 session

复制代码
async def get_database():
    async with AsyncSessionLocal() as session:
        try:
            yield session  # 把会话给到接口函数使用(yield实现依赖的上下文暂停)
            await session.commit()  # 接口无报错:自动提交事务
        except Exception:
            await session.rollback()  # 接口抛异常:事务回滚,保证数据一致性
            raise  # 重新抛出异常,让FastAPI正常返回错误响应
        finally:
            await session.close()  # 无论成功失败,最终强制关闭会话释放连接

4.5.3路由层:Depends 注入使用

复制代码
@app.get("/book/books")
async def get_book_list(
    db: AsyncSession = Depends(get_database)
):
    # 使用注入进来的db会话执行查询
    result = await db.execute(select(Book))
    book_list = result.scalars().all()
    return book_list

五、路由中使用 ORM 与数据库操作

5.1 依赖注入数据库会话

核心:创建依赖项 get_database,返回异步数据库会话 AsyncSession,并通过 Depends 注入到路由处理函数。

复制代码
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker

# 创建异步会话工厂
AsyncSessionLocal = async_sessionmaker(async_engine, expire_on_commit=False)

async def get_database() -> AsyncSession:
    async with AsyncSessionLocal() as session:
        yield session

在路由中:

复制代码
from fastapi import Depends, FastAPI
from sqlalchemy.ext.asyncio import AsyncSession

app = FastAPI()

@app.get("/books")
async def get_books(db: AsyncSession = Depends(get_database)):
    # 使用 db 执行查询
    pass

5.2 查询操作

5.2.1 查询所有数据与单条数据

查询所有: select(模型类)db.execute()scalars().all()

复制代码
from sqlalchemy import select

@app.get("/book/get_books")
async def get_book_list(db: AsyncSession = Depends(get_database)):
    result = await db.execute(select(Book))
    books = result.scalars().all()
    return books

查询单条: 使用 scalars().first()db.get(模型类, 主键值)

复制代码
@app.get("/book/get_book")
async def get_book(db: AsyncSession = Depends(get_database)):
    # 方式一:提取第一条
    # result = await db.execute(select(Book))
    # book = result.scalars().first()
    # 方式二:通过主键获取
    book = await db.get(Book, 1)
    return book

5.2.2 查询条件

select().where() 中添加条件,支持多种比较和逻辑运算。

  • 比较判断==, >, <, >=, <=

    @app.get("/book/{book_id}")
    async def get_book_by_id(book_id: int, db: AsyncSession = Depends(get_database)):
    result = await db.execute(select(Book).where(Book.id == book_id))
    book = result.scalar_one_or_none()
    return book

  • 模糊查询like()% 匹配任意字符,_ 匹配单个字符

    @app.get("/book/get_books_by_author")
    async def get_books_by_author(db: AsyncSession = Depends(get_database)):
    result = await db.execute(select(Book).where(Book.author.like("曹%")))
    books = result.scalars().all()
    return books

  • 与非查询 :使用 &(与)、|(或)、~(非)

    @app.get("/book/get_books_filter")
    async def get_books_filter(db: AsyncSession = Depends(get_database)):
    result = await db.execute(
    select(Book).where((Book.author == "曹雪芹") & (Book.price == 200))
    )
    books = result.scalars().all()
    return books

  • 包含查询in_()

    @app.get("/book/get_books_by_ids")
    async def get_books_by_ids(db: AsyncSession = Depends(get_database)):
    id_list = [1, 2, 3, 4, 5, 6]
    result = await db.execute(select(Book).where(Book.id.in_(id_list)))
    books = result.scalars().all()
    return books

5.2.3 聚合查询

使用 func.count(), func.avg(), func.max(), func.min(), func.sum() 进行聚合。

复制代码
from sqlalchemy import func

@app.get("/book/count")
async def get_count(db: AsyncSession = Depends(get_database)):
    # result = await db.execute(select(func.count(Book.id)))
    # result = await db.execute(select(func.max(Book.price)))
    # result = await db.execute(select(func.sum(Book.price)))
    result = await db.execute(select(func.avg(Book.price)))
    count = result.scalar()
    return count

5.2.4 分页查询

使用 .offset().limit() 实现分页。offset 为跳过的记录数,limit 为每页返回的记录数。

计算公式:offset = (当前页码 - 1) * 每页数量

当前页码 每页数量(limit) 跳过数量(offset)
1 10 0
2 10 10
3 10 20
4 10 30
复制代码
@app.get("/book/get_books")
async def get_book_list(
    page: int = 1,
    page_size: int = 3,
    db: AsyncSession = Depends(get_database)
):
    skip = (page - 1) * page_size
    stmt = select(Book).offset(skip).limit(page_size)
    result = await db.execute(stmt)
    books = result.scalars().all()
    return {"books": books}

5.2.5 查询总结

  • 核心思路select()db.execute() → 从 ORM 对象获取数据 → 响应结果

  • 获取数据方式

    • scalars().all():获取所有数据

    • scalars().first():获取第一条数据

    • scalar_one_or_none():获取一个或 None(若多于一条则报错)

    • scalar():获取标量值(聚合查询常用)

5.3 新增操作

步骤:创建 ORM 对象 → db.add()await db.commit()

复制代码
from pydantic import BaseModel

class BookBase(BaseModel):
    bookname: str
    author: str
    price: float

@app.post("/book/add_book")
async def add_book(book: BookBase, db: AsyncSession = Depends(get_database)):
    book_obj = Book(**book.__dict__)  # 或 Book(bookname=book.bookname, ...)
    db.add(book_obj)
    await db.commit()
    return book_obj

5.4 更新操作

步骤:查询获取对象 → 修改属性 → commit

复制代码
class BookUpdate(BaseModel):
    bookname: str
    author: str
    price: float

@app.put("/book/update_book/{book_id}")
async def update_book(
    book_id: int,
    data: BookUpdate,
    db: AsyncSession = Depends(get_database)
):
    book = await db.get(Book, book_id)
    if book is None:
        raise HTTPException(status_code=404, detail="Book not found")
    book.bookname = data.bookname
    book.author = data.author
    book.price = data.price
    await db.commit()
    return book

5.5 删除操作

步骤:查询获取对象 → db.delete()commit

复制代码
@app.delete("/book/delete_book/{book_id}")
async def delete_book(book_id: int, db: AsyncSession = Depends(get_database)):
    db_book = await db.get(Book, book_id)
    if db_book is None:
        raise HTTPException(status_code=404, detail="Book not found")
    await db.delete(db_book)
    await db.commit()
    return {"message": "Book deleted"}

六、总结

本文围绕 FastAPI 进阶的三大核心内容展开:

  • 中间件:为每个请求/响应添加统一处理逻辑,定义简单,执行顺序自下而上。

  • 依赖注入:抽取可复用组件(如数据库会话、认证逻辑),实现解耦与易测性。

  • ORM(SQLAlchemy):通过对象方式操作数据库,避免手写 SQL,提升开发效率与安全性。

ORM 使用全流程速览:

  1. 安装依赖:pip install sqlalchemy[asyncio] aiomysql

  2. 创建异步引擎(create_async_engine

  3. 定义基类(DeclarativeBase)和模型类

  4. 启动时建表(run_sync(Base.metadata.create_all)

  5. 路由中注入会话依赖(Depends(get_database)

  6. 执行 CRUD:select()add()、属性赋值、delete(),最后 commit()

掌握这些技能,即可构建健壮、可维护的 FastAPI 企业级应用。

😊

相关推荐
MrDJun1 小时前
长期稳定跑网页监控:TLS 指纹、代理选路与请求节流的工程实践
运维·爬虫·python·网络协议·网站监控
m沐沐1 小时前
【计算机视觉】OpenCV 物体跟踪——原理、算法与CSRT跟踪器实战
人工智能·python·深度学习·opencv·算法·计算机视觉·人脸识别
大数据魔法师2 小时前
AI Agent - OpenAI从零开始完整学习教程(零基础入门+实战落地)
python
卷无止境2 小时前
Python的魔术方法:那些藏在双下划线背后的魔法
后端·python
米码收割机2 小时前
【Python】Django恒达科技门户网站(源码+文档)【独一无二】
数据库·python·科技
卷无止境2 小时前
别再被"乱码"吓到了:Python文件操作的门道
后端·python
STLearner3 小时前
ICML 2026 | LLM×Graph论文总结[2]【Graph4LLM,Graph4Agent,智能体记忆(Memory)
大数据·人工智能·python·深度学习·学习·机器学习·数据挖掘
一个王同学10 小时前
从零到一 | CV转多模态大模型 | week19 | 基于 FastAPI 和 vLLM 的多模态大模型部署
人工智能·深度学习·计算机视觉·fastapi·改行学it·vllm
像风一样自由202011 小时前
从本地到公网:Windows 下使用 Cloudflare Quick Tunnel 与 Natapp 联调 FastAPI
windows·fastapi