SPMS_API/SPMS.Infrastructure/Caching/RedisConnection.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

80 lines
2.2 KiB
C#

using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using SPMS.Application.Settings;
using StackExchange.Redis;
namespace SPMS.Infrastructure.Caching;
public class RedisConnection : IAsyncDisposable
{
private readonly RedisSettings _settings;
private readonly ILogger<RedisConnection> _logger;
private readonly SemaphoreSlim _semaphore = new(1, 1);
private ConnectionMultiplexer? _connection;
public RedisConnection(IOptions<RedisSettings> settings, ILogger<RedisConnection> logger)
{
_settings = settings.Value;
_logger = logger;
}
public async Task<IDatabase> GetDatabaseAsync()
{
if (_connection is { IsConnected: true })
return _connection.GetDatabase();
await _semaphore.WaitAsync();
try
{
if (_connection is { IsConnected: true })
return _connection.GetDatabase();
_connection?.Dispose();
_logger.LogInformation("Redis 연결 시도: {ConnectionString}",
MaskConnectionString(_settings.ConnectionString));
_connection = await ConnectionMultiplexer.ConnectAsync(_settings.ConnectionString);
_logger.LogInformation("Redis 연결 성공");
return _connection.GetDatabase();
}
catch (Exception ex)
{
_logger.LogError(ex, "Redis 연결 실패");
throw;
}
finally
{
_semaphore.Release();
}
}
public IServer? GetServer()
{
if (_connection is not { IsConnected: true })
return null;
var endpoint = _connection.GetEndPoints().FirstOrDefault();
return endpoint != null ? _connection.GetServer(endpoint) : null;
}
private static string MaskConnectionString(string connectionString)
{
var parts = connectionString.Split(',');
return parts.Length > 0 ? parts[0] + ",..." : connectionString;
}
public async ValueTask DisposeAsync()
{
if (_connection != null)
{
await _connection.CloseAsync();
_connection.Dispose();
}
_semaphore.Dispose();
GC.SuppressFinalize(this);
}
}