"""统一 Redis 连接管理。 提供全局 Redis 连接池单例,所有模块共享同一连接池, 避免多处独立创建连接导致资源浪费和连接数失控。 用法: from app.core.redis import get_redis redis = await get_redis() await redis.set("key", "value") """ import logging import redis.asyncio as aioredis from app.config import settings logger = logging.getLogger(__name__) # 全局 Redis 连接池单例 _redis: aioredis.Redis | None = None async def get_redis() -> aioredis.Redis: """获取全局 Redis 连接。 首次调用时根据 settings.REDIS_URL 创建连接池, 后续调用返回同一实例。REDIS_URL 为空时抛出 ValueError。 """ global _redis if _redis is None: if not settings.REDIS_URL: raise ValueError("REDIS_URL is not configured") _redis = aioredis.from_url( settings.REDIS_URL, encoding="utf-8", decode_responses=True, ) logger.info("Redis connection pool created (url=%s)", _safe_url(settings.REDIS_URL)) return _redis async def close_redis() -> None: """关闭全局 Redis 连接。在应用 shutdown 时调用。""" global _redis if _redis is not None: await _redis.aclose() _redis = None logger.info("Redis connection pool closed") def is_redis_configured() -> bool: """检查 Redis 是否已配置(REDIS_URL 非空)。""" return bool(settings.REDIS_URL) def _safe_url(url: str) -> str: """隐藏 Redis URL 中的密码部分。""" if "@" in url: # redis://:password@host:port/db → redis://***@host:port/db parts = url.split("@", 1) prefix = parts[0].rsplit(":", 1)[0] return f"{prefix}:***@{parts[1]}" return url