106 lines
2.9 KiB
Python
106 lines
2.9 KiB
Python
|
|
"""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}"
|