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 ResponseArea = BaseStat + "/workload/response-areas";
|
||||
|
||||
public const string ReportInfo = BaseStat + "/workload/report-info";
|
||||
}
|
||||
|
||||
#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>
|
||||
@@ -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>
|
||||
@@ -123,7 +137,8 @@ namespace PARR.API.Controllers.V1.Statistics
|
||||
var report = await workloadService.GetWorkloadReport(reportType, dateStart, durationDays, offset, filter);
|
||||
|
||||
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);
|
||||
|
||||
|
||||
@@ -444,6 +444,10 @@ namespace PARR.API.MappingProfiles
|
||||
|
||||
CreateMap<WorkloadDailyItem, WorkloadDailyItemResponse>();
|
||||
|
||||
|
||||
|
||||
CreateMap<WorkloadCacheInfo, StatWorkloadCacheInfoResponse>();
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
@@ -94,6 +94,14 @@
|
||||
/// <returns></returns>
|
||||
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>
|
||||
/// Получить все значения из Hash
|
||||
/// </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>
|
||||
/// Формирует ключ для отчета Workload
|
||||
/// </summary>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using PARR.Core.Services.NextRunServices;
|
||||
using PARR.Core.Services.TaskServices.Interfaces;
|
||||
using PARR.Core.Services.Workload.Interfaces;
|
||||
using PARR.Core.Services.Workload.Models;
|
||||
using PARR.Domain.DTOs.TaskDTO;
|
||||
using PARR.Domain.DTOs.Workload;
|
||||
using PARR.Domain.Enums;
|
||||
|
||||
@@ -12,16 +14,19 @@ namespace PARR.Core.Services.Workload.Implementations
|
||||
private readonly ILogger<WorkloadService> logger;
|
||||
private readonly WorkloadCacheService workloadCacheService;
|
||||
private readonly INextRunService nextRunService;
|
||||
private readonly ITaskManagementService taskManagementService;
|
||||
|
||||
public WorkloadService(
|
||||
ILogger<WorkloadService> logger,
|
||||
WorkloadCacheService workloadCacheService,
|
||||
INextRunService nextRunService
|
||||
INextRunService nextRunService,
|
||||
ITaskManagementService taskManagementService
|
||||
)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.workloadCacheService = workloadCacheService;
|
||||
this.nextRunService = nextRunService;
|
||||
this.taskManagementService = taskManagementService;
|
||||
}
|
||||
|
||||
|
||||
@@ -107,6 +112,39 @@ namespace PARR.Core.Services.Workload.Implementations
|
||||
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>
|
||||
|
||||
@@ -8,6 +8,12 @@ namespace PARR.Core.Services.Workload.Interfaces
|
||||
/// </summary>
|
||||
public interface IWorkloadService
|
||||
{
|
||||
/// <summary>
|
||||
/// Получить статус кэш/отчета
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<WorkloadCacheInfo> GetReportInfoAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Получить отчет о загруженности
|
||||
/// </summary>
|
||||
|
||||
@@ -5,10 +5,19 @@
|
||||
/// </summary>
|
||||
public interface IBaseCache<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Объект
|
||||
/// </summary>
|
||||
public T Data { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Дата формирования Cache
|
||||
/// </summary>
|
||||
public DateTimeOffset Timestamp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Источник Cache (class)
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
// получить все записи из Hash
|
||||
|
||||
Reference in New Issue
Block a user