You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
765 lines
30 KiB
765 lines
30 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel.DataAnnotations;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Linq.Dynamic.Core;
|
|
using System.Linq.Expressions;
|
|
using System.Reflection;
|
|
using System.Security.Principal;
|
|
using System.Text.Json;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using AutoMapper.Internal;
|
|
using DocumentFormat.OpenXml.Math;
|
|
using DocumentFormat.OpenXml.Office2010.ExcelAc;
|
|
using DocumentFormat.OpenXml.Spreadsheet;
|
|
using DocumentFormat.OpenXml.Vml.Office;
|
|
using DocumentFormat.OpenXml.Wordprocessing;
|
|
using EFCore.BulkExtensions;
|
|
using JetBrains.Annotations;
|
|
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Net.Http.Headers;
|
|
using Volo.Abp;
|
|
using Volo.Abp.Application.Dtos;
|
|
using Volo.Abp.Application.Services;
|
|
using Volo.Abp.Caching;
|
|
using Volo.Abp.Domain.Entities;
|
|
using Volo.Abp.Domain.Entities.Auditing;
|
|
using Volo.Abp.Domain.Repositories;
|
|
using Volo.Abp.EventBus.Local;
|
|
using Volo.Abp.SettingManagement;
|
|
using Volo.Abp.Uow;
|
|
using Win_in.Sfs.Basedata.Application.Contracts;
|
|
using Win_in.Sfs.Shared.Application.Contracts;
|
|
using Win_in.Sfs.Shared.Application.Contracts.ExportAndImport;
|
|
using Win_in.Sfs.Shared.Domain;
|
|
using Win_in.Sfs.Shared.Domain.Shared;
|
|
using Win_in.Sfs.Shared.Event;
|
|
|
|
namespace Win_in.Sfs.Shared.Application;
|
|
|
|
/// <summary>
|
|
/// 应用服务基类
|
|
/// </summary>
|
|
public abstract class SfsCrudWithDetailsAppServiceBase<TEntity, TEntityDto, TRequestInput, TCreateInput,
|
|
TDetail, TDetailDTO, TDetailRequestInput, TImportInput>
|
|
: CrudAppService<TEntity, TEntityDto, Guid, TRequestInput, TCreateInput>
|
|
, ISfsCrudWithDetailsAppService<TEntityDto, TRequestInput, TCreateInput, TDetailDTO, TDetailRequestInput>
|
|
, ISfsGetByNumberAppService<TEntityDto>
|
|
where TEntity : class, IEntity<Guid>
|
|
where TEntityDto : class, IEntityDto<Guid>, new()
|
|
where TRequestInput : SfsRequestInputBase
|
|
where TDetail : SfsDetailEntityBase
|
|
where TDetailDTO : class, new()
|
|
where TImportInput : class, new()
|
|
{
|
|
protected readonly ISfsRepositoryBase<TEntity> _repository;
|
|
|
|
/// <summary>
|
|
/// 实体名称
|
|
/// </summary>
|
|
protected readonly string EntityClassName = typeof(TEntity).Name;
|
|
|
|
protected SfsCrudWithDetailsAppServiceBase(ISfsRepositoryBase<TEntity> repository) : base(repository)
|
|
{
|
|
_repository = repository;
|
|
}
|
|
|
|
protected IDistributedCache<TEntityDto> Cache => LazyServiceProvider.LazyGetRequiredService<IDistributedCache<TEntityDto>>();
|
|
|
|
protected IDocumentSettingAppService DocumentSettingAppService => LazyServiceProvider.LazyGetRequiredService<IDocumentSettingAppService>();
|
|
protected IExportImportService ExportImportService => LazyServiceProvider.LazyGetRequiredService<IExportImportService>();
|
|
|
|
protected IHttpContextAccessor HttpContextAccessor => LazyServiceProvider.LazyGetRequiredService<IHttpContextAccessor>();
|
|
|
|
protected ILocalEventBus LocalEventBus => LazyServiceProvider.LazyGetRequiredService<ILocalEventBus>();
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
protected ISettingManager SettingManager => LazyServiceProvider.LazyGetRequiredService<ISettingManager>();
|
|
|
|
/// <summary>
|
|
/// 添加明细列表
|
|
/// </summary>
|
|
/// <param name="id">实体Id</param>
|
|
/// <param name="list">明细列表</param>
|
|
[HttpPost("details/")]
|
|
public virtual async Task AddDetailListAsync(Guid id, List<TDetailDTO> list)
|
|
{
|
|
var entity = await _repository.GetAsync(id).ConfigureAwait(false);
|
|
Check.NotNull(entity, EntityClassName);
|
|
var details = ObjectMapper.Map<List<TDetailDTO>, List<TDetail>>(list);
|
|
if (entity is SfsMasterAggregateRootBase<TDetail> masterEntity)
|
|
{
|
|
masterEntity.AddDetails(details);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 新增实体
|
|
/// </summary>
|
|
/// <param name="input">CreateInput</param>
|
|
[HttpPost("")]
|
|
public override async Task<TEntityDto> CreateAsync(TCreateInput input)
|
|
{
|
|
await CheckCreatePolicyAsync().ConfigureAwait(continueOnCapturedContext: false);
|
|
var entity = input.ToObject<TEntity>();
|
|
SetIdForGuids(entity);
|
|
TryToSetTenantId(entity);
|
|
await Repository.InsertAsync(entity, autoSave: true).ConfigureAwait(continueOnCapturedContext: false);
|
|
var dto = entity.ToObject<TEntityDto>();
|
|
await Cache.SetItemAsync(dto.Id.ToString(), dto, SfsCacheConst.SeveralMinutes).ConfigureAwait(false);
|
|
return dto;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 删除实体
|
|
/// </summary>
|
|
/// <param name="id">实体Id</param>
|
|
[HttpDelete("{id}")]
|
|
public override async Task DeleteAsync(Guid id)
|
|
{
|
|
await Repository.DeleteAsync(id).ConfigureAwait(false);
|
|
await Cache.DeleteItemAsync(id.ToString()).ConfigureAwait(false);
|
|
return;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 删除明细
|
|
/// </summary>
|
|
/// <param name="id">实体Id</param>
|
|
/// <param name="detailId">明细Id</param>
|
|
/// <returns></returns>
|
|
[HttpDelete("details/{id}")]
|
|
public virtual async Task DeleteDetailAsync(Guid id, Guid detailId)
|
|
{
|
|
var entity = await _repository.GetAsync(id).ConfigureAwait(false);
|
|
Check.NotNull(entity, EntityClassName);
|
|
if (entity is SfsMasterAggregateRootBase<TDetail> masterEntity)
|
|
{
|
|
masterEntity.RemoveDetail(detailId);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 导出数据
|
|
/// </summary>
|
|
[HttpPost("export")]
|
|
public virtual async Task<IActionResult> ExportAsync(TRequestInput input)
|
|
{
|
|
var expression = input.Condition.Filters?.Count > 0
|
|
? input.Condition.Filters.ToLambda<TEntity>()
|
|
: p => true;
|
|
var entities = await _repository.GetPagedListAsync(expression, input.SkipCount, input.MaxResultCount,
|
|
input.Sorting, true).ConfigureAwait(false);
|
|
var list = ObjectMapper.Map<List<TEntity>, List<TEntityDto>>(entities);
|
|
var hasDetails = typeof(TEntity) is SfsMasterAggregateRootBase<TDetail> detailEntity;
|
|
return ExportImportService.Export(list, detailsProptyName: hasDetails ? nameof(detailEntity.Details) : null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 按条件获取全部数据列表
|
|
/// </summary>
|
|
/// <param name="sfsRequestInput">RequestInput</param>
|
|
/// <param name="includeDetails">是否包含明细</param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
[HttpPost("get-all")]
|
|
public virtual async Task<List<TEntityDto>> GetAllListByFilterAsync(TRequestInput sfsRequestInput, bool includeDetails = false,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
Expression<Func<TEntity, bool>> expression = sfsRequestInput.Condition.Filters?.Count > 0
|
|
? sfsRequestInput.Condition.Filters.ToLambda<TEntity>()
|
|
: p => true;
|
|
return await GetAllListAsync(expression, sfsRequestInput.Sorting, includeDetails, cancellationToken).ConfigureAwait(false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 按Id获取实体
|
|
/// </summary>
|
|
/// <param name="id">实体Id</param>
|
|
/// <returns></returns>
|
|
[HttpGet("{id}")]
|
|
public override async Task<TEntityDto> GetAsync(Guid id)
|
|
{
|
|
var dto = await Cache.GetOrAddItemAsync(
|
|
id.ToString(),
|
|
async () => await base.GetAsync(id).ConfigureAwait(false),
|
|
SfsCacheConst.SeveralMinutes).ConfigureAwait(false);
|
|
|
|
return dto;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 按编号获取实体
|
|
/// </summary>
|
|
/// <param name="number">编号</param>
|
|
/// <returns></returns>
|
|
[HttpGet("by-number")]
|
|
public virtual async Task<TEntityDto> GetByNumberAsync(string number)
|
|
{
|
|
var entity = (await _repository.GetQueryableAsync().ConfigureAwait(false)).Where("Number=@0", number).FirstOrDefault();
|
|
var dto = ObjectMapper.Map<TEntity, TEntityDto>(entity);
|
|
return dto;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 按条件获取数量
|
|
/// request sample
|
|
/// {
|
|
/// "maxResultCount": 1000,
|
|
/// "skipCount": 0,
|
|
/// "sorting": "",
|
|
/// "condition": { "filters": []}
|
|
/// }
|
|
/// </summary>
|
|
/// <param name="sfsRequestInput">RequestInput</param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
[HttpPost("count")]
|
|
public virtual async Task<long> GetCountByFilterAsync(TRequestInput sfsRequestInput, CancellationToken cancellationToken = default)
|
|
{
|
|
Expression<Func<TEntity, bool>> expression = sfsRequestInput.Condition.Filters?.Count > 0
|
|
? sfsRequestInput.Condition.Filters.ToLambda<TEntity>()
|
|
: p => true;
|
|
|
|
var count = await _repository.GetCountAsync(expression, cancellationToken).ConfigureAwait(false);
|
|
return count;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取明细
|
|
/// </summary>
|
|
/// <param name="id">实体Id</param>
|
|
/// <param name="detailId">明细Id</param>
|
|
/// <returns></returns>
|
|
[HttpGet("details/{id}")]
|
|
public virtual async Task<TDetailDTO> GetDetailAsync(Guid id, Guid detailId)
|
|
{
|
|
var entity = await _repository.GetAsync(id).ConfigureAwait(false);
|
|
Check.NotNull(entity, EntityClassName);
|
|
if (entity is SfsMasterAggregateRootBase<TDetail> masterEntity)
|
|
{
|
|
var detail = masterEntity.GetDetail(detailId);
|
|
var dto = ObjectMapper.Map<TDetail, TDetailDTO>(detail);
|
|
return dto;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 按条件获取明细列表
|
|
/// </summary>
|
|
/// <param name="id">实体Id</param>
|
|
/// <param name="requestInput">明细RequestInput</param>
|
|
/// <returns></returns>
|
|
[HttpGet("details/")]
|
|
public virtual async Task<List<TDetailDTO>> GetDetailListAsync(Guid id, TDetailRequestInput requestInput)
|
|
{
|
|
var entity = await _repository.GetAsync(id).ConfigureAwait(false);
|
|
Check.NotNull(entity, EntityClassName);
|
|
if (entity is SfsMasterAggregateRootBase<TDetail> masterEntity)
|
|
{
|
|
var details = masterEntity.GetDetailList(d => d.MasterID == id).ToList();
|
|
var dtos = ObjectMapper.Map<List<TDetail>, List<TDetailDTO>>(details);
|
|
return dtos;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 屏蔽基类方法
|
|
/// </summary>
|
|
/// <param name="input"></param>
|
|
/// <returns></returns>
|
|
[NonAction]
|
|
public override async Task<PagedResultDto<TEntityDto>> GetListAsync(TRequestInput input)
|
|
{
|
|
return await base.GetListAsync(input).ConfigureAwait(false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 按条件获取分页列表
|
|
/// request sample
|
|
/// {
|
|
/// "maxResultCount": 1000,
|
|
/// "skipCount": 0,
|
|
/// "sorting": "",
|
|
/// "condition": { "filters": []}
|
|
/// }
|
|
/// </summary>
|
|
/// <param name="sfsRequestInput">RequestInput</param>
|
|
/// <param name="includeDetails">是否包含明细</param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
[HttpPost("list")]
|
|
public virtual async Task<PagedResultDto<TEntityDto>> GetPagedListByFilterAsync(TRequestInput sfsRequestInput, bool includeDetails = false,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
Expression<Func<TEntity, bool>> expression = sfsRequestInput.Condition.Filters?.Count > 0
|
|
? sfsRequestInput.Condition.Filters.ToLambda<TEntity>()
|
|
: p => true;
|
|
|
|
return await GetPagedListAsync(expression, sfsRequestInput.SkipCount, sfsRequestInput.MaxResultCount, sfsRequestInput.Sorting, includeDetails, cancellationToken).ConfigureAwait(false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 按关键字获取分页列表
|
|
/// </summary>
|
|
/// <param name="keyWord">关键字</param>
|
|
/// <param name="skipCount">跳过数</param>
|
|
/// <param name="maxResultCount">最大结果数</param>
|
|
/// <param name="sorting">排序</param>
|
|
/// <param name="includeDetails">是否包含明细</param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
[HttpPost("search")]
|
|
public virtual async Task<PagedResultDto<TEntityDto>> GetPagedListByKeyWordAsync(string keyWord, int skipCount, int maxResultCount, string sorting,
|
|
bool includeDetails = false, CancellationToken cancellationToken = default)
|
|
{
|
|
var expression = BuildSearchExpression(keyWord);
|
|
return await GetPagedListAsync(expression, skipCount, maxResultCount, sorting, includeDetails, cancellationToken).ConfigureAwait(false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 导入数据
|
|
/// </summary>
|
|
[HttpPost("import")]
|
|
[Consumes("multipart/form-data")]
|
|
[UnitOfWork]
|
|
public virtual async Task<IActionResult> ImportAsync([FromForm] SfsImportRequestInput requestInput, [Required] IFormFile file)
|
|
{
|
|
using var ms = new MemoryStream();
|
|
await file.OpenReadStream().CopyToAsync(ms).ConfigureAwait(false);
|
|
var inputFileBytes = ms.GetAllBytes();
|
|
var result = await ImportInternalAsync(requestInput, inputFileBytes);
|
|
var bytes = result.FileContents;
|
|
result.FileContents = null;
|
|
|
|
HttpContextAccessor.HttpContext.Response.Headers.AccessControlExposeHeaders="X-Response";
|
|
HttpContextAccessor.HttpContext.Response.Headers.Add("X-Response",
|
|
JsonSerializer.Serialize(new { result.ExceptionMessage, result.FileName, result.FileCode, result }));
|
|
|
|
Console.WriteLine(@"导入错误信息:"+result.ExceptionMessage);
|
|
|
|
var resultAction = new TestResult(bytes, ExportImportService.ContentType) { FileDownloadName = result.FileName };
|
|
resultAction.errorNum = result.ErrorNum;
|
|
resultAction.successNum = resultAction.successNum;
|
|
return resultAction;
|
|
}
|
|
|
|
public class TestResult: FileContentResult
|
|
{
|
|
public byte[]_bytes { get; set; }
|
|
public int errorNum { get; set; }
|
|
public int successNum { get; set; }
|
|
|
|
public TestResult([NotNull] byte[] fileContents, [NotNull] string contentType) : base(fileContents, contentType)
|
|
{
|
|
}
|
|
|
|
public TestResult([NotNull] byte[] fileContents, [NotNull] MediaTypeHeaderValue contentType) : base(fileContents, contentType)
|
|
{
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取导入模板
|
|
/// </summary>
|
|
[HttpPost("import-template")]
|
|
public virtual IActionResult ImportTemplateAsync()
|
|
{
|
|
return ExportImportService.GetImportTemplate<TImportInput>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 修改实体
|
|
/// </summary>
|
|
/// <param name="id">实体Id</param>
|
|
/// <param name="input">UpdateInput</param>
|
|
/// <returns></returns>
|
|
[HttpPut("{id}")]
|
|
public override async Task<TEntityDto> UpdateAsync(Guid id, TCreateInput input)
|
|
{
|
|
try
|
|
{
|
|
//Value Inject 替换 AutoMapper
|
|
await CheckUpdatePolicyAsync().ConfigureAwait(continueOnCapturedContext: false);
|
|
TEntity entity = await GetEntityByIdAsync(id).ConfigureAwait(continueOnCapturedContext: false);
|
|
entity.FromObject(input);
|
|
await Repository.UpdateAsync(entity, autoSave: true).ConfigureAwait(continueOnCapturedContext: false);
|
|
var dto = entity.ToObject<TEntityDto>();
|
|
await Cache.SetItemAsync(dto.Id.ToString(), dto, SfsCacheConst.SeveralMinutes).ConfigureAwait(false);
|
|
return dto;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
var message = ex.Message.Contains("Database operation expected to affect 1 row(s) but actually affected 0 row(s)")
|
|
? $" {typeof(TEntityDto).Name} 已经被他人更改, 请刷新数据后再更新."
|
|
: ex.ToString();
|
|
throw new UserFriendlyException(message);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 更新明细
|
|
/// </summary>
|
|
/// <param name="id">实体Id</param>
|
|
/// <param name="detailId">明细Id</param>
|
|
/// <param name="updateDTO">明细UpdateDto</param>
|
|
/// <returns></returns>
|
|
[HttpPut("details/{id}")]
|
|
public virtual async Task UpdateDetailAsync(Guid id, Guid detailId, TDetailDTO updateDTO)
|
|
{
|
|
var entity = await _repository.GetAsync(id).ConfigureAwait(false);
|
|
Check.NotNull(entity, EntityClassName);
|
|
var detail = ObjectMapper.Map<TDetailDTO, TDetail>(updateDTO);
|
|
|
|
if (entity is SfsMasterAggregateRootBase<TDetail> masterEntity)
|
|
{
|
|
masterEntity.ReplaceDetail(detailId, detail);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 构造搜索条件表达式
|
|
/// </summary>
|
|
/// <param name="keyWord">搜索关键字</param>
|
|
/// <returns></returns>
|
|
protected virtual Expression<Func<TEntity, bool>> BuildSearchExpression(string keyWord)
|
|
{
|
|
// p => p.Id.ToString().Contains(keyWord);
|
|
var expression = typeof(TEntity).GetExpressionByProperty<TEntity>("Number", keyWord);
|
|
return expression;
|
|
}
|
|
|
|
protected virtual async Task<string> GenerateNumberAsync(string typeName, DateTime time)
|
|
{
|
|
var documentInput = new DocumentSettingGenerateInput(typeName, Clock.Normalize(time));
|
|
|
|
var maxCount = 10_000;
|
|
var tryCount = 0;
|
|
while (true)
|
|
{
|
|
var number = await DocumentSettingAppService.GenerateNumberAsync(documentInput).ConfigureAwait(false);
|
|
var entity = await GetByNumberAsync(number).ConfigureAwait(false);
|
|
if (entity == null)
|
|
{
|
|
return number;
|
|
}
|
|
|
|
tryCount++;
|
|
if (tryCount >= maxCount)
|
|
{
|
|
throw new UserFriendlyException($"无法生成 {documentInput.Type} 在 {documentInput.Time} 单据编号,请联系管理员处理。");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 按表达式条件获取分页列表
|
|
/// </summary>
|
|
/// <param name="expression"></param>
|
|
/// <param name="sorting"></param>
|
|
/// <param name="includeDetails"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
protected async Task<List<TEntityDto>> GetAllListAsync(Expression<Func<TEntity, bool>> expression, string sorting, bool includeDetails,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var entities = await _repository.GetListAsync(expression, sorting, includeDetails, cancellationToken).ConfigureAwait(false);
|
|
var dtos = ObjectMapper.Map<List<TEntity>, List<TEntityDto>>(entities);
|
|
return dtos;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 导入查询实体,可重写
|
|
/// </summary>
|
|
/// <param name="importInput"></param>
|
|
/// <returns></returns>
|
|
protected virtual async Task<TEntity> GetEntityAsync(TImportInput importInput)
|
|
{
|
|
var source = await _repository.GetDbSetAsync().ConfigureAwait(false);
|
|
return source.AsQueryable().WhereByKey(importInput)?.FirstOrDefault();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 按表达式条件获取分页列表
|
|
/// </summary>
|
|
/// <param name="expression"></param>
|
|
/// <param name="skipCount"></param>
|
|
/// <param name="maxResultCount"></param>
|
|
/// <param name="sorting"></param>
|
|
/// <param name="includeDetails"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
protected async Task<PagedResultDto<TEntityDto>> GetPagedListAsync(Expression<Func<TEntity, bool>> expression, int skipCount, int maxResultCount, string sorting, bool includeDetails,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var totalCount = await _repository.GetCountAsync(expression, cancellationToken).ConfigureAwait(false);
|
|
var entities = await _repository.GetPagedListAsync(expression, skipCount, maxResultCount, sorting, includeDetails, cancellationToken).ConfigureAwait(false);
|
|
var dtos = ObjectMapper.Map<List<TEntity>, List<TEntityDto>>(entities);
|
|
return new PagedResultDto<TEntityDto>(totalCount, dtos);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 导入数据具体实现,可重写
|
|
/// </summary>
|
|
[UnitOfWork]
|
|
protected virtual async Task<SfsImportResult> ImportInternalAsync(SfsImportRequestInput requestInput, byte[] inputFileBytes)
|
|
{
|
|
try
|
|
{
|
|
var hasDetails = typeof(TEntity).GetInterfaces().Any(o => o.IsGenericType && o.GetGenericTypeDefinition() == typeof(IMasterEntity<>));
|
|
var modelList = ExportImportService.Import<TImportInput>(inputFileBytes);
|
|
var modelDict = new Dictionary<TImportInput, List<ValidationResult>>();
|
|
var entityDict = new Dictionary<TEntity, EntityState>();
|
|
|
|
foreach (var model in modelList)
|
|
{
|
|
// DataAnnotations 静态验证
|
|
var validationRresults = new List<ValidationResult>();
|
|
modelDict.Add(model, validationRresults);
|
|
Validator.TryValidateObject(model, new ValidationContext(model, null, null), validationRresults);
|
|
}
|
|
|
|
// 如果没有验证错误或允许部分导入
|
|
if (!modelDict.SelectMany(o => o.Value).Any() || requestInput.IsAllowPartImport)
|
|
{
|
|
// 遍历并处理导入
|
|
if (hasDetails) //
|
|
{
|
|
var groups = modelList.AsQueryable().GroupByKey();
|
|
foreach (var item in groups)
|
|
{
|
|
var model = item.ToList().FirstOrDefault();
|
|
var validationRresults = modelDict[model];
|
|
await ImportValidationAsync(requestInput, modelDict, entityDict, model, validationRresults, item.ToList()).ConfigureAwait(false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
foreach (var item in modelDict)
|
|
{
|
|
var model = item.Key;
|
|
var validationRresults = item.Value;
|
|
await ImportValidationAsync(requestInput, modelDict, entityDict, model, validationRresults).ConfigureAwait(false);
|
|
}
|
|
}
|
|
}
|
|
// 批量更新
|
|
if (entityDict.Any())
|
|
{
|
|
entityDict=await ImportProcessingEntityAsync(entityDict).ConfigureAwait(false);
|
|
|
|
// 调用批量验证
|
|
var entityListStatus = await ValidateImportEntities(entityDict).ConfigureAwait(false);
|
|
if (entityListStatus)
|
|
{
|
|
await SaveImportAsync(entityDict).ConfigureAwait(false);
|
|
}
|
|
}
|
|
//将需要新增的数据进行发布
|
|
var addList= entityDict.Where(p => p.Value == EntityState.Added).Select(p=>p.Key).ToList();
|
|
await PublishCreatedAsync(addList).ConfigureAwait(false);
|
|
// 创建导入报告
|
|
var reportFile = ExportImportService.GetImportReport(inputFileBytes, modelDict);
|
|
// 创建返回值
|
|
return new SfsImportResult
|
|
{
|
|
TotalNum = modelList.Count,
|
|
ErrorNum = modelDict.Count(o => o.Value.Any()),
|
|
FileName = reportFile.FileDownloadName,
|
|
FileContents = reportFile.FileContents
|
|
};
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.LogException(ex);
|
|
return new SfsImportResult() { ExceptionMessage = ex.Message };
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 发布新增事件
|
|
/// </summary>
|
|
/// <param name="entities"></param>
|
|
/// <returns></returns>
|
|
private async Task PublishCreatedAsync(List<TEntity> entities)
|
|
{
|
|
try
|
|
{
|
|
await LocalEventBus.PublishAsync(new SfsCreatedEntityEventData<List<TEntity>>(entities),false).ConfigureAwait(false);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.LogDebug($"{typeof(TEntity).Name} Created Event:{ex.Message}", null);
|
|
Console.WriteLine(ex.Source);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 用来重写 导入数据时可以加工数据
|
|
/// </summary>
|
|
/// <param name="dictionary"></param>
|
|
/// <returns></returns>
|
|
protected virtual async Task<Dictionary<TEntity, EntityState>> ImportProcessingEntityAsync(Dictionary<TEntity, EntityState> dictionary)
|
|
{
|
|
return dictionary;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 导入保存到数据库,可重写
|
|
/// </summary>
|
|
|
|
protected virtual async Task SaveImportAsync(Dictionary<TEntity, EntityState> dict)
|
|
{
|
|
var entityList = dict.Keys.ToList();
|
|
var context = await _repository.GetDbContextAsync().ConfigureAwait(false);
|
|
var list = new List<TDetail>();
|
|
if (entityList.Count > 0)
|
|
{
|
|
foreach (var entity in entityList)
|
|
{
|
|
foreach (var propertyInfo in entity.GetType().GetProperties())
|
|
{
|
|
if (propertyInfo.PropertyType.IsListType() && propertyInfo.Name == "Details")
|
|
{
|
|
var entityDetails= propertyInfo.GetValue(entity, null);
|
|
|
|
list.AddRange(((List<TDetail>)entityDetails)!);
|
|
}
|
|
}
|
|
}
|
|
var bulkConfig = new BulkConfig()
|
|
{
|
|
SetOutputIdentity = true,
|
|
PreserveInsertOrder = true
|
|
};
|
|
|
|
await context.BulkInsertOrUpdateAsync(entityList, bulkConfig).ConfigureAwait(false);
|
|
await context.BulkInsertAsync(list).ConfigureAwait(false);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 导入批量验证,可重写
|
|
/// </summary>
|
|
protected virtual async Task<bool> ValidateImportEntities(Dictionary<TEntity, EntityState> dict)
|
|
{
|
|
return await Task.FromResult(true).ConfigureAwait(false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 导入单个输入,可重写
|
|
/// </summary>
|
|
/// <param name="model"></param>
|
|
/// <param name="validationRresult"></param>
|
|
/// <returns></returns>
|
|
protected virtual async Task ValidateImportModelAsync(TImportInput model, List<ValidationResult> validationRresult)
|
|
{
|
|
await Task.CompletedTask.ConfigureAwait(false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 验证单个实体,可重写
|
|
/// </summary>
|
|
/// <param name="model"></param>
|
|
/// <param name="entity"></param>
|
|
/// <param name="validationRresult"></param>
|
|
/// <returns></returns>
|
|
protected virtual async Task ValidateImportEntityAsync(TImportInput model, TEntity entity, List<ValidationResult> validationRresult)
|
|
{
|
|
await Task.CompletedTask.ConfigureAwait(false);
|
|
}
|
|
|
|
private async Task ImportValidationAsync(SfsImportRequestInput requestInput, Dictionary<TImportInput, List<ValidationResult>> modelDict, Dictionary<TEntity, EntityState> entityDict, TImportInput model, List<ValidationResult> validationRresults, List<TImportInput> detailModels = null)
|
|
{
|
|
var entity = await GetEntityAsync(model).ConfigureAwait(false);
|
|
if (requestInput.Method == EnumImportMethod.Append)
|
|
{
|
|
if (entity != null)
|
|
{
|
|
validationRresults.Add(new ValidationResult($"无法添加,数据已存在", new string[] { "错误" }));
|
|
}
|
|
}
|
|
else if (requestInput.Method == EnumImportMethod.Update)
|
|
{
|
|
if (entity == null)
|
|
{
|
|
validationRresults.Add(new ValidationResult($"无法更新,数据不存在", new string[] { "错误" }));
|
|
}
|
|
}
|
|
// 不包含错误或运行部分插入时,执行动态验证
|
|
if (!modelDict.SelectMany(o => o.Value).Any() || requestInput.IsAllowPartImport)
|
|
{
|
|
var hasEntity = entity != null;
|
|
await ValidateImportModelAsync(model, validationRresults).ConfigureAwait(false);
|
|
}
|
|
// 不包含错误或运行部分插入时,加入批量操作列表
|
|
if (!modelDict.SelectMany(o => o.Value).Any() || requestInput.IsAllowPartImport)
|
|
{
|
|
if (entity is IHasWorker worker)
|
|
{
|
|
worker.Worker = CurrentUser.GetUserName();
|
|
}
|
|
var hasEntity = entity != null;
|
|
if (entity == null)
|
|
{
|
|
entity = ObjectMapper.Map<TImportInput, TEntity>(model);
|
|
if (entity is SfsMasterAggregateRootBase<TDetail> entityWithIdAndNumber)
|
|
{
|
|
entityWithIdAndNumber.SetIdAndNumberWithDetails(GuidGenerator, await GenerateNumberAsync(typeof(TEntity).Name, DateTime.Now.Date).ConfigureAwait(false));
|
|
}
|
|
else if (entity is ISetId entityWithId)
|
|
{
|
|
entityWithId.SetId(GuidGenerator.Create());
|
|
}
|
|
if (entity is CreationAuditedAggregateRoot<Guid> creation)
|
|
{
|
|
creation.CreationTime = DateTime.Now;
|
|
creation.CreatorId = CurrentUser.Id;
|
|
}
|
|
// 清空现有子表
|
|
(entity as SfsMasterAggregateRootBase<TDetail>)?.Details.Clear();
|
|
}
|
|
else
|
|
{
|
|
var id = entity.Id;
|
|
ObjectMapper.Map(model, entity);
|
|
// ObjectMapper.Map 清空了 id,因此需要把预存的 id 取回
|
|
if (entity is ISetId entityWithId)
|
|
{
|
|
entityWithId.SetId(id);
|
|
}
|
|
}
|
|
// 设置子表
|
|
if (detailModels != null && entity is SfsMasterAggregateRootBase<TDetail> masterEntity)
|
|
{
|
|
foreach (var item in detailModels)
|
|
{
|
|
var detail = ObjectMapper.Map<TImportInput, TDetail>(item);
|
|
masterEntity.Details.Add(detail);
|
|
if (detail is ISetId entityWithId)
|
|
{
|
|
entityWithId.SetId(GuidGenerator.Create());
|
|
}
|
|
detail.SetIdAndNumber(GuidGenerator, masterEntity.Id, masterEntity.Number);
|
|
if (detail is CreationAuditedAggregateRoot<Guid> creation)
|
|
{
|
|
creation.CreationTime = DateTime.Now;
|
|
creation.CreatorId = CurrentUser.Id;
|
|
}
|
|
}
|
|
}
|
|
await ValidateImportEntityAsync(model, entity, validationRresults).ConfigureAwait(false);
|
|
entityDict.Add(entity, hasEntity ? EntityState.Modified : EntityState.Added);
|
|
}
|
|
}
|
|
}
|
|
|