zuohuaijun 1 жил өмнө
parent
commit
f547fe7495

+ 1 - 1
Admin.NET/Admin.NET.Application/Configuration/Upload.json

@@ -19,7 +19,7 @@
     "Bucket": "admin.net"
   },
   "SSHProvider": {
-    "IsEnable": false, 
+    "IsEnable": false,
     "Host": "127.0.0.1",
     "Port": 8222,
     "Username": "sshuser",

+ 12 - 15
Admin.NET/Admin.NET.Core/Entity/SysFile.cs

@@ -77,32 +77,29 @@ public partial class SysFile : EntityBase
     [MaxLength(128)]
     public string? FileMd5 { get; set; }
 
-    
     /// <summary>
     /// 关联对象名称(如子对象)
     /// </summary>
-    [SugarColumn(ColumnDescription = "关联对象名称", Length = 100)]
-    [MaxLength(100)]
-    public string RelationName { get; set; }
-
+    [SugarColumn(ColumnDescription = "关联对象名称", Length = 128)]
+    [MaxLength(128)]
+    public string? RelationName { get; set; }
 
     /// <summary>
-    /// 关联对象ID
-    /// </summary> 
-    [SugarColumn(ColumnDescription = "关联对象ID" )] 
+    /// 关联对象Id
+    /// </summary>
+    [SugarColumn(ColumnDescription = "关联对象Id")]
     public long? RelationId { get; set; }
- 
 
     /// <summary>
-    /// 所属ID(如主对象)
-    /// </summary> 
-    [SugarColumn(ColumnDescription = "所属ID" )] 
+    /// 所属Id(如主对象)
+    /// </summary>
+    [SugarColumn(ColumnDescription = "所属Id")]
     public long? BelongId { get; set; }
 
     /// <summary>
     /// 文件类别
     /// </summary>
-    [SugarColumn(ColumnDescription = "文件类别", Length = 100)]
-    [MaxLength(100)]
-    public string FileType { get; set; } 
+    [SugarColumn(ColumnDescription = "文件类别", Length = 128)]
+    [MaxLength(128)]
+    public string? FileType { get; set; }
 }

+ 7 - 1
Admin.NET/Admin.NET.Core/Entity/SysUserFavorites.cs

@@ -1,9 +1,15 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
 namespace Admin.NET.Core;
 
 /// <summary>
 /// 用户菜单快捷导航表
 /// </summary>
-[SugarTable("Sys_User_Favorites", "用户菜单快捷导航表")]
+[SugarTable(null, "用户菜单快捷导航表")]
 [SysTable]
 public partial class SysUserFavorites : EntityBaseId
 {

+ 11 - 5
Admin.NET/Admin.NET.Core/Hub/OnlineUserHub.cs

@@ -64,9 +64,12 @@ public class OnlineUserHub : Hub<IOnlineUserHub>
         await _sysOnlineUerRep.InsertAsync(user);
 
         // 是否开启单用户登录
-        if (await _sysConfigService.GetConfigValue<bool>(CommonConst.SysSingleLogin)) {
+        if (await _sysConfigService.GetConfigValue<bool>(CommonConst.SysSingleLogin))
+        {
             _sysCacheService.Set(CacheConst.KeyUserOnline + user.UserId, user);
-        } else {
+        }
+        else
+        {
             var device = (client.UA.Family + client.UA.Major + client.OS.Family + client.OS.Major).Trim();
             _sysCacheService.Set(CacheConst.KeyUserOnline + user.UserId + device, user);
         }
@@ -93,7 +96,7 @@ public class OnlineUserHub : Hub<IOnlineUserHub>
     public override async Task OnDisconnectedAsync(Exception exception)
     {
         if (string.IsNullOrEmpty(Context.ConnectionId)) return;
-        
+
         var httpContext = Context.GetHttpContext();
         var client = Parser.GetDefault().Parse(httpContext.Request.Headers["User-Agent"]);
 
@@ -103,9 +106,12 @@ public class OnlineUserHub : Hub<IOnlineUserHub>
         await _sysOnlineUerRep.DeleteAsync(u => u.Id == user.Id);
 
         // 是否开启单用户登录
-        if (await _sysConfigService.GetConfigValue<bool>(CommonConst.SysSingleLogin)) {
+        if (await _sysConfigService.GetConfigValue<bool>(CommonConst.SysSingleLogin))
+        {
             _sysCacheService.Remove(CacheConst.KeyUserOnline + user.UserId);
-        } else {
+        }
+        else
+        {
             var device = (client.UA.Family + client.UA.Major + client.OS.Family + client.OS.Major).Trim();
             _sysCacheService.Remove(CacheConst.KeyUserOnline + user.UserId + device);
         }

+ 2 - 2
Admin.NET/Admin.NET.Core/Service/Config/SysConfigService.cs

@@ -200,11 +200,11 @@ public class SysConfigService : IDynamicApiController, ITransient
     }
 
     /// <summary>
-    /// 获取后配置
+    /// 获取后配置
     /// </summary>
     /// <returns></returns>
     [AllowAnonymous]
-    [DisplayName("获取后配置")]
+    [DisplayName("获取后配置")]
     public async Task<dynamic> GetBackendConfig()
     {
         return await _sysConfigRep.AsQueryable().Where(u => u.GroupCode == "Backend").Select(u => new { u.Code, u.Value }).ToListAsync();

+ 32 - 11
Admin.NET/Admin.NET.Core/Service/File/Dto/FileInput.cs

@@ -12,11 +12,12 @@ public class FileInput : BaseIdInput
     /// 文件名称
     /// </summary>
     public string FileName { get; set; }
+
     /// <summary>
     /// 文件类型
     /// </summary>
     public string FileType { get; set; }
-    
+
     /// <summary>
     /// 文件Url
     /// </summary>
@@ -44,7 +45,6 @@ public class PageFileInput : BasePageInput
 public class DeleteFileInput : BaseIdInput
 {
 }
- 
 
 public class UploadFileFromBase64Input
 {
@@ -67,11 +67,11 @@ public class UploadFileFromBase64Input
     /// 保存路径
     /// </summary>
     public string Path { get; set; }
+
     /// <summary>
     /// 文件类型
     /// </summary>
     public string FileType { get; set; }
-
 }
 
 /// <summary>
@@ -79,9 +79,20 @@ public class UploadFileFromBase64Input
 /// </summary>
 public class FileUploadInput
 {
+    /// <summary>
+    /// 文件
+    /// </summary>
     [Required]
     public IFormFile File { get; set; }
+
+    /// <summary>
+    /// 文件类型
+    /// </summary>
     public string FileType { get; set; }
+
+    /// <summary>
+    /// 文件路径
+    /// </summary>
     public string Path { get; set; }
 }
 
@@ -94,31 +105,39 @@ public class RelationQueryInput
     /// 关联对象名称
     /// </summary>
     public string RelationName { get; set; }
+
     /// <summary>
     /// 关联对象Id
     /// </summary>
     public long? RelationId { get; set; }
+
     /// <summary>
-    /// 文件,多个以“,”分割
+    /// 文件,多个以","分割
     /// </summary>
-    public string FileTypes { get; set; } 
+    public string FileTypes { get; set; }
 
     /// <summary>
-    /// 所属ID
+    /// 所属Id
     /// </summary>
-    public long? BelongId { get; set; } 
+    public long? BelongId { get; set; }
 
+    /// <summary>
+    ///
+    /// </summary>
+    /// <returns></returns>
     public string[] GetFileTypeBS()
     {
         return FileTypes.Split(',');
     }
 }
+
 public class FileOutput
 {
     /// <summary>
     /// Id
     /// </summary>
-    public long Id { get; set; } 
+    public long Id { get; set; }
+
     /// <summary>
     /// 名称
     /// </summary>
@@ -158,17 +177,19 @@ public class FileOutput
     /// 上传时间
     /// </summary>
     public DateTime? CreateTime { get; set; }
+
     /// <summary>
     /// 关联对象名称
     /// </summary>
     public string RelationName { get; set; }
+
     /// <summary>
     /// 关联对象Id
     /// </summary>
-    public long? RelationId { get; set; } 
+    public long? RelationId { get; set; }
+
     /// <summary>
-    /// 所属ID
+    /// 所属Id
     /// </summary>
     public long? BelongId { get; set; }
-
 }

+ 38 - 48
Admin.NET/Admin.NET.Core/Service/File/SysFileService.cs

@@ -6,9 +6,7 @@
 
 using Aliyun.OSS.Util;
 using Furion.VirtualFileServer;
-using Microsoft.AspNetCore.Components.Forms;
 using OnceMi.AspNetCore.OSS;
-using System.IO;
 
 namespace Admin.NET.Core.Service;
 
@@ -58,11 +56,10 @@ public class SysFileService : IDynamicApiController, ITransient
     /// <summary>
     /// 上传文件 🔖
     /// </summary>
-    /// <param name="input"></param> 
+    /// <param name="input"></param>
     /// <returns></returns>
     [DisplayName("上传文件")]
-    [HttpPost]
-    public async Task<SysFile> UploadFile([FromForm]FileUploadInput input)
+    public async Task<SysFile> UploadFile([FromForm] FileUploadInput input)
     {
         return await HandleUploadFile(input.File, input.Path, filetype: input.FileType);
     }
@@ -76,7 +73,7 @@ public class SysFileService : IDynamicApiController, ITransient
     /// <param name="path"></param>
     /// <param name="fileType"></param>
     /// <returns></returns>
-    private async Task<SysFile> UploadFileFromBase64(string strBase64, string fileName, string contentType, string? path,string? fileType)
+    private async Task<SysFile> UploadFileFromBase64(string strBase64, string fileName, string contentType, string? path, string? fileType)
     {
         byte[] fileData = Convert.FromBase64String(strBase64);
         var ms = new MemoryStream();
@@ -91,7 +88,7 @@ public class SysFileService : IDynamicApiController, ITransient
             Headers = new HeaderDictionary(),
             ContentType = contentType
         };
-        return await UploadFile(new FileUploadInput {File= formFile, Path= path,FileType= fileType });
+        return await UploadFile(new FileUploadInput { File = formFile, Path = path, FileType = fileType });
     }
 
     /// <summary>
@@ -100,10 +97,9 @@ public class SysFileService : IDynamicApiController, ITransient
     /// <param name="input"></param>
     /// <returns></returns>
     [DisplayName("上传文件Base64")]
-    [HttpPost]
     public async Task<SysFile> UploadFileFromBase64(UploadFileFromBase64Input input)
     {
-        return await UploadFileFromBase64(input.FileDataBase64, input.FileName, input.ContentType, input.Path,input.FileType);
+        return await UploadFileFromBase64(input.FileDataBase64, input.FileName, input.ContentType, input.Path, input.FileType);
     }
 
     /// <summary>
@@ -117,7 +113,7 @@ public class SysFileService : IDynamicApiController, ITransient
         var filelist = new List<SysFile>();
         foreach (var file in files)
         {
-            filelist.Add(await UploadFile(new FileUploadInput {File=file}));
+            filelist.Add(await UploadFile(new FileUploadInput { File = file }));
         }
         return filelist;
     }
@@ -133,7 +129,7 @@ public class SysFileService : IDynamicApiController, ITransient
         var file = input.Id > 0 ? await GetFile(input) : await _sysFileRep.GetFirstAsync(u => u.Url == input.Url);
         var fileName = HttpUtility.UrlEncode(file.FileName, Encoding.GetEncoding("UTF-8"));
 
-       if (_OSSProviderOptions.IsEnable)
+        if (_OSSProviderOptions.IsEnable)
         {
             var filePath = string.Concat(file.FilePath, "/", file.Id.ToString() + file.Suffix);
             var stream = await (await _OSSService.PresignedGetObjectAsync(file.BucketName.ToString(), filePath, 5)).GetAsStreamAsync();
@@ -141,32 +137,37 @@ public class SysFileService : IDynamicApiController, ITransient
         }
         else if (App.Configuration["SSHProvider:IsEnable"].ToBoolean())
         {
-            var fullPath = string.Concat(file.FilePath, "/", file.Id+ file.Suffix);
+            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"]))
-            { 
+            {
                 return new FileStreamResult(helper.OpenRead(fullPath), "application/octet-stream") { FileDownloadName = fileName + file.Suffix };
             }
         }
-        else 
+        else
         {
             var filePath = Path.Combine(file.FilePath, file.Id.ToString() + file.Suffix);
             var path = Path.Combine(App.WebHostEnvironment.WebRootPath, filePath);
             return new FileStreamResult(new FileStream(path, FileMode.Open), "application/octet-stream") { FileDownloadName = fileName + file.Suffix };
         }
     }
+
+    /// <summary>
+    /// 文件预览
+    /// </summary>
+    /// <param name="Id"></param>
+    /// <returns></returns>
     [AllowAnonymous]
-    [HttpGet("/api/sysFile/Preview/{id}")]
-    public async Task<IActionResult> Preview([FromRoute] long Id)
+    public async Task<IActionResult> GetPreview([FromRoute] long Id)
     {
         var file = await GetFile(new FileInput { Id = Id });
-        var fileName = HttpUtility.UrlEncode(file.FileName, Encoding.GetEncoding("UTF-8"));
+        //var fileName = HttpUtility.UrlEncode(file.FileName, Encoding.GetEncoding("UTF-8"));
 
         if (_OSSProviderOptions.IsEnable)
         {
             var filePath = string.Concat(file.FilePath, "/", file.Id.ToString() + file.Suffix);
             var stream = await (await _OSSService.PresignedGetObjectAsync(file.BucketName.ToString(), filePath, 5)).GetAsStreamAsync();
-            return new FileStreamResult(stream.Stream, "application/octet-stream") ;
+            return new FileStreamResult(stream.Stream, "application/octet-stream");
         }
         else if (App.Configuration["SSHProvider:IsEnable"].ToBoolean())
         {
@@ -174,14 +175,14 @@ public class SysFileService : IDynamicApiController, ITransient
             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(fullPath), "application/octet-stream") ;
+                return new FileStreamResult(helper.OpenRead(fullPath), "application/octet-stream");
             }
         }
         else
         {
             var filePath = Path.Combine(file.FilePath, file.Id.ToString() + file.Suffix);
             var path = Path.Combine(App.WebHostEnvironment.WebRootPath, filePath);
-            return new FileStreamResult(new FileStream(path, FileMode.Open), "application/octet-stream") ;
+            return new FileStreamResult(new FileStream(path, FileMode.Open), "application/octet-stream");
         }
     }
 
@@ -193,7 +194,7 @@ public class SysFileService : IDynamicApiController, ITransient
     [AllowAnonymous]
     [DisplayName("下载指定文件Base64格式")]
     public async Task<string> DownloadFileBase64([FromBody] string url)
-    { 
+    {
         if (_OSSProviderOptions.IsEnable)
         {
             using var httpClient = new HttpClient();
@@ -214,11 +215,11 @@ public class SysFileService : IDynamicApiController, ITransient
             var sysFile = await _sysFileRep.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 
+        else
         {
             var sysFile = await _sysFileRep.GetFirstAsync(u => u.Url == url) ?? throw Oops.Oh($"文件不存在");
             var filePath = Path.Combine(App.WebHostEnvironment.WebRootPath, sysFile.FilePath);
@@ -281,7 +282,7 @@ public class SysFileService : IDynamicApiController, ITransient
         var isExist = await _sysFileRep.IsAnyAsync(u => u.Id == input.Id);
         if (!isExist) throw Oops.Oh(ErrorCodeEnum.D8000);
 
-        await _sysFileRep.UpdateAsync(u => new SysFile() { FileName = input.FileName,FileType=input.FileType }, u => u.Id == input.Id);
+        await _sysFileRep.UpdateAsync(u => new SysFile() { FileName = input.FileName, FileType = input.FileType }, u => u.Id == input.Id);
     }
 
     /// <summary>
@@ -302,7 +303,7 @@ public class SysFileService : IDynamicApiController, ITransient
     /// <param name="savePath">路径</param>
     /// <param name="allowSuffix">允许格式:.jpg.png.gif.tif.bmp</param>
     /// <returns></returns>
-    private async Task<SysFile> HandleUploadFile(IFormFile file, string savePath, string allowSuffix = "",string filetype = "")
+    private async Task<SysFile> HandleUploadFile(IFormFile file, string savePath, string allowSuffix = "", string filetype = "")
     {
         if (file == null) throw Oops.Oh(ErrorCodeEnum.D8000);
 
@@ -365,11 +366,11 @@ public class SysFileService : IDynamicApiController, ITransient
             SizeKb = sizeKb.ToString(),
             FilePath = path,
             FileMd5 = fileMd5,
-            FileType= filetype
+            FileType = filetype
         };
 
         var finalName = newFile.Id + suffix; // 文件最终名称
-       if (_OSSProviderOptions.IsEnable)
+        if (_OSSProviderOptions.IsEnable)
         {
             newFile.Provider = Enum.GetName(_OSSProviderOptions.Provider);
             var filePath = string.Concat(path, "/", finalName);
@@ -392,14 +393,14 @@ public class SysFileService : IDynamicApiController, ITransient
         }
         else if (App.Configuration["SSHProvider:IsEnable"].ToBoolean())
         {
-            var fullPath = string.Concat( path.StartsWith("/")?path:"/"+path, "/", finalName);
+            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"])) {
-
+               App.Configuration["SSHProvider:Port"].ToInt(), App.Configuration["SSHProvider:Username"], App.Configuration["SSHProvider:Password"]))
+            {
                 helper.UploadFile(file.OpenReadStream(), fullPath);
             }
         }
-        else 
+        else
         {
             newFile.Provider = ""; // 本地存储 Provider 显示为空
             var filePath = Path.Combine(App.WebHostEnvironment.WebRootPath, path);
@@ -407,25 +408,14 @@ public class SysFileService : IDynamicApiController, ITransient
                 Directory.CreateDirectory(filePath);
 
             var realFile = Path.Combine(filePath, finalName);
-            //IDetector detector;
             using (var stream = File.Create(realFile))
             {
                 await file.CopyToAsync(stream);
-                //detector = stream.DetectFiletype();
             }
-            //var realExt = detector.Extension; // 真实扩展名
-            //// 二次校验扩展名
-            //if (!string.Equals(realExt, suffix.Replace(".", ""), StringComparison.OrdinalIgnoreCase))
-            //{
-            //    var delFilePath = Path.Combine(App.WebHostEnvironment.WebRootPath, realFile);
-            //    if (File.Exists(delFilePath))
-            //        File.Delete(delFilePath);
-            //    throw Oops.Oh(ErrorCodeEnum.D8001);
-            //}
 
             // 生成外链
             var host = CommonUtil.GetLocalhost();
-            if (!host.EndsWith("/"))
+            if (!host.EndsWith('/'))
                 host += "/";
             newFile.Url = $"{host}{newFile.FilePath}/{newFile.Id + newFile.Suffix}";
         }
@@ -495,12 +485,12 @@ public class SysFileService : IDynamicApiController, ITransient
     /// <param name="relationName"></param>
     /// <param name="relationId"></param>
     /// <param name="belongId"></param>
-    /// <returns></returns>  
+    /// <returns></returns>
     [NonAction]
     public async Task<int> UpdateRelation(List<long> ids, string relationName, long relationId, long belongId = 0)
     {
         if (ids == null || ids.Count == 0)
-            return 0; 
+            return 0;
         return await _sysFileRep.AsUpdateable()
               .SetColumns(m => m.RelationName == relationName)
               .SetColumns(m => m.RelationId == relationId)
@@ -512,16 +502,16 @@ public class SysFileService : IDynamicApiController, ITransient
     /// <summary>
     /// 根据关联查询附件
     /// </summary>
-    /// <param name="input"></param> 
+    /// <param name="input"></param>
     /// <returns></returns>
-    /// <exception cref="ArgumentNullException"></exception>  
+    /// <exception cref="ArgumentNullException"></exception>
     public async Task<List<FileOutput>> GetRelationFiles(RelationQueryInput input)
     {
         return await _sysFileRep.AsQueryable()
            .Where(m => !m.IsDelete)
            .WhereIF(input.RelationId.HasValue && input.RelationId > 0, m => m.RelationId == input.RelationId)
            .WhereIF(input.BelongId.HasValue && input.BelongId > 0, m => m.BelongId == input.BelongId.Value)
-           .WhereIF(!string.IsNullOrWhiteSpace(input.RelationName), m => m.RelationName == input.RelationName) 
+           .WhereIF(!string.IsNullOrWhiteSpace(input.RelationName), m => m.RelationName == input.RelationName)
            .WhereIF(!string.IsNullOrWhiteSpace(input.FileTypes), m => input.GetFileTypeBS().Contains(m.FileType))
             .Select(m => new FileOutput
             {

+ 6 - 0
Admin.NET/Admin.NET.Core/Service/User/Dto/UserFavoritesInput.cs

@@ -1,3 +1,9 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
 namespace Admin.NET.Core.Service;
 
 /// <summary>

+ 13 - 10
Admin.NET/Admin.NET.Core/Service/User/SysUserFavoritesService.cs

@@ -1,23 +1,26 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
 namespace Admin.NET.Core.Service;
 
 /// <summary>
-/// 用户菜单快捷导航服务
+/// 用户菜单快捷导航服务 🧩
 /// </summary>
 [ApiDescriptionSettings(Order = 500)]
 public class SysUserFavoritesService : IDynamicApiController, ITransient
 {
     private readonly SqlSugarRepository<SysUserFavorites> _sysUserFavoritesRep;
-    private readonly SysCacheService _sysCacheService;
 
-    public SysUserFavoritesService(SqlSugarRepository<SysUserFavorites> sysUserFavoritesRep,
-        SysCacheService sysCacheService)
+    public SysUserFavoritesService(SqlSugarRepository<SysUserFavorites> sysUserFavoritesRep)
     {
         _sysUserFavoritesRep = sysUserFavoritesRep;
-        _sysCacheService = sysCacheService;
     }
 
     /// <summary>
-    /// 收藏菜单
+    /// 收藏菜单 🔖
     /// </summary>
     /// <param name="input"></param>
     /// <returns></returns>
@@ -36,7 +39,7 @@ public class SysUserFavoritesService : IDynamicApiController, ITransient
     }
 
     /// <summary>
-    /// 取消收藏菜单
+    /// 取消收藏菜单 🔖
     /// </summary>
     /// <param name="input"></param>
     /// <returns></returns>
@@ -46,7 +49,7 @@ public class SysUserFavoritesService : IDynamicApiController, ITransient
     }
 
     /// <summary>
-    /// 根据用户Id删除收藏菜单
+    /// 根据用户Id删除收藏菜单 🔖
     /// </summary>
     /// <param name="userId"></param>
     /// <returns></returns>
@@ -56,7 +59,7 @@ public class SysUserFavoritesService : IDynamicApiController, ITransient
     }
 
     /// <summary>
-    /// 根据用户Id获取收藏菜单集合
+    /// 根据用户Id获取收藏菜单集合 🔖
     /// </summary>
     /// <param name="userId"></param>
     /// <returns></returns>
@@ -69,7 +72,7 @@ public class SysUserFavoritesService : IDynamicApiController, ITransient
     }
 
     /// <summary>
-    /// 根据用户Id获取收藏菜单Id集合
+    /// 根据用户Id获取收藏菜单Id集合 🔖
     /// </summary>
     /// <param name="userId"></param>
     /// <returns></returns>

+ 71 - 58
Admin.NET/Admin.NET.Core/Util/SSHHelper.cs

@@ -1,44 +1,57 @@
-
-using Microsoft.Extensions.Logging;
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
 using Renci.SshNet;
 
 namespace Admin.NET.Core
 {
     /// <summary>
-    /// SSH / Sftp Helper
+    /// SSH/Sftp 工具类
     /// </summary>
     public class SSHHelper : IDisposable
     {
-        private SftpClient sftp;
+        private readonly SftpClient _sftp;
+
         public SSHHelper(string host, int port, string user, string password)
         {
-            sftp = new SftpClient(host, port, user, password);
+            _sftp = new SftpClient(host, port, user, password);
         }
-        public bool Exists(string ftpFileName)
-        {
-            Connect();
-            return sftp.Exists(ftpFileName);
 
+        /// <summary>
+        /// 连接
+        /// </summary>
+        private void Connect()
+        {
+            if (!_sftp.IsConnected)
+                _sftp.Connect();
         }
 
-        private void Connect()
+        /// <summary>
+        /// 是否存在同名文件
+        /// </summary>
+        /// <param name="ftpFileName"></param>
+        /// <returns></returns>
+        public bool Exists(string ftpFileName)
         {
-            if (!sftp.IsConnected)
-            {
-                sftp.Connect();
-            }
+            Connect();
+
+            return _sftp.Exists(ftpFileName);
         }
 
         /// <summary>
-        /// 删除
+        /// 删除文件
         /// </summary>
         /// <param name="ftpFileName"></param>
         public void DeleteFile(string ftpFileName)
-        { 
+        {
             Connect();
-            sftp.DeleteFile(ftpFileName);
 
+            _sftp.DeleteFile(ftpFileName);
         }
+
         /// <summary>
         /// 下载到指定目录
         /// </summary>
@@ -47,9 +60,10 @@ namespace Admin.NET.Core
         public void DownloadFile(string ftpFileName, string localFileName)
         {
             Connect();
+
             using (Stream fileStream = File.OpenWrite(localFileName))
             {
-                sftp.DownloadFile(ftpFileName, fileStream);
+                _sftp.DownloadFile(ftpFileName, fileStream);
             }
         }
 
@@ -61,8 +75,10 @@ namespace Admin.NET.Core
         public byte[] ReadAllBytes(string ftpFileName)
         {
             Connect();
-            return sftp.ReadAllBytes(ftpFileName);
+
+            return _sftp.ReadAllBytes(ftpFileName);
         }
+
         /// <summary>
         /// 读取流
         /// </summary>
@@ -70,7 +86,7 @@ namespace Admin.NET.Core
         /// <returns></returns>
         public Stream OpenRead(string path)
         {
-            return sftp.Open(path, FileMode.Open, FileAccess.Read);
+            return _sftp.Open(path, FileMode.Open, FileAccess.Read);
         }
 
         /// <summary>
@@ -82,6 +98,7 @@ namespace Admin.NET.Core
         {
             DownloadFile(ftpFileName, localFileName);
         }
+
         /// <summary>
         /// 重命名
         /// </summary>
@@ -89,7 +106,7 @@ namespace Admin.NET.Core
         /// <param name="newPath"></param>
         public void RenameFile(string oldPath, string newPath)
         {
-            sftp.RenameFile(oldPath, newPath);
+            _sftp.RenameFile(oldPath, newPath);
         }
 
         /// <summary>
@@ -100,21 +117,18 @@ namespace Admin.NET.Core
         /// <returns></returns>
         public List<string> GetFileList(string folder, IEnumerable<string> filters)
         {
-            var files = new List<string>();
             Connect();
 
-            var sftpFiles = sftp.ListDirectory(folder);
-
+            var files = new List<string>();
+            var sftpFiles = _sftp.ListDirectory(folder);
             foreach (var file in sftpFiles)
             {
                 if (file.IsRegularFile && filters.Any(f => file.Name.EndsWith(f)))
-                {
                     files.Add(file.Name);
-                }
             }
-
             return files;
         }
+
         /// <summary>
         /// 上传指定目录文件
         /// </summary>
@@ -123,13 +137,15 @@ namespace Admin.NET.Core
         public void UploadFile(string localFileName, string ftpFileName)
         {
             Connect();
+
             var dir = Path.GetDirectoryName(ftpFileName);
-            CreateDir(sftp, dir);
+            CreateDir(_sftp, dir);
             using (var fileStream = new FileStream(localFileName, FileMode.Open))
             {
-                sftp.UploadFile(fileStream, ftpFileName);
+                _sftp.UploadFile(fileStream, ftpFileName);
             }
         }
+
         /// <summary>
         /// 上传字节
         /// </summary>
@@ -137,12 +153,11 @@ namespace Admin.NET.Core
         /// <param name="ftpFileName"></param>
         public void UploadFile(byte[] bs, string ftpFileName)
         {
-
             Connect();
-            var dir = Path.GetDirectoryName(ftpFileName);
-            CreateDir(sftp, dir);
 
-            sftp.WriteAllBytes(ftpFileName, bs);
+            var dir = Path.GetDirectoryName(ftpFileName);
+            CreateDir(_sftp, dir);
+            _sftp.WriteAllBytes(ftpFileName, bs);
         }
 
         /// <summary>
@@ -153,12 +168,13 @@ namespace Admin.NET.Core
         public void UploadFile(Stream fileStream, string ftpFileName)
         {
             Connect();
+
             var dir = Path.GetDirectoryName(ftpFileName);
-            CreateDir(sftp, dir);
-            sftp.UploadFile(fileStream, ftpFileName);
+            CreateDir(_sftp, dir);
+            _sftp.UploadFile(fileStream, ftpFileName);
             fileStream.Dispose();
-
         }
+
         /// <summary>
         /// 创建目录
         /// </summary>
@@ -167,33 +183,30 @@ namespace Admin.NET.Core
         /// <exception cref="ArgumentNullException"></exception>
         private void CreateDir(SftpClient sftp, string dir)
         {
-            if (dir is null)
-            {
-                throw new ArgumentNullException(nameof(dir));
-            }
-            if (!sftp.Exists(dir))
+            ArgumentNullException.ThrowIfNull(dir);
+
+            if (sftp.Exists(dir)) return;
+
+            var index = dir.LastIndexOfAny(['/', '\\']);
+            if (index > 0)
             {
-                var index = dir.LastIndexOfAny(new char[] { '/', '\\' });
-                if (index > 0)
-                {
-                    var p = dir.Substring(0, index);
-                    if (!sftp.Exists(p))
-                    {
-                        CreateDir(sftp, p);
-                    }
-                    sftp.CreateDirectory(dir);
-                }
+                var p = dir[..index];
+                if (!sftp.Exists(p))
+                    CreateDir(sftp, p);
+                sftp.CreateDirectory(dir);
             }
         }
 
+        /// <summary>
+        /// 释放对象
+        /// </summary>
         public void Dispose()
         {
-            if (sftp != null)
-            {
-                if (sftp.IsConnected)
-                    sftp.Disconnect();
-                sftp.Dispose();
-            }
+            if (_sftp == null) return;
+
+            if (_sftp.IsConnected)
+                _sftp.Disconnect();
+            _sftp.Dispose();
         }
     }
-}
+}

+ 5 - 7
Web/src/components/table/search.vue

@@ -96,10 +96,8 @@
 					</el-form-item>
 				</el-col>
 			</el-row>
-           <el-divider style="margin-top: 5px;" v-if="search.length > 3">
-				<el-button :icon="state.isToggle ? 'ele-ArrowUpBold' : 'ele-ArrowDownBold'" class="divider-btn"
-					@click="state.isToggle = !state.isToggle">
-				</el-button>
+			<el-divider style="margin-top: 5px" v-if="search.length > 3">
+				<el-button :icon="state.isToggle ? 'ele-ArrowUpBold' : 'ele-ArrowDownBold'" class="divider-btn" @click="state.isToggle = !state.isToggle"> </el-button>
 			</el-divider>
 		</el-form>
 	</div>
@@ -218,8 +216,8 @@ const shortcuts = [
 	}
 }
 
-.divider-btn{
-   height: 20px;
-   border-radius: 10px;
+.divider-btn {
+	height: 20px;
+	border-radius: 10px;
 }
 </style>

+ 9 - 17
Web/src/stores/themeConfig.ts

@@ -2,14 +2,6 @@ import { defineStore } from 'pinia';
 import { SysConfigApi } from '/@/api-services';
 import { getAPI } from '/@/utils/axios-utils';
 
-/**
- * 布局配置
- * 修复:https://gitee.com/lyt-top/vue-next-admin/issues/I567R1,感谢@lanbao123
- * 2020.05.28 by lyt 优化。开发时配置不生效问题
- * 修改配置时:
- * 1、需要每次都清理 `window.localStorage` 浏览器永久缓存
- * 2、或者点击布局配置最底部 `一键恢复默认` 按钮即可看到效果
- */
 export const useThemeConfig = defineStore('themeConfig', {
 	state: (): ThemeConfigState => ({
 		themeConfig: {
@@ -145,7 +137,7 @@ export const useThemeConfig = defineStore('themeConfig', {
 			// 网站副标题(登录页顶部文字)
 			globalViceTitleMsg: '站在巨人肩膀上的 .NET 通用权限开发框架',
 			// 版权和备案文字
-			copyright: 'Copyright © 2021-2014 Admin.NET All rights reserved.',
+			copyright: 'Copyright © 2021-present Admin.NET All rights reserved.',
 			// 默认初始语言,可选值"<zh-cn|en|zh-tw>",默认 zh-cn
 			globalI18n: 'zh-cn',
 			// 默认全局组件大小,可选值"<large|'default'|small>",默认 'large'
@@ -158,27 +150,27 @@ export const useThemeConfig = defineStore('themeConfig', {
 		},
 
 		async setWebConfig() {
-		    var res = await getAPI(SysConfigApi).apiSysConfigWebConfigGet();
+			var res = await getAPI(SysConfigApi).apiSysConfigWebConfigGet();
 			var webConfig = res.data.result;
 			for (let index = 0; index < webConfig.length; index++) {
 				const element = webConfig[index];
-				if (element.code == "title") {
+				if (element.code == 'title') {
 					document.title = element.value;
 					this.themeConfig.globalTitle = element.value;
 				}
-				if (element.code == "watermark_text") {
+				if (element.code == 'watermark_text') {
 					this.themeConfig.globalTitle = element.value;
 				}
-				if (element.code == "vice_title") {
+				if (element.code == 'vice_title') {
 					this.themeConfig.globalViceTitle = element.value;
 				}
-				if (element.code == "vice_title_msg") {
+				if (element.code == 'vice_title_msg') {
 					this.themeConfig.globalViceTitleMsg = element.value;
 				}
-				if (element.code == "web_copyright") {
-					this.themeConfig.copyright = 'Copyright © ' + element.value + ' ' + this.themeConfig.globalTitle + ' All rights reserved.'
+				if (element.code == 'web_copyright') {
+					this.themeConfig.copyright = 'Copyright © ' + element.value + ' ' + this.themeConfig.globalTitle + ' All rights reserved.';
 				}
 			}
-		}
+		},
 	},
 });

+ 6 - 5
Web/src/utils/arrayOperation.ts

@@ -63,15 +63,16 @@ export function removeDuplicate(arr: EmptyArrayType, attr?: string) {
 		}
 	}
 }
+
 /* 数组、对象深拷贝
  * @param value 需要拷贝内容
  * @returns
  */
 export const clone = <T>(value: T): T => {
-	if (!value) return value
+	if (!value) return value;
 
 	// 数组
-	if (Array.isArray(value)) return value.map((item) => clone(item)) as unknown as T
+	if (Array.isArray(value)) return value.map((item) => clone(item)) as unknown as T;
 
 	// 普通对象
 	if (typeof value === 'object') {
@@ -79,8 +80,8 @@ export const clone = <T>(value: T): T => {
 			Object.entries(value).map(([k, v]: [string, any]) => {
 				return [k, clone(v)];
 			})
-		) as unknown as T
+		) as unknown as T;
 	}
 	// 基本类型
-	return value
-}
+	return value;
+};

+ 2 - 2
Web/src/utils/axios-utils.ts

@@ -150,7 +150,7 @@ axiosInstance.interceptors.response.use(
 			}
 			// 用户自定义处理异常
 			if (!res.config?.customCatch) {
-			    ElMessage.error(message);
+				ElMessage.error(message);
 			}
 			throw new Error(message);
 		}
@@ -167,7 +167,7 @@ axiosInstance.interceptors.response.use(
 
 		// 用户自定义处理异常
 		if (!error.config?.customCatch) {
-		    // 响应错误代码及自定义处理
+			// 响应错误代码及自定义处理
 			ElMessage.error(error);
 		}
 

+ 45 - 45
Web/src/views/home/widgets/components/myapp.vue

@@ -1,50 +1,50 @@
 <template>
-  <el-card shadow="hover" header="快捷入口">
-    <ul class="myMods">
-      <li v-for="mod in myMods" :key="mod.path!">
-        <router-link :to="{ path: mod.path! }">
-          <SvgIcon :name="mod.meta?.icon" style="font-size: 18px" />
-          <p>{{ mod.meta?.title }}</p>
-        </router-link>
-      </li>
-      <li class="modItem-add" @click="addMods">
-        <a>
-          <el-icon><ele-Plus :style="{ color: '#fff' }" /></el-icon>
-        </a>
-      </li>
-    </ul>
+	<el-card shadow="hover" header="快捷入口">
+		<ul class="myMods">
+			<li v-for="mod in myMods" :key="mod.path!">
+				<router-link :to="{ path: mod.path! }">
+					<SvgIcon :name="mod.meta?.icon" style="font-size: 18px" />
+					<p>{{ mod.meta?.title }}</p>
+				</router-link>
+			</li>
+			<li class="modItem-add" @click="addMods">
+				<a>
+					<el-icon><ele-Plus :style="{ color: '#fff' }" /></el-icon>
+				</a>
+			</li>
+		</ul>
 
-    <el-drawer title="添加应用" v-model="modsDrawer" :size="520" destroy-on-close :before-close="beforeClose">
-      <div class="setMods mt15">
-        <h4>我的常用 ( {{ myMods.length }} )</h4>
-        <draggable tag="ul" v-model="myMods" animation="200" item-key="id" group="app" class="draggable-box" force-fallback fallback-on-body>
-          <template #item="{ element }">
-            <li>
-              <SvgIcon :name="element.meta.icon" style="font-size: 18px" />
-              <p>{{ element.meta.title }}</p>
-            </li>
-          </template>
-        </draggable>
-      </div>
-      <div class="setMods">
-        <h4>全部应用 ( {{ filterMods.length }} )</h4>
-        <draggable tag="ul" v-model="filterMods" animation="200" item-key="id" group="app" class="draggable-box-all" force-fallback fallback-on-body>
-          <template #item="{ element }">
-            <li :style="{ background: element.meta.color || '#909399' }">
-              <SvgIcon :name="element.meta.icon" style="font-size: 18px" />
-              <p>{{ element.meta.title }}</p>
-            </li>
-          </template>
-        </draggable>
-      </div>
-      <template #footer>
-        <div style="margin: 0 20px 20px 0">
-          <el-button @click="beforeClose">取消</el-button>
-          <el-button type="primary" @click="saveMods">保存</el-button>
-        </div>
-      </template>
-    </el-drawer>
-  </el-card>
+		<el-drawer title="添加应用" v-model="modsDrawer" :size="520" destroy-on-close :before-close="beforeClose">
+			<div class="setMods mt15">
+				<h4>我的常用 ( {{ myMods.length }} )</h4>
+				<draggable tag="ul" v-model="myMods" animation="200" item-key="id" group="app" class="draggable-box" force-fallback fallback-on-body>
+					<template #item="{ element }">
+						<li>
+							<SvgIcon :name="element.meta.icon" style="font-size: 18px" />
+							<p>{{ element.meta.title }}</p>
+						</li>
+					</template>
+				</draggable>
+			</div>
+			<div class="setMods">
+				<h4>全部应用 ( {{ filterMods.length }} )</h4>
+				<draggable tag="ul" v-model="filterMods" animation="200" item-key="id" group="app" class="draggable-box-all" force-fallback fallback-on-body>
+					<template #item="{ element }">
+						<li :style="{ background: element.meta.color || '#909399' }">
+							<SvgIcon :name="element.meta.icon" style="font-size: 18px" />
+							<p>{{ element.meta.title }}</p>
+						</li>
+					</template>
+				</draggable>
+			</div>
+			<template #footer>
+				<div style="margin: 0 20px 20px 0">
+					<el-button @click="beforeClose">取消</el-button>
+					<el-button type="primary" @click="saveMods">保存</el-button>
+				</div>
+			</template>
+		</el-drawer>
+	</el-card>
 </template>
 
 <script lang="ts">

+ 13 - 13
Web/src/views/home/widgets/index.vue

@@ -86,7 +86,7 @@
 							</el-row>
 						</div>
 						<div class="selectLayout-item item02" :class="{ active: grid.layout.join(',') === '24,6,12,6' }" @click="setLayout([24, 6, 12, 6])">
-							<el-row :gutter="2"> 
+							<el-row :gutter="2">
 								<el-col :span="24"><span></span></el-col>
 								<el-col :span="6"><span></span></el-col>
 								<el-col :span="12"><span></span></el-col>
@@ -94,7 +94,7 @@
 							</el-row>
 						</div>
 						<div class="selectLayout-item item05" :class="{ active: grid.layout.join(',') === '24,6,12,6,24' }" @click="setLayout([24, 6, 12, 6, 24])">
-							<el-row :gutter="2"> 
+							<el-row :gutter="2">
 								<el-col :span="24"><span></span></el-col>
 								<el-col :span="6"><span></span></el-col>
 								<el-col :span="12"><span></span></el-col>
@@ -142,10 +142,10 @@ interface Grid {
 	layout: number[];
 	copmsList: string[][];
 }
-const defaultGrid = ({
+const defaultGrid = {
 	layout: [12, 6, 6],
 	copmsList: [['welcome'], ['about', 'ver'], ['timeing', 'progressing']],
-});
+};
 
 const customizing = ref<boolean>(false);
 const allCompsList = ref(allComps);
@@ -200,15 +200,15 @@ const custom = () => {
 const setLayout = (layout: number[]) => {
 	grid.value.layout = layout;
 	if (grid.value.layout.length < grid.value.copmsList.length) {
-    for (let i = grid.value.layout.length; i < grid.value.copmsList.length; i++) {
-      grid.value.copmsList[grid.value.layout.length - 1] = grid.value.copmsList[grid.value.layout.length - 1].concat(grid.value.copmsList[i])
-    }
-    grid.value.copmsList = grid.value.copmsList.slice(0, grid.value.layout.length)
-  } else if (grid.value.layout.length > grid.value.copmsList.length) {
-    for (let i = grid.value.copmsList.length; i < grid.value.layout.length; i++) {
-      grid.value.copmsList[i] = []
-    }
-  }
+		for (let i = grid.value.layout.length; i < grid.value.copmsList.length; i++) {
+			grid.value.copmsList[grid.value.layout.length - 1] = grid.value.copmsList[grid.value.layout.length - 1].concat(grid.value.copmsList[i]);
+		}
+		grid.value.copmsList = grid.value.copmsList.slice(0, grid.value.layout.length);
+	} else if (grid.value.layout.length > grid.value.copmsList.length) {
+		for (let i = grid.value.copmsList.length; i < grid.value.layout.length; i++) {
+			grid.value.copmsList[i] = [];
+		}
+	}
 };
 
 // 追加

+ 8 - 8
Web/src/views/system/file/index.vue

@@ -29,7 +29,7 @@
 				<el-table-column prop="fileType" label="文件类型" min-width="150" header-align="center" show-overflow-tooltip />
 				<el-table-column type="relationName" label="关联对象名称" align="center" />
 				<el-table-column type="relationtId" label="关联对象ID" align="center" />
-				<el-table-column type="belongId" label="所属ID" align="center" /> 
+				<el-table-column type="belongId" label="所属ID" align="center" />
 				<el-table-column prop="fileName" label="名称" min-width="150" header-align="center" show-overflow-tooltip />
 				<el-table-column prop="suffix" label="后缀" align="center" show-overflow-tooltip>
 					<template #default="scope">
@@ -91,12 +91,12 @@
 				</div>
 			</template>
 			<div>
-				<div> 
+				<div>
 					<el-select v-model="state.fileType" placeholder="请选择文件类型">
-						 <el-option label="相关文件" value="相关文件" />
-						  <el-option label="归档文件" value="归档文件" />
-						 </el-select>
-				 </div>
+						<el-option label="相关文件" value="相关文件" />
+						<el-option label="归档文件" value="归档文件" />
+					</el-select>
+				</div>
 				<el-upload ref="uploadRef" drag :auto-upload="false" :limit="1" :file-list="state.fileList" action="" :on-change="handleChange" accept=".jpg,.png,.bmp,.gif,.txt,.pdf,.xlsx,.docx">
 					<el-icon class="el-icon--upload">
 						<ele-UploadFilled />
@@ -173,7 +173,7 @@ const state = reactive({
 	excelUrl: '',
 	pdfUrl: '',
 	fileName: '',
-	fileType:'',
+	fileType: '',
 	previewList: [] as string[],
 });
 
@@ -216,7 +216,7 @@ const handleChange = (file: any, fileList: []) => {
 // 上传
 const uploadFile = async () => {
 	if (state.fileList.length < 1) return;
-	await getAPI(SysFileApi).apiSysFileUploadFilePostForm(state.fileList[0].raw,undefined,state.fileType);
+	await getAPI(SysFileApi).apiSysFileUploadFilePostForm(state.fileList[0].raw, undefined, state.fileType);
 	handleQuery();
 	ElMessage.success('上传成功');
 	state.dialogUploadVisible = false;