From 0e8dea4c486345b3f7621bd3ac36be87da11deef Mon Sep 17 00:00:00 2001 From: Mikhail Kuznetsov Date: Thu, 4 Jun 2026 14:14:10 +1000 Subject: [PATCH] =?UTF-8?q?feat(aihitSyncer):=20RemoveFromCacheAsync=20?= =?UTF-8?q?=D0=BF=D0=BE=D1=81=D0=BB=D0=B5=20=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B0=D1=82=D1=80=D0=B8=D0=B1?= =?UTF-8?q?=D1=83=D1=82=D0=BE=D0=B2=20=D0=AD=D0=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Services/SyncerService.cs | 118 ++++++++---------- .../MatchingStatusService.cs | 2 +- PARR.Test/Program.cs | 30 ++--- PARR.Test/Worker.cs | 64 +++++++--- 4 files changed, 105 insertions(+), 109 deletions(-) diff --git a/PARR.AIHITMainSyncer/Services/SyncerService.cs b/PARR.AIHITMainSyncer/Services/SyncerService.cs index e2b56782..8e826530 100644 --- a/PARR.AIHITMainSyncer/Services/SyncerService.cs +++ b/PARR.AIHITMainSyncer/Services/SyncerService.cs @@ -1,9 +1,9 @@ -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; -using Newtonsoft.Json.Linq; using PARR.AIHITMainSyncer.Settings; using PARR.Core.Common.Interfaces; using PARR.Core.Repositories.Interfaces.Unit; +using PARR.Core.Services.UnitService.Interfaces; using PARR.Domain.Common.Rabbit.Messages; using PARR.Domain.Entities.Unit; @@ -13,26 +13,29 @@ namespace PARR.AIHITMainSyncer.Services { private readonly ILogger logger; private readonly ITransformService transformService; - private readonly IUnitRepository unitService; - private readonly IUnitFieldRepository unitFieldService; - private readonly IUnitFieldValueRepository unitFieldValueService; + private readonly IUnitRepository unitRepository; + private readonly IUnitFieldRepository unitFieldRepository; + private readonly IUnitFieldValueRepository unitFieldValueRepository; private readonly FieldValueReplacementsSettings fieldValueReplacementsSettings; + private readonly IUnitService unitService; public SyncerService( ILogger logger, ITransformService transformService, - IUnitRepository unitService, - IUnitFieldRepository unitFieldService, - IUnitFieldValueRepository unitFieldValueService, - FieldValueReplacementsSettings fieldValueReplacementsSettings + IUnitRepository unitRepository, + IUnitFieldRepository unitFieldRepository, + IUnitFieldValueRepository unitFieldValueRepository, + FieldValueReplacementsSettings fieldValueReplacementsSettings, + IUnitService unitService ) { this.logger = logger; this.transformService = transformService; - this.unitService = unitService; - this.unitFieldService = unitFieldService; - this.unitFieldValueService = unitFieldValueService; + this.unitRepository = unitRepository; + this.unitFieldRepository = unitFieldRepository; + this.unitFieldValueRepository = unitFieldValueRepository; this.fieldValueReplacementsSettings = fieldValueReplacementsSettings; + this.unitService = unitService; } @@ -45,7 +48,6 @@ namespace PARR.AIHITMainSyncer.Services logger.LogWarning("Не удалось десериализовать сообщение в объект AihitMainDataMq. Сообщение: {Message}", msg); return; } - await SyncUnitAsync(objFromQuery); } @@ -55,7 +57,7 @@ namespace PARR.AIHITMainSyncer.Services logger.LogDebug("Начинаем синхронизацию Unit для объекта с именем: {UnitName}", objFromQuery.Name); //Получаем сразу словарь атрибутов, чтобы не ходить каждый раз в базу - var fieldsFromDB = await unitFieldService.Get().AsNoTracking().ToListAsync(); + var fieldsFromDB = await unitFieldRepository.Get().AsNoTracking().ToListAsync(); if (!objFromQuery.Properties.Any()) { @@ -63,24 +65,24 @@ namespace PARR.AIHITMainSyncer.Services return; } - // 1. Создаем копию и нормализуем свойства (удаляем лишние пробелы) + // 1. Создаем копию и нормализуем свойства var normalizedProperties = new Dictionary(); foreach (var kvp in objFromQuery.Properties) normalizedProperties[kvp.Key.Trim()] = kvp.Value?.Trim(); - // 2. Применяем замены значений к нормализованным свойствам + // 2. Применяем замены значений ApplyFieldValueReplacements(normalizedProperties); var listAihitData = new List>(); - // 3. Обрабатываем простые значения из нормализованных свойств + // 3. Обрабатываем простые значения var propertisWithSimpleValues = normalizedProperties .Where(t => fieldsFromDB.Any(a => IsStringEqual(t.Key.Trim(), a.AihitName) && a.IsMultipleValue != true)) .Select(s => new KeyValuePair(s.Key.Trim(), s.Value?.Trim())).ToList(); listAihitData.AddRange(propertisWithSimpleValues); - // 4. Обрабатываем множественные значения из нормализованных свойств + // 4. Обрабатываем множественные значения var propertiesWithMultipleValues = objFromQuery.Properties.Where(t => !propertisWithSimpleValues.Any(a => t.Key.Trim() == a.Key)).ToList(); propertiesWithMultipleValues.ForEach(p => listAihitData.AddRange( @@ -110,10 +112,13 @@ namespace PARR.AIHITMainSyncer.Services ) .ToList(); + bool hasChanges = false; + if (unitValuesToRemove.Any()) { logger.LogDebug("Удаляем {Count} устаревших значений для Unit {UnitName}", unitValuesToRemove.Count, unit.Name); unitValuesToRemove.ForEach(uv => unit.UnitValues.Remove(uv)); + hasChanges = true; } var unitValueMissing = listAihitData.Where(t => @@ -130,7 +135,6 @@ namespace PARR.AIHITMainSyncer.Services var valuesInChanges = unitValueMissing.Select(s => s.Value).Distinct().ToList(); await SyncFieldsAsync(fieldsInChanges!, fieldsFromDB); - await SyncValuesAsync(valuesInChanges); var normalizedFieldNames = fieldsInChanges @@ -138,7 +142,7 @@ namespace PARR.AIHITMainSyncer.Services .Where(n => n != null) .ToHashSet(StringComparer.OrdinalIgnoreCase); - var fieldsNameWithId = await unitFieldService.Get().AsNoTracking() + var fieldsNameWithId = await unitFieldRepository.Get().AsNoTracking() .Where(f => normalizedFieldNames.Contains(f.AihitName)) .ToListAsync(); @@ -147,7 +151,7 @@ namespace PARR.AIHITMainSyncer.Services .Select(v => v!.Trim()) .ToHashSet(StringComparer.OrdinalIgnoreCase); - var valuesWithId = await unitFieldValueService.Get().AsNoTracking() + var valuesWithId = await unitFieldValueRepository.Get().AsNoTracking() .Where(v => (v.Value == null && valuesInChanges.Contains(null)) || (v.Value != null && normalizedValues.Contains(v.Value))) @@ -156,17 +160,23 @@ namespace PARR.AIHITMainSyncer.Services foreach (var change in unitValueMissing) ApplyChange(unit, fieldsNameWithId, valuesWithId, change); - if (!await unitService.CommitAsync()) + hasChanges = true; + } + else + logger.LogDebug("Нет новых значений для синхронизации для Unit {UnitName}", unit.Name); + + // Сохраняем изменения и инвалидируем кэш только если были реальные изменения + if (hasChanges) + { + if (!await unitRepository.CommitAsync()) { - logger.LogError("Не удалось применить изменения по актуализиции аттрибуто Unit {UnitName} в базе данных", unit.Name); + logger.LogError("Не удалось применить изменения по актуализации атрибутов Unit {UnitName} в базе данных", unit.Name); return; } - logger.LogInformation("Успешно обновлены атрибуты для Unit {UnitName}. Добавлено {AddedCount} значений", unit.Name, unitValueMissing.Count); - } - else - { - logger.LogDebug("Нет новых значений для синхронизации для Unit {UnitName}", unit.Name); + // Инвалидация кэша после успешного сохранения + await unitService.RemoveFromCacheAsync(unit.Id); + logger.LogInformation("Успешно обновлены атрибуты для Unit {UnitName}. Кэш инвалидирован.", unit.Name); } logger.LogInformation("Завершена синхронизация Unit {UnitName}", unit.Name); @@ -191,7 +201,6 @@ namespace PARR.AIHITMainSyncer.Services bool isTagProperty = tagUnitField != null && multiValueProperty.Key == tagUnitField.AihitName; string[] splitValues; - if (isTagProperty) { // Для тегов нормализуем разделители до пробела @@ -249,10 +258,9 @@ namespace PARR.AIHITMainSyncer.Services } - private async Task SyncValuesAsync(List values) +private async Task SyncValuesAsync(List values) { bool hasNull = values.Any(v => v is null); - // все поля из АИХИТ var normalizedNoneNullValues = values .Where(v => v != null) .Select(v => v!.Trim()) @@ -261,8 +269,7 @@ namespace PARR.AIHITMainSyncer.Services logger.LogDebug("Синхронизируем {Count} значений (включая null: {HasNull})", normalizedNoneNullValues.Count, hasNull); - // Получаем существующие значения из БД - var existingValuesInDb = await unitFieldValueService.Get() + var existingValuesInDb = await unitFieldValueRepository.Get() .Where(v => normalizedNoneNullValues.Contains(v.Value!) || v.Value == null) .Select(v => v.Value) .ToListAsync(); @@ -287,35 +294,22 @@ namespace PARR.AIHITMainSyncer.Services return; } - // Создаём сущности — сохраняем оригинальный регистр из nonNullValues! - // (для null — просто null) var newFieldValues = newValuesToInsert.Select(v => new UnitFieldValue { Id = Guid.NewGuid(), - Value = v // ← v — уже trim()-нутый non-null, или null + Value = v }) .ToList(); - if (!await unitFieldValueService.AddRangeAsync(newFieldValues) || !await unitFieldValueService.CommitAsync()) + // Только добавляем в контекст, НЕ коммитим + if (!await unitFieldValueRepository.AddRangeAsync(newFieldValues)) { - logger.LogError( - "Не удалось сохранить {Count} новых значений FieldValues", - newValuesToInsert.Count); + logger.LogError("Не удалось добавить {Count} новых значений FieldValues в контекст", newValuesToInsert.Count); } else { - if (newValuesToInsert.Count <= 10) - { - var preview = string.Join(", ", newValuesToInsert.Select(v => v ?? "")); - logger.LogInformation("Добавлено {Count} значений FieldValues: [{Values}]", newValuesToInsert.Count, preview); - } - else - { - logger.LogInformation("Добавлено {Count} значений FieldValues (первые 5: {Preview})", - newValuesToInsert.Count, - string.Join(", ", newValuesToInsert.Take(5).Select(v => v ?? ""))); - } + logger.LogDebug("Подготовлено {Count} новых значений FieldValues для сохранения", newValuesToInsert.Count); } } @@ -341,24 +335,16 @@ namespace PARR.AIHITMainSyncer.Services EsppName = null, }; - if (!await unitFieldService.CreateAsync(field)) + // Только добавляем в контекст, НЕ коммитим + if (!await unitFieldRepository.CreateAsync(field)) { - logger.LogError("Не удалось создать запись в таблице Fields: {FieldName}", item); + logger.LogError("Не удалось добавить поле в контекст: {FieldName}", item); } else { - logger.LogInformation("Создана запись в таблице Fields: {FieldName}", item); + logger.LogDebug("Подготовлено поле для сохранения: {FieldName}", item); } } - - if (!await unitFieldService.CommitAsync()) - { - logger.LogError("Не удалось применить изменения по добавлению новых Fields в базе данных"); - } - else - { - logger.LogDebug("Успешно зафиксированы изменения по добавлению новых Fields"); - } } @@ -393,7 +379,7 @@ namespace PARR.AIHITMainSyncer.Services { var unit = new Unit { Name = name.Trim().ToUpperInvariant() }; - if (!await unitService.CreateAsync(unit) || !await unitService.CommitAsync()) + if (!await unitRepository.CreateAsync(unit) || !await unitRepository.CommitAsync()) { logger.LogError("Не удалось создать Unit {UnitName}", unit.Name); throw new InvalidOperationException($"Не удалось создать Unit {unit.Name}"); @@ -402,7 +388,6 @@ namespace PARR.AIHITMainSyncer.Services { logger.LogInformation("Создан Unit: {UnitName}", unit.Name); } - return unit; } @@ -417,7 +402,7 @@ namespace PARR.AIHITMainSyncer.Services private IQueryable GetUnitWithFieldsAndValues() { - return unitService.Get() + return unitRepository.Get() .Include(t => t.UnitValues) .ThenInclude(uv => uv.Field) .Include(t => t.UnitValues) @@ -442,7 +427,6 @@ namespace PARR.AIHITMainSyncer.Services replacement.FieldName, currentValue, replacement.NewValue); properties[replacement.FieldName] = replacement.NewValue; } - } } } diff --git a/PARR.Core/Services/MatchingStatusService/MatchingStatusService.cs b/PARR.Core/Services/MatchingStatusService/MatchingStatusService.cs index 93fef426..b2460671 100644 --- a/PARR.Core/Services/MatchingStatusService/MatchingStatusService.cs +++ b/PARR.Core/Services/MatchingStatusService/MatchingStatusService.cs @@ -58,7 +58,7 @@ namespace PARR.Core.Services.MatchingStatusService case SyncTaskEntityTypeEnum.Template: throw new Exception("Нет реализации получения кэша статуса matching для template"); //todo: У нас вообще нет matching для Template, позже надо удалить это вообще из Enum SyncTaskEntityTypeEnum - break; + //break; } // Если списки пустые, мало ли, говорим что нет matching`a diff --git a/PARR.Test/Program.cs b/PARR.Test/Program.cs index 962c091b..f337bcc0 100644 --- a/PARR.Test/Program.cs +++ b/PARR.Test/Program.cs @@ -1,29 +1,12 @@ using Elastic.CommonSchema.Serilog; -using PARR.Core; using PARR.DAL; using PARR.EsppApi; -using PARR.Infrastructure; +using PARR.TemplateMatcher; using PARR.Test; using PARR.Test.NextRun; using Serilog; -/*IHost host = Host.CreateDefaultBuilder(args) - .ConfigureServices((hostContext, services) => - { - //services.InstallEsppApiServices(hostContext.Configuration); - //services.InstallBllServices(hostContext.Configuration); - services.InstallDalServices(hostContext.Configuration); - //services.InstallTemplateDistributorSerivces(hostContext.Configuration); - services.AddHostedService(); - }) - .UseSerilog((hostContext, services, config) => - { - config - .WriteTo.Console() - .ReadFrom.Configuration(hostContext.Configuration); - }) - .Build();*/ var builder = Host.CreateApplicationBuilder(); builder.Services.AddLogging(config => @@ -42,12 +25,17 @@ builder.Services.AddLogging(config => config.AddSerilog(logger.CreateLogger()); }); -builder.Services.AddDalServices(builder.Configuration); -builder.Services.AddCoreServices(builder.Configuration); -builder.Services.AddInfrastructureServices(builder.Configuration); + +builder.Services.InstallTemplateMatcherSerivces(builder.Configuration); +// builder.Services.AddDalServices(builder.Configuration); +// builder.Services.AddCoreServices(builder.Configuration); +// builder.Services.AddInfrastructureServices(builder.Configuration); builder.Services.InstallEsppApiServices(builder.Configuration); +builder.Configuration.AddTemplateMatcherConfigurations(builder.Services); builder.Configuration.AddDalConfigurations(builder.Services); + +builder.Services.AddTemplateMatcherSettings(builder.Configuration); builder.Services.AddDallSettings(builder.Configuration); builder.Services.AddTransient(); diff --git a/PARR.Test/Worker.cs b/PARR.Test/Worker.cs index 6ab1c586..8ac05e1d 100644 --- a/PARR.Test/Worker.cs +++ b/PARR.Test/Worker.cs @@ -1,9 +1,13 @@ +using Microsoft.EntityFrameworkCore; using PARR.Core.Common.Interfaces; +using PARR.Core.Repositories.Interfaces.Job; using PARR.Domain.Cache; +using PARR.Domain.Entities.Base.History; using PARR.Domain.Enums; using PARR.EsppApi; using PARR.EsppApi.Constants; using PARR.EsppApi.Models.Query; +using PARR.TemplateMatcher; using PARR.Test.NextRun; namespace PARR.Test @@ -37,16 +41,11 @@ namespace PARR.Test //var bbb = aaa.ToOffset(new TimeSpan(3, 0, 0)); - #region NextRunTest + await TemplateMatcherTest(); - using (var scope = serviceProvider.CreateScope()) - { - var service = scope.ServiceProvider.GetService(); - await service!.Test(); - } - #endregion + //await NextRunTest(); #region test @@ -161,21 +160,46 @@ namespace PARR.Test #endregion - - ////test interval - //await intervalService.IntervalInitAsync(async () => - //{ - // _logger.LogInformation("---Выполняю метод---"); - // await Task.Delay(29*1000); - // _logger.LogInformation("===Выполнил метод==="); - //}, new TimeSpan(0, 0, 30)); - - - - //await TestEsppApi(); - //await StopDiscontinuedOrdersInEspp(); } + #region TemplateMatcher + private async Task TemplateMatcherTest() + { + await using var scope = serviceProvider.CreateAsyncScope(); + + var templateMatcher = scope.ServiceProvider.GetRequiredService(); + var groupRepository = scope.ServiceProvider.GetRequiredService(); + + var groups = await groupRepository.Get().AsNoTracking() + .Where(t => t.GroupType != null && t.GroupType.Code == JobGroupTypesEnum.Group) + .Select(t => t.Id).ToHashSetAsync(); + + foreach (var groupId in groups) + { + await templateMatcher.SyncTemplatesForJobGroup(groupId, + new HistoryInitiator + { + InitiatorIp = "10.99.246.156", + InitiatorParrComponentId = ParrComponentsEnum.Master, + InitiatorComment = "Тестовый проект. Синхронизация Группированых работ" + + } + ); + } + + return; + } + #endregion + + private async Task NextRunTest() + { + using (var scope = serviceProvider.CreateScope()) + { + var service = scope.ServiceProvider.GetService(); + + await service!.Test(); + } + } private async Task TestEsppApi() {