Сервисы парсинга траекторий

1. Сделан рефакторинг сервисов парсинга траекторий
2. Добавлена фабрика создания парсеров
3. Рефакторинг тестов
This commit is contained in:
Степанов Дмитрий 2024-01-29 15:03:53 +05:00
parent a726602be8
commit 82650b1cfb
10 changed files with 228 additions and 190 deletions

View File

@ -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<IProcessMapPlanService<ProcessMapPlanWellReamDto>, ProcessMapPlanService<ProcessMapPlanWellReamDto>>();
services.AddTransient<IWellSectionPlanRepository, WellSectionPlanRepository>();
services.AddSingleton<ParserServiceFactory>();
return services;
}

View File

@ -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<int, Func<IParserService>> parsers = new Dictionary<int, Func<IParserService>>
{
{ IdTrajectoryPlanParserService, () => new TrajectoryPlanParserService() },
{ IdTrajectoryFactManualParserService, () => new TrajectoryFactManualParserService() }
};
public IParserService<TDto, TOptions> Create<TDto, TOptions>(int idImportService)
where TDto : class, IId
where TOptions : ParserOptionsRequestBase
{
var parser = parsers[idImportService].Invoke();
return parser as IParserService<TDto, TOptions>
?? throw new ArgumentNullException(nameof(idImportService), "Не удалось распознать файл");
}
}

View File

@ -1,30 +0,0 @@
using AsbCloudApp.Data.Trajectory;
using ClosedXML.Excel;
namespace AsbCloudInfrastructure.Services.Trajectory.Import
{
public class TrajectoryFactManualParserService : TrajectoryParserService<TrajectoryGeoFactDto>
{
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<double>(),
ZenithAngle = row.Cell(2).GetCellValue<double>(),
AzimuthGeo = row.Cell(3).GetCellValue<double>(),
AzimuthMagnetic = row.Cell(4).GetCellValue<double>(),
VerticalDepth = row.Cell(5).GetCellValue<double>(),
Comment = row.Cell(6).GetCellValue<string?>()
};
//TODO: Добавить валидацию модели IValidatableObject
return trajectoryRow;
}
}
}

View File

@ -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<T>
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<T> Import(Stream stream)
{
using var workbook = new XLWorkbook(stream, XLEventTracking.Disabled);
var trajectoryRows = ParseFileStream(stream);
return trajectoryRows;
}
private IEnumerable<T> ParseFileStream(Stream stream)
{
using var workbook = new XLWorkbook(stream, XLEventTracking.Disabled);
return ParseWorkbook(workbook);
}
private IEnumerable<T> 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<T> 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<T>(count);
var parseErrors = new List<string>();
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;
}
}
}

View File

@ -1,33 +0,0 @@
using AsbCloudApp.Data.Trajectory;
using ClosedXML.Excel;
namespace AsbCloudInfrastructure.Services.Trajectory.Import
{
public class TrajectoryPlanParserService : TrajectoryParserService<TrajectoryGeoPlanDto>
{
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<double>(),
ZenithAngle = row.Cell(2).GetCellValue<double>(),
AzimuthGeo = row.Cell(3).GetCellValue<double>(),
AzimuthMagnetic = row.Cell(4).GetCellValue<double>(),
VerticalDepth = row.Cell(5).GetCellValue<double>(),
Radius = row.Cell(6).GetCellValue<double>(),
Comment = row.Cell(7).GetCellValue<string?>()
};
//TODO: Добавить валидацию модели IValidatableObject
return trajectoryRow;
}
}
}

View File

@ -0,0 +1,28 @@
using AsbCloudApp.Data;
using AsbCloudApp.Data.Trajectory;
using ClosedXML.Excel;
namespace AsbCloudInfrastructure.Services.Trajectory.Parser;
public class TrajectoryFactManualParserService : TrajectoryParserService<TrajectoryGeoFactDto>
{
protected override ValidationResultDto<TrajectoryGeoFactDto> ParseRow(IXLRow row)
{
var trajectoryRow = new TrajectoryGeoFactDto
{
WellboreDepth = row.Cell(1).GetCellValue<double>(),
ZenithAngle = row.Cell(2).GetCellValue<double>(),
AzimuthGeo = row.Cell(3).GetCellValue<double>(),
AzimuthMagnetic = row.Cell(4).GetCellValue<double>(),
VerticalDepth = row.Cell(5).GetCellValue<double>(),
Comment = row.Cell(6).GetCellValue<string?>()
};
//TODO: Добавить валидацию модели
return new ValidationResultDto<TrajectoryGeoFactDto>
{
Item = trajectoryRow
};
}
}

View File

@ -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<T> : IParserService<T, TrajectoryParserRequest>
where T : TrajectoryGeoDto
{
protected abstract ValidationResultDto<T> ParseRow(IXLRow row);
public ParserResultDto<T> Parse(Stream file, TrajectoryParserRequest options)
{
using var workbook = new XLWorkbook(file, XLEventTracking.Disabled);
var trajectoryRows = ParseFileStream(file, options);
return trajectoryRows;
}
private ParserResultDto<T> ParseFileStream(Stream stream, TrajectoryParserRequest options)
{
using var workbook = new XLWorkbook(stream, XLEventTracking.Disabled);
return ParseWorkbook(workbook, options);
}
private ParserResultDto<T> 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<T> 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<ValidationResultDto<T>>(count);
var parseErrors = new List<string>();
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<T>
{
Item = trajectoryRows
};
}
}

View File

@ -0,0 +1,29 @@
using AsbCloudApp.Data;
using AsbCloudApp.Data.Trajectory;
using ClosedXML.Excel;
namespace AsbCloudInfrastructure.Services.Trajectory.Parser;
public class TrajectoryPlanParserService : TrajectoryParserService<TrajectoryGeoPlanDto>
{
protected override ValidationResultDto<TrajectoryGeoPlanDto> ParseRow(IXLRow row)
{
var trajectoryRow = new TrajectoryGeoPlanDto
{
WellboreDepth = row.Cell(1).GetCellValue<double>(),
ZenithAngle = row.Cell(2).GetCellValue<double>(),
AzimuthGeo = row.Cell(3).GetCellValue<double>(),
AzimuthMagnetic = row.Cell(4).GetCellValue<double>(),
VerticalDepth = row.Cell(5).GetCellValue<double>(),
Radius = row.Cell(6).GetCellValue<double>(),
Comment = row.Cell(7).GetCellValue<string?>()
};
//TODO: Добавить валидацию модели
return new ValidationResultDto<TrajectoryGeoPlanDto>
{
Item = trajectoryRow
};
}
}

View File

@ -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());
}
}
}

View File

@ -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<TrajectoryGeoPlanDto, TrajectoryParserRequest>(
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<TrajectoryGeoFactDto, TrajectoryParserRequest>(
factTrajectoryParserOptions.IdParserService);
var trajectoryRows = parserService.Parse(stream, factTrajectoryParserOptions);
Assert.Equal(4, trajectoryRows.Item.Count());
}
}