From a726602be8723d074e0f6b8233d4ac3585a3dac1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=82=D0=B5=D0=BF=D0=B0=D0=BD=D0=BE=D0=B2=20=D0=94?= =?UTF-8?q?=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Mon, 29 Jan 2024 14:39:53 +0500 Subject: [PATCH 01/12] =?UTF-8?q?=D0=90=D0=B1=D1=81=D1=82=D1=80=D0=B0?= =?UTF-8?q?=D0=BA=D1=86=D0=B8=D0=B8=20=D0=B4=D0=BB=D1=8F=20=D0=BF=D0=B0?= =?UTF-8?q?=D1=80=D1=81=D0=B8=D0=BD=D0=B3=D0=B0=20=D1=84=D0=B0=D0=B9=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AsbCloudApp/Data/ParserResultDto.cs | 12 ++++++++ .../Data/Trajectory/TrajectoryGeoDto.cs | 2 +- .../Import/ParserOptionsRequestBase.cs | 17 +++++++++++ .../Import/TrajectoryParserRequest.cs | 12 ++++++++ AsbCloudApp/Services/IParserService.cs | 30 +++++++++++++++++++ 5 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 AsbCloudApp/Data/ParserResultDto.cs create mode 100644 AsbCloudApp/Requests/Import/ParserOptionsRequestBase.cs create mode 100644 AsbCloudApp/Requests/Import/TrajectoryParserRequest.cs create mode 100644 AsbCloudApp/Services/IParserService.cs diff --git a/AsbCloudApp/Data/ParserResultDto.cs b/AsbCloudApp/Data/ParserResultDto.cs new file mode 100644 index 00000000..5b6eca8d --- /dev/null +++ b/AsbCloudApp/Data/ParserResultDto.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace AsbCloudApp.Data; + +/// +/// Результат парсинга файла +/// +/// +public class ParserResultDto : ValidationResultDto>> + where TDto : class, IId +{ +} \ No newline at end of file diff --git a/AsbCloudApp/Data/Trajectory/TrajectoryGeoDto.cs b/AsbCloudApp/Data/Trajectory/TrajectoryGeoDto.cs index 47563011..e47a66e8 100644 --- a/AsbCloudApp/Data/Trajectory/TrajectoryGeoDto.cs +++ b/AsbCloudApp/Data/Trajectory/TrajectoryGeoDto.cs @@ -5,7 +5,7 @@ namespace AsbCloudApp.Data.Trajectory /// /// Базовая географическая траектория /// - public abstract class TrajectoryGeoDto + public abstract class TrajectoryGeoDto : IId { /// /// ИД строки с координатами diff --git a/AsbCloudApp/Requests/Import/ParserOptionsRequestBase.cs b/AsbCloudApp/Requests/Import/ParserOptionsRequestBase.cs new file mode 100644 index 00000000..852459ee --- /dev/null +++ b/AsbCloudApp/Requests/Import/ParserOptionsRequestBase.cs @@ -0,0 +1,17 @@ +namespace AsbCloudApp.Requests.Import; + +/// +/// Параметры парсинга +/// +public abstract class ParserOptionsRequestBase +{ + /// + /// Id сервиса для парсинга + /// + public int IdParserService { get; set; } + + /// + /// Название листа в файле Excel + /// + public string SheetName { get; set; } = null!; +} \ No newline at end of file diff --git a/AsbCloudApp/Requests/Import/TrajectoryParserRequest.cs b/AsbCloudApp/Requests/Import/TrajectoryParserRequest.cs new file mode 100644 index 00000000..6a7fedab --- /dev/null +++ b/AsbCloudApp/Requests/Import/TrajectoryParserRequest.cs @@ -0,0 +1,12 @@ +namespace AsbCloudApp.Requests.Import; + +/// +/// Параметры парсинга траекторий +/// +public class TrajectoryParserRequest : ParserOptionsRequestBase +{ + /// + /// Количество строк заголовка + /// + public int HeaderRowsCount { get; set; } +} \ No newline at end of file diff --git a/AsbCloudApp/Services/IParserService.cs b/AsbCloudApp/Services/IParserService.cs new file mode 100644 index 00000000..81177c64 --- /dev/null +++ b/AsbCloudApp/Services/IParserService.cs @@ -0,0 +1,30 @@ +using System.IO; +using AsbCloudApp.Data; +using AsbCloudApp.Requests.Import; + +namespace AsbCloudApp.Services; + +/// +/// Сервис парсинга файлов +/// +/// +/// +public interface IParserService : IParserService + where TDto : class, IId + where TOptions : ParserOptionsRequestBase +{ + /// + /// Распарсить файл + /// + /// + /// + /// + ParserResultDto Parse(Stream file, TOptions options); +} + +/// +/// Сервис парсинга файлов +/// +public interface IParserService +{ +} \ No newline at end of file From 82650b1cfb402ade60e1cd17041b8af8efcf5af4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=82=D0=B5=D0=BF=D0=B0=D0=BD=D0=BE=D0=B2=20=D0=94?= =?UTF-8?q?=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Mon, 29 Jan 2024 15:03:53 +0500 Subject: [PATCH 02/12] =?UTF-8?q?=D0=A1=D0=B5=D1=80=D0=B2=D0=B8=D1=81?= =?UTF-8?q?=D1=8B=20=D0=BF=D0=B0=D1=80=D1=81=D0=B8=D0=BD=D0=B3=D0=B0=20?= =?UTF-8?q?=D1=82=D1=80=D0=B0=D0=B5=D0=BA=D1=82=D0=BE=D1=80=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Сделан рефакторинг сервисов парсинга траекторий 2. Добавлена фабрика создания парсеров 3. Рефакторинг тестов --- AsbCloudInfrastructure/DependencyInjection.cs | 4 +- .../Services/ParserServiceFactory.cs | 30 +++++++ .../TrajectoryFactManualParserService.cs | 30 ------- .../Import/TrajectoryParserService.cs | 78 ------------------- .../Import/TrajectoryPlanParserService.cs | 33 -------- .../TrajectoryFactManualParserService.cs | 28 +++++++ .../Parser/TrajectoryParserService.cs | 78 +++++++++++++++++++ .../Parser/TrajectoryPlanParserService.cs | 29 +++++++ .../Trajectory/TrajectoryImportTest.cs | 48 ------------ .../Trajectory/TrajectoryParserTest.cs | 60 ++++++++++++++ 10 files changed, 228 insertions(+), 190 deletions(-) create mode 100644 AsbCloudInfrastructure/Services/ParserServiceFactory.cs delete mode 100644 AsbCloudInfrastructure/Services/Trajectory/Import/TrajectoryFactManualParserService.cs delete mode 100644 AsbCloudInfrastructure/Services/Trajectory/Import/TrajectoryParserService.cs delete mode 100644 AsbCloudInfrastructure/Services/Trajectory/Import/TrajectoryPlanParserService.cs create mode 100644 AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryFactManualParserService.cs create mode 100644 AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryParserService.cs create mode 100644 AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryPlanParserService.cs delete mode 100644 AsbCloudWebApi.Tests/Services/Trajectory/TrajectoryImportTest.cs create mode 100644 AsbCloudWebApi.Tests/Services/Trajectory/TrajectoryParserTest.cs diff --git a/AsbCloudInfrastructure/DependencyInjection.cs b/AsbCloudInfrastructure/DependencyInjection.cs index 0e88a972..eec917a7 100644 --- a/AsbCloudInfrastructure/DependencyInjection.cs +++ b/AsbCloudInfrastructure/DependencyInjection.cs @@ -30,7 +30,6 @@ using AsbCloudInfrastructure.Services.SAUB; using AsbCloudInfrastructure.Services.Subsystems; using AsbCloudInfrastructure.Services.Trajectory; using AsbCloudInfrastructure.Services.Trajectory.Export; -using AsbCloudInfrastructure.Services.Trajectory.Import; using AsbCloudInfrastructure.Services.WellOperationImport; using AsbCloudInfrastructure.Services.WellOperationImport.FileParser; using AsbCloudInfrastructure.Services.WellOperationService; @@ -45,6 +44,7 @@ using AsbCloudDb.Model.DailyReports.Blocks.TimeBalance; using AsbCloudDb.Model.WellSections; using AsbCloudInfrastructure.Services.ProcessMaps; using AsbCloudApp.Data.ProcessMapPlan; +using AsbCloudInfrastructure.Services.Trajectory.Parser; namespace AsbCloudInfrastructure { @@ -325,6 +325,8 @@ namespace AsbCloudInfrastructure services.AddTransient, ProcessMapPlanService>(); services.AddTransient(); + + services.AddSingleton(); return services; } diff --git a/AsbCloudInfrastructure/Services/ParserServiceFactory.cs b/AsbCloudInfrastructure/Services/ParserServiceFactory.cs new file mode 100644 index 00000000..17f3ed23 --- /dev/null +++ b/AsbCloudInfrastructure/Services/ParserServiceFactory.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using AsbCloudApp.Data; +using AsbCloudApp.Requests.Import; +using AsbCloudApp.Services; +using AsbCloudInfrastructure.Services.Trajectory.Parser; + +namespace AsbCloudInfrastructure.Services; + +public class ParserServiceFactory +{ + public const int IdTrajectoryFactManualParserService = 1; + public const int IdTrajectoryPlanParserService = 2; + + private readonly IDictionary> parsers = new Dictionary> + { + { IdTrajectoryPlanParserService, () => new TrajectoryPlanParserService() }, + { IdTrajectoryFactManualParserService, () => new TrajectoryFactManualParserService() } + }; + + public IParserService Create(int idImportService) + where TDto : class, IId + where TOptions : ParserOptionsRequestBase + { + var parser = parsers[idImportService].Invoke(); + + return parser as IParserService + ?? throw new ArgumentNullException(nameof(idImportService), "Не удалось распознать файл"); + } +} \ No newline at end of file diff --git a/AsbCloudInfrastructure/Services/Trajectory/Import/TrajectoryFactManualParserService.cs b/AsbCloudInfrastructure/Services/Trajectory/Import/TrajectoryFactManualParserService.cs deleted file mode 100644 index be08e12e..00000000 --- a/AsbCloudInfrastructure/Services/Trajectory/Import/TrajectoryFactManualParserService.cs +++ /dev/null @@ -1,30 +0,0 @@ -using AsbCloudApp.Data.Trajectory; -using ClosedXML.Excel; - -namespace AsbCloudInfrastructure.Services.Trajectory.Import -{ - - public class TrajectoryFactManualParserService : TrajectoryParserService - { - public override string templateFileName { get; } = "TrajectoryFactManualTemplate.xlsx"; - public override string usingTemplateFile { get; } = "AsbCloudInfrastructure.Services.Trajectory.Templates"; - public override string sheetName { get; } = "Фактическая траектория"; - public override int headerRowsCount { get; } = 2; - - protected override TrajectoryGeoFactDto ParseRow(IXLRow row) - { - var trajectoryRow = new TrajectoryGeoFactDto - { - WellboreDepth = row.Cell(1).GetCellValue(), - ZenithAngle = row.Cell(2).GetCellValue(), - AzimuthGeo = row.Cell(3).GetCellValue(), - AzimuthMagnetic = row.Cell(4).GetCellValue(), - VerticalDepth = row.Cell(5).GetCellValue(), - Comment = row.Cell(6).GetCellValue() - }; - //TODO: Добавить валидацию модели IValidatableObject - return trajectoryRow; - } - } -} - diff --git a/AsbCloudInfrastructure/Services/Trajectory/Import/TrajectoryParserService.cs b/AsbCloudInfrastructure/Services/Trajectory/Import/TrajectoryParserService.cs deleted file mode 100644 index c67bc409..00000000 --- a/AsbCloudInfrastructure/Services/Trajectory/Import/TrajectoryParserService.cs +++ /dev/null @@ -1,78 +0,0 @@ -using AsbCloudApp.Data.Trajectory; -using ClosedXML.Excel; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -namespace AsbCloudInfrastructure.Services.Trajectory.Import -{ - public abstract class TrajectoryParserService - where T : TrajectoryGeoDto - { - public abstract string templateFileName { get; } - public abstract string usingTemplateFile { get; } - public abstract string sheetName { get; } - public abstract int headerRowsCount { get; } - - protected abstract T ParseRow(IXLRow row); - - public IEnumerable Import(Stream stream) - { - using var workbook = new XLWorkbook(stream, XLEventTracking.Disabled); - var trajectoryRows = ParseFileStream(stream); - - return trajectoryRows; - } - - - private IEnumerable ParseFileStream(Stream stream) - { - using var workbook = new XLWorkbook(stream, XLEventTracking.Disabled); - return ParseWorkbook(workbook); - } - - private IEnumerable ParseWorkbook(IXLWorkbook workbook) - { - var sheetTrajectory = workbook.Worksheets.FirstOrDefault(ws => ws.Name == sheetName); - if (sheetTrajectory is null) - throw new FileFormatException($"Книга excel не содержит листа {sheetName}."); - var trajectoryRows = ParseSheet(sheetTrajectory); - return trajectoryRows; - } - - private IEnumerable ParseSheet(IXLWorksheet sheet) - { - if (sheet.RangeUsed().RangeAddress.LastAddress.ColumnNumber < 6) - throw new FileFormatException($"Лист {sheet.Name} содержит меньшее количество столбцов."); - - var count = sheet.RowsUsed().Count() - headerRowsCount; - - if (count > 1024) - throw new FileFormatException($"Лист {sheet.Name} содержит слишком большое количество строк."); - - if (count <= 0) - throw new FileFormatException($"Лист {sheet.Name} некорректного формата либо пустой"); - - var trajectoryRows = new List(count); - var parseErrors = new List(); - for (int i = 0; i < count; i++) - { - var row = sheet.Row(1 + i + headerRowsCount); - try - { - var trajectoryRow = ParseRow(row); - trajectoryRows.Add(trajectoryRow); - } - catch (FileFormatException ex) - { - parseErrors.Add(ex.Message); - } - } - - if (parseErrors.Any()) - throw new FileFormatException(string.Join("\r\n", parseErrors)); - - return trajectoryRows; - } - } -} diff --git a/AsbCloudInfrastructure/Services/Trajectory/Import/TrajectoryPlanParserService.cs b/AsbCloudInfrastructure/Services/Trajectory/Import/TrajectoryPlanParserService.cs deleted file mode 100644 index 00f3f197..00000000 --- a/AsbCloudInfrastructure/Services/Trajectory/Import/TrajectoryPlanParserService.cs +++ /dev/null @@ -1,33 +0,0 @@ -using AsbCloudApp.Data.Trajectory; -using ClosedXML.Excel; - -namespace AsbCloudInfrastructure.Services.Trajectory.Import -{ - - public class TrajectoryPlanParserService : TrajectoryParserService - { - public override string templateFileName { get; } = "TrajectoryPlanTemplate.xlsx"; - public override string usingTemplateFile { get; } = "AsbCloudInfrastructure.Services.Trajectory.Templates"; - public override string sheetName { get; } = "Плановая траектория"; - public override int headerRowsCount { get; } = 2; - - protected override TrajectoryGeoPlanDto ParseRow(IXLRow row) - { - var trajectoryRow = new TrajectoryGeoPlanDto - { - WellboreDepth = row.Cell(1).GetCellValue(), - ZenithAngle = row.Cell(2).GetCellValue(), - AzimuthGeo = row.Cell(3).GetCellValue(), - AzimuthMagnetic = row.Cell(4).GetCellValue(), - VerticalDepth = row.Cell(5).GetCellValue(), - Radius = row.Cell(6).GetCellValue(), - Comment = row.Cell(7).GetCellValue() - }; - - //TODO: Добавить валидацию модели IValidatableObject - return trajectoryRow; - } - } - -} - diff --git a/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryFactManualParserService.cs b/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryFactManualParserService.cs new file mode 100644 index 00000000..6a3a89cc --- /dev/null +++ b/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryFactManualParserService.cs @@ -0,0 +1,28 @@ +using AsbCloudApp.Data; +using AsbCloudApp.Data.Trajectory; +using ClosedXML.Excel; + +namespace AsbCloudInfrastructure.Services.Trajectory.Parser; + +public class TrajectoryFactManualParserService : TrajectoryParserService +{ + protected override ValidationResultDto ParseRow(IXLRow row) + { + var trajectoryRow = new TrajectoryGeoFactDto + { + WellboreDepth = row.Cell(1).GetCellValue(), + ZenithAngle = row.Cell(2).GetCellValue(), + AzimuthGeo = row.Cell(3).GetCellValue(), + AzimuthMagnetic = row.Cell(4).GetCellValue(), + VerticalDepth = row.Cell(5).GetCellValue(), + Comment = row.Cell(6).GetCellValue() + }; + + //TODO: Добавить валидацию модели + + return new ValidationResultDto + { + Item = trajectoryRow + }; + } +} \ No newline at end of file diff --git a/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryParserService.cs b/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryParserService.cs new file mode 100644 index 00000000..6b5177fe --- /dev/null +++ b/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryParserService.cs @@ -0,0 +1,78 @@ +using AsbCloudApp.Data.Trajectory; +using ClosedXML.Excel; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using AsbCloudApp.Data; +using AsbCloudApp.Requests.Import; +using AsbCloudApp.Services; + +namespace AsbCloudInfrastructure.Services.Trajectory.Parser; + +public abstract class TrajectoryParserService : IParserService + where T : TrajectoryGeoDto +{ + protected abstract ValidationResultDto ParseRow(IXLRow row); + + public ParserResultDto Parse(Stream file, TrajectoryParserRequest options) + { + using var workbook = new XLWorkbook(file, XLEventTracking.Disabled); + var trajectoryRows = ParseFileStream(file, options); + + return trajectoryRows; + } + + private ParserResultDto ParseFileStream(Stream stream, TrajectoryParserRequest options) + { + using var workbook = new XLWorkbook(stream, XLEventTracking.Disabled); + return ParseWorkbook(workbook, options); + } + + private ParserResultDto ParseWorkbook(IXLWorkbook workbook, TrajectoryParserRequest options) + { + var sheetTrajectory = workbook.Worksheets.FirstOrDefault(ws => + ws.Name.ToLower().Trim() == options.SheetName.ToLower().Trim()); + if (sheetTrajectory is null) + throw new FileFormatException($"Книга excel не содержит листа {options.SheetName}."); + var trajectoryRows = ParseSheet(sheetTrajectory, options); + return trajectoryRows; + } + + private ParserResultDto ParseSheet(IXLWorksheet sheet, TrajectoryParserRequest options) + { + if (sheet.RangeUsed().RangeAddress.LastAddress.ColumnNumber < 6) + throw new FileFormatException($"Лист {sheet.Name} содержит меньшее количество столбцов."); + + var count = sheet.RowsUsed().Count() - options.HeaderRowsCount; + + if (count > 1024) + throw new FileFormatException($"Лист {sheet.Name} содержит слишком большое количество строк."); + + if (count <= 0) + throw new FileFormatException($"Лист {sheet.Name} некорректного формата либо пустой"); + + var trajectoryRows = new List>(count); + var parseErrors = new List(); + for (int i = 0; i < count; i++) + { + var row = sheet.Row(1 + i + options.HeaderRowsCount); + try + { + var trajectoryRow = ParseRow(row); + trajectoryRows.Add(trajectoryRow); + } + catch (FileFormatException ex) + { + parseErrors.Add(ex.Message); + } + } + + if (parseErrors.Any()) + throw new FileFormatException(string.Join("\r\n", parseErrors)); + + return new ParserResultDto + { + Item = trajectoryRows + }; + } +} \ No newline at end of file diff --git a/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryPlanParserService.cs b/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryPlanParserService.cs new file mode 100644 index 00000000..6b4654fd --- /dev/null +++ b/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryPlanParserService.cs @@ -0,0 +1,29 @@ +using AsbCloudApp.Data; +using AsbCloudApp.Data.Trajectory; +using ClosedXML.Excel; + +namespace AsbCloudInfrastructure.Services.Trajectory.Parser; + +public class TrajectoryPlanParserService : TrajectoryParserService +{ + protected override ValidationResultDto ParseRow(IXLRow row) + { + var trajectoryRow = new TrajectoryGeoPlanDto + { + WellboreDepth = row.Cell(1).GetCellValue(), + ZenithAngle = row.Cell(2).GetCellValue(), + AzimuthGeo = row.Cell(3).GetCellValue(), + AzimuthMagnetic = row.Cell(4).GetCellValue(), + VerticalDepth = row.Cell(5).GetCellValue(), + Radius = row.Cell(6).GetCellValue(), + Comment = row.Cell(7).GetCellValue() + }; + + //TODO: Добавить валидацию модели + + return new ValidationResultDto + { + Item = trajectoryRow + }; + } +} \ No newline at end of file diff --git a/AsbCloudWebApi.Tests/Services/Trajectory/TrajectoryImportTest.cs b/AsbCloudWebApi.Tests/Services/Trajectory/TrajectoryImportTest.cs deleted file mode 100644 index e6763582..00000000 --- a/AsbCloudWebApi.Tests/Services/Trajectory/TrajectoryImportTest.cs +++ /dev/null @@ -1,48 +0,0 @@ -using AsbCloudInfrastructure.Services.Trajectory.Import; -using System.Linq; -using Xunit; - -namespace AsbCloudWebApi.Tests.Services.Trajectory -{ - public class TrajectoryImportTest - { - private readonly TrajectoryPlanParserService trajectoryPlanImportService; - private readonly TrajectoryFactManualParserService trajectoryFactManualImportService; - - private string usingTemplateFile = "AsbCloudWebApi.Tests.Services.Trajectory.Templates"; - - public TrajectoryImportTest() - { - trajectoryPlanImportService = new TrajectoryPlanParserService(); - trajectoryFactManualImportService = new TrajectoryFactManualParserService(); - } - - [Fact] - public void Import_trajectory_plan() - { - var stream = System.Reflection.Assembly.GetExecutingAssembly() - .GetManifestResourceStream($"{usingTemplateFile}.TrajectoryPlanTemplate.xlsx"); - - if (stream is null) - Assert.Fail("Файла для импорта не существует"); - - var trajectoryRows = trajectoryPlanImportService.Import(stream); - - Assert.Equal(3, trajectoryRows.Count()); - } - - [Fact] - public void Import_trajectory_fact_manual() - { - var stream = System.Reflection.Assembly.GetExecutingAssembly() - .GetManifestResourceStream($"{usingTemplateFile}.TrajectoryFactManualTemplate.xlsx"); - - if (stream is null) - Assert.Fail("Файла для импорта не существует"); - - var trajectoryRows = trajectoryFactManualImportService.Import(stream); - - Assert.Equal(4, trajectoryRows.Count()); - } - } -} diff --git a/AsbCloudWebApi.Tests/Services/Trajectory/TrajectoryParserTest.cs b/AsbCloudWebApi.Tests/Services/Trajectory/TrajectoryParserTest.cs new file mode 100644 index 00000000..0da13a71 --- /dev/null +++ b/AsbCloudWebApi.Tests/Services/Trajectory/TrajectoryParserTest.cs @@ -0,0 +1,60 @@ +using System.Linq; +using AsbCloudApp.Data.Trajectory; +using AsbCloudApp.Requests.Import; +using AsbCloudInfrastructure.Services; +using Xunit; + +namespace AsbCloudWebApi.Tests.Services.Trajectory; + +public class TrajectoryParserTest +{ + private const string UsingTemplateFile = "AsbCloudWebApi.Tests.Services.Trajectory.Templates"; + + private readonly TrajectoryParserRequest planTrajectoryParserOptions = new() + { + IdParserService = ParserServiceFactory.IdTrajectoryPlanParserService, + SheetName = "Плановая траектория", + HeaderRowsCount = 2 + }; + + private readonly TrajectoryParserRequest factTrajectoryParserOptions = new() + { + IdParserService = ParserServiceFactory.IdTrajectoryFactManualParserService, + SheetName = "Фактическая траектория", + HeaderRowsCount = 2 + }; + + private static readonly ParserServiceFactory parserServiceFactory = new(); + + [Fact] + public void Parse_trajectory_plan() + { + var stream = System.Reflection.Assembly.GetExecutingAssembly() + .GetManifestResourceStream($"{UsingTemplateFile}.TrajectoryPlanTemplate.xlsx"); + + if (stream is null) + Assert.Fail("Файла для импорта не существует"); + + var parserService = parserServiceFactory.Create( + planTrajectoryParserOptions.IdParserService); + var trajectoryRows = parserService.Parse(stream, planTrajectoryParserOptions); + + Assert.Equal(3, trajectoryRows.Item.Count()); + } + + [Fact] + public void Parse_trajectory_fact_manual() + { + var stream = System.Reflection.Assembly.GetExecutingAssembly() + .GetManifestResourceStream($"{UsingTemplateFile}.TrajectoryFactManualTemplate.xlsx"); + + if (stream is null) + Assert.Fail("Файла для импорта не существует"); + + var parserService = parserServiceFactory.Create( + factTrajectoryParserOptions.IdParserService); + var trajectoryRows = parserService.Parse(stream, factTrajectoryParserOptions); + + Assert.Equal(4, trajectoryRows.Item.Count()); + } +} \ No newline at end of file From 4874a9288ba6a168574be28fdfdb92e3dae65685 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=82=D0=B5=D0=BF=D0=B0=D0=BD=D0=BE=D0=B2=20=D0=94?= =?UTF-8?q?=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Mon, 29 Jan 2024 15:39:39 +0500 Subject: [PATCH 03/12] =?UTF-8?q?=D0=A0=D0=B5=D1=84=D0=B0=D0=BA=D1=82?= =?UTF-8?q?=D0=BE=D1=80=D0=B8=D0=BD=D0=B3=20=D0=BA=D0=BE=D0=BD=D1=82=D1=80?= =?UTF-8?q?=D0=BE=D0=BB=D0=BB=D0=B5=D1=80=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Добавлен метод расширения для парсинга Excel файлов 2. Рефакторинг контроллеров траекторий --- .../TrajectoryEditableController.cs | 156 ++++++++---------- .../TrajectoryFactManualController.cs | 30 ++-- .../Trajectory/TrajectoryPlanController.cs | 90 +++++----- AsbCloudWebApi/Extentions.cs | 35 +++- 4 files changed, 170 insertions(+), 141 deletions(-) diff --git a/AsbCloudWebApi/Controllers/Trajectory/TrajectoryEditableController.cs b/AsbCloudWebApi/Controllers/Trajectory/TrajectoryEditableController.cs index 10ed41f3..acd7a7d2 100644 --- a/AsbCloudWebApi/Controllers/Trajectory/TrajectoryEditableController.cs +++ b/AsbCloudWebApi/Controllers/Trajectory/TrajectoryEditableController.cs @@ -2,14 +2,15 @@ using AsbCloudApp.Repositories; using AsbCloudApp.Services; using AsbCloudInfrastructure.Services.Trajectory.Export; -using AsbCloudInfrastructure.Services.Trajectory.Import; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using System.Collections.Generic; -using System.IO; using System.Threading; using System.Threading.Tasks; +using AsbCloudApp.Data; +using AsbCloudApp.Requests.Import; +using AsbCloudInfrastructure.Services; namespace AsbCloudWebApi.Controllers.Trajectory { @@ -21,26 +22,27 @@ namespace AsbCloudWebApi.Controllers.Trajectory [Authorize] public abstract class TrajectoryEditableController : TrajectoryController where TDto : TrajectoryGeoDto - { - private readonly TrajectoryParserService trajectoryImportService; - private readonly TrajectoryExportService trajectoryExportService; + { + private readonly ParserServiceFactory parserServiceFactory; + private readonly TrajectoryExportService trajectoryExportService; private readonly ITrajectoryEditableRepository trajectoryRepository; - public TrajectoryEditableController(IWellService wellService, - TrajectoryParserService trajectoryImportService, - TrajectoryExportService trajectoryExportService, + protected TrajectoryEditableController(IWellService wellService, + ParserServiceFactory parserServiceFactory, + TrajectoryExportService trajectoryExportService, ITrajectoryEditableRepository trajectoryRepository) : base( wellService, trajectoryExportService, trajectoryRepository) { - this.trajectoryImportService = trajectoryImportService; - this.trajectoryExportService = trajectoryExportService; + this.trajectoryExportService = trajectoryExportService; this.trajectoryRepository = trajectoryRepository; + this.parserServiceFactory = parserServiceFactory; + } - } - + protected abstract TrajectoryParserRequest ParserOptions { get; } + /// /// Возвращает excel шаблон для заполнения строк траектории /// @@ -55,56 +57,32 @@ namespace AsbCloudWebApi.Controllers.Trajectory return File(stream, "application/octet-stream", fileName); } - /// - /// Импортирует координаты из excel (xlsx) файла - /// - /// id скважины - /// Коллекция из одного файла xlsx - /// Удалить операции перед импортом, если фал валидный - /// Токен отмены задачи - /// количество успешно записанных строк в БД - [HttpPost("import/{deleteBeforeImport}")] - [ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)] - [ProducesResponseType(typeof(ValidationProblemDetails), (int)System.Net.HttpStatusCode.BadRequest)] - public async Task ImportAsync(int idWell, - [FromForm] IFormFileCollection files, - bool deleteBeforeImport, - CancellationToken token) - { - int? idUser = User.GetUserId(); - if (!idUser.HasValue) - return Forbid(); - if (!await CanUserAccessToWellAsync(idWell, - token).ConfigureAwait(false)) - return Forbid(); - if (files.Count < 1) - return this.ValidationBadRequest(nameof(files), "нет файла"); - var file = files[0]; - if (Path.GetExtension(file.FileName).ToLower() != ".xlsx") - return this.ValidationBadRequest(nameof(files), "Требуется xlsx файл."); - using Stream stream = file.OpenReadStream(); + /// + /// Импортирует координаты из excel (xlsx) файла + /// + /// id скважины + /// Коллекция из одного файла xlsx + /// Токен отмены задачи + /// количество успешно записанных строк в БД + [HttpPost("parse")] + [ProducesResponseType((int)System.Net.HttpStatusCode.OK)] + [ProducesResponseType(typeof(ValidationProblemDetails), (int)System.Net.HttpStatusCode.BadRequest)] + public async Task>> Parse(int idWell, + [FromForm] IFormFileCollection files, + CancellationToken token) + { + var idUser = User.GetUserId(); - try - { - var trajectoryRows = trajectoryImportService.Import(stream); - foreach (var row in trajectoryRows) - { - row.IdWell = idWell; - row.IdUser = idUser.Value; - } + if (!idUser.HasValue) + return Forbid(); - if (deleteBeforeImport) - await trajectoryRepository.DeleteByIdWellAsync(idWell, token); + if (!await CanUserAccessToWellAsync(idWell, token)) + return Forbid(); - var rowsCount = await trajectoryRepository.AddRangeAsync(trajectoryRows, token); + var parserService = parserServiceFactory.Create(ParserOptions.IdParserService); - return Ok(rowsCount); - } - catch (FileFormatException ex) - { - return this.ValidationBadRequest(nameof(files), ex.Message); - } - } + return this.ParseExcelFile(files, ParserOptions, parserService); + } /// @@ -130,31 +108,41 @@ namespace AsbCloudWebApi.Controllers.Trajectory return Ok(result); } - /// - /// Добавить массив строчек координат для плановой траектории - /// - /// - /// - /// - /// количество успешно записанных строк в БД - [HttpPost("range")] - [ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)] - public async Task AddRangeAsync(int idWell, [FromBody] IEnumerable rows, - CancellationToken token) - { - if (!await CanUserAccessToWellAsync(idWell, token).ConfigureAwait(false)) - return Forbid(); - int? idUser = User.GetUserId(); - if (!idUser.HasValue) - return Forbid(); - foreach (var item in rows) - { - item.IdUser = idUser.Value; - item.IdWell = idWell; - } - var result = await trajectoryRepository.AddRangeAsync(rows, token); - return Ok(result); - } + /// + /// Добавить массив строчек координат для плановой траектории + /// + /// + /// + /// + /// + /// количество успешно записанных строк в БД + [HttpPost("range/{deleteBeforeInsert:bool}")] + [ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)] + public async Task AddRangeAsync(int idWell, + [FromBody] IEnumerable dtos, + bool deleteBeforeInsert, + CancellationToken token) + { + if (!await CanUserAccessToWellAsync(idWell, token)) + return Forbid(); + + var idUser = User.GetUserId(); + + if (!idUser.HasValue) + return Forbid(); + + foreach (var dto in dtos) + { + dto.IdUser = idUser.Value; + dto.IdWell = idWell; + } + + if (deleteBeforeInsert) + await trajectoryRepository.DeleteByIdWellAsync(idWell, token); + + var result = await trajectoryRepository.AddRangeAsync(dtos, token); + return Ok(result); + } /// /// Изменить выбранную строку с координатами @@ -201,4 +189,4 @@ namespace AsbCloudWebApi.Controllers.Trajectory return Ok(result); } } -} +} \ No newline at end of file diff --git a/AsbCloudWebApi/Controllers/Trajectory/TrajectoryFactManualController.cs b/AsbCloudWebApi/Controllers/Trajectory/TrajectoryFactManualController.cs index e3020342..65ee4b9e 100644 --- a/AsbCloudWebApi/Controllers/Trajectory/TrajectoryFactManualController.cs +++ b/AsbCloudWebApi/Controllers/Trajectory/TrajectoryFactManualController.cs @@ -1,8 +1,9 @@ using AsbCloudApp.Data.Trajectory; using AsbCloudApp.Repositories; +using AsbCloudApp.Requests.Import; using AsbCloudApp.Services; +using AsbCloudInfrastructure.Services; using AsbCloudInfrastructure.Services.Trajectory.Export; -using AsbCloudInfrastructure.Services.Trajectory.Import; using Microsoft.AspNetCore.Mvc; namespace AsbCloudWebApi.Controllers.Trajectory; @@ -14,15 +15,20 @@ namespace AsbCloudWebApi.Controllers.Trajectory; [Route("api/well/{idWell}/[controller]")] public class TrajectoryFactManualController : TrajectoryEditableController { - protected override string fileName => "ЕЦП_шаблон_файла_фактическая_траектория.xlsx"; - public TrajectoryFactManualController(IWellService wellService, - TrajectoryFactManualParserService factTrajectoryImportService, - TrajectoryFactManualExportService factTrajectoryExportService, - ITrajectoryEditableRepository trajectoryFactRepository) - : base( - wellService, - factTrajectoryImportService, - factTrajectoryExportService, - trajectoryFactRepository) - { } + protected override string fileName => "ЕЦП_шаблон_файла_фактическая_траектория.xlsx"; + + public TrajectoryFactManualController(IWellService wellService, + TrajectoryFactManualExportService trajectoryExportService, + ParserServiceFactory parserServiceFactory, + ITrajectoryEditableRepository trajectoryRepository) + : base(wellService, parserServiceFactory, trajectoryExportService, trajectoryRepository) + { + } + + protected override TrajectoryParserRequest ParserOptions => new() + { + IdParserService = ParserServiceFactory.IdTrajectoryFactManualParserService, + SheetName = "Фактическая траектория", + HeaderRowsCount = 2 + }; } \ No newline at end of file diff --git a/AsbCloudWebApi/Controllers/Trajectory/TrajectoryPlanController.cs b/AsbCloudWebApi/Controllers/Trajectory/TrajectoryPlanController.cs index 122352ad..87c4ac20 100644 --- a/AsbCloudWebApi/Controllers/Trajectory/TrajectoryPlanController.cs +++ b/AsbCloudWebApi/Controllers/Trajectory/TrajectoryPlanController.cs @@ -2,58 +2,62 @@ using AsbCloudApp.Repositories; using AsbCloudApp.Services; using AsbCloudInfrastructure.Services.Trajectory; -using AsbCloudInfrastructure.Services.Trajectory.Import; using AsbCloudInfrastructure.Services.Trajectory.Export; using Microsoft.AspNetCore.Mvc; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using AsbCloudApp.Requests.Import; +using AsbCloudInfrastructure.Services; namespace AsbCloudWebApi.Controllers.Trajectory { + /// + /// Плановая траектория (загрузка и хранение) + /// + [Route("api/well/{idWell}/[controller]")] + [ApiController] + public class TrajectoryPlanController : TrajectoryEditableController + { + private readonly TrajectoryService trajectoryVisualizationService; - /// - /// Плановая траектория (загрузка и хранение) - /// - [Route("api/well/{idWell}/[controller]")] - [ApiController] - public class TrajectoryPlanController : TrajectoryEditableController - { - private readonly TrajectoryService trajectoryVisualizationService; + protected override string fileName => "ЕЦП_шаблон_файла_плановая_траектория.xlsx"; - protected override string fileName => "ЕЦП_шаблон_файла_плановая_траектория.xlsx"; + public TrajectoryPlanController(IWellService wellService, + TrajectoryPlanExportService trajectoryExportService, + ParserServiceFactory parserServiceFactory, + ITrajectoryEditableRepository trajectoryRepository, + TrajectoryService trajectoryVisualizationService) + : base(wellService, parserServiceFactory, trajectoryExportService, trajectoryRepository) + { + this.trajectoryVisualizationService = trajectoryVisualizationService; + } + + protected override TrajectoryParserRequest ParserOptions => new() + { + IdParserService = ParserServiceFactory.IdTrajectoryPlanParserService, + SheetName = "Плановая траектория", + HeaderRowsCount = 2 + }; - public TrajectoryPlanController(IWellService wellService, - TrajectoryPlanParserService trajectoryPlanImportService, - TrajectoryPlanExportService trajectoryPlanExportService, - ITrajectoryEditableRepository trajectoryPlanRepository, - TrajectoryService trajectoryVisualizationService) - : base( - wellService, - trajectoryPlanImportService, - trajectoryPlanExportService, - trajectoryPlanRepository) - { - this.trajectoryVisualizationService = trajectoryVisualizationService; - } + /// + /// Получение координат для визуализации траектории (плановой и фактической) + /// + /// + /// + /// + [HttpGet("trajectoryCartesianPlanFact")] + [ProducesResponseType( + typeof(TrajectoryPlanFactDto, IEnumerable>), + (int)System.Net.HttpStatusCode.OK)] + public async Task GetTrajectoryCartesianPlanFactAsync(int idWell, CancellationToken token) + { + if (!await CanUserAccessToWellAsync(idWell, + token).ConfigureAwait(false)) + return Forbid(); - /// - /// Получение координат для визуализации траектории (плановой и фактической) - /// - /// - /// - /// - [HttpGet("trajectoryCartesianPlanFact")] - [ProducesResponseType(typeof(TrajectoryPlanFactDto, IEnumerable>), (int)System.Net.HttpStatusCode.OK)] - public async Task GetTrajectoryCartesianPlanFactAsync(int idWell, CancellationToken token) - { - if (!await CanUserAccessToWellAsync(idWell, - token).ConfigureAwait(false)) - return Forbid(); - - var result = await trajectoryVisualizationService.GetTrajectoryCartesianAsync(idWell, token); - return Ok(result); - } - } - -} + var result = await trajectoryVisualizationService.GetTrajectoryCartesianAsync(idWell, token); + return Ok(result); + } + } +} \ No newline at end of file diff --git a/AsbCloudWebApi/Extentions.cs b/AsbCloudWebApi/Extentions.cs index ba87612f..4e643a74 100644 --- a/AsbCloudWebApi/Extentions.cs +++ b/AsbCloudWebApi/Extentions.cs @@ -3,7 +3,12 @@ using AsbCloudWebApi.Converters; using System; using System.Collections.Generic; using System.ComponentModel; +using System.IO; using System.Security.Claims; +using AsbCloudApp.Data; +using AsbCloudApp.Requests.Import; +using AsbCloudApp.Services; +using Microsoft.AspNetCore.Http; namespace Microsoft.AspNetCore.Mvc { @@ -59,6 +64,32 @@ namespace Microsoft.AspNetCore.Mvc return options; } - } -} + public static ActionResult> ParseExcelFile(this ControllerBase controller, + IFormFileCollection files, + TOptions options, + IParserService parserService) + where TDto : class, IId + where TOptions : ParserOptionsRequestBase + { + if (files.Count < 1) + return controller.ValidationBadRequest(nameof(files), "Нет файла"); + var file = files[0]; + if (Path.GetExtension(file.FileName).ToLower() != ".xlsx") + return controller.ValidationBadRequest(nameof(files), "Требуется .xlsx файл."); + + using var stream = file.OpenReadStream(); + + try + { + var items = parserService.Parse(stream, options); + + return controller.Ok(items); + } + catch (FileFormatException ex) + { + return controller.ValidationBadRequest(nameof(files), ex.Message); + } + } + } +} \ No newline at end of file From a47a5799c116fef7ea4286d270feae1babc7b54f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=82=D0=B5=D0=BF=D0=B0=D0=BD=D0=BE=D0=B2=20=D0=94?= =?UTF-8?q?=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Mon, 29 Jan 2024 15:45:36 +0500 Subject: [PATCH 04/12] fix --- AsbCloudInfrastructure/DependencyInjection.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/AsbCloudInfrastructure/DependencyInjection.cs b/AsbCloudInfrastructure/DependencyInjection.cs index ce33cda3..28a051ab 100644 --- a/AsbCloudInfrastructure/DependencyInjection.cs +++ b/AsbCloudInfrastructure/DependencyInjection.cs @@ -44,6 +44,7 @@ using AsbCloudDb.Model.DailyReports.Blocks.TimeBalance; using AsbCloudDb.Model.WellSections; using AsbCloudInfrastructure.Services.ProcessMaps; using AsbCloudApp.Data.ProcessMapPlan; +using AsbCloudApp.Requests; using AsbCloudInfrastructure.Services.Trajectory.Parser; namespace AsbCloudInfrastructure From 501ef529381396f705e4bfd1c7c58b9ebe4cc124 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=82=D0=B5=D0=BF=D0=B0=D0=BD=D0=BE=D0=B2=20=D0=94?= =?UTF-8?q?=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Wed, 31 Jan 2024 17:18:02 +0500 Subject: [PATCH 05/12] =?UTF-8?q?=D0=A0=D0=B5=D1=84=D0=B0=D0=BA=D1=82?= =?UTF-8?q?=D0=BE=D1=80=D0=B8=D0=BD=D0=B3=20=D0=B4=D0=BE=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Разделил сервисы парсинга. 2. Выделил интерфейс маркер для доп. параметров парсинга. Данный сервис применяется только для ограничения обощённых параметров. --- .../Import/ParserOptionsRequestBase.cs | 17 ----------------- .../Import/TrajectoryParserRequest.cs | 12 ------------ .../ParserOptions/IParserOptionsRequest.cs | 8 ++++++++ AsbCloudApp/Services/Parser/IParserService.cs | 19 +++++++++++++++++++ .../IParserServiceWithOptions.cs} | 17 +++++------------ 5 files changed, 32 insertions(+), 41 deletions(-) delete mode 100644 AsbCloudApp/Requests/Import/ParserOptionsRequestBase.cs delete mode 100644 AsbCloudApp/Requests/Import/TrajectoryParserRequest.cs create mode 100644 AsbCloudApp/Requests/ParserOptions/IParserOptionsRequest.cs create mode 100644 AsbCloudApp/Services/Parser/IParserService.cs rename AsbCloudApp/Services/{IParserService.cs => Parser/IParserServiceWithOptions.cs} (54%) diff --git a/AsbCloudApp/Requests/Import/ParserOptionsRequestBase.cs b/AsbCloudApp/Requests/Import/ParserOptionsRequestBase.cs deleted file mode 100644 index 852459ee..00000000 --- a/AsbCloudApp/Requests/Import/ParserOptionsRequestBase.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace AsbCloudApp.Requests.Import; - -/// -/// Параметры парсинга -/// -public abstract class ParserOptionsRequestBase -{ - /// - /// Id сервиса для парсинга - /// - public int IdParserService { get; set; } - - /// - /// Название листа в файле Excel - /// - public string SheetName { get; set; } = null!; -} \ No newline at end of file diff --git a/AsbCloudApp/Requests/Import/TrajectoryParserRequest.cs b/AsbCloudApp/Requests/Import/TrajectoryParserRequest.cs deleted file mode 100644 index 6a7fedab..00000000 --- a/AsbCloudApp/Requests/Import/TrajectoryParserRequest.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace AsbCloudApp.Requests.Import; - -/// -/// Параметры парсинга траекторий -/// -public class TrajectoryParserRequest : ParserOptionsRequestBase -{ - /// - /// Количество строк заголовка - /// - public int HeaderRowsCount { get; set; } -} \ No newline at end of file diff --git a/AsbCloudApp/Requests/ParserOptions/IParserOptionsRequest.cs b/AsbCloudApp/Requests/ParserOptions/IParserOptionsRequest.cs new file mode 100644 index 00000000..dd8f8e4f --- /dev/null +++ b/AsbCloudApp/Requests/ParserOptions/IParserOptionsRequest.cs @@ -0,0 +1,8 @@ +namespace AsbCloudApp.Requests.ParserOptions; + +/// +/// Интерфейс для параметров парсера +/// +public interface IParserOptionsRequest +{ +} \ No newline at end of file diff --git a/AsbCloudApp/Services/Parser/IParserService.cs b/AsbCloudApp/Services/Parser/IParserService.cs new file mode 100644 index 00000000..628859bb --- /dev/null +++ b/AsbCloudApp/Services/Parser/IParserService.cs @@ -0,0 +1,19 @@ +using System.IO; +using AsbCloudApp.Data; + +namespace AsbCloudApp.Services.Parser; + +/// +/// Сервис парсинга файлов +/// +/// +public interface IParserService + where TDto : class, IId +{ + /// + /// Распарсить файл + /// + /// + /// + ParserResultDto Parse(Stream file); +} \ No newline at end of file diff --git a/AsbCloudApp/Services/IParserService.cs b/AsbCloudApp/Services/Parser/IParserServiceWithOptions.cs similarity index 54% rename from AsbCloudApp/Services/IParserService.cs rename to AsbCloudApp/Services/Parser/IParserServiceWithOptions.cs index 81177c64..83e54a27 100644 --- a/AsbCloudApp/Services/IParserService.cs +++ b/AsbCloudApp/Services/Parser/IParserServiceWithOptions.cs @@ -1,17 +1,17 @@ using System.IO; using AsbCloudApp.Data; -using AsbCloudApp.Requests.Import; +using AsbCloudApp.Requests.ParserOptions; -namespace AsbCloudApp.Services; +namespace AsbCloudApp.Services.Parser; /// -/// Сервис парсинга файлов +/// Сервис парсинга файлов с доп. параметрами /// /// /// -public interface IParserService : IParserService +public interface IParserServiceWithOptions where TDto : class, IId - where TOptions : ParserOptionsRequestBase + where TOptions : IParserOptionsRequest { /// /// Распарсить файл @@ -20,11 +20,4 @@ public interface IParserService : IParserService /// /// ParserResultDto Parse(Stream file, TOptions options); -} - -/// -/// Сервис парсинга файлов -/// -public interface IParserService -{ } \ No newline at end of file From 108644c13d24e521d6342b5e4a8d630477701d9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=82=D0=B5=D0=BF=D0=B0=D0=BD=D0=BE=D0=B2=20=D0=94?= =?UTF-8?q?=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Wed, 31 Jan 2024 17:18:55 +0500 Subject: [PATCH 06/12] =?UTF-8?q?=D0=9C=D0=B5=D1=82=D0=BE=D0=B4=D1=8B=20?= =?UTF-8?q?=D1=80=D0=B0=D1=81=D1=88=D0=B8=D1=80=D0=B5=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Почистил методы расширения для ячеек 2. Добавил методы расширения для парсинга --- AsbCloudInfrastructure/XLExtentions.cs | 116 +------------------ AsbCloudInfrastructure/XLParserExtensions.cs | 59 ++++++++++ 2 files changed, 60 insertions(+), 115 deletions(-) create mode 100644 AsbCloudInfrastructure/XLParserExtensions.cs diff --git a/AsbCloudInfrastructure/XLExtentions.cs b/AsbCloudInfrastructure/XLExtentions.cs index 3a583bf9..401e2423 100644 --- a/AsbCloudInfrastructure/XLExtentions.cs +++ b/AsbCloudInfrastructure/XLExtentions.cs @@ -2,96 +2,11 @@ using System; using System.Globalization; using System.IO; -using AsbCloudInfrastructure.Services.DailyReport; namespace AsbCloudInfrastructure; internal static class XLExtentions { - internal static IXLRange _SetValue(this IXLRange range, object value) - { - var mergedRange = range.Merge(); - mergedRange.FirstCell()._SetValue(value); - var colWidth = mergedRange.FirstCell().WorksheetColumn().Width; - var maxCharsToWrap = colWidth / (0.1d * mergedRange.FirstCell().Style.Font.FontSize); - if (value is string valueString && valueString.Length > maxCharsToWrap) - { - var row = mergedRange.FirstCell().WorksheetRow(); - var baseHeight = row.Height; - row.Height = 0.5d * baseHeight * Math.Ceiling(1d + valueString.Length / maxCharsToWrap); - } - - mergedRange.Style.SetAllBorders() - .Alignment.SetWrapText(true); - return mergedRange; - } - - internal static IXLCell _SetValue(this IXLCell cell, object value) - { - switch (value) - { - case DateTime dateTime: - cell._SetValue(dateTime); - break; - case IFormattable formattable: - cell._SetValue(formattable); - break; - case string valueString: - cell._SetValue(valueString); - break; - default: - cell.Value = value; - break; - } - - return cell; - } - - internal static IXLCell _SetValue(this IXLCell cell, string value, bool adaptRowHeight = false) - { - cell.Value = value; - cell.Style - .SetAllBorders() - .Alignment.WrapText = true; - - cell.Value = value; - if (adaptRowHeight) - { - var colWidth = cell.WorksheetColumn().Width; - var maxCharsToWrap = colWidth / (0.1d * cell.Style.Font.FontSize); - if (value.Length > maxCharsToWrap) - { - var row = cell.WorksheetRow(); - var baseHeight = row.Height; - row.Height = 0.5d * baseHeight * Math.Ceiling(1d + value.Length / maxCharsToWrap); - } - } - - return cell; - } - - internal static IXLCell _ValueNoBorder(this IXLCell cell, string value, bool adaptRowHeight = false) - { - cell.Value = value; - cell.Style.Alignment.WrapText = true; - - cell.Value = value; - if (adaptRowHeight) - { - var colWidth = cell.WorksheetColumn().Width; - var maxCharsToWrap = colWidth / (0.1d * cell.Style.Font.FontSize); - if (value.Length > maxCharsToWrap) - { - var row = cell.WorksheetRow(); - var baseHeight = row.Height; - row.Height = 0.5d * baseHeight * Math.Ceiling(1d + value.Length / maxCharsToWrap); - } - } - - return cell; - } - - internal static IXLCell _SetValue(this IXLCell cell, DateTime value, string dateFormat = "DD.MM.YYYY HH:MM:SS", bool setAllBorders = true) { cell.Value = value; @@ -111,21 +26,6 @@ internal static class XLExtentions return cell; } - internal static IXLCell _SetValue(this IXLCell cell, IFormattable value, string format = "0.00") - { - cell.Value = value; - cell.Style - .SetAllBorders() - .Alignment.WrapText = true; - - cell.Value = value; - - cell.DataType = XLDataType.Number; - cell.Style.NumberFormat.Format = "0.00"; - - return cell; - } - public static IXLCell SetVal(this IXLCell cell, double? value, string format = "0.00") { cell.Value = (value is not null && double.IsFinite(value.Value)) ? value : null; @@ -161,7 +61,7 @@ internal static class XLExtentions return cell; } - internal static IXLStyle SetAllBorders(this IXLStyle style, XLBorderStyleValues borderStyle = XLBorderStyleValues.Thin) + private static IXLStyle SetAllBorders(this IXLStyle style, XLBorderStyleValues borderStyle = XLBorderStyleValues.Thin) { style.Border.RightBorder = borderStyle; style.Border.LeftBorder = borderStyle; @@ -172,20 +72,6 @@ internal static class XLExtentions return style; } - internal static IXLStyle SetBaseFont(this IXLStyle style) - { - style.Font.FontName = "Calibri"; - style.Font.FontSize = 10; - return style; - } - - internal static IXLStyle SetH1(this IXLStyle style) - { - style.Font.FontName = "Calibri"; - style.Font.FontSize = 14; - return style; - } - internal static T? GetCellValue(this IXLCell cell) { try diff --git a/AsbCloudInfrastructure/XLParserExtensions.cs b/AsbCloudInfrastructure/XLParserExtensions.cs new file mode 100644 index 00000000..28ecb089 --- /dev/null +++ b/AsbCloudInfrastructure/XLParserExtensions.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.IO; +using System.Linq; +using AsbCloudApp.Data; +using ClosedXML.Excel; + +namespace AsbCloudInfrastructure; + +public static class XLParserExtensions +{ + public static ParserResultDto Parse(this IXLWorksheet sheet, + Func> parseRow, + int columnCount, + int headerRowsCount = 0) + where TDto : class, IId + { + if (sheet.RangeUsed().RangeAddress.LastAddress.ColumnNumber < columnCount) + throw new FileFormatException($"Лист {sheet.Name} содержит меньшее количество столбцов."); + + var count = sheet.RowsUsed().Count() - headerRowsCount; + + if (count > 1024) + throw new FileFormatException($"Лист {sheet.Name} содержит слишком большое количество строк."); + + if (count <= 0) + return new ParserResultDto(); + + var dtos = new List>(count); + var warnings = new List(); + + for (var i = 0; i < count; i++) + { + var row = sheet.Row(1 + i + headerRowsCount); + + try + { + var dto = parseRow.Invoke(row); + dtos.Add(dto); + } + catch (FileFormatException ex) + { + var warning = new ValidationResult(ex.Message); + warnings.Add(warning); + } + } + + var parserResult = new ParserResultDto + { + Item = dtos + }; + + if (warnings.Any()) + parserResult.Warnings = warnings; + + return parserResult; + } +} \ No newline at end of file From 94c7e1e7c90ae5548f18d45feb0333f1baa31910 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=82=D0=B5=D0=BF=D0=B0=D0=BD=D0=BE=D0=B2=20=D0=94?= =?UTF-8?q?=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Wed, 31 Jan 2024 17:20:54 +0500 Subject: [PATCH 07/12] =?UTF-8?q?=D0=9D=D0=BE=D0=B2=D0=B0=D1=8F=20=D1=80?= =?UTF-8?q?=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20=D0=BF?= =?UTF-8?q?=D0=B0=D1=80=D1=81=D0=B8=D0=BD=D0=B3=D0=B0=20=D1=82=D1=80=D0=B0?= =?UTF-8?q?=D0=B5=D0=BA=D1=82=D0=BE=D1=80=D0=B8=D0=B9=20=D0=B8=20=D1=84?= =?UTF-8?q?=D0=B0=D0=B1=D1=80=D0=B8=D0=BA=D0=B8=20=D0=BF=D0=B0=D1=80=D1=81?= =?UTF-8?q?=D0=B5=D1=80=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Services/ParserServiceFactory.cs | 27 +++++-- .../TrajectoryFactManualParserService.cs | 2 + .../Parser/TrajectoryParserService.cs | 73 ++++--------------- .../Parser/TrajectoryPlanParserService.cs | 2 + .../Trajectory/TrajectoryParserTest.cs | 25 +------ 5 files changed, 40 insertions(+), 89 deletions(-) diff --git a/AsbCloudInfrastructure/Services/ParserServiceFactory.cs b/AsbCloudInfrastructure/Services/ParserServiceFactory.cs index 17f3ed23..7aeceb98 100644 --- a/AsbCloudInfrastructure/Services/ParserServiceFactory.cs +++ b/AsbCloudInfrastructure/Services/ParserServiceFactory.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; using AsbCloudApp.Data; -using AsbCloudApp.Requests.Import; -using AsbCloudApp.Services; +using AsbCloudApp.Requests.ParserOptions; +using AsbCloudApp.Services.Parser; using AsbCloudInfrastructure.Services.Trajectory.Parser; namespace AsbCloudInfrastructure.Services; @@ -12,19 +12,30 @@ public class ParserServiceFactory public const int IdTrajectoryFactManualParserService = 1; public const int IdTrajectoryPlanParserService = 2; - private readonly IDictionary> parsers = new Dictionary> + private readonly IDictionary> parsers = new Dictionary> { { IdTrajectoryPlanParserService, () => new TrajectoryPlanParserService() }, { IdTrajectoryFactManualParserService, () => new TrajectoryFactManualParserService() } }; - public IParserService Create(int idImportService) + public IParserService GetParser(int idParserService) where TDto : class, IId - where TOptions : ParserOptionsRequestBase { - var parser = parsers[idImportService].Invoke(); + if (!parsers.TryGetValue(idParserService, out var parserService)) + throw new ArgumentNullException(nameof(idParserService), "Сервис не зарегистрирован"); - return parser as IParserService - ?? throw new ArgumentNullException(nameof(idImportService), "Не удалось распознать файл"); + return parserService.Invoke() as IParserService + ?? throw new ArgumentNullException(nameof(idParserService), "Ошибка приведения типа"); + } + + public IParserServiceWithOptions GetParserWithOptions(int idParserService) + where TDto : class, IId + where TOptions : IParserOptionsRequest + { + if (!parsers.TryGetValue(idParserService, out var parserService)) + throw new ArgumentNullException(nameof(idParserService), "Сервис не зарегистрирован"); + + return parserService.Invoke() as IParserServiceWithOptions + ?? throw new ArgumentNullException(nameof(idParserService), "Ошибка приведения типа"); } } \ No newline at end of file diff --git a/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryFactManualParserService.cs b/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryFactManualParserService.cs index 6a3a89cc..d494c5b6 100644 --- a/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryFactManualParserService.cs +++ b/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryFactManualParserService.cs @@ -6,6 +6,8 @@ namespace AsbCloudInfrastructure.Services.Trajectory.Parser; public class TrajectoryFactManualParserService : TrajectoryParserService { + protected override string SheetName => "Фактическая траектория"; + protected override ValidationResultDto ParseRow(IXLRow row) { var trajectoryRow = new TrajectoryGeoFactDto diff --git a/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryParserService.cs b/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryParserService.cs index 6b5177fe..16dbf55d 100644 --- a/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryParserService.cs +++ b/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryParserService.cs @@ -1,78 +1,31 @@ using AsbCloudApp.Data.Trajectory; using ClosedXML.Excel; -using System.Collections.Generic; using System.IO; using System.Linq; using AsbCloudApp.Data; -using AsbCloudApp.Requests.Import; -using AsbCloudApp.Services; +using AsbCloudApp.Services.Parser; namespace AsbCloudInfrastructure.Services.Trajectory.Parser; -public abstract class TrajectoryParserService : IParserService +public abstract class TrajectoryParserService : IParserService where T : TrajectoryGeoDto { + private const int HeaderRowsCount = 2; + private const int ColumnCount = 6; + + protected abstract string SheetName { get; } + protected abstract ValidationResultDto ParseRow(IXLRow row); - public ParserResultDto Parse(Stream file, TrajectoryParserRequest options) + public ParserResultDto Parse(Stream file) { using var workbook = new XLWorkbook(file, XLEventTracking.Disabled); - var trajectoryRows = ParseFileStream(file, options); + var sheet = workbook.Worksheets.FirstOrDefault(ws => + ws.Name.ToLower().Trim() == SheetName.ToLower().Trim()) + ?? throw new FileFormatException($"Книга excel не содержит листа {SheetName}."); + + var trajectoryRows = sheet.Parse(ParseRow, ColumnCount, HeaderRowsCount); return trajectoryRows; } - - private ParserResultDto ParseFileStream(Stream stream, TrajectoryParserRequest options) - { - using var workbook = new XLWorkbook(stream, XLEventTracking.Disabled); - return ParseWorkbook(workbook, options); - } - - private ParserResultDto ParseWorkbook(IXLWorkbook workbook, TrajectoryParserRequest options) - { - var sheetTrajectory = workbook.Worksheets.FirstOrDefault(ws => - ws.Name.ToLower().Trim() == options.SheetName.ToLower().Trim()); - if (sheetTrajectory is null) - throw new FileFormatException($"Книга excel не содержит листа {options.SheetName}."); - var trajectoryRows = ParseSheet(sheetTrajectory, options); - return trajectoryRows; - } - - private ParserResultDto ParseSheet(IXLWorksheet sheet, TrajectoryParserRequest options) - { - if (sheet.RangeUsed().RangeAddress.LastAddress.ColumnNumber < 6) - throw new FileFormatException($"Лист {sheet.Name} содержит меньшее количество столбцов."); - - var count = sheet.RowsUsed().Count() - options.HeaderRowsCount; - - if (count > 1024) - throw new FileFormatException($"Лист {sheet.Name} содержит слишком большое количество строк."); - - if (count <= 0) - throw new FileFormatException($"Лист {sheet.Name} некорректного формата либо пустой"); - - var trajectoryRows = new List>(count); - var parseErrors = new List(); - for (int i = 0; i < count; i++) - { - var row = sheet.Row(1 + i + options.HeaderRowsCount); - try - { - var trajectoryRow = ParseRow(row); - trajectoryRows.Add(trajectoryRow); - } - catch (FileFormatException ex) - { - parseErrors.Add(ex.Message); - } - } - - if (parseErrors.Any()) - throw new FileFormatException(string.Join("\r\n", parseErrors)); - - return new ParserResultDto - { - Item = trajectoryRows - }; - } } \ No newline at end of file diff --git a/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryPlanParserService.cs b/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryPlanParserService.cs index 6b4654fd..97d7ca29 100644 --- a/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryPlanParserService.cs +++ b/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryPlanParserService.cs @@ -6,6 +6,8 @@ namespace AsbCloudInfrastructure.Services.Trajectory.Parser; public class TrajectoryPlanParserService : TrajectoryParserService { + protected override string SheetName => "Плановая траектория"; + protected override ValidationResultDto ParseRow(IXLRow row) { var trajectoryRow = new TrajectoryGeoPlanDto diff --git a/AsbCloudWebApi.Tests/Services/Trajectory/TrajectoryParserTest.cs b/AsbCloudWebApi.Tests/Services/Trajectory/TrajectoryParserTest.cs index 0da13a71..b5c722b4 100644 --- a/AsbCloudWebApi.Tests/Services/Trajectory/TrajectoryParserTest.cs +++ b/AsbCloudWebApi.Tests/Services/Trajectory/TrajectoryParserTest.cs @@ -1,6 +1,5 @@ using System.Linq; using AsbCloudApp.Data.Trajectory; -using AsbCloudApp.Requests.Import; using AsbCloudInfrastructure.Services; using Xunit; @@ -10,20 +9,6 @@ public class TrajectoryParserTest { private const string UsingTemplateFile = "AsbCloudWebApi.Tests.Services.Trajectory.Templates"; - private readonly TrajectoryParserRequest planTrajectoryParserOptions = new() - { - IdParserService = ParserServiceFactory.IdTrajectoryPlanParserService, - SheetName = "Плановая траектория", - HeaderRowsCount = 2 - }; - - private readonly TrajectoryParserRequest factTrajectoryParserOptions = new() - { - IdParserService = ParserServiceFactory.IdTrajectoryFactManualParserService, - SheetName = "Фактическая траектория", - HeaderRowsCount = 2 - }; - private static readonly ParserServiceFactory parserServiceFactory = new(); [Fact] @@ -35,9 +20,8 @@ public class TrajectoryParserTest if (stream is null) Assert.Fail("Файла для импорта не существует"); - var parserService = parserServiceFactory.Create( - planTrajectoryParserOptions.IdParserService); - var trajectoryRows = parserService.Parse(stream, planTrajectoryParserOptions); + var parserService = parserServiceFactory.GetParser(ParserServiceFactory.IdTrajectoryPlanParserService); + var trajectoryRows = parserService.Parse(stream); Assert.Equal(3, trajectoryRows.Item.Count()); } @@ -51,9 +35,8 @@ public class TrajectoryParserTest if (stream is null) Assert.Fail("Файла для импорта не существует"); - var parserService = parserServiceFactory.Create( - factTrajectoryParserOptions.IdParserService); - var trajectoryRows = parserService.Parse(stream, factTrajectoryParserOptions); + var parserService = parserServiceFactory.GetParser(ParserServiceFactory.IdTrajectoryFactManualParserService); + var trajectoryRows = parserService.Parse(stream); Assert.Equal(4, trajectoryRows.Item.Count()); } From 96a8c3fbd38c421ce09a9fe3234cb66deff15855 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=82=D0=B5=D0=BF=D0=B0=D0=BD=D0=BE=D0=B2=20=D0=94?= =?UTF-8?q?=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Wed, 31 Jan 2024 17:24:00 +0500 Subject: [PATCH 08/12] =?UTF-8?q?=D0=A0=D0=B5=D1=84=D0=B0=D0=BA=D1=82?= =?UTF-8?q?=D0=BE=D1=80=D0=B8=D0=BD=D0=B3=20=D0=BA=D0=BE=D0=BD=D1=82=D1=80?= =?UTF-8?q?=D0=BE=D0=BB=D0=BB=D0=B5=D1=80=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/Interfaces/IHasExcelParser.cs | 11 +++++ .../Interfaces/IHasExcelParserWithOptions.cs | 10 ++++ .../TrajectoryEditableController.cs | 30 ++++++++---- .../TrajectoryFactManualController.cs | 8 +--- .../Trajectory/TrajectoryPlanController.cs | 10 +--- AsbCloudWebApi/Extentions.cs | 46 ++++++++++--------- 6 files changed, 70 insertions(+), 45 deletions(-) create mode 100644 AsbCloudWebApi/Controllers/Interfaces/IHasExcelParser.cs create mode 100644 AsbCloudWebApi/Controllers/Interfaces/IHasExcelParserWithOptions.cs diff --git a/AsbCloudWebApi/Controllers/Interfaces/IHasExcelParser.cs b/AsbCloudWebApi/Controllers/Interfaces/IHasExcelParser.cs new file mode 100644 index 00000000..f78d17da --- /dev/null +++ b/AsbCloudWebApi/Controllers/Interfaces/IHasExcelParser.cs @@ -0,0 +1,11 @@ +using System.IO; +using AsbCloudApp.Data; +using Microsoft.AspNetCore.Mvc; + +namespace AsbCloudWebApi.Controllers.Interfaces; + +public interface IHasExcelParser + where TDto : class, IId +{ + ActionResult> Parse(Stream file); +} \ No newline at end of file diff --git a/AsbCloudWebApi/Controllers/Interfaces/IHasExcelParserWithOptions.cs b/AsbCloudWebApi/Controllers/Interfaces/IHasExcelParserWithOptions.cs new file mode 100644 index 00000000..96e95eef --- /dev/null +++ b/AsbCloudWebApi/Controllers/Interfaces/IHasExcelParserWithOptions.cs @@ -0,0 +1,10 @@ +using System.IO; +using AsbCloudApp.Data; + +namespace AsbCloudWebApi.Controllers.Interfaces; + +public interface IHasExcelParserWithOptions + where TDto : class, IId +{ + ParserResultDto Parse(Stream file, TOptions options); +} \ No newline at end of file diff --git a/AsbCloudWebApi/Controllers/Trajectory/TrajectoryEditableController.cs b/AsbCloudWebApi/Controllers/Trajectory/TrajectoryEditableController.cs index acd7a7d2..19f834e0 100644 --- a/AsbCloudWebApi/Controllers/Trajectory/TrajectoryEditableController.cs +++ b/AsbCloudWebApi/Controllers/Trajectory/TrajectoryEditableController.cs @@ -6,11 +6,12 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using System.Collections.Generic; +using System.IO; using System.Threading; using System.Threading.Tasks; using AsbCloudApp.Data; -using AsbCloudApp.Requests.Import; using AsbCloudInfrastructure.Services; +using AsbCloudWebApi.Controllers.Interfaces; namespace AsbCloudWebApi.Controllers.Trajectory { @@ -20,14 +21,15 @@ namespace AsbCloudWebApi.Controllers.Trajectory /// [ApiController] [Authorize] - public abstract class TrajectoryEditableController : TrajectoryController + public abstract class TrajectoryEditableController : TrajectoryController, + IHasExcelParser where TDto : TrajectoryGeoDto { private readonly ParserServiceFactory parserServiceFactory; private readonly TrajectoryExportService trajectoryExportService; private readonly ITrajectoryEditableRepository trajectoryRepository; - protected TrajectoryEditableController(IWellService wellService, + protected TrajectoryEditableController(IWellService wellService, ParserServiceFactory parserServiceFactory, TrajectoryExportService trajectoryExportService, ITrajectoryEditableRepository trajectoryRepository) @@ -41,7 +43,20 @@ namespace AsbCloudWebApi.Controllers.Trajectory this.parserServiceFactory = parserServiceFactory; } - protected abstract TrajectoryParserRequest ParserOptions { get; } + protected abstract int IdParserService { get; } + + ActionResult> IHasExcelParser.Parse(Stream file) + { + try + { + var parserService = parserServiceFactory.GetParser(IdParserService); + return Ok(parserService.Parse(file)); + } + catch (FileFormatException ex) + { + return this.ValidationBadRequest("files", ex.Message); + } + } /// /// Возвращает excel шаблон для заполнения строк траектории @@ -79,12 +94,9 @@ namespace AsbCloudWebApi.Controllers.Trajectory if (!await CanUserAccessToWellAsync(idWell, token)) return Forbid(); - var parserService = parserServiceFactory.Create(ParserOptions.IdParserService); - - return this.ParseExcelFile(files, ParserOptions, parserService); + return this.ParseExcelFile(files); } - - + /// /// Добавить одну новую строчку координат для плановой траектории /// diff --git a/AsbCloudWebApi/Controllers/Trajectory/TrajectoryFactManualController.cs b/AsbCloudWebApi/Controllers/Trajectory/TrajectoryFactManualController.cs index 65ee4b9e..081704ff 100644 --- a/AsbCloudWebApi/Controllers/Trajectory/TrajectoryFactManualController.cs +++ b/AsbCloudWebApi/Controllers/Trajectory/TrajectoryFactManualController.cs @@ -1,6 +1,5 @@ using AsbCloudApp.Data.Trajectory; using AsbCloudApp.Repositories; -using AsbCloudApp.Requests.Import; using AsbCloudApp.Services; using AsbCloudInfrastructure.Services; using AsbCloudInfrastructure.Services.Trajectory.Export; @@ -25,10 +24,5 @@ public class TrajectoryFactManualController : TrajectoryEditableController new() - { - IdParserService = ParserServiceFactory.IdTrajectoryFactManualParserService, - SheetName = "Фактическая траектория", - HeaderRowsCount = 2 - }; + protected override int IdParserService => ParserServiceFactory.IdTrajectoryFactManualParserService; } \ No newline at end of file diff --git a/AsbCloudWebApi/Controllers/Trajectory/TrajectoryPlanController.cs b/AsbCloudWebApi/Controllers/Trajectory/TrajectoryPlanController.cs index 87c4ac20..2d525c16 100644 --- a/AsbCloudWebApi/Controllers/Trajectory/TrajectoryPlanController.cs +++ b/AsbCloudWebApi/Controllers/Trajectory/TrajectoryPlanController.cs @@ -7,7 +7,6 @@ using Microsoft.AspNetCore.Mvc; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using AsbCloudApp.Requests.Import; using AsbCloudInfrastructure.Services; namespace AsbCloudWebApi.Controllers.Trajectory @@ -32,13 +31,8 @@ namespace AsbCloudWebApi.Controllers.Trajectory { this.trajectoryVisualizationService = trajectoryVisualizationService; } - - protected override TrajectoryParserRequest ParserOptions => new() - { - IdParserService = ParserServiceFactory.IdTrajectoryPlanParserService, - SheetName = "Плановая траектория", - HeaderRowsCount = 2 - }; + + protected override int IdParserService => ParserServiceFactory.IdTrajectoryPlanParserService; /// /// Получение координат для визуализации траектории (плановой и фактической) diff --git a/AsbCloudWebApi/Extentions.cs b/AsbCloudWebApi/Extentions.cs index 650fed2e..ab111444 100644 --- a/AsbCloudWebApi/Extentions.cs +++ b/AsbCloudWebApi/Extentions.cs @@ -8,8 +8,9 @@ using System.ComponentModel.DataAnnotations; using System.Linq; using System.Security.Claims; using AsbCloudApp.Data; -using AsbCloudApp.Requests.Import; -using AsbCloudApp.Services; +using AsbCloudApp.Exceptions; +using AsbCloudApp.Requests.ParserOptions; +using AsbCloudWebApi.Controllers.Interfaces; using Microsoft.AspNetCore.Http; namespace Microsoft.AspNetCore.Mvc @@ -87,32 +88,35 @@ namespace Microsoft.AspNetCore.Mvc return options; } - public static ActionResult> ParseExcelFile(this ControllerBase controller, - IFormFileCollection files, - TOptions options, - IParserService parserService) + public static ActionResult> ParseExcelFile(this IHasExcelParser controller, + IFormFileCollection files) where TDto : class, IId - where TOptions : ParserOptionsRequestBase + { + using var file = GetExcelFile(files); + return controller.Parse(file); + } + + public static ActionResult> ParseExcelFileWithOptions( + this IHasExcelParserWithOptions controller, + IFormFileCollection files, + TOptions options) + where TDto : class, IId + where TOptions : class, IParserOptionsRequest + { + using var file = GetExcelFile(files); + return controller.Parse(file, options); + } + + private static Stream GetExcelFile(IFormFileCollection files) { if (files.Count < 1) - return controller.ValidationBadRequest(nameof(files), "Нет файла"); + throw new ArgumentInvalidException(nameof(files), "Нет файла"); var file = files[0]; if (Path.GetExtension(file.FileName).ToLower() != ".xlsx") - return controller.ValidationBadRequest(nameof(files), "Требуется .xlsx файл."); + throw new ArgumentInvalidException(nameof(files), "Требуется .xlsx файл."); - using var stream = file.OpenReadStream(); - - try - { - var items = parserService.Parse(stream, options); - - return controller.Ok(items); - } - catch (FileFormatException ex) - { - return controller.ValidationBadRequest(nameof(files), ex.Message); - } + return file.OpenReadStream(); } } } \ No newline at end of file From 3865c330c209f58498340414b66cdfbad7d32b24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=82=D0=B5=D0=BF=D0=B0=D0=BD=D0=BE=D0=B2=20=D0=94?= =?UTF-8?q?=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Thu, 1 Feb 2024 16:02:19 +0500 Subject: [PATCH 09/12] =?UTF-8?q?=D0=A0=D1=84=D0=B5=D0=B0=D0=BA=D1=82?= =?UTF-8?q?=D0=BE=D1=80=D0=B8=D0=BD=D0=B3=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4?= =?UTF-8?q?=D0=B0=20=D1=80=D0=B0=D1=81=D1=88=D0=B8=D1=80=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D0=BF=D0=BE=D0=BB=D1=83=D1=87=D0=B5=D0=BD=D0=B8=D1=8F?= =?UTF-8?q?=20=D1=84=D0=B0=D0=B9=D0=BB=D0=B0=20=D0=B8=D0=B7=20=D1=81=D0=B1?= =?UTF-8?q?=D0=BE=D1=80=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AsbCloudInfrastructure/AssemblyExtensions.cs | 56 +++++++++++++------- 1 file changed, 38 insertions(+), 18 deletions(-) diff --git a/AsbCloudInfrastructure/AssemblyExtensions.cs b/AsbCloudInfrastructure/AssemblyExtensions.cs index 9594261b..03cf7325 100644 --- a/AsbCloudInfrastructure/AssemblyExtensions.cs +++ b/AsbCloudInfrastructure/AssemblyExtensions.cs @@ -1,30 +1,50 @@ using System; -using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; -using System.Text; using System.Threading; using System.Threading.Tasks; namespace AsbCloudInfrastructure { - public static class AssemblyExtensions - { - public static async Task GetTemplateCopyStreamAsync(this Assembly assembly, string templateName, CancellationToken cancellationToken) - { - var resourceName = assembly - .GetManifestResourceNames() - .FirstOrDefault(n => n.EndsWith(templateName))!; + public static class AssemblyExtensions + { + public static Stream GetTemplateCopyStream(this Assembly assembly, string templateName) + { + var resourceName = assembly + .GetManifestResourceNames() + .FirstOrDefault(n => n.EndsWith(templateName)); - using var stream = Assembly.GetExecutingAssembly() - .GetManifestResourceStream(resourceName)!; + if (string.IsNullOrWhiteSpace(resourceName)) + throw new ArgumentNullException(nameof(resourceName)); - var memoryStream = new MemoryStream(); - await stream.CopyToAsync(memoryStream, cancellationToken); - memoryStream.Position = 0; + using var stream = Assembly.GetExecutingAssembly() + .GetManifestResourceStream(resourceName); - return memoryStream; - } - } -} + var memoryStream = new MemoryStream(); + stream?.CopyTo(memoryStream); + memoryStream.Position = 0; + + return memoryStream; + } + + [Obsolete] + public static async Task GetTemplateCopyStreamAsync(this Assembly assembly, + string templateName, + CancellationToken cancellationToken) + { + var resourceName = assembly + .GetManifestResourceNames() + .FirstOrDefault(n => n.EndsWith(templateName))!; + + using var stream = Assembly.GetExecutingAssembly() + .GetManifestResourceStream(resourceName)!; + + var memoryStream = new MemoryStream(); + await stream.CopyToAsync(memoryStream, cancellationToken); + memoryStream.Position = 0; + + return memoryStream; + } + } +} \ No newline at end of file From e361ccf4c1cbdcd58687e42aab05c1c9f98e3336 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=82=D0=B5=D0=BF=D0=B0=D0=BD=D0=BE=D0=B2=20=D0=94?= =?UTF-8?q?=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Thu, 1 Feb 2024 16:35:29 +0500 Subject: [PATCH 10/12] =?UTF-8?q?=D0=A0=D0=B5=D1=84=D0=B0=D0=BA=D1=82?= =?UTF-8?q?=D0=BE=D1=80=D0=B8=D0=BD=D0=B3=20=D1=81=D0=B5=D1=80=D0=B2=D0=B8?= =?UTF-8?q?=D1=81=D0=BE=D0=B2=20=D0=BF=D0=B0=D1=80=D1=81=D0=B8=D0=BD=D0=B3?= =?UTF-8?q?=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ParserOptions/IParserOptionsRequest.cs | 13 +++++++- ...erviceWithOptions.cs => IParserService.cs} | 21 ++++++++++-- AsbCloudApp/Services/Parser/IParserService.cs | 19 ----------- ...rserExtensions.cs => ParserServiceBase.cs} | 25 +++++++++++---- .../Services/ParserServiceFactory.cs | 27 +++++++--------- .../TrajectoryFactManualParserService.cs | 13 ++++++-- .../Parser/TrajectoryParserService.cs | 23 ++++++++++--- .../Parser/TrajectoryPlanParserService.cs | 13 ++++++-- AsbCloudInfrastructure/XLExtentions.cs | 2 +- .../Trajectory/TrajectoryParserTest.cs | 32 +++++++++++++++---- 10 files changed, 127 insertions(+), 61 deletions(-) rename AsbCloudApp/Services/{Parser/IParserServiceWithOptions.cs => IParserService.cs} (50%) delete mode 100644 AsbCloudApp/Services/Parser/IParserService.cs rename AsbCloudInfrastructure/{XLParserExtensions.cs => ParserServiceBase.cs} (69%) diff --git a/AsbCloudApp/Requests/ParserOptions/IParserOptionsRequest.cs b/AsbCloudApp/Requests/ParserOptions/IParserOptionsRequest.cs index dd8f8e4f..b3b12f51 100644 --- a/AsbCloudApp/Requests/ParserOptions/IParserOptionsRequest.cs +++ b/AsbCloudApp/Requests/ParserOptions/IParserOptionsRequest.cs @@ -1,8 +1,19 @@ namespace AsbCloudApp.Requests.ParserOptions; /// -/// Интерфейс для параметров парсера +/// Параметры парсинга /// public interface IParserOptionsRequest { + private static DummyOptions empty => new(); + + private class DummyOptions : IParserOptionsRequest + { + } + + /// + /// Получение пустого объекта опций + /// + /// + public static IParserOptionsRequest Empty() => empty; } \ No newline at end of file diff --git a/AsbCloudApp/Services/Parser/IParserServiceWithOptions.cs b/AsbCloudApp/Services/IParserService.cs similarity index 50% rename from AsbCloudApp/Services/Parser/IParserServiceWithOptions.cs rename to AsbCloudApp/Services/IParserService.cs index 83e54a27..2cfd8eb7 100644 --- a/AsbCloudApp/Services/Parser/IParserServiceWithOptions.cs +++ b/AsbCloudApp/Services/IParserService.cs @@ -1,15 +1,17 @@ using System.IO; +using System.Threading; +using System.Threading.Tasks; using AsbCloudApp.Data; using AsbCloudApp.Requests.ParserOptions; -namespace AsbCloudApp.Services.Parser; +namespace AsbCloudApp.Services; /// -/// Сервис парсинга файлов с доп. параметрами +/// Сервис парсинга /// /// /// -public interface IParserServiceWithOptions +public interface IParserService : IParserService where TDto : class, IId where TOptions : IParserOptionsRequest { @@ -20,4 +22,17 @@ public interface IParserServiceWithOptions /// /// ParserResultDto Parse(Stream file, TOptions options); + + /// + /// Получение шаблона для заполнения + /// + /// + Stream GetTemplateFile(); +} + +/// +/// Сервис парсинга(интерфейс маркер) +/// +public interface IParserService +{ } \ No newline at end of file diff --git a/AsbCloudApp/Services/Parser/IParserService.cs b/AsbCloudApp/Services/Parser/IParserService.cs deleted file mode 100644 index 628859bb..00000000 --- a/AsbCloudApp/Services/Parser/IParserService.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.IO; -using AsbCloudApp.Data; - -namespace AsbCloudApp.Services.Parser; - -/// -/// Сервис парсинга файлов -/// -/// -public interface IParserService - where TDto : class, IId -{ - /// - /// Распарсить файл - /// - /// - /// - ParserResultDto Parse(Stream file); -} \ No newline at end of file diff --git a/AsbCloudInfrastructure/XLParserExtensions.cs b/AsbCloudInfrastructure/ParserServiceBase.cs similarity index 69% rename from AsbCloudInfrastructure/XLParserExtensions.cs rename to AsbCloudInfrastructure/ParserServiceBase.cs index 28ecb089..d98404e7 100644 --- a/AsbCloudInfrastructure/XLParserExtensions.cs +++ b/AsbCloudInfrastructure/ParserServiceBase.cs @@ -4,17 +4,30 @@ using System.ComponentModel.DataAnnotations; using System.IO; using System.Linq; using AsbCloudApp.Data; +using AsbCloudApp.Requests.ParserOptions; +using AsbCloudApp.Services; using ClosedXML.Excel; namespace AsbCloudInfrastructure; -public static class XLParserExtensions +public abstract class ParserServiceBase : IParserService + where TDto : class, IId + where TOptions : IParserOptionsRequest { - public static ParserResultDto Parse(this IXLWorksheet sheet, + protected readonly IServiceProvider serviceProvider; + + protected ParserServiceBase(IServiceProvider serviceProvider) + { + this.serviceProvider = serviceProvider; + } + + public abstract ParserResultDto Parse(Stream file, TOptions options); + public abstract Stream GetTemplateFile(); + + protected virtual ParserResultDto ParseExcelSheet(IXLWorksheet sheet, Func> parseRow, int columnCount, int headerRowsCount = 0) - where TDto : class, IId { if (sheet.RangeUsed().RangeAddress.LastAddress.ColumnNumber < columnCount) throw new FileFormatException($"Лист {sheet.Name} содержит меньшее количество столбцов."); @@ -26,10 +39,10 @@ public static class XLParserExtensions if (count <= 0) return new ParserResultDto(); - + var dtos = new List>(count); var warnings = new List(); - + for (var i = 0; i < count; i++) { var row = sheet.Row(1 + i + headerRowsCount); @@ -45,7 +58,7 @@ public static class XLParserExtensions warnings.Add(warning); } } - + var parserResult = new ParserResultDto { Item = dtos diff --git a/AsbCloudInfrastructure/Services/ParserServiceFactory.cs b/AsbCloudInfrastructure/Services/ParserServiceFactory.cs index 7aeceb98..5b852a8e 100644 --- a/AsbCloudInfrastructure/Services/ParserServiceFactory.cs +++ b/AsbCloudInfrastructure/Services/ParserServiceFactory.cs @@ -2,7 +2,7 @@ using System; using System.Collections.Generic; using AsbCloudApp.Data; using AsbCloudApp.Requests.ParserOptions; -using AsbCloudApp.Services.Parser; +using AsbCloudApp.Services; using AsbCloudInfrastructure.Services.Trajectory.Parser; namespace AsbCloudInfrastructure.Services; @@ -12,30 +12,25 @@ public class ParserServiceFactory public const int IdTrajectoryFactManualParserService = 1; public const int IdTrajectoryPlanParserService = 2; - private readonly IDictionary> parsers = new Dictionary> - { - { IdTrajectoryPlanParserService, () => new TrajectoryPlanParserService() }, - { IdTrajectoryFactManualParserService, () => new TrajectoryFactManualParserService() } - }; + private readonly IDictionary> parsers; - public IParserService GetParser(int idParserService) - where TDto : class, IId + public ParserServiceFactory(IServiceProvider serviceProvider) { - if (!parsers.TryGetValue(idParserService, out var parserService)) - throw new ArgumentNullException(nameof(idParserService), "Сервис не зарегистрирован"); - - return parserService.Invoke() as IParserService - ?? throw new ArgumentNullException(nameof(idParserService), "Ошибка приведения типа"); + parsers = new Dictionary> + { + { IdTrajectoryPlanParserService, () => new TrajectoryPlanParserService(serviceProvider) }, + { IdTrajectoryFactManualParserService, () => new TrajectoryFactManualParserService(serviceProvider) } + }; } - public IParserServiceWithOptions GetParserWithOptions(int idParserService) + public IParserService Create(int idParserService) where TDto : class, IId where TOptions : IParserOptionsRequest { if (!parsers.TryGetValue(idParserService, out var parserService)) - throw new ArgumentNullException(nameof(idParserService), "Сервис не зарегистрирован"); + throw new ArgumentNullException(nameof(idParserService), "Не правильный идентификатор парсера"); - return parserService.Invoke() as IParserServiceWithOptions + return parserService.Invoke() as IParserService ?? throw new ArgumentNullException(nameof(idParserService), "Ошибка приведения типа"); } } \ No newline at end of file diff --git a/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryFactManualParserService.cs b/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryFactManualParserService.cs index d494c5b6..6e4034a5 100644 --- a/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryFactManualParserService.cs +++ b/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryFactManualParserService.cs @@ -1,4 +1,5 @@ -using AsbCloudApp.Data; +using System; +using AsbCloudApp.Data; using AsbCloudApp.Data.Trajectory; using ClosedXML.Excel; @@ -7,7 +8,13 @@ namespace AsbCloudInfrastructure.Services.Trajectory.Parser; public class TrajectoryFactManualParserService : TrajectoryParserService { protected override string SheetName => "Фактическая траектория"; + protected override string TemplateFileName => "TrajectoryFactManualTemplate.xlsx"; + public TrajectoryFactManualParserService(IServiceProvider serviceProvider) + : base(serviceProvider) + { + } + protected override ValidationResultDto ParseRow(IXLRow row) { var trajectoryRow = new TrajectoryGeoFactDto @@ -22,9 +29,11 @@ public class TrajectoryFactManualParserService : TrajectoryParserService + var validationResult = new ValidationResultDto { Item = trajectoryRow }; + + return validationResult; } } \ No newline at end of file diff --git a/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryParserService.cs b/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryParserService.cs index 16dbf55d..d0afe513 100644 --- a/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryParserService.cs +++ b/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryParserService.cs @@ -1,23 +1,36 @@ -using AsbCloudApp.Data.Trajectory; +using System; +using AsbCloudApp.Data.Trajectory; using ClosedXML.Excel; using System.IO; using System.Linq; +using System.Reflection; using AsbCloudApp.Data; -using AsbCloudApp.Services.Parser; +using AsbCloudApp.Requests.ParserOptions; namespace AsbCloudInfrastructure.Services.Trajectory.Parser; -public abstract class TrajectoryParserService : IParserService +public abstract class TrajectoryParserService : ParserServiceBase where T : TrajectoryGeoDto { private const int HeaderRowsCount = 2; private const int ColumnCount = 6; + protected TrajectoryParserService(IServiceProvider serviceProvider) + : base(serviceProvider) + { + } + protected abstract string SheetName { get; } + protected abstract string TemplateFileName { get; } + protected abstract ValidationResultDto ParseRow(IXLRow row); - public ParserResultDto Parse(Stream file) + public override Stream GetTemplateFile() => + Assembly.GetExecutingAssembly().GetTemplateCopyStream(TemplateFileName) + ?? throw new ArgumentNullException($"Файл '{TemplateFileName}' не найден"); + + public override ParserResultDto Parse(Stream file, IParserOptionsRequest options) { using var workbook = new XLWorkbook(file, XLEventTracking.Disabled); @@ -25,7 +38,7 @@ public abstract class TrajectoryParserService : IParserService ws.Name.ToLower().Trim() == SheetName.ToLower().Trim()) ?? throw new FileFormatException($"Книга excel не содержит листа {SheetName}."); - var trajectoryRows = sheet.Parse(ParseRow, ColumnCount, HeaderRowsCount); + var trajectoryRows = ParseExcelSheet(sheet, ParseRow, ColumnCount, HeaderRowsCount); return trajectoryRows; } } \ No newline at end of file diff --git a/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryPlanParserService.cs b/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryPlanParserService.cs index 97d7ca29..fbf5a537 100644 --- a/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryPlanParserService.cs +++ b/AsbCloudInfrastructure/Services/Trajectory/Parser/TrajectoryPlanParserService.cs @@ -1,4 +1,5 @@ -using AsbCloudApp.Data; +using System; +using AsbCloudApp.Data; using AsbCloudApp.Data.Trajectory; using ClosedXML.Excel; @@ -7,7 +8,13 @@ namespace AsbCloudInfrastructure.Services.Trajectory.Parser; public class TrajectoryPlanParserService : TrajectoryParserService { protected override string SheetName => "Плановая траектория"; + protected override string TemplateFileName => "TrajectoryPlanTemplate.xlsx"; + public TrajectoryPlanParserService(IServiceProvider serviceProvider) + : base(serviceProvider) + { + } + protected override ValidationResultDto ParseRow(IXLRow row) { var trajectoryRow = new TrajectoryGeoPlanDto @@ -23,9 +30,11 @@ public class TrajectoryPlanParserService : TrajectoryParserService + var validationResult = new ValidationResultDto { Item = trajectoryRow }; + + return validationResult; } } \ No newline at end of file diff --git a/AsbCloudInfrastructure/XLExtentions.cs b/AsbCloudInfrastructure/XLExtentions.cs index 401e2423..e633a875 100644 --- a/AsbCloudInfrastructure/XLExtentions.cs +++ b/AsbCloudInfrastructure/XLExtentions.cs @@ -90,7 +90,7 @@ internal static class XLExtentions catch { throw new FileFormatException( - $"Лист '{cell.Worksheet.Name}'. Ячейка: ({cell.Address.RowNumber},{cell.Address.ColumnNumber}) содержит некорректное значение"); + $"Лист '{cell.Worksheet.Name}'. {cell.Address.RowNumber} строка содержит некорректное значение в {cell.Address.ColumnNumber} столбце"); } } } \ No newline at end of file diff --git a/AsbCloudWebApi.Tests/Services/Trajectory/TrajectoryParserTest.cs b/AsbCloudWebApi.Tests/Services/Trajectory/TrajectoryParserTest.cs index b5c722b4..1fddd56d 100644 --- a/AsbCloudWebApi.Tests/Services/Trajectory/TrajectoryParserTest.cs +++ b/AsbCloudWebApi.Tests/Services/Trajectory/TrajectoryParserTest.cs @@ -1,6 +1,10 @@ -using System.Linq; +using System; +using System.Linq; using AsbCloudApp.Data.Trajectory; +using AsbCloudApp.Requests.ParserOptions; using AsbCloudInfrastructure.Services; +using Microsoft.Extensions.DependencyInjection; +using NSubstitute; using Xunit; namespace AsbCloudWebApi.Tests.Services.Trajectory; @@ -8,8 +12,20 @@ namespace AsbCloudWebApi.Tests.Services.Trajectory; public class TrajectoryParserTest { private const string UsingTemplateFile = "AsbCloudWebApi.Tests.Services.Trajectory.Templates"; + + private readonly IServiceProvider serviceProviderMock = Substitute.For(); + private readonly IServiceScope serviceScopeMock = Substitute.For(); + private readonly IServiceScopeFactory serviceScopeFactoryMock = Substitute.For(); - private static readonly ParserServiceFactory parserServiceFactory = new(); + private readonly ParserServiceFactory parserServiceFactory; + + public TrajectoryParserTest() + { + serviceScopeFactoryMock.CreateScope().Returns(serviceScopeMock); + ((ISupportRequiredService)serviceProviderMock).GetRequiredService(typeof(IServiceScopeFactory)).Returns(serviceScopeFactoryMock); + + parserServiceFactory = new ParserServiceFactory(serviceProviderMock); + } [Fact] public void Parse_trajectory_plan() @@ -20,8 +36,10 @@ public class TrajectoryParserTest if (stream is null) Assert.Fail("Файла для импорта не существует"); - var parserService = parserServiceFactory.GetParser(ParserServiceFactory.IdTrajectoryPlanParserService); - var trajectoryRows = parserService.Parse(stream); + var parserService = parserServiceFactory.Create( + ParserServiceFactory.IdTrajectoryPlanParserService); + + var trajectoryRows = parserService.Parse(stream, IParserOptionsRequest.Empty()); Assert.Equal(3, trajectoryRows.Item.Count()); } @@ -35,8 +53,10 @@ public class TrajectoryParserTest if (stream is null) Assert.Fail("Файла для импорта не существует"); - var parserService = parserServiceFactory.GetParser(ParserServiceFactory.IdTrajectoryFactManualParserService); - var trajectoryRows = parserService.Parse(stream); + var parserService = parserServiceFactory.Create( + ParserServiceFactory.IdTrajectoryFactManualParserService); + + var trajectoryRows = parserService.Parse(stream, IParserOptionsRequest.Empty()); Assert.Equal(4, trajectoryRows.Item.Count()); } From dc19829948531a2d4ceb1c44f5a900866b187982 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=82=D0=B5=D0=BF=D0=B0=D0=BD=D0=BE=D0=B2=20=D0=94?= =?UTF-8?q?=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Thu, 1 Feb 2024 16:37:03 +0500 Subject: [PATCH 11/12] =?UTF-8?q?=D0=A0=D0=B5=D1=84=D0=B0=D0=BA=D1=82?= =?UTF-8?q?=D0=BE=D1=80=D0=B8=D0=BD=D0=B3=20=D0=BA=D0=BE=D0=BD=D1=82=D1=80?= =?UTF-8?q?=D0=BE=D0=BB=D0=BB=D0=B5=D1=80=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Interfaces/IControllerWithParser.cs | 13 +++ .../Controllers/Interfaces/IHasExcelParser.cs | 11 --- .../Interfaces/IHasExcelParserWithOptions.cs | 10 -- .../TrajectoryEditableController.cs | 98 ++++++++++--------- .../TrajectoryFactManualController.cs | 8 +- .../Trajectory/TrajectoryPlanController.cs | 8 +- AsbCloudWebApi/Extentions.cs | 14 +-- 7 files changed, 76 insertions(+), 86 deletions(-) create mode 100644 AsbCloudWebApi/Controllers/Interfaces/IControllerWithParser.cs delete mode 100644 AsbCloudWebApi/Controllers/Interfaces/IHasExcelParser.cs delete mode 100644 AsbCloudWebApi/Controllers/Interfaces/IHasExcelParserWithOptions.cs diff --git a/AsbCloudWebApi/Controllers/Interfaces/IControllerWithParser.cs b/AsbCloudWebApi/Controllers/Interfaces/IControllerWithParser.cs new file mode 100644 index 00000000..d11ac201 --- /dev/null +++ b/AsbCloudWebApi/Controllers/Interfaces/IControllerWithParser.cs @@ -0,0 +1,13 @@ +using System.IO; +using AsbCloudApp.Data; +using Microsoft.AspNetCore.Mvc; + +namespace AsbCloudWebApi.Controllers.Interfaces; + +public interface IControllerWithParser + where TDto : class, IId +{ + ActionResult> Parse(Stream file, TOptions options); + + IActionResult GetTemplate(); +} \ No newline at end of file diff --git a/AsbCloudWebApi/Controllers/Interfaces/IHasExcelParser.cs b/AsbCloudWebApi/Controllers/Interfaces/IHasExcelParser.cs deleted file mode 100644 index f78d17da..00000000 --- a/AsbCloudWebApi/Controllers/Interfaces/IHasExcelParser.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.IO; -using AsbCloudApp.Data; -using Microsoft.AspNetCore.Mvc; - -namespace AsbCloudWebApi.Controllers.Interfaces; - -public interface IHasExcelParser - where TDto : class, IId -{ - ActionResult> Parse(Stream file); -} \ No newline at end of file diff --git a/AsbCloudWebApi/Controllers/Interfaces/IHasExcelParserWithOptions.cs b/AsbCloudWebApi/Controllers/Interfaces/IHasExcelParserWithOptions.cs deleted file mode 100644 index 96e95eef..00000000 --- a/AsbCloudWebApi/Controllers/Interfaces/IHasExcelParserWithOptions.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.IO; -using AsbCloudApp.Data; - -namespace AsbCloudWebApi.Controllers.Interfaces; - -public interface IHasExcelParserWithOptions - where TDto : class, IId -{ - ParserResultDto Parse(Stream file, TOptions options); -} \ No newline at end of file diff --git a/AsbCloudWebApi/Controllers/Trajectory/TrajectoryEditableController.cs b/AsbCloudWebApi/Controllers/Trajectory/TrajectoryEditableController.cs index 19f834e0..7a04e7fc 100644 --- a/AsbCloudWebApi/Controllers/Trajectory/TrajectoryEditableController.cs +++ b/AsbCloudWebApi/Controllers/Trajectory/TrajectoryEditableController.cs @@ -10,6 +10,7 @@ using System.IO; using System.Threading; using System.Threading.Tasks; using AsbCloudApp.Data; +using AsbCloudApp.Requests.ParserOptions; using AsbCloudInfrastructure.Services; using AsbCloudWebApi.Controllers.Interfaces; @@ -22,35 +23,33 @@ namespace AsbCloudWebApi.Controllers.Trajectory [ApiController] [Authorize] public abstract class TrajectoryEditableController : TrajectoryController, - IHasExcelParser + IControllerWithParser where TDto : TrajectoryGeoDto { - private readonly ParserServiceFactory parserServiceFactory; - private readonly TrajectoryExportService trajectoryExportService; - private readonly ITrajectoryEditableRepository trajectoryRepository; + private readonly IParserService parserService; + private readonly ITrajectoryEditableRepository trajectoryRepository; protected TrajectoryEditableController(IWellService wellService, ParserServiceFactory parserServiceFactory, TrajectoryExportService trajectoryExportService, - ITrajectoryEditableRepository trajectoryRepository) + ITrajectoryEditableRepository trajectoryRepository, + int idParserService) : base( wellService, trajectoryExportService, trajectoryRepository) { - this.trajectoryExportService = trajectoryExportService; - this.trajectoryRepository = trajectoryRepository; - this.parserServiceFactory = parserServiceFactory; + parserService = parserServiceFactory.Create(idParserService); + this.trajectoryRepository = trajectoryRepository; } - protected abstract int IdParserService { get; } - - ActionResult> IHasExcelParser.Parse(Stream file) + ActionResult> IControllerWithParser.Parse(Stream file, + IParserOptionsRequest options) { try { - var parserService = parserServiceFactory.GetParser(IdParserService); - return Ok(parserService.Parse(file)); + var parserResult = parserService.Parse(file, options); + return Ok(parserResult); } catch (FileFormatException ex) { @@ -68,7 +67,7 @@ namespace AsbCloudWebApi.Controllers.Trajectory [ProducesResponseType(StatusCodes.Status204NoContent)] public IActionResult GetTemplate() { - var stream = trajectoryExportService.GetTemplateFile(); + var stream = parserService.GetTemplateFile(); return File(stream, "application/octet-stream", fileName); } @@ -94,46 +93,19 @@ namespace AsbCloudWebApi.Controllers.Trajectory if (!await CanUserAccessToWellAsync(idWell, token)) return Forbid(); - return this.ParseExcelFile(files); + return this.ParseExcelFile(files, IParserOptionsRequest.Empty()); } - /// - /// Добавить одну новую строчку координат для плановой траектории - /// - /// - /// - /// - /// количество успешно записанных строк в БД - [HttpPost] - [ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)] - public async Task AddAsync(int idWell, [FromBody] TDto row, - CancellationToken token) - { - if (!await CanUserAccessToWellAsync(idWell, token).ConfigureAwait(false)) - return Forbid(); - var idUser = User.GetUserId(); - if (!idUser.HasValue) - return Forbid(); - row.IdUser = idUser.Value; - row.IdWell = idWell; - var result = await trajectoryRepository.AddAsync(row, token); - return Ok(result); - } - /// - /// Добавить массив строчек координат для плановой траектории + /// Добавление /// /// /// - /// /// - /// количество успешно записанных строк в БД - [HttpPost("range/{deleteBeforeInsert:bool}")] + /// + [HttpPost] [ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)] - public async Task AddRangeAsync(int idWell, - [FromBody] IEnumerable dtos, - bool deleteBeforeInsert, - CancellationToken token) + public async Task InsertRangeAsync(int idWell, [FromBody] IEnumerable dtos, CancellationToken token) { if (!await CanUserAccessToWellAsync(idWell, token)) return Forbid(); @@ -149,9 +121,39 @@ namespace AsbCloudWebApi.Controllers.Trajectory dto.IdWell = idWell; } - if (deleteBeforeInsert) - await trajectoryRepository.DeleteByIdWellAsync(idWell, token); + var result = await trajectoryRepository.AddRangeAsync(dtos, token); + return Ok(result); + } + /// + /// Удалить все по скважине и добавить новые + /// + /// + /// + /// + /// + [HttpPost("replace")] + [ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)] + public async Task ClearAndInsertRangeAsync(int idWell, [FromBody] IEnumerable dtos, CancellationToken token) + { + //TODO: это вся радость требует рефакторинга. + //Удаление с добавлением новых записей должно происходить в рамках одной транзакции, да и вообще должно быть реализовано на уровне репозиторий. + //Рефакторинг будет когда доберёмся до журнала изменений для траекторий. + if (!await CanUserAccessToWellAsync(idWell, token)) + return Forbid(); + + var idUser = User.GetUserId(); + + if (!idUser.HasValue) + return Forbid(); + + foreach (var dto in dtos) + { + dto.IdUser = idUser.Value; + dto.IdWell = idWell; + } + + await trajectoryRepository.DeleteByIdWellAsync(idWell, token); var result = await trajectoryRepository.AddRangeAsync(dtos, token); return Ok(result); } diff --git a/AsbCloudWebApi/Controllers/Trajectory/TrajectoryFactManualController.cs b/AsbCloudWebApi/Controllers/Trajectory/TrajectoryFactManualController.cs index 081704ff..97b84b07 100644 --- a/AsbCloudWebApi/Controllers/Trajectory/TrajectoryFactManualController.cs +++ b/AsbCloudWebApi/Controllers/Trajectory/TrajectoryFactManualController.cs @@ -20,9 +20,11 @@ public class TrajectoryFactManualController : TrajectoryEditableController trajectoryRepository) - : base(wellService, parserServiceFactory, trajectoryExportService, trajectoryRepository) + : base(wellService, + parserServiceFactory, + trajectoryExportService, + trajectoryRepository, + ParserServiceFactory.IdTrajectoryFactManualParserService) { } - - protected override int IdParserService => ParserServiceFactory.IdTrajectoryFactManualParserService; } \ No newline at end of file diff --git a/AsbCloudWebApi/Controllers/Trajectory/TrajectoryPlanController.cs b/AsbCloudWebApi/Controllers/Trajectory/TrajectoryPlanController.cs index 2d525c16..cf3f648d 100644 --- a/AsbCloudWebApi/Controllers/Trajectory/TrajectoryPlanController.cs +++ b/AsbCloudWebApi/Controllers/Trajectory/TrajectoryPlanController.cs @@ -27,13 +27,15 @@ namespace AsbCloudWebApi.Controllers.Trajectory ParserServiceFactory parserServiceFactory, ITrajectoryEditableRepository trajectoryRepository, TrajectoryService trajectoryVisualizationService) - : base(wellService, parserServiceFactory, trajectoryExportService, trajectoryRepository) + : base(wellService, + parserServiceFactory, + trajectoryExportService, + trajectoryRepository, + ParserServiceFactory.IdTrajectoryPlanParserService) { this.trajectoryVisualizationService = trajectoryVisualizationService; } - protected override int IdParserService => ParserServiceFactory.IdTrajectoryPlanParserService; - /// /// Получение координат для визуализации траектории (плановой и фактической) /// diff --git a/AsbCloudWebApi/Extentions.cs b/AsbCloudWebApi/Extentions.cs index ab111444..d6ce96a4 100644 --- a/AsbCloudWebApi/Extentions.cs +++ b/AsbCloudWebApi/Extentions.cs @@ -87,17 +87,9 @@ namespace Microsoft.AspNetCore.Mvc TypeDescriptor.AddAttributes(typeof(DateOnly), new TypeConverterAttribute(typeof(DateOnlyTypeConverter))); return options; } - - public static ActionResult> ParseExcelFile(this IHasExcelParser controller, - IFormFileCollection files) - where TDto : class, IId - { - using var file = GetExcelFile(files); - return controller.Parse(file); - } - - public static ActionResult> ParseExcelFileWithOptions( - this IHasExcelParserWithOptions controller, + + public static ActionResult> ParseExcelFile( + this IControllerWithParser controller, IFormFileCollection files, TOptions options) where TDto : class, IId From 51bce976ded30fc88ef66bd7606be51b0808fd02 Mon Sep 17 00:00:00 2001 From: ngfrolov Date: Fri, 2 Feb 2024 11:13:48 +0500 Subject: [PATCH 12/12] refactor Extensions.ParseExcelFile() --- AsbCloudWebApi/Extensions.cs | 125 +++++++++++++++++++++++++++++++++++ AsbCloudWebApi/Extentions.cs | 116 -------------------------------- 2 files changed, 125 insertions(+), 116 deletions(-) create mode 100644 AsbCloudWebApi/Extensions.cs delete mode 100644 AsbCloudWebApi/Extentions.cs diff --git a/AsbCloudWebApi/Extensions.cs b/AsbCloudWebApi/Extensions.cs new file mode 100644 index 00000000..489d6149 --- /dev/null +++ b/AsbCloudWebApi/Extensions.cs @@ -0,0 +1,125 @@ +using AsbCloudApp.Data.User; +using AsbCloudWebApi.Converters; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Security.Claims; +using AsbCloudApp.Data; +using AsbCloudApp.Requests.ParserOptions; +using AsbCloudWebApi.Controllers.Interfaces; +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.Mvc; + +public static class Extensions +{ + public static int? GetCompanyId(this ClaimsPrincipal user) + { + var claimIdCompany = user.FindFirst(nameof(UserDto.IdCompany)); + if (claimIdCompany is null) + return null; + + return int.TryParse(claimIdCompany.Value, out int uid) + ? uid + : null; + } + + public static int? GetUserId(this ClaimsPrincipal user) + { + var userId = user.FindFirst(nameof(UserDto.Id)); + if (userId is null) + return null; + + return int.TryParse(userId.Value, out int uid) + ? uid + : null; + } + + /// + /// + /// Returns BadRequest with ValidationProblemDetails as body + /// + /// + /// Используйте этот метод только если валидацию нельзя сделать через + /// атрибуты валидации или IValidatableObject модели. + /// + /// + /// + /// + /// + /// + public static BadRequestObjectResult ValidationBadRequest(this ControllerBase controller, string paramName, string error) + { + return MakeBadRequestObjectResult(paramName, error); + } + + private static BadRequestObjectResult MakeBadRequestObjectResult(string paramName, string error) + { + var errors = new Dictionary { + { paramName, new[]{ error } } + }; + var problem = new ValidationProblemDetails(errors); + var badRequestObject = new BadRequestObjectResult(problem); + return badRequestObject; + } + + /// + /// + /// Returns BadRequest with ValidationProblemDetails as body + /// + /// + /// Используйте этот метод только если валидацию нельзя сделать через + /// атрибуты валидации или IValidatableObject модели. + /// + /// + /// + /// + /// + public static BadRequestObjectResult ValidationBadRequest(this ControllerBase controller, IEnumerable validationResults) + { + var errors = validationResults + .SelectMany(e => e.MemberNames.Select(name => new { name, e.ErrorMessage })) + .GroupBy(e => e.name) + .ToDictionary(e => e.Key, e => e.Select(el => el.ErrorMessage ?? string.Empty).ToArray()); + + var problem = new ValidationProblemDetails(errors); + return controller.BadRequest(problem); + } + + public static MvcOptions UseDateOnlyTimeOnlyStringConverters(this MvcOptions options) + { + TypeDescriptor.AddAttributes(typeof(DateOnly), new TypeConverterAttribute(typeof(DateOnlyTypeConverter))); + return options; + } + + /// + /// Вызов парсера со стандартной валидацией входного файла + /// + /// + /// + /// + /// + /// + /// + public static ActionResult> ParseExcelFile( + this IControllerWithParser controller, + IFormFileCollection files, + TOptions options) + where TDto : class, IId + where TOptions : class, IParserOptionsRequest + { + if (files.Count < 1) + return MakeBadRequestObjectResult(nameof(files), "Нет файла"); + + var file = files[0]; + if (Path.GetExtension(file.FileName).ToLower() != ".xlsx") + return MakeBadRequestObjectResult(nameof(files), "Требуется .xlsx файл."); + + var stream = file.OpenReadStream(); + + return controller.Parse(stream, options); + } +} \ No newline at end of file diff --git a/AsbCloudWebApi/Extentions.cs b/AsbCloudWebApi/Extentions.cs deleted file mode 100644 index 5e46eef6..00000000 --- a/AsbCloudWebApi/Extentions.cs +++ /dev/null @@ -1,116 +0,0 @@ -using AsbCloudApp.Data.User; -using AsbCloudWebApi.Converters; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.IO; -using System.ComponentModel.DataAnnotations; -using System.Linq; -using System.Security.Claims; -using AsbCloudApp.Data; -using AsbCloudApp.Exceptions; -using AsbCloudApp.Requests.ParserOptions; -using AsbCloudWebApi.Controllers.Interfaces; -using Microsoft.AspNetCore.Http; - -namespace Microsoft.AspNetCore.Mvc -{ - public static class Extentions - { - public static int? GetCompanyId(this ClaimsPrincipal user) - { - var claimIdCompany = user.FindFirst(nameof(UserDto.IdCompany)); - if (claimIdCompany is null) - return null; - - return int.TryParse(claimIdCompany.Value, out int uid) - ? uid - : null; - } - - public static int? GetUserId(this ClaimsPrincipal user) - { - var userId = user.FindFirst(nameof(UserDto.Id)); - if (userId is null) - return null; - - return int.TryParse(userId.Value, out int uid) - ? uid - : null; - } - - /// - /// - /// Returns BadRequest with ValidationProblemDetails as body - /// - /// - /// Используйте этот метод только если валидацию нельзя сделать через - /// атрибуты валидации или IValidatableObject модели. - /// - /// - /// - /// - /// - /// - public static BadRequestObjectResult ValidationBadRequest(this ControllerBase controller, string paramName, string error) - { - var errors = new Dictionary { - { paramName, new[]{ error } } - }; - var problem = new ValidationProblemDetails(errors); - return controller.BadRequest(problem); - } - - /// - /// - /// Returns BadRequest with ValidationProblemDetails as body - /// - /// - /// Используйте этот метод только если валидацию нельзя сделать через - /// атрибуты валидации или IValidatableObject модели. - /// - /// - /// - /// - /// - public static BadRequestObjectResult ValidationBadRequest(this ControllerBase controller, IEnumerable validationResults) - { - var errors = validationResults - .SelectMany(e => e.MemberNames.Select(name => new { name, e.ErrorMessage })) - .GroupBy(e => e.name) - .ToDictionary(e => e.Key, e => e.Select(el => el.ErrorMessage ?? string.Empty).ToArray()); - - var problem = new ValidationProblemDetails(errors); - return controller.BadRequest(problem); - } - - public static MvcOptions UseDateOnlyTimeOnlyStringConverters(this MvcOptions options) - { - TypeDescriptor.AddAttributes(typeof(DateOnly), new TypeConverterAttribute(typeof(DateOnlyTypeConverter))); - return options; - } - - public static ActionResult> ParseExcelFile( - this IControllerWithParser controller, - IFormFileCollection files, - TOptions options) - where TDto : class, IId - where TOptions : class, IParserOptionsRequest - { - using var file = GetExcelFile(files); - return controller.Parse(file, options); - } - - private static Stream GetExcelFile(IFormFileCollection files) - { - if (files.Count < 1) - throw new ArgumentInvalidException(nameof(files), "Нет файла"); - - var file = files[0]; - if (Path.GetExtension(file.FileName).ToLower() != ".xlsx") - throw new ArgumentInvalidException(nameof(files), "Требуется .xlsx файл."); - - return file.OpenReadStream(); - } - } -} \ No newline at end of file