From 2c005f038e5dfac5504c57603acebd61e27951a3 Mon Sep 17 00:00:00 2001 From: Mikhail Kuznetsov Date: Thu, 14 Aug 2025 15:20:04 +1000 Subject: [PATCH 1/2] =?UTF-8?q?feat(templateGeneratorWorker):=20=D0=A1?= =?UTF-8?q?=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD=20Worker=20=D1=87=D0=B8=D1=82?= =?UTF-8?q?=D0=B0=D1=8E=D1=89=D0=B8=D0=B9=20=D0=BE=D1=87=D0=B5=D1=80=D0=B5?= =?UTF-8?q?=D0=B4=D1=8C=20=D0=B8=20=D0=B3=D0=B5=D0=BD=D0=B5=D1=80=D0=B8?= =?UTF-8?q?=D1=80=D1=83=D1=8E=D1=89=D0=B8=D0=B9=20=D0=BD=D0=BE=D0=B2=D1=8B?= =?UTF-8?q?=D0=B5=20=D0=BE=D0=B1=D1=8A=D0=B5=D0=BA=D1=82=D1=8B=20Template?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PARR.API.sln | 6 + .../Domain/Mq/TemplateGeneratorWorkerMq.cs | 36 ++++++ PARR.Constants/ParrComponentsEnum.cs | 7 +- .../Implementations/UnitFilterService.cs | 3 - .../Services/ValidatorService.cs | 1 + PARR.TemplateGeneratorWorker/Dockerfile | 28 ++++ .../PARR.TemplateGeneratorWorker.csproj | 25 ++++ PARR.TemplateGeneratorWorker/Program.cs | 30 +++++ .../Properties/launchSettings.json | 14 ++ .../Services/IValidatorService.cs | 7 + .../Services/ValidatorService.cs | 62 +++++++++ .../Settings/MqSettings.cs | 12 ++ .../TemplateGeneratorWorkerInstaller.cs | 36 ++++++ PARR.TemplateGeneratorWorker/Worker.cs | 121 ++++++++++++++++++ .../appsettings.Development.json | 11 ++ PARR.TemplateGeneratorWorker/appsettings.json | 35 +++++ PARR.Test/Worker.cs | 32 ++--- 17 files changed, 446 insertions(+), 20 deletions(-) create mode 100644 PARR.BLL/Domain/Mq/TemplateGeneratorWorkerMq.cs create mode 100644 PARR.TemplateGeneratorWorker/Dockerfile create mode 100644 PARR.TemplateGeneratorWorker/PARR.TemplateGeneratorWorker.csproj create mode 100644 PARR.TemplateGeneratorWorker/Program.cs create mode 100644 PARR.TemplateGeneratorWorker/Properties/launchSettings.json create mode 100644 PARR.TemplateGeneratorWorker/Services/IValidatorService.cs create mode 100644 PARR.TemplateGeneratorWorker/Services/ValidatorService.cs create mode 100644 PARR.TemplateGeneratorWorker/Settings/MqSettings.cs create mode 100644 PARR.TemplateGeneratorWorker/TemplateGeneratorWorkerInstaller.cs create mode 100644 PARR.TemplateGeneratorWorker/Worker.cs create mode 100644 PARR.TemplateGeneratorWorker/appsettings.Development.json create mode 100644 PARR.TemplateGeneratorWorker/appsettings.json diff --git a/PARR.API.sln b/PARR.API.sln index 38dd4826..898fdffb 100644 --- a/PARR.API.sln +++ b/PARR.API.sln @@ -90,6 +90,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PARR.AIHITRelationshipsSync EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PARR.AIHITRelationshipsSyncerWorker", "PARR.AIHITRelationshipsSyncerWorker\PARR.AIHITRelationshipsSyncerWorker.csproj", "{64010D07-4053-4EA9-B0F0-E773B5749DBD}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PARR.TemplateGeneratorWorker", "PARR.TemplateGeneratorWorker\PARR.TemplateGeneratorWorker.csproj", "{17245D0B-3290-4C96-B5B1-4B0711BABAF2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -250,6 +252,10 @@ Global {64010D07-4053-4EA9-B0F0-E773B5749DBD}.Debug|Any CPU.Build.0 = Debug|Any CPU {64010D07-4053-4EA9-B0F0-E773B5749DBD}.Release|Any CPU.ActiveCfg = Release|Any CPU {64010D07-4053-4EA9-B0F0-E773B5749DBD}.Release|Any CPU.Build.0 = Release|Any CPU + {17245D0B-3290-4C96-B5B1-4B0711BABAF2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {17245D0B-3290-4C96-B5B1-4B0711BABAF2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {17245D0B-3290-4C96-B5B1-4B0711BABAF2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {17245D0B-3290-4C96-B5B1-4B0711BABAF2}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/PARR.BLL/Domain/Mq/TemplateGeneratorWorkerMq.cs b/PARR.BLL/Domain/Mq/TemplateGeneratorWorkerMq.cs new file mode 100644 index 00000000..9029aca1 --- /dev/null +++ b/PARR.BLL/Domain/Mq/TemplateGeneratorWorkerMq.cs @@ -0,0 +1,36 @@ +using PARR.Common.Domain; + +namespace PARR.BLL.Domain.Mq +{ + /// + /// Модель в MQ, простого создания Template + /// + public class TemplateGeneratorWorkerMq + { + /// + /// Id регламентной работы + /// + public Guid JobId { get; set; } + + /// + /// Id Юнита(единицы обслуживания)/ЭК + /// + public Guid UnitId { get; set; } + + /// + /// Актировать шаблон при инициализации + /// + public bool? IsActiveTemplate { get; set; } + + /// + /// Актировать расписание при инициализации + /// + public bool? IsActiveSchedule { get; set; } + + /// + /// Инициатор запроса к генератору + /// + public HistoryInitiator? HistoryInitiator { get; set; } + + } +} diff --git a/PARR.Constants/ParrComponentsEnum.cs b/PARR.Constants/ParrComponentsEnum.cs index 3603cf76..ac3f1091 100644 --- a/PARR.Constants/ParrComponentsEnum.cs +++ b/PARR.Constants/ParrComponentsEnum.cs @@ -71,6 +71,11 @@ /// /// Автораспределение РР /// - TemplateDistributor = 12 + TemplateDistributor = 12, + + /// + /// Генератор Template + /// + TemplateGenerator = 13 } } diff --git a/PARR.DAL/DomainServices/Implementations/UnitFilterService.cs b/PARR.DAL/DomainServices/Implementations/UnitFilterService.cs index d7f80a5c..7822fd9b 100644 --- a/PARR.DAL/DomainServices/Implementations/UnitFilterService.cs +++ b/PARR.DAL/DomainServices/Implementations/UnitFilterService.cs @@ -66,8 +66,6 @@ namespace PARR.DAL.DomainServices.Implementations .ThenInclude(t => t.UnitValues) .ThenInclude(t => t.Value); - logger.LogInformation($"В baseQuery записей {await baseQuery.CountAsync()}"); - foreach (var unitFilter in job.UnitFilters) { var query = baseQuery.Where(t => EF.Functions.ILike(t.Name, unitFilter.UnitFilter)); @@ -94,7 +92,6 @@ namespace PARR.DAL.DomainServices.Implementations else query = query.Where(t => !t.ParentUnits.Any() || !t.ParentUnits.Any(p => p.ParentUnit!.UnitValues.Any(pf => pf.FieldId == parentFilter.FieldId && (pf.Value!.Value != null && EF.Functions.ILike(pf.Value.Value, parentFilter.ValueMask))))); } - logger.LogInformation($"!В query записей {await query.CountAsync()}"); } } var newUnits = await query.Select(t => t.Id).ToListAsync(); diff --git a/PARR.TemplateActivator/Services/ValidatorService.cs b/PARR.TemplateActivator/Services/ValidatorService.cs index b4b31388..af645101 100644 --- a/PARR.TemplateActivator/Services/ValidatorService.cs +++ b/PARR.TemplateActivator/Services/ValidatorService.cs @@ -18,6 +18,7 @@ internal class ValidatorService : IValidatorService this.logger = logger; } + public async Task IsValidApplicationInWorkAsync(Guid applicationInWorkId) { using (var scope = serviceProvider.CreateScope()) diff --git a/PARR.TemplateGeneratorWorker/Dockerfile b/PARR.TemplateGeneratorWorker/Dockerfile new file mode 100644 index 00000000..4b3d1ba6 --- /dev/null +++ b/PARR.TemplateGeneratorWorker/Dockerfile @@ -0,0 +1,28 @@ +# See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. + +# This stage is used when running from VS in fast mode (Default for Debug configuration) +FROM mcr.microsoft.com/dotnet/runtime:7.0 AS base +WORKDIR /app + + +# This stage is used to build the service project +FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build +ARG BUILD_CONFIGURATION=Release +WORKDIR /src +COPY ["NuGet.config", "."] +COPY ["PARR.TemplateGeneratorWorker/PARR.TemplateGeneratorWorker.csproj", "PARR.TemplateGeneratorWorker/"] +RUN dotnet restore "./PARR.TemplateGeneratorWorker/PARR.TemplateGeneratorWorker.csproj" +COPY . . +WORKDIR "/src/PARR.TemplateGeneratorWorker" +RUN dotnet build "./PARR.TemplateGeneratorWorker.csproj" -c $BUILD_CONFIGURATION -o /app/build + +# This stage is used to publish the service project to be copied to the final stage +FROM build AS publish +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "./PARR.TemplateGeneratorWorker.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false + +# This stage is used in production or when running from VS in regular mode (Default when not using the Debug configuration) +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "PARR.TemplateGeneratorWorker.dll"] \ No newline at end of file diff --git a/PARR.TemplateGeneratorWorker/PARR.TemplateGeneratorWorker.csproj b/PARR.TemplateGeneratorWorker/PARR.TemplateGeneratorWorker.csproj new file mode 100644 index 00000000..3d16e5a9 --- /dev/null +++ b/PARR.TemplateGeneratorWorker/PARR.TemplateGeneratorWorker.csproj @@ -0,0 +1,25 @@ + + + + net7.0 + enable + enable + dotnet-PARR.TemplateGeneratorWorker-4ea24dbb-6876-46db-9b6e-68e93850120e + Linux + + + + + + + + + + + + + + + + + diff --git a/PARR.TemplateGeneratorWorker/Program.cs b/PARR.TemplateGeneratorWorker/Program.cs new file mode 100644 index 00000000..6295dd43 --- /dev/null +++ b/PARR.TemplateGeneratorWorker/Program.cs @@ -0,0 +1,30 @@ +using Elastic.CommonSchema.Serilog; +using PARR.TemplateGeneratorWorker; +using Serilog; + +var builder = Host.CreateApplicationBuilder(); + +builder.Services.AddLogging(config => +{ + config.ClearProviders(); + + var logger = new LoggerConfiguration(); + + if (builder.Environment.IsProduction()) + logger.WriteTo.Console(new EcsTextFormatter()); + else + logger.WriteTo.Console(); + + logger.ReadFrom.Configuration(builder.Configuration); + + config.AddSerilog(logger.CreateLogger()); +}); + +builder.Services.InstallTemplateGeneratorWorkerSerivces(builder.Configuration); +builder.Configuration.AddTemplateGeneratorWorkerConfigurations(builder.Services); +builder.Services.AddTemplateGeneratorWorkerSettings(builder.Configuration); + +builder.Services.AddHostedService(); + +var host = builder.Build(); +host.Run(); diff --git a/PARR.TemplateGeneratorWorker/Properties/launchSettings.json b/PARR.TemplateGeneratorWorker/Properties/launchSettings.json new file mode 100644 index 00000000..77f33fba --- /dev/null +++ b/PARR.TemplateGeneratorWorker/Properties/launchSettings.json @@ -0,0 +1,14 @@ +{ + "profiles": { + "PARR.TemplateGeneratorWorker": { + "commandName": "Project", + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true + }, + "Container (Dockerfile)": { + "commandName": "Docker" + } + } +} \ No newline at end of file diff --git a/PARR.TemplateGeneratorWorker/Services/IValidatorService.cs b/PARR.TemplateGeneratorWorker/Services/IValidatorService.cs new file mode 100644 index 00000000..34a57370 --- /dev/null +++ b/PARR.TemplateGeneratorWorker/Services/IValidatorService.cs @@ -0,0 +1,7 @@ +namespace PARR.TemplateGeneratorWorker.Services +{ + public interface IValidatorService + { + Task IsValidAsync(Guid jobId, Guid unitId); + } +} diff --git a/PARR.TemplateGeneratorWorker/Services/ValidatorService.cs b/PARR.TemplateGeneratorWorker/Services/ValidatorService.cs new file mode 100644 index 00000000..88077c40 --- /dev/null +++ b/PARR.TemplateGeneratorWorker/Services/ValidatorService.cs @@ -0,0 +1,62 @@ +using Microsoft.EntityFrameworkCore; +using PARR.DAL.Services.Interfaces.Job; +using PARR.DAL.Services.Interfaces.Unit; + +namespace PARR.TemplateGeneratorWorker.Services +{ + internal class ValidatorService : IValidatorService + { + private readonly IServiceProvider serviceProvider; + private readonly ILogger logger; + + public ValidatorService( + IServiceProvider serviceProvider, + ILogger logger + ) + { + this.serviceProvider = serviceProvider; + this.logger = logger; + } + + + public async Task IsValidAsync(Guid jobId, Guid unitId) + { + using (var scope = serviceProvider.CreateScope()) + { + var jobService = GetServiceInScope(scope); + var job = await jobService + .Get().AsNoTracking() + .FirstOrDefaultAsync(t => t.Id == jobId); + + if (job == null) + { + logger.LogError($"Не найдена регалментная работа {nameof(jobId)}: {jobId}"); + return false; + } + + var unitService = GetServiceInScope(scope); + var unit = await unitService + .Get().AsNoTracking() + .FirstOrDefaultAsync(t => t.Id == unitId); + + if (unit == null) + { + logger.LogError($"Не найден элемент конфигурации {nameof(unitId)}: {unitId}"); + return false; + } + + return true; + } + } + + + private Service GetServiceInScope(IServiceScope scope) + { + var service = scope.ServiceProvider.GetService(); + if (service == null) + throw new Exception($"Не найден сервис: {nameof(Service)}"); + + return service; + } + } +} diff --git a/PARR.TemplateGeneratorWorker/Settings/MqSettings.cs b/PARR.TemplateGeneratorWorker/Settings/MqSettings.cs new file mode 100644 index 00000000..652588ab --- /dev/null +++ b/PARR.TemplateGeneratorWorker/Settings/MqSettings.cs @@ -0,0 +1,12 @@ +using PARR.BLL.Contracts.Interfaces; + +namespace PARR.TemplateGeneratorWorker.Settings +{ + public class MqSettings : IMqSettings//TODO почему-то только публик работает + { + public string HostName { get; set; } = string.Empty; + public string QueueName { get; set; } = string.Empty; + public string User { get; set; } = string.Empty; + public string Password { get; set; } = string.Empty; + } +} diff --git a/PARR.TemplateGeneratorWorker/TemplateGeneratorWorkerInstaller.cs b/PARR.TemplateGeneratorWorker/TemplateGeneratorWorkerInstaller.cs new file mode 100644 index 00000000..708189e8 --- /dev/null +++ b/PARR.TemplateGeneratorWorker/TemplateGeneratorWorkerInstaller.cs @@ -0,0 +1,36 @@ +using PARR.BLL; +using PARR.DAL; +using PARR.TemplateGeneratorWorker.Services; +using PARR.TemplateGeneratorWorker.Settings; + +namespace PARR.TemplateGeneratorWorker +{ + public static class TemplateGeneratorWorkerInstaller + { + public static void InstallTemplateGeneratorWorkerSerivces(this IServiceCollection services, IConfiguration configuration) + { + services.InstallBllServices(configuration); + services.InstallDalServices(configuration); + + var mqSettings = new MqSettings(); + configuration.GetSection(nameof(MqSettings)).Bind(mqSettings); + services.AddSingleton(mqSettings); + + //services.AddTransient(); + //services.AddTransient(); + services.AddTransient(); + } + + public static IConfigurationBuilder AddTemplateGeneratorWorkerConfigurations(this IConfigurationBuilder builder, IServiceCollection services) + { + builder.AddDalConfigurations(services); + + return builder; + } + + public static void AddTemplateGeneratorWorkerSettings(this IServiceCollection services, IConfiguration configuration) + { + services.AddDallSettings(configuration); + } + } +} diff --git a/PARR.TemplateGeneratorWorker/Worker.cs b/PARR.TemplateGeneratorWorker/Worker.cs new file mode 100644 index 00000000..f3be9a1e --- /dev/null +++ b/PARR.TemplateGeneratorWorker/Worker.cs @@ -0,0 +1,121 @@ +using Microsoft.EntityFrameworkCore; +using PARR.BLL.Contracts.Interfaces; +using PARR.BLL.Domain.Mq; +using PARR.BLL.Services.Interfaces; +using PARR.Common.Domain; +using PARR.Constants; +using PARR.DAL.DomainServices.Interfaces; +using PARR.DAL.Models; +using PARR.DAL.Services.Interfaces; +using PARR.DAL.Services.Interfaces.Job; +using PARR.TemplateGeneratorWorker.Services; +using PARR.TemplateGeneratorWorker.Settings; + +namespace PARR.TemplateGeneratorWorker +{ + public class Worker : BackgroundService + { + private readonly ILogger logger; + private readonly IMqSettings mqSettings; + private readonly IMqService mqService; + private readonly ITransformService transformService; + private readonly IValidatorService validatorService; + private readonly IServiceProvider serviceProvider; + + public Worker( + ILogger logger, + MqSettings mqSettings, + IMqService mqService, + ITransformService transformService, + IValidatorService validatorService, + IServiceProvider serviceProvider + ) + { + this.logger = logger; + this.mqSettings = mqSettings; + this.mqService = mqService; + this.transformService = transformService; + this.validatorService = validatorService; + this.serviceProvider = serviceProvider; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while (!stoppingToken.IsCancellationRequested) + { + var isConnected = mqService.InitConsumer(mqSettings, GenerateTemplateAsync); + + if (!isConnected) + throw new Exception(" RabbitMq"); + } + } + + + public override Task StopAsync(CancellationToken cancellationToken) + { + mqService.Dispose(); + + return base.StopAsync(cancellationToken); + } + + + private async Task GenerateTemplateAsync(string msg) + { + logger.LogInformation($"{this.GetType().Name}. : {msg}"); + + var query = transformService.GetModelFromJson(msg); + if (query == null) + return; + + if (!await validatorService.IsValidAsync(query.JobId, query.UnitId)) + { + logger.LogError($" {nameof(query.JobId)}: {query.JobId}"); + return; + } + + using (var scope = serviceProvider.CreateScope()) + { + var jobService = GetServiceInScope(scope); + + var job = await jobService + .Get().AsNoTracking() + .FirstOrDefaultAsync(t => t.Id == query.JobId); + + var shortcodesService = GetServiceInScope(scope); + var templateName = await shortcodesService.ApplyShortcodesAsync(job!.TemplateNameMask!, query.UnitId, query.JobId); + + var template = new Template + { + Id = Guid.NewGuid(), + Name = templateName, + UnitId = query.UnitId, + JobId = query.JobId, + IsActiveTemplate = query.IsActiveTemplate ?? false, + IsActiveSchedule = query.IsActiveSchedule ?? false, + InitiatorComment = query.HistoryInitiator?.InitiatorComment, + InitiatorParrComponentId = query.HistoryInitiator?.InitiatorParrComponentId + }; + var templateService = GetServiceInScope(scope); + + if (!await templateService.CreateAsync(template) || !await templateService.CommitAsync(new HistoryInitiator { InitiatorComment = " ", InitiatorParrComponentId = ParrComponentsEnum.TemplateGenerator })) + { + logger.LogError($" : Name: {template.Name}, {nameof(template.Job)}: {template.JobId}, {nameof(template.Unit)}: {template.UnitId}"); + } + else + { + logger.LogInformation($" : Name: {template.Name}, {nameof(template.Job)}: {template.JobId}, {nameof(template.Unit)}: {template.UnitId}"); + } + + } + } + + private Service GetServiceInScope(IServiceScope scope) + { + var service = scope.ServiceProvider.GetService(); + if (service == null) + throw new Exception($" : {nameof(Service)}"); + + return service; + } + } +} diff --git a/PARR.TemplateGeneratorWorker/appsettings.Development.json b/PARR.TemplateGeneratorWorker/appsettings.Development.json new file mode 100644 index 00000000..992d2719 --- /dev/null +++ b/PARR.TemplateGeneratorWorker/appsettings.Development.json @@ -0,0 +1,11 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "MqSettings": { + "HostName": "10.99.253.216" + } +} diff --git a/PARR.TemplateGeneratorWorker/appsettings.json b/PARR.TemplateGeneratorWorker/appsettings.json new file mode 100644 index 00000000..b7b41d4e --- /dev/null +++ b/PARR.TemplateGeneratorWorker/appsettings.json @@ -0,0 +1,35 @@ +{ + "ConnectionStrings": { + "DefaultConnection": "Server=10.99.253.184;Database=parr;User Id=app_parr; Password=PosdfkhT&)%sdfligL&%5546;" + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "Serilog": { + "MinimumLevel": { + "Default": "Debug", + "Override": { + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Debug" + } + }, + "WriteTo": [ + { + "Name": "File", + "Args": { + "path": "log/log-.txt", + "rollingInterval": "Day" + } + } + ] + }, + "MqSettings": { + "HostName": "parr-rabbitmq", + "QueueName": "parr-template-generator", + "User": "template_generator_reader", + "Password": "Jy7vsoygcl7e0:Qy" + } +} diff --git a/PARR.Test/Worker.cs b/PARR.Test/Worker.cs index 70062f76..919ade88 100644 --- a/PARR.Test/Worker.cs +++ b/PARR.Test/Worker.cs @@ -35,28 +35,28 @@ namespace PARR.Test protected override async Task ExecuteAsync(CancellationToken stoppingToken) { #region test - //using (var scope = serviceProvider.CreateScope()) - //{ - // var unitFilterService = scope.ServiceProvider.GetService(); + using (var scope = serviceProvider.CreateScope()) + { + var unitFilterService = scope.ServiceProvider.GetService(); - // if (unitFilterService == null) - // return; + if (unitFilterService == null) + return; - // var jobId = Guid.Parse("58b28517-8178-4683-a519-22f5989c9912"); - // var unitsId = await unitFilterService.GetUnitsIdByJobFilterAsync(jobId); + var jobId = Guid.Parse("926793f7-c6d3-4f08-b5ce-517ce1a1ac37"); + //var unitsId = await unitFilterService.GetUnitsIdByJobFilterAsync(jobId); - // var unitService = scope.ServiceProvider.GetService(); + var unitService = scope.ServiceProvider.GetService(); - // if (unitService == null) - // return; + if (unitService == null) + return; - // var tForCreate = await unitFilterService.GetUnitsIdForExistTemplatesByJobFilterAsync(jobId); - // if (tForCreate == null) - // return; + var tForCreate = await unitFilterService.GetUnitsIdForExistTemplatesByJobFilterAsync(jobId); + if (tForCreate == null) + return; - // var units = await unitService - // .Get().AsNoTracking() - // .Where(t => tForCreate.Any(x => x == t.Id)).ToListAsync(); + var units = await unitService + .Get().AsNoTracking() + .Where(t => tForCreate.Any(x => x == t.Id)).ToListAsync(); } #endregion From 963da184f1b366940372f68eeb93035252317764 Mon Sep 17 00:00:00 2001 From: Mikhail Kuznetsov Date: Tue, 19 Aug 2025 11:13:56 +1000 Subject: [PATCH 2/2] =?UTF-8?q?feat(templateGeneratorWorker):=20ci/cd,=20?= =?UTF-8?q?=D0=BE=D1=81=D0=BD=D0=BE=D0=B2=D0=BD=D0=BE=D0=B9=20=D0=BC=D0=B5?= =?UTF-8?q?=D1=82=D0=BE=D0=B4=20=D0=B2=D1=8B=D0=BD=D0=B5=D1=81=D0=B5=D0=BD?= =?UTF-8?q?=20=D0=B2=20=D0=BE=D1=82=D0=B4=D0=B5=D0=BB=D1=8C=D0=BD=D1=8B?= =?UTF-8?q?=D0=B9=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitlab-ci.yml | 49 +++++++++++ PARR.TemplateGeneratorWorker/Dockerfile | 28 ------ .../ITemplateGenerator.cs | 7 ++ .../Services/ValidatorService.cs | 59 ++++++------- .../TemplateGenerator.cs | 86 +++++++++++++++++++ .../TemplateGeneratorWorkerInstaller.cs | 3 +- PARR.TemplateGeneratorWorker/Worker.cs | 71 ++------------- docker-compose.dcproj | 1 + docker-compose.template-generator.yml | 25 ++++++ 9 files changed, 202 insertions(+), 127 deletions(-) delete mode 100644 PARR.TemplateGeneratorWorker/Dockerfile create mode 100644 PARR.TemplateGeneratorWorker/ITemplateGenerator.cs create mode 100644 PARR.TemplateGeneratorWorker/TemplateGenerator.cs create mode 100644 docker-compose.template-generator.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a37d1b49..16711b02 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -13,6 +13,7 @@ variables: PROD_NAME_NEXT_RUN: "parr/parr-next-run" PROD_TEMPLATE_DISTRIBOTOR: "parr/parr-template-distributor" PROD_TEMPLATE_ACTIVATOR: "parr/parr-template-activator" + PROD_TEMPLATE_GENERATOR: "parr/parr-template-generator" PROD_JOB_AUTO_CONTROL: "parr/parr-job-auto-control" stages: @@ -659,6 +660,54 @@ prod_template_activator_deploy: - ${RUNNER} +### TEMPLATE GENERATOR PROD ### +prod_template_generator_build: + stage: build + only: + - /^tg[0-9]+\.[0-9]+\.[0-9]+$/ + except: + - branches + services: + - name: docker:20.10.21-dind + command: [ + "--insecure-registry=10.99.253.167:8090", + "--registry-mirror=http://10.99.253.167:8090", + "--insecure-registry=harbor.dvgd.rzd", + "--tls=false" + ] + variables: + DOCKER_HOST: tcp://docker:2375 + DOCKER_DRIVER: overlay2 + DOCKER_TLS_CERTDIR: "" + script: + - APP_VERSION=$(echo $CI_COMMIT_TAG | tr -d tg) + - IMAGE_VERSION=$(echo $CI_COMMIT_TAG | sed 's/tg/v/g') + - docker build -t $REPO/$PROD_TEMPLATE_GENERATOR:$IMAGE_VERSION -t $REPO/$PROD_TEMPLATE_GENERATORR:latest -t $PROD_TEMPLATE_GENERATOR:$IMAGE_VERSION -t $PROD_TEMPLATE_GENERATOR:latest --build-arg app_version=$APP_VERSION -f PARR.TemplateGeneratorWorker/Dockerfile . + - docker login -u $HARBOR_PUSH_USER -p $HARBOR_PUSH_PASS $REPO + - docker push --all-tags $REPO/$PROD_TEMPLATE_GENERATOR + tags: + - docker + + +prod_template_generator_deploy: + stage: deploy + environment: + name: parr-template-generator + only: + - /^tg[0-9]+\.[0-9]+\.[0-9]+$/ + except: + - branches + script: + - IMAGE_VERSION=$(echo $CI_COMMIT_TAG | sed 's/tg/v/g') + - docker login -u $HARBOR_PULL_USER -p $HARBOR_PULL_PASS $REPO + - tag=$IMAGE_VERSION docker stack deploy -c docker-compose.template-generator.yml parr-template-generator --with-registry-auth + parallel: + matrix: + - RUNNER: shell-api-swarm-01 + tags: + - ${RUNNER} + + ### JOB AUTO CONTROL PROD ### prod_job_auto_control_build: stage: build diff --git a/PARR.TemplateGeneratorWorker/Dockerfile b/PARR.TemplateGeneratorWorker/Dockerfile deleted file mode 100644 index 4b3d1ba6..00000000 --- a/PARR.TemplateGeneratorWorker/Dockerfile +++ /dev/null @@ -1,28 +0,0 @@ -# See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. - -# This stage is used when running from VS in fast mode (Default for Debug configuration) -FROM mcr.microsoft.com/dotnet/runtime:7.0 AS base -WORKDIR /app - - -# This stage is used to build the service project -FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build -ARG BUILD_CONFIGURATION=Release -WORKDIR /src -COPY ["NuGet.config", "."] -COPY ["PARR.TemplateGeneratorWorker/PARR.TemplateGeneratorWorker.csproj", "PARR.TemplateGeneratorWorker/"] -RUN dotnet restore "./PARR.TemplateGeneratorWorker/PARR.TemplateGeneratorWorker.csproj" -COPY . . -WORKDIR "/src/PARR.TemplateGeneratorWorker" -RUN dotnet build "./PARR.TemplateGeneratorWorker.csproj" -c $BUILD_CONFIGURATION -o /app/build - -# This stage is used to publish the service project to be copied to the final stage -FROM build AS publish -ARG BUILD_CONFIGURATION=Release -RUN dotnet publish "./PARR.TemplateGeneratorWorker.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false - -# This stage is used in production or when running from VS in regular mode (Default when not using the Debug configuration) -FROM base AS final -WORKDIR /app -COPY --from=publish /app/publish . -ENTRYPOINT ["dotnet", "PARR.TemplateGeneratorWorker.dll"] \ No newline at end of file diff --git a/PARR.TemplateGeneratorWorker/ITemplateGenerator.cs b/PARR.TemplateGeneratorWorker/ITemplateGenerator.cs new file mode 100644 index 00000000..a75efada --- /dev/null +++ b/PARR.TemplateGeneratorWorker/ITemplateGenerator.cs @@ -0,0 +1,7 @@ +namespace PARR.TemplateGeneratorWorker +{ + public interface ITemplateGenerator + { + Task GenerateTemplateAsync(string msg); + } +} diff --git a/PARR.TemplateGeneratorWorker/Services/ValidatorService.cs b/PARR.TemplateGeneratorWorker/Services/ValidatorService.cs index 88077c40..a0bb92e4 100644 --- a/PARR.TemplateGeneratorWorker/Services/ValidatorService.cs +++ b/PARR.TemplateGeneratorWorker/Services/ValidatorService.cs @@ -6,57 +6,46 @@ namespace PARR.TemplateGeneratorWorker.Services { internal class ValidatorService : IValidatorService { - private readonly IServiceProvider serviceProvider; private readonly ILogger logger; + private readonly IJobService jobService; + private readonly IUnitService unitService; public ValidatorService( - IServiceProvider serviceProvider, - ILogger logger + ILogger logger, + IJobService jobService, + IUnitService unitService ) { - this.serviceProvider = serviceProvider; this.logger = logger; + this.jobService = jobService; + this.unitService = unitService; } public async Task IsValidAsync(Guid jobId, Guid unitId) { - using (var scope = serviceProvider.CreateScope()) + + var job = await jobService + .Get().AsNoTracking() + .FirstOrDefaultAsync(t => t.Id == jobId); + + if (job == null) { - var jobService = GetServiceInScope(scope); - var job = await jobService - .Get().AsNoTracking() - .FirstOrDefaultAsync(t => t.Id == jobId); - - if (job == null) - { - logger.LogError($"Не найдена регалментная работа {nameof(jobId)}: {jobId}"); - return false; - } - - var unitService = GetServiceInScope(scope); - var unit = await unitService - .Get().AsNoTracking() - .FirstOrDefaultAsync(t => t.Id == unitId); - - if (unit == null) - { - logger.LogError($"Не найден элемент конфигурации {nameof(unitId)}: {unitId}"); - return false; - } - - return true; + logger.LogError($"Не найдена регалментная работа {nameof(jobId)}: {jobId}"); + return false; } - } + var unit = await unitService + .Get().AsNoTracking() + .FirstOrDefaultAsync(t => t.Id == unitId); - private Service GetServiceInScope(IServiceScope scope) - { - var service = scope.ServiceProvider.GetService(); - if (service == null) - throw new Exception($"Не найден сервис: {nameof(Service)}"); + if (unit == null) + { + logger.LogError($"Не найден элемент конфигурации {nameof(unitId)}: {unitId}"); + return false; + } - return service; + return true; } } } diff --git a/PARR.TemplateGeneratorWorker/TemplateGenerator.cs b/PARR.TemplateGeneratorWorker/TemplateGenerator.cs new file mode 100644 index 00000000..e3256814 --- /dev/null +++ b/PARR.TemplateGeneratorWorker/TemplateGenerator.cs @@ -0,0 +1,86 @@ + +using Microsoft.EntityFrameworkCore; +using PARR.BLL.Domain.Mq; +using PARR.BLL.Services.Interfaces; +using PARR.Common.Domain; +using PARR.Constants; +using PARR.DAL.DomainServices.Interfaces; +using PARR.DAL.Models; +using PARR.DAL.Models.Job; +using PARR.DAL.Models.Unit; +using PARR.DAL.Services.Interfaces; +using PARR.DAL.Services.Interfaces.Job; +using PARR.TemplateGeneratorWorker.Services; + +namespace PARR.TemplateGeneratorWorker +{ + internal class TemplateGenerator : ITemplateGenerator + { + private readonly ILogger logger; + private readonly ITransformService transformService; + private readonly IValidatorService validatorService; + private readonly IJobService jobService; + private readonly IShortcodesService shortcodesService; + private readonly ITemplateService templateService; + + public TemplateGenerator( + ILogger logger, + ITransformService transformService, + IValidatorService validatorService, + IJobService jobService, + IShortcodesService shortcodesService, + ITemplateService templateService + ) + { + this.logger = logger; + this.transformService = transformService; + this.validatorService = validatorService; + this.jobService = jobService; + this.shortcodesService = shortcodesService; + this.templateService = templateService; + } + public async Task GenerateTemplateAsync(string msg) + { + logger.LogInformation($"Получили запрос: {msg}"); + + var query = transformService.GetModelFromJson(msg); + + if (query == null) + return; + + if (!await validatorService.IsValidAsync(query.JobId, query.UnitId)) + { + logger.LogError($"Некорректные параметры регламентной работы или Unit {nameof(Job)}: {query.JobId}, {nameof(Unit)}: {query.UnitId}"); + return; + } + + var job = await jobService + .Get().AsNoTracking() + .FirstOrDefaultAsync(t => t.Id == query.JobId); + + var templateName = await shortcodesService.ApplyShortcodesAsync(job!.TemplateNameMask!, query.UnitId, query.JobId); + + var template = new Template + { + Id = Guid.NewGuid(), + Name = templateName, + UnitId = query.UnitId, + JobId = query.JobId, + IsActiveTemplate = query.IsActiveTemplate ?? false, + IsActiveSchedule = query.IsActiveSchedule ?? false, + InitiatorComment = query.HistoryInitiator?.InitiatorComment, + InitiatorParrComponentId = query.HistoryInitiator?.InitiatorParrComponentId + }; + + if (!await templateService.CreateAsync(template) || !await templateService.CommitAsync(new HistoryInitiator { InitiatorComment = "Запрос на генерацию с тестового шаблона", InitiatorParrComponentId = ParrComponentsEnum.TemplateGenerator })) + { + logger.LogError($"Ошибка при создании шаблона: Name: {template.Name}, {nameof(template.Job)}: {template.Job}, {nameof(template.Unit)}: {template.UnitId}"); + } + else + { + logger.LogInformation($"Создан шаблон: Name: {template.Name}, {nameof(template.Job)}: {template.JobId}, {nameof(template.Unit)}: {template.UnitId}"); + } + } + + } +} diff --git a/PARR.TemplateGeneratorWorker/TemplateGeneratorWorkerInstaller.cs b/PARR.TemplateGeneratorWorker/TemplateGeneratorWorkerInstaller.cs index 708189e8..d6c769c5 100644 --- a/PARR.TemplateGeneratorWorker/TemplateGeneratorWorkerInstaller.cs +++ b/PARR.TemplateGeneratorWorker/TemplateGeneratorWorkerInstaller.cs @@ -16,8 +16,7 @@ namespace PARR.TemplateGeneratorWorker configuration.GetSection(nameof(MqSettings)).Bind(mqSettings); services.AddSingleton(mqSettings); - //services.AddTransient(); - //services.AddTransient(); + services.AddTransient(); services.AddTransient(); } diff --git a/PARR.TemplateGeneratorWorker/Worker.cs b/PARR.TemplateGeneratorWorker/Worker.cs index f3be9a1e..9372ddb4 100644 --- a/PARR.TemplateGeneratorWorker/Worker.cs +++ b/PARR.TemplateGeneratorWorker/Worker.cs @@ -1,11 +1,6 @@ -using Microsoft.EntityFrameworkCore; using PARR.BLL.Contracts.Interfaces; -using PARR.BLL.Domain.Mq; using PARR.BLL.Services.Interfaces; -using PARR.Common.Domain; -using PARR.Constants; using PARR.DAL.DomainServices.Interfaces; -using PARR.DAL.Models; using PARR.DAL.Services.Interfaces; using PARR.DAL.Services.Interfaces.Job; using PARR.TemplateGeneratorWorker.Services; @@ -18,24 +13,23 @@ namespace PARR.TemplateGeneratorWorker private readonly ILogger logger; private readonly IMqSettings mqSettings; private readonly IMqService mqService; + private readonly IServiceProvider serviceProvider; private readonly ITransformService transformService; private readonly IValidatorService validatorService; - private readonly IServiceProvider serviceProvider; + private readonly IJobService jobService; + private readonly IShortcodesService shortcodesService; + private readonly ITemplateService templateService; public Worker( ILogger logger, MqSettings mqSettings, IMqService mqService, - ITransformService transformService, - IValidatorService validatorService, IServiceProvider serviceProvider ) { this.logger = logger; this.mqSettings = mqSettings; this.mqService = mqService; - this.transformService = transformService; - this.validatorService = validatorService; this.serviceProvider = serviceProvider; } @@ -61,61 +55,14 @@ namespace PARR.TemplateGeneratorWorker private async Task GenerateTemplateAsync(string msg) { - logger.LogInformation($"{this.GetType().Name}. : {msg}"); - - var query = transformService.GetModelFromJson(msg); - if (query == null) - return; - - if (!await validatorService.IsValidAsync(query.JobId, query.UnitId)) - { - logger.LogError($" {nameof(query.JobId)}: {query.JobId}"); - return; - } - using (var scope = serviceProvider.CreateScope()) { - var jobService = GetServiceInScope(scope); - - var job = await jobService - .Get().AsNoTracking() - .FirstOrDefaultAsync(t => t.Id == query.JobId); - - var shortcodesService = GetServiceInScope(scope); - var templateName = await shortcodesService.ApplyShortcodesAsync(job!.TemplateNameMask!, query.UnitId, query.JobId); - - var template = new Template - { - Id = Guid.NewGuid(), - Name = templateName, - UnitId = query.UnitId, - JobId = query.JobId, - IsActiveTemplate = query.IsActiveTemplate ?? false, - IsActiveSchedule = query.IsActiveSchedule ?? false, - InitiatorComment = query.HistoryInitiator?.InitiatorComment, - InitiatorParrComponentId = query.HistoryInitiator?.InitiatorParrComponentId - }; - var templateService = GetServiceInScope(scope); - - if (!await templateService.CreateAsync(template) || !await templateService.CommitAsync(new HistoryInitiator { InitiatorComment = " ", InitiatorParrComponentId = ParrComponentsEnum.TemplateGenerator })) - { - logger.LogError($" : Name: {template.Name}, {nameof(template.Job)}: {template.JobId}, {nameof(template.Unit)}: {template.UnitId}"); - } - else - { - logger.LogInformation($" : Name: {template.Name}, {nameof(template.Job)}: {template.JobId}, {nameof(template.Unit)}: {template.UnitId}"); - } - + var templateGenerator = scope.ServiceProvider.GetService(); + if (templateGenerator == null) + throw new Exception($" : {nameof(ITemplateGenerator)}"); + await templateGenerator.GenerateTemplateAsync(msg); } } - - private Service GetServiceInScope(IServiceScope scope) - { - var service = scope.ServiceProvider.GetService(); - if (service == null) - throw new Exception($" : {nameof(Service)}"); - - return service; - } } } + diff --git a/docker-compose.dcproj b/docker-compose.dcproj index 40e5b9bd..556b079f 100644 --- a/docker-compose.dcproj +++ b/docker-compose.dcproj @@ -9,6 +9,7 @@ parr.api + diff --git a/docker-compose.template-generator.yml b/docker-compose.template-generator.yml new file mode 100644 index 00000000..e7d877d0 --- /dev/null +++ b/docker-compose.template-generator.yml @@ -0,0 +1,25 @@ +version: '3.4' + +#PARR TEMPLATE ACTIVATOR +services: + parr-template-activator: + image: harbor.dvgd.rzd/parr/parr-template-generator:${tag-latest} + environment: + - ASPNETCORE_ENVIRONMENT=Production + - TZ=Europe/Moscow + logging: + driver: fluentd + options: + fluentd-address: dvgd-efk-01.dvgd.oao.rzd:24224 + fluentd-retry-wait: '30s' + fluentd-max-retries: '30' + tag: parr.template-generator.serilog + deploy: + replicas: 4 + networks: + parr-network: + +networks: + parr-network: + driver: overlay + external: true \ No newline at end of file