using Application.Abstractions.Cache; using Application.Abstractions.Chat; using StackExchange.Redis; using System.Text.Json; namespace Infrastructure.Chat; public class RedisChatConnectionTracker(IConnectionMultiplexer redis) : IChatConnectionTracker { private readonly IDatabase _db = redis.GetDatabase(); private static readonly JsonSerializerOptions _jsonOptions = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, WriteIndented = false }; public async Task AddAsync(string channelSID, ConnectedUser user) { var json = JsonSerializer.Serialize(user, _jsonOptions); await _db.HashSetAsync(CacheKeys.ChatChannelConnections(channelSID), user.ConnectionId, json); } public async Task RemoveAsync(string channelSID, string connectionId) { await _db.HashDeleteAsync(CacheKeys.ChatChannelConnections(channelSID), connectionId); } public async Task> GetByChannelAsync(string channelSID) { var entries = await _db.HashGetAllAsync(CacheKeys.ChatChannelConnections(channelSID)); if (entries.Length <= 0) { return []; } var list = new List(entries.Length); foreach (var entry in entries) { if (!entry.Value.IsNullOrEmpty) { var user = JsonSerializer.Deserialize((string)entry.Value!, _jsonOptions); if (user is not null) { list.Add(user); } } } return list.AsReadOnly(); } public async Task GetCountByChannelAsync(string channelSID) { return (int)await _db.HashLengthAsync(CacheKeys.ChatChannelConnections(channelSID)); } public async Task> GetAllAsync() { // 모든 채널 키를 스캔 후 병합 (관리자 집계용) var server = _db.Multiplexer.GetServer(_db.Multiplexer.GetEndPoints().First()); var list = new List<( string ChannelSID, ConnectedUser User )>(); await foreach (var key in server.KeysAsync(pattern: "chat:channel:*:connections")) { // 키에서 channelSID 추출 — chat:channel:{sid}:connections var keyStr = (string)key!; var channelSID = keyStr["chat:channel:".Length..^":connections".Length]; var entries = await _db.HashGetAllAsync(key); foreach (var entry in entries) { if (!entry.Value.IsNullOrEmpty) { var user = JsonSerializer.Deserialize((string)entry.Value!, _jsonOptions); if (user is not null) { list.Add((channelSID, user)); } } } } return list.AsReadOnly(); } public async Task ClearAllAsync() { // 서버 시작 시 모든 채널 연결 초기화 (stale 제거) var server = _db.Multiplexer.GetServer(_db.Multiplexer.GetEndPoints().First()); await foreach (var key in server.KeysAsync(pattern: "chat:channel:*:connections")) { await _db.KeyDeleteAsync(key); } } }