feat(api,core,domain): Статистика по роботам, кол-во работающих роботов в час
This commit is contained in:
@@ -19,10 +19,19 @@ namespace PARR.Core.Services.RobotSnapshotServices
|
||||
Task<RobotSnapshotItemDto> CreateAsync(CreateRobotSnapshot robotSnapshot);
|
||||
|
||||
/// <summary>
|
||||
/// Получить аналитику за период
|
||||
/// Получить аналитику за период с группировкой по ip серверов. Разбивка по часам.
|
||||
/// Максимальная одновременная работа роботов. Среднее кол-во роботов в час. Максимально возможное кол-во роботов.
|
||||
/// </summary>
|
||||
/// <param name="queryDto"></param>
|
||||
/// <returns></returns>
|
||||
Task<List<ServerHourlyAnalyticsDto>> GetHourlyAnalyticsAsync(RobotAnalyticsQuery queryDto);
|
||||
|
||||
/// <summary>
|
||||
/// Получить аналитику за период по типу робота. Разбивка по часам.
|
||||
/// Среднее кол-во роботов в час.
|
||||
/// </summary>
|
||||
/// <param name="queryDto"></param>
|
||||
/// <returns></returns>
|
||||
Task<List<HourlyRobotTypeAnalyticsDto>> GetHourlyAnalyticsByRobotTypeAsync(RobotAnalyticsRobotTypeQuery queryDto);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,9 +11,9 @@ namespace PARR.Core.Services.RobotSnapshotServices
|
||||
{
|
||||
internal class RobotSnapshotService : IRobotSnapshotService
|
||||
{
|
||||
private readonly IRobotSnapshotRepository robotSnapshotRepository;
|
||||
private readonly IUserRepository userRepository;
|
||||
private readonly IMapper mapper;
|
||||
private readonly IRobotSnapshotRepository _robotSnapshotRepository;
|
||||
private readonly IUserRepository _userRepository;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public RobotSnapshotService(
|
||||
IRobotSnapshotRepository robotSnapshotRepository,
|
||||
@@ -21,9 +21,9 @@ namespace PARR.Core.Services.RobotSnapshotServices
|
||||
IMapper mapper
|
||||
)
|
||||
{
|
||||
this.robotSnapshotRepository = robotSnapshotRepository;
|
||||
this.userRepository = userRepository;
|
||||
this.mapper = mapper;
|
||||
_robotSnapshotRepository = robotSnapshotRepository;
|
||||
_userRepository = userRepository;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace PARR.Core.Services.RobotSnapshotServices
|
||||
var dateEnd = DateTimeOffset.UtcNow;
|
||||
var dateStart = dateEnd.AddMinutes(-minutes);
|
||||
|
||||
var query = robotSnapshotRepository.Get().AsNoTracking()
|
||||
var query = _robotSnapshotRepository.Get().AsNoTracking()
|
||||
.Where(t => dateStart <= t.DateCreated && t.DateCreated <= dateEnd);
|
||||
|
||||
if (!string.IsNullOrEmpty(queryDto.Ip))
|
||||
@@ -47,7 +47,7 @@ namespace PARR.Core.Services.RobotSnapshotServices
|
||||
|
||||
|
||||
var robotIps = groupingSnapshots.Select(t => t.Key).ToHashSet();
|
||||
var robotList = await userRepository.Get()
|
||||
var robotList = await _userRepository.Get()
|
||||
.AsNoTracking()
|
||||
.Where(t => robotIps.Contains(t.Ip))
|
||||
.ToDictionaryAsync(t => t.Ip);
|
||||
@@ -59,7 +59,7 @@ namespace PARR.Core.Services.RobotSnapshotServices
|
||||
robotList.TryGetValue(item.Key, out var robot);
|
||||
|
||||
var userDto = new UserBaseDto { Ip = item.Key, Description = robot?.Description ?? string.Empty, Name = robot?.Name ?? string.Empty };
|
||||
var snapshots = mapper.Map<List<RobotSnapshotDto>>(item).OrderBy(t => t.DateCreated).ToList();
|
||||
var snapshots = _mapper.Map<List<RobotSnapshotDto>>(item).OrderBy(t => t.DateCreated).ToList();
|
||||
|
||||
result.Add(new RobotSnapshotItemDto { Robot = userDto, Snapshots = snapshots });
|
||||
}
|
||||
@@ -70,7 +70,7 @@ namespace PARR.Core.Services.RobotSnapshotServices
|
||||
|
||||
public async Task<List<ServerHourlyAnalyticsDto>> GetHourlyAnalyticsAsync(RobotAnalyticsQuery queryDto)
|
||||
{
|
||||
var snapshotsQuery = robotSnapshotRepository.Get()
|
||||
var snapshotsQuery = _robotSnapshotRepository.Get()
|
||||
.AsNoTracking()
|
||||
.Where(t => queryDto.DateStart <= t.DateCreated && t.DateCreated <= queryDto.DateEnd);
|
||||
|
||||
@@ -78,9 +78,6 @@ namespace PARR.Core.Services.RobotSnapshotServices
|
||||
if (!string.IsNullOrEmpty(queryDto.Ip))
|
||||
snapshotsQuery = snapshotsQuery.Where(t => t.Ip == queryDto.Ip);
|
||||
|
||||
// Превращает TimeSpan в строку вида "+03:00" или "-05:00"
|
||||
string formattedOffset = (queryDto.Offset >= TimeSpan.Zero ? "+" : "-") +
|
||||
queryDto.Offset.ToString(@"hh\:mm");
|
||||
|
||||
// Группируем на стороне PostgreSQL с учетом часового пояса пользователя.
|
||||
// Метод .Add(offset) сдвигает UTC-дату в Postgres на интервал времени пользователя,
|
||||
@@ -117,12 +114,12 @@ namespace PARR.Core.Services.RobotSnapshotServices
|
||||
|
||||
// Получаем информацию о серверах
|
||||
var serverIps = rawGroupedData.Select(g => g.Ip).ToHashSet();
|
||||
var serverDictionary = await userRepository.Get()
|
||||
var serverDictionary = await _userRepository.Get()
|
||||
.AsNoTracking()
|
||||
.Where(t => serverIps.Contains(t.Ip))
|
||||
.ToDictionaryAsync(t => t.Ip);
|
||||
|
||||
// Группируем полученные агрегаты по серверам уже в памяти веб-сервера
|
||||
// Группируем полученные агрегаты по серверам уже в памяти
|
||||
var dataByServers = rawGroupedData.GroupBy(g => g.Ip);
|
||||
var result = new List<ServerHourlyAnalyticsDto>();
|
||||
|
||||
@@ -206,6 +203,103 @@ namespace PARR.Core.Services.RobotSnapshotServices
|
||||
}
|
||||
|
||||
|
||||
public async Task<List<HourlyRobotTypeAnalyticsDto>> GetHourlyAnalyticsByRobotTypeAsync(RobotAnalyticsRobotTypeQuery queryDto)
|
||||
{
|
||||
var snapshotsQuery = _robotSnapshotRepository.Get()
|
||||
.AsNoTracking()
|
||||
.Where(t => queryDto.DateStart <= t.DateCreated && t.DateCreated <= queryDto.DateEnd)
|
||||
.GroupBy(t => new
|
||||
{
|
||||
// Явно сдвигаем дату
|
||||
Year = (t.DateCreated + queryDto.Offset).DateTime.Year,
|
||||
Month = (t.DateCreated + queryDto.Offset).DateTime.Month,
|
||||
Day = (t.DateCreated + queryDto.Offset).DateTime.Day,
|
||||
Hour = (t.DateCreated + queryDto.Offset).DateTime.Hour
|
||||
});
|
||||
|
||||
IQueryable<RobotSnapshotRobotTypeDbDto> groupedQuery;
|
||||
|
||||
switch (queryDto.RobotType)
|
||||
{
|
||||
case Domain.Enums.RobotsEnum.TemplateOrder:
|
||||
groupedQuery = snapshotsQuery.Select(g => new RobotSnapshotRobotTypeDbDto
|
||||
{
|
||||
// Вытаскиваем компоненты даты из уже смещенного локального времени
|
||||
LocalYear = g.Key.Year,
|
||||
LocalMonth = g.Key.Month,
|
||||
LocalDay = g.Key.Day,
|
||||
LocalHour = g.Key.Hour,
|
||||
|
||||
// Макс не правильно.
|
||||
//MaxAllowed = g.Max(t => t.MaxRobots),
|
||||
Sum = g.Sum(t => t.TemplateRobotsCount)
|
||||
});
|
||||
break;
|
||||
case Domain.Enums.RobotsEnum.ScheduleOrder:
|
||||
groupedQuery = snapshotsQuery.Select(g => new RobotSnapshotRobotTypeDbDto
|
||||
{
|
||||
// Вытаскиваем компоненты даты из уже смещенного локального времени
|
||||
LocalYear = g.Key.Year,
|
||||
LocalMonth = g.Key.Month,
|
||||
LocalDay = g.Key.Day,
|
||||
LocalHour = g.Key.Hour,
|
||||
|
||||
// Макс не правильно.
|
||||
//MaxAllowed = g.Max(t => t.MaxRobots),
|
||||
Sum = g.Sum(t => t.ScheduleRobotsCount)
|
||||
});
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException("Не обработано значение параметра", nameof(queryDto.RobotType));
|
||||
}
|
||||
|
||||
var data = await groupedQuery.ToListAsync();
|
||||
|
||||
if (!data.Any())
|
||||
return new List<HourlyRobotTypeAnalyticsDto>();
|
||||
|
||||
var dbDataDict = data.ToDictionary(
|
||||
k => new DateTime(k.LocalYear, k.LocalMonth, k.LocalDay, k.LocalHour, 0, 0),
|
||||
v => new
|
||||
{
|
||||
//MaxAllowedRobots = t.MaxAllowed,
|
||||
AvgRobots = Math.Round(v.Sum / 60.0, 1)
|
||||
});
|
||||
|
||||
// Заполняем модель
|
||||
var statistics = new List<HourlyRobotTypeAnalyticsDto>();
|
||||
|
||||
#region Алгоритм заполнения пропусков во времени (если сервер был выключен)
|
||||
// Превращаем границы в локальное время для генерации сетки часов
|
||||
var currentHour = queryDto.DateStart.ToOffset(queryDto.Offset).DateTime;
|
||||
var localEnd = queryDto.DateEnd.ToOffset(queryDto.Offset).DateTime;
|
||||
|
||||
// Срезаем минуты/секунды для старта
|
||||
currentHour = new DateTime(currentHour.Year, currentHour.Month, currentHour.Day, currentHour.Hour, 0, 0);
|
||||
|
||||
while (currentHour <= localEnd)
|
||||
{
|
||||
// Ищем значение в словаре. Если нет — ставим 0
|
||||
dbDataDict.TryGetValue(currentHour, out var statInfo);
|
||||
|
||||
// 1. Создаем DateTimeOffset для локального времени с правильным смещением
|
||||
var localDateTimeOffset = new DateTimeOffset(currentHour, queryDto.Offset);
|
||||
|
||||
statistics.Add(new HourlyRobotTypeAnalyticsDto
|
||||
{
|
||||
// Возвращаем DateTimeOffset с нулевым смещением (как в вашем коде) или с queryDto.Offset
|
||||
Hour = localDateTimeOffset.ToUniversalTime(),
|
||||
AvgRobots = statInfo?.AvgRobots ?? 0
|
||||
});
|
||||
|
||||
currentHour = currentHour.AddHours(1);
|
||||
}
|
||||
#endregion
|
||||
|
||||
return statistics;
|
||||
}
|
||||
|
||||
|
||||
public async Task<RobotSnapshotItemDto> CreateAsync(CreateRobotSnapshot robotSnapshot)
|
||||
{
|
||||
var snapshot = new RobotSnapshot
|
||||
@@ -217,19 +311,19 @@ namespace PARR.Core.Services.RobotSnapshotServices
|
||||
Ip = robotSnapshot.Ip
|
||||
};
|
||||
|
||||
var createdResult = await robotSnapshotRepository.CreateAsync(snapshot);
|
||||
var commitResult = await robotSnapshotRepository.CommitAsync();
|
||||
var createdResult = await _robotSnapshotRepository.CreateAsync(snapshot);
|
||||
var commitResult = await _robotSnapshotRepository.CommitAsync();
|
||||
|
||||
if (!createdResult || !commitResult)
|
||||
throw new DbErrorException("Ошибка сохранения в БД");
|
||||
|
||||
var user = await userRepository.Get().AsNoTracking().FirstOrDefaultAsync(t => t.Ip == robotSnapshot.Ip);
|
||||
var user = await _userRepository.Get().AsNoTracking().FirstOrDefaultAsync(t => t.Ip == robotSnapshot.Ip);
|
||||
var userDto = new UserBaseDto { Ip = robotSnapshot.Ip, Description = user?.Description ?? string.Empty, Name = user?.Name ?? string.Empty };
|
||||
|
||||
var result = new RobotSnapshotItemDto
|
||||
{
|
||||
Robot = userDto,
|
||||
Snapshots = mapper.Map<List<RobotSnapshotDto>>(new List<RobotSnapshot> { snapshot })
|
||||
Snapshots = _mapper.Map<List<RobotSnapshotDto>>(new List<RobotSnapshot> { snapshot })
|
||||
};
|
||||
|
||||
return result;
|
||||
|
||||
Reference in New Issue
Block a user