FastAPI 依赖注入 + 中间件详解(完整技术文档)
本文基于你提供的代码与注释,系统化、结构化、深入原理地讲解 FastAPI 的两个核心机制:依赖注入 & 中间件
一、依赖注入系统:让代码更具复用性
核心思想: 将"通用功能"抽象为"可重用的组件",由 FastAPI 自动注入到路径操作函数中。
什么是依赖注入?
- 定义 :依赖注入(Dependency Injection, DI)是一种设计模式,用于将对象的依赖关系外部化,由框架(如 FastAPI)在运行时自动提供。
- 好处 :
- 避免代码重复
- 提高模块化与可维护性
- 易于测试、扩展与复用
举例:分页参数
skip和limit在多个接口中都用到,可以用依赖项统一管理。
依赖注入的三大步骤
| 步骤 | 操作 | 说明 |
|---|---|---|
| 1. 创建依赖项 | 定义函数或类 | 实现公共逻辑(如分页、认证、数据库会话) |
2. 导入 Depends |
from fastapi import Depends |
启用依赖注射系统 |
| 3. 声明依赖项 | 使用 Depends(函数名) |
注入到路径操作函数参数中 |
实战:分页参数依赖项(通用逻辑复用)
python
from fastapi import Depends, FastAPI, Query
# 1. 创建依赖项:提取通用逻辑
async def common_parameters(
skip: int = Query(0, ge=0, description="跳过多少条"),
limit: int = Query(10, le=100, description="返回条数上限")
):
return {"skip": skip, "limit": limit}
app = FastAPI()
# 3. 声明依赖项:在路径操作函数中使用
@app.get("/news/news_list")
async def get_news_list(commons: dict = Depends(common_parameters)):
return commons
@app.get("/users/users_list")
async def get_users_list(commons: dict = Depends(common_parameters)):
return commons
@app.get("/products/product_list")
async def get_product_list(commons: dict = Depends(common_parameters)):
return commons
客户端请求示例:
GET /news/news_list?skip=20&limit=5
返回结果:
json
{"skip": 20, "limit": 5}
执行流程图(必看)
客户端请求 → FastAPI 路由匹配 → 检测到 Depends(common_parameters)
↓
调用 common_parameters(skip=20, limit=5) → 返回字典
↓
注入到 commons 参数
↓
执行 get_news_list() → 返回数据
↓
响应返回客户端
依赖注入的本质:自动注入 + 类型推断
- FastAPI 会自动根据函数参数的 类型注解 来决定如何注入。
- 例如:
request: Request、db: AsyncSession、commons: dict,框架会自动填入对应对象。 - 双向一体化:参数定义 + 类型提示 = 自动注入
重要提醒:
- 依赖项必须是 可调用对象 (函数或类
__call__方法)- 依赖项可以是 异步函数 (
async def)- 依赖项可以返回任意类型(dict、model、session 等)
最佳实践建议
| 建议 | 说明 |
|---|---|
使用 Query() 设置校验规则 |
如 ge=0、le=100 防止非法参数 |
使用 description 提供 API 文档说明 |
自动生成 Swagger 文档,提升可读性 |
优先使用 Pydantic 模型作为依赖项 |
更安全、更结构化(如 BookCreate) |
| 依赖项应无副作用 | 不应修改全局状态或执行耗时操作 |
| 支持缓存的依赖项 | 如 session、cache、auth_user,可声明为全局依赖 |
二、中间件(Middleware):全局拦截器
核心思想: 在请求进入路由前、响应返回客户端前,插入预处理与后处理逻辑。
中间件的作用
| 作用 | 说明 |
|---|---|
| 日志记录 | 记录请求 URL、IP、耗时 |
| 身份认证 | 检查 Token 是否有效 |
| 请求/响应过滤 | 改写 Header、Body、状态码 |
| 错误处理 | 捕获异常,返回统一错误码 |
| 性能分析 | 统计 API 响应时间 |
| 缓存控制 | 检查是否可缓存,返回缓存数据 |
中间件的调用机制
客户端 → 中间件1(请求) → 中间件2(请求) → 路由函数 → 中间件2(响应) → 中间件1(响应) → 客户端
执行顺序:从上往下进入 → 从下往上返回
实战:定义两个中间件
python
from fastapi import FastAPI, Request
app = FastAPI()
@app.middleware("http")
async def middleware1(request: Request, call_next):
print("🔧 请求进入中间件1:开始") # 执行前
response = await call_next(request) # 调用下一个处理
print("🔧 响应进入中间件1:结束")
return response
@app.middleware("http")
async def middleware2(request: Request, call_next):
print("🔧 请求进入中间件2:开始")
response = await call_next(request)
print("🔧 响应进入中间件2:结束")
return response
@app.get("/")
async def root():
return {"message": "Hello World"}
访问
http://localhost:8000控制台输出:
🔧 请求进入中间件1:开始
🔧 请求进入中间件2:开始
🔧 响应进入中间件2:结束
🔧 响应进入中间件1:结束
执行顺序:
1 → 2 → 路由 → 2 → 1
关键术语解释
| 术语 | 说明 |
|---|---|
request: Request |
FastAPI 的请求对象,包含 headers、path、cookies 等信息 |
call_next(request) |
继续执行下一个中间件或路由函数 |
response |
由 call_next 返回的响应对象,可被修改后返回 |
中间件的最佳实践
| 建议 | 说明 |
|---|---|
使用 async def 定义中间件 |
因为 FastAPI 是异步框架 |
优先使用 await call_next(request) |
否则不会进入路由 |
| 避免阻塞操作 | 如同步 I/O,否则影响并发量 |
| 把日志/认证放到中间件里 | 便于统一管理 |
| 核心逻辑不要放进中间件 | 中间件应专注"拦截"而非"业务逻辑" |