diff --git a/Yi.Abp.Net8/framework/Yi.Framework.Core/Enums/FileTypeEnum.cs b/Yi.Abp.Net8/framework/Yi.Framework.Core/Enums/FileTypeEnum.cs index 1776d35f..193f7909 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.Core/Enums/FileTypeEnum.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.Core/Enums/FileTypeEnum.cs @@ -11,10 +11,10 @@ namespace Yi.Framework.Core.Enums /// public enum FileTypeEnum { - File, - Image, - Thumbnail, - Excel, - Temp + file, + image, + thumbnail, + excel, + temp } } diff --git a/Yi.Abp.Net8/framework/Yi.Framework.Core/Helper/MimeHelper.cs b/Yi.Abp.Net8/framework/Yi.Framework.Core/Helper/MimeHelper.cs index d03bf16b..a89d9212 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.Core/Helper/MimeHelper.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.Core/Helper/MimeHelper.cs @@ -45,8 +45,8 @@ namespace Yi.Framework.Core.Helper { var extension = Path.GetExtension(fileName); if (ImageType.Contains(extension.ToLower())) - return FileTypeEnum.Image; - return FileTypeEnum.File; + return FileTypeEnum.image; + return FileTypeEnum.file; } diff --git a/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Application.Contracts/IServices/IFileService.cs b/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Application.Contracts/IServices/IFileService.cs index d9fdc6c5..52132f84 100644 --- a/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Application.Contracts/IServices/IFileService.cs +++ b/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Application.Contracts/IServices/IFileService.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; using Volo.Abp.Application.Services; using Yi.Framework.Rbac.Application.Contracts.Dtos.FileManager; @@ -6,7 +7,16 @@ namespace Yi.Framework.Rbac.Application.Contracts.IServices { public interface IFileService : IApplicationService { - Task GetReturnPathAsync(Guid code, bool? isThumbnail); - Task> Post(IFormFileCollection file); + /// + /// 下载文件,支持缩略图 + /// + /// + Task Get([FromRoute] Guid code, [FromRoute] bool? isThumbnail); + + /// + /// 上传文件 + /// + /// + Task> Post([FromForm] IFormFileCollection file); } } diff --git a/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Application/Services/FileService.cs b/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Application/Services/FileService.cs index 011be87f..f7be6221 100644 --- a/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Application/Services/FileService.cs +++ b/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Application/Services/FileService.cs @@ -15,129 +15,55 @@ using Yi.Framework.Core.Helper; using Yi.Framework.Rbac.Application.Contracts.Dtos.FileManager; using Yi.Framework.Rbac.Application.Contracts.IServices; using Yi.Framework.Rbac.Domain.Entities; +using Yi.Framework.Rbac.Domain.Managers; namespace Yi.Framework.Rbac.Application.Services { public class FileService : ApplicationService, IFileService { private readonly IRepository _repository; - private IGuidGenerator _guidGenerator; - public FileService(IRepository repository, IGuidGenerator guidGenerator) + private readonly FileManager _fileManager; + + public FileService(IRepository repository, FileManager fileManager) { - _guidGenerator = guidGenerator; _repository = repository; + _fileManager = fileManager; } /// - /// 下载文件,是否缩略图 + /// 下载文件,支持缩略图 /// /// [Route("file/{code}/{isThumbnail?}")] public async Task Get([FromRoute] Guid code, [FromRoute] bool? isThumbnail) { - var path = await GetReturnPathAsync(code, isThumbnail); - - if (path is null||!File.Exists(path)) + var file = await _repository.GetAsync(x => x.Id == code); + var path = file.GetQueryFileSavePath(isThumbnail); + if (path is null || !File.Exists(path)) { return new NotFoundResult(); - // throw new UserFriendlyException("文件不存在",code:"404"); } - - var steam = await File.ReadAllBytesAsync(path); - - //考虑从路径中获取 - var fileContentType = MimeHelper.GetMimeMapping(Path.GetFileName(path)); - //设置附件下载,下载名称 - //_httpContext.FileAttachmentHandle(file.FileName); - return new FileContentResult(steam, fileContentType ?? @"text/plain"); + return new FileContentResult(steam, file.GetMimeMapping()); } - - public async Task GetReturnPathAsync(Guid code, bool? isThumbnail) - { - var file = await _repository.GetAsync(x => x.Id == code); - if (file is null) - { - return null; - // throw new UserFriendlyException("文件编号未匹配", "404"); - } - var path = file.FilePath; - //如果为缩略图,需要修改路径 - //if (isThumbnail is true) - //{ - // path = $"wwwroot/{FileTypeEnum.Thumbnail}/{file.Id}{Path.GetExtension(file.FileName)}"; - //} - //路径为: 文件路径/文件id+文件扩展名 - return path; - } - + /// /// 上传文件 - /// Todo: 可放入领域层 /// /// public async Task> Post([FromForm] IFormFileCollection file) { - if (file.Count() == 0) + var entities = await _fileManager.CreateAsync(file); + + for (int i = 0; i < file.Count; i++) { - throw new ArgumentException("文件上传为空!"); + var entity= entities[i]; + using (var steam = file[i].OpenReadStream()) + { + await _fileManager.SaveFileAsync(entity,steam); + } } - //批量插入 - List entities = new(); - - foreach (var f in file) - { - FileAggregateRoot data = new(_guidGenerator.Create()); - data.FileSize = (decimal)f.Length / 1024; - data.FileName = f.FileName; - - var type = MimeHelper.GetFileType(f.FileName); - - //落盘文件,文件名为雪花id+自己的扩展名 - string filename = data.Id.ToString() + Path.GetExtension(f.FileName); - string typePath = $"wwwroot/{type}"; - if (!Directory.Exists(typePath)) - { - Directory.CreateDirectory(typePath); - } - var filePath = Path.Combine(typePath, filename); - data.FilePath = filePath; - - - //生成文件 - using (var stream = new FileStream(filePath, FileMode.CreateNew, FileAccess.ReadWrite)) - { - await f.CopyToAsync(stream); - - //如果是图片类型,还需要生成缩略图,当然,如果图片很小,直接复制过去即可 - if (FileTypeEnum.Image.Equals(type)) - { - string thumbnailPath = $"wwwroot/{FileTypeEnum.Thumbnail}"; - if (!Directory.Exists(thumbnailPath)) - { - Directory.CreateDirectory(thumbnailPath); - } - string thumbnailFilePath = Path.Combine(thumbnailPath, filename); - try - { - // _imageSharpManager.ImageCompress(f.FileName, f.OpenReadStream(), thumbnailFilePath); - } - catch - { - var result = new byte[stream.Length]; - await stream.ReadAsync(result, 0, result.Length); - await File.WriteAllBytesAsync(thumbnailFilePath, result); - } - } - - - }; - entities.Add(data); - } - await _repository.InsertManyAsync(entities); return entities.Adapt>(); - - } } -} +} \ No newline at end of file diff --git a/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/Entities/FileAggregateRoot.cs b/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/Entities/FileAggregateRoot.cs index 16beb5c2..7cb6faf9 100644 --- a/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/Entities/FileAggregateRoot.cs +++ b/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/Entities/FileAggregateRoot.cs @@ -7,6 +7,8 @@ using SqlSugar; using Volo.Abp.Auditing; using Volo.Abp.Data; using Volo.Abp.Domain.Entities; +using Yi.Framework.Core.Enums; +using Yi.Framework.Core.Helper; namespace Yi.Framework.Rbac.Domain.Entities { @@ -17,28 +19,140 @@ namespace Yi.Framework.Rbac.Domain.Entities { } - public FileAggregateRoot(Guid fileId) + /// + /// 创建文件 + /// + /// 文件标识id + /// 文件名 + /// 文件大小 + public FileAggregateRoot(Guid fileId, string fileName, decimal fileSize) { this.Id = fileId; + this.FileSize = fileSize; + this.FileName = fileName; + + var type = GetFileType(); + + var savePath = GetSaveFilePath(); + var filePath = Path.Combine(savePath, this.FileName); + this.FilePath = filePath; } - [SugarColumn(IsPrimaryKey = true)] - public override Guid Id { get; protected set; } + /// + /// 检测目录是否存在,不存在便创建 + /// + public void CheckDirectoryOrCreate() + { + var savePath = GetSaveDirPath(); + if (!Directory.Exists(savePath)) + { + Directory.CreateDirectory(savePath); + } + } + + /// + /// 文件类型 + /// + /// + public FileTypeEnum GetFileType() + { + return MimeHelper.GetFileType(this.FileName); + } + + /// + /// 获取文件mime + /// + /// + public string GetMimeMapping() + { + return MimeHelper.GetMimeMapping(this.FileName)??@"text/plain"; + } + + /// + /// 落库目录路径 + /// + /// + public string GetSaveDirPath() + { + return $"wwwroot/{GetFileType()}"; + } + + /// + /// 落库文件路径 + /// + /// + public string GetSaveFilePath() + { + string savefileName = GetSaveFileName(); + return Path.Combine(GetSaveDirPath(), savefileName); + } + + /// + /// 获取保存的文件名 + /// + /// + public string GetSaveFileName() + { + return this.Id.ToString() + Path.GetExtension(this.FileName); + } + + /// + /// 检测,并且返回缩略图的保存路径 + /// + /// + /// + public string GetAndCheakThumbnailSavePath(bool isCheak=false) + { + string thumbnailPath = $"wwwroot/{FileTypeEnum.thumbnail}"; + if (isCheak) + { + if (!Directory.Exists(thumbnailPath)) + { + Directory.CreateDirectory(thumbnailPath); + } + } + return Path.Combine(thumbnailPath, GetSaveFileName()); + } + + + /// + /// 获取查询的的文件路径 + /// + /// + /// + /// + public string? GetQueryFileSavePath(bool? isThumbnail) + { + string fileSavePath; + //如果为缩略图,需要修改路径 + if (isThumbnail is true) + { + fileSavePath = this.GetAndCheakThumbnailSavePath(); + } + else + { + fileSavePath = this.GetSaveFilePath(); + } + return fileSavePath; + } + /// /// 文件大小 /// [SugarColumn(ColumnName = "FileSize")] - public decimal FileSize { get; set; } + public decimal FileSize { get; internal set; } + /// /// 文件名 /// [SugarColumn(ColumnName = "FileName")] - public string FileName { get; set; } + public string FileName { get; internal set; } + /// /// 文件路径 /// [SugarColumn(ColumnName = "FilePath")] - public string FilePath { get; set; } + public string FilePath { get; internal set; } public DateTime CreationTime { get; set; } public Guid? CreatorId { get; set; } @@ -46,8 +160,5 @@ namespace Yi.Framework.Rbac.Domain.Entities public Guid? LastModifierId { get; set; } public DateTime? LastModificationTime { get; set; } - - [SugarColumn(IsIgnore=true)] - public override ExtraPropertyDictionary ExtraProperties { get; protected set; } } -} +} \ No newline at end of file diff --git a/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/Managers/FileManager.cs b/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/Managers/FileManager.cs new file mode 100644 index 00000000..4a39c846 --- /dev/null +++ b/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/Managers/FileManager.cs @@ -0,0 +1,116 @@ +using Mapster; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.Domain.Services; +using Volo.Abp.Guids; +using Volo.Abp.Imaging; +using Yi.Framework.Core.Enums; +using Yi.Framework.Core.Helper; +using Yi.Framework.Rbac.Domain.Entities; + +namespace Yi.Framework.Rbac.Domain.Managers; + +public class FileManager : DomainService, IFileManager +{ + private IGuidGenerator _guidGenerator; + private readonly IRepository _repository; + private readonly IImageCompressor _imageCompressor; + public FileManager(IGuidGenerator guidGenerator, IRepository repository, + + IImageCompressor imageCompressor) + { + _guidGenerator = guidGenerator; + _repository = repository; + _imageCompressor = imageCompressor; + } + + /// + /// 批量插入数据库 + /// + /// + /// + public async Task> CreateAsync(IEnumerable files) + { + if (files.Count() == 0) + { + throw new ArgumentException("文件上传为空!"); + } + + //批量插入 + List entities = new(); + foreach (var file in files) + { + FileAggregateRoot data = new(_guidGenerator.Create(), file.FileName, (decimal)file.Length / 1024); + data.CheckDirectoryOrCreate(); + entities.Add(data); + } + + await _repository.InsertManyAsync(entities); + return entities; + } + + + /// + /// 保存文件 + /// + /// + /// + public async Task SaveFileAsync(FileAggregateRoot file, Stream fileStream) + { + var filePath = file.GetSaveFilePath(); + + //生成文件 + using (var stream = new FileStream(filePath, FileMode.CreateNew, FileAccess.ReadWrite)) + { + await fileStream.CopyToAsync(stream); + fileStream.Position = 0; + } + + + //如果是图片类型,还需要生成缩略图 + //这里根据自己需求变更,我们的需求是:原始文件与缩略图文件,都要一份 + var fileType = file.GetFileType(); + //如果文件类型是图片,尝试进行压缩 + if (FileTypeEnum.image==fileType) + { + var thumbnailSavePath = file.GetAndCheakThumbnailSavePath(true); + Stream compressImageStream=null; + try + { + //压缩图片 + var compressResult = await _imageCompressor.CompressAsync(fileStream, file.GetMimeMapping()); + if (compressResult.State == ImageProcessState.Done) + { + compressImageStream = + (await _imageCompressor.CompressAsync(fileStream, file.GetMimeMapping())).Result; + } + else if (compressResult.State == ImageProcessState.Canceled) + { + throw new NotSupportedException($"当前图片无法再进行压缩,文件id:{file.Id}"); + } + else + { + throw new NotSupportedException($"当前图片不支持压缩,文件id:{file.Id}"); + } + } + catch (Exception exception) when (exception is NotSupportedException) + { + this.LoggerFactory.CreateLogger().LogInformation(exception, exception.Message); + } + catch (Exception exception) + { + //如果失败了,直接复制一份到缩略图上即可 + compressImageStream = fileStream; + this.LoggerFactory.CreateLogger().LogError(exception, exception.Message); + } + + + using (var stream = new FileStream(thumbnailSavePath, FileMode.CreateNew, FileAccess.ReadWrite)) + { + await compressImageStream.CopyToAsync(stream); + compressImageStream.Position = 0; + } + } + } +} \ No newline at end of file diff --git a/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/Managers/IFileManager.cs b/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/Managers/IFileManager.cs new file mode 100644 index 00000000..838476ab --- /dev/null +++ b/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/Managers/IFileManager.cs @@ -0,0 +1,6 @@ +namespace Yi.Framework.Rbac.Domain.Managers; + +public interface IFileManager +{ + +} \ No newline at end of file diff --git a/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/Yi.Framework.Rbac.Domain.csproj b/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/Yi.Framework.Rbac.Domain.csproj index 799e5d95..734dc0a1 100644 --- a/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/Yi.Framework.Rbac.Domain.csproj +++ b/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/Yi.Framework.Rbac.Domain.csproj @@ -16,7 +16,8 @@ - + + diff --git a/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/YiFrameworkRbacDomainModule.cs b/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/YiFrameworkRbacDomainModule.cs index a5568c1e..a5a61d92 100644 --- a/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/YiFrameworkRbacDomainModule.cs +++ b/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/YiFrameworkRbacDomainModule.cs @@ -2,6 +2,7 @@ using Volo.Abp.AspNetCore.SignalR; using Volo.Abp.Caching; using Volo.Abp.Domain; +using Volo.Abp.Imaging; using Volo.Abp.Modularity; using Yi.Framework.Caching.FreeRedis; using Yi.Framework.Mapster; @@ -18,7 +19,8 @@ namespace Yi.Framework.Rbac.Domain typeof(AbpAspNetCoreSignalRModule), typeof(AbpDddDomainModule), - typeof(AbpCachingModule) + typeof(AbpCachingModule), + typeof(AbpImagingImageSharpModule) )] public class YiFrameworkRbacDomainModule : AbpModule {