"""Redis 缓存工具模块。 提供 Redis 连接管理和缓存读写操作,所有操作在 Redis 不可用时静默降级。 被 OrderService、CustomerService、pricing 路由等调用。 """ import hashlib import json import logging import redis from backend.app.core.config import get_settings logger = logging.getLogger(__name__) _redis_client: redis.Redis | None = None def get_redis() -> redis.Redis | None: """获取 Redis 连接单例,连接失败时返回 None。""" global _redis_client if _redis_client is not None: return _redis_client try: settings = get_settings() _redis_client = redis.Redis( host=settings.redis_host, port=settings.redis_port, db=settings.redis_db, password=settings.redis_password or None, decode_responses=True, socket_connect_timeout=2, socket_timeout=2, ) _redis_client.ping() logger.info("Redis 连接成功: %s:%s/%s", settings.redis_host, settings.redis_port, settings.redis_db) return _redis_client except Exception as exc: logger.warning("Redis 连接失败,降级为无缓存模式: %s", exc) _redis_client = None return None def cache_get(key: str): """读取缓存,返回反序列化后的 dict/list,未命中或异常返回 None。""" try: r = get_redis() if r is None: return None raw = r.get(key) if raw is None: return None return json.loads(raw) except Exception: return None def cache_set(key: str, data, ttl: int = 60) -> None: """写入缓存,JSON 序列化后存储并设置 TTL(秒)。异常时静默跳过。""" try: r = get_redis() if r is None: return r.set(key, json.dumps(data, ensure_ascii=False, default=str), ex=ttl) except Exception: pass def cache_delete(key: str) -> None: """精确删除单个缓存 key。""" try: r = get_redis() if r is not None: r.delete(key) except Exception: pass def cache_delete_pattern(pattern: str) -> None: """按模式批量删除缓存(SCAN + DELETE),如 "order:list:*"。""" try: r = get_redis() if r is None: return cursor = 0 while True: cursor, keys = r.scan(cursor=cursor, match=pattern, count=100) if keys: r.delete(*keys) if cursor == 0: break except Exception: pass def make_cache_key(prefix: str, **params) -> str: """生成带参数哈希的缓存 key。 例: make_cache_key("order:list", user_id=1, status="draft") → "order:list:a1b2c3d4" """ raw = json.dumps(params, sort_keys=True, ensure_ascii=False, default=str) suffix = hashlib.md5(raw.encode()).hexdigest()[:8] return f"{prefix}:{suffix}"