feat(api,core,infrastructure): Workload - дополнительная фильтрация по свяанным ЗО и РГ. RedisCacheService - при формирование hash key, дополнительная фильтрация на null, empty.

This commit is contained in:
Mikhail Trubnikov
2026-06-09 10:16:32 +10:00
parent 8cb7a2d599
commit 6fa9c3d445
7 changed files with 102 additions and 77 deletions

View File

@@ -415,9 +415,9 @@ namespace PARR.Core.Services.Workload.Implementations
/// <param name="offset"></param>
/// <param name="filterParam">Наименование ЗО или РГ или null</param>
/// <returns></returns>
public async Task<WorkloadReport?> GetWorkloadReportAsync(WorkloadReportType reportType, DateOnly dateStart, int durationDays, TimeSpan offset, string? filterParam)
public async Task<WorkloadReport?> GetWorkloadReportAsync(WorkloadReportType reportType, DateOnly dateStart, int durationDays, TimeSpan offset, string? responseArea, string? workGroup)
{
var key = GetReportCacheKey(reportType, dateStart, durationDays, offset, filterParam);
var key = GetReportCacheKey(reportType, dateStart, durationDays, offset, responseArea, workGroup);
return await redisCacheService.GetCachedDataAsync<WorkloadReport>(key, true);
}
@@ -433,9 +433,9 @@ namespace PARR.Core.Services.Workload.Implementations
/// <param name="offset"></param>
/// <param name="filterParam">Наименование ЗО или РГ или null</param>
/// <returns></returns>
public async Task SetWorkloadReport(WorkloadReport report, WorkloadReportType reportType, DateOnly dateStart, int durationDays, TimeSpan offset, string? filterParam)
public async Task SetWorkloadReport(WorkloadReport report, WorkloadReportType reportType, DateOnly dateStart, int durationDays, TimeSpan offset, string? responseArea, string? workGroup)
{
var key = GetReportCacheKey(reportType, dateStart, durationDays, offset, filterParam);
var key = GetReportCacheKey(reportType, dateStart, durationDays, offset, responseArea, workGroup);
await redisCacheService.SetCachedDataAsync(key, report, cacheWorkloadTtl, true);
}
@@ -462,10 +462,10 @@ namespace PARR.Core.Services.Workload.Implementations
/// <param name="offset"></param>
/// <param name="filterParam"></param>
/// <returns></returns>
private string GetReportCacheKey(WorkloadReportType reportType, DateOnly dateStart, int durationDays, TimeSpan offset, string? filterParam = null)
private string GetReportCacheKey(WorkloadReportType reportType, DateOnly dateStart, int durationDays, TimeSpan offset, string? responseArea, string? workGroup)
{
// формируе ключ, вида: основа + дата начала + продолжительность + смещение
// Если есть параметр (filterParam), добавляется параметр в виде Hash
// Если есть параметр (responseArea, workGroup), добавляется параметр в виде Hash
var keyList = new List<string>();
@@ -493,10 +493,10 @@ namespace PARR.Core.Services.Workload.Implementations
var keyStr = string.Empty;
if (filterParam == null)
if (responseArea == null && workGroup == null)
keyStr = redisCacheService.GetKey(keyList.ToArray());
else
keyStr = redisCacheService.GetKey(keyList.ToArray(), new string[] { filterParam });
keyStr = redisCacheService.GetKey(keyList.ToArray(), new string[] { responseArea ?? string.Empty, workGroup ?? string.Empty });
logger.LogDebug("Сформирован ключ для кэш '{Key}'", keyStr);

View File

@@ -8,6 +8,7 @@ using PARR.Core.Services.NextRunServices;
using PARR.Core.Services.TaskServices.Helpers;
using PARR.Core.Services.Workload.Interfaces;
using PARR.Core.Services.Workload.Models;
using PARR.Domain.Cache.Models;
using PARR.Domain.DTOs.Workload;
using PARR.Domain.Entities;
using PARR.Domain.Enums;
@@ -45,19 +46,25 @@ namespace PARR.Core.Services.Workload.Implementations
}
public async Task<WorkloadReport?> GetWorkloadReportAsync(WorkloadReportType reportType, DateOnly dateStart, int durationDays, TimeSpan offset, string? filterParam)
public async Task<WorkloadReport?> GetWorkloadReportAsync(WorkloadReportType reportType, DateOnly dateStart, int durationDays, TimeSpan offset, string? responseArea, string? workGroup)
{
logger.LogDebug("Начинаю формировать отчет о загруженности по параметрам: reportType '{ReportType}', dateStart {DateStart}, durationDays {DurationDays}, offset {Offset}",
reportType.ToString(), dateStart, durationDays, offset);
if ((reportType == WorkloadReportType.WorkGroupJob || reportType == WorkloadReportType.ResponseAreaWorkGroups) && string.IsNullOrEmpty(filterParam))
if (reportType == WorkloadReportType.WorkGroupJob && string.IsNullOrEmpty(workGroup))
{
logger.LogError("Для типа отчета {ReportType} не передан параметр.", reportType.ToString());
throw new ArgumentNullException(nameof(filterParam), $"Для отчета типа {reportType.ToString()} параметр является обязательным.");
throw new ArgumentNullException(nameof(workGroup), $"Для отчета типа {reportType.ToString()} параметр является обязательным.");
}
if (reportType == WorkloadReportType.ResponseAreaWorkGroups && string.IsNullOrEmpty(responseArea))
{
logger.LogError("Для типа отчета {ReportType} не передан параметр.", reportType.ToString());
throw new ArgumentNullException(nameof(responseArea), $"Для отчета типа {reportType.ToString()} параметр является обязательным.");
}
// Если есть отчет в КЭШ, вернуть
var cacheReport = await workloadCacheService.GetWorkloadReportAsync(reportType, dateStart, durationDays, offset, filterParam);
var cacheReport = await workloadCacheService.GetWorkloadReportAsync(reportType, dateStart, durationDays, offset, responseArea, workGroup);
if (cacheReport != null)
{
logger.LogDebug("Вернул отчет из кэша.");
@@ -92,11 +99,10 @@ namespace PARR.Core.Services.Workload.Implementations
case WorkloadReportType.ResponseAreaWorkGroups:
displayName = "По зоне ответственности";
// Фильтруем по ЗО
var filteredRA = templates.Where(t => string.Equals(t.ResponseArea, filterParam, StringComparison.OrdinalIgnoreCase)).ToList();
var filteredRA = templates.Where(t => string.Equals(t.ResponseArea, responseArea, StringComparison.OrdinalIgnoreCase)).ToList();
// Группируем работы по РГ
foreach (var group in filteredRA.GroupBy(t => t.WorkGroup, StringComparer.OrdinalIgnoreCase))
statistics.Add(BuildStatisticItem(group.Key, null, group, reportDays, offset));
//statistics.Add(BuildStatisticItem(filterParam!, filteredRA, reportDays, offset));
break;
case WorkloadReportType.WorkGroupAll:
displayName = "По рабочим группам";
@@ -106,7 +112,12 @@ namespace PARR.Core.Services.Workload.Implementations
case WorkloadReportType.WorkGroupJob:
displayName = "По рабочей группе";
// Фильтруем по РГ
var filteredWG = templates.Where(t => string.Equals(t.WorkGroup, filterParam, StringComparison.OrdinalIgnoreCase)).ToList();
var filteredWG = templates.Where(t => string.Equals(t.WorkGroup, workGroup, StringComparison.OrdinalIgnoreCase)).ToList();
if (!string.IsNullOrWhiteSpace(responseArea))
{
// Дополнительно фильтруем по ЗО
filteredWG = filteredWG.Where(t => string.Equals(t.ResponseArea, responseArea, StringComparison.OrdinalIgnoreCase)).ToList();
}
// Получим все JobName из бд
var jobIds = filteredWG.Select(t => t.JobId).ToHashSet();
@@ -123,11 +134,12 @@ namespace PARR.Core.Services.Workload.Implementations
break;
}
var report = new WorkloadReport
{
Type = reportType,
DisplayName = displayName,
SubfilterName = filterParam,
SubfilterName = string.Join(", ", new[] { responseArea, workGroup }),
ReportCacheCreatedAt = DateTimeOffset.UtcNow,
TemplateCacheCreatedAt = templates.FirstOrDefault()?.CacheDate ?? DateTimeOffset.MinValue,
Days = reportDays,
@@ -135,7 +147,7 @@ namespace PARR.Core.Services.Workload.Implementations
};
// Сохраним в кэш
await workloadCacheService.SetWorkloadReport(report, reportType, dateStart, durationDays, offset, filterParam);
await workloadCacheService.SetWorkloadReport(report, reportType, dateStart, durationDays, offset, responseArea, workGroup);
logger.LogDebug("Отчет построен и сохранен в кэш");
return report;
@@ -181,8 +193,8 @@ namespace PARR.Core.Services.Workload.Implementations
WorkloadTemplateReportState state,
DateOnly date,
TimeSpan offset,
WorkloadJobSubfliterType? jobSubfilterType,
string? jobSubfilterValue
string? responseArea,
string? workGroup
)
{
if (string.IsNullOrWhiteSpace(filter))
@@ -243,8 +255,8 @@ namespace PARR.Core.Services.Workload.Implementations
if (dbTemplates.Count == 0)
return new List<WorkloadTemplateReport>();
// Если тип - работы, и нет дополнительных фильтров по ЗО или РГ, не полезем в кэш, вернем сразу
if (reportType == WorkloadTemplateReportType.Job && (!jobSubfilterType.HasValue || string.IsNullOrWhiteSpace(jobSubfilterValue)))
// Если тип - работы, и нет дополнительных фильтров по ЗО и РГ, не полезем в кэш, вернем сразу
if (reportType == WorkloadTemplateReportType.Job && string.IsNullOrWhiteSpace(responseArea) && string.IsNullOrWhiteSpace(workGroup))
{
// Тут кэш не нужен, вернем сразу из бд (нет фильтров по РГ и ЗО, нужен список всех работ на дату)
return FilterWorkloadJobTemplateReport(dbTemplates, null);
@@ -259,41 +271,51 @@ namespace PARR.Core.Services.Workload.Implementations
if (dbTemplates.Count != cacheData.Count)
logger.LogWarning("Количество шаблонов в БД {DbCount} не совпадает с количеством шаблонов полученных из кэша {CacheCount}", dbTemplates.Count, cacheData.Count);
// Дополнительные фильтры
var trimmedResponseArea = responseArea?.Trim();
var trimmedWorkGroup = workGroup?.Trim();
// Фильтр по типу
switch (reportType)
{
case WorkloadTemplateReportType.Job:
// Смотрим есть ли дополнительные фильтры
if (jobSubfilterType.HasValue && !string.IsNullOrWhiteSpace(jobSubfilterValue))
if (!string.IsNullOrEmpty(trimmedResponseArea) || !string.IsNullOrEmpty(trimmedWorkGroup))
{
var cacheTemplatesJobs = new HashSet<Guid>();
// Фильтруем из кэша
var trimmedJobSubfilterValue = jobSubfilterValue.Trim();
switch (jobSubfilterType)
{
case WorkloadJobSubfliterType.ResponseArea:
cacheTemplatesJobs = cacheData.Where(t => string.Equals(t.Data.ResponseArea, trimmedJobSubfilterValue, StringComparison.OrdinalIgnoreCase))
.Select(t => t.Data.TemplateId).ToHashSet();
break;
case WorkloadJobSubfliterType.WorkGroup:
cacheTemplatesJobs = cacheData.Where(t => string.Equals(t.Data.WorkGroup, trimmedJobSubfilterValue, StringComparison.OrdinalIgnoreCase))
.Select(t => t.Data.TemplateId).ToHashSet();
break;
default:
throw new ArgumentException("Неверный тип фильтра", nameof(jobSubfilterType));
}
IEnumerable<TemplateCache> cacheTemplatesJobs = cacheData;
return FilterWorkloadJobTemplateReport(dbTemplates, cacheTemplatesJobs);
// Фильтруем по ЗО если есть
if (!string.IsNullOrEmpty(trimmedResponseArea))
cacheTemplatesJobs = cacheTemplatesJobs.Where(t => string.Equals(t.Data.ResponseArea, trimmedResponseArea, StringComparison.OrdinalIgnoreCase));
// Дополнительный фильтр по РГ, если есть
if (!string.IsNullOrEmpty(trimmedWorkGroup))
cacheTemplatesJobs = cacheTemplatesJobs.Where(t => string.Equals(t.Data.WorkGroup, trimmedWorkGroup, StringComparison.OrdinalIgnoreCase));
var filteredTemplateJobIds = cacheTemplatesJobs.Select(t => t.Data.TemplateId).ToHashSet();
return FilterWorkloadJobTemplateReport(dbTemplates, filteredTemplateJobIds);
}
return FilterWorkloadJobTemplateReport(dbTemplates, null);
case WorkloadTemplateReportType.WorkGroup:
var cacheTemplatesWg = cacheData.Where(t => string.Equals(t.Data.WorkGroup, trimmedFilter, StringComparison.OrdinalIgnoreCase))
.Select(t => t.Data.TemplateId).ToHashSet();
return dbTemplates.Where(t => cacheTemplatesWg.Contains(t.TemplateId)).ToList();
var cacheTemplatesWg = cacheData.Where(t => string.Equals(t.Data.WorkGroup, trimmedFilter, StringComparison.OrdinalIgnoreCase));
// Дополнительный фильтр по ЗО, если есть
if (!string.IsNullOrEmpty(trimmedResponseArea))
cacheTemplatesWg = cacheTemplatesWg.Where(t => string.Equals(t.Data.ResponseArea, trimmedResponseArea, StringComparison.OrdinalIgnoreCase));
var filteredTemplateWgIds = cacheTemplatesWg.Select(t => t.Data.TemplateId).ToHashSet();
return dbTemplates.Where(t => filteredTemplateWgIds.Contains(t.TemplateId)).ToList();
case WorkloadTemplateReportType.ResponseArea:
var cacheTemplatesRa = cacheData.Where(t => string.Equals(t.Data.ResponseArea, trimmedFilter, StringComparison.OrdinalIgnoreCase))
.Select(t => t.Data.TemplateId).ToHashSet();
return dbTemplates.Where(t => cacheTemplatesRa.Contains(t.TemplateId)).ToList();
var cacheTemplatesRa = cacheData.Where(t => string.Equals(t.Data.ResponseArea, trimmedFilter, StringComparison.OrdinalIgnoreCase));
// Дополнительный фильтр по РГ если есть
if (!string.IsNullOrEmpty(trimmedWorkGroup))
cacheTemplatesRa = cacheTemplatesRa.Where(t => string.Equals(t.Data.WorkGroup, trimmedWorkGroup, StringComparison.OrdinalIgnoreCase));
var filteredTemplateRaIds = cacheTemplatesRa.Select(t => t.Data.TemplateId).ToHashSet();
return dbTemplates.Where(t => filteredTemplateRaIds.Contains(t.TemplateId)).ToList();
}
logger.LogWarning("Не обработанный тип отчета {ReportType}", reportType);