forked from ddrilling/AsbCloudServer
Рефакторинг сервисов парсинга
This commit is contained in:
parent
3865c330c2
commit
e361ccf4c1
@ -1,8 +1,19 @@
|
|||||||
namespace AsbCloudApp.Requests.ParserOptions;
|
namespace AsbCloudApp.Requests.ParserOptions;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Интерфейс для параметров парсера
|
/// Параметры парсинга
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IParserOptionsRequest
|
public interface IParserOptionsRequest
|
||||||
{
|
{
|
||||||
|
private static DummyOptions empty => new();
|
||||||
|
|
||||||
|
private class DummyOptions : IParserOptionsRequest
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получение пустого объекта опций
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static IParserOptionsRequest Empty() => empty;
|
||||||
}
|
}
|
@ -1,15 +1,17 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using AsbCloudApp.Data;
|
using AsbCloudApp.Data;
|
||||||
using AsbCloudApp.Requests.ParserOptions;
|
using AsbCloudApp.Requests.ParserOptions;
|
||||||
|
|
||||||
namespace AsbCloudApp.Services.Parser;
|
namespace AsbCloudApp.Services;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Сервис парсинга файлов с доп. параметрами
|
/// Сервис парсинга
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TDto"></typeparam>
|
/// <typeparam name="TDto"></typeparam>
|
||||||
/// <typeparam name="TOptions"></typeparam>
|
/// <typeparam name="TOptions"></typeparam>
|
||||||
public interface IParserServiceWithOptions<TDto, in TOptions>
|
public interface IParserService<TDto, in TOptions> : IParserService
|
||||||
where TDto : class, IId
|
where TDto : class, IId
|
||||||
where TOptions : IParserOptionsRequest
|
where TOptions : IParserOptionsRequest
|
||||||
{
|
{
|
||||||
@ -20,4 +22,17 @@ public interface IParserServiceWithOptions<TDto, in TOptions>
|
|||||||
/// <param name="options"></param>
|
/// <param name="options"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
ParserResultDto<TDto> Parse(Stream file, TOptions options);
|
ParserResultDto<TDto> Parse(Stream file, TOptions options);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получение шаблона для заполнения
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
Stream GetTemplateFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Сервис парсинга(интерфейс маркер)
|
||||||
|
/// </summary>
|
||||||
|
public interface IParserService
|
||||||
|
{
|
||||||
}
|
}
|
@ -1,19 +0,0 @@
|
|||||||
using System.IO;
|
|
||||||
using AsbCloudApp.Data;
|
|
||||||
|
|
||||||
namespace AsbCloudApp.Services.Parser;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Сервис парсинга файлов
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="TDto"></typeparam>
|
|
||||||
public interface IParserService<TDto>
|
|
||||||
where TDto : class, IId
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Распарсить файл
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="file"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
ParserResultDto<TDto> Parse(Stream file);
|
|
||||||
}
|
|
@ -4,17 +4,30 @@ using System.ComponentModel.DataAnnotations;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using AsbCloudApp.Data;
|
using AsbCloudApp.Data;
|
||||||
|
using AsbCloudApp.Requests.ParserOptions;
|
||||||
|
using AsbCloudApp.Services;
|
||||||
using ClosedXML.Excel;
|
using ClosedXML.Excel;
|
||||||
|
|
||||||
namespace AsbCloudInfrastructure;
|
namespace AsbCloudInfrastructure;
|
||||||
|
|
||||||
public static class XLParserExtensions
|
public abstract class ParserServiceBase<TDto, TOptions> : IParserService<TDto, TOptions>
|
||||||
|
where TDto : class, IId
|
||||||
|
where TOptions : IParserOptionsRequest
|
||||||
{
|
{
|
||||||
public static ParserResultDto<TDto> Parse<TDto>(this IXLWorksheet sheet,
|
protected readonly IServiceProvider serviceProvider;
|
||||||
|
|
||||||
|
protected ParserServiceBase(IServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
this.serviceProvider = serviceProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract ParserResultDto<TDto> Parse(Stream file, TOptions options);
|
||||||
|
public abstract Stream GetTemplateFile();
|
||||||
|
|
||||||
|
protected virtual ParserResultDto<TDto> ParseExcelSheet(IXLWorksheet sheet,
|
||||||
Func<IXLRow, ValidationResultDto<TDto>> parseRow,
|
Func<IXLRow, ValidationResultDto<TDto>> parseRow,
|
||||||
int columnCount,
|
int columnCount,
|
||||||
int headerRowsCount = 0)
|
int headerRowsCount = 0)
|
||||||
where TDto : class, IId
|
|
||||||
{
|
{
|
||||||
if (sheet.RangeUsed().RangeAddress.LastAddress.ColumnNumber < columnCount)
|
if (sheet.RangeUsed().RangeAddress.LastAddress.ColumnNumber < columnCount)
|
||||||
throw new FileFormatException($"Лист {sheet.Name} содержит меньшее количество столбцов.");
|
throw new FileFormatException($"Лист {sheet.Name} содержит меньшее количество столбцов.");
|
||||||
@ -26,10 +39,10 @@ public static class XLParserExtensions
|
|||||||
|
|
||||||
if (count <= 0)
|
if (count <= 0)
|
||||||
return new ParserResultDto<TDto>();
|
return new ParserResultDto<TDto>();
|
||||||
|
|
||||||
var dtos = new List<ValidationResultDto<TDto>>(count);
|
var dtos = new List<ValidationResultDto<TDto>>(count);
|
||||||
var warnings = new List<ValidationResult>();
|
var warnings = new List<ValidationResult>();
|
||||||
|
|
||||||
for (var i = 0; i < count; i++)
|
for (var i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
var row = sheet.Row(1 + i + headerRowsCount);
|
var row = sheet.Row(1 + i + headerRowsCount);
|
||||||
@ -45,7 +58,7 @@ public static class XLParserExtensions
|
|||||||
warnings.Add(warning);
|
warnings.Add(warning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var parserResult = new ParserResultDto<TDto>
|
var parserResult = new ParserResultDto<TDto>
|
||||||
{
|
{
|
||||||
Item = dtos
|
Item = dtos
|
@ -2,7 +2,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using AsbCloudApp.Data;
|
using AsbCloudApp.Data;
|
||||||
using AsbCloudApp.Requests.ParserOptions;
|
using AsbCloudApp.Requests.ParserOptions;
|
||||||
using AsbCloudApp.Services.Parser;
|
using AsbCloudApp.Services;
|
||||||
using AsbCloudInfrastructure.Services.Trajectory.Parser;
|
using AsbCloudInfrastructure.Services.Trajectory.Parser;
|
||||||
|
|
||||||
namespace AsbCloudInfrastructure.Services;
|
namespace AsbCloudInfrastructure.Services;
|
||||||
@ -12,30 +12,25 @@ public class ParserServiceFactory
|
|||||||
public const int IdTrajectoryFactManualParserService = 1;
|
public const int IdTrajectoryFactManualParserService = 1;
|
||||||
public const int IdTrajectoryPlanParserService = 2;
|
public const int IdTrajectoryPlanParserService = 2;
|
||||||
|
|
||||||
private readonly IDictionary<int, Func<object>> parsers = new Dictionary<int, Func<object>>
|
private readonly IDictionary<int, Func<IParserService>> parsers;
|
||||||
{
|
|
||||||
{ IdTrajectoryPlanParserService, () => new TrajectoryPlanParserService() },
|
|
||||||
{ IdTrajectoryFactManualParserService, () => new TrajectoryFactManualParserService() }
|
|
||||||
};
|
|
||||||
|
|
||||||
public IParserService<TDto> GetParser<TDto>(int idParserService)
|
public ParserServiceFactory(IServiceProvider serviceProvider)
|
||||||
where TDto : class, IId
|
|
||||||
{
|
{
|
||||||
if (!parsers.TryGetValue(idParserService, out var parserService))
|
parsers = new Dictionary<int, Func<IParserService>>
|
||||||
throw new ArgumentNullException(nameof(idParserService), "Сервис не зарегистрирован");
|
{
|
||||||
|
{ IdTrajectoryPlanParserService, () => new TrajectoryPlanParserService(serviceProvider) },
|
||||||
return parserService.Invoke() as IParserService<TDto>
|
{ IdTrajectoryFactManualParserService, () => new TrajectoryFactManualParserService(serviceProvider) }
|
||||||
?? throw new ArgumentNullException(nameof(idParserService), "Ошибка приведения типа");
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public IParserServiceWithOptions<TDto, TOptions> GetParserWithOptions<TDto, TOptions>(int idParserService)
|
public IParserService<TDto, TOptions> Create<TDto, TOptions>(int idParserService)
|
||||||
where TDto : class, IId
|
where TDto : class, IId
|
||||||
where TOptions : IParserOptionsRequest
|
where TOptions : IParserOptionsRequest
|
||||||
{
|
{
|
||||||
if (!parsers.TryGetValue(idParserService, out var parserService))
|
if (!parsers.TryGetValue(idParserService, out var parserService))
|
||||||
throw new ArgumentNullException(nameof(idParserService), "Сервис не зарегистрирован");
|
throw new ArgumentNullException(nameof(idParserService), "Не правильный идентификатор парсера");
|
||||||
|
|
||||||
return parserService.Invoke() as IParserServiceWithOptions<TDto, TOptions>
|
return parserService.Invoke() as IParserService<TDto, TOptions>
|
||||||
?? throw new ArgumentNullException(nameof(idParserService), "Ошибка приведения типа");
|
?? throw new ArgumentNullException(nameof(idParserService), "Ошибка приведения типа");
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,4 +1,5 @@
|
|||||||
using AsbCloudApp.Data;
|
using System;
|
||||||
|
using AsbCloudApp.Data;
|
||||||
using AsbCloudApp.Data.Trajectory;
|
using AsbCloudApp.Data.Trajectory;
|
||||||
using ClosedXML.Excel;
|
using ClosedXML.Excel;
|
||||||
|
|
||||||
@ -7,7 +8,13 @@ namespace AsbCloudInfrastructure.Services.Trajectory.Parser;
|
|||||||
public class TrajectoryFactManualParserService : TrajectoryParserService<TrajectoryGeoFactDto>
|
public class TrajectoryFactManualParserService : TrajectoryParserService<TrajectoryGeoFactDto>
|
||||||
{
|
{
|
||||||
protected override string SheetName => "Фактическая траектория";
|
protected override string SheetName => "Фактическая траектория";
|
||||||
|
protected override string TemplateFileName => "TrajectoryFactManualTemplate.xlsx";
|
||||||
|
|
||||||
|
public TrajectoryFactManualParserService(IServiceProvider serviceProvider)
|
||||||
|
: base(serviceProvider)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
protected override ValidationResultDto<TrajectoryGeoFactDto> ParseRow(IXLRow row)
|
protected override ValidationResultDto<TrajectoryGeoFactDto> ParseRow(IXLRow row)
|
||||||
{
|
{
|
||||||
var trajectoryRow = new TrajectoryGeoFactDto
|
var trajectoryRow = new TrajectoryGeoFactDto
|
||||||
@ -22,9 +29,11 @@ public class TrajectoryFactManualParserService : TrajectoryParserService<Traject
|
|||||||
|
|
||||||
//TODO: Добавить валидацию модели
|
//TODO: Добавить валидацию модели
|
||||||
|
|
||||||
return new ValidationResultDto<TrajectoryGeoFactDto>
|
var validationResult = new ValidationResultDto<TrajectoryGeoFactDto>
|
||||||
{
|
{
|
||||||
Item = trajectoryRow
|
Item = trajectoryRow
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return validationResult;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,23 +1,36 @@
|
|||||||
using AsbCloudApp.Data.Trajectory;
|
using System;
|
||||||
|
using AsbCloudApp.Data.Trajectory;
|
||||||
using ClosedXML.Excel;
|
using ClosedXML.Excel;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
using AsbCloudApp.Data;
|
using AsbCloudApp.Data;
|
||||||
using AsbCloudApp.Services.Parser;
|
using AsbCloudApp.Requests.ParserOptions;
|
||||||
|
|
||||||
namespace AsbCloudInfrastructure.Services.Trajectory.Parser;
|
namespace AsbCloudInfrastructure.Services.Trajectory.Parser;
|
||||||
|
|
||||||
public abstract class TrajectoryParserService<T> : IParserService<T>
|
public abstract class TrajectoryParserService<T> : ParserServiceBase<T, IParserOptionsRequest>
|
||||||
where T : TrajectoryGeoDto
|
where T : TrajectoryGeoDto
|
||||||
{
|
{
|
||||||
private const int HeaderRowsCount = 2;
|
private const int HeaderRowsCount = 2;
|
||||||
private const int ColumnCount = 6;
|
private const int ColumnCount = 6;
|
||||||
|
|
||||||
|
protected TrajectoryParserService(IServiceProvider serviceProvider)
|
||||||
|
: base(serviceProvider)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
protected abstract string SheetName { get; }
|
protected abstract string SheetName { get; }
|
||||||
|
|
||||||
|
protected abstract string TemplateFileName { get; }
|
||||||
|
|
||||||
protected abstract ValidationResultDto<T> ParseRow(IXLRow row);
|
protected abstract ValidationResultDto<T> ParseRow(IXLRow row);
|
||||||
|
|
||||||
public ParserResultDto<T> Parse(Stream file)
|
public override Stream GetTemplateFile() =>
|
||||||
|
Assembly.GetExecutingAssembly().GetTemplateCopyStream(TemplateFileName)
|
||||||
|
?? throw new ArgumentNullException($"Файл '{TemplateFileName}' не найден");
|
||||||
|
|
||||||
|
public override ParserResultDto<T> Parse(Stream file, IParserOptionsRequest options)
|
||||||
{
|
{
|
||||||
using var workbook = new XLWorkbook(file, XLEventTracking.Disabled);
|
using var workbook = new XLWorkbook(file, XLEventTracking.Disabled);
|
||||||
|
|
||||||
@ -25,7 +38,7 @@ public abstract class TrajectoryParserService<T> : IParserService<T>
|
|||||||
ws.Name.ToLower().Trim() == SheetName.ToLower().Trim())
|
ws.Name.ToLower().Trim() == SheetName.ToLower().Trim())
|
||||||
?? throw new FileFormatException($"Книга excel не содержит листа {SheetName}.");
|
?? throw new FileFormatException($"Книга excel не содержит листа {SheetName}.");
|
||||||
|
|
||||||
var trajectoryRows = sheet.Parse(ParseRow, ColumnCount, HeaderRowsCount);
|
var trajectoryRows = ParseExcelSheet(sheet, ParseRow, ColumnCount, HeaderRowsCount);
|
||||||
return trajectoryRows;
|
return trajectoryRows;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,4 +1,5 @@
|
|||||||
using AsbCloudApp.Data;
|
using System;
|
||||||
|
using AsbCloudApp.Data;
|
||||||
using AsbCloudApp.Data.Trajectory;
|
using AsbCloudApp.Data.Trajectory;
|
||||||
using ClosedXML.Excel;
|
using ClosedXML.Excel;
|
||||||
|
|
||||||
@ -7,7 +8,13 @@ namespace AsbCloudInfrastructure.Services.Trajectory.Parser;
|
|||||||
public class TrajectoryPlanParserService : TrajectoryParserService<TrajectoryGeoPlanDto>
|
public class TrajectoryPlanParserService : TrajectoryParserService<TrajectoryGeoPlanDto>
|
||||||
{
|
{
|
||||||
protected override string SheetName => "Плановая траектория";
|
protected override string SheetName => "Плановая траектория";
|
||||||
|
protected override string TemplateFileName => "TrajectoryPlanTemplate.xlsx";
|
||||||
|
|
||||||
|
public TrajectoryPlanParserService(IServiceProvider serviceProvider)
|
||||||
|
: base(serviceProvider)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
protected override ValidationResultDto<TrajectoryGeoPlanDto> ParseRow(IXLRow row)
|
protected override ValidationResultDto<TrajectoryGeoPlanDto> ParseRow(IXLRow row)
|
||||||
{
|
{
|
||||||
var trajectoryRow = new TrajectoryGeoPlanDto
|
var trajectoryRow = new TrajectoryGeoPlanDto
|
||||||
@ -23,9 +30,11 @@ public class TrajectoryPlanParserService : TrajectoryParserService<TrajectoryGeo
|
|||||||
|
|
||||||
//TODO: Добавить валидацию модели
|
//TODO: Добавить валидацию модели
|
||||||
|
|
||||||
return new ValidationResultDto<TrajectoryGeoPlanDto>
|
var validationResult = new ValidationResultDto<TrajectoryGeoPlanDto>
|
||||||
{
|
{
|
||||||
Item = trajectoryRow
|
Item = trajectoryRow
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return validationResult;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -90,7 +90,7 @@ internal static class XLExtentions
|
|||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
throw new FileFormatException(
|
throw new FileFormatException(
|
||||||
$"Лист '{cell.Worksheet.Name}'. Ячейка: ({cell.Address.RowNumber},{cell.Address.ColumnNumber}) содержит некорректное значение");
|
$"Лист '{cell.Worksheet.Name}'. {cell.Address.RowNumber} строка содержит некорректное значение в {cell.Address.ColumnNumber} столбце");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,6 +1,10 @@
|
|||||||
using System.Linq;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using AsbCloudApp.Data.Trajectory;
|
using AsbCloudApp.Data.Trajectory;
|
||||||
|
using AsbCloudApp.Requests.ParserOptions;
|
||||||
using AsbCloudInfrastructure.Services;
|
using AsbCloudInfrastructure.Services;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using NSubstitute;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace AsbCloudWebApi.Tests.Services.Trajectory;
|
namespace AsbCloudWebApi.Tests.Services.Trajectory;
|
||||||
@ -8,8 +12,20 @@ namespace AsbCloudWebApi.Tests.Services.Trajectory;
|
|||||||
public class TrajectoryParserTest
|
public class TrajectoryParserTest
|
||||||
{
|
{
|
||||||
private const string UsingTemplateFile = "AsbCloudWebApi.Tests.Services.Trajectory.Templates";
|
private const string UsingTemplateFile = "AsbCloudWebApi.Tests.Services.Trajectory.Templates";
|
||||||
|
|
||||||
|
private readonly IServiceProvider serviceProviderMock = Substitute.For<IServiceProvider, ISupportRequiredService>();
|
||||||
|
private readonly IServiceScope serviceScopeMock = Substitute.For<IServiceScope>();
|
||||||
|
private readonly IServiceScopeFactory serviceScopeFactoryMock = Substitute.For<IServiceScopeFactory>();
|
||||||
|
|
||||||
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]
|
[Fact]
|
||||||
public void Parse_trajectory_plan()
|
public void Parse_trajectory_plan()
|
||||||
@ -20,8 +36,10 @@ public class TrajectoryParserTest
|
|||||||
if (stream is null)
|
if (stream is null)
|
||||||
Assert.Fail("Файла для импорта не существует");
|
Assert.Fail("Файла для импорта не существует");
|
||||||
|
|
||||||
var parserService = parserServiceFactory.GetParser<TrajectoryGeoPlanDto>(ParserServiceFactory.IdTrajectoryPlanParserService);
|
var parserService = parserServiceFactory.Create<TrajectoryGeoPlanDto, IParserOptionsRequest>(
|
||||||
var trajectoryRows = parserService.Parse(stream);
|
ParserServiceFactory.IdTrajectoryPlanParserService);
|
||||||
|
|
||||||
|
var trajectoryRows = parserService.Parse(stream, IParserOptionsRequest.Empty());
|
||||||
|
|
||||||
Assert.Equal(3, trajectoryRows.Item.Count());
|
Assert.Equal(3, trajectoryRows.Item.Count());
|
||||||
}
|
}
|
||||||
@ -35,8 +53,10 @@ public class TrajectoryParserTest
|
|||||||
if (stream is null)
|
if (stream is null)
|
||||||
Assert.Fail("Файла для импорта не существует");
|
Assert.Fail("Файла для импорта не существует");
|
||||||
|
|
||||||
var parserService = parserServiceFactory.GetParser<TrajectoryGeoFactDto>(ParserServiceFactory.IdTrajectoryFactManualParserService);
|
var parserService = parserServiceFactory.Create<TrajectoryGeoFactDto, IParserOptionsRequest>(
|
||||||
var trajectoryRows = parserService.Parse(stream);
|
ParserServiceFactory.IdTrajectoryFactManualParserService);
|
||||||
|
|
||||||
|
var trajectoryRows = parserService.Parse(stream, IParserOptionsRequest.Empty());
|
||||||
|
|
||||||
Assert.Equal(4, trajectoryRows.Item.Count());
|
Assert.Equal(4, trajectoryRows.Item.Count());
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user