""" 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)