- core/: database/redis_client/jwt/password/response/exceptions/security/logger/deps/middleware/app - create_app 迁移到 core/app.py,中间件注册顺序与异常处理器原样保留 - extensions/ utils/ apps/ 旧文件全部改为 re-export 垫片,import 路径保持兼容 - 新增 core 单元测试(jwt/password/response/request 共 12 个) Co-Authored-By: Claude <noreply@anthropic.com>
80 lines
2.4 KiB
Python
80 lines
2.4 KiB
Python
"""
|
|
Redis客户端核心模块(由 utils/redis_ctx.py 迁移而来)
|
|
|
|
封装 Redis 连接管理和请求级上下文注入:
|
|
- lifespan 中创建连接池,关闭时断开
|
|
- 每个请求通过中间件将 Redis 客户端注入 contextvars
|
|
- 业务代码通过 get_redis() 获取当前请求的 Redis 客户端
|
|
"""
|
|
|
|
import logging
|
|
from contextvars import ContextVar
|
|
from contextlib import asynccontextmanager
|
|
|
|
from fastapi import FastAPI
|
|
from redis.asyncio import Redis
|
|
from redis import asyncio as aioredis
|
|
|
|
from config import REDIS_HOST, REDIS_PORT, REDIS_PASSWORD, REDIS_INDEX, REDIS_AUTH
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# 请求级 Redis 客户端上下文变量
|
|
_redis_ctx: ContextVar[Redis] = ContextVar('_redis_ctx')
|
|
|
|
|
|
def get_redis() -> Redis:
|
|
"""获取当前请求的 Redis 客户端"""
|
|
return _redis_ctx.get()
|
|
|
|
|
|
def _build_redis_url() -> str:
|
|
"""构建 Redis 连接 URL"""
|
|
if REDIS_AUTH and REDIS_PASSWORD:
|
|
return f"redis://:{REDIS_PASSWORD}@{REDIS_HOST}:{REDIS_PORT}/{REDIS_INDEX}"
|
|
return f"redis://{REDIS_HOST}:{REDIS_PORT}/{REDIS_INDEX}"
|
|
|
|
|
|
async def create_redis_pool() -> Redis:
|
|
"""创建 Redis 异步连接池"""
|
|
redis = await aioredis.from_url(
|
|
_build_redis_url(),
|
|
encoding="utf-8",
|
|
decode_responses=True,
|
|
protocol=2
|
|
)
|
|
return redis
|
|
|
|
|
|
@asynccontextmanager
|
|
async def redis_lifespan(app: FastAPI):
|
|
"""Redis 生命周期管理,替代已废弃的 on_event("startup"/"shutdown")"""
|
|
# startup: 创建连接池
|
|
redis = await create_redis_pool()
|
|
await redis.ping()
|
|
app.state.redis = redis
|
|
print(f"Redis 连接成功 --> {REDIS_HOST}:{REDIS_PORT}/{REDIS_INDEX}")
|
|
logger.info(f"Redis 连接成功: {REDIS_HOST}:{REDIS_PORT}/{REDIS_INDEX}")
|
|
|
|
# startup: 初始化定时任务调度器
|
|
from apps.services.scheduler import init_scheduler, shutdown_scheduler
|
|
job_count = init_scheduler()
|
|
logger.info(f"定时任务调度器已启动,加载 {job_count} 个任务")
|
|
|
|
yield
|
|
# shutdown: 关闭调度器
|
|
shutdown_scheduler()
|
|
|
|
# shutdown: 关闭连接
|
|
await redis.aclose()
|
|
app.state.redis = None
|
|
logger.info("Redis 连接已关闭")
|
|
|
|
|
|
async def redis_middleware(request, call_next):
|
|
"""Redis 上下文注入中间件,每个请求开始时将 Redis 客户端注入 contextvars"""
|
|
redis = getattr(request.app.state, 'redis', None)
|
|
if redis is not None:
|
|
_redis_ctx.set(redis)
|
|
return await call_next(request)
|