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

📅 2026/7/28 0:02:42 👁️ 阅读次数 📝 编程学习
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)
1100
21010
31020
41030
@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 企业级应用。

😊