From 729c0c60f857c2eea42486c0c09e25cd6f2d524d Mon Sep 17 00:00:00 2001 From: master Date: Thu, 7 Nov 2024 11:33:52 +0800 Subject: [PATCH] =?UTF-8?q?feat(File):=20=E5=9C=A8=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E5=88=86=E9=A1=B5=E6=9F=A5=E8=AF=A2=E4=B8=AD=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E8=B7=AF=E5=BE=84=E7=AD=9B=E9=80=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 PageFileInput 类中新增 FilePath 属性,用于筛选文件路径 - 这个改动允许用户在分页查询时根据文件路径进行筛选,提高了文件查询的灵活性和效率 --- .../Service/File/Dto/FileInput.cs | 251 ++-- .../Service/File/SysFileService.cs | 1014 +++++++++-------- Web/src/api-services/apis/sys-file-api.ts | 76 ++ .../api-services/models/page-file-input.ts | 8 + .../system/file/component/folderTree.vue | 148 +++ Web/src/views/system/file/index.vue | 243 ++-- 6 files changed, 1018 insertions(+), 722 deletions(-) create mode 100644 Web/src/views/system/file/component/folderTree.vue diff --git a/Admin.NET/Admin.NET.Core/Service/File/Dto/FileInput.cs b/Admin.NET/Admin.NET.Core/Service/File/Dto/FileInput.cs index 482b02a4..6728ac6b 100644 --- a/Admin.NET/Admin.NET.Core/Service/File/Dto/FileInput.cs +++ b/Admin.NET/Admin.NET.Core/Service/File/Dto/FileInput.cs @@ -1,124 +1,129 @@ -// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 -// -// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 -// -// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! - -namespace Admin.NET.Core.Service; - -/// -/// 文件分页查询 -/// -public class PageFileInput : BasePageInput -{ - /// - /// 文件名称 - /// - public string FileName { get; set; } - - /// - /// 文件后缀 - /// - public string? Suffix { get; set; } - - /// - /// 开始时间 - /// - public DateTime? StartTime { get; set; } - - /// - /// 结束时间 - /// - public DateTime? EndTime { get; set; } -} - -public class FileInput : BaseIdInput -{ -} - -public class DeleteFileInput : BaseIdInput -{ -} - -/// -/// 上传文件 -/// -public class UploadFileInput : SysFile -{ - /// - /// 文件 - /// - [Required] - public IFormFile File { get; set; } - - /// - /// 文件路径 - /// - public string Path { get; set; } - - /// - /// 文件保存路径 - /// - public string SavePath { get; set; } - - /// - /// 允许格式:.jpeg.jpg.png.bmp.gif.tif - /// - public string AllowSuffix { get; set; } -} - -/// -/// 上传文件Base64 -/// -public class UploadFileFromBase64Input : SysFile -{ - /// - /// 文件内容 - /// - public string FileDataBase64 { get; set; } - - /// - /// 文件类型( "image/jpeg",) - /// - public string ContentType { get; set; } - - /// - /// 保存路径 - /// - public string Path { get; set; } -} - -/// -/// 查询关联查询输入 -/// -public class RelationQueryInput -{ - /// - /// 关联对象名称 - /// - public string RelationName { get; set; } - - /// - /// 关联对象Id - /// - public long? RelationId { get; set; } - - /// - /// 文件类型:多个以","分割 - /// - public string FileTypes { get; set; } - - /// - /// 所属Id - /// - public long? BelongId { get; set; } - - /// - /// 文件类型分割 - /// - /// - public string[] GetFileTypeBS() - { - return FileTypes.Split(','); - } +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 文件分页查询 +/// +public class PageFileInput : BasePageInput +{ + /// + /// 文件名称 + /// + public string FileName { get; set; } + + /// + /// 文件路径 + /// + public string FilePath { get; set; } + + /// + /// 文件后缀 + /// + public string? Suffix { get; set; } + + /// + /// 开始时间 + /// + public DateTime? StartTime { get; set; } + + /// + /// 结束时间 + /// + public DateTime? EndTime { get; set; } +} + +public class FileInput : BaseIdInput +{ +} + +public class DeleteFileInput : BaseIdInput +{ +} + +/// +/// 上传文件 +/// +public class UploadFileInput : SysFile +{ + /// + /// 文件 + /// + [Required] + public IFormFile File { get; set; } + + /// + /// 文件路径 + /// + public string Path { get; set; } + + /// + /// 文件保存路径 + /// + public string SavePath { get; set; } + + /// + /// 允许格式:.jpeg.jpg.png.bmp.gif.tif + /// + public string AllowSuffix { get; set; } +} + +/// +/// 上传文件Base64 +/// +public class UploadFileFromBase64Input : SysFile +{ + /// + /// 文件内容 + /// + public string FileDataBase64 { get; set; } + + /// + /// 文件类型( "image/jpeg",) + /// + public string ContentType { get; set; } + + /// + /// 保存路径 + /// + public string Path { get; set; } +} + +/// +/// 查询关联查询输入 +/// +public class RelationQueryInput +{ + /// + /// 关联对象名称 + /// + public string RelationName { get; set; } + + /// + /// 关联对象Id + /// + public long? RelationId { get; set; } + + /// + /// 文件类型:多个以","分割 + /// + public string FileTypes { get; set; } + + /// + /// 所属Id + /// + public long? BelongId { get; set; } + + /// + /// 文件类型分割 + /// + /// + public string[] GetFileTypeBS() + { + return FileTypes.Split(','); + } } \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/File/SysFileService.cs b/Admin.NET/Admin.NET.Core/Service/File/SysFileService.cs index 0b0571e4..4b3d3b33 100644 --- a/Admin.NET/Admin.NET.Core/Service/File/SysFileService.cs +++ b/Admin.NET/Admin.NET.Core/Service/File/SysFileService.cs @@ -1,492 +1,524 @@ -// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 -// -// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 -// -// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! - -using Aliyun.OSS.Util; -using OnceMi.AspNetCore.OSS; - -namespace Admin.NET.Core.Service; - -/// -/// 系统文件服务 🧩 -/// -[ApiDescriptionSettings(Order = 410, Description = "系统文件")] -public class SysFileService : IDynamicApiController, ITransient -{ - private readonly UserManager _userManager; - private readonly SqlSugarRepository _sysFileRep; - private readonly OSSProviderOptions _OSSProviderOptions; - private readonly UploadOptions _uploadOptions; - private readonly IOSSService _OSSService; - private readonly string _imageType = ".jpeg.jpg.png.bmp.gif.tif"; - - public SysFileService(UserManager userManager, - SqlSugarRepository sysFileRep, - IOptions oSSProviderOptions, - IOptions uploadOptions, - IOSSServiceFactory ossServiceFactory) - { - _userManager = userManager; - _sysFileRep = sysFileRep; - _OSSProviderOptions = oSSProviderOptions.Value; - _uploadOptions = uploadOptions.Value; - if (_OSSProviderOptions.Enabled) - _OSSService = ossServiceFactory.Create(Enum.GetName(_OSSProviderOptions.Provider)); - } - - /// - /// 获取文件分页列表 🔖 - /// - /// - /// - [DisplayName("获取文件分页列表")] - public async Task> Page(PageFileInput input) - { - // 获取所有公开附件 - var publicList = _sysFileRep.AsQueryable().ClearFilter().Where(u => u.IsPublic == true); - // 获取私有附件 - var privateList = _sysFileRep.AsQueryable().Where(u => u.IsPublic == false); - // 合并公开和私有附件并分页 - return await _sysFileRep.Context.UnionAll(publicList, privateList) - .WhereIF(!string.IsNullOrWhiteSpace(input.FileName), u => u.FileName.Contains(input.FileName.Trim())) - .WhereIF(!string.IsNullOrWhiteSpace(input.StartTime.ToString()) && !string.IsNullOrWhiteSpace(input.EndTime.ToString()), - u => u.CreateTime >= input.StartTime && u.CreateTime <= input.EndTime) - .OrderBy(u => u.CreateTime, OrderByType.Desc) - .ToPagedListAsync(input.Page, input.PageSize); - } - - /// - /// 上传文件Base64 🔖 - /// - /// - /// - [DisplayName("上传文件Base64")] - public async Task UploadFileFromBase64(UploadFileFromBase64Input input) - { - var pattern = @"data:(?.+?);base64,(?[^""]+)"; - var regex = new Regex(pattern, RegexOptions.Compiled); - var match = regex.Match(input.FileDataBase64); - - byte[] fileData = Convert.FromBase64String(match.Groups["data"].Value); - var contentType = match.Groups["type"].Value; - if (string.IsNullOrEmpty(input.FileName)) - input.FileName = $"{YitIdHelper.NextId()}.{contentType.AsSpan(contentType.LastIndexOf('/') + 1)}"; - - var ms = new MemoryStream(); - ms.Write(fileData); - ms.Seek(0, SeekOrigin.Begin); - IFormFile formFile = new FormFile(ms, 0, fileData.Length, "file", input.FileName) - { - Headers = new HeaderDictionary(), - ContentType = contentType - }; - var uploadFileInput = input.Adapt(); - uploadFileInput.File = formFile; - return await UploadFile(uploadFileInput); - } - - /// - /// 上传多文件 🔖 - /// - /// - /// - [DisplayName("上传多文件")] - public async Task> UploadFiles([Required] List files) - { - var filelist = new List(); - foreach (var file in files) - { - filelist.Add(await UploadFile(new UploadFileInput { File = file })); - } - return filelist; - } - - /// - /// 根据文件Id或Url下载 🔖 - /// - /// - /// - [DisplayName("根据文件Id或Url下载")] - public async Task DownloadFile(SysFile input) - { - var file = input.Id > 0 ? await GetFile(input.Id) : await _sysFileRep.CopyNew().GetFirstAsync(u => u.Url == input.Url); - var fileName = HttpUtility.UrlEncode(file.FileName, Encoding.GetEncoding("UTF-8")); - var filePath = Path.Combine(file.FilePath, file.Id.ToString() + file.Suffix); - - if (_OSSProviderOptions.Enabled) - { - var stream = await (await _OSSService.PresignedGetObjectAsync(file.BucketName.ToString(), filePath, 5)).GetAsStreamAsync(); - return new FileStreamResult(stream.Stream, "application/octet-stream") { FileDownloadName = fileName + file.Suffix }; - } - else if (App.Configuration["SSHProvider:Enabled"].ToBoolean()) - { - using (SSHHelper helper = new SSHHelper(App.Configuration["SSHProvider:Host"], - App.Configuration["SSHProvider:Port"].ToInt(), App.Configuration["SSHProvider:Username"], App.Configuration["SSHProvider:Password"])) - { - return new FileStreamResult(helper.OpenRead(filePath), "application/octet-stream") { FileDownloadName = fileName + file.Suffix }; - } - } - else - { - var path = Path.Combine(App.WebHostEnvironment.WebRootPath, filePath); - return new FileStreamResult(new FileStream(path, FileMode.Open), "application/octet-stream") { FileDownloadName = fileName + file.Suffix }; - } - } - - /// - /// 文件预览 🔖 - /// - /// - /// - [DisplayName("文件预览")] - public async Task GetPreview([FromRoute] long id) - { - var file = await GetFile(id); - //var fileName = HttpUtility.UrlEncode(file.FileName, Encoding.GetEncoding("UTF-8")); - var filePath = Path.Combine(file.FilePath, file.Id.ToString() + file.Suffix); - - if (_OSSProviderOptions.Enabled) - { - var stream = await (await _OSSService.PresignedGetObjectAsync(file.BucketName.ToString(), filePath, 5)).GetAsStreamAsync(); - return new FileStreamResult(stream.Stream, "application/octet-stream"); - } - else if (App.Configuration["SSHProvider:Enabled"].ToBoolean()) - { - using (SSHHelper helper = new SSHHelper(App.Configuration["SSHProvider:Host"], - App.Configuration["SSHProvider:Port"].ToInt(), App.Configuration["SSHProvider:Username"], App.Configuration["SSHProvider:Password"])) - { - return new FileStreamResult(helper.OpenRead(filePath), "application/octet-stream"); - } - } - else - { - var path = Path.Combine(App.WebHostEnvironment.WebRootPath, filePath); - return new FileStreamResult(new FileStream(path, FileMode.Open), "application/octet-stream"); - } - } - - /// - /// 下载指定文件Base64格式 🔖 - /// - /// - /// - [DisplayName("下载指定文件Base64格式")] - public async Task DownloadFileBase64([FromBody] string url) - { - if (_OSSProviderOptions.Enabled) - { - using var httpClient = new HttpClient(); - HttpResponseMessage response = await httpClient.GetAsync(url); - if (response.IsSuccessStatusCode) - { - // 读取文件内容并将其转换为 Base64 字符串 - byte[] fileBytes = await response.Content.ReadAsByteArrayAsync(); - return Convert.ToBase64String(fileBytes); - } - else - { - throw new HttpRequestException($"Request failed with status code: {response.StatusCode}"); - } - } - else if (App.Configuration["SSHProvider:Enabled"].ToBoolean()) - { - var sysFile = await _sysFileRep.CopyNew().GetFirstAsync(u => u.Url == url) ?? throw Oops.Oh($"文件不存在"); - using (SSHHelper helper = new SSHHelper(App.Configuration["SSHProvider:Host"], - App.Configuration["SSHProvider:Port"].ToInt(), App.Configuration["SSHProvider:Username"], App.Configuration["SSHProvider:Password"])) - { - return Convert.ToBase64String(helper.ReadAllBytes(sysFile.FilePath)); - } - } - else - { - var sysFile = await _sysFileRep.CopyNew().GetFirstAsync(u => u.Url == url) ?? throw Oops.Oh($"文件不存在"); - var filePath = Path.Combine(App.WebHostEnvironment.WebRootPath, sysFile.FilePath); - if (!Directory.Exists(filePath)) - Directory.CreateDirectory(filePath); - - var realFile = Path.Combine(filePath, $"{sysFile.Id}{sysFile.Suffix}"); - if (!File.Exists(realFile)) - { - Log.Error($"DownloadFileBase64:文件[{realFile}]不存在"); - throw Oops.Oh($"文件[{sysFile.FilePath}]不存在"); - } - byte[] fileBytes = File.ReadAllBytes(realFile); - return Convert.ToBase64String(fileBytes); - } - } - - /// - /// 删除文件 🔖 - /// - /// - /// - [ApiDescriptionSettings(Name = "Delete"), HttpPost] - [DisplayName("删除文件")] - public async Task DeleteFile(DeleteFileInput input) - { - var file = await _sysFileRep.GetByIdAsync(input.Id); - if (file != null) - { - await _sysFileRep.DeleteAsync(file); - - if (_OSSProviderOptions.Enabled) - { - await _OSSService.RemoveObjectAsync(file.BucketName.ToString(), string.Concat(file.FilePath, "/", $"{input.Id}{file.Suffix}")); - } - else if (App.Configuration["SSHProvider:Enabled"].ToBoolean()) - { - var fullPath = string.Concat(file.FilePath, "/", file.Id + file.Suffix); - using (SSHHelper helper = new SSHHelper(App.Configuration["SSHProvider:Host"], - App.Configuration["SSHProvider:Port"].ToInt(), App.Configuration["SSHProvider:Username"], App.Configuration["SSHProvider:Password"])) - { - helper.DeleteFile(fullPath); - } - } - else - { - var filePath = Path.Combine(App.WebHostEnvironment.WebRootPath, file.FilePath, input.Id.ToString() + file.Suffix); - if (File.Exists(filePath)) - File.Delete(filePath); - } - } - } - - /// - /// 更新文件 🔖 - /// - /// - /// - [ApiDescriptionSettings(Name = "Update"), HttpPost] - [DisplayName("更新文件")] - public async Task UpdateFile(SysFile input) - { - var isExist = await _sysFileRep.IsAnyAsync(u => u.Id == input.Id); - if (!isExist) throw Oops.Oh(ErrorCodeEnum.D8000); - - await _sysFileRep.UpdateAsync(u => input.Adapt(), u => u.Id == input.Id); - } - - /// - /// 获取文件 🔖 - /// - /// - /// - [DisplayName("获取文件")] - public async Task GetFile([FromQuery] long id) - { - var file = await _sysFileRep.CopyNew().GetByIdAsync(id); - return file ?? throw Oops.Oh(ErrorCodeEnum.D8000); - } - - /// - /// 上传文件 🔖 - /// - /// - /// - [DisplayName("上传文件")] - public async Task UploadFile([FromForm] UploadFileInput input) - { - if (input.File == null) throw Oops.Oh(ErrorCodeEnum.D8000); - - // 判断是否重复上传的文件 - var sizeKb = input.File.Length / 1024; // 大小KB - var fileMd5 = string.Empty; - if (_uploadOptions.EnableMd5) - { - using (var fileStream = input.File.OpenReadStream()) - { - fileMd5 = OssUtils.ComputeContentMd5(fileStream, fileStream.Length); - } - // Mysql8 中如果使用了 utf8mb4_general_ci 之外的编码会出错,尽量避免在条件里使用.ToString() - // 因为 Squsugar 并不是把变量转换为字符串来构造SQL语句,而是构造了CAST(123 AS CHAR)这样的语句,这样这个返回值是utf8mb4_general_ci,所以容易出错。 - var sysFile = await _sysFileRep.GetFirstAsync(u => u.FileMd5 == fileMd5 && u.SizeKb == sizeKb); - if (sysFile != null) return sysFile; - } - - // 验证文件类型 - if (!_uploadOptions.ContentType.Contains(input.File.ContentType)) - throw Oops.Oh($"{ErrorCodeEnum.D8001}:{input.File.ContentType}"); - - // 验证文件大小 - if (sizeKb > _uploadOptions.MaxSize) - throw Oops.Oh($"{ErrorCodeEnum.D8002},允许最大:{_uploadOptions.MaxSize}KB"); - - // 获取文件后缀 - var suffix = Path.GetExtension(input.File.FileName).ToLower(); // 后缀 - if (string.IsNullOrWhiteSpace(suffix)) - suffix = string.Concat(".", input.File.ContentType.AsSpan(input.File.ContentType.LastIndexOf('/') + 1)); - if (!string.IsNullOrWhiteSpace(suffix)) - { - //var contentTypeProvider = FS.GetFileExtensionContentTypeProvider(); - //suffix = contentTypeProvider.Mappings.FirstOrDefault(u => u.Value == file.ContentType).Key; - // 修改 image/jpeg 类型返回的 .jpeg、jpe 后缀 - if (suffix == ".jpeg" || suffix == ".jpe") - suffix = ".jpg"; - } - if (string.IsNullOrWhiteSpace(suffix)) - throw Oops.Oh(ErrorCodeEnum.D8003); - - // 防止客户端伪造文件类型 - if (!string.IsNullOrWhiteSpace(input.AllowSuffix) && !input.AllowSuffix.Contains(suffix)) - throw Oops.Oh(ErrorCodeEnum.D8003); - //if (!VerifyFileExtensionName.IsSameType(file.OpenReadStream(), suffix)) - // throw Oops.Oh(ErrorCodeEnum.D8001); - - // 文件存储位置 - var path = string.IsNullOrWhiteSpace(input.SavePath) ? _uploadOptions.Path : input.SavePath; - path = path.ParseToDateTimeForRep(); - - var newFile = input.Adapt(); - newFile.Id = YitIdHelper.NextId(); - newFile.BucketName = _OSSProviderOptions.Enabled ? _OSSProviderOptions.Bucket : "Local"; // 阿里云对bucket名称有要求,1.只能包括小写字母,数字,短横线(-)2.必须以小写字母或者数字开头 3.长度必须在3-63字节之间 - newFile.FileName = Path.GetFileNameWithoutExtension(input.File.FileName); - newFile.Suffix = suffix; - newFile.SizeKb = sizeKb; - newFile.FilePath = path; - newFile.FileMd5 = fileMd5; - - var finalName = newFile.Id + suffix; // 文件最终名称 - if (_OSSProviderOptions.Enabled) - { - newFile.Provider = Enum.GetName(_OSSProviderOptions.Provider); - var filePath = string.Concat(path, "/", finalName); - await _OSSService.PutObjectAsync(newFile.BucketName, filePath, input.File.OpenReadStream()); - // http://<你的bucket名字>.oss.aliyuncs.com/<你的object名字> - // 生成外链地址 方便前端预览 - switch (_OSSProviderOptions.Provider) - { - case OSSProvider.Aliyun: - newFile.Url = $"{(_OSSProviderOptions.IsEnableHttps ? "https" : "http")}://{newFile.BucketName}.{_OSSProviderOptions.Endpoint}/{filePath}"; - break; - - case OSSProvider.QCloud: - newFile.Url = $"{(_OSSProviderOptions.IsEnableHttps ? "https" : "http")}://{newFile.BucketName}-{_OSSProviderOptions.Endpoint}.cos.{_OSSProviderOptions.Region}.myqcloud.com/{filePath}"; - break; - - case OSSProvider.Minio: - // 获取Minio文件的下载或者预览地址 - // newFile.Url = await GetMinioPreviewFileUrl(newFile.BucketName, filePath);// 这种方法生成的Url是有7天有效期的,不能这样使用 - // 需要在MinIO中的Buckets开通对 Anonymous 的readonly权限 - var customHost = _OSSProviderOptions.CustomHost; - if (string.IsNullOrWhiteSpace(customHost)) - customHost = _OSSProviderOptions.Endpoint; - newFile.Url = $"{(_OSSProviderOptions.IsEnableHttps ? "https" : "http")}://{customHost}/{newFile.BucketName}/{filePath}"; - break; - } - } - else if (App.Configuration["SSHProvider:Enabled"].ToBoolean()) - { - var fullPath = string.Concat(path.StartsWith('/') ? path : "/" + path, "/", finalName); - using (SSHHelper helper = new SSHHelper(App.Configuration["SSHProvider:Host"], - App.Configuration["SSHProvider:Port"].ToInt(), App.Configuration["SSHProvider:Username"], App.Configuration["SSHProvider:Password"])) - { - helper.UploadFile(input.File.OpenReadStream(), fullPath); - } - } - else - { - newFile.Provider = ""; // 本地存储 Provider 显示为空 - var filePath = Path.Combine(App.WebHostEnvironment.WebRootPath, path); - if (!Directory.Exists(filePath)) - Directory.CreateDirectory(filePath); - - var realFile = Path.Combine(filePath, finalName); - using (var stream = File.Create(realFile)) - { - await input.File.CopyToAsync(stream); - } - - newFile.Url = $"{newFile.FilePath}/{newFile.Id + newFile.Suffix}"; - } - await _sysFileRep.AsInsertable(newFile).ExecuteCommandAsync(); - return newFile; - } - - /// - /// 上传头像 🔖 - /// - /// - /// - [DisplayName("上传头像")] - public async Task UploadAvatar([Required] IFormFile file) - { - var sysFile = await UploadFile(new UploadFileInput { File = file, AllowSuffix = _imageType, SavePath = "upload/avatar" }); - - var sysUserRep = _sysFileRep.ChangeRepository>(); - var user = await sysUserRep.GetByIdAsync(_userManager.UserId); - // 删除已有头像文件 - if (!string.IsNullOrWhiteSpace(user.Avatar)) - { - var fileId = Path.GetFileNameWithoutExtension(user.Avatar); - await DeleteFile(new DeleteFileInput { Id = long.Parse(fileId) }); - } - await sysUserRep.UpdateAsync(u => new SysUser() { Avatar = sysFile.Url }, u => u.Id == user.Id); - return sysFile; - } - - /// - /// 上传电子签名 🔖 - /// - /// - /// - [DisplayName("上传电子签名")] - public async Task UploadSignature([Required] IFormFile file) - { - var sysFile = await UploadFile(new UploadFileInput { File = file, AllowSuffix = _imageType, SavePath = "upload/signature" }); - - var sysUserRep = _sysFileRep.ChangeRepository>(); - var user = await sysUserRep.GetByIdAsync(_userManager.UserId); - // 删除已有电子签名文件 - if (!string.IsNullOrWhiteSpace(user.Signature) && user.Signature.EndsWith(".png")) - { - var fileId = Path.GetFileNameWithoutExtension(user.Signature); - await DeleteFile(new DeleteFileInput { Id = long.Parse(fileId) }); - } - await sysUserRep.UpdateAsync(u => new SysUser() { Signature = sysFile.Url }, u => u.Id == user.Id); - return sysFile; - } - - /// - /// 修改附件关联对象 🔖 - /// - /// - /// - /// - /// - /// - [NonAction] - public async Task UpdateRelation(List ids, string relationName, long relationId, long belongId = 0) - { - if (ids == null || ids.Count == 0) - return 0; - return await _sysFileRep.AsUpdateable() - .SetColumns(u => u.RelationName == relationName) - .SetColumns(u => u.RelationId == relationId) - .SetColumns(u => u.BelongId == belongId) - .Where(u => ids.Contains(u.Id)) - .ExecuteCommandAsync(); - } - - /// - /// 根据关联查询附件 - /// - /// - /// - /// - [DisplayName("根据关联查询附件")] - public async Task> GetRelationFiles([FromQuery] RelationQueryInput input) - { - return await _sysFileRep.AsQueryable() - .WhereIF(input.RelationId.HasValue && input.RelationId > 0, u => u.RelationId == input.RelationId) - .WhereIF(input.BelongId.HasValue && input.BelongId > 0, u => u.BelongId == input.BelongId.Value) - .WhereIF(!string.IsNullOrWhiteSpace(input.RelationName), u => u.RelationName == input.RelationName) - .WhereIF(!string.IsNullOrWhiteSpace(input.FileTypes), u => input.GetFileTypeBS().Contains(u.FileType)) - .Select(u => new SysFile - { - Url = SqlFunc.MergeString("/api/sysFile/Preview/", u.Id.ToString()), - }, true) - .ToListAsync(); - } +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using Aliyun.OSS.Util; +using OnceMi.AspNetCore.OSS; + +namespace Admin.NET.Core.Service; + +/// +/// 系统文件服务 🧩 +/// +[ApiDescriptionSettings(Order = 410, Description = "系统文件")] +public class SysFileService : IDynamicApiController, ITransient +{ + private readonly UserManager _userManager; + private readonly SqlSugarRepository _sysFileRep; + private readonly OSSProviderOptions _OSSProviderOptions; + private readonly UploadOptions _uploadOptions; + private readonly IOSSService _OSSService; + private readonly string _imageType = ".jpeg.jpg.png.bmp.gif.tif"; + + public SysFileService(UserManager userManager, + SqlSugarRepository sysFileRep, + IOptions oSSProviderOptions, + IOptions uploadOptions, + IOSSServiceFactory ossServiceFactory) + { + _userManager = userManager; + _sysFileRep = sysFileRep; + _OSSProviderOptions = oSSProviderOptions.Value; + _uploadOptions = uploadOptions.Value; + if (_OSSProviderOptions.Enabled) + _OSSService = ossServiceFactory.Create(Enum.GetName(_OSSProviderOptions.Provider)); + } + + /// + /// 获取文件分页列表 🔖 + /// + /// + /// + [DisplayName("获取文件分页列表")] + public async Task> Page(PageFileInput input) + { + // 获取所有公开附件 + var publicList = _sysFileRep.AsQueryable().ClearFilter().Where(u => u.IsPublic == true); + // 获取私有附件 + var privateList = _sysFileRep.AsQueryable().Where(u => u.IsPublic == false); + // 合并公开和私有附件并分页 + return await _sysFileRep.Context.UnionAll(publicList, privateList) + .WhereIF(!string.IsNullOrWhiteSpace(input.FileName), u => u.FileName.Contains(input.FileName.Trim())) + .WhereIF(!string.IsNullOrWhiteSpace(input.FilePath), u => u.FilePath.Contains(input.FilePath.Trim())) + .WhereIF(!string.IsNullOrWhiteSpace(input.StartTime.ToString()) && !string.IsNullOrWhiteSpace(input.EndTime.ToString()), + u => u.CreateTime >= input.StartTime && u.CreateTime <= input.EndTime) + .OrderBy(u => u.CreateTime, OrderByType.Desc) + .ToPagedListAsync(input.Page, input.PageSize); + } + + /// + /// 上传文件Base64 🔖 + /// + /// + /// + [DisplayName("上传文件Base64")] + public async Task UploadFileFromBase64(UploadFileFromBase64Input input) + { + var pattern = @"data:(?.+?);base64,(?[^""]+)"; + var regex = new Regex(pattern, RegexOptions.Compiled); + var match = regex.Match(input.FileDataBase64); + + byte[] fileData = Convert.FromBase64String(match.Groups["data"].Value); + var contentType = match.Groups["type"].Value; + if (string.IsNullOrEmpty(input.FileName)) + input.FileName = $"{YitIdHelper.NextId()}.{contentType.AsSpan(contentType.LastIndexOf('/') + 1)}"; + + var ms = new MemoryStream(); + ms.Write(fileData); + ms.Seek(0, SeekOrigin.Begin); + IFormFile formFile = new FormFile(ms, 0, fileData.Length, "file", input.FileName) + { + Headers = new HeaderDictionary(), + ContentType = contentType + }; + var uploadFileInput = input.Adapt(); + uploadFileInput.File = formFile; + return await UploadFile(uploadFileInput); + } + + /// + /// 上传多文件 🔖 + /// + /// + /// + [DisplayName("上传多文件")] + public async Task> UploadFiles([Required] List files) + { + var filelist = new List(); + foreach (var file in files) + { + filelist.Add(await UploadFile(new UploadFileInput { File = file })); + } + return filelist; + } + + /// + /// 根据文件Id或Url下载 🔖 + /// + /// + /// + [DisplayName("根据文件Id或Url下载")] + public async Task DownloadFile(SysFile input) + { + var file = input.Id > 0 ? await GetFile(input.Id) : await _sysFileRep.CopyNew().GetFirstAsync(u => u.Url == input.Url); + var fileName = HttpUtility.UrlEncode(file.FileName, Encoding.GetEncoding("UTF-8")); + var filePath = Path.Combine(file.FilePath, file.Id.ToString() + file.Suffix); + + if (_OSSProviderOptions.Enabled) + { + var stream = await (await _OSSService.PresignedGetObjectAsync(file.BucketName.ToString(), filePath, 5)).GetAsStreamAsync(); + return new FileStreamResult(stream.Stream, "application/octet-stream") { FileDownloadName = fileName + file.Suffix }; + } + else if (App.Configuration["SSHProvider:Enabled"].ToBoolean()) + { + using (SSHHelper helper = new SSHHelper(App.Configuration["SSHProvider:Host"], + App.Configuration["SSHProvider:Port"].ToInt(), App.Configuration["SSHProvider:Username"], App.Configuration["SSHProvider:Password"])) + { + return new FileStreamResult(helper.OpenRead(filePath), "application/octet-stream") { FileDownloadName = fileName + file.Suffix }; + } + } + else + { + var path = Path.Combine(App.WebHostEnvironment.WebRootPath, filePath); + return new FileStreamResult(new FileStream(path, FileMode.Open), "application/octet-stream") { FileDownloadName = fileName + file.Suffix }; + } + } + + /// + /// 文件预览 🔖 + /// + /// + /// + [DisplayName("文件预览")] + public async Task GetPreview([FromRoute] long id) + { + var file = await GetFile(id); + //var fileName = HttpUtility.UrlEncode(file.FileName, Encoding.GetEncoding("UTF-8")); + var filePath = Path.Combine(file.FilePath, file.Id.ToString() + file.Suffix); + + if (_OSSProviderOptions.Enabled) + { + var stream = await (await _OSSService.PresignedGetObjectAsync(file.BucketName.ToString(), filePath, 5)).GetAsStreamAsync(); + return new FileStreamResult(stream.Stream, "application/octet-stream"); + } + else if (App.Configuration["SSHProvider:Enabled"].ToBoolean()) + { + using (SSHHelper helper = new SSHHelper(App.Configuration["SSHProvider:Host"], + App.Configuration["SSHProvider:Port"].ToInt(), App.Configuration["SSHProvider:Username"], App.Configuration["SSHProvider:Password"])) + { + return new FileStreamResult(helper.OpenRead(filePath), "application/octet-stream"); + } + } + else + { + var path = Path.Combine(App.WebHostEnvironment.WebRootPath, filePath); + return new FileStreamResult(new FileStream(path, FileMode.Open), "application/octet-stream"); + } + } + + /// + /// 下载指定文件Base64格式 🔖 + /// + /// + /// + [DisplayName("下载指定文件Base64格式")] + public async Task DownloadFileBase64([FromBody] string url) + { + if (_OSSProviderOptions.Enabled) + { + using var httpClient = new HttpClient(); + HttpResponseMessage response = await httpClient.GetAsync(url); + if (response.IsSuccessStatusCode) + { + // 读取文件内容并将其转换为 Base64 字符串 + byte[] fileBytes = await response.Content.ReadAsByteArrayAsync(); + return Convert.ToBase64String(fileBytes); + } + else + { + throw new HttpRequestException($"Request failed with status code: {response.StatusCode}"); + } + } + else if (App.Configuration["SSHProvider:Enabled"].ToBoolean()) + { + var sysFile = await _sysFileRep.CopyNew().GetFirstAsync(u => u.Url == url) ?? throw Oops.Oh($"文件不存在"); + using (SSHHelper helper = new SSHHelper(App.Configuration["SSHProvider:Host"], + App.Configuration["SSHProvider:Port"].ToInt(), App.Configuration["SSHProvider:Username"], App.Configuration["SSHProvider:Password"])) + { + return Convert.ToBase64String(helper.ReadAllBytes(sysFile.FilePath)); + } + } + else + { + var sysFile = await _sysFileRep.CopyNew().GetFirstAsync(u => u.Url == url) ?? throw Oops.Oh($"文件不存在"); + var filePath = Path.Combine(App.WebHostEnvironment.WebRootPath, sysFile.FilePath); + if (!Directory.Exists(filePath)) + Directory.CreateDirectory(filePath); + + var realFile = Path.Combine(filePath, $"{sysFile.Id}{sysFile.Suffix}"); + if (!File.Exists(realFile)) + { + Log.Error($"DownloadFileBase64:文件[{realFile}]不存在"); + throw Oops.Oh($"文件[{sysFile.FilePath}]不存在"); + } + byte[] fileBytes = File.ReadAllBytes(realFile); + return Convert.ToBase64String(fileBytes); + } + } + + /// + /// 删除文件 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "Delete"), HttpPost] + [DisplayName("删除文件")] + public async Task DeleteFile(DeleteFileInput input) + { + var file = await _sysFileRep.GetByIdAsync(input.Id); + if (file != null) + { + await _sysFileRep.DeleteAsync(file); + + if (_OSSProviderOptions.Enabled) + { + await _OSSService.RemoveObjectAsync(file.BucketName.ToString(), string.Concat(file.FilePath, "/", $"{input.Id}{file.Suffix}")); + } + else if (App.Configuration["SSHProvider:Enabled"].ToBoolean()) + { + var fullPath = string.Concat(file.FilePath, "/", file.Id + file.Suffix); + using (SSHHelper helper = new SSHHelper(App.Configuration["SSHProvider:Host"], + App.Configuration["SSHProvider:Port"].ToInt(), App.Configuration["SSHProvider:Username"], App.Configuration["SSHProvider:Password"])) + { + helper.DeleteFile(fullPath); + } + } + else + { + var filePath = Path.Combine(App.WebHostEnvironment.WebRootPath, file.FilePath, input.Id.ToString() + file.Suffix); + if (File.Exists(filePath)) + File.Delete(filePath); + } + } + } + + /// + /// 更新文件 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "Update"), HttpPost] + [DisplayName("更新文件")] + public async Task UpdateFile(SysFile input) + { + var isExist = await _sysFileRep.IsAnyAsync(u => u.Id == input.Id); + if (!isExist) throw Oops.Oh(ErrorCodeEnum.D8000); + + await _sysFileRep.UpdateAsync(u => input.Adapt(), u => u.Id == input.Id); + } + + /// + /// 获取文件 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "File"), HttpGet] + [DisplayName("获取文件")] + public async Task GetFile([FromQuery] long id) + { + var file = await _sysFileRep.CopyNew().GetByIdAsync(id); + return file ?? throw Oops.Oh(ErrorCodeEnum.D8000); + } + + /// + /// 获取文件路径 🔖 + /// + /// + [ApiDescriptionSettings(Name = "Folder"), HttpGet] + [DisplayName("获取文件路径")] + public async Task GetFolder() + { + var files = await _sysFileRep.AsQueryable().ToListAsync(); + var folders = files.GroupBy(x => x.FilePath).Select(g => g.First().FilePath).ToList(); + var result = folders + .GroupBy(p => p.Split('/').First()) + .Select((g, index) => new + { + id = index + 1, // 组的索引加1作为id + pid = 0, + name = g.Key, + children = g.Select((item, subIndex) => new + { + id = (index + 1 * 100) + subIndex + 1, // 子项的索引加1作为id + pid = index + 1, + name = item.Split('/').Last(), + children = new List() + }) + .ToList() + }) + .ToList(); + return result; + } + + /// + /// 上传文件 🔖 + /// + /// + /// + [DisplayName("上传文件")] + public async Task UploadFile([FromForm] UploadFileInput input) + { + if (input.File == null) throw Oops.Oh(ErrorCodeEnum.D8000); + + // 判断是否重复上传的文件 + var sizeKb = input.File.Length / 1024; // 大小KB + var fileMd5 = string.Empty; + if (_uploadOptions.EnableMd5) + { + using (var fileStream = input.File.OpenReadStream()) + { + fileMd5 = OssUtils.ComputeContentMd5(fileStream, fileStream.Length); + } + // Mysql8 中如果使用了 utf8mb4_general_ci 之外的编码会出错,尽量避免在条件里使用.ToString() + // 因为 Squsugar 并不是把变量转换为字符串来构造SQL语句,而是构造了CAST(123 AS CHAR)这样的语句,这样这个返回值是utf8mb4_general_ci,所以容易出错。 + var sysFile = await _sysFileRep.GetFirstAsync(u => u.FileMd5 == fileMd5 && u.SizeKb == sizeKb); + if (sysFile != null) return sysFile; + } + + // 验证文件类型 + if (!_uploadOptions.ContentType.Contains(input.File.ContentType)) + throw Oops.Oh($"{ErrorCodeEnum.D8001}:{input.File.ContentType}"); + + // 验证文件大小 + if (sizeKb > _uploadOptions.MaxSize) + throw Oops.Oh($"{ErrorCodeEnum.D8002},允许最大:{_uploadOptions.MaxSize}KB"); + + // 获取文件后缀 + var suffix = Path.GetExtension(input.File.FileName).ToLower(); // 后缀 + if (string.IsNullOrWhiteSpace(suffix)) + suffix = string.Concat(".", input.File.ContentType.AsSpan(input.File.ContentType.LastIndexOf('/') + 1)); + if (!string.IsNullOrWhiteSpace(suffix)) + { + //var contentTypeProvider = FS.GetFileExtensionContentTypeProvider(); + //suffix = contentTypeProvider.Mappings.FirstOrDefault(u => u.Value == file.ContentType).Key; + // 修改 image/jpeg 类型返回的 .jpeg、jpe 后缀 + if (suffix == ".jpeg" || suffix == ".jpe") + suffix = ".jpg"; + } + if (string.IsNullOrWhiteSpace(suffix)) + throw Oops.Oh(ErrorCodeEnum.D8003); + + // 防止客户端伪造文件类型 + if (!string.IsNullOrWhiteSpace(input.AllowSuffix) && !input.AllowSuffix.Contains(suffix)) + throw Oops.Oh(ErrorCodeEnum.D8003); + //if (!VerifyFileExtensionName.IsSameType(file.OpenReadStream(), suffix)) + // throw Oops.Oh(ErrorCodeEnum.D8001); + + // 文件存储位置 + var path = string.IsNullOrWhiteSpace(input.SavePath) ? _uploadOptions.Path : input.SavePath; + path = path.ParseToDateTimeForRep(); + + var newFile = input.Adapt(); + newFile.Id = YitIdHelper.NextId(); + newFile.BucketName = _OSSProviderOptions.Enabled ? _OSSProviderOptions.Bucket : "Local"; // 阿里云对bucket名称有要求,1.只能包括小写字母,数字,短横线(-)2.必须以小写字母或者数字开头 3.长度必须在3-63字节之间 + newFile.FileName = Path.GetFileNameWithoutExtension(input.File.FileName); + newFile.Suffix = suffix; + newFile.SizeKb = sizeKb; + newFile.FilePath = path; + newFile.FileMd5 = fileMd5; + + var finalName = newFile.Id + suffix; // 文件最终名称 + if (_OSSProviderOptions.Enabled) + { + newFile.Provider = Enum.GetName(_OSSProviderOptions.Provider); + var filePath = string.Concat(path, "/", finalName); + await _OSSService.PutObjectAsync(newFile.BucketName, filePath, input.File.OpenReadStream()); + // http://<你的bucket名字>.oss.aliyuncs.com/<你的object名字> + // 生成外链地址 方便前端预览 + switch (_OSSProviderOptions.Provider) + { + case OSSProvider.Aliyun: + newFile.Url = $"{(_OSSProviderOptions.IsEnableHttps ? "https" : "http")}://{newFile.BucketName}.{_OSSProviderOptions.Endpoint}/{filePath}"; + break; + + case OSSProvider.QCloud: + newFile.Url = $"{(_OSSProviderOptions.IsEnableHttps ? "https" : "http")}://{newFile.BucketName}-{_OSSProviderOptions.Endpoint}.cos.{_OSSProviderOptions.Region}.myqcloud.com/{filePath}"; + break; + + case OSSProvider.Minio: + // 获取Minio文件的下载或者预览地址 + // newFile.Url = await GetMinioPreviewFileUrl(newFile.BucketName, filePath);// 这种方法生成的Url是有7天有效期的,不能这样使用 + // 需要在MinIO中的Buckets开通对 Anonymous 的readonly权限 + var customHost = _OSSProviderOptions.CustomHost; + if (string.IsNullOrWhiteSpace(customHost)) + customHost = _OSSProviderOptions.Endpoint; + newFile.Url = $"{(_OSSProviderOptions.IsEnableHttps ? "https" : "http")}://{customHost}/{newFile.BucketName}/{filePath}"; + break; + } + } + else if (App.Configuration["SSHProvider:Enabled"].ToBoolean()) + { + var fullPath = string.Concat(path.StartsWith('/') ? path : "/" + path, "/", finalName); + using (SSHHelper helper = new SSHHelper(App.Configuration["SSHProvider:Host"], + App.Configuration["SSHProvider:Port"].ToInt(), App.Configuration["SSHProvider:Username"], App.Configuration["SSHProvider:Password"])) + { + helper.UploadFile(input.File.OpenReadStream(), fullPath); + } + } + else + { + newFile.Provider = ""; // 本地存储 Provider 显示为空 + var filePath = Path.Combine(App.WebHostEnvironment.WebRootPath, path); + if (!Directory.Exists(filePath)) + Directory.CreateDirectory(filePath); + + var realFile = Path.Combine(filePath, finalName); + using (var stream = File.Create(realFile)) + { + await input.File.CopyToAsync(stream); + } + + newFile.Url = $"{newFile.FilePath}/{newFile.Id + newFile.Suffix}"; + } + await _sysFileRep.AsInsertable(newFile).ExecuteCommandAsync(); + return newFile; + } + + /// + /// 上传头像 🔖 + /// + /// + /// + [DisplayName("上传头像")] + public async Task UploadAvatar([Required] IFormFile file) + { + var sysFile = await UploadFile(new UploadFileInput { File = file, AllowSuffix = _imageType, SavePath = "upload/avatar" }); + + var sysUserRep = _sysFileRep.ChangeRepository>(); + var user = await sysUserRep.GetByIdAsync(_userManager.UserId); + // 删除已有头像文件 + if (!string.IsNullOrWhiteSpace(user.Avatar)) + { + var fileId = Path.GetFileNameWithoutExtension(user.Avatar); + await DeleteFile(new DeleteFileInput { Id = long.Parse(fileId) }); + } + await sysUserRep.UpdateAsync(u => new SysUser() { Avatar = sysFile.Url }, u => u.Id == user.Id); + return sysFile; + } + + /// + /// 上传电子签名 🔖 + /// + /// + /// + [DisplayName("上传电子签名")] + public async Task UploadSignature([Required] IFormFile file) + { + var sysFile = await UploadFile(new UploadFileInput { File = file, AllowSuffix = _imageType, SavePath = "upload/signature" }); + + var sysUserRep = _sysFileRep.ChangeRepository>(); + var user = await sysUserRep.GetByIdAsync(_userManager.UserId); + // 删除已有电子签名文件 + if (!string.IsNullOrWhiteSpace(user.Signature) && user.Signature.EndsWith(".png")) + { + var fileId = Path.GetFileNameWithoutExtension(user.Signature); + await DeleteFile(new DeleteFileInput { Id = long.Parse(fileId) }); + } + await sysUserRep.UpdateAsync(u => new SysUser() { Signature = sysFile.Url }, u => u.Id == user.Id); + return sysFile; + } + + /// + /// 修改附件关联对象 🔖 + /// + /// + /// + /// + /// + /// + [NonAction] + public async Task UpdateRelation(List ids, string relationName, long relationId, long belongId = 0) + { + if (ids == null || ids.Count == 0) + return 0; + return await _sysFileRep.AsUpdateable() + .SetColumns(u => u.RelationName == relationName) + .SetColumns(u => u.RelationId == relationId) + .SetColumns(u => u.BelongId == belongId) + .Where(u => ids.Contains(u.Id)) + .ExecuteCommandAsync(); + } + + /// + /// 根据关联查询附件 + /// + /// + /// + /// + [DisplayName("根据关联查询附件")] + public async Task> GetRelationFiles([FromQuery] RelationQueryInput input) + { + return await _sysFileRep.AsQueryable() + .WhereIF(input.RelationId.HasValue && input.RelationId > 0, u => u.RelationId == input.RelationId) + .WhereIF(input.BelongId.HasValue && input.BelongId > 0, u => u.BelongId == input.BelongId.Value) + .WhereIF(!string.IsNullOrWhiteSpace(input.RelationName), u => u.RelationName == input.RelationName) + .WhereIF(!string.IsNullOrWhiteSpace(input.FileTypes), u => input.GetFileTypeBS().Contains(u.FileType)) + .Select(u => new SysFile + { + Url = SqlFunc.MergeString("/api/sysFile/Preview/", u.Id.ToString()), + }, true) + .ToListAsync(); + } } \ No newline at end of file diff --git a/Web/src/api-services/apis/sys-file-api.ts b/Web/src/api-services/apis/sys-file-api.ts index bea84fa7..2ed96036 100644 --- a/Web/src/api-services/apis/sys-file-api.ts +++ b/Web/src/api-services/apis/sys-file-api.ts @@ -20,6 +20,7 @@ import { BASE_PATH, COLLECTION_FORMATS, RequestArgs, BaseAPI, RequiredError } fr import { AccountTypeEnum } from '../models'; import { AdminResultIActionResult } from '../models'; import { AdminResultListSysFile } from '../models'; +import { AdminResultObject } from '../models'; import { AdminResultSqlSugarPagedListSysFile } from '../models'; import { AdminResultString } from '../models'; import { AdminResultSysFile } from '../models'; @@ -231,6 +232,49 @@ export const SysFileApiAxiosParamCreator = function (configuration?: Configurati options: localVarRequestOptions, }; }, + /** + * + * @summary 获取文件路径 🔖 + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + apiSysFileFolderGet: async (options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/api/sysFile/folder`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, 'https://example.com'); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + const localVarRequestOptions :AxiosRequestConfig = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication Bearer required + // http bearer authentication required + if (configuration && configuration.accessToken) { + const accessToken = typeof configuration.accessToken === 'function' + ? await configuration.accessToken() + : await configuration.accessToken; + localVarHeaderParameter["Authorization"] = "Bearer " + accessToken; + } + + const query = new URLSearchParams(localVarUrlObj.search); + for (const key in localVarQueryParameter) { + query.set(key, localVarQueryParameter[key]); + } + for (const key in options.params) { + query.set(key, options.params[key]); + } + localVarUrlObj.search = (new URLSearchParams(query)).toString(); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: localVarUrlObj.pathname + localVarUrlObj.search + localVarUrlObj.hash, + options: localVarRequestOptions, + }; + }, /** * * @summary 获取文件分页列表 🔖 @@ -1909,6 +1953,19 @@ export const SysFileApiFp = function(configuration?: Configuration) { return axios.request(axiosRequestArgs); }; }, + /** + * + * @summary 获取文件路径 🔖 + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async apiSysFileFolderGet(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise>> { + const localVarAxiosArgs = await SysFileApiAxiosParamCreator(configuration).apiSysFileFolderGet(options); + return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => { + const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url}; + return axios.request(axiosRequestArgs); + }; + }, /** * * @summary 获取文件分页列表 🔖 @@ -2316,6 +2373,15 @@ export const SysFileApiFactory = function (configuration?: Configuration, basePa async apiSysFileFileGet(id?: number, options?: AxiosRequestConfig): Promise> { return SysFileApiFp(configuration).apiSysFileFileGet(id, options).then((request) => request(axios, basePath)); }, + /** + * + * @summary 获取文件路径 🔖 + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async apiSysFileFolderGet(options?: AxiosRequestConfig): Promise> { + return SysFileApiFp(configuration).apiSysFileFolderGet(options).then((request) => request(axios, basePath)); + }, /** * * @summary 获取文件分页列表 🔖 @@ -2692,6 +2758,16 @@ export class SysFileApi extends BaseAPI { public async apiSysFileFileGet(id?: number, options?: AxiosRequestConfig) : Promise> { return SysFileApiFp(this.configuration).apiSysFileFileGet(id, options).then((request) => request(this.axios, this.basePath)); } + /** + * + * @summary 获取文件路径 🔖 + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof SysFileApi + */ + public async apiSysFileFolderGet(options?: AxiosRequestConfig) : Promise> { + return SysFileApiFp(this.configuration).apiSysFileFolderGet(options).then((request) => request(this.axios, this.basePath)); + } /** * * @summary 获取文件分页列表 🔖 diff --git a/Web/src/api-services/models/page-file-input.ts b/Web/src/api-services/models/page-file-input.ts index 1a5213b6..10c0a5bc 100644 --- a/Web/src/api-services/models/page-file-input.ts +++ b/Web/src/api-services/models/page-file-input.ts @@ -90,6 +90,14 @@ export interface PageFileInput { */ fileName?: string | null; + /** + * 文件路径 + * + * @type {string} + * @memberof PageFileInput + */ + filePath?: string | null; + /** * 文件后缀 * diff --git a/Web/src/views/system/file/component/folderTree.vue b/Web/src/views/system/file/component/folderTree.vue new file mode 100644 index 00000000..0f5e7d9f --- /dev/null +++ b/Web/src/views/system/file/component/folderTree.vue @@ -0,0 +1,148 @@ + + + + + diff --git a/Web/src/views/system/file/index.vue b/Web/src/views/system/file/index.vue index 198f1ad0..b398db79 100644 --- a/Web/src/views/system/file/index.vue +++ b/Web/src/views/system/file/index.vue @@ -1,119 +1,127 @@