From d27d594e57fbf8748f590b83d490ac33ab8ce95a Mon Sep 17 00:00:00 2001 From: Mikhail Trubnikov Date: Mon, 1 Jun 2026 16:10:00 +1000 Subject: [PATCH] =?UTF-8?q?feat(core,=20infrastructure):=20UnitService=20-?= =?UTF-8?q?=20=D1=83=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5/=D0=BC?= =?UTF-8?q?=D0=B0=D1=81=D1=81=D0=BE=D0=B2=D0=BE=D0=B5=20=D1=83=D0=B4=D0=B0?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=8E=D0=BD=D0=B8=D1=82=D0=BE?= =?UTF-8?q?=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PARR.API/Controllers/V1/TestController.cs | 11 ++++++-- .../Common/Interfaces/IRedisCacheService.cs | 7 +++++ .../Implementations/UnitCacheService.cs | 27 ++++++++++++++++--- .../Implementations/UnitService.cs | 8 ++++++ .../UnitService/Interfaces/IUnitService.cs | 7 ++++- .../Redis/RedisCacheService.cs | 27 ++++++++++++++++++- 6 files changed, 80 insertions(+), 7 deletions(-) diff --git a/PARR.API/Controllers/V1/TestController.cs b/PARR.API/Controllers/V1/TestController.cs index 39589063..3adb08c8 100644 --- a/PARR.API/Controllers/V1/TestController.cs +++ b/PARR.API/Controllers/V1/TestController.cs @@ -91,14 +91,21 @@ namespace PARR.API.Controllers.V1 [HttpPost(ApiRoutes.Test.CreateCache)] public async Task CreateCache([FromBody] Guid unitId) { - var units = await unitRepository.Get().Take(100).Select(t => t.Id).ToListAsync(); - var listData = await unitService.GetWithCachingAsync(units); + //var units = await unitRepository.Get().Take(100).Select(t => t.Id).ToListAsync(); + //var listData = await unitService.GetWithCachingAsync(units); //foreach(var unit in units) //{ // //поштучно // var unitTtt = await unitService.GetWithCachingAsync(unit); //} + // удалить элемент + //await unitService.RemoveFromCacheAsync(Guid.Parse("79833f20-a84f-45f4-a4ed-0dae09b683d7")); + + // todo: проверить удаление пачки юнитов !!!!!!!!!!!!!!!!!!!!! + //await unitService.RemoveFromCacheAsync(units); + + return Ok(); //var unit = await unitService.GetWithCachingAsync(unitId); diff --git a/PARR.Core/Common/Interfaces/IRedisCacheService.cs b/PARR.Core/Common/Interfaces/IRedisCacheService.cs index e5176fbd..6f7fdee2 100644 --- a/PARR.Core/Common/Interfaces/IRedisCacheService.cs +++ b/PARR.Core/Common/Interfaces/IRedisCacheService.cs @@ -158,6 +158,13 @@ /// Task> GetHashFieldsAsync(List<(string HashKey, string FieldKey)> keys, bool useCompression = false); + /// + /// Удалить список из Hash + /// + /// + /// + Task DeleteHashFieldsAsync(List<(string HashKey, string FieldKey)> keys); + #endregion } } diff --git a/PARR.Core/Services/UnitService/Implementations/UnitCacheService.cs b/PARR.Core/Services/UnitService/Implementations/UnitCacheService.cs index 671b05fb..6fe0acc5 100644 --- a/PARR.Core/Services/UnitService/Implementations/UnitCacheService.cs +++ b/PARR.Core/Services/UnitService/Implementations/UnitCacheService.cs @@ -125,11 +125,32 @@ namespace PARR.Core.Services.UnitService.Implementations /// /// /// - public Task RemoveFromCacheAsync(Guid id) + public async Task RemoveFromCacheAsync(Guid id) { - //todo: может удалять тоже списком + var hashKey = redisCacheService.GetKey(CacheKeys.Unit.UnitHashWithBucket(id)); + var unitKey = redisCacheService.GetKey(CacheKeys.Unit.UnitItem(id)); - throw new NotImplementedException(); + await redisCacheService.DeleteHashFieldAsync(hashKey, unitKey); + } + + /// + /// Удалить записи UnitInfo из кэш + /// + /// + /// + public async Task RemoveFromCacheAsync(IReadOnlySet ids) + { + if (ids == null || ids.Count == 0) + return; + + // Формируем ключи + var keys = ids.Select(t => + { + var (hashKey, fieldKey) = (redisCacheService.GetKey(CacheKeys.Unit.UnitHashWithBucket(t)), redisCacheService.GetKey(CacheKeys.Unit.UnitItem(t))); + return (HashKey: hashKey, FieldKey: fieldKey); + }).ToList(); + + await redisCacheService.DeleteHashFieldsAsync(keys); } diff --git a/PARR.Core/Services/UnitService/Implementations/UnitService.cs b/PARR.Core/Services/UnitService/Implementations/UnitService.cs index 5bd287ed..45cb0765 100644 --- a/PARR.Core/Services/UnitService/Implementations/UnitService.cs +++ b/PARR.Core/Services/UnitService/Implementations/UnitService.cs @@ -36,5 +36,13 @@ namespace PARR.Core.Services.UnitService.Implementations { await unitCacheService.RemoveFromCacheAsync(id); } + + public async Task RemoveFromCacheAsync(IEnumerable ids) + { + // Защищаем себя от Multiple Enumeration и убираем дубликаты, если передали List + var uniqueIds = ids as IReadOnlySet ?? ids.ToHashSet(); + + await unitCacheService.RemoveFromCacheAsync(uniqueIds); + } } } diff --git a/PARR.Core/Services/UnitService/Interfaces/IUnitService.cs b/PARR.Core/Services/UnitService/Interfaces/IUnitService.cs index fa621213..42de90db 100644 --- a/PARR.Core/Services/UnitService/Interfaces/IUnitService.cs +++ b/PARR.Core/Services/UnitService/Interfaces/IUnitService.cs @@ -30,6 +30,11 @@ namespace PARR.Core.Services.UnitService.Interfaces /// Task RemoveFromCacheAsync(Guid id); - + /// + /// Удалить юниты из кэша + /// + /// + /// + Task RemoveFromCacheAsync(IEnumerable ids); } } diff --git a/PARR.Infrastructure/Redis/RedisCacheService.cs b/PARR.Infrastructure/Redis/RedisCacheService.cs index 45155695..2070064d 100644 --- a/PARR.Infrastructure/Redis/RedisCacheService.cs +++ b/PARR.Infrastructure/Redis/RedisCacheService.cs @@ -1,6 +1,5 @@ using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Logging; -using Newtonsoft.Json.Linq; using PARR.Core.Common.Interfaces; using PARR.Infrastructure.Redis.Helpers; using StackExchange.Redis; @@ -376,6 +375,32 @@ namespace PARR.Infrastructure.Redis await redis.HashDeleteAsync(hashKey, field); } + public async Task DeleteHashFieldsAsync(List<(string HashKey, string FieldKey)> keys) + { + if (keys == null || keys.Count == 0) + return; + + int deletedCount = 0; + + // Делим на порции по 5000, чтобы не перегружать буфер команд Redis + foreach (var chunk in keys.Chunk(5000)) + { + var tasks = new List>(chunk.Length); + foreach (var key in chunk) + tasks.Add(redis.HashDeleteAsync(key.HashKey, key.FieldKey)); + + logger.LogDebug("Отправил пачку из {Count} команд на удаление в Redis", tasks.Count); + + // Ждем выполнения текущей порции команд удалений + bool[] results = await Task.WhenAll(tasks); + + // Считаем, сколько элементов реально было удалено + deletedCount += results.Count(x => x == true); + } + + logger.LogDebug("Успешно удалено элементов из кэша: {Count}.", deletedCount); + } + public async Task HashFieldExistsAsync(string hashKey, string field) { // есть ли запись в Hash