geo/backend/app/services/cache.py

106 lines
3.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Redis 缓存服务层。
提供统一的缓存读写接口,供各 API 端点使用:
- 品牌列表TTL: 5 分钟)
- 仪表盘统计数据TTL: 2 分钟)
- 用户配置信息TTL: 10 分钟)
"""
import json
import logging
import redis.asyncio as aioredis
from app.config import settings
logger = logging.getLogger(__name__)
# TTL 常量(秒)
TTL_BRANDS = 300 # 5 分钟
TTL_DASHBOARD = 120 # 2 分钟
TTL_USER_PROFILE = 600 # 10 分钟
class CacheService:
"""异步 Redis 缓存服务。"""
def __init__(self) -> None:
self._redis: aioredis.Redis | None = None
@property
def redis(self) -> aioredis.Redis:
if self._redis is None:
self._redis = aioredis.from_url(
settings.REDIS_URL,
encoding="utf-8",
decode_responses=True,
)
return self._redis
async def get(self, key: str) -> str | None:
"""从缓存读取字符串值,不存在或出错时返回 None。"""
try:
return await self.redis.get(key)
except Exception as exc:
logger.warning("Cache GET failed for key=%s: %s", key, exc)
return None
async def get_json(self, key: str) -> dict | list | str | int | float | bool | None:
"""从缓存读取并反序列化 JSON 值。"""
raw = await self.get(key)
if raw is None:
return None
try:
return json.loads(raw)
except json.JSONDecodeError:
return None
async def set(self, key: str, value: str, expire: int = 300) -> None:
"""写入缓存字符串值expire 单位为秒。"""
try:
await self.redis.set(key, value, ex=expire)
except Exception as exc:
logger.warning("Cache SET failed for key=%s: %s", key, exc)
async def set_json(self, key: str, value: dict | list | str | int | float | bool, expire: int = 300) -> None:
"""序列化为 JSON 后写入缓存。"""
try:
await self.set(key, json.dumps(value, default=str), expire=expire)
except Exception as exc:
logger.warning("Cache SET_JSON failed for key=%s: %s", key, exc)
async def delete(self, key: str) -> None:
"""删除指定缓存键。"""
try:
await self.redis.delete(key)
except Exception as exc:
logger.warning("Cache DELETE failed for key=%s: %s", key, exc)
async def invalidate_pattern(self, pattern: str) -> int:
"""批量删除匹配 pattern 的所有缓存键,返回删除数量。"""
try:
keys = await self.redis.keys(pattern)
if keys:
return await self.redis.delete(*keys)
return 0
except Exception as exc:
logger.warning("Cache INVALIDATE_PATTERN failed for pattern=%s: %s", pattern, exc)
return 0
async def close(self) -> None:
"""关闭 Redis 连接。"""
if self._redis is not None:
await self._redis.aclose()
self._redis = None
# 模块级单例(懒加载,应用启动后自动创建连接池)
_cache_service: CacheService | None = None
def get_cache_service() -> CacheService:
"""获取全局缓存服务单例。"""
global _cache_service
if _cache_service is None:
_cache_service = CacheService()
return _cache_service