подписка
Tests / frontend-test (pull_request) Successful in 47s
Tests / backend-test (pull_request) Successful in 2m36s

This commit is contained in:
gorbenko.pavel
2026-08-19 13:30:09 +05:00
parent cf44ae3e2e
commit a1e6c5c8b4
12 changed files with 443 additions and 21 deletions
@@ -5,6 +5,7 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Tnsu.ApprovalRoute.Infrastructure.Persistence;
using Tnsu.ApprovalRoute.Infrastructure.SharePoint;
using Tnsu.ApprovalRoute.Infrastructure.TnsuPortal;
namespace Tnsu.ApprovalRoute.Application.ListPartitioning;
@@ -35,11 +36,27 @@ public sealed class CreateNextListPartitionJob(
ListPartitioningLock provisioningLock,
ListPartitionRegistry registry,
ISharePointListProvisioner provisioner,
TnsuPortalCallerToken callerToken,
IOptions<ListPartitioningOptions> options,
ILogger<CreateNextListPartitionJob> logger)
{
public async Task RunAsync(int requestTypeId, CancellationToken cancellationToken)
/// <param name="authorization">
/// Заголовок Authorization пользователя, чей запрос вызвал провижининг (см.
/// ListPartitionService.TryEnqueueProvisioningAsync). Нужен, чтобы подписать
/// новый список на уведомления от имени этого пользователя: своего HttpContext
/// у фоновой задачи нет, а client_credentials сейчас не настроен (см.
/// TnsuPortalAuthorizer). Провижининг занимает секунды, поэтому токен не
/// успевает истечь — но на ПОВТОРЕ Hangfire (минуты спустя) он уже может быть
/// просроченным: тогда подписка вернёт 401 и упадёт мягко, не сорвав создание
/// списка (см. ISharePointWebhookSubscriber).
///
/// Учтите: как аргумент задачи он попадает в хранилище Hangfire (Postgres) и
/// виден в его дашборде.
/// </param>
public async Task RunAsync(int requestTypeId, string? authorization, CancellationToken cancellationToken)
{
callerToken.Set(authorization);
// Единственная настоящая защита от параллельного запуска: атомарный захват
// флага в строке request_types. Проверка перед постановкой в очередь —
// только чтобы не плодить лишние задачи; гонку разруливает именно здесь.
@@ -7,6 +7,7 @@ using Tnsu.ApprovalRoute.Application.Common.Errors;
using Tnsu.ApprovalRoute.Domain.Entities;
using Tnsu.ApprovalRoute.Infrastructure.Persistence;
using Tnsu.ApprovalRoute.Infrastructure.SharePoint;
using Tnsu.ApprovalRoute.Infrastructure.TnsuPortal;
namespace Tnsu.ApprovalRoute.Application.ListPartitioning;
@@ -19,6 +20,7 @@ public sealed class ListPartitionService(
ISharePointClient sharePoint,
ListPartitionRegistry registry,
IBackgroundJobClient backgroundJobs,
TnsuPortalCallerToken callerToken,
IOptions<ListPartitioningOptions> options,
ILogger<ListPartitionService> logger)
{
@@ -151,8 +153,12 @@ public sealed class ListPartitionService(
// самой задачи (см. CreateNextListPartitionJob).
if (requestType.ListPartitioningStartedAt is not null) return false;
// Токен ЭТОГО запроса уезжает в задачу: подписать новый список на
// уведомления нужно от имени пользователя, а своего токена у фоновой задачи
// нет (см. CreateNextListPartitionJob.RunAsync — там же про его хранение и
// срок жизни).
backgroundJobs.Enqueue<CreateNextListPartitionJob>(
job => job.RunAsync(requestType.Id, CancellationToken.None));
job => job.RunAsync(requestType.Id, callerToken.Current, CancellationToken.None));
logger.LogInformation(
"В списке {ListId} типа заявок {RequestTypeId} {ItemCount} элементов (порог {Threshold}) — " +