关于 fastapi-patterns
fastapi-patterns 是提供 FastAPI 进阶应用模式指导的技能,面向处理中间件、认证、依赖注入、WebSocket 和应用生命周期等问题的开发者。
主要功能
- 01中间件选择与顺序
- 02JWT 与角色权限依赖
- 03依赖缓存与资源清理
- 04后台任务指导
- 05WebSocket 连接管理
- 06生命周期与 OpenAPI 配置
适用场景
- 01添加日志或 CORS 中间件
- 02构建 JWT 认证依赖链
- 03管理应用启动与关闭
- 04实现经过认证的 WebSocket 端点
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ hieutrtr/ ai1-skills --skill fastapi-patterns安装后在 Claude Code 中输入/fastapi-patterns确认已加载。
附带文件references/dependency-injection-patterns.mdreferences/middleware-examples.md
详细介绍
FastAPI 模式
使用时机
在以下情况下启用此技能:
- 配置 FastAPI 中间件(CORS、日志记录、计时、错误处理)
- 创建复杂的依赖注入链
- 实现带连接管理的 WebSocket 端点
- 自定义 OpenAPI 文档(标签、示例、弃用标记)
- 设置 JWT 身份验证和基于角色的访问依赖项
- 实现后台任务(轻量级或分布式)
- 管理应用生命周期(通过 lifespan 处理启动和关闭)
- 设置速率限制或请求节流
不要在以下情况下使用此技能:
- 基本的端点 CRUD、仓储或服务模式(请使用
python-backend-expert) - 为 FastAPI 端点编写测试(请使用
pytest-patterns) - API 契约设计或架构规划(请使用
api-design-patterns) - 架构决策(请使用
system-architecture)
说明
中间件栈
中间件按 LIFO(后进先出)顺序执行。最后添加的中间件位于最外层。
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
# Order matters: added last = executed first (outermost)
app.add_middleware(TimingMiddleware) # 3rd added = runs 1st
app.add_middleware(RequestLoggingMiddleware) # 2nd added = runs 2nd
app.add_middleware( # 1st added = runs 3rd (innermost)
CORSMiddleware,
allow_origins=["https://app.example.com"], # NEVER use "*" in production
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "PATCH", "DELETE"],
allow_headers=["Authorization", "Content-Type"],
)
ASGI 中间件(首选)
对于性能关键路径,使用纯 ASGI 中间件:
from starlette.types import ASGIApp, Receive, Scope, Send
import time
class TimingMiddleware:
def __init__(self, app: ASGIApp) -> None:
self.app = app
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
if scope["type"] != "http":
await self.app(scope, receive, send)
return
start = time.perf_counter()
await self.app(scope, receive, send)
duration = time.perf_counter() - start
# Log or record the duration
BaseHTTPMiddleware(更简单,但速度较慢)
仅在中间件需要读取或修改请求体或响应时使用:
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
from starlette.responses import Response
class RequestIdMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next) -> Response:
request_id = request.headers.get("X-Request-ID", str(uuid.uuid4()))
request.state.request_id = request_id
response = await call_next(request)
response.headers["X-Request-ID"] = request_id
return response
如何选择:
- ASGI 中间件: 性能关键,且无需读取请求体或响应体
- BaseHTTPMiddleware: 需要访问
Request/Response对象,API 更简单
身份验证依赖项
JWT 令牌验证
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/login")
async def get_current_user(
token: str = Depends(oauth2_scheme),
session: AsyncSession = Depends(get_async_session),
) -> User:
try:
payload = jwt.decode(token, settings.jwt_secret, algorithms=["HS256"])
user_id: int = payload.get("sub")
if user_id is None:
raise HTTPException(status_code=401, detail="Invalid token")
except JWTError:
raise HTTPException(status_code=401, detail="Invalid token")
user = await session.get(User, user_id)
if user is None or not user.is_active:
raise HTTPException(status_code=401, detail="User not found or inactive")
return user
基于角色的访问控制(工厂模式)
def require_role(*roles: str):
"""Factory that creates a dependency requiring specific roles."""
async def check_role(user: User = Depends(get_current_user)) -> User:
if user.role not in roles:
raise HTTPException(
status_code=403,
detail=f"Requires one of: {', '.join(roles)}",
)
return user
return check_role
# Usage in routes
@router.delete("/users/{user_id}", dependencies=[Depends(require_role("admin"))])
async def delete_user(user_id: int, ...) -> None:
...
@router.patch("/posts/{post_id}")
async def update_post(
post_id: int,
user: User = Depends(require_role("admin", "editor")),
) -> PostResponse:
...
依赖注入链
缓存行为
FastAPI 会在单个请求中缓存依赖项的结果。同一依赖项被多次调用时,会返回同一个实例:
# get_async_session is called once per request, even if used by multiple deps
async def get_user_service(session: AsyncSession = Depends(get_async_session)) -> UserService:
return UserService(session)
async def get_post_service(session: AsyncSession = Depends(get_async_session)) -> PostService:
return PostService(session) # Same session instance as user_service
要禁用缓存(每次都获取新实例),请使用 use_cache=False:
session: AsyncSession = Depends(get_async_session, use_cache=False)
Yield 依赖项(资源清理)
async def get_http_client() -> AsyncGenerator[httpx.AsyncClient, None]:
async with httpx.AsyncClient(timeout=30.0) as client:
yield client
# Client is automatically closed after the request
在测试中覆盖依赖项
# In tests
from app.main import app
from app.dependencies.auth import get_current_user
async def mock_current_user() -> User:
return User(id=1, email="[email protected]", role="admin")
app.dependency_overrides[get_current_user] = mock_current_user
后台任务
FastAPI BackgroundTasks(轻量级)
对于无需在服务重启后继续执行的任务:
from fastapi import BackgroundTasks
@router.post("/users", status_code=201)
async def create_user(
data: UserCreate,
background_tasks: BackgroundTasks,
service: UserService = Depends(get_user_service),
) -> UserResponse:
user = await service.create_user(data)
background_tasks.add_task(send_welcome_email, user.email)
return UserResponse.model_validate(user)
async def send_welcome_email(email: str) -> None:
"""Runs after the response is sent. Creates its own session."""
async with async_session_factory() as session:
async with session.begin():
# Send email, log activity, etc.
...
规则:
- 切勿在后台任务中复用请求会话——请创建新会话
- 后台任务在同一进程中运行——不重试、不持久化
- 对于需要可靠性、重试或分布式执行的任务,请使用 Celery 或类似工具
WebSocket 模式
from fastapi import WebSocket, WebSocketDisconnect
class ConnectionManager:
def __init__(self) -> None:
self.active: dict[int, list[WebSocket]] = {}
async def connect(self, user_id: int, ws: WebSocket) -> None:
await ws.accept()
self.active.setdefault(user_id, []).append(ws)
def disconnect(self, user_id: int, ws: WebSocket) -> None:
if user_id in self.active:
self.active[user_id].remove(ws)
if not self.active[user_id]:
del self.active[user_id]
async def send_to_user(self, user_id: int, message: dict) -> None:
for ws in self.active.get(user_id, []):
await ws.send_json(message)
manager = ConnectionManager()
@router.websocket("/ws")
async def websocket_endpoint(ws: WebSocket, token: str) -> None:
# Auth via query parameter: /ws?token=xxx
user = await verify_ws_token(token)
if not user:
await ws.close(code=4001)
return
await manager.connect(user.id, ws)
try:
while True:
data = await ws.receive_json()
# Process incoming messages
await handle_message(user.id, data)
except WebSocketDisconnect:
manager.disconnect(user.id, ws)
WebSocket 身份验证方式:
- 查询参数:
ws://host/ws?token=xxx(最简单,但令牌会显示在日志中) - 首条消息:连接后将令牌作为首条消息发送(更安全)
- Cookie:使用现有的会话 Cookie(要求域名相同)
应用生命周期
使用 lifespan 上下文管理器(不要使用已弃用的 on_event):
from contextlib import asynccontextmanager
from collections.abc import AsyncIterator
from fastapi import FastAPI
@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
# Startup: initialize resources
await init_database()
redis = await aioredis.from_url(settings.redis_url)
app.state.redis = redis
yield # Application runs here
# Shutdown: cleanup resources
await redis.close()
await dispose_engine()
app = FastAPI(lifespan=lifespan)
生命周期职责:
- 初始化和释放数据库连接池
- 设置和拆除 Redis/缓存连接
- 创建 HTTP 客户端连接池
- 启动和关闭后台调度器
- 启动时预热缓存
异常处理器
注册全局异常处理器,以统一错误响应:
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from fastapi.exceptions import RequestValidationError
@app.exception_handler(RequestValidationError)
async def validation_handler(request: Request, exc: RequestValidationError):
return JSONResponse(
status_code=422,
content={
"detail": "Validation error",
"code": "VALIDATION_ERROR",
"field_errors": [
{"field": e["loc"][-1], "message": e["msg"], "code": e["type"]}
for e in exc.errors()
],
},
)
OpenAPI 自定义
app = FastAPI(
title="My API",
version="1.0.0",
description="API description with **markdown** support",
openapi_tags=[
{"name": "Users", "description": "User management operations"},
{"name": "Auth", "description": "Authentication endpoints"},
],
docs_url="/docs", # Swagger UI
redoc_url="/redoc", # ReDoc
openapi_url="/openapi.json",
)
示例
JWT 身份验证依赖链
从令牌到已授权用户的完整身份验证链:
Request with Authorization: Bearer <token>
↓
oauth2_scheme (extracts token from header)
↓
get_current_user (decodes JWT, loads user from DB)
↓
require_role("admin") (checks user.role)
↓
Route handler (receives verified admin user)
链中的每个依赖项都可以通过 dependency_overrides 独立测试。
边缘情况
-
中间件与依赖项: 使用中间件处理横切关注点(日志记录、计时、CORS)。使用依赖项处理路由级逻辑(身份验证、分页参数、功能标志)。
-
ASGI 与 BaseHTTPMiddleware: 优先使用 ASGI 中间件以获得更好的性能。
BaseHTTPMiddleware会将整个响应正文读入内存,从而导致流式传输和大型响应出现问题。 -
Lifespan 与 on_event: 始终使用
lifespan上下文管理器。FastAPI 0.109+ 中的@app.on_event("startup")和@app.on_event("shutdown")已弃用。 -
跨子应用的 Depends 缓存: 依赖项缓存仅在单个应用实例内按请求生效。如果使用
app.mount()挂载子应用,每个子应用都有自己的依赖项解析范围。 -
WebSocket 扩展: 单个服务实例会持有所有 WebSocket 连接。对于多实例部署,请使用 Redis pub/sub 在各实例之间广播消息。
参见 references/middleware-examples.md,了解完整的中间件实现。
参见 references/dependency-injection-patterns.md,了解高级 DI 模式。