feat(phase5): integracion auth hibrida, email dinamico y moodle 4.1 backend con tolerancia a fallos
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
import time
|
||||
import json
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class CacheService:
|
||||
"""
|
||||
Servicio de caché híbrido para sesiones y lecturas rápidas de Moodle.
|
||||
Soporta Redis si está disponible en la infraestructura y hace fallback transparente
|
||||
a caché en memoria con TTL para entornos de desarrollo y pruebas.
|
||||
"""
|
||||
_memory_cache = {}
|
||||
_redis_client = None
|
||||
_redis_checked = False
|
||||
|
||||
@classmethod
|
||||
def _get_redis(cls):
|
||||
if not cls._redis_checked:
|
||||
cls._redis_checked = True
|
||||
try:
|
||||
import redis
|
||||
from app.models.setting import SystemSetting
|
||||
redis_url = SystemSetting.get_value('redis_url', 'redis://127.0.0.1:6379/0')
|
||||
client = redis.from_url(redis_url, socket_connect_timeout=2)
|
||||
client.ping()
|
||||
cls._redis_client = client
|
||||
logger.info("Conexión exitosa a Redis en %s", redis_url)
|
||||
except Exception as e:
|
||||
cls._redis_client = None
|
||||
logger.info("Redis no disponible (%s). Operando con memoria local/TTL.", str(e).split('\n')[0])
|
||||
return cls._redis_client
|
||||
|
||||
@classmethod
|
||||
def get(cls, key: str, default=None):
|
||||
r = cls._get_redis()
|
||||
if r:
|
||||
try:
|
||||
val = r.get(key)
|
||||
if val is not None:
|
||||
return json.loads(val.decode('utf-8'))
|
||||
except Exception as e:
|
||||
logger.debug("Error leyendo de Redis: %s", e)
|
||||
|
||||
# Fallback memoria
|
||||
item = cls._memory_cache.get(key)
|
||||
if item:
|
||||
val, expire_at = item
|
||||
if expire_at is None or expire_at > time.time():
|
||||
return val
|
||||
else:
|
||||
del cls._memory_cache[key]
|
||||
return default
|
||||
|
||||
@classmethod
|
||||
def set(cls, key: str, value, ttl_seconds: int = 300):
|
||||
r = cls._get_redis()
|
||||
if r:
|
||||
try:
|
||||
serialized = json.dumps(value)
|
||||
r.setex(key, ttl_seconds, serialized)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.debug("Error escribiendo en Redis: %s", e)
|
||||
|
||||
expire_at = (time.time() + ttl_seconds) if ttl_seconds else None
|
||||
cls._memory_cache[key] = (value, expire_at)
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def delete(cls, key: str):
|
||||
r = cls._get_redis()
|
||||
if r:
|
||||
try:
|
||||
r.delete(key)
|
||||
except Exception:
|
||||
pass
|
||||
cls._memory_cache.pop(key, None)
|
||||
|
||||
@classmethod
|
||||
def clear(cls):
|
||||
cls._memory_cache.clear()
|
||||
cls._redis_checked = False
|
||||
cls._redis_client = None
|
||||
|
||||
cache_service = CacheService()
|
||||
Reference in New Issue
Block a user