FastAPI 进阶 — 中间件与依赖注入
一句话说明
FastAPI 的中间件拦截所有请求做横切处理(日志/限流/鉴权),依赖注入(Depends)把公共逻辑(数据库连接/当前用户)自动传给路由函数,是构建生产级 API 的关键技巧。
安装与配置
# pip 安装
pip install "fastapi[standard]" # 含 uvicorn,当前 0.115+
# 启动开发服务器
uvicorn main:app --reload # 热重载模式
中间件
基础中间件
from fastapi import FastAPI, Request
from starlette.middleware.base import BaseHTTPMiddleware
import time
app = FastAPI()
# 自定义中间件:记录请求耗时
class TimingMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
start = time.time() # 记录开始时间
response = await call_next(request) # 调用下一层(真正的路由)
duration = time.time() - start # 计算耗时
response.headers["X-Process-Time"] = f"{duration:.3f}s" # 加到响应头
return response
app.add_middleware(TimingMiddleware) # 注册中间件
# CORS 中间件(允许前端跨域请求)
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins = ["http://localhost:3000"], # 允许的前端域名
allow_credentials = True,
allow_methods = ["*"], # 允许所有 HTTP 方法
allow_headers = ["*"],
)
限流中间件
from fastapi import FastAPI, Request, HTTPException
from collections import defaultdict
import time
app = FastAPI()
# 简单的 IP 限流(生产用 slowapi 或 redis)
request_counts = defaultdict(list) # IP -> 请求时间戳列表
class RateLimitMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
client_ip = request.client.host # 获取客户端 IP
now = time.time()
window = 60 # 时间窗口:60秒
max_reqs = 100 # 最多 100 次请求
# 清除窗口外的记录
request_counts[client_ip] = [
t for t in request_counts[client_ip] if now - t < window
]
if len(request_counts[client_ip]) >= max_reqs:
raise HTTPException(429, "请求太频繁,请稍后重试")
request_counts[client_ip].append(now) # 记录本次请求
return await call_next(request)
app.add_middleware(RateLimitMiddleware)
依赖注入(Depends)
基本依赖
from fastapi import FastAPI, Depends, HTTPException, Header
from typing import Annotated
app = FastAPI()
# 定义依赖函数
def get_db():
"""数据库连接依赖(用完自动关闭)"""
db = {"connection": "fake_db_connection"} # 实际用 SQLAlchemy Session
try:
yield db # yield 前:建立连接
finally:
pass # yield 后:关闭连接(放在 finally)
def verify_token(x_token: Annotated[str, Header()]) -> str:
"""从 Header 验证 API Token"""
if x_token != "secret-token-123":
raise HTTPException(401, "Token 无效")
return x_token
# 在路由中使用依赖
@app.get("/patients/{patient_id}")
async def get_patient(
patient_id: int,
db: Annotated[dict, Depends(get_db)], # 注入数据库
token: Annotated[str, Depends(verify_token)], # 注入鉴权
):
# 此时 db 和 token 都已准备好
return {"patient_id": patient_id, "db": db["connection"]}
路由级权限控制
from fastapi import FastAPI, Depends, Security
# 定义当前用户依赖(从 JWT 解析)
async def get_current_user(token: Annotated[str, Header(alias="authorization")]):
# 实际应解析 JWT
if token == "Bearer admin-token":
return {"user_id": 1, "role": "admin"}
elif token == "Bearer user-token":
return {"user_id": 2, "role": "user"}
raise HTTPException(401, "未登录")
def require_admin(user = Depends(get_current_user)):
"""只允许管理员访问的依赖"""
if user["role"] != "admin":
raise HTTPException(403, "权限不足,需要管理员权限")
return user
@app.delete("/patients/{pid}", dependencies=[Depends(require_admin)])
async def delete_patient(pid: int):
return {"message": f"患者 {pid} 已删除"}
# 路由组级别应用依赖
from fastapi import APIRouter
admin_router = APIRouter(prefix="/admin", dependencies=[Depends(require_admin)])
常见报错与解决
| 报错 | 原因 | 解决 |
|---|
422 Unprocessable Entity | 请求参数格式错 | 检查 Header/Body 字段名和类型 |
| 依赖不执行 | 忘记 Depends() 包装 | 确保用 Annotated[T, Depends(func)] |
| 中间件顺序问题 | 中间件执行顺序是逆序注册 | 先 add 的后执行,注意 CORS 要最先 add |
速查表
| 操作 | 代码 |
|---|
| 自定义中间件 | 继承 BaseHTTPMiddleware,实现 dispatch |
| 注册中间件 | app.add_middleware(MyMiddleware) |
| CORS | app.add_middleware(CORSMiddleware, ...) |
| 基本依赖 | Depends(my_func) |
| Header 依赖 | Annotated[str, Header()] |
| yield 依赖 | 函数中用 yield,之后为清理代码 |
| 路由级依赖 | @app.get("/path", dependencies=[Depends(...)]) |
| 路由组依赖 | APIRouter(dependencies=[Depends(...)]) |