feat(dal): уточнена логика получение имени связанных ЭК, переписано кэширование МАКС:FIELD, в синхронизацию и контролер заданий роботов добавлены include

This commit is contained in:
Mikhail Kuznetsov
2025-12-29 19:21:51 +10:00
parent d5d84c1853
commit acf232ecdf
24 changed files with 598 additions and 558 deletions

View File

@@ -0,0 +1,10 @@
namespace PARR.DAL.DomainServices.Shortcodes.Models
{
internal class CachedGroupedShortCode
{
public string Value { get; set; } = string.Empty;
public DateTimeOffset Timestamp { get; set; }
public string? Source { get; set; } = "ShortcodesService";
public int Version { get; set; } = 1;
}
}

View File

@@ -1,6 +1,7 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using PARR.Constants;
using PARR.DAL.CacheServices;
using PARR.DAL.Contracts;
using PARR.DAL.DomainModels;
using PARR.DAL.DomainServices.Interfaces;
@@ -31,7 +32,7 @@ namespace PARR.DAL.DomainServices.Shortcodes
private readonly IUnitInValueService unitInValueService;
private readonly IUnitFieldService unitFieldService;
private readonly ITemplateService templateService;
private readonly IGroupedShortcodesCacheService groupedShortcodesCacheService;
private readonly IRedisCacheService cacheService;
private readonly IUnitFilterService unitFilterService;
public ShortcodesService(
@@ -43,7 +44,7 @@ namespace PARR.DAL.DomainServices.Shortcodes
IUnitInValueService unitInValueService,
IUnitFieldService unitFieldService,
ITemplateService templateService,
IGroupedShortcodesCacheService groupedShortcodesCacheService
IRedisCacheService cacheService
)
{
this.logger = logger;
@@ -53,7 +54,7 @@ namespace PARR.DAL.DomainServices.Shortcodes
this.unitInValueService = unitInValueService;
this.unitFieldService = unitFieldService;
this.templateService = templateService;
this.groupedShortcodesCacheService = groupedShortcodesCacheService;
this.cacheService = cacheService;
this.unitFilterService = unitFilterService;
}
@@ -124,8 +125,7 @@ namespace PARR.DAL.DomainServices.Shortcodes
var maxShortcodes = Regex.Matches(resultName, maxShortcodePattern);
if (maxShortcodes.Count > 0)
{
var unitIds = template.UnitsInTemplate.Select(uit => uit.UnitId).ToList();
resultName = await ReplaceMaxShortcodesAsync(job.Group.Id, template.UnitId, unitIds, resultName, maxShortcodes);
resultName = await ReplaceMaxShortcodesAsync(job.Group.Id, template.UnitId, resultName, maxShortcodes);
}
}
@@ -273,9 +273,6 @@ namespace PARR.DAL.DomainServices.Shortcodes
new ShortcodeInfoDto { Shortcode = "%МАКС:ИМЯ АТРИБУТА%",
Description = "Используется только с групповым типом работ. Наиболее часто встречающееся значение поля в группе (игнорирует пустые). Пример: %МАКС:РАБОЧАЯ_ГР_ОТВ_ЗАК%",
Type = ShortcodeTypeEnum.Standart },
new ShortcodeInfoDto { Shortcode = "%ГРОЛЕ-ПН%",
Description = "Нумерованный список unit-ов из шаблона: 1. ЭК-123 (Значение1, Значение2). Использует GroupingUnitFieldId из JobGroup.",
Type = ShortcodeTypeEnum.Relationship },
new ShortcodeInfoDto{ Shortcode = "%БУКВЫ:ИМЯ АТРИБУТА%",
Description = "Извлекает только буквы из значения поля. Пример: %БУКВЫ:ЗОНА_ОТВЕТСТВЕННОСТИ% → ПРИВ",
Type = ShortcodeTypeEnum.Standart
@@ -290,7 +287,10 @@ namespace PARR.DAL.DomainServices.Shortcodes
Type = ShortcodeTypeEnum.Relationship },
new ShortcodeInfoDto { Shortcode = "%СВЯЗИ-ПН%",
Description = "Связанные ЭК с нумерацией (1. ..., 2. ...), выбираются только при настроенном фильтре по полям в связанных ЭК",
Type = ShortcodeTypeEnum.Relationship }
Type = ShortcodeTypeEnum.Relationship },
new ShortcodeInfoDto { Shortcode = "%ГРОЛЕ-ПН%",
Description = "Нумерованный список unit-ов из шаблона: 1. ЭК-123 (Значение1, Значение2). Использует GroupingUnitFieldId из JobGroup.",
Type = ShortcodeTypeEnum.Relationship },
});
// 4. Все доступные поля из UnitField
@@ -363,7 +363,7 @@ namespace PARR.DAL.DomainServices.Shortcodes
return resultName;
}
private async Task<string> ReplaceMaxShortcodesAsync(Guid jobGroupId, Guid unitId, List<Guid> unitIds, string input, MatchCollection maxShortcodes)
private async Task<string> ReplaceMaxShortcodesAsync(Guid jobGroupId, Guid unitId, string input, MatchCollection maxShortcodes)
{
var shortcodeToMatches = maxShortcodes
.Cast<Match>()
@@ -379,56 +379,69 @@ namespace PARR.DAL.DomainServices.Shortcodes
logger.LogDebug("Обработка {Shortcode} для JobGroup {JobGroupId}, Template.UnitId {UnitId}, fieldName {FieldName}",
fullShortcode, jobGroupId, unitId, fieldName);
var mostFrequentValue = await groupedShortcodesCacheService.GetAggregatedValueAsync(
jobGroupId,
unitId,
fullShortcode,
async () =>
{
logger.LogDebug("Кэш промахнут для {Shortcode}, начинаем вычисление.", fullShortcode);
// ✅ Пытаемся загрузить UnitsInTemplate из БД
var dbUnitIds = await jobService.Get()
.Where(j => j.GroupId == jobGroupId)
.Join(
templateService.Get()
.Where(t => t.StatusTypeId == TemplateStatusTypeEnum.Used)
.Include(t => t.UnitsInTemplate),
job => job.Id,
template => template.JobId,
(job, template) => template
)
.SelectMany(template => template.UnitsInTemplate)
.Select(uit => uit.UnitId)
.Distinct()
.ToListAsync();
logger.LogDebug("Загружено {Count} unitIds из БД для JobGroup {JobGroupId}", dbUnitIds.Count, jobGroupId);
// ✅ Если в БД нет UnitsInTemplate — используем переданные unitIds
var effectiveUnitIds = dbUnitIds.Any() ? dbUnitIds : unitIds;
logger.LogDebug("EffectiveUnitIds: [{Ids}], Count: {Count}", string.Join(", ", effectiveUnitIds), effectiveUnitIds.Count);
// Вызываем метод из сервиса
var result = await unitInValueService.GetMostFrequentValueForFieldAsync(effectiveUnitIds, fieldName);
logger.LogDebug("Результат GetMostFrequentValueForFieldAsync: {Result}, для поля {FieldName}, unitIds: [{Ids}]", result, fieldName, string.Join(", ", effectiveUnitIds));
return result ?? string.Empty;
});
logger.LogDebug("Итоговое значение для {Shortcode}: {Value}", fullShortcode, mostFrequentValue);
var mostFrequentValue = await GetMaxShortCodeFromCacheOrDbAsync(jobGroupId, unitId, fullShortcode, fieldName);
foreach (var match in matches)
{
input = input.Replace(match.Value, mostFrequentValue);
}
}
return input;
}
private async Task<string> GetMaxShortCodeFromCacheOrDbAsync(Guid jobGroupId, Guid unitId, string fullShortcode, string fieldName)
{
var cacheKey = $"gr_shcd_{jobGroupId:N}_{unitId:N}_{ComputeHash(fullShortcode)}";
var cachedData = await cacheService.GetCachedDataAsync<CachedGroupedShortCode>(cacheKey);
if (cachedData != null)
{
logger.LogDebug("Кэш попал для GroupedShortCode '{Name}': {Value}", fullShortcode, cachedData.Value);
return cachedData.Value;
}
logger.LogDebug("Кэш промахнут для GroupedShortCode '{Name}'. Запрашиваем из БД.", fullShortcode);
// Загружаем юниты из БД
var effectiveUnitIds = await jobService.Get()
.Where(j => j.GroupId == jobGroupId)
.Join(
templateService.Get()
.Where(t => t.StatusTypeId == TemplateStatusTypeEnum.Used && t.UnitId == unitId)
.Include(t => t.UnitsInTemplate),
job => job.Id,
template => template.JobId,
(job, template) => template
)
.SelectMany(template => template.UnitsInTemplate)
.Select(uit => uit.UnitId)
.Distinct()
.ToListAsync();
logger.LogDebug("EffectiveUnitIds: [{Ids}], Count: {Count}", string.Join(", ", effectiveUnitIds), effectiveUnitIds.Count);
// Вызываем метод из сервиса
var mostFrequentValue = await unitInValueService.GetMostFrequentValueForFieldAsync(effectiveUnitIds, fieldName);
logger.LogDebug("Результат GetMostFrequentValueForFieldAsync: {Result}, для поля {FieldName}, unitIds: [{Ids}]", mostFrequentValue, fieldName, string.Join(", ", effectiveUnitIds));
// Не кэшируем пустые значения
if (!string.IsNullOrEmpty(mostFrequentValue))
{
var toCache = new CachedGroupedShortCode
{
Value = mostFrequentValue,
Timestamp = DateTimeOffset.UtcNow,
Source = GetType().Name,
Version = 1
};
await cacheService.SetCachedDataAsync(cacheKey, toCache, TimeSpan.FromHours(1));
return mostFrequentValue;
}
return "Нет данных";
}
private async Task<string> ReplaceLettersShortcodesAsync(Guid unitId, string input, MatchCollection lettersShortcodes)
{
var shortcodeToMatches = lettersShortcodes
@@ -485,5 +498,12 @@ namespace PARR.DAL.DomainServices.Shortcodes
}
return result.ToString();
}
private static string ComputeHash(string input)
{
using var sha256 = System.Security.Cryptography.SHA256.Create();
var hashedBytes = sha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes(input));
return Convert.ToBase64String(hashedBytes).Replace('+', '-').Replace('/', '_').Substring(0, 16);
}
}
}