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:
Leonid Pershin
2026-07-24 08:57:08 +03:00
parent e15ecbdb29
commit 4fa9dae37f
86 changed files with 4094 additions and 15 deletions
@@ -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);
}