فهرست منبع

优化获取服务器信息及显示

zuohuaijun 3 سال پیش
والد
کامیت
b1e84b7ee8

+ 205 - 57
Admin.NET/Admin.NET.Core/Admin.NET.Core.xml

@@ -30,6 +30,23 @@
             SqlSugar二级缓存(必须是内存缓存)
             </summary>
         </member>
+        <member name="M:Admin.NET.Core.LazyCaptchaSetup.AddLazyCaptcha(Microsoft.Extensions.DependencyInjection.IServiceCollection)">
+            <summary>
+            验证码初始化
+            </summary>
+            <param name="services"></param>
+        </member>
+        <member name="T:Admin.NET.Core.RandomCaptcha">
+            <summary>
+            随机验证码
+            </summary>
+        </member>
+        <member name="M:Admin.NET.Core.RandomCaptcha.ChangeOptions(Lazy.Captcha.Core.CaptchaOptions)">
+            <summary>
+            更新选项
+            </summary>
+            <param name="options"></param>
+        </member>
         <member name="T:Admin.NET.Core.CacheConst">
             <summary>
             缓存相关常量
@@ -2940,6 +2957,51 @@
             <param name="pi"></param>
             <returns></returns>
         </member>
+        <member name="M:Admin.NET.Core.ObjectExtension.ParseToLong(System.Object)">
+            <summary>
+            将object转换为long,若失败则返回0
+            </summary>
+            <param name="obj"></param>
+            <returns></returns>
+        </member>
+        <member name="M:Admin.NET.Core.ObjectExtension.ParseToLong(System.String,System.Int64)">
+            <summary>
+            将object转换为long,若失败则返回指定值
+            </summary>
+            <param name="str"></param>
+            <param name="defaultValue"></param>
+            <returns></returns>
+        </member>
+        <member name="M:Admin.NET.Core.ObjectExtension.ParseToDouble(System.Object)">
+            <summary>
+            将object转换为double,若失败则返回0  
+            </summary>
+            <param name="obj"></param>
+            <returns></returns>
+        </member>
+        <member name="M:Admin.NET.Core.ObjectExtension.ParseToDouble(System.Object,System.Double)">
+            <summary>
+            将object转换为double,若失败则返回指定值 
+            </summary>
+            <param name="str"></param>
+            <param name="defaultValue"></param>
+            <returns></returns>
+        </member>
+        <member name="M:Admin.NET.Core.ObjectExtension.ParseToDateTime(System.String)">
+            <summary>
+            将string转换为DateTime,若失败则返回日期最小值 
+            </summary>
+            <param name="str"></param>
+            <returns></returns>
+        </member>
+        <member name="M:Admin.NET.Core.ObjectExtension.ParseToDateTime(System.String,System.Nullable{System.DateTime})">
+            <summary>
+            将string转换为DateTime,若失败则返回默认值  
+            </summary>
+            <param name="str"></param>
+            <param name="defaultValue"></param>
+            <returns></returns>
+        </member>
         <member name="T:Admin.NET.Core.RepositoryExtension">
             <summary>
             仓储拓展
@@ -6330,21 +6392,27 @@
             系统服务器监控服务
             </summary>
         </member>
-        <member name="M:Admin.NET.Core.Service.SysServerService.GetServerBaseInfo">
+        <member name="M:Admin.NET.Core.Service.SysServerService.GetServerBase">
+            <summary>
+            服务器配置信息
+            </summary>
+            <returns></returns>
+        </member>
+        <member name="M:Admin.NET.Core.Service.SysServerService.GetServerUsed">
             <summary>
-            服务器基本配置
+            服务器内存信息
             </summary>
             <returns></returns>
         </member>
-        <member name="M:Admin.NET.Core.Service.SysServerService.GetServerUseInfo">
+        <member name="M:Admin.NET.Core.Service.SysServerService.GetServerDisk">
             <summary>
-            服务器使用资源
+            服务器磁盘信息
             </summary>
             <returns></returns>
         </member>
-        <member name="M:Admin.NET.Core.Service.SysServerService.GetServerNetWorkInfo">
+        <member name="M:Admin.NET.Core.Service.SysServerService.GetAssembly">
             <summary>
-            服务器网络信息
+            框架主要程序集
             </summary>
             <returns></returns>
         </member>
@@ -7499,131 +7567,211 @@
             </summary>
             <returns></returns>
         </member>
-        <member name="T:Admin.NET.Core.CommonUtil">
+        <member name="M:Admin.NET.Core.ComputerUtil.GetComputerInfo">
             <summary>
-            通用工具类
+            内存信息
             </summary>
+            <returns></returns>
         </member>
-        <member name="M:Admin.NET.Core.CommonUtil.GetTodayTimeList(System.DateTime)">
+        <member name="M:Admin.NET.Core.ComputerUtil.GetDiskInfos">
             <summary>
-            获取今天日期范围00:00:00 - 23:59:59
+            磁盘信息
             </summary>
             <returns></returns>
         </member>
-        <member name="M:Admin.NET.Core.CommonUtil.GetRootPathFileText(System.String)">
+        <member name="M:Admin.NET.Core.ComputerUtil.GetIpFromPCOnline">
             <summary>
-            获取项目根目录文件内容
+            IP地址信息
             </summary>
-            <param name="path"></param>
             <returns></returns>
         </member>
-        <member name="T:Admin.NET.Core.LongJsonConverter">
+        <member name="M:Admin.NET.Core.ComputerUtil.GetRunTime">
             <summary>
-            序列化时long转string(防止js精度溢出)
+            获取系统运行时间
             </summary>
+            <returns></returns>
         </member>
-        <member name="T:Admin.NET.Core.RandomCaptcha">
+        <member name="T:Admin.NET.Core.MemoryMetrics">
             <summary>
-            随机验证码
+            内存信息
             </summary>
         </member>
-        <member name="M:Admin.NET.Core.RandomCaptcha.ChangeOptions(Lazy.Captcha.Core.CaptchaOptions)">
+        <member name="P:Admin.NET.Core.MemoryMetrics.UsedRam">
             <summary>
-            更新选项
+            已用内存
             </summary>
-            <param name="options"></param>
         </member>
-        <member name="T:Admin.NET.Core.ReflectionUtil">
+        <member name="P:Admin.NET.Core.MemoryMetrics.CpuRate">
             <summary>
-            反射工具类
+            CPU使用率%
             </summary>
         </member>
-        <member name="M:Admin.NET.Core.ReflectionUtil.GetDescriptionValue``1(System.Reflection.FieldInfo)">
+        <member name="P:Admin.NET.Core.MemoryMetrics.TotalRam">
             <summary>
-            获取字段特性
+            总内存 GB
             </summary>
-            <param name="field"></param>
-            <typeparam name="T"></typeparam>
-            <returns></returns>
         </member>
-        <member name="T:Admin.NET.Core.ServerUtil">
+        <member name="P:Admin.NET.Core.MemoryMetrics.RamRate">
             <summary>
-            服务器信息
+            内存使用率 %
             </summary>
         </member>
-        <member name="M:Admin.NET.Core.ServerUtil.GetServerBaseInfo">
+        <member name="P:Admin.NET.Core.MemoryMetrics.FreeRam">
             <summary>
-            服务器基本配置
+            空闲内存
             </summary>
-            <returns></returns>
         </member>
-        <member name="M:Admin.NET.Core.ServerUtil.GetServerUseInfo">
+        <member name="T:Admin.NET.Core.DiskInfo">
             <summary>
-            服务器使用资源
+            磁盘信息
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.DiskInfo.DiskName">
+            <summary>
+            磁盘名
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.DiskInfo.TypeName">
+            <summary>
+            类型名
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.DiskInfo.TotalFree">
+            <summary>
+            总剩余
             </summary>
-            <returns></returns>
         </member>
-        <member name="M:Admin.NET.Core.ServerUtil.GetServerNetWorkInfo">
+        <member name="P:Admin.NET.Core.DiskInfo.TotalSize">
             <summary>
-            服务器网络信息
+            总量
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.DiskInfo.Used">
+            <summary>
+            已使用
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.DiskInfo.AvailableFreeSpace">
+            <summary>
+            可使用
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.DiskInfo.AvailablePercent">
+            <summary>
+            使用百分比
+            </summary>
+        </member>
+        <member name="M:Admin.NET.Core.MemoryMetricsClient.GetWindowsMetrics">
+            <summary>
+            windows系统获取内存信息
             </summary>
             <returns></returns>
         </member>
-        <member name="M:Admin.NET.Core.ServerUtil.GetRamInfo">
+        <member name="M:Admin.NET.Core.MemoryMetricsClient.GetUnixMetrics">
             <summary>
-            内存信息
+            Unix系统获取
             </summary>
             <returns></returns>
         </member>
-        <member name="M:Admin.NET.Core.ServerUtil.GetCpuRate">
+        <member name="M:Admin.NET.Core.ShellUtil.Bash(System.String)">
             <summary>
-            CPU信息
+            linux 系统命令
             </summary>
+            <param name="command"></param>
             <returns></returns>
         </member>
-        <member name="M:Admin.NET.Core.ServerUtil.GetRunTime">
+        <member name="M:Admin.NET.Core.ShellUtil.Cmd(System.String,System.String)">
             <summary>
-            系统运行时间
+            windows系统命令
             </summary>
+            <param name="fileName"></param>
+            <param name="args"></param>
             <returns></returns>
         </member>
-        <member name="M:Admin.NET.Core.ServerUtil.IsUnix">
+        <member name="M:Admin.NET.Core.DateTimeUtil.GetBeginTime(System.Nullable{System.DateTime},System.Int32)">
             <summary>
-            是否Linux
+            获取开始时间
             </summary>
+            <param name="dateTime"></param>
             <returns></returns>
         </member>
-        <member name="M:Admin.NET.Core.ServerUtil.FormatTime(System.Int64)">
+        <member name="M:Admin.NET.Core.DateTimeUtil.ToLocalTimeDateBySeconds(System.Int64)">
+            <summary>
+             时间戳转本地时间-时间戳精确到秒
+            </summary>
+        </member>
+        <member name="M:Admin.NET.Core.DateTimeUtil.ToUnixTimestampBySeconds(System.DateTime)">
+            <summary>
+             时间转时间戳Unix-时间戳精确到秒
+            </summary>
+        </member>
+        <member name="M:Admin.NET.Core.DateTimeUtil.ToLocalTimeDateByMilliseconds(System.Int64)">
+            <summary>
+             时间戳转本地时间-时间戳精确到毫秒
+            </summary>
+        </member>
+        <member name="M:Admin.NET.Core.DateTimeUtil.ToUnixTimestampByMilliseconds(System.DateTime)">
+            <summary>
+             时间转时间戳Unix-时间戳精确到毫秒
+            </summary>
+        </member>
+        <member name="M:Admin.NET.Core.DateTimeUtil.FormatTime(System.Int64)">
             <summary>
             毫秒转天时分秒
             </summary>
             <param name="ms"></param>
             <returns></returns>
         </member>
-        <member name="M:Admin.NET.Core.ServerUtil.GetWanIpFromPCOnline">
+        <member name="M:Admin.NET.Core.DateTimeUtil.GetUnixTimeStamp(System.DateTime)">
             <summary>
-            获取外网IP和位置
+            获取unix时间戳
             </summary>
+            <param name="dt"></param>
             <returns></returns>
         </member>
-        <member name="T:Admin.NET.Core.ShellUtil">
+        <member name="M:Admin.NET.Core.DateTimeUtil.GetDayMinDate(System.DateTime)">
             <summary>
-            系统Shell命令
+            获取日期天的最小时间
             </summary>
+            <param name="dt"></param>
+            <returns></returns>
         </member>
-        <member name="M:Admin.NET.Core.ShellUtil.Bash(System.String)">
+        <member name="M:Admin.NET.Core.DateTimeUtil.GetDayMaxDate(System.DateTime)">
             <summary>
-            Bash命令
+            获取日期天的最大时间
             </summary>
-            <param name="command"></param>
+            <param name="dt"></param>
             <returns></returns>
         </member>
-        <member name="M:Admin.NET.Core.ShellUtil.Cmd(System.String,System.String)">
+        <member name="M:Admin.NET.Core.DateTimeUtil.FormatDateTime(System.Nullable{System.DateTime})">
             <summary>
-            cmd命令
+            获取日期天的最大时间
             </summary>
-            <param name="fileName"></param>
-            <param name="args"></param>
+            <param name="dt"></param>
+            <returns></returns>
+        </member>
+        <member name="M:Admin.NET.Core.DateTimeUtil.GetTodayTimeList(System.DateTime)">
+            <summary>
+            获取今天日期范围00:00:00 - 23:59:59
+            </summary>
+            <returns></returns>
+        </member>
+        <member name="T:Admin.NET.Core.LongJsonConverter">
+            <summary>
+            序列化时long转string(防止js精度溢出)
+            </summary>
+        </member>
+        <member name="T:Admin.NET.Core.ReflectionUtil">
+            <summary>
+            反射工具类
+            </summary>
+        </member>
+        <member name="M:Admin.NET.Core.ReflectionUtil.GetDescriptionValue``1(System.Reflection.FieldInfo)">
+            <summary>
+            获取字段特性
+            </summary>
+            <param name="field"></param>
+            <typeparam name="T"></typeparam>
             <returns></returns>
         </member>
         <member name="T:Admin.NET.Core.ByteUtil">

+ 16 - 3
Admin.NET/Admin.NET.Core/Util/RandomCaptcha.cs → Admin.NET/Admin.NET.Core/Captcha/LazyCaptchaSetup.cs

@@ -4,6 +4,19 @@ using Lazy.Captcha.Core.Storage;
 
 namespace Admin.NET.Core;
 
+public static class LazyCaptchaSetup
+{
+    /// <summary>
+    /// 验证码初始化
+    /// </summary>
+    /// <param name="services"></param>
+    public static void AddLazyCaptcha(this IServiceCollection services)
+    {
+        services.AddCaptcha(App.Configuration);
+        services.AddScoped<ICaptcha, RandomCaptcha>();
+    }
+}
+
 /// <summary>
 /// 随机验证码
 /// </summary>
@@ -53,9 +66,9 @@ public class RandomCaptcha : DefaultCaptcha
         options.ImageOption.InterferenceLineCount = random.Next(1, 5); // 干扰线数量
 
         options.ImageOption.BubbleCount = random.Next(1, 5); // 气泡数量
-                                                             //options.ImageOption.BubbleMinRadius = 5; // 气泡最小半径
-                                                             //options.ImageOption.BubbleMaxRadius = 15; // 气泡最大半径
-                                                             //options.ImageOption.BubbleThickness = 1; // 气泡边沿厚度
+        //options.ImageOption.BubbleMinRadius = 5; // 气泡最小半径
+        //options.ImageOption.BubbleMaxRadius = 15; // 气泡最大半径
+        //options.ImageOption.BubbleThickness = 1; // 气泡边沿厚度
 
         options.ImageOption.BackgroundColor = SixLabors.ImageSharp.Color.White; // 背景色
 

+ 174 - 1
Admin.NET/Admin.NET.Core/Extension/ObjectExtension.cs

@@ -4,7 +4,7 @@ namespace Admin.NET.Core;
 /// 对象拓展
 /// </summary>
 [SuppressSniffer]
-public static class ObjectExtension
+public static partial class ObjectExtension
 {
     /// <summary>
     /// 判断类型是否实现某个泛型
@@ -125,4 +125,177 @@ public static class ObjectExtension
         var sc = pi.GetCustomAttributes<SugarColumn>(false).FirstOrDefault(u => u.IsIgnore == true);
         return sc != null;
     }
+
+    /// <summary>
+    /// 将object转换为long,若失败则返回0
+    /// </summary>
+    /// <param name="obj"></param>
+    /// <returns></returns>
+    public static long ParseToLong(this object obj)
+    {
+        try
+        {
+            return long.Parse(obj.ToString());
+        }
+        catch
+        {
+            return 0L;
+        }
+    }
+
+    /// <summary>
+    /// 将object转换为long,若失败则返回指定值
+    /// </summary>
+    /// <param name="str"></param>
+    /// <param name="defaultValue"></param>
+    /// <returns></returns>
+    public static long ParseToLong(this string str, long defaultValue)
+    {
+        try
+        {
+            return long.Parse(str);
+        }
+        catch
+        {
+            return defaultValue;
+        }
+    }
+
+    /// <summary>
+    /// 将object转换为double,若失败则返回0
+    /// </summary>
+    /// <param name="obj"></param>
+    /// <returns></returns>
+    public static double ParseToDouble(this object obj)
+    {
+        try
+        {
+            return double.Parse(obj.ToString());
+        }
+        catch
+        {
+            return 0;
+        }
+    }
+
+    /// <summary>
+    /// 将object转换为double,若失败则返回指定值
+    /// </summary>
+    /// <param name="str"></param>
+    /// <param name="defaultValue"></param>
+    /// <returns></returns>
+    public static double ParseToDouble(this object str, double defaultValue)
+    {
+        try
+        {
+            return double.Parse(str.ToString());
+        }
+        catch
+        {
+            return defaultValue;
+        }
+    }
+
+    /// <summary>
+    /// 将string转换为DateTime,若失败则返回日期最小值
+    /// </summary>
+    /// <param name="str"></param>
+    /// <returns></returns>
+    public static DateTime ParseToDateTime(this string str)
+    {
+        try
+        {
+            if (string.IsNullOrWhiteSpace(str))
+            {
+                return DateTime.MinValue;
+            }
+            if (str.Contains("-") || str.Contains("/"))
+            {
+                return DateTime.Parse(str);
+            }
+            else
+            {
+                int length = str.Length;
+                switch (length)
+                {
+                    case 4:
+                        return DateTime.ParseExact(str, "yyyy", System.Globalization.CultureInfo.CurrentCulture);
+
+                    case 6:
+                        return DateTime.ParseExact(str, "yyyyMM", System.Globalization.CultureInfo.CurrentCulture);
+
+                    case 8:
+                        return DateTime.ParseExact(str, "yyyyMMdd", System.Globalization.CultureInfo.CurrentCulture);
+
+                    case 10:
+                        return DateTime.ParseExact(str, "yyyyMMddHH", System.Globalization.CultureInfo.CurrentCulture);
+
+                    case 12:
+                        return DateTime.ParseExact(str, "yyyyMMddHHmm", System.Globalization.CultureInfo.CurrentCulture);
+
+                    case 14:
+                        return DateTime.ParseExact(str, "yyyyMMddHHmmss", System.Globalization.CultureInfo.CurrentCulture);
+
+                    default:
+                        return DateTime.ParseExact(str, "yyyyMMddHHmmss", System.Globalization.CultureInfo.CurrentCulture);
+                }
+            }
+        }
+        catch
+        {
+            return DateTime.MinValue;
+        }
+    }
+
+    /// <summary>
+    /// 将string转换为DateTime,若失败则返回默认值
+    /// </summary>
+    /// <param name="str"></param>
+    /// <param name="defaultValue"></param>
+    /// <returns></returns>
+    public static DateTime ParseToDateTime(this string str, DateTime? defaultValue)
+    {
+        try
+        {
+            if (string.IsNullOrWhiteSpace(str))
+            {
+                return defaultValue.GetValueOrDefault();
+            }
+            if (str.Contains("-") || str.Contains("/"))
+            {
+                return DateTime.Parse(str);
+            }
+            else
+            {
+                int length = str.Length;
+                switch (length)
+                {
+                    case 4:
+                        return DateTime.ParseExact(str, "yyyy", System.Globalization.CultureInfo.CurrentCulture);
+
+                    case 6:
+                        return DateTime.ParseExact(str, "yyyyMM", System.Globalization.CultureInfo.CurrentCulture);
+
+                    case 8:
+                        return DateTime.ParseExact(str, "yyyyMMdd", System.Globalization.CultureInfo.CurrentCulture);
+
+                    case 10:
+                        return DateTime.ParseExact(str, "yyyyMMddHH", System.Globalization.CultureInfo.CurrentCulture);
+
+                    case 12:
+                        return DateTime.ParseExact(str, "yyyyMMddHHmm", System.Globalization.CultureInfo.CurrentCulture);
+
+                    case 14:
+                        return DateTime.ParseExact(str, "yyyyMMddHHmmss", System.Globalization.CultureInfo.CurrentCulture);
+
+                    default:
+                        return DateTime.ParseExact(str, "yyyyMMddHHmmss", System.Globalization.CultureInfo.CurrentCulture);
+                }
+            }
+        }
+        catch
+        {
+            return defaultValue.GetValueOrDefault();
+        }
+    }
 }

+ 85 - 11
Admin.NET/Admin.NET.Core/Service/Server/SysServerService.cs

@@ -1,4 +1,13 @@
-namespace Admin.NET.Core.Service;
+using AspNetCoreRateLimit;
+using Lazy.Captcha.Core;
+using Magicodes.ExporterAndImporter.Excel;
+using Magicodes.ExporterAndImporter.Pdf;
+using Microsoft.Extensions.Hosting;
+using Nest;
+using NewLife.Caching;
+using OnceMi.AspNetCore.OSS;
+
+namespace Admin.NET.Core.Service;
 
 /// <summary>
 /// 系统服务器监控服务
@@ -11,32 +20,97 @@ public class SysServerService : IDynamicApiController, ITransient
     }
 
     /// <summary>
-    /// 服务器基本配置
+    /// 服务器配置信息
     /// </summary>
     /// <returns></returns>
     [HttpGet("/server/base")]
-    public async Task<dynamic> GetServerBaseInfo()
+    public dynamic GetServerBase()
     {
-        return await Task.FromResult(ServerUtil.GetServerBaseInfo());
+        return new
+        {
+            HostName = Environment.MachineName, // 主机名称
+            SystemOs = RuntimeInformation.OSDescription, // 操作系统
+            OsArchitecture = Environment.OSVersion.Platform.ToString() + " " + RuntimeInformation.OSArchitecture.ToString(), // 系统架构
+            ProcessorCount = Environment.ProcessorCount + " 核", // CPU核心数
+            SysRunTime = ComputerUtil.GetRunTime(), // 系统运行时间
+            RemoteIp = ComputerUtil.GetIpFromPCOnline(), // 外网地址
+            LocalIp = App.HttpContext?.Connection?.LocalIpAddress.ToString(), // 本地地址
+            FrameworkDescription = RuntimeInformation.FrameworkDescription, // NET框架
+            Environment = App.HostEnvironment.IsDevelopment() ? "Development" : "Production",
+        };
     }
 
     /// <summary>
-    /// 服务器使用资源
+    /// 服务器内存信息
     /// </summary>
     /// <returns></returns>
     [HttpGet("/server/use")]
-    public async Task<dynamic> GetServerUseInfo()
+    public dynamic GetServerUsed()
     {
-        return await Task.FromResult(ServerUtil.GetServerUseInfo());
+        var programStartTime = Process.GetCurrentProcess().StartTime;
+        var programRunTime = DateTimeUtil.FormatTime((DateTime.Now - programStartTime).TotalMilliseconds.ToString().Split('.')[0].ParseToLong());
+
+        var memoryMetrics = ComputerUtil.GetComputerInfo();
+        return new
+        {
+            memoryMetrics.FreeRam, // 空闲内存
+            memoryMetrics.UsedRam, // 已用内存
+            memoryMetrics.TotalRam, // 总内存
+            memoryMetrics.RamRate, // 内存使用率
+            memoryMetrics.CpuRate, // Cpu使用率
+            StartTime = programStartTime.ToString("yyyy-MM-dd HH:mm:ss"), // 服务启动时间
+            RunTime = programRunTime, // 服务运行时间
+        };
     }
 
     /// <summary>
-    /// 服务器网络信息
+    /// 服务器磁盘信息
     /// </summary>
     /// <returns></returns>
-    [HttpGet("/server/network")]
-    public async Task<dynamic> GetServerNetWorkInfo()
+    [HttpGet("/server/disk")]
+    public dynamic GetServerDisk()
     {
-        return await ServerUtil.GetServerNetWorkInfo();
+        return ComputerUtil.GetDiskInfos();
+    }
+
+    /// <summary>
+    /// 框架主要程序集
+    /// </summary>
+    /// <returns></returns>
+    [HttpGet("/server/assembly")]
+    public dynamic GetAssembly()
+    {
+        var furionAssembly = typeof(App).Assembly.GetName();
+        var sqlSugarAssembly = typeof(ISqlSugarClient).Assembly.GetName();
+        var yitIdAssembly = typeof(Yitter.IdGenerator.YitIdHelper).Assembly.GetName();
+        var redisAssembly = typeof(Redis).Assembly.GetName();
+        var jsonAssembly = typeof(NewtonsoftJsonMvcCoreBuilderExtensions).Assembly.GetName();
+        var excelAssembly = typeof(IExcelImporter).Assembly.GetName();
+        var pdfAssembly = typeof(IPdfExporter).Assembly.GetName();
+        var captchaAssembly = typeof(ICaptcha).Assembly.GetName();
+        var wechatApiAssembly = typeof(WechatApiClient).Assembly.GetName();
+        var wechatTenpayAssembly = typeof(WechatTenpayClient).Assembly.GetName();
+        var ossAssembly = typeof(IOSSServiceFactory).Assembly.GetName();
+        var parserAssembly = typeof(Parser).Assembly.GetName();
+        var nestAssembly = typeof(IElasticClient).Assembly.GetName();
+        var limitAssembly = typeof(IpRateLimitMiddleware).Assembly.GetName();
+
+        return new[]
+        {
+            new { furionAssembly.Name, furionAssembly.Version },
+            new { sqlSugarAssembly.Name, sqlSugarAssembly.Version },
+            new { yitIdAssembly.Name, yitIdAssembly.Version },
+            new { redisAssembly.Name, redisAssembly.Version },
+            new { jsonAssembly.Name, jsonAssembly.Version },
+            new { excelAssembly.Name, excelAssembly.Version },
+            new { pdfAssembly.Name, pdfAssembly.Version },
+            new { captchaAssembly.Name, captchaAssembly.Version },
+            new { wechatApiAssembly.Name, wechatApiAssembly.Version },
+            new { wechatTenpayAssembly.Name, wechatTenpayAssembly.Version },
+            new { ossAssembly.Name, ossAssembly.Version },
+            new { parserAssembly.Name, parserAssembly.Version },
+            new { nestAssembly.Name, nestAssembly.Version },
+            new { limitAssembly.Name, limitAssembly.Version },
+        };
     }
 }

+ 1 - 1
Admin.NET/Admin.NET.Core/Service/WeChat/WeChatPayService.cs

@@ -33,7 +33,7 @@ public class WeChatPayService : IDynamicApiController, ITransient
             MerchantId = _weChatPayOptions.MerchantId,
             MerchantV3Secret = _weChatPayOptions.MerchantV3Secret,
             MerchantCertificateSerialNumber = _weChatPayOptions.MerchantCertificateSerialNumber,
-            MerchantCertificatePrivateKey = CommonUtil.GetRootPathFileText(_weChatPayOptions.MerchantCertificatePrivateKey),
+            MerchantCertificatePrivateKey = File.ReadAllText(App.WebHostEnvironment.ContentRootPath + _weChatPayOptions.MerchantCertificatePrivateKey),
             PlatformCertificateManager = new InMemoryCertificateManager()
         };
         return new WechatTenpayClient(tenpayClientOptions);

+ 0 - 30
Admin.NET/Admin.NET.Core/Util/CommonUtil.cs

@@ -1,30 +0,0 @@
-namespace Admin.NET.Core;
-
-/// <summary>
-/// 通用工具类
-/// </summary>
-public static class CommonUtil
-{
-    /// <summary>
-    /// 获取今天日期范围00:00:00 - 23:59:59
-    /// </summary>
-    /// <returns></returns>
-    public static List<DateTime> GetTodayTimeList(DateTime time)
-    {
-        return new List<DateTime>
-        {
-            Convert.ToDateTime(time.ToString("D").ToString()),
-            Convert.ToDateTime(time.AddDays(1).ToString("D").ToString()).AddSeconds(-1)
-        };
-    }
-
-    /// <summary>
-    /// 获取项目根目录文件内容
-    /// </summary>
-    /// <param name="path"></param>
-    /// <returns></returns>
-    public static string GetRootPathFileText(string path)
-    {
-        return File.ReadAllText(App.WebHostEnvironment.ContentRootPath + path);
-    }
-}

+ 309 - 0
Admin.NET/Admin.NET.Core/Util/ComputerUtil.cs

@@ -0,0 +1,309 @@
+namespace Admin.NET.Core;
+
+public static class ComputerUtil
+{
+    /// <summary>
+    /// 内存信息
+    /// </summary>
+    /// <returns></returns>
+    public static MemoryMetrics GetComputerInfo()
+    {
+        MemoryMetricsClient client = new();
+        MemoryMetrics memoryMetrics = IsUnix() ? client.GetUnixMetrics() : client.GetWindowsMetrics();
+
+        memoryMetrics.FreeRam = Math.Round(memoryMetrics.Free / 1024, 2) + "GB";
+        memoryMetrics.UsedRam = Math.Round(memoryMetrics.Used / 1024, 2) + "GB";
+        memoryMetrics.TotalRam = Math.Round(memoryMetrics.Total / 1024, 2) + "GB";
+        memoryMetrics.RamRate = Math.Ceiling(100 * memoryMetrics.Used / memoryMetrics.Total).ToString() + "%";
+        memoryMetrics.CpuRate = Math.Ceiling(GetCPURate().ParseToDouble()) + "%";
+        return memoryMetrics;
+    }
+
+    /// <summary>
+    /// 磁盘信息
+    /// </summary>
+    /// <returns></returns>
+    public static List<DiskInfo> GetDiskInfos()
+    {
+        List<DiskInfo> diskInfos = new();
+
+        if (IsUnix())
+        {
+            string output = ShellUtil.Bash("df -m / | awk '{print $2,$3,$4,$5,$6}'");
+            var arr = output.Split('\n', StringSplitOptions.RemoveEmptyEntries);
+            if (arr.Length == 0) return diskInfos;
+
+            var rootDisk = arr[1].Split(' ', (char)StringSplitOptions.RemoveEmptyEntries);
+            if (rootDisk == null || rootDisk.Length == 0)
+                return diskInfos;
+
+            DiskInfo diskInfo = new()
+            {
+                DiskName = "/",
+                TotalSize = long.Parse(rootDisk[0]) / 1024,
+                Used = long.Parse(rootDisk[1]) / 1024,
+                AvailableFreeSpace = long.Parse(rootDisk[2]) / 1024,
+                AvailablePercent = decimal.Parse(rootDisk[3].Replace("%", ""))
+            };
+            diskInfos.Add(diskInfo);
+        }
+        else
+        {
+            var driv = DriveInfo.GetDrives();
+            foreach (var item in driv)
+            {
+                var obj = new DiskInfo()
+                {
+                    DiskName = item.Name,
+                    TypeName = item.DriveType.ToString(),
+                    TotalSize = item.TotalSize / 1024 / 1024 / 1024,
+                    AvailableFreeSpace = item.AvailableFreeSpace / 1024 / 1024 / 1024,
+                };
+                obj.Used = obj.TotalSize - obj.AvailableFreeSpace;
+                obj.AvailablePercent = decimal.Ceiling(obj.Used / (decimal)obj.TotalSize * 100);
+                diskInfos.Add(obj);
+            }
+        }
+        return diskInfos;
+    }
+
+    /// <summary>
+    /// IP地址信息
+    /// </summary>
+    /// <returns></returns>
+    public static string GetIpFromPCOnline()
+    {
+        Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
+
+        var url = "http://whois.pconline.com.cn/ipJson.jsp";
+        var stream = url.GetAsStreamAsync().GetAwaiter().GetResult();
+        var streamReader = new StreamReader(stream.Stream, stream.Encoding);
+        var html = streamReader.ReadToEnd();
+        var tmp = html[(html.IndexOf("({") + 2)..].Split(",");
+        var ipAddr = tmp[0].Split(":")[1] + "【" + tmp[7].Split(":")[1] + "】";
+        return ipAddr.Replace("\"", "");
+    }
+
+    public static bool IsUnix()
+    {
+        return RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
+    }
+
+    public static string GetCPURate()
+    {
+        string cpuRate;
+        if (IsUnix())
+        {
+            string output = ShellUtil.Bash("top -b -n1 | grep \"Cpu(s)\" | awk '{print $2 + $4}'");
+            cpuRate = output.Trim();
+        }
+        else
+        {
+            string output = ShellUtil.Cmd("wmic", "cpu get LoadPercentage");
+            cpuRate = output.Replace("LoadPercentage", string.Empty).Trim();
+        }
+        return cpuRate;
+    }
+
+    /// <summary>
+    /// 获取系统运行时间
+    /// </summary>
+    /// <returns></returns>
+    public static string GetRunTime()
+    {
+        string runTime = string.Empty;
+        if (IsUnix())
+        {
+            string output = ShellUtil.Bash("uptime -s").Trim();
+            runTime = DateTimeUtil.FormatTime((DateTime.Now - output.ParseToDateTime()).TotalMilliseconds.ToString().Split('.')[0].ParseToLong());
+        }
+        else
+        {
+            string output = ShellUtil.Cmd("wmic", "OS get LastBootUpTime/Value");
+            string[] outputArr = output.Split('=', (char)StringSplitOptions.RemoveEmptyEntries);
+            if (outputArr.Length == 2)
+                runTime = DateTimeUtil.FormatTime((DateTime.Now - outputArr[1].Split('.')[0].ParseToDateTime()).TotalMilliseconds.ToString().Split('.')[0].ParseToLong());
+        }
+        return runTime;
+    }
+}
+
+/// <summary>
+/// 内存信息
+/// </summary>
+public class MemoryMetrics
+{
+    [JsonIgnore]
+    public double Total { get; set; }
+
+    [JsonIgnore]
+    public double Used { get; set; }
+
+    [JsonIgnore]
+    public double Free { get; set; }
+
+    /// <summary>
+    /// 已用内存
+    /// </summary>
+    public string UsedRam { get; set; }
+
+    /// <summary>
+    /// CPU使用率%
+    /// </summary>
+    public string CpuRate { get; set; }
+
+    /// <summary>
+    /// 总内存 GB
+    /// </summary>
+    public string TotalRam { get; set; }
+
+    /// <summary>
+    /// 内存使用率 %
+    /// </summary>
+    public string RamRate { get; set; }
+
+    /// <summary>
+    /// 空闲内存
+    /// </summary>
+    public string FreeRam { get; set; }
+}
+
+/// <summary>
+/// 磁盘信息
+/// </summary>
+public class DiskInfo
+{
+    /// <summary>
+    /// 磁盘名
+    /// </summary>
+    public string DiskName { get; set; }
+
+    /// <summary>
+    /// 类型名
+    /// </summary>
+    public string TypeName { get; set; }
+
+    /// <summary>
+    /// 总剩余
+    /// </summary>
+    public long TotalFree { get; set; }
+
+    /// <summary>
+    /// 总量
+    /// </summary>
+    public long TotalSize { get; set; }
+
+    /// <summary>
+    /// 已使用
+    /// </summary>
+    public long Used { get; set; }
+
+    /// <summary>
+    /// 可使用
+    /// </summary>
+    public long AvailableFreeSpace { get; set; }
+
+    /// <summary>
+    /// 使用百分比
+    /// </summary>
+    public decimal AvailablePercent { get; set; }
+}
+
+public class MemoryMetricsClient
+{
+    /// <summary>
+    /// windows系统获取内存信息
+    /// </summary>
+    /// <returns></returns>
+    public MemoryMetrics GetWindowsMetrics()
+    {
+        string output = ShellUtil.Cmd("wmic", "OS get FreePhysicalMemory,TotalVisibleMemorySize /Value");
+        var metrics = new MemoryMetrics();
+        var lines = output.Trim().Split('\n', (char)StringSplitOptions.RemoveEmptyEntries);
+        if (lines.Length <= 0) return metrics;
+
+        var freeMemoryParts = lines[0].Split('=', (char)StringSplitOptions.RemoveEmptyEntries);
+        var totalMemoryParts = lines[1].Split('=', (char)StringSplitOptions.RemoveEmptyEntries);
+
+        metrics.Total = Math.Round(double.Parse(totalMemoryParts[1]) / 1024, 0);
+        metrics.Free = Math.Round(double.Parse(freeMemoryParts[1]) / 1024, 0);//m
+        metrics.Used = metrics.Total - metrics.Free;
+
+        return metrics;
+    }
+
+    /// <summary>
+    /// Unix系统获取
+    /// </summary>
+    /// <returns></returns>
+    public MemoryMetrics GetUnixMetrics()
+    {
+        string output = ShellUtil.Bash("free -m | awk '{print $2,$3,$4,$5,$6}'");
+        var metrics = new MemoryMetrics();
+        var lines = output.Split('\n', (char)StringSplitOptions.RemoveEmptyEntries);
+        if (lines.Length <= 0) return metrics;
+
+        if (lines != null && lines.Length > 0)
+        {
+            var memory = lines[1].Split(' ', (char)StringSplitOptions.RemoveEmptyEntries);
+            if (memory.Length >= 3)
+            {
+                metrics.Total = double.Parse(memory[0]);
+                metrics.Used = double.Parse(memory[1]);
+                metrics.Free = double.Parse(memory[2]);//m
+            }
+        }
+        return metrics;
+    }
+}
+
+public class ShellUtil
+{
+    /// <summary>
+    /// linux 系统命令
+    /// </summary>
+    /// <param name="command"></param>
+    /// <returns></returns>
+    public static string Bash(string command)
+    {
+        var escapedArgs = command.Replace("\"", "\\\"");
+        var process = new Process()
+        {
+            StartInfo = new ProcessStartInfo
+            {
+                FileName = "/bin/bash",
+                Arguments = $"-c \"{escapedArgs}\"",
+                RedirectStandardOutput = true,
+                UseShellExecute = false,
+                CreateNoWindow = true,
+            }
+        };
+        process.Start();
+        string result = process.StandardOutput.ReadToEnd();
+        process.WaitForExit();
+        process.Dispose();
+        return result;
+    }
+
+    /// <summary>
+    /// windows系统命令
+    /// </summary>
+    /// <param name="fileName"></param>
+    /// <param name="args"></param>
+    /// <returns></returns>
+    public static string Cmd(string fileName, string args)
+    {
+        string output = string.Empty;
+
+        var info = new ProcessStartInfo();
+        info.FileName = fileName;
+        info.Arguments = args;
+        info.RedirectStandardOutput = true;
+
+        using (var process = Process.Start(info))
+        {
+            output = process.StandardOutput.ReadToEnd();
+        }
+        return output;
+    }
+}

+ 136 - 0
Admin.NET/Admin.NET.Core/Util/DateTimeUtil.cs

@@ -0,0 +1,136 @@
+namespace Admin.NET.Core;
+
+public class DateTimeUtil
+{
+    /// <summary>
+    /// 获取开始时间
+    /// </summary>
+    /// <param name="dateTime"></param>
+    /// <returns></returns>
+    public static DateTime GetBeginTime(DateTime? dateTime, int days = 0)
+    {
+        if (dateTime == DateTime.MinValue || dateTime == null)
+            return DateTime.Now.AddDays(days);
+
+        return dateTime ?? DateTime.Now;
+    }
+
+    /// <summary>
+    ///  时间戳转本地时间-时间戳精确到秒
+    /// </summary>
+    public static DateTime ToLocalTimeDateBySeconds(long unix)
+    {
+        return DateTimeOffset.FromUnixTimeSeconds(unix).ToLocalTime().DateTime;
+    }
+
+    /// <summary>
+    ///  时间转时间戳Unix-时间戳精确到秒
+    /// </summary>
+    public static long ToUnixTimestampBySeconds(DateTime dt)
+    {
+        return new DateTimeOffset(dt).ToUnixTimeSeconds();
+    }
+
+    /// <summary>
+    ///  时间戳转本地时间-时间戳精确到毫秒
+    /// </summary>
+    public static DateTime ToLocalTimeDateByMilliseconds(long unix)
+    {
+        return DateTimeOffset.FromUnixTimeMilliseconds(unix).ToLocalTime().DateTime;
+    }
+
+    /// <summary>
+    ///  时间转时间戳Unix-时间戳精确到毫秒
+    /// </summary>
+    public static long ToUnixTimestampByMilliseconds(DateTime dt)
+    {
+        return new DateTimeOffset(dt).ToUnixTimeMilliseconds();
+    }
+
+    /// <summary>
+    /// 毫秒转天时分秒
+    /// </summary>
+    /// <param name="ms"></param>
+    /// <returns></returns>
+    public static string FormatTime(long ms)
+    {
+        int ss = 1000;
+        int mi = ss * 60;
+        int hh = mi * 60;
+        int dd = hh * 24;
+
+        long day = ms / dd;
+        long hour = (ms - day * dd) / hh;
+        long minute = (ms - day * dd - hour * hh) / mi;
+        long second = (ms - day * dd - hour * hh - minute * mi) / ss;
+        long milliSecond = ms - day * dd - hour * hh - minute * mi - second * ss;
+
+        string sDay = day < 10 ? "0" + day : "" + day; //天
+        string sHour = hour < 10 ? "0" + hour : "" + hour;//小时
+        string sMinute = minute < 10 ? "0" + minute : "" + minute;//分钟
+        string sSecond = second < 10 ? "0" + second : "" + second;//秒
+        string sMilliSecond = milliSecond < 10 ? "0" + milliSecond : "" + milliSecond;//毫秒
+        sMilliSecond = milliSecond < 100 ? "0" + sMilliSecond : "" + sMilliSecond;
+
+        return string.Format("{0} 天 {1} 小时 {2} 分 {3} 秒", sDay, sHour, sMinute, sSecond);
+    }
+
+    /// <summary>
+    /// 获取unix时间戳
+    /// </summary>
+    /// <param name="dt"></param>
+    /// <returns></returns>
+    public static long GetUnixTimeStamp(DateTime dt)
+    {
+        return ((DateTimeOffset)dt).ToUnixTimeMilliseconds();
+    }
+
+    /// <summary>
+    /// 获取日期天的最小时间
+    /// </summary>
+    /// <param name="dt"></param>
+    /// <returns></returns>
+    public static DateTime GetDayMinDate(DateTime dt)
+    {
+        return new DateTime(dt.Year, dt.Month, dt.Day, 0, 0, 0);
+    }
+
+    /// <summary>
+    /// 获取日期天的最大时间
+    /// </summary>
+    /// <param name="dt"></param>
+    /// <returns></returns>
+
+    public static DateTime GetDayMaxDate(DateTime dt)
+    {
+        return new DateTime(dt.Year, dt.Month, dt.Day, 23, 59, 59);
+    }
+
+    /// <summary>
+    /// 获取日期天的最大时间
+    /// </summary>
+    /// <param name="dt"></param>
+    /// <returns></returns>
+    public static string FormatDateTime(DateTime? dt)
+    {
+        if (dt == null) return string.Empty;
+
+        if (dt.Value.Year == DateTime.Now.Year)
+            return dt.Value.ToString("MM-dd HH:mm");
+        else
+            return dt.Value.ToString("yyyy-MM-dd HH:mm");
+    }
+
+    /// <summary>
+    /// 获取今天日期范围00:00:00 - 23:59:59
+    /// </summary>
+    /// <returns></returns>
+    public static List<DateTime> GetTodayTimeList(DateTime time)
+    {
+        return new List<DateTime>
+        {
+            Convert.ToDateTime(time.ToString("D").ToString()),
+            Convert.ToDateTime(time.AddDays(1).ToString("D").ToString()).AddSeconds(-1)
+        };
+    }
+}

+ 0 - 176
Admin.NET/Admin.NET.Core/Util/ServerUtil.cs

@@ -1,176 +0,0 @@
-namespace Admin.NET.Core;
-
-/// <summary>
-/// 服务器信息
-/// </summary>
-public class ServerUtil
-{
-    /// <summary>
-    /// 服务器基本配置
-    /// </summary>
-    /// <returns></returns>
-    public static dynamic GetServerBaseInfo()
-    {
-        var furionAssembly = typeof(Furion.App).Assembly.GetName();
-        var sqlSugarAssembly = typeof(ISqlSugarClient).Assembly.GetName();
-        return new
-        {
-            HostName = Environment.MachineName, // HostName
-            SystemOs = RuntimeInformation.OSDescription, // 系统名称
-            OsArchitecture = Environment.OSVersion.Platform.ToString() + " " + RuntimeInformation.OSArchitecture.ToString(), // 系统架构
-            ProcessorCount = Environment.ProcessorCount.ToString() + " 核", // CPU核心数
-            FrameworkDescription = RuntimeInformation.FrameworkDescription + " / " +
-            furionAssembly.Name.ToString() + " " + furionAssembly.Version.ToString() + " / " +
-            sqlSugarAssembly.Name.ToString() + " " + sqlSugarAssembly.Version.ToString(), // .NET、Furion、SqlSugar
-        };
-    }
-
-    /// <summary>
-    /// 服务器使用资源
-    /// </summary>
-    /// <returns></returns>
-    public static dynamic GetServerUseInfo()
-    {
-        var ramInfo = GetRamInfo();
-        return new
-        {
-            TotalRam = Math.Ceiling(ramInfo.Total / 1024).ToString() + " GB", // 总内存
-            RamRate = Math.Ceiling(100 * ramInfo.Used / ramInfo.Total) + " %", // 内存使用率
-            CpuRate = Math.Ceiling(double.Parse(GetCpuRate())) + " %", // Cpu使用率
-            RunTime = GetRunTime()
-        };
-    }
-
-    /// <summary>
-    /// 服务器网络信息
-    /// </summary>
-    /// <returns></returns>
-    public static async Task<dynamic> GetServerNetWorkInfo()
-    {
-        return new
-        {
-            WanIp = await GetWanIpFromPCOnline(), // 外网IP
-            LocalIp = App.HttpContext?.Connection?.LocalIpAddress.ToString(),
-            SendAndReceived = "", //"上行" + Math.Round(networkInfo.SendLength / 1024.0 / 1024 / 1024, 2) + "GB 下行" + Math.Round(networkInfo.ReceivedLength / 1024.0 / 1024 / 1024, 2) + "GB", // 上下行流量统计
-            NetworkSpeed = "" //"上行" + Send / 1024 + "kb/s 下行" + Received / 1024 + "kb/s" // 网络速度
-        };
-    }
-
-    /// <summary>
-    /// 内存信息
-    /// </summary>
-    /// <returns></returns>
-    private static dynamic GetRamInfo()
-    {
-        if (IsUnix())
-        {
-            var output = ShellUtil.Bash("cat /proc/meminfo");
-            var lines = output.Split("\n");
-            return new
-            {
-                Total = Math.Round(double.Parse(lines[0].Split(" ", StringSplitOptions.RemoveEmptyEntries)[1]) / 1024, 2),
-                Used = Math.Round((double.Parse(lines[0].Split(" ", StringSplitOptions.RemoveEmptyEntries)[1])
-                - double.Parse(lines[1].Split(" ", StringSplitOptions.RemoveEmptyEntries)[1])) / 1024, 2),
-                Free = Math.Round(double.Parse(lines[1].Split(" ", StringSplitOptions.RemoveEmptyEntries)[1]) / 1024, 2),
-            };
-        }
-        else
-        {
-            var output = ShellUtil.Cmd("wmic", "OS get FreePhysicalMemory,TotalVisibleMemorySize /Value");
-            var lines = output.Trim().Split("\n");
-            var freeMemoryParts = lines[0].Split("=", StringSplitOptions.RemoveEmptyEntries);
-            var totalMemoryParts = lines[1].Split("=", StringSplitOptions.RemoveEmptyEntries);
-            var total = Math.Round(double.Parse(totalMemoryParts[1]) / 1024, 2);
-            var free = Math.Round(double.Parse(freeMemoryParts[1]) / 1024, 2);
-            return new
-            {
-                Total = total,
-                Free = free,
-                Used = total - free
-            };
-        }
-    }
-
-    /// <summary>
-    /// CPU信息
-    /// </summary>
-    /// <returns></returns>
-    private static string GetCpuRate()
-    {
-        string cpuRate;
-        if (IsUnix())
-        {
-            var output = ShellUtil.Bash("top -b -n1 | grep \"Cpu(s)\" | awk '{print $2 + $4}'");
-            cpuRate = output.Trim();
-        }
-        else
-        {
-            var output = ShellUtil.Cmd("wmic", "cpu get LoadPercentage");
-            cpuRate = output.Replace("LoadPercentage", string.Empty).Trim();
-        }
-        return cpuRate == "" ? "0" : cpuRate;
-    }
-
-    /// <summary>
-    /// 系统运行时间
-    /// </summary>
-    /// <returns></returns>
-    private static string GetRunTime()
-    {
-        return FormatTime((long)(DateTimeOffset.Now - Process.GetCurrentProcess().StartTime).TotalMilliseconds);
-        //return DateTimeUtil.FormatTime(Environment.TickCount);
-    }
-
-    /// <summary>
-    /// 是否Linux
-    /// </summary>
-    /// <returns></returns>
-    private static bool IsUnix()
-    {
-        return RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
-    }
-
-    /// <summary>
-    /// 毫秒转天时分秒
-    /// </summary>
-    /// <param name="ms"></param>
-    /// <returns></returns>
-    private static string FormatTime(long ms)
-    {
-        int ss = 1000;
-        int mi = ss * 60;
-        int hh = mi * 60;
-        int dd = hh * 24;
-
-        long day = ms / dd;
-        long hour = (ms - day * dd) / hh;
-        long minute = (ms - day * dd - hour * hh) / mi;
-        long second = (ms - day * dd - hour * hh - minute * mi) / ss;
-        //long milliSecond = ms - day * dd - hour * hh - minute * mi - second * ss;
-
-        string sDay = day < 10 ? "0" + day : "" + day; //天
-        string sHour = hour < 10 ? "0" + hour : "" + hour;//小时
-        string sMinute = minute < 10 ? "0" + minute : "" + minute;//分钟
-        string sSecond = second < 10 ? "0" + second : "" + second;//秒
-        //string sMilliSecond = milliSecond < 10 ? "0" + milliSecond : "" + milliSecond;//毫秒
-        //sMilliSecond = milliSecond < 100 ? "0" + sMilliSecond : "" + sMilliSecond;
-        return string.Format("{0} 天 {1} 小时 {2} 分 {3} 秒", sDay, sHour, sMinute, sSecond);
-    }
-
-    /// <summary>
-    /// 获取外网IP和位置
-    /// </summary>
-    /// <returns></returns>
-    private static async Task<string> GetWanIpFromPCOnline()
-    {
-        Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
-
-        var url = "http://whois.pconline.com.cn/ipJson.jsp";
-        var stream = await url.GetAsStreamAsync();
-        var streamReader = new StreamReader(stream.Stream, stream.Encoding);
-        var html = streamReader.ReadToEnd();
-        var tmp = html[(html.IndexOf("({") + 2)..].Split(",");
-        var ipAddr = tmp[0].Split(":")[1] + "【" + tmp[7].Split(":")[1] + "】";
-        return ipAddr.Replace("\"", "");
-    }
-}

+ 0 - 55
Admin.NET/Admin.NET.Core/Util/ShellUtil.cs

@@ -1,55 +0,0 @@
-namespace Admin.NET.Core;
-
-/// <summary>
-/// 系统Shell命令
-/// </summary>
-public class ShellUtil
-{
-    /// <summary>
-    /// Bash命令
-    /// </summary>
-    /// <param name="command"></param>
-    /// <returns></returns>
-    public static string Bash(string command)
-    {
-        var escapedArgs = command.Replace("\"", "\\\"");
-        var process = new Process()
-        {
-            StartInfo = new ProcessStartInfo
-            {
-                FileName = "/bin/bash",
-                Arguments = $"-c \"{escapedArgs}\"",
-                RedirectStandardOutput = true,
-                UseShellExecute = false,
-                CreateNoWindow = true,
-            }
-        };
-        process.Start();
-        string result = process.StandardOutput.ReadToEnd();
-        process.WaitForExit();
-        process.Dispose();
-        return result;
-    }
-
-    /// <summary>
-    /// cmd命令
-    /// </summary>
-    /// <param name="fileName"></param>
-    /// <param name="args"></param>
-    /// <returns></returns>
-    public static string Cmd(string fileName, string args)
-    {
-        string output = string.Empty;
-        var info = new ProcessStartInfo
-        {
-            FileName = fileName,
-            Arguments = args,
-            RedirectStandardOutput = true
-        };
-        using (var process = Process.Start(info))
-        {
-            output = process.StandardOutput.ReadToEnd();
-        }
-        return output;
-    }
-}

+ 1 - 5
Admin.NET/Admin.NET.Web.Core/Startup.cs

@@ -3,7 +3,6 @@ using AspNetCoreRateLimit;
 using Furion;
 using Furion.SpecificationDocument;
 using IGeekFan.AspNetCore.Knife4jUI;
-using Lazy.Captcha.Core;
 using Microsoft.AspNetCore.Builder;
 using Microsoft.AspNetCore.Hosting;
 using Microsoft.AspNetCore.HttpOverrides;
@@ -165,10 +164,7 @@ public class Startup : AppStartup
         });
 
         // 验证码
-        services.AddCaptcha(App.Configuration);
-        //services.AddScoped<ICaptcha, DefaultCaptcha>();
-        services.AddScoped<ICaptcha, RandomCaptcha>();
-        //services.AddScoped<IStorage, DefaultStorage>();        
+        services.AddLazyCaptcha();
     }
 
     public void Configure(IApplicationBuilder app, IWebHostEnvironment env)

+ 88 - 20
vue-next-admin/src/api-services/apis/sys-server-api.ts

@@ -25,7 +25,43 @@ export const SysServerApiAxiosParamCreator = function (configuration?: Configura
     return {
         /**
          * 
-         * @summary 服务器基本配置
+         * @summary 框架主要程序集
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        serverAssemblyGet: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            const localVarPath = `/server/assembly`;
+            // 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
+
+            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 服务器配置信息
          * @param {*} [options] Override http request option.
          * @throws {RequiredError}
          */
@@ -61,12 +97,12 @@ export const SysServerApiAxiosParamCreator = function (configuration?: Configura
         },
         /**
          * 
-         * @summary 服务器网络信息
+         * @summary 服务器磁盘信息
          * @param {*} [options] Override http request option.
          * @throws {RequiredError}
          */
-        serverNetworkGet: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
-            const localVarPath = `/server/network`;
+        serverDiskGet: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            const localVarPath = `/server/disk`;
             // use dummy base URL string because the URL constructor only accepts absolute URLs.
             const localVarUrlObj = new URL(localVarPath, 'https://example.com');
             let baseOptions;
@@ -97,7 +133,7 @@ export const SysServerApiAxiosParamCreator = function (configuration?: Configura
         },
         /**
          * 
-         * @summary 服务器使用资源
+         * @summary 服务器内存信息
          * @param {*} [options] Override http request option.
          * @throws {RequiredError}
          */
@@ -142,7 +178,20 @@ export const SysServerApiFp = function(configuration?: Configuration) {
     return {
         /**
          * 
-         * @summary 服务器基本配置
+         * @summary 框架主要程序集
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async serverAssemblyGet(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<AdminResultObject>>> {
+            const localVarAxiosArgs = await SysServerApiAxiosParamCreator(configuration).serverAssemblyGet(options);
+            return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
+                const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url};
+                return axios.request(axiosRequestArgs);
+            };
+        },
+        /**
+         * 
+         * @summary 服务器配置信息
          * @param {*} [options] Override http request option.
          * @throws {RequiredError}
          */
@@ -155,12 +204,12 @@ export const SysServerApiFp = function(configuration?: Configuration) {
         },
         /**
          * 
-         * @summary 服务器网络信息
+         * @summary 服务器磁盘信息
          * @param {*} [options] Override http request option.
          * @throws {RequiredError}
          */
-        async serverNetworkGet(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<AdminResultObject>>> {
-            const localVarAxiosArgs = await SysServerApiAxiosParamCreator(configuration).serverNetworkGet(options);
+        async serverDiskGet(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<AdminResultObject>>> {
+            const localVarAxiosArgs = await SysServerApiAxiosParamCreator(configuration).serverDiskGet(options);
             return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
                 const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url};
                 return axios.request(axiosRequestArgs);
@@ -168,7 +217,7 @@ export const SysServerApiFp = function(configuration?: Configuration) {
         },
         /**
          * 
-         * @summary 服务器使用资源
+         * @summary 服务器内存信息
          * @param {*} [options] Override http request option.
          * @throws {RequiredError}
          */
@@ -190,7 +239,16 @@ export const SysServerApiFactory = function (configuration?: Configuration, base
     return {
         /**
          * 
-         * @summary 服务器基本配置
+         * @summary 框架主要程序集
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async serverAssemblyGet(options?: AxiosRequestConfig): Promise<AxiosResponse<AdminResultObject>> {
+            return SysServerApiFp(configuration).serverAssemblyGet(options).then((request) => request(axios, basePath));
+        },
+        /**
+         * 
+         * @summary 服务器配置信息
          * @param {*} [options] Override http request option.
          * @throws {RequiredError}
          */
@@ -199,16 +257,16 @@ export const SysServerApiFactory = function (configuration?: Configuration, base
         },
         /**
          * 
-         * @summary 服务器网络信息
+         * @summary 服务器磁盘信息
          * @param {*} [options] Override http request option.
          * @throws {RequiredError}
          */
-        async serverNetworkGet(options?: AxiosRequestConfig): Promise<AxiosResponse<AdminResultObject>> {
-            return SysServerApiFp(configuration).serverNetworkGet(options).then((request) => request(axios, basePath));
+        async serverDiskGet(options?: AxiosRequestConfig): Promise<AxiosResponse<AdminResultObject>> {
+            return SysServerApiFp(configuration).serverDiskGet(options).then((request) => request(axios, basePath));
         },
         /**
          * 
-         * @summary 服务器使用资源
+         * @summary 服务器内存信息
          * @param {*} [options] Override http request option.
          * @throws {RequiredError}
          */
@@ -227,7 +285,17 @@ export const SysServerApiFactory = function (configuration?: Configuration, base
 export class SysServerApi extends BaseAPI {
     /**
      * 
-     * @summary 服务器基本配置
+     * @summary 框架主要程序集
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof SysServerApi
+     */
+    public async serverAssemblyGet(options?: AxiosRequestConfig) : Promise<AxiosResponse<AdminResultObject>> {
+        return SysServerApiFp(this.configuration).serverAssemblyGet(options).then((request) => request(this.axios, this.basePath));
+    }
+    /**
+     * 
+     * @summary 服务器配置信息
      * @param {*} [options] Override http request option.
      * @throws {RequiredError}
      * @memberof SysServerApi
@@ -237,17 +305,17 @@ export class SysServerApi extends BaseAPI {
     }
     /**
      * 
-     * @summary 服务器网络信息
+     * @summary 服务器磁盘信息
      * @param {*} [options] Override http request option.
      * @throws {RequiredError}
      * @memberof SysServerApi
      */
-    public async serverNetworkGet(options?: AxiosRequestConfig) : Promise<AxiosResponse<AdminResultObject>> {
-        return SysServerApiFp(this.configuration).serverNetworkGet(options).then((request) => request(this.axios, this.basePath));
+    public async serverDiskGet(options?: AxiosRequestConfig) : Promise<AxiosResponse<AdminResultObject>> {
+        return SysServerApiFp(this.configuration).serverDiskGet(options).then((request) => request(this.axios, this.basePath));
     }
     /**
      * 
-     * @summary 服务器使用资源
+     * @summary 服务器内存信息
      * @param {*} [options] Override http request option.
      * @throws {RequiredError}
      * @memberof SysServerApi

+ 235 - 45
vue-next-admin/src/views/system/server/index.vue

@@ -2,7 +2,7 @@
   <div class="sys-server-container">
     <el-row :gutter="8">
       <el-col :md="12" :sm="24">
-        <el-card shadow="hover" header="系统信息" style="margin-bottom;: 8px">
+        <el-card shadow="hover" header="系统信息">
           <table class="sysInfo_table">
             <tr>
               <td class="sysInfo_td">主机名称:</td>
@@ -20,6 +20,18 @@
               <td class="sysInfo_td">CPU核数:</td>
               <td class="sysInfo_td">{{ machineBaseInfo.processorCount }}</td>
             </tr>
+            <tr>
+              <td class="sysInfo_td">运行时长:</td>
+              <td class="sysInfo_td">{{ machineBaseInfo.sysRunTime }}</td>
+            </tr>
+            <tr>
+              <td class="sysInfo_td">外网地址:</td>
+              <td class="sysInfo_td">{{ machineBaseInfo.remoteIp }}</td>
+            </tr>
+            <tr>
+              <td class="sysInfo_td">内网地址:</td>
+              <td class="sysInfo_td">{{ machineBaseInfo.localIp }}</td>
+            </tr>
             <tr>
               <td class="sysInfo_td">运行框架:</td>
               <td class="sysInfo_td">{{ machineBaseInfo.frameworkDescription }}</td>
@@ -28,53 +40,87 @@
         </el-card>
       </el-col>
       <el-col :md="12" :sm="24">
-        <el-card shadow="hover" header="网络信息" style="margin-bottom;: 8px">
+        <el-card shadow="hover" header="使用信息">
           <table class="sysInfo_table">
             <tr>
-              <td class="sysInfo_td">外网信息:</td>
-              <td class="sysInfo_td">{{ machineNetworkInfo.wanIp }}</td>
+              <td class="sysInfo_td">启动时间:</td>
+              <td class="sysInfo_td">{{ machineUseInfo.startTime }}</td>
             </tr>
             <tr>
-              <td class="sysInfo_td">IPv4地址:</td>
-              <td class="sysInfo_td">{{ machineNetworkInfo.localIp }}</td>
+              <td class="sysInfo_td">运行时长:</td>
+              <td class="sysInfo_td">{{ machineUseInfo.runTime }}</td>
             </tr>
             <tr>
-              <td class="sysInfo_td">网卡MAC:</td>
-              <td class="sysInfo_td">{{ machineNetworkInfo.ipMac }}</td>
+              <td class="sysInfo_td">内存总量:</td>
+              <td class="sysInfo_td">{{ machineUseInfo.totalRam }}</td>
             </tr>
             <tr>
-              <td class="sysInfo_td">流量统计:</td>
-              <td class="sysInfo_td">{{ machineNetworkInfo.sendAndReceived }}</td>
+              <td class="sysInfo_td">使用内存:</td>
+              <td class="sysInfo_td">{{ machineUseInfo.usedRam }}</td>
             </tr>
             <tr>
-              <td class="sysInfo_td">网络速度:</td>
-              <td class="sysInfo_td">{{ machineNetworkInfo.networkSpeed }}</td>
+              <td class="sysInfo_td">空闲内存:</td>
+              <td class="sysInfo_td">{{ machineUseInfo.freeRam }}</td>
+            </tr>
+            <tr>
+              <td class="sysInfo_td">内存使用率:</td>
+              <td class="sysInfo_td">
+                <el-tag round>{{ machineUseInfo.ramRate }}</el-tag>
+              </td>
+            </tr>
+            <tr>
+              <td class="sysInfo_td">CPU使用率:</td>
+              <td class="sysInfo_td">
+                <el-tag round>{{ machineUseInfo.cpuRate }}</el-tag>
+              </td>
+            </tr>
+            <tr>
+              <td class="sysInfo_td">其他信息:</td>
+              <td class="sysInfo_td">{{ machineBaseInfo.environment }}</td>
             </tr>
           </table>
         </el-card>
       </el-col>
     </el-row>
-    <el-card shadow="hover" header="其他信息" style="margin-top: 8px">
-      <table class="sysInfo_table">
-        <tr>
-          <td class="sysInfo_td">运行时间:</td>
-          <td class="sysInfo_td">{{ machineUseInfo.runTime }}</td>
-          <td class="sysInfo_td">CPU使用率:</td>
-          <td class="sysInfo_td">{{ machineUseInfo.cpuRate }}</td>
-        </tr>
-        <tr>
-          <td class="sysInfo_td">总内存:</td>
-          <td class="sysInfo_td">{{ machineUseInfo.totalRam }}</td>
-          <td class="sysInfo_td">内存使用率:</td>
-          <td class="sysInfo_td">{{ machineUseInfo.ramRate }}</td>
-        </tr>
-      </table>
+    <el-card shadow="hover" header="程序集信息" style="margin-top: 8px;">
+      <div v-for="d in assemblyInfo" :key="d.name" style="display:inline-flex; margin:4px;">
+        <el-tag round>
+          <div style="display:inline-flex;">
+            <div style="">{{ d.name }}</div>
+            <div style="font-size:9px; color:black; margin-left: 3px;">{{ d.version }}</div>
+          </div>
+        </el-tag>
+      </div>
+    </el-card>
+    <el-card shadow="hover" header="磁盘信息" style="margin-top: 8px">
+      <el-table :data="machineDiskInfo" style="width: 100%">
+        <el-table-column prop="diskName" label="盘符">
+          <template #default="scope">
+            <el-tag round> {{ scope.row.diskName }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="typeName" label="类型" />
+        <el-table-column prop="totalSize" label="磁盘总量">
+          <template #default="scope">{{ scope.row.totalSize }} GB</template>
+        </el-table-column>
+        <el-table-column prop="used" label="已使用">
+          <template #default="scope">{{ scope.row.used }} GB</template>
+        </el-table-column>
+        <el-table-column prop="availableFreeSpace" label="剩余量">
+          <template #default="scope">{{ scope.row.availableFreeSpace }} GB</template>
+        </el-table-column>
+        <el-table-column prop="availablePercent" label="使用率">
+          <template #default="scope">
+            <el-tag> {{ scope.row.availablePercent }}%</el-tag>
+          </template>
+        </el-table-column>
+      </el-table>
     </el-card>
   </div>
 </template>
 
 <script lang="ts">
-import { toRefs, reactive, defineComponent, onActivated, onDeactivated } from 'vue';
+import { toRefs, reactive, defineComponent, onActivated, onDeactivated, onMounted } from 'vue';
 
 import { getAPI } from '/@/utils/axios-utils';
 import { SysServerApi } from '/@/api-services';
@@ -87,46 +133,56 @@ export default defineComponent({
       loading: true,
       machineBaseInfo: [] as any,
       machineUseInfo: [] as any,
-      machineNetworkInfo: [] as any,
+      machineDiskInfo: [] as any,
+      assemblyInfo: [] as any,
       timer: null as any,
     });
-
-    // 服务器基本配置
+    onMounted(async () => {
+      state.loading = true;
+      loadMachineBaseInfo();
+      loadMachineUseInfo();
+      loadMachineDiskInfo();
+      loadAssemblyInfo();
+      state.loading = false;
+    });
+    // 服务器配置信息
     const loadMachineBaseInfo = async () => {
       var res = await getAPI(SysServerApi).serverBaseGet();
       state.machineBaseInfo = res.data.result;
     };
-    // 服务器使用资源
+    // 服务器内存信息
     const loadMachineUseInfo = async () => {
       var res = await getAPI(SysServerApi).serverUseGet();
       state.machineUseInfo = res.data.result;
     };
-    // 服务器网络信息
-    const loadMachineNetworkInfo = async () => {
-      var res = await getAPI(SysServerApi).serverNetworkGet();
-      state.machineNetworkInfo = res.data.result;
+    // 服务器磁盘信息
+    const loadMachineDiskInfo = async () => {
+      var res = await getAPI(SysServerApi).serverDiskGet();
+      state.machineDiskInfo = res.data.result;
     };
+    // 框架程序集信息
+    const loadAssemblyInfo = async () => {
+      var res = await getAPI(SysServerApi).serverAssemblyGet();
+      state.assemblyInfo = res.data.result;
+    };
+    // 实时刷新内存
     const refreshData = () => {
       loadMachineUseInfo();
-      loadMachineNetworkInfo();
     };
     onActivated(() => {
       state.timer = setInterval(() => {
         refreshData();
-      }, 3000);
+      }, 5000);
     });
     onDeactivated(() => {
       clearInterval(state.timer);
-      // state.timer = null;
     });
 
-    loadMachineBaseInfo();
-    loadMachineUseInfo();
-    loadMachineNetworkInfo();
     return {
       loadMachineBaseInfo,
       loadMachineUseInfo,
-      loadMachineNetworkInfo,
+      loadMachineDiskInfo,
+      loadAssemblyInfo,
       refreshData,
       ...toRefs(state),
     };
@@ -137,12 +193,146 @@ export default defineComponent({
 <style lang="scss">
 .sysInfo_table {
   width: 100%;
-  min-height: 45px;
-  line-height: 45px;
+  min-height: 40px;
+  line-height: 40px;
   text-align: center;
 }
 
 .sysInfo_td {
   border-bottom: 1px solid #e8e8e8;
 }
+
+.waterfall-last {
+  display: grid;
+  grid-gap: 0.25em;
+  grid-auto-flow: row dense;
+  grid-auto-rows: minmax(188px, 20vmin);
+  grid-template-columns: 1fr;
+
+  .waterfall-last-item {
+    height: 100%;
+    background: var(--el-color-primary);
+    color: var(--el-color-white);
+    transition: all 0.3s ease;
+    border-radius: 3px;
+
+    &:hover {
+      box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%);
+      transition: all 0.3s ease;
+      cursor: pointer;
+    }
+  }
+}
+
+@media (min-width: 576px) {
+  .waterfall-last {
+    grid-template-columns: repeat(7, 1fr);
+
+    .waterfall-last-item {
+      &:nth-of-type(9n + 9) {
+        grid-column: auto / span 2;
+      }
+
+      &:nth-of-type(9n + 8) {
+        grid-column: auto / span 2;
+      }
+
+      &:nth-of-type(9n + 7) {
+        grid-column: auto / span 3;
+      }
+
+      &:nth-of-type(9n + 6) {
+        grid-column: auto / span 2;
+      }
+
+      &:nth-of-type(9n + 5) {
+        grid-column: auto / span 3;
+      }
+
+      &:nth-of-type(9n + 4) {
+        grid-column: auto / span 2;
+      }
+
+      &:nth-of-type(9n + 3) {
+        grid-column: auto / span 3;
+      }
+
+      &:nth-of-type(9n + 2) {
+        grid-column: auto / span 2;
+      }
+
+      &:nth-of-type(9n + 1) {
+        grid-column: auto / span 2;
+      }
+    }
+  }
+}
+
+@media (min-width: 576px) and (min-width: 1024px) {
+  .waterfall-last {
+    grid-template-columns: repeat(14, 1fr);
+
+    .waterfall-last-item {
+      &:nth-of-type(15n + 15) {
+        grid-column: auto / span 3;
+      }
+
+      &:nth-of-type(15n + 14) {
+        grid-column: auto / span 3;
+      }
+
+      &:nth-of-type(15n + 13) {
+        grid-column: auto / span 2;
+      }
+
+      &:nth-of-type(15n + 12) {
+        grid-column: auto / span 3;
+      }
+
+      &:nth-of-type(15n + 11) {
+        grid-column: auto / span 3;
+      }
+
+      &:nth-of-type(15n + 10) {
+        grid-column: auto / span 2;
+      }
+
+      &:nth-of-type(15n + 9) {
+        grid-column: auto / span 3;
+      }
+
+      &:nth-of-type(15n + 8) {
+        grid-column: auto / span 3;
+      }
+
+      &:nth-of-type(15n + 7) {
+        grid-column: auto / span 3;
+      }
+
+      &:nth-of-type(15n + 6) {
+        grid-column: auto / span 3;
+      }
+
+      &:nth-of-type(15n + 5) {
+        grid-column: auto / span 3;
+      }
+
+      &:nth-of-type(15n + 4) {
+        grid-column: auto / span 3;
+      }
+
+      &:nth-of-type(15n + 3) {
+        grid-column: auto / span 3;
+      }
+
+      &:nth-of-type(15n + 2) {
+        grid-column: auto / span 3;
+      }
+
+      &:nth-of-type(15n + 1) {
+        grid-column: auto / span 2;
+      }
+    }
+  }
+}
 </style>

+ 3 - 3
vue-next-admin/src/views/system/user/index.vue

@@ -85,13 +85,13 @@
 									<template #dropdown>
 										<el-dropdown-menu>
 											<el-dropdown-item icon="ele-OfficeBuilding" @click="openGrantOrg(scope.row)"
-												:v-auth="'sysUser:grantData'">数据范围
+												v-auth="'sysUser:grantData'">数据范围
 											</el-dropdown-item>
 											<el-dropdown-item icon="ele-RefreshLeft" @click="resetUserPwd(scope.row)"
-												:v-auth="'sysUser:resetPwd'" disabled>重置密码
+												v-auth="'sysUser:resetPwd'">重置密码
 											</el-dropdown-item>
 											<el-dropdown-item icon="ele-Delete" @click="delUser(scope.row)"
-												:v-auth="'sysUser:delete'">
+												v-auth="'sysUser:delete'">
 												删除账号
 											</el-dropdown-item>
 										</el-dropdown-menu>