This commit is contained in:
Mikhail Kuznetsov
2025-12-05 09:54:55 +10:00
34 changed files with 4796 additions and 23 deletions

View File

@@ -17,6 +17,7 @@ variables:
PROD_TEMPLATE_TASK_GENERATOR: "parr/parr-template-task-generator" PROD_TEMPLATE_TASK_GENERATOR: "parr/parr-template-task-generator"
PROD_JOB_AUTO_CONTROL: "parr/parr-job-auto-control" PROD_JOB_AUTO_CONTROL: "parr/parr-job-auto-control"
PROD_TEMPLATE_MATCHER: "parr/parr-template-matcher" PROD_TEMPLATE_MATCHER: "parr/parr-template-matcher"
PROD_TEMPLATE_UPDATER: "parr/parr-template-updater"
stages: stages:
- build - build
@@ -788,7 +789,7 @@ prod_job_auto_control_build:
prod_job_auto_control_deploy: prod_job_auto_control_deploy:
stage: deploy stage: deploy
environment: environment:
name: parr-template-activator name: parr-job-auto-control
only: only:
- /^jac[0-9]+\.[0-9]+\.[0-9]+$/ - /^jac[0-9]+\.[0-9]+\.[0-9]+$/
except: except:
@@ -837,7 +838,7 @@ prod_template_matcher_build:
prod_template_matcher_deploy: prod_template_matcher_deploy:
stage: deploy stage: deploy
environment: environment:
name: parr-template-activator name: parr-template-matcher
only: only:
- /^tm[0-9]+\.[0-9]+\.[0-9]+$/ - /^tm[0-9]+\.[0-9]+\.[0-9]+$/
except: except:
@@ -852,3 +853,51 @@ prod_template_matcher_deploy:
tags: tags:
- ${RUNNER} - ${RUNNER}
### TEMPLATE UPDATER PROD ###
prod_template_updater_build:
stage: build
only:
- /^tu[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 tu)
- IMAGE_VERSION=$(echo $CI_COMMIT_TAG | sed 's/tu/v/g')
- docker build -t $REPO/$PROD_TEMPLATE_UPDATER:$IMAGE_VERSION -t $REPO/$PROD_TEMPLATE_UPDATER:latest -t $PROD_TEMPLATE_UPDATER:$IMAGE_VERSION -t $PROD_TEMPLATE_UPDATER:latest --build-arg app_version=$APP_VERSION -f PARR.TemplateUpdaterWorker/Dockerfile .
- docker login -u $HARBOR_PUSH_USER -p $HARBOR_PUSH_PASS $REPO
- docker push --all-tags $REPO/$PROD_TEMPLATE_UPDATER
tags:
- docker
prod_template_updater_deploy:
stage: deploy
environment:
name: parr-template-updater
only:
- /^tu[0-9]+\.[0-9]+\.[0-9]+$/
except:
- branches
script:
- IMAGE_VERSION=$(echo $CI_COMMIT_TAG | sed 's/tu/v/g')
- docker login -u $HARBOR_PULL_USER -p $HARBOR_PULL_PASS $REPO
- tag=$IMAGE_VERSION docker stack deploy -c docker-compose.template-updater.yml parr-template-updater --with-registry-auth
parallel:
matrix:
- RUNNER: shell-api-swarm-01
tags:
- ${RUNNER}

View File

@@ -1,7 +1,7 @@
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 18 # Visual Studio Version 18
VisualStudioVersion = 18.0.11222.15 d18.0 VisualStudioVersion = 18.0.11222.15
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PARR.API", "PARR.API\PARR.API.csproj", "{82C57299-DA04-4A99-9C04-614FDDCE7AA1}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PARR.API", "PARR.API\PARR.API.csproj", "{82C57299-DA04-4A99-9C04-614FDDCE7AA1}"
EndProject EndProject
@@ -102,6 +102,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PARR.TemplateMatcherWorker"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PARR.DAL.Tests", "PARR.DAL.Tests\PARR.DAL.Tests.csproj", "{D61A7AB6-D785-477A-95D1-FBED049D6CE2}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PARR.DAL.Tests", "PARR.DAL.Tests\PARR.DAL.Tests.csproj", "{D61A7AB6-D785-477A-95D1-FBED049D6CE2}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PARR.TemplateUpdaterWorker", "PARR.TemplateUpdaterWorker\PARR.TemplateUpdaterWorker.csproj", "{27263750-9C80-4F9D-A550-B9D9C473B386}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PARR.TemplateUpdater", "PARR.TemplateUpdater\PARR.TemplateUpdater.csproj", "{10905C04-A6E5-42BC-9804-8671D22C5D6E}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@@ -286,6 +290,14 @@ Global
{D61A7AB6-D785-477A-95D1-FBED049D6CE2}.Debug|Any CPU.Build.0 = Debug|Any CPU {D61A7AB6-D785-477A-95D1-FBED049D6CE2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D61A7AB6-D785-477A-95D1-FBED049D6CE2}.Release|Any CPU.ActiveCfg = Release|Any CPU {D61A7AB6-D785-477A-95D1-FBED049D6CE2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D61A7AB6-D785-477A-95D1-FBED049D6CE2}.Release|Any CPU.Build.0 = Release|Any CPU {D61A7AB6-D785-477A-95D1-FBED049D6CE2}.Release|Any CPU.Build.0 = Release|Any CPU
{27263750-9C80-4F9D-A550-B9D9C473B386}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{27263750-9C80-4F9D-A550-B9D9C473B386}.Debug|Any CPU.Build.0 = Debug|Any CPU
{27263750-9C80-4F9D-A550-B9D9C473B386}.Release|Any CPU.ActiveCfg = Release|Any CPU
{27263750-9C80-4F9D-A550-B9D9C473B386}.Release|Any CPU.Build.0 = Release|Any CPU
{10905C04-A6E5-42BC-9804-8671D22C5D6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{10905C04-A6E5-42BC-9804-8671D22C5D6E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{10905C04-A6E5-42BC-9804-8671D22C5D6E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{10905C04-A6E5-42BC-9804-8671D22C5D6E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@@ -23,6 +23,9 @@
public required string Exclude { get; set; } public required string Exclude { get; set; }
// сделал возможный null, так как может в будущем это поле может не заполняться
public string? ExcludeCalendar { get; set; }
public required string Timezone { get; set; } public required string Timezone { get; set; }
public required string RepeatRange { get; set; } public required string RepeatRange { get; set; }

View File

@@ -10,6 +10,7 @@ using PARR.API.Controllers.V1.Base;
using PARR.API.Extensions; using PARR.API.Extensions;
using PARR.API.Services.Interfaces; using PARR.API.Services.Interfaces;
using PARR.DAL.CacheServices; using PARR.DAL.CacheServices;
using PARR.DAL.Contracts;
using PARR.DAL.DomainModels; using PARR.DAL.DomainModels;
using PARR.DAL.DomainServices.Interfaces; using PARR.DAL.DomainServices.Interfaces;
using PARR.DAL.Models.Job; using PARR.DAL.Models.Job;

View File

@@ -224,6 +224,7 @@ namespace PARR.API.MappingProfiles
.ForMember(d => d.TemplateId, o => o.MapFrom(s => s.Template!.Id)) .ForMember(d => d.TemplateId, o => o.MapFrom(s => s.Template!.Id))
.ForMember(d => d.WorkGroup, o => o.MapFrom(s => s.Template!.Job!.WorkGroupMask)) .ForMember(d => d.WorkGroup, o => o.MapFrom(s => s.Template!.Job!.WorkGroupMask))
.ForMember(d => d.Exclude, o => o.MapFrom<RobotTaskScheduleExcludeResolver>()) .ForMember(d => d.Exclude, o => o.MapFrom<RobotTaskScheduleExcludeResolver>())
.ForMember(d => d.ExcludeCalendar, o => o.MapFrom<RobotTaskScheduleExcludeCalendarResolver>())
.ForMember(d => d.Timezone, o => o.MapFrom<RobotTaskScheduleTimezoneResolver>()) .ForMember(d => d.Timezone, o => o.MapFrom<RobotTaskScheduleTimezoneResolver>())
.ForMember(d => d.RepeatRange, o => o.MapFrom<RobotTaskScheduleRepeatRangeResolver>()) .ForMember(d => d.RepeatRange, o => o.MapFrom<RobotTaskScheduleRepeatRangeResolver>())
//todo: GenerationTime //todo: GenerationTime

View File

@@ -0,0 +1,22 @@
using AutoMapper;
using PARR.API.Contracts.V1.Responses;
using PARR.DAL.Contracts;
using PARR.DAL.Models;
namespace PARR.API.MappingProfiles.Resolvers
{
public class RobotTaskScheduleExcludeCalendarResolver : IValueResolver<RobotConfiguration, RobotTaskScheduleResponse, string?>
{
private readonly SettingsFromDb settingsFromDb;
public RobotTaskScheduleExcludeCalendarResolver(SettingsFromDb settingsFromDb)
{
this.settingsFromDb = settingsFromDb;
}
public string? Resolve(RobotConfiguration source, RobotTaskScheduleResponse destination, string? destMember, ResolutionContext context)
{
return settingsFromDb.ScheduleExcludeCalendar;
}
}
}

View File

@@ -16,7 +16,7 @@ namespace PARR.API.MappingProfiles.Resolvers
public string Resolve(RobotConfiguration source, RobotTaskScheduleResponse destination, string destMember, ResolutionContext context) public string Resolve(RobotConfiguration source, RobotTaskScheduleResponse destination, string destMember, ResolutionContext context)
{ {
return settingsFromDb.ScheduleExclude; return settingsFromDb.ScheduleExcludeType;
} }
} }
} }

View File

@@ -25,6 +25,6 @@ namespace PARR.BLL.Domain.Mq
public TemplateStatusTypeEnum StatusTypeId { get; set; } public TemplateStatusTypeEnum StatusTypeId { get; set; }
public required IHistoryInitiator Initiator { get; set; } public required HistoryInitiator Initiator { get; set; }
} }
} }

View File

@@ -172,14 +172,15 @@ namespace PARR.DAL.Context
{ {
// При добавлении записей, добавлять тоже в PARR.DAL.Contracts.SettingsFromDb // При добавлении записей, добавлять тоже в PARR.DAL.Contracts.SettingsFromDb
f.HasData( f.HasData(
new { Name = nameof(SettingsFromDb.Initiator), Description = "Инициатор регламентной работы, указывается при создании шаблона в ЕСПП.", Value = "СТАРОСТИНА СВЕТЛАНА БОРИСОВНА (STAROSTINASB@GVC.OAO.RZD)" }, new { Name = nameof(SettingsFromDb.Initiator), Description = "Инициатор регламентной работы, указывается при создании шаблона в ЕСПП.", Value = "АКСЕНОВ АЛЕКСАНДР ЕВГЕНЬЕВИЧ (AKSENOVAE@GVC.OAO.RZD)" },
new { Name = nameof(SettingsFromDb.ClosingCode), Description = "Код закрытия регламентной работы, указывается при создании шаблона в ЕСПП.", Value = "выполнен" }, new { Name = nameof(SettingsFromDb.ClosingCode), Description = "Код закрытия регламентной работы, указывается при создании шаблона в ЕСПП.", Value = "выполнен" },
new { Name = nameof(SettingsFromDb.Category), Description = "Категория создаваемого объекта в ЕСПП", Value = "регламентная работа" }, new { Name = nameof(SettingsFromDb.Category), Description = "Категория создаваемого объекта в ЕСПП", Value = "регламентная работа" },
new { Name = nameof(SettingsFromDb.TemplatePrefixName), Description = "Префикс имени шаблона в ЕСПП", Value = "%PREFIX%-ЭИТИ-ПТК-ПАРР" }, new { Name = nameof(SettingsFromDb.TemplatePrefixName), Description = "Префикс имени шаблона в ЕСПП", Value = "%PREFIX%-ЭИТИ-ПТК-ПАРР" },
new { Name = nameof(SettingsFromDb.RobotAttemptsNumber), Description = "Количество попыток выполнения задания роботом", Value = "3" }, new { Name = nameof(SettingsFromDb.RobotAttemptsNumber), Description = "Количество попыток выполнения задания роботом", Value = "3" },
new { Name = nameof(SettingsFromDb.RobotWaitTime), Description = "Время ожидания выполнения роботом задания", Value = "00:15:00" }, new { Name = nameof(SettingsFromDb.RobotWaitTime), Description = "Время ожидания выполнения роботом задания", Value = "00:15:00" },
new { Name = nameof(SettingsFromDb.ScheduleTimezone), Description = "Расписание регламентной работы - В каком часовом поясе", Value = "MSK" }, new { Name = nameof(SettingsFromDb.ScheduleTimezone), Description = "Расписание регламентной работы - В каком часовом поясе", Value = "MSK" },
new { Name = nameof(SettingsFromDb.ScheduleExclude), Description = "Расписание регламентной работы - Тип исключения", Value = "Нет исключений" }, new { Name = nameof(SettingsFromDb.ScheduleExcludeType), Description = "Расписание регламентной работы - Тип исключения", Value = "Выполнить ТОЛЬКО В указанном календаре" },
new { Name = nameof(SettingsFromDb.ScheduleExcludeCalendar), Description = "Расписание регламентной работы - Календарь", Value = "8x5 (8.00-17.00)" },
new { Name = nameof(SettingsFromDb.ScheduleRepeatRange), Description = "Расписание регламентной работы - Диапазн повторов", Value = "Отсутствует дата завершения" }, new { Name = nameof(SettingsFromDb.ScheduleRepeatRange), Description = "Расписание регламентной работы - Диапазн повторов", Value = "Отсутствует дата завершения" },
new { Name = nameof(SettingsFromDb.OrderSearchDeltaDate), Description = "Промежуток времени для поиска нарядов в ЕСПП", Value = new TimeSpan(1, 30, 0).ToString() }, new { Name = nameof(SettingsFromDb.OrderSearchDeltaDate), Description = "Промежуток времени для поиска нарядов в ЕСПП", Value = new TimeSpan(1, 30, 0).ToString() },
new { Name = nameof(SettingsFromDb.EsppRobotAccountTimeZoneHour), Description = "Таймзона УЗ роботов в ЕСПП, в часах (может быть положительная и отрицательная)", Value = "3" }, new { Name = nameof(SettingsFromDb.EsppRobotAccountTimeZoneHour), Description = "Таймзона УЗ роботов в ЕСПП, в часах (может быть положительная и отрицательная)", Value = "3" },

View File

@@ -85,7 +85,12 @@ namespace PARR.DAL.Contracts
/// <summary> /// <summary>
/// Расписание регламентной работы - Тип исключения /// Расписание регламентной работы - Тип исключения
/// </summary> /// </summary>
public string ScheduleExclude { get; set; } = string.Empty; public string ScheduleExcludeType { get; set; } = string.Empty;
/// <summary>
/// Расписание регламентной работы - Календарь
/// </summary>
public string ScheduleExcludeCalendar { get; set; } = string.Empty;
/// <summary> /// <summary>
/// Расписание регламентной работы - Диапазн повторов /// Расписание регламентной работы - Диапазн повторов

View File

@@ -5,8 +5,17 @@
/// </summary> /// </summary>
public enum TaskStatusEnum public enum TaskStatusEnum
{ {
/// <summary>
/// Требуется создание объекта в ЕСПП
/// </summary>
Creating = 10, Creating = 10,
/// <summary>
/// Требуется обновление объекта в ЕСПП
/// </summary>
Updating = 20, Updating = 20,
/// <summary>
/// Нормальное состояние объекта в ЕСПП и ПАРР. Объект в ПАРР соответствует объекту в ЕСПП
/// </summary>
Ok = 30 Ok = 30
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,63 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
namespace PARR.DAL.Migrations
{
/// <inheritdoc />
public partial class updateSettings : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DeleteData(
table: "Settings",
keyColumn: "Name",
keyValue: "ScheduleExclude");
migrationBuilder.UpdateData(
table: "Settings",
keyColumn: "Name",
keyValue: "Initiator",
column: "Value",
value: "АКСЕНОВ АЛЕКСАНДР ЕВГЕНЬЕВИЧ (AKSENOVAE@GVC.OAO.RZD)");
migrationBuilder.InsertData(
table: "Settings",
columns: new[] { "Name", "Description", "Value" },
values: new object[,]
{
{ "ScheduleExcludeCalendar", "Расписание регламентной работы - Календарь", "8x5 (8.00-17.00)" },
{ "ScheduleExcludeType", "Расписание регламентной работы - Тип исключения", "Выполнить ТОЛЬКО В указанном календаре" }
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DeleteData(
table: "Settings",
keyColumn: "Name",
keyValue: "ScheduleExcludeCalendar");
migrationBuilder.DeleteData(
table: "Settings",
keyColumn: "Name",
keyValue: "ScheduleExcludeType");
migrationBuilder.UpdateData(
table: "Settings",
keyColumn: "Name",
keyValue: "Initiator",
column: "Value",
value: "СТАРОСТИНА СВЕТЛАНА БОРИСОВНА (STAROSTINASB@GVC.OAO.RZD)");
migrationBuilder.InsertData(
table: "Settings",
columns: new[] { "Name", "Description", "Value" },
values: new object[] { "ScheduleExclude", "Расписание регламентной работы - Тип исключения", "Нет исключений" });
}
}
}

View File

@@ -2514,7 +2514,7 @@ namespace PARR.DAL.Migrations
{ {
Name = "Initiator", Name = "Initiator",
Description = "Инициатор регламентной работы, указывается при создании шаблона в ЕСПП.", Description = "Инициатор регламентной работы, указывается при создании шаблона в ЕСПП.",
Value = "СТАРОСТИНА СВЕТЛАНА БОРИСОВНА (STAROSTINASB@GVC.OAO.RZD)" Value = "АКСЕНОВ АЛЕКСАНДР ЕВГЕНЬЕВИЧ (AKSENOVAE@GVC.OAO.RZD)"
}, },
new new
{ {
@@ -2554,9 +2554,15 @@ namespace PARR.DAL.Migrations
}, },
new new
{ {
Name = "ScheduleExclude", Name = "ScheduleExcludeType",
Description = "Расписание регламентной работы - Тип исключения", Description = "Расписание регламентной работы - Тип исключения",
Value = "Нет исключений" Value = "Выполнить ТОЛЬКО В указанном календаре"
},
new
{
Name = "ScheduleExcludeCalendar",
Description = "Расписание регламентной работы - Календарь",
Value = "8x5 (8.00-17.00)"
}, },
new new
{ {

View File

@@ -103,7 +103,9 @@ namespace PARR.EsppScheduleSync
//WorkGroup = template.Host!.WorkGroup!, //WorkGroup = template.Host!.WorkGroup!,
//WorkGroup = template.Host!.WorkGroup!.Name,//TODO Migration to job //WorkGroup = template.Host!.WorkGroup!.Name,//TODO Migration to job
//Мы решили, что для всех расписаний "Нет исключений", если что-то поменяется, тут нужно переделать //Мы решили, что для всех расписаний "Нет исключений", если что-то поменяется, тут нужно переделать
TypeV60calendar = settingsFromDb.ScheduleExclude == "Нет исключений" ? "NONE" : "", //TypeV60calendar = settingsFromDb.ScheduleExcludeType == "Нет исключений" ? "NONE" : "",
TypeV60calendar = GetTypeV60calendar(),
V60calendar = settingsFromDb.ScheduleExcludeCalendar,
//Scheduled = EsppScheduleHelpers.GetNextRun(template.NextRun), //Scheduled = EsppScheduleHelpers.GetNextRun(template.NextRun),
//Scheduled = EsppScheduleHelpers.GetNextRun(nextRunModifierService.GetNextRunByAccountRobotTimeZone(template.NextRun)), //Scheduled = EsppScheduleHelpers.GetNextRun(nextRunModifierService.GetNextRunByAccountRobotTimeZone(template.NextRun)),
//BasisTime = EsppScheduleHelpers.GetGenerationTime(nextRunModifierService.GetNextRunByAccountRobotTimeZone(template.NextRun)), //BasisTime = EsppScheduleHelpers.GetGenerationTime(nextRunModifierService.GetNextRunByAccountRobotTimeZone(template.NextRun)),
@@ -112,8 +114,7 @@ namespace PARR.EsppScheduleSync
Timezone = settingsFromDb.ScheduleTimezone, Timezone = settingsFromDb.ScheduleTimezone,
//Мы решили, что для всех расписаний "Отсутствует дата завершения", если что-то поменяется, тут нужно переделать //Мы решили, что для всех расписаний "Отсутствует дата завершения", если что-то поменяется, тут нужно переделать
TerminationType = settingsFromDb.ScheduleRepeatRange == "Отсутствует дата завершения" ? "forever" : "", TerminationType = settingsFromDb.ScheduleRepeatRange == "Отсутствует дата завершения" ? "forever" : "",
CompleteAfter = "", CompleteAfter = ""
V60calendar = ""
}; };
FillScheduleFromDb(template, ref esppObjectFromDb); FillScheduleFromDb(template, ref esppObjectFromDb);
@@ -121,6 +122,25 @@ namespace PARR.EsppScheduleSync
} }
/// <summary>
/// Получить тип календаря
/// </summary>
/// <returns></returns>
private string GetTypeV60calendar()
{
// Это костыль, нужно придумать как это хранить в БД.
switch (settingsFromDb.ScheduleExcludeType.ToLower())
{
case ("нет исключений"):
return "NONE";
case ("выполнить только в указанном календаре"):
return "ONLY";
default:
return "";
}
}
/// <summary> /// <summary>
/// Заполнить расписание из БД /// Заполнить расписание из БД
/// </summary> /// </summary>

View File

@@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using PARR.Constants; using PARR.Constants;
using PARR.DAL.Contracts; using PARR.DAL.Contracts;
@@ -101,17 +102,17 @@ namespace PARR.EsppSync
var lightDbObj = new EsppLightObject(dbObjectInEsppObject); var lightDbObj = new EsppLightObject(dbObjectInEsppObject);
var lightEsppObject = new EsppLightObject(esppObject); var lightEsppObject = new EsppLightObject(esppObject);
isChanged = IsChanged(lightEsppObject, lightDbObj); isChanged = IsChanged(lightEsppObject, lightDbObj, esppObject.TemplateName);
} }
else else
{ {
logger.LogDebug($"Объект активирован в ПАРР. Сравниваем все поля. {esppObject.TemplateName}"); logger.LogDebug($"Объект активирован в ПАРР. Сравниваем все поля. {esppObject.TemplateName}");
isChanged = IsChanged(esppObject, dbObjectInEsppObject); isChanged = IsChanged(esppObject, dbObjectInEsppObject, esppObject.TemplateName);
} }
if (isChanged) if (isChanged)
{ {
logger.LogInformation($"Есть изменения, требуется обновление. {esppObject.TemplateName}"); logger.LogDebug($"Есть изменения, требуется обновление. {esppObject.TemplateName}");
var config = robotConfigurationService.GetFromTemplateByRobotCode(esppObject.Robot, template); var config = robotConfigurationService.GetFromTemplateByRobotCode(esppObject.Robot, template);
@@ -144,7 +145,7 @@ namespace PARR.EsppSync
}//надо ли проверять если не изменился, но был статус Updating не понятно. Доверяем роботу пока, что после окончания работ он точно сообщит }//надо ли проверять если не изменился, но был статус Updating не понятно. Доверяем роботу пока, что после окончания работ он точно сообщит
else else
{ {
logger.LogInformation($"Нет изменений, обновление не требуется. {esppObject.TemplateName}"); logger.LogDebug($"Нет изменений, обновление не требуется. {esppObject.TemplateName}");
//если все поля совпали //если все поля совпали
//проверяем, какой был статус предыдущий статус в БД, если он был не Ок, то ставим ему ОК //проверяем, какой был статус предыдущий статус в БД, если он был не Ок, то ставим ему ОК
@@ -186,7 +187,7 @@ namespace PARR.EsppSync
//private bool IsChanged(EsppObject esppObj, EsppObject dbObj) //private bool IsChanged(EsppObject esppObj, EsppObject dbObj)
private bool IsChanged(object esppObj, object dbObj) private bool IsChanged(object esppObj, object dbObj, string templateName)
{ {
foreach (var prop in dbObj.GetType().GetProperties()) foreach (var prop in dbObj.GetType().GetProperties())
{ {
@@ -205,7 +206,7 @@ namespace PARR.EsppSync
if (dbValueStr != esppValueStr) if (dbValueStr != esppValueStr)
{ {
logger.LogDebug($"Не совпадают поля({prop.Name}). dbValueStr: {dbValueStr}, esppValueStr: {esppValueStr}"); logger.LogInformation($"Не совпадают поля ({prop.Name}). dbValueStr: {dbValueStr}, esppValueStr: {esppValueStr}. Имя шаблона: {templateName}");
return true; return true;
} }

View File

@@ -1,5 +1,4 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging;
using PARR.BLL.Services.Interfaces; using PARR.BLL.Services.Interfaces;
using PARR.DAL.Contracts; using PARR.DAL.Contracts;
using PARR.DAL.Models; using PARR.DAL.Models;

View File

@@ -0,0 +1,8 @@
namespace PARR.TemplateUpdater
{
public interface ITemplateUpdater
{
Task StartAsync();
Task StopAsync();
}
}

View File

@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\PARR.BLL\PARR.BLL.csproj" />
<ProjectReference Include="..\PARR.DAL\PARR.DAL.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,9 @@
using PARR.BLL.Domain.Mq;
namespace PARR.TemplateUpdater.Services
{
internal interface ITemplateUpdaterService
{
Task UpdateTemplateAsync(TemplateUpdaterMq query);
}
}

View File

@@ -0,0 +1,173 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using PARR.BLL.Domain.Mq;
using PARR.Constants;
using PARR.DAL.Contracts;
using PARR.DAL.Extensions;
using PARR.DAL.Services.Interfaces;
using PARR.DAL.Services.Interfaces.Job;
using PARR.DAL.Services.Interfaces.Unit;
namespace PARR.TemplateUpdater.Services
{
internal class TemplateUpdaterService : ITemplateUpdaterService
{
private readonly ILogger<TemplateUpdaterService> logger;
private readonly ITemplateService templateService;
private readonly IJobService jobService;
private readonly IUnitService unitService;
private readonly IRobotConfigurationService robotConfigurationService;
public TemplateUpdaterService(
ILogger<TemplateUpdaterService> logger,
ITemplateService templateService,
IJobService jobService,
IUnitService unitService,
IRobotConfigurationService robotConfigurationService
)
{
this.logger = logger;
this.templateService = templateService;
this.jobService = jobService;
this.unitService = unitService;
this.robotConfigurationService = robotConfigurationService;
}
public async Task UpdateTemplateAsync(TemplateUpdaterMq query)
{
var isValid = await IsValidAsync(query);
if (!isValid)
{
logger.LogError($"Не валидны входные данные. Конец обработки сообщения {query.ToJson()}");
return;
}
var template = await templateService.Get()
.Include(t => t.RobotConfigurations)
.FirstOrDefaultAsync(t => t.Id == query.TemplateId);
if (template == null)
{
logger.LogError($"Не найден шаблон с id {query.TemplateId}");
return;
}
var templateIsChanged = false;
var scheduleIsChanged = false;
if (template.Name != query.Name.Trim())
{
template.Name = query.Name.Trim();
templateIsChanged = true;
scheduleIsChanged = true;
}
if (template.JobId != query.JobId)
{
template.JobId = query.JobId;
templateIsChanged = true;
scheduleIsChanged = true;
}
if (template.IsActiveTemplate != query.IsActiveTemplate)
{
template.IsActiveTemplate = query.IsActiveTemplate;
templateIsChanged = true;
}
if (template.IsActiveSchedule != query.IsActiveSchedule)
{
template.IsActiveSchedule = query.IsActiveSchedule;
scheduleIsChanged = true;
}
if (template.NextRun != query.NextRun)
{
template.NextRun = query.NextRun;
scheduleIsChanged = true;
}
if (template.UnitId != query.UnitId)
{
template.UnitId = query.UnitId;
templateIsChanged = true;
scheduleIsChanged = true;
}
//просто обнволяем. не влияет ни на шаблон, ни на расписание
template.LastRun = query.LastRun;
template.Index = query.Index;
template.StatusTypeId = query.StatusTypeId;
if (templateIsChanged)
{
// ставим задачу на обновление шаблона
var config = robotConfigurationService.GetFromTemplateByRobotCode(RobotsEnum.TemplateOrder, template);
robotConfigurationService.ChangeTaskStatus(TaskStatusEnum.Updating, config);
logger.LogDebug($"Для шаблона {nameof(template.Id)}:{template.Id} устанавливаю статус {RobotStatusEnum.Wait.ToString()}");
}
if (scheduleIsChanged)
{
// ставим задачу на обновление расписания
var config = robotConfigurationService.GetFromTemplateByRobotCode(RobotsEnum.ScheduleOrder, template);
robotConfigurationService.ChangeTaskStatus(TaskStatusEnum.Updating, config);
logger.LogDebug($"Для расписания {nameof(template.Id)}:{template.Id} устанавливаю статус {RobotStatusEnum.Wait.ToString()}");
}
if (!await templateService.CommitAsync(query.Initiator))
logger.LogError($"Не удалось сохранить изменения в БД. {nameof(TemplateUpdaterMq)}: {query.ToJson()}.");
logger.LogInformation($"Выполнено изменение шаблона в БД. Отправлен запрос на синхронизацию шаблона: {templateIsChanged}, расписания: {scheduleIsChanged} .Query {query.ToJson()}");
}
/// <summary>
/// Валидация входящего сообщения
/// </summary>
/// <param name="query"></param>
/// <returns></returns>
private async Task<bool> IsValidAsync(TemplateUpdaterMq query)
{
var template = await templateService.Get().AsNoTracking().AnyAsync(t => t.Id == query.TemplateId);
if (!template)
{
logger.LogError($"Сообщение не валидно. Не найден шаблон с id: {query.TemplateId}");
return false;
}
if (string.IsNullOrEmpty(query.Name.Trim()))
{
logger.LogError($"Сообщение не валидно. Имя шаблона не может быть пустым: {query.Name}");
return false;
}
var existTemplateName = await templateService.Get().AsNoTracking().AnyAsync(t => t.Id != query.TemplateId && t.Name.ToUpper() == query.Name.Trim().ToUpper());
if (existTemplateName)
{
logger.LogError($"Сообщение не валидно. Имя шаблона не уникально: {query.Name}");
return false;
}
var job = await jobService.Get().AsNoTracking().AnyAsync(t => t.Id == query.JobId);
if (!job)
{
logger.LogError($"Сообщение не валидно. Не найдена работа с JobId: {query.JobId}");
return false;
}
var unit = await unitService.Get().AsNoTracking().AnyAsync(t => t.Id == query.UnitId);
if (!unit)
{
logger.LogError($"Сообщение не валидно. Не найден unit с UnitId: {query.UnitId}");
return false;
}
return true;
}
}
}

View File

@@ -0,0 +1,18 @@
using PARR.BLL.Contracts.Interfaces;
namespace PARR.TemplateUpdater.Settings
{
internal class MqSettings
{
public TemplatesUpdaterMq TemplatesUpdater { get; set; } = new TemplatesUpdaterMq();
}
internal class TemplatesUpdaterMq : IMqSettings
{
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;
public ushort? PrefetchCount { get; set; } = 0;
}
}

View File

@@ -0,0 +1,67 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using PARR.BLL.Domain.Mq;
using PARR.BLL.Services.Interfaces;
using PARR.TemplateUpdater.Services;
using PARR.TemplateUpdater.Settings;
namespace PARR.TemplateUpdater
{
internal class TemplateUpdater : ITemplateUpdater
{
private readonly MqSettings mqSettings;
private readonly IMqService mqService;
private readonly ILogger<TemplateUpdater> logger;
private readonly IServiceProvider serviceProvider;
private readonly ITransformService transformService;
public TemplateUpdater(
MqSettings mqSettings,
IMqService mqService,
ILogger<TemplateUpdater> logger,
IServiceProvider serviceProvider,
ITransformService transformService
)
{
this.mqSettings = mqSettings;
this.mqService = mqService;
this.logger = logger;
this.serviceProvider = serviceProvider;
this.transformService = transformService;
}
public async Task StartAsync()
{
var isConnected = await mqService.InitConsumerAsync(mqSettings.TemplatesUpdater, UpdateTemplateAsync);
if (!isConnected)
throw new Exception("Ошибка при подключении к RabbitMq");
}
public async Task StopAsync()
{
await mqService.DisposeAsync();
}
private async Task UpdateTemplateAsync(string msg)
{
logger.LogInformation($"Получили запрос: {msg}");
var query = transformService.GetModelFromJson<TemplateUpdaterMq>(msg);
if (query == null)
return;
using (var scope = serviceProvider.CreateScope())
{
var service = scope.ServiceProvider.GetService<ITemplateUpdaterService>();
if (service == null)
throw new Exception($"Не найден сервис: {nameof(service.GetType)}");
await service.UpdateTemplateAsync(query);
}
}
}
}

View File

@@ -0,0 +1,37 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using PARR.BLL;
using PARR.DAL;
using PARR.TemplateUpdater.Services;
using PARR.TemplateUpdater.Settings;
namespace PARR.TemplateUpdater
{
public static class TemplateUpdaterInstaller
{
public static void InstallTemplateUpdaterServices(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<ITemplateUpdater, TemplateUpdater>();
services.AddTransient<ITemplateUpdaterService, TemplateUpdaterService>();
}
public static IConfigurationBuilder AddTemplateUpdaterConfigurations(this IConfigurationBuilder builder, IServiceCollection services)
{
builder.AddDalConfigurations(services);
return builder;
}
public static void AddTemplateUpdaterSettings(this IServiceCollection services, IConfiguration configuration)
{
services.AddDallSettings(configuration);
}
}
}

View File

@@ -0,0 +1,34 @@
# 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 10.99.253.167:8090/dotnet/runtime:7.0 AS base
WORKDIR /app
# This stage is used to build the service project
FROM 10.99.253.167:8090/dotnet/sdk:7.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["NuGet.config", "."]
COPY ["PARR.TemplateUpdaterWorker/PARR.TemplateUpdaterWorker.csproj", "PARR.TemplateUpdaterWorker/"]
COPY ["PARR.TemplateUpdater/PARR.TemplateUpdater.csproj", "PARR.TemplateUpdater/"]
COPY ["PARR.BLL/PARR.BLL.csproj", "PARR.BLL/"]
COPY ["PARR.Common/PARR.Common.csproj", "PARR.Common/"]
COPY ["PARR.Constants/PARR.Constants.csproj", "PARR.Constants/"]
COPY ["PARR.DAL/PARR.DAL.csproj", "PARR.DAL/"]
RUN dotnet restore "./PARR.TemplateUpdaterWorker/PARR.TemplateUpdaterWorker.csproj"
COPY . .
WORKDIR "/src/PARR.TemplateUpdaterWorker"
RUN dotnet build "./PARR.TemplateUpdaterWorker.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 app_version=0.0.0-default
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./PARR.TemplateUpdaterWorker.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false /p:Version=$app_version
# 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.TemplateUpdaterWorker.dll"]

View File

@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk.Worker">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>dotnet-PARR.TemplateUpdaterWorker-413e055b-7f45-4b87-bbb3-28a3427d03f2</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Elastic.CommonSchema.Serilog" Version="8.6.1" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.23.0" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="7.0.0" />
<PackageReference Include="Serilog.Settings.Configuration" Version="7.0.1" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\PARR.TemplateUpdater\PARR.TemplateUpdater.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,31 @@
using Elastic.CommonSchema.Serilog;
using PARR.TemplateUpdater;
using PARR.TemplateUpdaterWorker;
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.InstallTemplateUpdaterServices(builder.Configuration);
builder.Configuration.AddTemplateUpdaterConfigurations(builder.Services);
builder.Services.AddTemplateUpdaterSettings(builder.Configuration);
builder.Services.AddHostedService<Worker>();
var host = builder.Build();
host.Run();

View File

@@ -0,0 +1,14 @@
{
"profiles": {
"PARR.TemplateUpdaterWorker": {
"commandName": "Project",
"environmentVariables": {
"DOTNET_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true
},
"Container (Dockerfile)": {
"commandName": "Docker"
}
}
}

View File

@@ -0,0 +1,29 @@
using PARR.TemplateUpdater;
namespace PARR.TemplateUpdaterWorker
{
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
private readonly ITemplateUpdater templateUpdater;
public Worker(ILogger<Worker> logger, ITemplateUpdater templateUpdater)
{
_logger = logger;
this.templateUpdater = templateUpdater;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await templateUpdater.StartAsync();
await Task.Delay(Timeout.Infinite, stoppingToken);
}
public override async Task StopAsync(CancellationToken cancellationToken)
{
await templateUpdater.StopAsync();
await base.StopAsync(cancellationToken);
}
}
}

View File

@@ -0,0 +1,31 @@
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"Microsoft.AspNetCore": "Debug"
}
},
"Serilog": {
"MinimumLevel": {
"Default": "Debug",
"Override": {
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"WriteTo": [
{
"Name": "File",
"Args": {
"path": "log/log-.txt",
"rollingInterval": "Day"
}
}
]
},
"MqSettings": {
"TemplatesUpdater": {
"HostName": "10.99.253.216"
}
}
}

View File

@@ -0,0 +1,28 @@
{
"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": "Information",
"Override": {
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
},
"MqSettings": {
"TemplatesUpdater": {
"HostName": "parr-rabbitmq",
"QueueName": "parr-template-updater",
"User": "template_updater_reader",
"Password": "JfdsfUFJJh2255&*&F1"
}
}
}

View File

@@ -43,4 +43,8 @@
- PARR.TemplateActivatorWorker - PARR.TemplateActivatorWorker
- PARR.TemplateDistributor - автораспределение РР - PARR.TemplateDistributor - автораспределение РР
- PARR.TemplateDistributorWorker - PARR.TemplateDistributorWorker
- PARR.TemplateMatcher
- PARR.TemplateMatcherWorker
- PARR.TemplateUpdater - обновление шаблонов
- PARR.TemplateUpdaterWorker
- PARR.Test - тестирование функций ПАРР - PARR.Test - тестирование функций ПАРР

View File

@@ -25,6 +25,7 @@
<None Include="docker-compose.next-run.yml" /> <None Include="docker-compose.next-run.yml" />
<None Include="docker-compose.template-activator.yml" /> <None Include="docker-compose.template-activator.yml" />
<None Include="docker-compose.template-distributor.yml" /> <None Include="docker-compose.template-distributor.yml" />
<None Include="docker-compose.template-updater.yml" />
<None Include="docker-compose.yml" /> <None Include="docker-compose.yml" />
<None Include=".dockerignore" /> <None Include=".dockerignore" />
<None Include="stack.yml" /> <None Include="stack.yml" />

View File

@@ -0,0 +1,27 @@
version: '3.4'
#PARR TEMPLATE UPDATER
services:
parr-template-updater:
image: harbor.dvgd.rzd/parr/parr-template-updater:${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: '10s'
fluentd-max-retries: '30'
fluentd-async: 'true'
fluentd-buffer-limit: '52428800'
tag: parr.template-updater.serilog
deploy:
replicas: 4
networks:
parr-network:
networks:
parr-network:
driver: overlay
external: true