Рефакторинг сервисов парсинга

This commit is contained in:
Степанов Дмитрий 2024-02-01 16:35:29 +05:00
parent 3865c330c2
commit e361ccf4c1
10 changed files with 127 additions and 61 deletions

View File

@ -1,8 +1,19 @@
namespace AsbCloudApp.Requests.ParserOptions;
/// <summary>
/// Интерфейс для параметров парсера
/// Параметры парсинга
/// </summary>
public interface IParserOptionsRequest
{
private static DummyOptions empty => new();
private class DummyOptions : IParserOptionsRequest
{
}
/// <summary>
/// Получение пустого объекта опций
/// </summary>
/// <returns></returns>
public static IParserOptionsRequest Empty() => empty;
}

View File

@ -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;
/// <summary>
/// Сервис парсинга файлов с доп. параметрами
/// Сервис парсинга
/// </summary>
/// <typeparam name="TDto"></typeparam>
/// <typeparam name="TOptions"></typeparam>
public interface IParserServiceWithOptions<TDto, in TOptions>
public interface IParserService<TDto, in TOptions> : IParserService
where TDto : class, IId
where TOptions : IParserOptionsRequest
{
@ -20,4 +22,17 @@ public interface IParserServiceWithOptions<TDto, in TOptions>
/// <param name="options"></param>
/// <returns></returns>
ParserResultDto<TDto> Parse(Stream file, TOptions options);
/// <summary>
/// Получение шаблона для заполнения
/// </summary>
/// <returns></returns>
Stream GetTemplateFile();
}
/// <summary>
/// Сервис парсинга(интерфейс маркер)
/// </summary>
public interface IParserService
{
}

View File

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

View File

@ -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<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,
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<TDto>();
var dtos = new List<ValidationResultDto<TDto>>(count);
var warnings = new List<ValidationResult>();
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<TDto>
{
Item = dtos

View File

@ -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<int, Func<object>> parsers = new Dictionary<int, Func<object>>
{
{ IdTrajectoryPlanParserService, () => new TrajectoryPlanParserService() },
{ IdTrajectoryFactManualParserService, () => new TrajectoryFactManualParserService() }
};
private readonly IDictionary<int, Func<IParserService>> parsers;
public IParserService<TDto> GetParser<TDto>(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<TDto>
?? throw new ArgumentNullException(nameof(idParserService), "Ошибка приведения типа");
parsers = new Dictionary<int, Func<IParserService>>
{
{ IdTrajectoryPlanParserService, () => new TrajectoryPlanParserService(serviceProvider) },
{ IdTrajectoryFactManualParserService, () => new TrajectoryFactManualParserService(serviceProvider) }
};
}
public IParserServiceWithOptions<TDto, TOptions> GetParserWithOptions<TDto, TOptions>(int idParserService)
public IParserService<TDto, TOptions> Create<TDto, TOptions>(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<TDto, TOptions>
return parserService.Invoke() as IParserService<TDto, TOptions>
?? throw new ArgumentNullException(nameof(idParserService), "Ошибка приведения типа");
}
}

View File

@ -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<TrajectoryGeoFactDto>
{
protected override string SheetName => "Фактическая траектория";
protected override string TemplateFileName => "TrajectoryFactManualTemplate.xlsx";
public TrajectoryFactManualParserService(IServiceProvider serviceProvider)
: base(serviceProvider)
{
}
protected override ValidationResultDto<TrajectoryGeoFactDto> ParseRow(IXLRow row)
{
var trajectoryRow = new TrajectoryGeoFactDto
@ -22,9 +29,11 @@ public class TrajectoryFactManualParserService : TrajectoryParserService<Traject
//TODO: Добавить валидацию модели
return new ValidationResultDto<TrajectoryGeoFactDto>
var validationResult = new ValidationResultDto<TrajectoryGeoFactDto>
{
Item = trajectoryRow
};
return validationResult;
}
}

View File

@ -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<T> : IParserService<T>
public abstract class TrajectoryParserService<T> : ParserServiceBase<T, IParserOptionsRequest>
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<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);
@ -25,7 +38,7 @@ public abstract class TrajectoryParserService<T> : IParserService<T>
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;
}
}

View File

@ -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<TrajectoryGeoPlanDto>
{
protected override string SheetName => "Плановая траектория";
protected override string TemplateFileName => "TrajectoryPlanTemplate.xlsx";
public TrajectoryPlanParserService(IServiceProvider serviceProvider)
: base(serviceProvider)
{
}
protected override ValidationResultDto<TrajectoryGeoPlanDto> ParseRow(IXLRow row)
{
var trajectoryRow = new TrajectoryGeoPlanDto
@ -23,9 +30,11 @@ public class TrajectoryPlanParserService : TrajectoryParserService<TrajectoryGeo
//TODO: Добавить валидацию модели
return new ValidationResultDto<TrajectoryGeoPlanDto>
var validationResult = new ValidationResultDto<TrajectoryGeoPlanDto>
{
Item = trajectoryRow
};
return validationResult;
}
}

View File

@ -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} столбце");
}
}
}

View File

@ -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<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]
public void Parse_trajectory_plan()
@ -20,8 +36,10 @@ public class TrajectoryParserTest
if (stream is null)
Assert.Fail("Файла для импорта не существует");
var parserService = parserServiceFactory.GetParser<TrajectoryGeoPlanDto>(ParserServiceFactory.IdTrajectoryPlanParserService);
var trajectoryRows = parserService.Parse(stream);
var parserService = parserServiceFactory.Create<TrajectoryGeoPlanDto, IParserOptionsRequest>(
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<TrajectoryGeoFactDto>(ParserServiceFactory.IdTrajectoryFactManualParserService);
var trajectoryRows = parserService.Parse(stream);
var parserService = parserServiceFactory.Create<TrajectoryGeoFactDto, IParserOptionsRequest>(
ParserServiceFactory.IdTrajectoryFactManualParserService);
var trajectoryRows = parserService.Parse(stream, IParserOptionsRequest.Empty());
Assert.Equal(4, trajectoryRows.Item.Count());
}