using Microsoft.Extensions.Caching.Distributed; using PARR.Core.Common.Interfaces; using StackExchange.Redis; using System.Text.Json; namespace PARR.Infrastructure.Redis { internal class RedisCacheService : IRedisCacheService { private readonly IDistributedCache cache; private readonly IDatabase redis; public RedisCacheService( IDistributedCache cache, IConnectionMultiplexer connectionMultiplexer ) { this.cache = cache; this.redis = connectionMultiplexer.GetDatabase(); } #region Распределенный кэш IDistributedCache public async Task GetCachedDataAsync(string key) { var jsonData = await cache.GetStringAsync(key); if (jsonData == null) return default(T); return JsonSerializer.Deserialize(jsonData); } public T? GetCachedData(string key) { var jsonData = cache.GetString(key); if (jsonData == null) return default(T); return JsonSerializer.Deserialize(jsonData); } public void SetCachedData(string key, T data, TimeSpan cacheDuration) { var options = new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = cacheDuration }; var jsonData = JsonSerializer.Serialize(data); cache.SetString(key, jsonData, options); } public async Task SetCachedDataAsync(string key, T data, TimeSpan cacheDuration) { var options = new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = cacheDuration }; var jsonData = JsonSerializer.Serialize(data); await cache.SetStringAsync(key, jsonData, options); } public async Task DeleteCachedDataAsync(string key) { await cache.RemoveAsync(key); } public void DeleteCachedData(string key) { cache.Remove(key); } #endregion #region Нативные операции Redis, Redis Hash public async Task SetHashFieldAsync(string hashKey, string field, T value, TimeSpan? ttl = null) { // меняет одно поле в Hash var jsonData = JsonSerializer.Serialize(value); // true - поля не было, создалось новое. false - поле было, обновили значение var result = await redis.HashSetAsync(hashKey, field, jsonData); // если указан ttl, обновим для всего Hash // если не указан и ранее был создан hashKey, оставит его ttl; а если hashKey не было, то создаст его БЕССРОЧНЫМ!!! if (ttl.HasValue) await SetHashTtlAsync(hashKey, ttl.Value); } public async Task GetHashFieldAsync(string hashKey, string field) { // получить значение поля из Hash var value = await redis.HashGetAsync(hashKey, field); if (value.IsNullOrEmpty) return default; return JsonSerializer.Deserialize(value); } public async Task> GetAllHashFieldsAsync(string hashKey) { // получить все записи из Hash var objs = await redis.HashGetAllAsync(hashKey); if (objs.Length == 0) return new Dictionary(); var result = objs.ToDictionary( t => t.Name.ToString(), t => JsonSerializer.Deserialize(t.Value) ); return result!; } public async Task DeleteHashFieldAsync(string hashKey, string field) { // удалить запись из Hash await redis.HashDeleteAsync(hashKey, field); } public async Task HashFieldExistsAsync(string hashKey, string field) { // есть ли запись в Hash return await redis.HashExistsAsync(hashKey, field); } public async Task DeleteHashAsync(string hashKey) { // удалить весь Hash await redis.KeyDeleteAsync(hashKey); } public async Task GetHashLengthAsync(string hashKey) { // кол-во записей в hash return await redis.HashLengthAsync(hashKey); } public async Task SetHashTtlAsync(string hashKey, TimeSpan ttl) { // Установить ttl для Hash await redis.KeyExpireAsync(hashKey, ttl); } #endregion #region Helpers public string GetKey(string[] keyParts, string[]? keyPartsToHash = null) { // разделитель между ИД var mainSeparator = "_"; // разделитель между словами var wordSeparator = "-"; if (keyParts.Length == 0) { throw new ArgumentNullException("keyParts не может быть пустым"); } var keyStr = string.Join(mainSeparator, keyParts).Replace(" ", wordSeparator); if (keyPartsToHash != null && keyPartsToHash.Length > 0) { var partsToHashStr = string.Join(mainSeparator, keyPartsToHash).Replace(" ", wordSeparator); using var sha256 = System.Security.Cryptography.SHA256.Create(); var hashedBytes = sha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes(partsToHashStr)); var base64str = Convert.ToBase64String(hashedBytes).Substring(0, 16); return (keyStr + mainSeparator + base64str).ToLower(); } return keyStr.ToLower(); } #endregion } }