YD_Event.Admin/YD_Event.Application/Event/EventStatusUpdateService.cs

167 lines
6.0 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using YD_Event.Core.Entities;
using YD_Event.Core.Enum;
namespace YD_Event.Application.Event;
/// <summary>
/// 赛事状态自动更新后台服务
/// 每10秒执行一次,自动更新赛事状态
/// </summary>
public class EventStatusUpdateService : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<EventStatusUpdateService> _logger;
private readonly PeriodicTimer _timer;
private int _executionCount = 0;
public EventStatusUpdateService(
IServiceProvider serviceProvider,
ILogger<EventStatusUpdateService> logger)
{
_serviceProvider = serviceProvider;
_logger = logger;
_timer = new PeriodicTimer(TimeSpan.FromSeconds(10));
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("赛事状态自动更新服务已启动,每10秒执行一次");
try
{
do
{
await UpdateEventStatusesAsync(stoppingToken);
} while (await _timer.WaitForNextTickAsync(stoppingToken));
}
catch (OperationCanceledException)
{
_logger.LogInformation("赛事状态自动更新服务已停止");
}
catch (Exception ex)
{
_logger.LogError(ex, "赛事状态自动更新服务发生异常");
}
}
private async Task UpdateEventStatusesAsync(CancellationToken stoppingToken)
{
try
{
_logger.LogDebug("开始执行赛事状态更新...");
using var scope = _serviceProvider.CreateScope();
var repository = scope.ServiceProvider.GetRequiredService<ISqlSugarRepository<EventInfo>>();
var now = DateTime.Now;
var eventsToUpdate = new List<EventInfo>();
_executionCount++;
// 每执行6次(约1分钟)做一次全量检查,其他时候只检查时间窗口内的赛事
var isFullCheck = _executionCount % 6 == 1;
List<EventInfo> candidateEvents;
if (isFullCheck)
{
_logger.LogDebug("执行全量赛事状态检查...");
candidateEvents = await repository.AsQueryable()
.Where(e => !e.DeleteMark && e.Status == AvailabilityStatus.Enable)
.ToListAsync(stoppingToken);
}
else
{
// 优化查询:只查询可能需要更新状态的赛事
// 逻辑:只查询那些当前时间接近关键时间点(报名开始/结束、比赛开始/结束)的赛事
// 设置一个时间窗口:当前时间前后各1分钟,这样只会处理即将或刚发生状态变化的赛事
var timeWindow = TimeSpan.FromMinutes(1);
var windowStart = now.Add(-timeWindow);
var windowEnd = now.Add(timeWindow);
candidateEvents = await repository.AsQueryable()
.Where(e => !e.DeleteMark && e.Status == AvailabilityStatus.Enable)
.Where(e =>
// 报名开始时间在时间窗口内
(e.RegistrationStartTime >= windowStart && e.RegistrationStartTime <= windowEnd) ||
// 报名结束时间在时间窗口内
(e.RegistrationEndTime >= windowStart && e.RegistrationEndTime <= windowEnd) ||
// 比赛开始时间在时间窗口内
(e.MatchStartTime >= windowStart && e.MatchStartTime <= windowEnd) ||
// 比赛结束时间在时间窗口内
(e.MatchEndTime >= windowStart && e.MatchEndTime <= windowEnd)
)
.ToListAsync(stoppingToken);
}
foreach (var evt in candidateEvents)
{
var newStatus = CalculateEventStatus(evt, now);
if (evt.EventStatus != newStatus)
{
evt.EventStatus = newStatus;
evt.UpdatedTime = now;
eventsToUpdate.Add(evt);
_logger.LogInformation("赛事 {EventId}({EventName}) 状态更新: {OldStatus} -> {NewStatus}",
evt.Id, evt.EventName, evt.EventStatus, newStatus);
}
}
if (eventsToUpdate.Any())
{
await repository.UpdateRangeAsync(eventsToUpdate);
_logger.LogInformation("成功更新 {Count} 个赛事的状态", eventsToUpdate.Count);
}
else
{
_logger.LogDebug("没有需要更新状态的赛事");
}
}
catch (Exception ex)
{
_logger.LogError(ex, "更新赛事状态时发生异常");
}
}
/// <summary>
/// 根据时间计算赛事状态
/// </summary>
/// <param name="evt">赛事信息</param>
/// <param name="now">当前时间</param>
/// <returns>计算后的赛事状态</returns>
public static EventStatus CalculateEventStatus(EventInfo evt, DateTime now)
{
if (now < evt.RegistrationStartTime)
{
return EventStatus.NotStarted;
}
else if (now >= evt.RegistrationStartTime && now <= evt.RegistrationEndTime)
{
return EventStatus.Enrolling;
}
else if (now > evt.RegistrationEndTime && now < evt.MatchStartTime)
{
return EventStatus.RegistrationEnded;
}
else if (now >= evt.MatchStartTime && now <= evt.MatchEndTime)
{
return EventStatus.Ongoing;
}
else if (now > evt.MatchEndTime)
{
return EventStatus.Ended;
}
return EventStatus.NotStarted;
}
public override void Dispose()
{
_timer.Dispose();
base.Dispose();
}
}