Add campaign manager
This commit is contained in:
55
Hcs.WebApp/BackgroundServices/CampaignManagementService.cs
Normal file
55
Hcs.WebApp/BackgroundServices/CampaignManagementService.cs
Normal file
@ -0,0 +1,55 @@
|
||||
using Hcs.WebApp.BackgroundServices.CampaignManagers;
|
||||
using Hcs.WebApp.Services;
|
||||
|
||||
namespace Hcs.WebApp.BackgroundServices
|
||||
{
|
||||
public class CampaignManagementService(
|
||||
CampaignManagementState campaignManagementState,
|
||||
ManagerFactory managerFactory,
|
||||
IServiceScopeFactory scopeFactory) : BackgroundService
|
||||
{
|
||||
private const int SLEEP_TIME = 30000;
|
||||
|
||||
private readonly CampaignManagementState campaignManagementState = campaignManagementState;
|
||||
private readonly ManagerFactory managerFactory = managerFactory;
|
||||
private readonly IServiceScopeFactory scopeFactory = scopeFactory;
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
await InitializeStateAsync();
|
||||
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
while (campaignManagementState.TryDequeueCampaign(out var campaign))
|
||||
{
|
||||
if (stoppingToken.IsCancellationRequested) return;
|
||||
|
||||
try
|
||||
{
|
||||
var manager = managerFactory.CreateManager(campaign);
|
||||
await manager.StartAsync(stoppingToken);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// TODO: Добавить таймауты
|
||||
campaignManagementState.EnqueueCampaign(campaign);
|
||||
}
|
||||
}
|
||||
|
||||
await Task.Delay(SLEEP_TIME, stoppingToken);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task InitializeStateAsync()
|
||||
{
|
||||
using var scope = scopeFactory.CreateScope();
|
||||
var headquartersService = scope.ServiceProvider.GetRequiredService<HeadquartersService>();
|
||||
|
||||
var campaigns = await headquartersService.GetInitiatedCampaignAsync();
|
||||
foreach (var campaign in campaigns)
|
||||
{
|
||||
campaignManagementState.EnqueueCampaign(campaign);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
27
Hcs.WebApp/BackgroundServices/CampaignManagementState.cs
Normal file
27
Hcs.WebApp/BackgroundServices/CampaignManagementState.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using Hcs.WebApp.Data.Hcs;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace Hcs.WebApp.BackgroundServices
|
||||
{
|
||||
public class CampaignManagementState
|
||||
{
|
||||
private readonly ConcurrentQueue<Campaign> campaigns = new();
|
||||
|
||||
public event Action<Campaign> OnCampaignStarted;
|
||||
|
||||
public void EnqueueCampaign(Campaign campaign)
|
||||
{
|
||||
campaigns.Enqueue(campaign);
|
||||
}
|
||||
|
||||
public bool TryDequeueCampaign(out Campaign campaign)
|
||||
{
|
||||
return campaigns.TryDequeue(out campaign);
|
||||
}
|
||||
|
||||
public void InvokeOnCampaignStarted(Campaign campaign)
|
||||
{
|
||||
OnCampaignStarted?.Invoke(campaign);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
using Hcs.WebApp.Data.Hcs;
|
||||
|
||||
namespace Hcs.WebApp.BackgroundServices.CampaignManagers
|
||||
{
|
||||
public class ExportRequiredRegistryElementsManager_15_7_0_1(OperationExecutionState state, IServiceScopeFactory scopeFactory, Campaign campaign) : ManagerBase(state, scopeFactory, campaign)
|
||||
{
|
||||
public override async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
namespace Hcs.WebApp.BackgroundServices.CampaignManagers
|
||||
{
|
||||
public interface IManager
|
||||
{
|
||||
Task StartAsync(CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
using Hcs.WebApp.Data.Hcs;
|
||||
|
||||
namespace Hcs.WebApp.BackgroundServices.CampaignManagers
|
||||
{
|
||||
public abstract class ManagerBase(OperationExecutionState state, IServiceScopeFactory scopeFactory, Campaign campaign) : IManager
|
||||
{
|
||||
protected readonly OperationExecutionState state = state;
|
||||
protected readonly IServiceScopeFactory scopeFactory = scopeFactory;
|
||||
protected readonly Campaign campaign = campaign;
|
||||
|
||||
public abstract Task StartAsync(CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
using Hcs.WebApp.Data.Hcs;
|
||||
|
||||
namespace Hcs.WebApp.BackgroundServices.CampaignManagers
|
||||
{
|
||||
public class ManagerFactory(OperationExecutionState state, IServiceScopeFactory scopeFactory)
|
||||
{
|
||||
protected readonly OperationExecutionState state = state;
|
||||
protected readonly IServiceScopeFactory scopeFactory = scopeFactory;
|
||||
|
||||
public IManager CreateManager(Campaign campaign)
|
||||
{
|
||||
switch (campaign.Type)
|
||||
{
|
||||
case Campaign.CampaignType.ExportRequiredRegistryElements_15_7_0_1:
|
||||
return new ExportRequiredRegistryElementsManager_15_7_0_1(state, scopeFactory, campaign);
|
||||
}
|
||||
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
using Hcs.Broker;
|
||||
using Hcs.Broker.Logger;
|
||||
using Hcs.Broker.MessageCapturer;
|
||||
using Hcs.WebApp.BackgroundServices.Executors;
|
||||
using Hcs.WebApp.BackgroundServices.OperationExecutors;
|
||||
using Hcs.WebApp.Config;
|
||||
using Hcs.WebApp.Services;
|
||||
|
||||
@ -26,12 +26,14 @@ namespace Hcs.WebApp.BackgroundServices
|
||||
InitializeClient();
|
||||
|
||||
var scope = scopeFactory.CreateScope();
|
||||
var operationService = scope.ServiceProvider.GetRequiredService<OperationService>();
|
||||
var headquartersService = scope.ServiceProvider.GetRequiredService<HeadquartersService>();
|
||||
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
while (state.TryDequeueOperation(out var operation))
|
||||
{
|
||||
if (stoppingToken.IsCancellationRequested) return;
|
||||
|
||||
var messageGuid = string.Empty;
|
||||
try
|
||||
{
|
||||
@ -40,12 +42,13 @@ namespace Hcs.WebApp.BackgroundServices
|
||||
}
|
||||
catch
|
||||
{
|
||||
// TODO: Добавить таймауты и макс количество попыток выполнения операции
|
||||
state.EnqueueOperation(operation);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(messageGuid))
|
||||
{
|
||||
await operationService.SetOperationMessageGuidAsync(operation.Id, messageGuid);
|
||||
await headquartersService.SetOperationMessageGuidAsync(operation.Id, messageGuid);
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,9 +59,9 @@ namespace Hcs.WebApp.BackgroundServices
|
||||
private async Task InitializeStateAsync()
|
||||
{
|
||||
using var scope = scopeFactory.CreateScope();
|
||||
var operationService = scope.ServiceProvider.GetRequiredService<OperationService>();
|
||||
var headquartersService = scope.ServiceProvider.GetRequiredService<HeadquartersService>();
|
||||
|
||||
var operations = await operationService.GetInitiatedOperationsAsync();
|
||||
var operations = await headquartersService.GetInitiatedOperationsAsync();
|
||||
foreach (var operation in operations)
|
||||
{
|
||||
state.EnqueueOperation(operation);
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
using Hcs.Broker;
|
||||
using Hcs.WebApp.Data.Hcs;
|
||||
|
||||
namespace Hcs.WebApp.BackgroundServices.Executors
|
||||
namespace Hcs.WebApp.BackgroundServices.OperationExecutors
|
||||
{
|
||||
public abstract class ExecutorBase(IClient client, Operation operation) : IExecutor
|
||||
{
|
||||
@ -1,8 +1,8 @@
|
||||
using Hcs.Broker;
|
||||
using Hcs.WebApp.BackgroundServices.Executors._15_7_0_1.NsiCommon;
|
||||
using Hcs.WebApp.BackgroundServices.OperationExecutors.NsiCommon;
|
||||
using Hcs.WebApp.Data.Hcs;
|
||||
|
||||
namespace Hcs.WebApp.BackgroundServices.Executors
|
||||
namespace Hcs.WebApp.BackgroundServices.OperationExecutors
|
||||
{
|
||||
public class ExecutorFactory
|
||||
{
|
||||
@ -10,8 +10,8 @@ namespace Hcs.WebApp.BackgroundServices.Executors
|
||||
{
|
||||
switch (operation.Type)
|
||||
{
|
||||
case Operation.OperationType.NsiCommon_15_7_0_1_ExportAllRegistryElements:
|
||||
return new ExportAllRegistryElementsExecutor(client, operation);
|
||||
case Operation.OperationType.NsiCommon_ExportNsiItem_15_7_0_1:
|
||||
return new ExportNsiItemExecutor_15_7_0_1(client, operation);
|
||||
}
|
||||
|
||||
throw new NotImplementedException();
|
||||
@ -1,4 +1,4 @@
|
||||
namespace Hcs.WebApp.BackgroundServices.Executors
|
||||
namespace Hcs.WebApp.BackgroundServices.OperationExecutors
|
||||
{
|
||||
public interface IExecutor
|
||||
{
|
||||
@ -1,9 +1,9 @@
|
||||
using Hcs.Broker;
|
||||
using Hcs.WebApp.Data.Hcs;
|
||||
|
||||
namespace Hcs.WebApp.BackgroundServices.Executors._15_7_0_1.NsiCommon
|
||||
namespace Hcs.WebApp.BackgroundServices.OperationExecutors.NsiCommon
|
||||
{
|
||||
public class ExportAllRegistryElementsExecutor(IClient client, Operation operation) : ExecutorBase(client, operation)
|
||||
public class ExportNsiItemExecutor_15_7_0_1(IClient client, Operation operation) : ExecutorBase(client, operation)
|
||||
{
|
||||
public override async Task<string> ExecuteAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
@ -13,10 +13,10 @@
|
||||
@attribute [Authorize]
|
||||
|
||||
@inject AuthenticationStateProvider AuthenticationStateProvider
|
||||
@inject OperationService OperationService
|
||||
@inject HeadquartersService HeadquartersService
|
||||
@inject RegistryService RegistryService
|
||||
@inject DialogService DialogService
|
||||
@inject OperationExecutionState OperationExecutionState
|
||||
@inject CampaignManagementState CampaignManagementState
|
||||
|
||||
<PageTitle>Общие справочники подсистемы НСИ</PageTitle>
|
||||
|
||||
@ -83,7 +83,7 @@
|
||||
var state = await AuthenticationStateProvider.GetAuthenticationStateAsync();
|
||||
if (state.User.IsInRole(AppRole.ADMINISTRATOR_TYPE) || state.User.IsInRole(AppRole.OPERATOR_TYPE))
|
||||
{
|
||||
var operationInProgress = await OperationService.HasActiveOperationAsync(Operation.OperationType.NsiCommon_15_7_0_1_ExportAllRegistryElements);
|
||||
var operationInProgress = await HeadquartersService.HasActiveCampaignAsync(Campaign.CampaignType.ExportRequiredRegistryElements_15_7_0_1);
|
||||
if (operationInProgress)
|
||||
{
|
||||
finalState = CommonPageState.OperationWaiting;
|
||||
@ -93,7 +93,7 @@
|
||||
registries = await RegistryService.GetAllRegistriesAsync(true);
|
||||
}
|
||||
|
||||
OperationExecutionState.OnOperationStarted += OnOperationStarted;
|
||||
CampaignManagementState.OnCampaignStarted += OnCampaignStarted;
|
||||
}
|
||||
|
||||
ChangeState(finalState);
|
||||
@ -106,14 +106,15 @@
|
||||
|
||||
ChangeState(CommonPageState.OperationWaiting);
|
||||
|
||||
if (await OperationService.HasActiveOperationAsync(Operation.OperationType.NsiCommon_15_7_0_1_ExportAllRegistryElements))
|
||||
if (await HeadquartersService.HasActiveCampaignAsync(Campaign.CampaignType.ExportRequiredRegistryElements_15_7_0_1))
|
||||
{
|
||||
ChangeState(CommonPageState.Idle);
|
||||
}
|
||||
else
|
||||
{
|
||||
var operation = await OperationService.InitiateOperationAsync(Operation.OperationType.NsiCommon_15_7_0_1_ExportAllRegistryElements, "");
|
||||
OperationExecutionState.EnqueueOperation(operation);
|
||||
// TODO: Use user id
|
||||
var campaign = await HeadquartersService.InitiateCampaignAsync(Campaign.CampaignType.ExportRequiredRegistryElements_15_7_0_1, "");
|
||||
CampaignManagementState.EnqueueCampaign(campaign);
|
||||
}
|
||||
}
|
||||
|
||||
@ -161,9 +162,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
void OnOperationStarted(Operation operation)
|
||||
void OnCampaignStarted(Campaign campaign)
|
||||
{
|
||||
if (operation.Type == Operation.OperationType.NsiCommon_15_7_0_1_ExportAllRegistryElements)
|
||||
if (campaign.Type == Campaign.CampaignType.ExportRequiredRegistryElements_15_7_0_1)
|
||||
{
|
||||
InvokeAsync(() => ChangeState(CommonPageState.OperationWaiting));
|
||||
}
|
||||
@ -171,6 +172,6 @@
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
OperationExecutionState.OnOperationStarted -= OnOperationStarted;
|
||||
CampaignManagementState.OnCampaignStarted -= OnCampaignStarted;
|
||||
}
|
||||
}
|
||||
|
||||
27
Hcs.WebApp/Data/Hcs/Campaign.cs
Normal file
27
Hcs.WebApp/Data/Hcs/Campaign.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Hcs.WebApp.Data.Hcs
|
||||
{
|
||||
public class Campaign
|
||||
{
|
||||
public enum CampaignType
|
||||
{
|
||||
ExportRequiredRegistryElements_15_7_0_1
|
||||
}
|
||||
|
||||
public int Id { get; set; }
|
||||
|
||||
public CampaignType Type { get; set; }
|
||||
|
||||
public string InitiatorId { get; set; }
|
||||
|
||||
public DateTime StartedAt { get; set; }
|
||||
|
||||
public DateTime? EndedAt { get; set; }
|
||||
|
||||
public virtual ICollection<Operation> Operations { get; set; } = [];
|
||||
|
||||
[NotMapped]
|
||||
public bool Completed => EndedAt.HasValue;
|
||||
}
|
||||
}
|
||||
@ -9,10 +9,16 @@ namespace Hcs.WebApp.Data.Hcs
|
||||
|
||||
public DbSet<RegistryElement> Elements { get; set; }
|
||||
|
||||
public DbSet<Campaign> Campaigns { get; set; }
|
||||
|
||||
public DbSet<Operation> Operations { get; set; }
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<Campaign>()
|
||||
.Property(x => x.Type)
|
||||
.HasConversion(new EnumToStringConverter<Campaign.CampaignType>());
|
||||
|
||||
modelBuilder.Entity<Operation>()
|
||||
.Property(x => x.Type)
|
||||
.HasConversion(new EnumToStringConverter<Operation.OperationType>());
|
||||
|
||||
@ -6,14 +6,16 @@ namespace Hcs.WebApp.Data.Hcs
|
||||
{
|
||||
public enum OperationType
|
||||
{
|
||||
NsiCommon_15_7_0_1_ExportAllRegistryElements
|
||||
NsiCommon_ExportNsiItem_15_7_0_1
|
||||
}
|
||||
|
||||
public int Id { get; set; }
|
||||
|
||||
public OperationType Type { get; set; }
|
||||
public int CampaignId { get; set; }
|
||||
|
||||
public string InitiatorId { get; set; }
|
||||
public virtual Campaign Campaign { get; set; } = null!;
|
||||
|
||||
public OperationType Type { get; set; }
|
||||
|
||||
public DateTime StartedAt { get; set; }
|
||||
|
||||
@ -21,6 +23,8 @@ namespace Hcs.WebApp.Data.Hcs
|
||||
|
||||
public string? MessageGuid { get; set; }
|
||||
|
||||
public virtual ICollection<Registry> Registries { get; set; } = [];
|
||||
|
||||
[NotMapped]
|
||||
public bool Completed => EndedAt.HasValue;
|
||||
}
|
||||
|
||||
@ -10,7 +10,13 @@
|
||||
|
||||
public bool IsCommon { get; set; }
|
||||
|
||||
public DateTime UpdatedAt { get; set; }
|
||||
public DateTime SyncedAt { get; set; }
|
||||
|
||||
public int LastSyncOperationId { get; set; }
|
||||
|
||||
public virtual Operation LastSyncOperation { get; set; }
|
||||
|
||||
public string LastSyncError { get; set; }
|
||||
|
||||
public virtual ICollection<RegistryElement> Elements { get; set; } = [];
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using Hcs.WebApp.BackgroundServices;
|
||||
using Hcs.WebApp.BackgroundServices.CampaignManagers;
|
||||
using Hcs.WebApp.Components;
|
||||
using Hcs.WebApp.Components.Shared;
|
||||
using Hcs.WebApp.Data.Hcs;
|
||||
@ -58,10 +59,14 @@ builder.Services.AddTransient<IClientProvider, ClientProvider>();
|
||||
#endif
|
||||
builder.Services.AddScoped<IdentityService>();
|
||||
builder.Services.AddScoped<UsersService>();
|
||||
builder.Services.AddScoped<OperationService>();
|
||||
builder.Services.AddScoped<HeadquartersService>();
|
||||
builder.Services.AddScoped<RegistryService>();
|
||||
|
||||
builder.Services.AddSingleton<CampaignManagementState>();
|
||||
builder.Services.AddSingleton<OperationExecutionState>();
|
||||
builder.Services.AddSingleton<ManagerFactory>();
|
||||
|
||||
builder.Services.AddHostedService<CampaignManagementService>();
|
||||
builder.Services.AddHostedService<OperationExecutionService>();
|
||||
|
||||
var activator = new RadzenComponentActivator();
|
||||
|
||||
@ -3,28 +3,22 @@ using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Hcs.WebApp.Services
|
||||
{
|
||||
public class OperationService(IDbContextFactory<HcsDbContext> factory)
|
||||
public class HeadquartersService(IDbContextFactory<HcsDbContext> factory)
|
||||
{
|
||||
private readonly IDbContextFactory<HcsDbContext> factory = factory;
|
||||
|
||||
public async Task<bool> HasActiveOperationAsync(Operation.OperationType type)
|
||||
public async Task<bool> HasActiveCampaignAsync(Campaign.CampaignType type)
|
||||
{
|
||||
using var context = factory.CreateDbContext();
|
||||
return await context.Operations.AnyAsync(x => x.Type == type && !x.EndedAt.HasValue);
|
||||
return await context.Campaigns.AnyAsync(x => x.Type == type && !x.EndedAt.HasValue);
|
||||
}
|
||||
|
||||
public async Task<Operation> InitiateOperationAsync(Operation.OperationType type, string initiatorId)
|
||||
public async Task<IEnumerable<Campaign>> GetInitiatedCampaignAsync()
|
||||
{
|
||||
using var context = factory.CreateDbContext();
|
||||
var operation = new Operation()
|
||||
{
|
||||
Type = type,
|
||||
InitiatorId = initiatorId,
|
||||
StartedAt = DateTime.UtcNow
|
||||
};
|
||||
await context.Operations.AddAsync(operation);
|
||||
await context.SaveChangesAsync();
|
||||
return operation;
|
||||
return await (from campaign in context.Campaigns
|
||||
where !campaign.EndedAt.HasValue
|
||||
select campaign).ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Operation>> GetInitiatedOperationsAsync()
|
||||
@ -35,6 +29,34 @@ namespace Hcs.WebApp.Services
|
||||
select operation).ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<Campaign> InitiateCampaignAsync(Campaign.CampaignType type, string initiatorId)
|
||||
{
|
||||
using var context = factory.CreateDbContext();
|
||||
var campaign = new Campaign()
|
||||
{
|
||||
Type = type,
|
||||
InitiatorId = initiatorId,
|
||||
StartedAt = DateTime.UtcNow
|
||||
};
|
||||
await context.Campaigns.AddAsync(campaign);
|
||||
await context.SaveChangesAsync();
|
||||
return campaign;
|
||||
}
|
||||
|
||||
public async Task<Operation> InitiateOperationAsync(int campaignId, Operation.OperationType type)
|
||||
{
|
||||
using var context = factory.CreateDbContext();
|
||||
var operation = new Operation()
|
||||
{
|
||||
CampaignId = campaignId,
|
||||
Type = type,
|
||||
StartedAt = DateTime.UtcNow
|
||||
};
|
||||
await context.Operations.AddAsync(operation);
|
||||
await context.SaveChangesAsync();
|
||||
return operation;
|
||||
}
|
||||
|
||||
public async Task SetOperationMessageGuidAsync(int operationId, string messageGuid)
|
||||
{
|
||||
using var context = factory.CreateDbContext();
|
||||
Reference in New Issue
Block a user