SPMS_API/SPMS.Infrastructure/Caching/TokenCacheService.cs
SEAN 1b6a87588c feat: Redis 토큰 캐시 관리 구현 (#154)
- ITokenCacheService 인터페이스 및 Redis 기반 TokenCacheService 구현
- Key: device:token:{serviceId}:{deviceId}, TTL: 1시간
- PushWorker single 발송 시 캐시 우선 조회, 미스 시 DB 조회 후 캐시 저장
- DeviceService 등록/수정/삭제/수신동의 변경 시 캐시 무효화
- RedisConnection에 GetServer() 메서드 추가 (서비스별 전체 무효화용)

Closes #154
2026-02-11 10:40:32 +09:00

103 lines
3.2 KiB
C#

using System.Text.Json;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using SPMS.Application.Interfaces;
using SPMS.Application.Settings;
using StackExchange.Redis;
namespace SPMS.Infrastructure.Caching;
public class TokenCacheService : ITokenCacheService
{
private readonly RedisConnection _redis;
private readonly RedisSettings _settings;
private readonly ILogger<TokenCacheService> _logger;
private static readonly TimeSpan CacheTtl = TimeSpan.FromHours(1);
public TokenCacheService(
RedisConnection redis,
IOptions<RedisSettings> settings,
ILogger<TokenCacheService> logger)
{
_redis = redis;
_settings = settings.Value;
_logger = logger;
}
public async Task<CachedDeviceInfo?> GetDeviceInfoAsync(long serviceId, long deviceId)
{
try
{
var db = await _redis.GetDatabaseAsync();
var value = await db.StringGetAsync(BuildKey(serviceId, deviceId));
if (value.IsNullOrEmpty)
return null;
return JsonSerializer.Deserialize<CachedDeviceInfo>(value!);
}
catch (Exception ex)
{
_logger.LogError(ex, "토큰 캐시 조회 실패: serviceId={ServiceId}, deviceId={DeviceId}",
serviceId, deviceId);
return null;
}
}
public async Task SetDeviceInfoAsync(long serviceId, long deviceId, CachedDeviceInfo info)
{
try
{
var db = await _redis.GetDatabaseAsync();
var json = JsonSerializer.Serialize(info);
await db.StringSetAsync(BuildKey(serviceId, deviceId), json, CacheTtl);
}
catch (Exception ex)
{
_logger.LogError(ex, "토큰 캐시 저장 실패: serviceId={ServiceId}, deviceId={DeviceId}",
serviceId, deviceId);
}
}
public async Task InvalidateAsync(long serviceId, long deviceId)
{
try
{
var db = await _redis.GetDatabaseAsync();
await db.KeyDeleteAsync(BuildKey(serviceId, deviceId));
}
catch (Exception ex)
{
_logger.LogError(ex, "토큰 캐시 무효화 실패: serviceId={ServiceId}, deviceId={DeviceId}",
serviceId, deviceId);
}
}
public async Task InvalidateByServiceAsync(long serviceId)
{
try
{
var db = await _redis.GetDatabaseAsync();
var server = _redis.GetServer();
if (server == null) return;
var pattern = $"{_settings.InstanceName}device:token:{serviceId}:*";
var keys = server.Keys(pattern: pattern).ToArray();
if (keys.Length > 0)
await db.KeyDeleteAsync(keys);
_logger.LogInformation("서비스 토큰 캐시 전체 무효화: serviceId={ServiceId}, 삭제={Count}건",
serviceId, keys.Length);
}
catch (Exception ex)
{
_logger.LogError(ex, "서비스 토큰 캐시 전체 무효화 실패: serviceId={ServiceId}", serviceId);
}
}
private string BuildKey(long serviceId, long deviceId) =>
$"{_settings.InstanceName}device:token:{serviceId}:{deviceId}";
}