first commit

This commit is contained in:
rayd1o
2026-03-05 11:46:58 +08:00
commit e7033775d8
20657 changed files with 1988940 additions and 0 deletions

128
backend/app/core/cache.py Normal file
View File

@@ -0,0 +1,128 @@
"""Redis caching service"""
import json
import logging
from datetime import timedelta
from typing import Optional, Any
import redis
from app.core.config import settings
logger = logging.getLogger(__name__)
# Lazy Redis client initialization
class _RedisClient:
_client = None
@classmethod
def get_client(cls):
if cls._client is None:
# Parse REDIS_URL or use default
redis_url = settings.REDIS_URL
if redis_url.startswith("redis://"):
cls._client = redis.from_url(redis_url, decode_responses=True)
else:
cls._client = redis.Redis(
host=settings.REDIS_SERVER,
port=settings.REDIS_PORT,
db=settings.REDIS_DB,
decode_responses=True,
)
return cls._client
class CacheService:
"""Redis caching service with JSON serialization"""
def __init__(self):
self.client = _RedisClient.get_client()
def get(self, key: str) -> Optional[Any]:
"""Get value from cache"""
try:
value = self.client.get(key)
if value:
return json.loads(value)
return None
except Exception as e:
logger.warning(f"Cache get error: {e}")
return None
def set(
self,
key: str,
value: Any,
expire_seconds: int = 300,
) -> bool:
"""Set value in cache with expiration"""
try:
serialized = json.dumps(value, default=str)
return self.client.setex(key, expire_seconds, serialized)
except Exception as e:
logger.warning(f"Cache set error: {e}")
return False
def delete(self, key: str) -> bool:
"""Delete key from cache"""
try:
return self.client.delete(key) > 0
except Exception as e:
logger.warning(f"Cache delete error: {e}")
return False
def delete_pattern(self, pattern: str) -> int:
"""Delete all keys matching pattern"""
try:
keys = self.client.keys(pattern)
if keys:
return self.client.delete(*keys)
return 0
except Exception as e:
logger.warning(f"Cache delete_pattern error: {e}")
return 0
def get_or_set(
self,
key: str,
fallback: callable,
expire_seconds: int = 300,
) -> Optional[Any]:
"""Get value from cache or set it using fallback"""
value = self.get(key)
if value is not None:
return value
value = fallback()
if value is not None:
self.set(key, value, expire_seconds)
return value
def invalidate_pattern(self, pattern: str) -> int:
"""Invalidate all keys matching pattern"""
return self.delete_pattern(pattern)
cache = CacheService()
def cached(expire_seconds: int = 300, key_prefix: str = ""):
"""Decorator for caching function results"""
def decorator(func):
async def wrapper(*args, **kwargs):
cache_key = f"{key_prefix}:{func.__name__}:{args}:{kwargs}"
cache_key = cache_key.replace(":", "_").replace(" ", "")
cached_value = cache.get(cache_key)
if cached_value is not None:
return cached_value
result = await func(*args, **kwargs)
cache.set(cache_key, result, expire_seconds)
return result
return wrapper
return decorator