Add broadcast scheduling features: implement Show and Channel entities, enhance AppDbContext and DependencyInjection for broadcasting, and update API routing. Include migration for new database schema and update documentation for broadcast-related functionalities.
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using TeleWave.Application.Broadcast.Scheduling;
|
||||
using TeleWave.Application.Common.Interfaces;
|
||||
|
||||
namespace TeleWave.Infrastructure.Broadcast;
|
||||
|
||||
/// <summary>
|
||||
/// Периодически достраивает расписание каждого включённого канала до горизонта. Расширение хвоста —
|
||||
/// без удаления существующих записей, поэтому эфир не «дёргается».
|
||||
/// </summary>
|
||||
public sealed class SchedulingBackgroundService(
|
||||
IServiceScopeFactory scopeFactory,
|
||||
IOptions<SchedulerOptions> options,
|
||||
ILogger<SchedulingBackgroundService> logger
|
||||
) : BackgroundService
|
||||
{
|
||||
private readonly SchedulerOptions _options = options.Value;
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
// Небольшая задержка на старте — дать примениться миграциям/сидингу.
|
||||
try
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var timer = new PeriodicTimer(
|
||||
TimeSpan.FromMinutes(Math.Max(1, _options.TickMinutes))
|
||||
);
|
||||
|
||||
do
|
||||
{
|
||||
try
|
||||
{
|
||||
await TickAsync(stoppingToken);
|
||||
}
|
||||
catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Ошибка тика планировщика");
|
||||
}
|
||||
} while (await timer.WaitForNextTickAsync(stoppingToken));
|
||||
}
|
||||
|
||||
private async Task TickAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await using var scope = scopeFactory.CreateAsyncScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<IAppDbContext>();
|
||||
var generator = scope.ServiceProvider.GetRequiredService<ScheduleGenerator>();
|
||||
|
||||
var channelIds = await db.Channels
|
||||
.Where(c => c.IsEnabled)
|
||||
.Select(c => c.Id)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
foreach (var channelId in channelIds)
|
||||
{
|
||||
var added = await generator.GenerateAsync(channelId, now, regenerate: false, cancellationToken);
|
||||
if (added > 0)
|
||||
logger.LogInformation(
|
||||
"Канал {ChannelId}: добавлено {Count} записей расписания",
|
||||
channelId,
|
||||
added
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using TeleWave.Domain.Broadcast.Scheduling;
|
||||
|
||||
namespace TeleWave.Infrastructure.Broadcast;
|
||||
|
||||
/// <summary>Боевой источник случайности поверх <see cref="Random.Shared"/> (потокобезопасен).</summary>
|
||||
public sealed class SystemRandomSource : IRandomSource
|
||||
{
|
||||
public int Next(int maxExclusive) => Random.Shared.Next(maxExclusive);
|
||||
}
|
||||
Reference in New Issue
Block a user