feat(api, core): Workload - статус формирования отчетности. RedisCacheService - получить первую запись из кэш типа Hash.
This commit is contained in:
@@ -303,6 +303,8 @@
|
|||||||
public const string WorkGroup = BaseStat + "/workload/work-groups";
|
public const string WorkGroup = BaseStat + "/workload/work-groups";
|
||||||
|
|
||||||
public const string ResponseArea = BaseStat + "/workload/response-areas";
|
public const string ResponseArea = BaseStat + "/workload/response-areas";
|
||||||
|
|
||||||
|
public const string ReportInfo = BaseStat + "/workload/report-info";
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using PARR.Domain.DTOs.Workload;
|
||||||
|
|
||||||
|
namespace PARR.API.Contracts.V1.Responses.Statistics
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Инфо о формирование КЭШ для отчета Workload
|
||||||
|
/// </summary>
|
||||||
|
public record StatWorkloadCacheInfoResponse
|
||||||
|
{
|
||||||
|
public WorkloadCacheInfo.StatusEnum Status { get; set; }
|
||||||
|
|
||||||
|
public DateTimeOffset? Date { get; init; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -70,8 +70,6 @@ namespace PARR.API.Controllers.V1.Statistics
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Сформировать отчетность
|
/// Сформировать отчетность
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -110,6 +108,22 @@ namespace PARR.API.Controllers.V1.Statistics
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получить информацию о формировании КЭШ
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
[HttpGet(ApiRoutes.Workload.ReportInfo)]
|
||||||
|
public async Task<IActionResult> GetReportInfo()
|
||||||
|
{
|
||||||
|
var report = await workloadService.GetReportInfoAsync();
|
||||||
|
|
||||||
|
var response = mapper.Map<StatWorkloadCacheInfoResponse>(report);
|
||||||
|
|
||||||
|
return Ok(new Response<StatWorkloadCacheInfoResponse>(response, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Универсальный метод получения отчетности
|
/// Универсальный метод получения отчетности
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -123,7 +137,8 @@ namespace PARR.API.Controllers.V1.Statistics
|
|||||||
var report = await workloadService.GetWorkloadReport(reportType, dateStart, durationDays, offset, filter);
|
var report = await workloadService.GetWorkloadReport(reportType, dateStart, durationDays, offset, filter);
|
||||||
|
|
||||||
if (report == null)
|
if (report == null)
|
||||||
return BadRequest(new Response(false, new List<ErrorModel> { new ErrorModel { Message = "Необходимо сформировать отчетность." } }));
|
//return BadRequest(new Response(false, new List<ErrorModel> { new ErrorModel { Message = "Необходимо сформировать отчетность." } }));
|
||||||
|
return BadRequest(new Response<string?>(null, false, new List<ErrorModel> { new ErrorModel { Message = "Необходимо сформировать отчетность." } }, "empty"));
|
||||||
|
|
||||||
var resonse = mapper.Map<StatWorkloadReportResponse>(report);
|
var resonse = mapper.Map<StatWorkloadReportResponse>(report);
|
||||||
|
|
||||||
|
|||||||
@@ -444,6 +444,10 @@ namespace PARR.API.MappingProfiles
|
|||||||
|
|
||||||
CreateMap<WorkloadDailyItem, WorkloadDailyItemResponse>();
|
CreateMap<WorkloadDailyItem, WorkloadDailyItemResponse>();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
CreateMap<WorkloadCacheInfo, StatWorkloadCacheInfoResponse>();
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -94,6 +94,14 @@
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Task<T?> GetHashFieldAsync<T>(string hashKey, string field);
|
Task<T?> GetHashFieldAsync<T>(string hashKey, string field);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получить случайную первую запись из Hash
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <param name="hashKey"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task<(string Field, T? Value)?> GetFirstHashFieldAsync<T>(string hashKey);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Получить все значения из Hash
|
/// Получить все значения из Hash
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -328,6 +328,18 @@ namespace PARR.Core.Services.Workload.Implementations
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Вернуть первую запись из TamplateCache если есть
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<TemplateCache?> GetFirstTemplateCacheAsync()
|
||||||
|
{
|
||||||
|
var cache = await redisCacheService.GetFirstHashFieldAsync<TemplateCache>(redisCacheService.GetKey(cacheTemplateKey));
|
||||||
|
|
||||||
|
return cache?.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Формирует ключ для отчета Workload
|
/// Формирует ключ для отчета Workload
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using PARR.Core.Services.NextRunServices;
|
using PARR.Core.Services.NextRunServices;
|
||||||
|
using PARR.Core.Services.TaskServices.Interfaces;
|
||||||
using PARR.Core.Services.Workload.Interfaces;
|
using PARR.Core.Services.Workload.Interfaces;
|
||||||
using PARR.Core.Services.Workload.Models;
|
using PARR.Core.Services.Workload.Models;
|
||||||
|
using PARR.Domain.DTOs.TaskDTO;
|
||||||
using PARR.Domain.DTOs.Workload;
|
using PARR.Domain.DTOs.Workload;
|
||||||
using PARR.Domain.Enums;
|
using PARR.Domain.Enums;
|
||||||
|
|
||||||
@@ -12,16 +14,19 @@ namespace PARR.Core.Services.Workload.Implementations
|
|||||||
private readonly ILogger<WorkloadService> logger;
|
private readonly ILogger<WorkloadService> logger;
|
||||||
private readonly WorkloadCacheService workloadCacheService;
|
private readonly WorkloadCacheService workloadCacheService;
|
||||||
private readonly INextRunService nextRunService;
|
private readonly INextRunService nextRunService;
|
||||||
|
private readonly ITaskManagementService taskManagementService;
|
||||||
|
|
||||||
public WorkloadService(
|
public WorkloadService(
|
||||||
ILogger<WorkloadService> logger,
|
ILogger<WorkloadService> logger,
|
||||||
WorkloadCacheService workloadCacheService,
|
WorkloadCacheService workloadCacheService,
|
||||||
INextRunService nextRunService
|
INextRunService nextRunService,
|
||||||
|
ITaskManagementService taskManagementService
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
this.workloadCacheService = workloadCacheService;
|
this.workloadCacheService = workloadCacheService;
|
||||||
this.nextRunService = nextRunService;
|
this.nextRunService = nextRunService;
|
||||||
|
this.taskManagementService = taskManagementService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -107,6 +112,39 @@ namespace PARR.Core.Services.Workload.Implementations
|
|||||||
return report;
|
return report;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task<WorkloadCacheInfo> GetReportInfoAsync()
|
||||||
|
{
|
||||||
|
// может быть три статуса
|
||||||
|
// 1 - отчет сейчас формируется (дата начала формирования отчета)
|
||||||
|
// 2 - отчет сформирован (вернуть дату формирования кэш)
|
||||||
|
// 3 - отчет сейчас не формируется, кэша нет (date = null)
|
||||||
|
|
||||||
|
|
||||||
|
// 1. проверить что выполнились все задания на обновление кэш
|
||||||
|
var activeTasks = await taskManagementService.GetActiveTasksAsync(TaskTypeEnum.Workload);
|
||||||
|
if (activeTasks.Count > 0)
|
||||||
|
{
|
||||||
|
// идет формирование отчета
|
||||||
|
// задача может быт только одна, так как у нее такой тип
|
||||||
|
var task = activeTasks.First();
|
||||||
|
|
||||||
|
return new WorkloadCacheInfo { Status = WorkloadCacheInfo.StatusEnum.Processing, Date = task.DateCreated };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. получить дату кэш
|
||||||
|
// Если нет активных задач, значит может быть кэш существует, пробуем получить дату из кэш
|
||||||
|
var templateCache = await workloadCacheService.GetFirstTemplateCacheAsync();
|
||||||
|
if (templateCache != null)
|
||||||
|
{
|
||||||
|
return new WorkloadCacheInfo { Status = WorkloadCacheInfo.StatusEnum.Generated, Date = templateCache.Timestamp };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если кэш нет, то возвращаем null
|
||||||
|
return new WorkloadCacheInfo { Status = WorkloadCacheInfo.StatusEnum.NotFound };
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Получить список дней для отчета, с отметкой выходной/рабочий
|
/// Получить список дней для отчета, с отметкой выходной/рабочий
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -8,6 +8,12 @@ namespace PARR.Core.Services.Workload.Interfaces
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IWorkloadService
|
public interface IWorkloadService
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Получить статус кэш/отчета
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task<WorkloadCacheInfo> GetReportInfoAsync();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Получить отчет о загруженности
|
/// Получить отчет о загруженности
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -5,10 +5,19 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IBaseCache<T>
|
public interface IBaseCache<T>
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Объект
|
||||||
|
/// </summary>
|
||||||
public T Data { get; set; }
|
public T Data { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Дата формирования Cache
|
||||||
|
/// </summary>
|
||||||
public DateTimeOffset Timestamp { get; set; }
|
public DateTimeOffset Timestamp { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Источник Cache (class)
|
||||||
|
/// </summary>
|
||||||
public string Source { get; set; }
|
public string Source { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
36
PARR.Domain/DTOs/Workload/WorkloadCacheInfo.cs
Normal file
36
PARR.Domain/DTOs/Workload/WorkloadCacheInfo.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace PARR.Domain.DTOs.Workload
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Инфо о состоянии КЭШ для отчета Workload
|
||||||
|
/// </summary>
|
||||||
|
public record WorkloadCacheInfo
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Статус отчета
|
||||||
|
/// </summary>
|
||||||
|
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||||
|
public enum StatusEnum
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Отчет формируется
|
||||||
|
/// </summary>
|
||||||
|
Processing = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Отчет сформирован
|
||||||
|
/// </summary>
|
||||||
|
Generated = 2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Нет отчета
|
||||||
|
/// </summary>
|
||||||
|
NotFound = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
public StatusEnum Status { get; init; }
|
||||||
|
|
||||||
|
public DateTimeOffset? Date { get; init; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -213,6 +213,38 @@ namespace PARR.Infrastructure.Redis
|
|||||||
return JsonSerializer.Deserialize<T>(value);
|
return JsonSerializer.Deserialize<T>(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<(string Field, T? Value)?> GetFirstHashFieldAsync<T>(string hashKey)
|
||||||
|
{
|
||||||
|
logger.LogDebug("Запрос первого поля из хеша '{HashKey}'", hashKey);
|
||||||
|
|
||||||
|
int attempts = 0;
|
||||||
|
int maxAttempts = 5;
|
||||||
|
|
||||||
|
await foreach (var entry in redis.HashScanAsync(hashKey, pageSize: 1))
|
||||||
|
{
|
||||||
|
if (++attempts > maxAttempts)
|
||||||
|
{
|
||||||
|
logger.LogWarning("Превышен лимит попыток ({Max}) для хеша '{HashKey}'", maxAttempts, hashKey);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.Value.IsNullOrEmpty)
|
||||||
|
{
|
||||||
|
logger.LogDebug("Поле '{Field}' в хеше '{HashKey}' пустое, пропускаем", entry.Name, hashKey);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var deserialized = JsonSerializer.Deserialize<T>(entry.Value);
|
||||||
|
|
||||||
|
logger.LogDebug("Получено поле '{Field}' из хеша '{HashKey}', тип: {Type}", entry.Name, hashKey, typeof(T).Name);
|
||||||
|
|
||||||
|
return (entry.Name.ToString(), deserialized);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.LogDebug("Хеш '{HashKey}' пуст или не существует", hashKey);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<Dictionary<string, T>> GetAllHashFieldsAsync<T>(string hashKey)
|
public async Task<Dictionary<string, T>> GetAllHashFieldsAsync<T>(string hashKey)
|
||||||
{
|
{
|
||||||
// получить все записи из Hash
|
// получить все записи из Hash
|
||||||
|
|||||||
Reference in New Issue
Block a user