feat(aihitSyncer): RemoveFromCacheAsync после обновления атрибутов ЭК

This commit is contained in:
Mikhail Kuznetsov
2026-06-04 14:14:10 +10:00
parent 1f4d338422
commit 0e8dea4c48
4 changed files with 105 additions and 109 deletions

View File

@@ -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<SyncerService> 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<SyncerService> 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<string, string?>();
foreach (var kvp in objFromQuery.Properties)
normalizedProperties[kvp.Key.Trim()] = kvp.Value?.Trim();
// 2. Применяем замены значений к нормализованным свойствам
// 2. Применяем замены значений
ApplyFieldValueReplacements(normalizedProperties);
var listAihitData = new List<KeyValuePair<string, string?>>();
// 3. Обрабатываем простые значения из нормализованных свойств
// 3. Обрабатываем простые значения
var propertisWithSimpleValues = normalizedProperties
.Where(t => fieldsFromDB.Any(a => IsStringEqual(t.Key.Trim(), a.AihitName) && a.IsMultipleValue != true))
.Select(s => new KeyValuePair<string, string?>(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<string?> values)
private async Task SyncValuesAsync(List<string?> 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 ?? "<null>"));
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 ?? "<null>")));
}
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<Unit> 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;
}
}
}
}

View File

@@ -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

View File

@@ -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<Worker>();
})
.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<NextRunTest>();

View File

@@ -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<NextRunTest>();
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<ITemplateMatcher>();
var groupRepository = scope.ServiceProvider.GetRequiredService<IJobGroupRepository>();
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<NextRunTest>();
await service!.Test();
}
}
private async Task TestEsppApi()
{