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 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
- 查询
- 重置
-
-
-
-
+
-
-
-
- 上传
-
-
-
-
-
-
- {{ row.suffix }}
-
-
- 是
- 否
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ 查询
+ 重置
+
+
+
+
-
-
-
-
- 上传文件
-
-
-
-
-
-
-
- 是否公开:
-
- 否
- 是
-
+
+
+
+ 上传
+
+
+
+
+
+
+ {{ row.suffix }}
+
+
+ 是
+ 否
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
- 将文件拖到此处,或点击上传
-
- 请上传大小不超过 10MB 的文件
+
+
+
+
+ 上传文件
+
-
-
-
-
-
-
+
+
+
+
+
+ 是否公开:
+
+ 否
+ 是
+
+
+
+
+
+
+ 将文件拖到此处,或点击上传
+
+ 请上传大小不超过 10MB 的文件
+
+
+
+
+
+
+
+
+
@@ -135,13 +143,19 @@ import { ElMessageBox, ElMessage, UploadInstance } from 'element-plus';
import { VxeGridInstance, VxeGridListeners, VxeGridPropTypes } from 'vxe-table';
import { useVxeTable } from '/@/hooks/useVxeTableOptionsHook';
import { Local } from '/@/utils/storage';
+
+import { Splitpanes, Pane } from 'splitpanes';
+import 'splitpanes/dist/splitpanes.css';
+
import { downloadByUrl } from '/@/utils/download';
+
import VueOfficeDocx from '@vue-office/docx';
import VueOfficeExcel from '@vue-office/excel';
import VueOfficePdf from '@vue-office/pdf';
import '@vue-office/docx/lib/index.css';
import '@vue-office/excel/lib/index.css';
+import FolderTree from '/@/views/system/file/component/folderTree.vue';
import EditFile from '/@/views/system/file/component/editFile.vue';
import { getAPI } from '/@/utils/axios-utils';
@@ -152,9 +166,12 @@ const xGrid = ref();
// const baseUrl = window.__env__.VITE_API_URL;
const uploadRef = ref();
const editRef = ref>();
+const folderTreeRef = ref>();
+
const state = reactive({
queryParams: {
fileName: undefined,
+ filePath: undefined,
startTime: undefined,
endTime: undefined,
},
@@ -239,6 +256,7 @@ const handleQuery = async (reset = false) => {
// 重置操作
const resetQuery = async () => {
state.queryParams.fileName = undefined;
+ state.queryParams.filePath = undefined;
state.queryParams.startTime = undefined;
state.queryParams.endTime = undefined;
await xGrid.value?.commitProxy('reload');
@@ -301,6 +319,15 @@ const gridEvents: VxeGridListeners = {
},
};
+// 树组件点击
+const handleNodeChange = async (node: any) => {
+ state.queryParams.fileName = undefined;
+ state.queryParams.filePath = node.name;
+ state.queryParams.startTime = undefined;
+ state.queryParams.endTime = undefined;
+ await handleQuery();
+};
+
// 打开Pdf预览页面
const showPreviewDialog = async (row: any) => {
if (row.suffix == '.pdf') {