feat(dal): уточнена логика получение имени связанных ЭК, переписано кэширование МАКС:FIELD, в синхронизацию и контролер заданий роботов добавлены include
This commit is contained in:
@@ -1,95 +0,0 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using PARR.DAL.CacheServices;
|
||||
using PARR.DAL.DomainServices.Interfaces;
|
||||
using PARR.DAL.Settings;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace PARR.DAL.DomainServices.Implementations
|
||||
{
|
||||
public class GroupedShortcodesCacheService : IGroupedShortcodesCacheService
|
||||
{
|
||||
private readonly IRedisCacheService cacheService;
|
||||
private readonly GroupedShortcodesCacheSettings settings;
|
||||
private readonly ILogger<GroupedShortcodesCacheService> logger;
|
||||
|
||||
public GroupedShortcodesCacheService(
|
||||
IRedisCacheService cacheService,
|
||||
GroupedShortcodesCacheSettings settings,
|
||||
ILogger<GroupedShortcodesCacheService> logger)
|
||||
{
|
||||
this.cacheService = cacheService;
|
||||
this.settings = settings;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public async Task<string> GetAggregatedValueAsync(
|
||||
Guid jobGroupId,
|
||||
Guid unitId,
|
||||
string shortcode,
|
||||
Func<Task<string>> computeIfMissing)
|
||||
{
|
||||
if (string.IsNullOrEmpty(shortcode))
|
||||
throw new ArgumentException("Ключ шорткода должен быть указан.", nameof(shortcode));
|
||||
|
||||
var cacheKey = GetCacheKey(jobGroupId, unitId, shortcode);
|
||||
|
||||
try
|
||||
{
|
||||
var cachedValue = await cacheService.GetCachedDataAsync<string>(cacheKey);
|
||||
if (cachedValue != null)
|
||||
{
|
||||
logger.LogDebug(
|
||||
"Попадание в кэш для шорткода '{ShortcodeKey}': unit={UnitId} → '{Value}'",
|
||||
shortcode, unitId, cachedValue);
|
||||
return cachedValue;
|
||||
}
|
||||
|
||||
logger.LogDebug(
|
||||
"Промах кэша для шорткода '{ShortcodeKey}': unit={UnitId}. Вычисление...",
|
||||
shortcode, unitId);
|
||||
|
||||
var computedValue = await computeIfMissing();
|
||||
|
||||
await cacheService.SetCachedDataAsync(cacheKey, computedValue, settings.ValueTtl);
|
||||
|
||||
logger.LogDebug(
|
||||
"Вычислено и сохранено значение для '{ShortcodeKey}': unit={UnitId} → '{Value}' (срок хранения={Ttl})",
|
||||
shortcode, unitId, computedValue, settings.ValueTtl);
|
||||
|
||||
return computedValue;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogWarning(
|
||||
ex,
|
||||
"Ошибка при получении или вычислении значения для шорткода '{ShortcodeKey}' (unit={UnitId}). Возвращена пустая строка.",
|
||||
shortcode, unitId);
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetCacheKey(Guid jobGroupId, Guid unitId, string shortcodeKey)
|
||||
{
|
||||
var safeKey = shortcodeKey
|
||||
.Trim()
|
||||
.Replace(":", "_")
|
||||
.Replace(" ", "_")
|
||||
.Replace(".", "_")
|
||||
.Replace("%", "")
|
||||
.Replace("[", "_")
|
||||
.Replace("]", "_")
|
||||
.Replace("/", "_")
|
||||
.Replace("\\", "_");
|
||||
|
||||
// ✅ SHA256 от safeKey
|
||||
using var sha256 = SHA256.Create();
|
||||
var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(safeKey));
|
||||
var hashHex = BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
|
||||
|
||||
// ✅ Новый формат ключа
|
||||
return $"gr_shcd_{jobGroupId:N}{unitId:N}_{hashHex}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -398,44 +398,68 @@ namespace PARR.DAL.DomainServices.Implementations
|
||||
|
||||
logger.LogDebug("Обработка UnitFilter.Id {FilterId}. Количество RelationshipFilters: {RelFilterCount}", filter.Id, filter.RelationshipFilters.Count());
|
||||
|
||||
foreach (var rf in filter.RelationshipFilters)
|
||||
// ✅ Получить все связи для юнита
|
||||
var parentLinks = await unitInUnitService.GetByChildIdAsync(unitId);
|
||||
var childLinks = await unitInUnitService.GetByParentIdAsync(unitId);
|
||||
|
||||
// ✅ Собрать все UnitId, участвующие в связях
|
||||
var allRelatedUnitIds = parentLinks
|
||||
.Select(l => l.ParentUnitId)
|
||||
.Concat(childLinks.Select(l => l.ChildUnitId))
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
if (!allRelatedUnitIds.Any()) continue;
|
||||
|
||||
// ✅ Получить значения для всех связанных юнитов
|
||||
var allUnitValues = await unitInValueService.GetByUnitIdsAsync(allRelatedUnitIds);
|
||||
|
||||
// ✅ Сгруппировать значения по UnitId
|
||||
var valuesByUnit = allUnitValues
|
||||
.GroupBy(uv => uv.UnitId)
|
||||
.ToDictionary(g => g.Key, g => g.ToList());
|
||||
|
||||
// ✅ Найти UnitId, которые проходят все RelationshipFilters
|
||||
var matchingUnitIds = new HashSet<Guid>();
|
||||
|
||||
foreach (var relatedUnitId in allRelatedUnitIds)
|
||||
{
|
||||
logger.LogDebug("Обработка RelationshipFilter (UnitFilterId={UnitFilterId}): IsParent={IsParent}, FieldId={FieldId}, ValueMask={ValueMask}",
|
||||
rf.UnitFilterId, rf.IsParent, rf.FieldId, rf.ValueMask);
|
||||
|
||||
var valueMask = rf.ValueMask?.Trim();
|
||||
if (string.IsNullOrEmpty(valueMask)) continue;
|
||||
|
||||
List<UnitInUnit> relevantLinks;
|
||||
if (rf.IsParent)
|
||||
relevantLinks = await unitInUnitService.GetByChildIdAsync(unitId);
|
||||
else
|
||||
relevantLinks = await unitInUnitService.GetByParentIdAsync(unitId);
|
||||
|
||||
var unitIdsToCheck = rf.IsParent
|
||||
? relevantLinks.Select(l => l.ParentUnitId).ToList()
|
||||
: relevantLinks.Select(l => l.ChildUnitId).ToList();
|
||||
|
||||
if (!unitIdsToCheck.Any()) continue;
|
||||
|
||||
var unitValues = await unitInValueService.GetByUnitIdsAsync(unitIdsToCheck);
|
||||
|
||||
var matchingUnitIds = unitValues
|
||||
.Where(uv => uv.FieldId == rf.FieldId
|
||||
&& uv.Value?.Value != null
|
||||
&& uv.Value.Value.Contains(valueMask, StringComparison.OrdinalIgnoreCase))
|
||||
.Select(uv => uv.UnitId)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
if (matchingUnitIds.Any())
|
||||
// ✅ Изменяем на `All` — все фильтры должны подходить
|
||||
bool passesAllFilters = filter.RelationshipFilters.All(rf =>
|
||||
{
|
||||
var names = await unitService.Get().AsNoTracking()
|
||||
.Where(u => matchingUnitIds.Contains(u.Id))
|
||||
.Select(u => u.Name)
|
||||
.ToListAsync();
|
||||
result.UnionWith(names);
|
||||
}
|
||||
var values = valuesByUnit.GetValueOrDefault(relatedUnitId, new List<UnitInValue>());
|
||||
|
||||
var matchingValues = values
|
||||
.Where(uv => uv.FieldId == rf.FieldId && uv.Value?.Value != null)
|
||||
.ToList();
|
||||
|
||||
if (!matchingValues.Any()) // Нет значений по полю
|
||||
{
|
||||
// ✅ Если нет значений, и IsInverse = false → не подходит
|
||||
// ✅ Если нет значений, и IsInverse = true → подходит
|
||||
return rf.IsInverse;
|
||||
}
|
||||
|
||||
var hasMatch = matchingValues.Any(uv => uv.Value!.Value!.Contains(rf.ValueMask.Trim('%'), StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
// ✅ Учитываем IsInverse
|
||||
if (rf.IsInverse)
|
||||
hasMatch = !hasMatch;
|
||||
|
||||
return hasMatch;
|
||||
});
|
||||
|
||||
if (passesAllFilters)
|
||||
matchingUnitIds.Add(relatedUnitId);
|
||||
}
|
||||
|
||||
if (matchingUnitIds.Any())
|
||||
{
|
||||
var names = await unitService.Get().AsNoTracking()
|
||||
.Where(u => matchingUnitIds.Contains(u.Id))
|
||||
.Select(u => u.Name)
|
||||
.ToListAsync();
|
||||
result.UnionWith(names);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -450,21 +474,25 @@ namespace PARR.DAL.DomainServices.Implementations
|
||||
if (string.IsNullOrEmpty(valueMask))
|
||||
return query;
|
||||
|
||||
logger.LogDebug("Применяем FieldFilterDto: FieldId={FieldId}, ValueMask={ValueMask}", fieldFilter.FieldId, valueMask);
|
||||
logger.LogDebug("Применяем FieldFilterDto: FieldId={FieldId}, ValueMask={ValueMask}, IsInverse={IsInverse}", fieldFilter.FieldId, valueMask, fieldFilter.IsInverse);
|
||||
|
||||
bool isStartsWith = valueMask.EndsWith("%") && !valueMask.EndsWith("%%");
|
||||
bool isEndsWith = valueMask.StartsWith("%") && !valueMask.StartsWith("%%");
|
||||
|
||||
return query.AsEnumerable().Where(dto =>
|
||||
dto.Values.Any(v =>
|
||||
{
|
||||
var hasMatch = dto.Values.Any(v =>
|
||||
v.FieldId == fieldFilter.FieldId &&
|
||||
v.Value != null &&
|
||||
(isStartsWith && isEndsWith ? v.Value.Contains(valueMask.Trim('%'), StringComparison.OrdinalIgnoreCase) :
|
||||
isStartsWith ? v.Value.StartsWith(valueMask.TrimEnd('%'), StringComparison.OrdinalIgnoreCase) :
|
||||
isEndsWith ? v.Value.EndsWith(valueMask.TrimStart('%'), StringComparison.OrdinalIgnoreCase) :
|
||||
v.Value.Contains(valueMask, StringComparison.OrdinalIgnoreCase))
|
||||
)
|
||||
).AsQueryable();
|
||||
);
|
||||
|
||||
// ✅ Правильная логика: если IsInverse = true, то юнит подходит, если НЕ проходит фильтр
|
||||
return fieldFilter.IsInverse ? !hasMatch : hasMatch;
|
||||
}).AsQueryable();
|
||||
}
|
||||
|
||||
private IQueryable<UnitDto> ApplyRelationshipFilterToQuery(IQueryable<UnitDto> query, JobRelationshipFilter relFilter)
|
||||
@@ -640,13 +668,12 @@ namespace PARR.DAL.DomainServices.Implementations
|
||||
// Есть фильтры по полям → считаем только связанные юниты, подходящие под фильтр
|
||||
foreach (var link in links)
|
||||
{
|
||||
bool hasMatch = link.Values.Any(v =>
|
||||
activeFilters.Any(f =>
|
||||
v.FieldId == f.FieldId &&
|
||||
v.Value != null &&
|
||||
v.Value.Contains(f.ValueMask, StringComparison.OrdinalIgnoreCase)
|
||||
)
|
||||
);
|
||||
// ✅ Изменяем на `All` — все фильтры должны подходить
|
||||
bool hasMatch = activeFilters.All(f =>
|
||||
{
|
||||
var value = link.Values.FirstOrDefault(v => v.FieldId == f.FieldId);
|
||||
return value != null && value.Value != null && value.Value.Contains(f.ValueMask, StringComparison.OrdinalIgnoreCase);
|
||||
});
|
||||
|
||||
if (hasMatch)
|
||||
matchingCount++;
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
namespace PARR.DAL.DomainServices.Interfaces
|
||||
{
|
||||
public interface IGroupedShortcodesCacheService
|
||||
{
|
||||
Task<string> GetAggregatedValueAsync(
|
||||
Guid jobGroupId,
|
||||
Guid unitId,
|
||||
string shortcodeKey,
|
||||
Func<Task<string>> computeIfMissing);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user