140 lines
4.8 KiB
C#
140 lines
4.8 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Hosting;
|
|
using Microsoft.Extensions.Logging;
|
|
using SPMS.Domain.Entities;
|
|
using SPMS.Domain.Enums;
|
|
using SPMS.Domain.Interfaces;
|
|
using SPMS.Infrastructure.Persistence;
|
|
|
|
namespace SPMS.Infrastructure.Workers;
|
|
|
|
public class DailyStatWorker : BackgroundService
|
|
{
|
|
private readonly IServiceScopeFactory _scopeFactory;
|
|
private readonly ILogger<DailyStatWorker> _logger;
|
|
|
|
private static readonly TimeZoneInfo KstZone = TimeZoneInfo.FindSystemTimeZoneById("Asia/Seoul");
|
|
|
|
public DailyStatWorker(
|
|
IServiceScopeFactory scopeFactory,
|
|
ILogger<DailyStatWorker> logger)
|
|
{
|
|
_scopeFactory = scopeFactory;
|
|
_logger = logger;
|
|
}
|
|
|
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
|
{
|
|
_logger.LogInformation("DailyStatWorker 시작");
|
|
|
|
while (!stoppingToken.IsCancellationRequested)
|
|
{
|
|
var delay = CalculateDelayUntilNextRun();
|
|
_logger.LogInformation("DailyStatWorker 다음 실행까지 {Delay} 대기", delay);
|
|
|
|
try
|
|
{
|
|
await Task.Delay(delay, stoppingToken);
|
|
}
|
|
catch (TaskCanceledException)
|
|
{
|
|
break;
|
|
}
|
|
|
|
try
|
|
{
|
|
await AggregateAsync(stoppingToken);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "DailyStatWorker 집계 중 오류 발생");
|
|
}
|
|
}
|
|
}
|
|
|
|
private TimeSpan CalculateDelayUntilNextRun()
|
|
{
|
|
var nowKst = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, KstZone);
|
|
var nextRun = nowKst.Date.AddHours(0).AddMinutes(5);
|
|
|
|
if (nowKst >= nextRun)
|
|
nextRun = nextRun.AddDays(1);
|
|
|
|
var delay = nextRun - nowKst;
|
|
return delay;
|
|
}
|
|
|
|
private async Task AggregateAsync(CancellationToken stoppingToken)
|
|
{
|
|
using var scope = _scopeFactory.CreateScope();
|
|
var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
|
var dailyStatRepo = scope.ServiceProvider.GetRequiredService<IDailyStatRepository>();
|
|
var unitOfWork = scope.ServiceProvider.GetRequiredService<IUnitOfWork>();
|
|
|
|
var nowKst = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, KstZone);
|
|
var yesterday = DateOnly.FromDateTime(nowKst.AddDays(-1));
|
|
var startUtc = TimeZoneInfo.ConvertTimeToUtc(yesterday.ToDateTime(TimeOnly.MinValue), KstZone);
|
|
var endUtc = startUtc.AddDays(1);
|
|
|
|
_logger.LogInformation("DailyStatWorker 집계 시작: {Date}", yesterday);
|
|
|
|
// 서비스별 발송 통계 집계
|
|
var sendStats = await context.Set<PushSendLog>()
|
|
.Where(l => l.SentAt >= startUtc && l.SentAt < endUtc)
|
|
.GroupBy(l => l.ServiceId)
|
|
.Select(g => new
|
|
{
|
|
ServiceId = g.Key,
|
|
SentCnt = g.Count(),
|
|
SuccessCnt = g.Count(l => l.Status == PushResult.Success),
|
|
FailCnt = g.Count(l => l.Status == PushResult.Failed)
|
|
})
|
|
.ToListAsync(stoppingToken);
|
|
|
|
// 서비스별 열람 통계 집계
|
|
var openStats = await context.Set<PushOpenLog>()
|
|
.Where(l => l.OpenedAt >= startUtc && l.OpenedAt < endUtc)
|
|
.GroupBy(l => l.ServiceId)
|
|
.Select(g => new
|
|
{
|
|
ServiceId = g.Key,
|
|
OpenCnt = g.Count()
|
|
})
|
|
.ToListAsync(stoppingToken);
|
|
|
|
var openDict = openStats.ToDictionary(o => o.ServiceId, o => o.OpenCnt);
|
|
|
|
// 모든 서비스 ID 수집 (발송 + 열람)
|
|
var allServiceIds = sendStats.Select(s => s.ServiceId)
|
|
.Union(openStats.Select(o => o.ServiceId))
|
|
.Distinct();
|
|
|
|
foreach (var serviceId in allServiceIds)
|
|
{
|
|
var send = sendStats.FirstOrDefault(s => s.ServiceId == serviceId);
|
|
var openCnt = openDict.GetValueOrDefault(serviceId, 0);
|
|
|
|
await dailyStatRepo.UpsertAsync(
|
|
serviceId, yesterday,
|
|
send?.SentCnt ?? 0,
|
|
send?.SuccessCnt ?? 0,
|
|
send?.FailCnt ?? 0,
|
|
openCnt);
|
|
}
|
|
|
|
await unitOfWork.SaveChangesAsync();
|
|
|
|
// SystemLog에 집계 완료 기록
|
|
context.Set<SystemLog>().Add(new SystemLog
|
|
{
|
|
Action = "DailyStatAggregation",
|
|
Details = $"{yesterday:yyyy-MM-dd} 통계 집계 완료 (서비스 {allServiceIds.Count()}개)",
|
|
CreatedAt = DateTime.UtcNow
|
|
});
|
|
await context.SaveChangesAsync(stoppingToken);
|
|
|
|
_logger.LogInformation("DailyStatWorker 집계 완료: {Date}, 서비스 {Count}개", yesterday, allServiceIds.Count());
|
|
}
|
|
}
|