168 lines
5.2 KiB
Python
168 lines
5.2 KiB
Python
"""
|
|
캐시 서비스
|
|
Redis 기반 캐싱 로직 관리하는 서비스 레이어
|
|
"""
|
|
|
|
import json
|
|
from typing import Optional, Dict, Any
|
|
from extensions import redis_cli
|
|
import settings
|
|
|
|
|
|
from extensions import custom_logger
|
|
|
|
logger = custom_logger(f"{settings.LOG_PREFIX}_auth_service")
|
|
class CacheService:
|
|
|
|
@staticmethod
|
|
def _is_redis_available() -> bool:
|
|
"""Redis 연결 상태 확인"""
|
|
return redis_cli.is_connected()
|
|
|
|
@staticmethod
|
|
def set(key: str, value: Any, ttl: int) -> bool:
|
|
"""Redis에 데이터 저장"""
|
|
if not CacheService._is_redis_available():
|
|
logger.warning("레디스가 연결되어 있지 않습니다. 등록 로직을 ㄴ생략합니다.")
|
|
return False
|
|
|
|
try:
|
|
if isinstance(value, dict):
|
|
value = json.dumps(value, ensure_ascii=False)
|
|
|
|
redis_cli.redis_client.setex(key, ttl, value)
|
|
logger.debug(f"캐시 저장: {key} (TTL: {ttl}s)")
|
|
return True
|
|
|
|
except Exception as e:
|
|
logger.error(f"캐시 저장 실패: {key} - {str(e)}")
|
|
return False
|
|
|
|
@staticmethod
|
|
def get(key: str, parse_json: bool = True) -> Optional[Any]:
|
|
"""Redis에서 데이터 조회"""
|
|
if not CacheService._is_redis_available():
|
|
logger.warning("레디스가 연결되어 있지 않습니다. 조회 로직을 생략합니다.")
|
|
return None
|
|
|
|
try:
|
|
value = redis_cli.redis_client.get(key)
|
|
|
|
if value:
|
|
logger.debug(f"Cache hit: {key}")
|
|
if parse_json:
|
|
return json.loads(value)
|
|
return value
|
|
|
|
logger.debug(f"Cache miss: {key}")
|
|
return None
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to get cache {key}: {str(e)}")
|
|
return None
|
|
|
|
@staticmethod
|
|
def delete(key: str) -> bool:
|
|
"""Redis에서 데이터 삭제
|
|
|
|
Args:
|
|
key: Redis 키
|
|
|
|
Returns:
|
|
bool: 성공 여부
|
|
"""
|
|
if not CacheService._is_redis_available():
|
|
logger.warning("레디스가 연결되어 있지 않습니다. 삭제가 불가능 합니다.")
|
|
return False
|
|
|
|
try:
|
|
redis_cli.redis_client.delete(key)
|
|
logger.debug(f"Cache deleted: {key}")
|
|
return True
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to delete cache {key}: {str(e)}")
|
|
return False
|
|
|
|
@staticmethod
|
|
def exists(key: str) -> bool:
|
|
"""Redis에 키가 존재하는지 확인"""
|
|
if not CacheService._is_redis_available():
|
|
logger.warning("레디스가 연결되어 있지 않습니다. 조회가 불가능 합니다.")
|
|
return False
|
|
|
|
try:
|
|
return redis_cli.redis_client.exists(key) > 0
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to check cache existence {key}: {str(e)}")
|
|
return False
|
|
|
|
class UserCacheService:
|
|
"""사용자 캐시 관리 서비스"""
|
|
|
|
PREFIX = "user:cache:"
|
|
TTL = settings.REDIS_USER_CACHE_TTL
|
|
|
|
@classmethod
|
|
def _get_key(cls, user_uuid: str) -> str:
|
|
"""캐시 키 생성"""
|
|
return f"{cls.PREFIX}{user_uuid}"
|
|
|
|
@classmethod
|
|
def set(cls, user_uuid: str, user_data: Dict[str, Any], ttl: Optional[int] = None) -> bool:
|
|
"""사용자 정보를 Redis에 캐싱"""
|
|
key = cls._get_key(user_uuid)
|
|
ttl = ttl or cls.TTL
|
|
return CacheService.set(key, user_data, ttl)
|
|
|
|
@classmethod
|
|
def get(cls, user_uuid: str) -> Optional[Dict[str, Any]]:
|
|
"""Redis에서 사용자 정보 조회"""
|
|
key = cls._get_key(user_uuid)
|
|
return CacheService.get(key, parse_json=True)
|
|
|
|
@classmethod
|
|
def delete(cls, user_uuid: str) -> bool:
|
|
"""캐시에서 사용자 정보 삭제"""
|
|
key = cls._get_key(user_uuid)
|
|
return CacheService.delete(key)
|
|
|
|
@classmethod
|
|
def refresh(cls, user_uuid: str, user_data: Dict[str, Any]) -> bool:
|
|
"""캐시 갱신 (삭제 후 재설정)"""
|
|
cls.delete(user_uuid)
|
|
return cls.set(user_uuid, user_data)
|
|
|
|
|
|
class JWTBlacklistService:
|
|
"""JWT 블랙리스트 관리 서비스 (로그아웃된 토큰 추적)"""
|
|
|
|
PREFIX = "jwt:blacklist:"
|
|
TTL = settings.REDIS_JWT_BLACKLIST_TTL
|
|
|
|
@classmethod
|
|
def _get_key(cls, jti: str) -> str:
|
|
"""블랙리스트 키 생성"""
|
|
return f"{cls.PREFIX}{jti}"
|
|
|
|
@classmethod
|
|
def add(cls, jti: str, ttl: Optional[int] = None) -> bool:
|
|
"""블랙리스트에 토큰 추가"""
|
|
key = cls._get_key(jti)
|
|
ttl = ttl or cls.TTL
|
|
|
|
if not CacheService._is_redis_available():
|
|
logger.warning("레디스가 연결되어 있지 않습니다. 블랙리스트에 추가가 블가능합니다.")
|
|
return False
|
|
|
|
success = CacheService.set(key, "1", ttl)
|
|
if success:
|
|
logger.info(f"JWT 블랙리스트 추가완료: {jti}")
|
|
return success
|
|
|
|
@classmethod
|
|
def is_blacklisted(cls, jti: str) -> bool:
|
|
"""토큰이 블랙리스트에 있는지 확인"""
|
|
key = cls._get_key(jti)
|
|
return CacheService.exists(key) |