Files
nuriq_back/src/services/auth/cache_service.py
윤영훈 e20c7d58b1 [FEAT] (사용자 로직) : 인증 서비스 구현 완료
v0.1.2 (2025-11-16)
- 로그인, 로그아웃, 토큰 갱신, 회원가입 API 구현 완료
- 로그 포맷 통일화
2025-11-16 16:20:45 +09:00

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)