From d76cde34c62213e4f9a2b8bb60d239a1b3b7de74 Mon Sep 17 00:00:00 2001 From: zuohuaijun Date: Mon, 16 Jun 2025 00:19:30 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=98=8E1=E3=80=81=E8=B0=83=E6=95=B4?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E7=9B=91=E6=8E=A7=E9=A1=B5=E9=9D=A2=20=20=20?= =?UTF-8?q?2=E3=80=81=E6=B8=85=E7=90=86=E4=B8=8E=E8=B0=83=E6=95=B4?= =?UTF-8?q?=E8=87=AA=E5=B8=A6=E5=B7=A5=E5=85=B7=E7=B1=BB=20=203=E3=80=81?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=95=B0=E6=8D=AE=E5=BA=93=E6=93=8D=E4=BD=9C?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=20=204=E3=80=81=E4=BB=A3=E7=A0=81=E6=B8=85?= =?UTF-8?q?=E7=90=86=E5=8F=8A=E5=8D=87=E7=BA=A7=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Service/App/Auth/AppAuthService.cs | 18 +- .../Service/Test/TestService.cs | 2 +- .../Admin.NET.Core/Admin.NET.Core.csproj | 8 +- .../Admin.NET.Core/Attribute/DictAttribute.cs | 3 +- Admin.NET/Admin.NET.Core/Entity/SysUser.cs | 2 +- .../Admin.NET.Core/Enum/ErrorCodeEnum.cs | 2 +- .../Admin.NET.Core/Extension/EnumExtension.cs | 12 +- .../EnumerableExtensions.cs | 2 +- .../Extension/HttpContextExtension.cs | 2 +- .../Admin.NET.Core/Extension/ListExtension.cs | 51 - .../Extension/ParseExtensions.cs | 459 ++++++ Admin.NET/Admin.NET.Core/GlobalUsings.cs | 1 - .../Logging/DatabaseLoggingWriter.cs | 2 +- .../Logging/ElasticSearchLoggingWriter.cs | 2 +- .../Mqtt/DefaultMqttEventInterceptor.cs | 4 +- .../SeedData/SysUserSeedData.cs | 2 +- .../Service/Auth/SysAuthService.cs | 16 +- .../Service/Auth/SysLdapService.cs | 10 +- .../Service/Cache/SysCacheService.cs | 12 +- .../Service/CodeGen/CustomViewEngine.cs | 4 +- .../Service/CodeGen/Dto/TableOutput.cs | 2 +- .../CodeGen/SysCodeGenConfigService.cs | 8 +- .../Service/CodeGen/SysCodeGenService.cs | 6 +- .../Service/Common/SysCommonService.cs | 9 +- .../Service/DataBase/Dto/DbColumnInput.cs | 27 + .../Service/DataBase/SysDatabaseService.cs | 102 +- .../Service/DataBase/SysDbBackupService.cs | 1 - .../Service/Menu/SysMenuService.cs | 2 +- .../Service/Message/SysMessageService.cs | 5 +- .../Service/Message/SysSmsService.cs | 4 +- .../OpenAccess/SysOpenAccessService.cs | 2 +- .../Service/Region/SysRegionService.cs | 2 +- .../Service/Server/SysServerService.cs | 121 +- .../Service/Tenant/SysTenantService.cs | 8 +- .../Service/User/SysUserService.cs | 20 +- .../Service/Wechat/SysWxOpenService.cs | 20 +- .../SignatureAuthenticationHandler.cs | 14 +- .../SqlSugar/SqlSugarExtension.cs | 2 +- .../Admin.NET.Core/SqlSugar/SqlSugarFilter.cs | 2 +- .../Admin.NET.Core/SqlSugar/SqlSugarSetup.cs | 13 +- .../Utils/AggregationBuilder.cs | 149 -- .../Utils/Collections/CollectionExtensions.cs | 198 +++ .../Utils/Collections/DictionaryExtensions.cs | 171 +++ .../Utils/Collections/LinkedListExtensions.cs | 586 +++++++ .../Utils/Collections/ListExtensions.cs | 374 +++++ .../Utils/Collections/QueueExtensions.cs | 361 +++++ .../Utils/Collections/StackExtensions.cs | 542 +++++++ .../Utils/Collections/TreeExtensions.cs | 375 +++++ .../Utils/CommandLine/ScriptExecutor.cs | 146 ++ .../Utils/CommandLine/ShellHelper.cs | 76 + .../Utils/{CommonUtil.cs => CommonHelper.cs} | 4 +- .../Admin.NET.Core/Utils/ComputerUtil.cs | 521 ------- .../{ => DateTime}/ChinaDateTimeConverter.cs | 0 .../DateTime/DateTimeFormatExtensions.cs | 303 ++++ .../Utils/DateTime/DateTimeRange.cs | 280 ++++ .../Utils/DateTime/LunarCalendarHelper.cs | 866 +++++++++++ .../Admin.NET.Core/Utils/DateTimeUtil.cs | 413 ----- .../Utils/{ => Excel}/ExcelHelper.cs | 2 +- .../MiniExcelHelper.cs} | 6 +- .../Utils/{ => Excel}/XlsxFileResult.cs | 0 .../CryptogramHelper.cs} | 14 +- .../Utils/GM/{GMUtil.cs => GMHelper.cs} | 4 +- .../Utils/HardwareInfo/BoardHelper.cs | 141 ++ .../Utils/HardwareInfo/CpuHelper.cs | 269 ++++ .../Utils/HardwareInfo/DiskHelper.cs | 137 ++ .../Utils/HardwareInfo/GpuHelper.cs | 347 +++++ .../Utils/HardwareInfo/HardwareInfoManager.cs | 227 +++ .../Utils/HardwareInfo/NetworkHelper.cs | 303 ++++ .../Utils/HardwareInfo/RamHelper.cs | 211 +++ Admin.NET/Admin.NET.Core/Utils/HashHelper.cs | 132 ++ .../Admin.NET.Core/Utils/IO/CompressHelper.cs | 170 ++ .../Utils/IO/DirectoryHelper.cs | 286 ++++ .../FileFormatExtensions.cs} | 38 +- .../Admin.NET.Core/Utils/IO/FileHelper.cs | 332 ++++ .../Utils/IO/StreamExtensions.cs | 113 ++ .../Admin.NET.Core/Utils/PathTreeBuilder.cs | 11 +- .../Admin.NET.Core/Utils/ReflectionHelper.cs | 1031 +++++++++++++ Admin.NET/Admin.NET.Core/Utils/RegexHelper.cs | 254 +++ .../Admin.NET.Core/Utils/RegularValidate.cs | 36 - .../Utils/Runtime/OSPlatformHelper.cs | 348 +++++ .../Utils/Runtime/RunningTimeHelper.cs | 95 ++ .../Utils/Runtime/RuntimeInfoManger.cs | 45 + .../Utils/Runtime/RuntimeMonitor.cs | 545 +++++++ Admin.NET/Admin.NET.Core/Utils/SSHHelper.cs | 14 +- .../{ => System}/AdminNETResultProvider.cs | 0 .../Utils/{ => System}/BaseFilter.cs | 0 .../Utils/{ => System}/BaseImportInput.cs | 0 .../Utils/{ => System}/BaseInput.cs | 0 .../Utils/{ => System}/BaseOutput.cs | 0 .../Utils/{ => System}/BasePageInput.cs | 0 .../Utils/{ => System}/BaseStatusInput.cs | 0 .../CodeGenHelper.cs} | 97 +- .../NewtonsoftJsonSerializerProvider.cs | 0 Admin.NET/Admin.NET.Core/Utils/TripleDES.cs | 53 - .../Utils/VerifyFileExtensionName.cs | 6 +- .../Service/DingTalkService.cs | 1 + .../Service/GoViewSys/GoViewSysService.cs | 2 +- Web/src/api-services/apis/sys-database-api.ts | 84 + Web/src/api-services/apis/sys-server-api.ts | 151 +- .../admin-netresult-list-nu-get-package.ts | 71 + .../admin-netresult-system-hardware-info.ts | 69 + .../admin-netresult-system-runtime-info.ts | 69 + Web/src/api-services/models/board-info.ts | 54 + Web/src/api-services/models/cpu-info.ts | 86 ++ .../api-services/models/db-column-input.ts | 6 + Web/src/api-services/models/disk-info.ts | 70 + Web/src/api-services/models/gpu-info.ts | 118 ++ Web/src/api-services/models/index.ts | 16 + .../api-services/models/ip-address-info.ts | 46 + .../models/move-db-column-input.ts | 54 + Web/src/api-services/models/network-info.ts | 134 ++ .../models/network-interface-statistics.ts | 86 ++ Web/src/api-services/models/nu-get-package.ts | 38 + Web/src/api-services/models/ram-info.ts | 78 + Web/src/api-services/models/runtime-info.ts | 246 +++ .../models/system-hardware-info.ts | 70 + .../models/system-runtime-info.ts | 37 + .../models/update-db-column-input.ts | 6 + .../system/database/component/addColumn.vue | 8 + .../system/database/component/addTable.vue | 7 + .../system/database/component/editColumn.vue | 5 + Web/src/views/system/database/index.vue | 70 +- Web/src/views/system/server/index.vue | 1362 ++++++++++++++--- Web/src/views/system/weChatPay/index.vue | 2 +- Web/vite.config.ts | 1 + 125 files changed, 12766 insertions(+), 1897 deletions(-) rename Admin.NET/Admin.NET.Core/{Utils => Extension}/EnumerableExtensions.cs (97%) delete mode 100644 Admin.NET/Admin.NET.Core/Extension/ListExtension.cs create mode 100644 Admin.NET/Admin.NET.Core/Extension/ParseExtensions.cs delete mode 100644 Admin.NET/Admin.NET.Core/Utils/AggregationBuilder.cs create mode 100644 Admin.NET/Admin.NET.Core/Utils/Collections/CollectionExtensions.cs create mode 100644 Admin.NET/Admin.NET.Core/Utils/Collections/DictionaryExtensions.cs create mode 100644 Admin.NET/Admin.NET.Core/Utils/Collections/LinkedListExtensions.cs create mode 100644 Admin.NET/Admin.NET.Core/Utils/Collections/ListExtensions.cs create mode 100644 Admin.NET/Admin.NET.Core/Utils/Collections/QueueExtensions.cs create mode 100644 Admin.NET/Admin.NET.Core/Utils/Collections/StackExtensions.cs create mode 100644 Admin.NET/Admin.NET.Core/Utils/Collections/TreeExtensions.cs create mode 100644 Admin.NET/Admin.NET.Core/Utils/CommandLine/ScriptExecutor.cs create mode 100644 Admin.NET/Admin.NET.Core/Utils/CommandLine/ShellHelper.cs rename Admin.NET/Admin.NET.Core/Utils/{CommonUtil.cs => CommonHelper.cs} (99%) delete mode 100644 Admin.NET/Admin.NET.Core/Utils/ComputerUtil.cs rename Admin.NET/Admin.NET.Core/Utils/{ => DateTime}/ChinaDateTimeConverter.cs (100%) create mode 100644 Admin.NET/Admin.NET.Core/Utils/DateTime/DateTimeFormatExtensions.cs create mode 100644 Admin.NET/Admin.NET.Core/Utils/DateTime/DateTimeRange.cs create mode 100644 Admin.NET/Admin.NET.Core/Utils/DateTime/LunarCalendarHelper.cs delete mode 100644 Admin.NET/Admin.NET.Core/Utils/DateTimeUtil.cs rename Admin.NET/Admin.NET.Core/Utils/{ => Excel}/ExcelHelper.cs (98%) rename Admin.NET/Admin.NET.Core/Utils/{MiniExcelUtil.cs => Excel/MiniExcelHelper.cs} (95%) rename Admin.NET/Admin.NET.Core/Utils/{ => Excel}/XlsxFileResult.cs (100%) rename Admin.NET/Admin.NET.Core/Utils/{CryptogramUtil.cs => GM/CryptogramHelper.cs} (90%) rename Admin.NET/Admin.NET.Core/Utils/GM/{GMUtil.cs => GMHelper.cs} (99%) create mode 100644 Admin.NET/Admin.NET.Core/Utils/HardwareInfo/BoardHelper.cs create mode 100644 Admin.NET/Admin.NET.Core/Utils/HardwareInfo/CpuHelper.cs create mode 100644 Admin.NET/Admin.NET.Core/Utils/HardwareInfo/DiskHelper.cs create mode 100644 Admin.NET/Admin.NET.Core/Utils/HardwareInfo/GpuHelper.cs create mode 100644 Admin.NET/Admin.NET.Core/Utils/HardwareInfo/HardwareInfoManager.cs create mode 100644 Admin.NET/Admin.NET.Core/Utils/HardwareInfo/NetworkHelper.cs create mode 100644 Admin.NET/Admin.NET.Core/Utils/HardwareInfo/RamHelper.cs create mode 100644 Admin.NET/Admin.NET.Core/Utils/HashHelper.cs create mode 100644 Admin.NET/Admin.NET.Core/Utils/IO/CompressHelper.cs create mode 100644 Admin.NET/Admin.NET.Core/Utils/IO/DirectoryHelper.cs rename Admin.NET/Admin.NET.Core/Utils/{ReflectionUtil.cs => IO/FileFormatExtensions.cs} (52%) create mode 100644 Admin.NET/Admin.NET.Core/Utils/IO/FileHelper.cs create mode 100644 Admin.NET/Admin.NET.Core/Utils/IO/StreamExtensions.cs create mode 100644 Admin.NET/Admin.NET.Core/Utils/ReflectionHelper.cs create mode 100644 Admin.NET/Admin.NET.Core/Utils/RegexHelper.cs delete mode 100644 Admin.NET/Admin.NET.Core/Utils/RegularValidate.cs create mode 100644 Admin.NET/Admin.NET.Core/Utils/Runtime/OSPlatformHelper.cs create mode 100644 Admin.NET/Admin.NET.Core/Utils/Runtime/RunningTimeHelper.cs create mode 100644 Admin.NET/Admin.NET.Core/Utils/Runtime/RuntimeInfoManger.cs create mode 100644 Admin.NET/Admin.NET.Core/Utils/Runtime/RuntimeMonitor.cs rename Admin.NET/Admin.NET.Core/Utils/{ => System}/AdminNETResultProvider.cs (100%) rename Admin.NET/Admin.NET.Core/Utils/{ => System}/BaseFilter.cs (100%) rename Admin.NET/Admin.NET.Core/Utils/{ => System}/BaseImportInput.cs (100%) rename Admin.NET/Admin.NET.Core/Utils/{ => System}/BaseInput.cs (100%) rename Admin.NET/Admin.NET.Core/Utils/{ => System}/BaseOutput.cs (100%) rename Admin.NET/Admin.NET.Core/Utils/{ => System}/BasePageInput.cs (100%) rename Admin.NET/Admin.NET.Core/Utils/{ => System}/BaseStatusInput.cs (100%) rename Admin.NET/Admin.NET.Core/Utils/{CodeGenUtil.cs => System/CodeGenHelper.cs} (77%) rename Admin.NET/Admin.NET.Core/Utils/{ => System}/NewtonsoftJsonSerializerProvider.cs (100%) delete mode 100644 Admin.NET/Admin.NET.Core/Utils/TripleDES.cs create mode 100644 Web/src/api-services/models/admin-netresult-list-nu-get-package.ts create mode 100644 Web/src/api-services/models/admin-netresult-system-hardware-info.ts create mode 100644 Web/src/api-services/models/admin-netresult-system-runtime-info.ts create mode 100644 Web/src/api-services/models/board-info.ts create mode 100644 Web/src/api-services/models/cpu-info.ts create mode 100644 Web/src/api-services/models/disk-info.ts create mode 100644 Web/src/api-services/models/gpu-info.ts create mode 100644 Web/src/api-services/models/ip-address-info.ts create mode 100644 Web/src/api-services/models/move-db-column-input.ts create mode 100644 Web/src/api-services/models/network-info.ts create mode 100644 Web/src/api-services/models/network-interface-statistics.ts create mode 100644 Web/src/api-services/models/nu-get-package.ts create mode 100644 Web/src/api-services/models/ram-info.ts create mode 100644 Web/src/api-services/models/runtime-info.ts create mode 100644 Web/src/api-services/models/system-hardware-info.ts create mode 100644 Web/src/api-services/models/system-runtime-info.ts diff --git a/Admin.NET/Admin.NET.Application/Service/App/Auth/AppAuthService.cs b/Admin.NET/Admin.NET.Application/Service/App/Auth/AppAuthService.cs index de96e68d..e9d73a5f 100644 --- a/Admin.NET/Admin.NET.Application/Service/App/Auth/AppAuthService.cs +++ b/Admin.NET/Admin.NET.Application/Service/App/Auth/AppAuthService.cs @@ -89,7 +89,7 @@ public class AppAuthService : IDynamicApiController, ITransient // 国密SM2解密(前端密码传输SM2加密后的) try { - input.Password = CryptogramUtil.SM2Decrypt(input.Password); + input.Password = CryptogramHelper.SM2Decrypt(input.Password); } catch { @@ -113,7 +113,7 @@ public class AppAuthService : IDynamicApiController, ITransient /// private void VerifyPassword(LoginInput input, string keyErrorPasswordCount, int errorPasswordCount, SysUser user) { - if (CryptogramUtil.CryptoType == CryptogramEnum.MD5.ToString()) + if (CryptogramHelper.CryptoType == CryptogramEnum.MD5.ToString()) { if (!user.Password.Equals(MD5Encryption.Encrypt(input.Password))) { @@ -123,7 +123,7 @@ public class AppAuthService : IDynamicApiController, ITransient } else { - if (!CryptogramUtil.Decrypt(user.Password).Equals(input.Password)) + if (!CryptogramHelper.Decrypt(user.Password).Equals(input.Password)) { _sysCacheService.Set(keyErrorPasswordCount, ++errorPasswordCount, TimeSpan.FromMinutes(30)); throw Oops.Oh(ErrorCodeEnum.D1000); @@ -284,18 +284,18 @@ public class AppAuthService : IDynamicApiController, ITransient public async Task ChangePwd(ChangePwdInput input) { // 国密SM2解密(前端密码传输SM2加密后的) - input.PasswordOld = CryptogramUtil.SM2Decrypt(input.PasswordOld); - input.PasswordNew = CryptogramUtil.SM2Decrypt(input.PasswordNew); + input.PasswordOld = CryptogramHelper.SM2Decrypt(input.PasswordOld); + input.PasswordNew = CryptogramHelper.SM2Decrypt(input.PasswordNew); var user = await _sysUserRep.GetByIdAsync(_appUserManager.UserId) ?? throw Oops.Oh(ErrorCodeEnum.D0009); - if (CryptogramUtil.CryptoType == CryptogramEnum.MD5.ToString()) + if (CryptogramHelper.CryptoType == CryptogramEnum.MD5.ToString()) { if (user.Password != MD5Encryption.Encrypt(input.PasswordOld)) throw Oops.Oh(ErrorCodeEnum.D1004); } else { - if (CryptogramUtil.Decrypt(user.Password) != input.PasswordOld) + if (CryptogramHelper.Decrypt(user.Password) != input.PasswordOld) throw Oops.Oh(ErrorCodeEnum.D1004); } @@ -307,12 +307,12 @@ public class AppAuthService : IDynamicApiController, ITransient { var sysConfig = await _sysConfigService.GetConfig(ConfigConst.SysPasswordStrengthExpression); user.Password = input.PasswordNew.TryValidate(sysConfig.Value) - ? CryptogramUtil.Encrypt(input.PasswordNew) + ? CryptogramHelper.Encrypt(input.PasswordNew) : throw Oops.Oh(sysConfig.Remark); } else { - user.Password = CryptogramUtil.Encrypt(input.PasswordNew); + user.Password = CryptogramHelper.Encrypt(input.PasswordNew); } return await _sysUserRep.AsUpdateable(user).UpdateColumns(u => u.Password).ExecuteCommandAsync(); diff --git a/Admin.NET/Admin.NET.Application/Service/Test/TestService.cs b/Admin.NET/Admin.NET.Application/Service/Test/TestService.cs index f1db5e9b..6321f132 100644 --- a/Admin.NET/Admin.NET.Application/Service/Test/TestService.cs +++ b/Admin.NET/Admin.NET.Application/Service/Test/TestService.cs @@ -50,7 +50,7 @@ public class TestService : IDynamicApiController { //L.SetCulture("en-US", true); - var cultureName = L.GetSelectCulture().Culture.Name; + //var cultureName = L.GetSelectCulture().Culture.Name; return L.Text["Admin.NET 通用权限开发平台"]; } diff --git a/Admin.NET/Admin.NET.Core/Admin.NET.Core.csproj b/Admin.NET/Admin.NET.Core/Admin.NET.Core.csproj index aa595635..27dcf9d9 100644 --- a/Admin.NET/Admin.NET.Core/Admin.NET.Core.csproj +++ b/Admin.NET/Admin.NET.Core/Admin.NET.Core.csproj @@ -28,9 +28,9 @@ - - - + + + @@ -68,7 +68,6 @@ - @@ -79,7 +78,6 @@ - diff --git a/Admin.NET/Admin.NET.Core/Attribute/DictAttribute.cs b/Admin.NET/Admin.NET.Core/Attribute/DictAttribute.cs index 1436b400..c01e91a9 100644 --- a/Admin.NET/Admin.NET.Core/Attribute/DictAttribute.cs +++ b/Admin.NET/Admin.NET.Core/Attribute/DictAttribute.cs @@ -75,8 +75,7 @@ public class DictAttribute : ValidationAttribute, ITransient if (value == null && AllowNullValue) return ValidationResult.Success; // 处理集合为空的情况 - var collection = value as IEnumerable; - if (collection == null) return ValidationResult.Success; + if (value is not IEnumerable collection) return ValidationResult.Success; // 获取集合的元素类型 var elementType = propertyType.GetGenericArguments()[0]; diff --git a/Admin.NET/Admin.NET.Core/Entity/SysUser.cs b/Admin.NET/Admin.NET.Core/Entity/SysUser.cs index 11434567..7b434a4e 100644 --- a/Admin.NET/Admin.NET.Core/Entity/SysUser.cs +++ b/Admin.NET/Admin.NET.Core/Entity/SysUser.cs @@ -126,7 +126,7 @@ public partial class SysUser : EntityTenant /// /// 毕业院校 - /// COLLEGE + /// [SugarColumn(ColumnDescription = "毕业院校", Length = 128)] [MaxLength(128)] public string? College { get; set; } diff --git a/Admin.NET/Admin.NET.Core/Enum/ErrorCodeEnum.cs b/Admin.NET/Admin.NET.Core/Enum/ErrorCodeEnum.cs index c98d42b5..71ce30c5 100644 --- a/Admin.NET/Admin.NET.Core/Enum/ErrorCodeEnum.cs +++ b/Admin.NET/Admin.NET.Core/Enum/ErrorCodeEnum.cs @@ -530,7 +530,7 @@ public enum ErrorCodeEnum D8000, /// - /// 不允许的文件类型 + /// 不允许的文件类型 /// [ErrorCodeItemMetadata("不允许的文件类型:{0}")] D8001, diff --git a/Admin.NET/Admin.NET.Core/Extension/EnumExtension.cs b/Admin.NET/Admin.NET.Core/Extension/EnumExtension.cs index 2b559879..c6c6abce 100644 --- a/Admin.NET/Admin.NET.Core/Extension/EnumExtension.cs +++ b/Admin.NET/Admin.NET.Core/Extension/EnumExtension.cs @@ -31,7 +31,7 @@ public static class EnumExtension throw new ArgumentException("Type '" + enumType.Name + "' is not an enum."); // 查询缓存 - var enumDic = EnumNameValueDict.TryGetValue(enumType, out var value) ? value : new Dictionary(); + var enumDic = EnumNameValueDict.TryGetValue(enumType, out var value) ? value : []; if (enumDic.Count != 0) return enumDic; // 取枚举类型的Key/Value字典集合 @@ -76,9 +76,7 @@ public static class EnumExtension throw new ArgumentException("Type '" + enumType.Name + "' is not an enum."); // 查询缓存 - var enumDic = EnumDisplayValueDict.TryGetValue(enumType, out var value) - ? value - : new Dictionary(); + var enumDic = EnumDisplayValueDict.TryGetValue(enumType, out var value) ? value : []; if (enumDic.Count != 0) return enumDic; // 取枚举类型的Key/Value字典集合 @@ -106,7 +104,11 @@ public static class EnumExtension foreach (var enumField in enumFields) { var intValue = (int)enumField.GetValue(enumType)!; - var desc = enumField.GetDescriptionValue(); + // 获取字段的指定特性,不包含继承中的特性 + object[] customAttributes = enumField.GetCustomAttributes(typeof(DescriptionAttribute), false); + // 如果没有数据返回null + var desc = customAttributes.Length > 0 ? (DescriptionAttribute)customAttributes[0] : null; + enumDic[intValue] = desc != null && !string.IsNullOrEmpty(desc.Description) ? desc.Description : enumField.Name; } diff --git a/Admin.NET/Admin.NET.Core/Utils/EnumerableExtensions.cs b/Admin.NET/Admin.NET.Core/Extension/EnumerableExtensions.cs similarity index 97% rename from Admin.NET/Admin.NET.Core/Utils/EnumerableExtensions.cs rename to Admin.NET/Admin.NET.Core/Extension/EnumerableExtensions.cs index f4270ee6..896caab7 100644 --- a/Admin.NET/Admin.NET.Core/Utils/EnumerableExtensions.cs +++ b/Admin.NET/Admin.NET.Core/Extension/EnumerableExtensions.cs @@ -4,7 +4,7 @@ // // 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! -namespace Admin.NET.Core.Utils; +namespace Admin.NET.Core; public static class EnumerableExtensions { diff --git a/Admin.NET/Admin.NET.Core/Extension/HttpContextExtension.cs b/Admin.NET/Admin.NET.Core/Extension/HttpContextExtension.cs index 3199c62a..d8791e81 100644 --- a/Admin.NET/Admin.NET.Core/Extension/HttpContextExtension.cs +++ b/Admin.NET/Admin.NET.Core/Extension/HttpContextExtension.cs @@ -42,7 +42,7 @@ public static class HttpContextExtension { ArgumentNullException.ThrowIfNull(context); - return CommonUtil.GetClientDeviceInfo(context.Request.Headers.UserAgent); + return CommonHelper.GetClientDeviceInfo(context.Request.Headers.UserAgent); } /// diff --git a/Admin.NET/Admin.NET.Core/Extension/ListExtension.cs b/Admin.NET/Admin.NET.Core/Extension/ListExtension.cs deleted file mode 100644 index 722cf510..00000000 --- a/Admin.NET/Admin.NET.Core/Extension/ListExtension.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 -// -// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 -// -// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! - -namespace Admin.NET.Core; - -/// -/// List 扩展方法 -/// -public static class ListExtension -{ - public static async Task ForEachAsync(this List list, Func func) - { - foreach (var value in list) - { - await func(value); - } - } - - public static async Task ForEachAsync(this IEnumerable source, Func action) - { - foreach (var value in source) - { - await action(value); - } - } - - public static void ForEach(this IEnumerable enumerable, Action consumer) - { - foreach (T item in enumerable) - { - consumer(item); - } - } - - public static void AddRange(this IList list, IEnumerable items) - { - if (list is List list2) - { - list2.AddRange(items); - return; - } - - foreach (T item in items) - { - list.Add(item); - } - } -} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Extension/ParseExtensions.cs b/Admin.NET/Admin.NET.Core/Extension/ParseExtensions.cs new file mode 100644 index 00000000..3e33a373 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Extension/ParseExtensions.cs @@ -0,0 +1,459 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 对象转换扩展方法 +/// +public static class ParseExtensions +{ + #region Bool + + /// + /// 对象转布尔值 + /// + /// + /// + public static bool ParseToBool(this object? thisValue) + { + return thisValue is not null && thisValue != DBNull.Value && bool.TryParse(thisValue.ToString(), out var reveal) && reveal; + } + + #endregion Bool + + #region Short + + /// + /// 对象转短整数 + /// + /// + /// + public static short ParseToShort(this object? thisValue) + { + return thisValue is not null && thisValue != DBNull.Value && + short.TryParse(thisValue.ToString(), out var reveal) + ? reveal + : (short)0; + } + + /// + /// 对象转短整数 + /// + /// + /// + /// + public static short ParseToShort(this object? thisValue, short errorValue) + { + return thisValue is not null && thisValue != DBNull.Value && + short.TryParse(thisValue.ToString(), out var reveal) + ? reveal + : errorValue; + } + + #endregion Short + + #region Long + + /// + /// 对象转长整数 + /// + /// + /// + public static long ParseToLong(this object? thisValue) + { + return thisValue is not null && thisValue != DBNull.Value && + long.TryParse(thisValue.ToString(), out var reveal) + ? reveal + : 0L; + } + + /// + /// 对象转长整数 + /// + /// + /// + /// + public static long ParseToLong(this object? thisValue, long errorValue) + { + return thisValue is not null && thisValue != DBNull.Value && + long.TryParse(thisValue.ToString(), out var reveal) + ? reveal + : errorValue; + } + + #endregion Long + + #region Float + + /// + /// 对象转浮点数 + /// ±1.5 x 10 e−45 至 ±3.4 x 10 e38 大约 6-9 位数字 + /// + /// + /// + public static float ParseToFloat(this object? thisValue) + { + return thisValue is not null && thisValue != DBNull.Value && + float.TryParse(thisValue.ToString(), out var reveal) + ? reveal + : 0.0F; + } + + /// + /// 对象转浮点数 + /// ±1.5 x 10 e−45 至 ±3.4 x 10 e38 大约 6-9 位数字 + /// + /// + /// + /// + public static float ParseToFloat(this object? thisValue, float errorValue) + { + return thisValue is not null && thisValue != DBNull.Value && + float.TryParse(thisValue.ToString(), out var reveal) + ? reveal + : errorValue; + } + + #endregion Float + + #region Double + + /// + /// 对象转浮点数 + /// ±5.0 × 10 e−324 到 ±1.7 × 10 e308 大约 15-17 位数字 + /// + /// + /// + public static double ParseToDouble(this object? thisValue) + { + return thisValue is not null && thisValue != DBNull.Value && + double.TryParse(thisValue.ToString(), out var reveal) + ? reveal + : 0.0D; + } + + /// + /// 对象转浮点数 + /// ±5.0 × 10 e−324 到 ±1.7 × 10 e308 大约 15-17 位数字 + /// + /// + /// + /// + public static double ParseToDouble(this object? thisValue, double errorValue) + { + return thisValue is not null && thisValue != DBNull.Value && + double.TryParse(thisValue.ToString(), out var reveal) + ? reveal + : errorValue; + } + + #endregion Double + + #region Decimal + + /// + /// 对象转浮点数 + /// ±1.0 x 10 e-28 至 ±7.9228 x 10 e28 28-29 位 + /// + /// + /// + public static decimal ParseToDecimal(this object? thisValue) + { + return thisValue is not null && thisValue != DBNull.Value && + decimal.TryParse(thisValue.ToString(), out var reveal) + ? reveal + : 0M; + } + + /// + /// 对象转浮点数 + /// ±1.0 x 10 e-28 至 ±7.9228 x 10 e28 28-29 位 + /// + /// + /// + /// + public static decimal ParseToDecimal(this object? thisValue, decimal errorValue) + { + return thisValue is not null && thisValue != DBNull.Value && + decimal.TryParse(thisValue.ToString(), out var reveal) + ? reveal + : errorValue; + } + + #endregion Decimal + + #region Int + + /// + /// 对象转数字 + /// + /// + /// + public static int ParseToInt(this object? thisValue) + { + return thisValue is null ? 0 : + thisValue != DBNull.Value && int.TryParse(thisValue.ToString(), out var reveal) ? reveal : 0; + } + + /// + /// 对象转数字 + /// + /// + /// + /// + public static int ParseToInt(this object? thisValue, int errorValue) + { + return thisValue is not null && thisValue != DBNull.Value && + int.TryParse(thisValue.ToString(), out var reveal) + ? reveal + : errorValue; + } + + #endregion Int + + #region Money + + /// + /// 对象转金额 + /// + /// + /// + public static double ParseToMoney(this object? thisValue) + { + return thisValue is not null && thisValue != DBNull.Value && + double.TryParse(thisValue.ToString(), out var reveal) + ? reveal + : 0; + } + + /// + /// 对象转金额 + /// + /// + /// + /// + public static double ParseToMoney(this object? thisValue, double errorValue) + { + return thisValue is not null && thisValue != DBNull.Value && + double.TryParse(thisValue.ToString(), out var reveal) + ? reveal + : errorValue; + } + + #endregion Money + + #region String + + /// + /// 对象转字符串 + /// + /// + /// + public static string ParseToString(this object? thisValue) + { + return thisValue is not null ? thisValue.ToString()!.Trim() : string.Empty; + } + + /// + /// 对象转字符串 + /// + /// + /// + /// + public static string ParseToString(this object? thisValue, string errorValue) + { + return thisValue is not null ? thisValue.ToString()!.Trim() : errorValue; + } + + /// + /// 判断是否为空 + /// + /// + /// + public static bool IsEmptyOrNull(this object? thisValue) + { + return !thisValue.IsNotEmptyOrNull(); + } + + /// + /// 判断是否为空 + /// + /// + /// + public static bool IsNotEmptyOrNull(this object? thisValue) + { + return thisValue is not null && thisValue.ParseToString() != string.Empty && + thisValue.ParseToString() != string.Empty && + thisValue.ParseToString() != "undefined" && thisValue.ParseToString() != "null"; + } + + /// + /// 判断是否为空或零 + /// + /// + /// + public static bool IsNullOrZero(this object? thisValue) + { + return !thisValue.IsNotNullOrZero(); + } + + /// + /// 判断是否为空或零 + /// + /// + /// + public static bool IsNotNullOrZero(this object? thisValue) + { + return thisValue.IsNotEmptyOrNull() && thisValue.ParseToString() != "0"; + } + + #endregion String + + #region DateTime + + /// + /// 对象转日期 + /// + /// + /// + public static DateTime ParseToDateTime(this object? thisValue) + { + var reveal = DateTime.MinValue; + if (thisValue is not null && thisValue != DBNull.Value && DateTime.TryParse(thisValue.ToString(), out reveal)) + { + reveal = Convert.ToDateTime(thisValue); + } + + return reveal; + } + + /// + /// 对象转日期 + /// + /// + /// + /// + public static DateTime ParseToDateTime(this object? thisValue, DateTime errorValue) + { + return thisValue is not null && thisValue != DBNull.Value && DateTime.TryParse(thisValue.ToString(), out var reveal) + ? reveal + : errorValue; + } + + #endregion DateTime + + #region DateTimeOffset + + /// + /// 对象转 DateTimeOffset + /// + /// + /// + public static DateTimeOffset ParseToDateTimeOffset(this object? thisValue) + { + return thisValue is not null && thisValue != DBNull.Value && + DateTimeOffset.TryParse(thisValue.ToString(), out var reveal) + ? reveal + : DateTimeOffset.MinValue; + } + + /// + /// 对象转 DateTimeOffset + /// + /// + /// + /// + public static DateTimeOffset ParseToDateTimeOffset(this object? thisValue, DateTimeOffset errorValue) + { + return thisValue is not null && thisValue != DBNull.Value && + DateTimeOffset.TryParse(thisValue.ToString(), out var reveal) + ? reveal + : errorValue; + } + + #endregion DateTimeOffset + + #region TimeSpan + + /// + /// 对象转 DateTimeOffset + /// + /// + /// + public static TimeSpan ParseToTimeSpan(this object? thisValue) + { + return thisValue is not null && thisValue != DBNull.Value && + TimeSpan.TryParse(thisValue.ToString(), out var reveal) + ? reveal + : TimeSpan.Zero; + } + + /// + /// 对象转 DateTimeOffset + /// + /// + /// + /// + public static TimeSpan ParseToTimeSpan(this object? thisValue, TimeSpan errorValue) + { + return thisValue is not null && thisValue != DBNull.Value && + TimeSpan.TryParse(thisValue.ToString(), out var reveal) + ? reveal + : errorValue; + } + + #endregion TimeSpan + + #region Guid + + /// + /// 将 string 转换为 Guid + /// 若转换失败,则返回 Guid.Empty,不抛出异常。 + /// + /// + /// + public static Guid ParseToGuid(this object? thisValue) + { + try + { + return new Guid(thisValue.ParseToString()); + } + catch + { + return Guid.Empty; + } + } + + #endregion Guid + + #region Dictionary + + /// + /// 对象转换成字典 + /// + /// + /// + public static IEnumerable> ParseToDictionary(this object? obj) + { + if (obj is not IEnumerable objDynamics) + { + yield break; + } + + foreach (var objDynamic in objDynamics) + { + // 找到所有的没有此特性、或有此特性但忽略字段的属性 + var item = (objDynamic as object).GetType().GetProperties() + .ToDictionary(prop => prop.Name, prop => prop.GetValue(objDynamic, null)); + + yield return item; + } + } + + #endregion Dictionary + +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/GlobalUsings.cs b/Admin.NET/Admin.NET.Core/GlobalUsings.cs index 19392a71..fbcf2a7e 100644 --- a/Admin.NET/Admin.NET.Core/GlobalUsings.cs +++ b/Admin.NET/Admin.NET.Core/GlobalUsings.cs @@ -5,7 +5,6 @@ // 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! global using Admin.NET.Core.Service; -global using Admin.NET.Core.Utils; global using Furion; global using Furion.ConfigurableOptions; global using Furion.DatabaseAccessor; diff --git a/Admin.NET/Admin.NET.Core/Logging/DatabaseLoggingWriter.cs b/Admin.NET/Admin.NET.Core/Logging/DatabaseLoggingWriter.cs index f7994c67..6dfc933d 100644 --- a/Admin.NET/Admin.NET.Core/Logging/DatabaseLoggingWriter.cs +++ b/Admin.NET/Admin.NET.Core/Logging/DatabaseLoggingWriter.cs @@ -113,7 +113,7 @@ public class DatabaseLoggingWriter : IDatabaseLoggingWriter, IDisposable // 获取IP地理位置 if (string.IsNullOrEmpty(remoteIPv4)) remoteIPv4 = loggingMonitor.remoteIPv4; - (string ipLocation, double? longitude, double? latitude) = CommonUtil.GetIpAddress(remoteIPv4); + (string ipLocation, double? longitude, double? latitude) = CommonHelper.GetIpAddress(remoteIPv4); // 获取设备信息 var browser = ""; diff --git a/Admin.NET/Admin.NET.Core/Logging/ElasticSearchLoggingWriter.cs b/Admin.NET/Admin.NET.Core/Logging/ElasticSearchLoggingWriter.cs index f68697a3..637d1800 100644 --- a/Admin.NET/Admin.NET.Core/Logging/ElasticSearchLoggingWriter.cs +++ b/Admin.NET/Admin.NET.Core/Logging/ElasticSearchLoggingWriter.cs @@ -57,7 +57,7 @@ public class ElasticSearchLoggingWriter : IDatabaseLoggingWriter, IDisposable } string remoteIPv4 = loggingMonitor.remoteIPv4; - (string ipLocation, double? longitude, double? latitude) = CommonUtil.GetIpAddress(remoteIPv4); + (string ipLocation, double? longitude, double? latitude) = CommonHelper.GetIpAddress(remoteIPv4); var sysLogOp = new SysLogOp { diff --git a/Admin.NET/Admin.NET.Core/Mqtt/DefaultMqttEventInterceptor.cs b/Admin.NET/Admin.NET.Core/Mqtt/DefaultMqttEventInterceptor.cs index f77a0283..2c57ceb8 100644 --- a/Admin.NET/Admin.NET.Core/Mqtt/DefaultMqttEventInterceptor.cs +++ b/Admin.NET/Admin.NET.Core/Mqtt/DefaultMqttEventInterceptor.cs @@ -30,14 +30,14 @@ public class DefaultMqttEventInterceptor : MqttEventInterceptor // 验证密码 var password = arg.Password; - if (CryptogramUtil.CryptoType == CryptogramEnum.MD5.ToString()) + if (CryptogramHelper.CryptoType == CryptogramEnum.MD5.ToString()) { if (user.Password.Equals(MD5Encryption.Encrypt(password))) return Task.CompletedTask; } else { - if (CryptogramUtil.Decrypt(user.Password).Equals(password)) + if (CryptogramHelper.Decrypt(user.Password).Equals(password)) return Task.CompletedTask; } arg.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword; diff --git a/Admin.NET/Admin.NET.Core/SeedData/SysUserSeedData.cs b/Admin.NET/Admin.NET.Core/SeedData/SysUserSeedData.cs index e3777f90..1a5241c9 100644 --- a/Admin.NET/Admin.NET.Core/SeedData/SysUserSeedData.cs +++ b/Admin.NET/Admin.NET.Core/SeedData/SysUserSeedData.cs @@ -18,7 +18,7 @@ public class SysUserSeedData : ISqlSugarEntitySeedData /// public IEnumerable HasData() { - var encryptPassword = CryptogramUtil.Encrypt(new SysConfigSeedData().HasData().First(u => u.Code == ConfigConst.SysPassword).Value); + var encryptPassword = CryptogramHelper.Encrypt(new SysConfigSeedData().HasData().First(u => u.Code == ConfigConst.SysPassword).Value); var posList = new SysPosSeedData().HasData().ToList(); return [ diff --git a/Admin.NET/Admin.NET.Core/Service/Auth/SysAuthService.cs b/Admin.NET/Admin.NET.Core/Service/Auth/SysAuthService.cs index fcaa8886..30c7cd96 100644 --- a/Admin.NET/Admin.NET.Core/Service/Auth/SysAuthService.cs +++ b/Admin.NET/Admin.NET.Core/Service/Auth/SysAuthService.cs @@ -81,7 +81,7 @@ public class SysAuthService : IDynamicApiController, ITransient { VerifyPassword(input.Password, keyPasswordErrorTimes, passwordErrorTimes, user); } - else if (!await App.GetRequiredService().AuthAccount(user.TenantId, userLdap.Account, CryptogramUtil.Decrypt(input.Password))) + else if (!await App.GetRequiredService().AuthAccount(user.TenantId, userLdap.Account, CryptogramHelper.Decrypt(input.Password))) { _sysCacheService.Set(keyPasswordErrorTimes, ++passwordErrorTimes, TimeSpan.FromMinutes(30)); throw Oops.Oh(ErrorCodeEnum.D1000); @@ -143,20 +143,20 @@ public class SysAuthService : IDynamicApiController, ITransient // 国密SM2解密(前端密码传输SM2加密后的) try { - password = CryptogramUtil.SM2Decrypt(password); + password = CryptogramHelper.SM2Decrypt(password); } catch { throw Oops.Oh(ErrorCodeEnum.D0010); } - if (CryptogramUtil.CryptoType == CryptogramEnum.MD5.ToString()) + if (CryptogramHelper.CryptoType == CryptogramEnum.MD5.ToString()) { if (user.Password.Equals(MD5Encryption.Encrypt(password))) return; } else { - if (CryptogramUtil.Decrypt(user.Password).Equals(password)) return; + if (CryptogramHelper.Decrypt(user.Password).Equals(password)) return; } _sysCacheService.Set(keyPasswordErrorTimes, ++passwordErrorTimes, TimeSpan.FromMinutes(30)); @@ -186,7 +186,7 @@ public class SysAuthService : IDynamicApiController, ITransient { VerifyPassword(password, keyPasswordErrorTimes, passwordErrorTimes, user); } - else if (!await App.GetRequiredService().AuthAccount(user.TenantId.Value, userLdap.Account, CryptogramUtil.Decrypt(password))) + else if (!await App.GetRequiredService().AuthAccount(user.TenantId.Value, userLdap.Account, CryptogramHelper.Decrypt(password))) { _sysCacheService.Set(keyPasswordErrorTimes, ++passwordErrorTimes, TimeSpan.FromMinutes(30)); throw Oops.Oh(ErrorCodeEnum.D1000); @@ -256,9 +256,9 @@ public class SysAuthService : IDynamicApiController, ITransient // 更新用户登录信息 user.LastLoginIp = _httpContextAccessor.HttpContext.GetRemoteIpAddressToIPv4(true); - (user.LastLoginAddress, double? longitude, double? latitude) = CommonUtil.GetIpAddress(user.LastLoginIp); + (user.LastLoginAddress, double? longitude, double? latitude) = CommonHelper.GetIpAddress(user.LastLoginIp); user.LastLoginTime = DateTime.Now; - user.LastLoginDevice = CommonUtil.GetClientDeviceInfo(_httpContextAccessor.HttpContext?.Request?.Headers?.UserAgent); + user.LastLoginDevice = CommonHelper.GetClientDeviceInfo(_httpContextAccessor.HttpContext?.Request?.Headers?.UserAgent); await _sysUserRep.AsUpdateable(user).UpdateColumns(u => new { u.LastLoginIp, @@ -409,7 +409,7 @@ public class SysAuthService : IDynamicApiController, ITransient await Login(new LoginInput { Account = auth.UserName, - Password = CryptogramUtil.SM2Encrypt(auth.Password), + Password = CryptogramHelper.SM2Encrypt(auth.Password), TenantId = SqlSugarConst.DefaultTenantId }); diff --git a/Admin.NET/Admin.NET.Core/Service/Auth/SysLdapService.cs b/Admin.NET/Admin.NET.Core/Service/Auth/SysLdapService.cs index 482c9280..8a2d94b4 100644 --- a/Admin.NET/Admin.NET.Core/Service/Auth/SysLdapService.cs +++ b/Admin.NET/Admin.NET.Core/Service/Auth/SysLdapService.cs @@ -46,7 +46,7 @@ public class SysLdapService : IDynamicApiController, ITransient public async Task Add(AddSysLdapInput input) { var entity = input.Adapt(); - entity.BindPass = CryptogramUtil.Encrypt(input.BindPass); + entity.BindPass = CryptogramHelper.Encrypt(input.BindPass); await _sysLdapRep.InsertAsync(entity); return entity.Id; } @@ -63,7 +63,7 @@ public class SysLdapService : IDynamicApiController, ITransient var entity = input.Adapt(); if (!string.IsNullOrEmpty(input.BindPass) && input.BindPass.Length < 32) { - entity.BindPass = CryptogramUtil.Encrypt(input.BindPass); // 加密 + entity.BindPass = CryptogramHelper.Encrypt(input.BindPass); // 加密 } await _sysLdapRep.AsUpdateable(entity).IgnoreColumns(ignoreAllNullColumns: true).ExecuteCommandAsync(); @@ -119,7 +119,7 @@ public class SysLdapService : IDynamicApiController, ITransient try { await ldapConn.ConnectAsync(sysLdap.Host, sysLdap.Port); - string bindPass = CryptogramUtil.Decrypt(sysLdap.BindPass); + string bindPass = CryptogramHelper.Decrypt(sysLdap.BindPass); await ldapConn.BindAsync(sysLdap.Version, sysLdap.BindDn, bindPass); var ldapSearchResults = await ldapConn.SearchAsync(sysLdap.BaseDn, LdapConnection.ScopeSub, sysLdap.AuthFilter.Replace("%s", account), null, false); string dn = string.Empty; @@ -190,7 +190,7 @@ public class SysLdapService : IDynamicApiController, ITransient try { await ldapConn.ConnectAsync(sysLdap.Host, sysLdap.Port); - string bindPass = CryptogramUtil.Decrypt(sysLdap.BindPass); + string bindPass = CryptogramHelper.Decrypt(sysLdap.BindPass); await ldapConn.BindAsync(sysLdap.Version, sysLdap.BindDn, bindPass); var ldapSearchResults = await ldapConn.SearchAsync(sysLdap.BaseDn, LdapConnection.ScopeOne, "(objectClass=*)", null, false); var userLdapList = new List(); @@ -335,7 +335,7 @@ public class SysLdapService : IDynamicApiController, ITransient try { await ldapConn.ConnectAsync(sysLdap.Host, sysLdap.Port); - string bindPass = CryptogramUtil.Decrypt(sysLdap.BindPass); + string bindPass = CryptogramHelper.Decrypt(sysLdap.BindPass); await ldapConn.BindAsync(sysLdap.Version, sysLdap.BindDn, bindPass); var ldapSearchResults = await ldapConn.SearchAsync(sysLdap.BaseDn, LdapConnection.ScopeOne, "(objectClass=*)", null, false); var orgList = new List(); diff --git a/Admin.NET/Admin.NET.Core/Service/Cache/SysCacheService.cs b/Admin.NET/Admin.NET.Core/Service/Cache/SysCacheService.cs index 7b63d839..1293b93d 100644 --- a/Admin.NET/Admin.NET.Core/Service/Cache/SysCacheService.cs +++ b/Admin.NET/Admin.NET.Core/Service/Cache/SysCacheService.cs @@ -248,7 +248,7 @@ public class SysCacheService : IDynamicApiController, ISingleton public async Task> GetListAsync(IEnumerable ids, Func, Task>> loadFromDb, bool cacheNull = true, TimeSpan? nullExpire = null) where T : class { var idList = ids.Distinct().ToList(); - if (idList.Count == 0) return new List(); + if (idList.Count == 0) return []; // 1. 批量获取缓存(保持同步,假设缓存操作快速) var cachedItems = GetFromCache(idList); @@ -296,7 +296,7 @@ public class SysCacheService : IDynamicApiController, ISingleton // 5. 按原始顺序返回 return idList.Select(id => resultDict.TryGetValue(id, out var item) - ? (item == null ? null : item) + ? (item ?? null) : null).ToList(); } @@ -312,7 +312,7 @@ public class SysCacheService : IDynamicApiController, ISingleton public List GetList(IEnumerable ids, Func, List> loadFromDb, bool cacheNull = true, TimeSpan? nullExpire = null) where T : class { var idList = ids.Distinct().ToList(); - if (idList.Count == 0) return new List(); + if (idList.Count == 0) return []; // 1. 批量获取缓存 var cachedItems = GetFromCache(idList); @@ -363,7 +363,7 @@ public class SysCacheService : IDynamicApiController, ISingleton // 5. 按原始顺序返回 return idList.Select(id => resultDict.TryGetValue(id, out var item) - ? (item == null ? null : item) + ? (item ?? null) : null).ToList(); } @@ -379,11 +379,9 @@ public class SysCacheService : IDynamicApiController, ISingleton [NonAction] public List GetFromCache(List ids) where T : class { - if (ids == null || ids.Count == 0) - return new List(); + if (ids == null || ids.Count == 0) return []; var keys = ids.Select(id => $"{_cacheOptions.Prefix}{id}").ToList(); - if (_cacheProvider.Cache is FullRedis redis) { var result = redis.GetAll(keys); diff --git a/Admin.NET/Admin.NET.Core/Service/CodeGen/CustomViewEngine.cs b/Admin.NET/Admin.NET.Core/Service/CodeGen/CustomViewEngine.cs index 385aadb8..04831591 100644 --- a/Admin.NET/Admin.NET.Core/Service/CodeGen/CustomViewEngine.cs +++ b/Admin.NET/Admin.NET.Core/Service/CodeGen/CustomViewEngine.cs @@ -94,7 +94,7 @@ public class CustomViewEngine : ViewEngineModel var config = App.GetOptions().ConnectionConfigs.FirstOrDefault(u => u.ConfigId.ToString() == ConfigId); ColumnList = GetColumnListByTableName(tbName.ToString()); var col = ColumnList.Where(c => (config.DbSettings.EnableUnderLine - ? CodeGenUtil.CamelColumnName(c.ColumnName, []) + ? CodeGenHelper.CamelColumnName(c.ColumnName, []) : c.ColumnName) == colName.ToString()).FirstOrDefault(); return col.NetType; } @@ -122,7 +122,7 @@ public class CustomViewEngine : ViewEngineModel ColumnName = u.DbColumnName, ColumnKey = u.IsPrimarykey.ToString(), DataType = u.DataType.ToString(), - NetType = CodeGenUtil.ConvertDataType(u, provider.CurrentConnectionConfig.DbType), + NetType = CodeGenHelper.ConvertDataType(u, provider.CurrentConnectionConfig.DbType), ColumnComment = u.ColumnDescription }).ToList(); } diff --git a/Admin.NET/Admin.NET.Core/Service/CodeGen/Dto/TableOutput.cs b/Admin.NET/Admin.NET.Core/Service/CodeGen/Dto/TableOutput.cs index 4206e55e..fb053e64 100644 --- a/Admin.NET/Admin.NET.Core/Service/CodeGen/Dto/TableOutput.cs +++ b/Admin.NET/Admin.NET.Core/Service/CodeGen/Dto/TableOutput.cs @@ -44,7 +44,7 @@ public class TableOutput /// /// 表字段个数 /// - public int ColumnCount { get; set; } + public int ColumnCount { get; set; } /// /// 程序集名称 diff --git a/Admin.NET/Admin.NET.Core/Service/CodeGen/SysCodeGenConfigService.cs b/Admin.NET/Admin.NET.Core/Service/CodeGen/SysCodeGenConfigService.cs index 8bbc1b53..c6391581 100644 --- a/Admin.NET/Admin.NET.Core/Service/CodeGen/SysCodeGenConfigService.cs +++ b/Admin.NET/Admin.NET.Core/Service/CodeGen/SysCodeGenConfigService.cs @@ -73,7 +73,7 @@ public class SysCodeGenConfigService : IDynamicApiController, ITransient IsPrimarykey = u.IsPrimarykey, IsNullable = u.IsNullable, ColumnKey = u.IsPrimarykey.ToString(), - NetType = CodeGenUtil.ConvertDataType(u, provider.CurrentConnectionConfig.DbType), + NetType = CodeGenHelper.ConvertDataType(u, provider.CurrentConnectionConfig.DbType), DataType = u.DataType, ColumnComment = string.IsNullOrWhiteSpace(u.ColumnDescription) ? u.DbColumnName : u.ColumnDescription, DefaultValue = u.DefaultValue, @@ -87,7 +87,7 @@ public class SysCodeGenConfigService : IDynamicApiController, ITransient // 先找自定义字段名的,如果找不到就再找自动生成字段名的(并且过滤掉没有SugarColumn的属性) var propertyInfo = entityProperties.FirstOrDefault(u => (u.GetCustomAttribute()?.ColumnName ?? "").ToLower() == columnOutput.ColumnName.ToLower()) ?? entityProperties.FirstOrDefault(u => u.GetCustomAttribute() != null && u.Name.ToLower() == (config.DbSettings.EnableUnderLine - ? CodeGenUtil.CamelColumnName(columnOutput.ColumnName, entityBasePropertyNames).ToLower() + ? CodeGenHelper.CamelColumnName(columnOutput.ColumnName, entityBasePropertyNames).ToLower() : columnOutput.ColumnName.ToLower())); if (propertyInfo != null) { @@ -265,7 +265,7 @@ public class SysCodeGenConfigService : IDynamicApiController, ITransient YesOrNo = YesNoEnum.N.ToString(); } - if (CodeGenUtil.IsCommonColumn(tableColumn.PropertyName)) + if (CodeGenHelper.IsCommonColumn(tableColumn.PropertyName)) { codeGenConfig.WhetherCommon = YesNoEnum.Y.ToString(); YesOrNo = YesNoEnum.N.ToString(); @@ -313,7 +313,7 @@ public class SysCodeGenConfigService : IDynamicApiController, ITransient codeGenConfig.ColumnKey = tableColumn.ColumnKey; codeGenConfig.DataType = tableColumn.DataType; - codeGenConfig.EffectType = CodeGenUtil.DataTypeToEff(codeGenConfig.NetType); + codeGenConfig.EffectType = CodeGenHelper.DataTypeToEff(codeGenConfig.NetType); codeGenConfig.QueryType = GetDefaultQueryType(codeGenConfig); // QueryTypeEnum.eq.ToString(); codeGenConfig.OrderNo = orderNo; codeGenConfig.DefaultValue = GetDefaultValue(tableColumn.DefaultValue); diff --git a/Admin.NET/Admin.NET.Core/Service/CodeGen/SysCodeGenService.cs b/Admin.NET/Admin.NET.Core/Service/CodeGen/SysCodeGenService.cs index 7b75f0d6..a8d89ffe 100644 --- a/Admin.NET/Admin.NET.Core/Service/CodeGen/SysCodeGenService.cs +++ b/Admin.NET/Admin.NET.Core/Service/CodeGen/SysCodeGenService.cs @@ -241,10 +241,10 @@ public class SysCodeGenService : IDynamicApiController, ITransient // 按原始类型的顺序获取所有实体类型属性(不包含导航属性,会返回null) var columnList = provider.DbMaintenance.GetColumnInfosByTableName(entityType.Name).Select(u => new ColumnOuput { - ColumnName = config!.DbSettings.EnableUnderLine ? CodeGenUtil.CamelColumnName(u.DbColumnName, entityBasePropertyNames) : u.DbColumnName, + ColumnName = config!.DbSettings.EnableUnderLine ? CodeGenHelper.CamelColumnName(u.DbColumnName, entityBasePropertyNames) : u.DbColumnName, ColumnKey = u.IsPrimarykey.ToString(), DataType = u.DataType.ToString(), - NetType = CodeGenUtil.ConvertDataType(u, provider.CurrentConnectionConfig.DbType), + NetType = CodeGenHelper.ConvertDataType(u, provider.CurrentConnectionConfig.DbType), ColumnComment = u.ColumnDescription }).ToList(); @@ -265,7 +265,7 @@ public class SysCodeGenService : IDynamicApiController, ITransient string GetRealColumnName(string name) { if (string.IsNullOrWhiteSpace(name)) return null; - string realName = config!.DbSettings.EnableUnderLine ? CodeGenUtil.CamelColumnName(name, entityBasePropertyNames) : name; + string realName = config!.DbSettings.EnableUnderLine ? CodeGenHelper.CamelColumnName(name, entityBasePropertyNames) : name; if (config.DbType == DbType.PostgreSQL) realName = realName.ToLower(); return realName; } diff --git a/Admin.NET/Admin.NET.Core/Service/Common/SysCommonService.cs b/Admin.NET/Admin.NET.Core/Service/Common/SysCommonService.cs index e416dbb9..67b574e1 100644 --- a/Admin.NET/Admin.NET.Core/Service/Common/SysCommonService.cs +++ b/Admin.NET/Admin.NET.Core/Service/Common/SysCommonService.cs @@ -35,7 +35,7 @@ public class SysCommonService : IDynamicApiController, ITransient [DisplayName("获取国密公钥私钥对")] public SmKeyPairOutput GetSmKeyPair() { - return CryptogramUtil.GetSmKeyPair(); + return CryptogramHelper.GetSmKeyPair(); } /// @@ -58,7 +58,7 @@ public class SysCommonService : IDynamicApiController, ITransient [DisplayName("国密SM2加密字符串")] public string SM2Encrypt([Required] string plainText) { - return CryptogramUtil.SM2Encrypt(plainText); + return CryptogramHelper.SM2Encrypt(plainText); } /// @@ -69,7 +69,7 @@ public class SysCommonService : IDynamicApiController, ITransient [DisplayName("国密SM2解密字符串")] public string SM2Decrypt([Required] string cipherText) { - return CryptogramUtil.SM2Decrypt(cipherText); + return CryptogramHelper.SM2Decrypt(cipherText); } /// @@ -185,7 +185,8 @@ public class SysCommonService : IDynamicApiController, ITransient /// /// 生成所有移动端接口文件 🔖 /// - /// + /// + /// [HttpGet] [DisplayName("生成所有移动端接口文件")] public void GenerateAppApi([FromQuery] string groupName = "", [FromQuery] bool isAppApi = true) diff --git a/Admin.NET/Admin.NET.Core/Service/DataBase/Dto/DbColumnInput.cs b/Admin.NET/Admin.NET.Core/Service/DataBase/Dto/DbColumnInput.cs index b3f20c7b..172e93fd 100644 --- a/Admin.NET/Admin.NET.Core/Service/DataBase/Dto/DbColumnInput.cs +++ b/Admin.NET/Admin.NET.Core/Service/DataBase/Dto/DbColumnInput.cs @@ -27,6 +27,8 @@ public class DbColumnInput public int IsPrimarykey { get; set; } public int DecimalDigits { get; set; } + + public string DefaultValue { get; set; } } public class UpdateDbColumnInput @@ -40,6 +42,8 @@ public class UpdateDbColumnInput public string OldColumnName { get; set; } public string Description { get; set; } + + public string DefaultValue { get; set; } } public class DeleteDbColumnInput @@ -49,4 +53,27 @@ public class DeleteDbColumnInput public string TableName { get; set; } public string DbColumnName { get; set; } +} + +public class MoveDbColumnInput +{ + /// + /// 数据库配置ID + /// + public string ConfigId { get; set; } + + /// + /// 目标表名 + /// + public string TableName { get; set; } + + /// + ///要移动的列名 + /// + public string ColumnName { get; set; } + + /// + /// 移动到该列后方(为空时移动到首列) + /// + public string AfterColumnName { get; set; } } \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/DataBase/SysDatabaseService.cs b/Admin.NET/Admin.NET.Core/Service/DataBase/SysDatabaseService.cs index 8569c946..30520352 100644 --- a/Admin.NET/Admin.NET.Core/Service/DataBase/SysDatabaseService.cs +++ b/Admin.NET/Admin.NET.Core/Service/DataBase/SysDatabaseService.cs @@ -122,10 +122,9 @@ public class SysDatabaseService : IDynamicApiController, ITransient [DisplayName("获取字段列表")] public List GetColumnList(string tableName, string configId = SqlSugarConst.MainConfigId) { - var db = _db.AsTenant().GetConnectionScope(configId); - if (string.IsNullOrWhiteSpace(tableName)) - return new List(); + if (string.IsNullOrWhiteSpace(tableName)) return []; + var db = _db.AsTenant().GetConnectionScope(configId); return db.DbMaintenance.GetColumnInfosByTableName(tableName, false).Adapt>(); } @@ -162,6 +161,9 @@ public class SysDatabaseService : IDynamicApiController, ITransient }; var db = _db.AsTenant().GetConnectionScope(input.ConfigId); db.DbMaintenance.AddColumn(input.TableName, column); + // 添加默认值 + if (!string.IsNullOrWhiteSpace(input.DefaultValue)) + db.DbMaintenance.AddDefaultValue(input.TableName, column.DbColumnName, input.DefaultValue); db.DbMaintenance.AddColumnRemark(input.DbColumnName, input.TableName, input.ColumnDescription); if (column.IsPrimarykey) db.DbMaintenance.AddPrimaryKey(input.TableName, input.DbColumnName); @@ -189,11 +191,88 @@ public class SysDatabaseService : IDynamicApiController, ITransient { var db = _db.AsTenant().GetConnectionScope(input.ConfigId); db.DbMaintenance.RenameColumn(input.TableName, input.OldColumnName, input.ColumnName); + if (!string.IsNullOrWhiteSpace(input.DefaultValue)) + db.DbMaintenance.AddDefaultValue(input.TableName, input.ColumnName, input.DefaultValue); if (db.DbMaintenance.IsAnyColumnRemark(input.ColumnName, input.TableName)) db.DbMaintenance.DeleteColumnRemark(input.ColumnName, input.TableName); db.DbMaintenance.AddColumnRemark(input.ColumnName, input.TableName, string.IsNullOrWhiteSpace(input.Description) ? input.ColumnName : input.Description); } + /// + /// 移动列顺序 🔖 + /// + /// + [DisplayName("移动列顺序")] + public void MoveColumn(MoveDbColumnInput input) + { + var db = _db.AsTenant().GetConnectionScope(input.ConfigId); + var dbMaintenance = db.DbMaintenance; + + var columns = dbMaintenance.GetColumnInfosByTableName(input.TableName, false); + var targetColumn = columns.FirstOrDefault(u => u.DbColumnName.Equals(input.ColumnName, StringComparison.OrdinalIgnoreCase)) ?? throw new Exception($"列 {input.ColumnName} 在表 {input.TableName} 中不存在"); + + var dbType = db.CurrentConnectionConfig.DbType; + switch (dbType) + { + case SqlSugar.DbType.MySql: + MoveColumnInMySQL(db, input.TableName, input.ColumnName, input.AfterColumnName); + break; + + default: + throw new NotSupportedException($"暂不支持 {dbType} 数据库的列移动操作"); + } + } + + /// + /// 获取列定义 + /// + /// + /// + /// + /// + /// + /// + private string GetColumnDefinitionInMySQL(ISqlSugarClient db, string tableName, string columnName, bool noDefault = false) + { + var columnDef = db.Ado.SqlQuery($"SHOW FULL COLUMNS FROM `{tableName}` WHERE Field = '{columnName}'").FirstOrDefault() ?? throw new Exception($"Column {columnName} not found"); + + var definition = new StringBuilder(); + definition.Append($"`{columnName}` "); // 列名 + definition.Append($"{columnDef.Type} "); // 数据类型 + + // 处理约束条件 + definition.Append(columnDef.Null == "YES" ? "NULL " : "NOT NULL "); + if (columnDef.Default != null && !noDefault) + definition.Append($"DEFAULT '{columnDef.Default}' "); + if (!string.IsNullOrEmpty(columnDef.Extra)) + definition.Append($"{columnDef.Extra} "); + if (!string.IsNullOrEmpty(columnDef.Comment)) + definition.Append($"COMMENT '{columnDef.Comment.Replace("'", "''")}'"); + + return definition.ToString(); + } + + /// + /// MySQL 列移动实现 + /// + /// + /// + /// + /// + private void MoveColumnInMySQL(ISqlSugarClient db, string tableName, string columnName, string afterColumnName) + { + var definition = GetColumnDefinitionInMySQL(db, tableName, columnName); + var sql = new StringBuilder(); + sql.Append($"ALTER TABLE `{tableName}` MODIFY COLUMN {definition}"); + + if (string.IsNullOrEmpty(afterColumnName)) + sql.Append(" FIRST"); + else + sql.Append($" AFTER `{afterColumnName}`"); + + db.Ado.ExecuteCommand(sql.ToString()); + } + /// /// 获取表列表 🔖 /// @@ -236,6 +315,7 @@ public class SysDatabaseService : IDynamicApiController, ITransient IsNullable = u.IsNullable == 1, DecimalDigits = u.DecimalDigits, ColumnDescription = u.ColumnDescription, + DefaultValue = u.DefaultValue, }); }); db.CodeFirst.InitTables(typeBuilder.BuilderType()); @@ -323,9 +403,9 @@ public class SysDatabaseService : IDynamicApiController, ITransient var config = App.GetOptions().ConnectionConfigs.FirstOrDefault(u => u.ConfigId.ToString() == input.ConfigId); input.Position = string.IsNullOrWhiteSpace(input.Position) ? "Admin.NET.Application" : input.Position; input.EntityName = string.IsNullOrWhiteSpace(input.EntityName) - ? (config.DbSettings.EnableUnderLine ? CodeGenUtil.CamelColumnName(input.TableName, null) : input.TableName) + ? (config.DbSettings.EnableUnderLine ? CodeGenHelper.CamelColumnName(input.TableName, null) : input.TableName) : input.EntityName; - string[] dbColumnNames = Array.Empty(); + string[] dbColumnNames = []; // Entity.cs.vm中是允许创建没有基类的实体的,所以这里也要做出相同的判断 if (!string.IsNullOrWhiteSpace(input.BaseClassName)) { @@ -338,8 +418,12 @@ public class SysDatabaseService : IDynamicApiController, ITransient var dbColumnInfos = db.DbMaintenance.GetColumnInfosByTableName(input.TableName, false); dbColumnInfos.ForEach(u => { - u.PropertyName = config.DbSettings.EnableUnderLine ? CodeGenUtil.CamelColumnName(u.DbColumnName, dbColumnNames) : u.DbColumnName; // 转下划线后的列名需要再转回来 - u.DataType = CodeGenUtil.ConvertDataType(u, config.DbType); + // 禁止字段全是大写的 + if (u.DbColumnName.ToUpper() == u.DbColumnName) + throw new Exception($"字段命名规范错误:{u.DbColumnName} 字段全是大写字母,请用大驼峰式命名规范!"); + + u.PropertyName = config.DbSettings.EnableUnderLine ? CodeGenHelper.CamelColumnName(u.DbColumnName, dbColumnNames) : u.DbColumnName; // 转下划线后的列名需要再转回来 + u.DataType = CodeGenHelper.ConvertDataType(u, config.DbType); }); if (_codeGenOptions.BaseEntityNames.Contains(input.BaseClassName, StringComparer.OrdinalIgnoreCase)) dbColumnInfos = dbColumnInfos.Where(u => !dbColumnNames.Contains(u.PropertyName, StringComparer.OrdinalIgnoreCase)).ToList(); @@ -431,7 +515,7 @@ public class SysDatabaseService : IDynamicApiController, ITransient var seedData = ((IEnumerable)hasDataMethod?.Invoke(instance, null))?.Cast(); if (seedData == null) continue; - List recordsToRemove = new(); + List recordsToRemove = []; foreach (var record in records) { object recordId = pkInfo.GetValue(record); @@ -460,7 +544,7 @@ public class SysDatabaseService : IDynamicApiController, ITransient int recordIndex = 0; foreach (var r in (IEnumerable)records) { - List record = new(); + List record = []; foreach (var item in jsonIgnoreProperties) { object v = item.GetValue(r); diff --git a/Admin.NET/Admin.NET.Core/Service/DataBase/SysDbBackupService.cs b/Admin.NET/Admin.NET.Core/Service/DataBase/SysDbBackupService.cs index 11349025..25116dcd 100644 --- a/Admin.NET/Admin.NET.Core/Service/DataBase/SysDbBackupService.cs +++ b/Admin.NET/Admin.NET.Core/Service/DataBase/SysDbBackupService.cs @@ -52,7 +52,6 @@ public class SysDbBackupService : IDynamicApiController, ITransient } dbBackupList = dbBackupList.OrderByDescending(u => u.CreateTime).ToList(); - return dbBackupList; } catch diff --git a/Admin.NET/Admin.NET.Core/Service/Menu/SysMenuService.cs b/Admin.NET/Admin.NET.Core/Service/Menu/SysMenuService.cs index 1fd34b44..cee8727b 100644 --- a/Admin.NET/Admin.NET.Core/Service/Menu/SysMenuService.cs +++ b/Admin.NET/Admin.NET.Core/Service/Menu/SysMenuService.cs @@ -85,7 +85,7 @@ public class SysMenuService : IDynamicApiController, ITransient [DisplayName("获取菜单列表")] public async Task> GetList([FromQuery] MenuInput input) { - var menuIdList = _userManager.SuperAdmin ? new List() : await GetMenuIdList(); + var menuIdList = _userManager.SuperAdmin ? [] : await GetMenuIdList(); // 有筛选条件时返回list列表(防止构造不出树) if (!string.IsNullOrWhiteSpace(input.Title) || input.Type is > 0) diff --git a/Admin.NET/Admin.NET.Core/Service/Message/SysMessageService.cs b/Admin.NET/Admin.NET.Core/Service/Message/SysMessageService.cs index 3b6ed4fb..ab0938a2 100644 --- a/Admin.NET/Admin.NET.Core/Service/Message/SysMessageService.cs +++ b/Admin.NET/Admin.NET.Core/Service/Message/SysMessageService.cs @@ -73,7 +73,10 @@ public class SysMessageService : IDynamicApiController, ITransient { var hashKey = SysCacheService.HashGetAll(CacheConst.KeyUserOnline); var receiveUsers = hashKey.Where(u => input.UserIds.Any(a => a == u.Value.UserId)).Select(u => u.Value).ToList(); - await receiveUsers.ForEachAsync(u => _chatHubContext.Clients.Client(u.ConnectionId ?? "").ReceiveMessage(input)); + foreach (var user in receiveUsers) + { + await _chatHubContext.Clients.Client(user.ConnectionId ?? "").ReceiveMessage(input); + } } /// diff --git a/Admin.NET/Admin.NET.Core/Service/Message/SysSmsService.cs b/Admin.NET/Admin.NET.Core/Service/Message/SysSmsService.cs index 42b05f00..26069439 100644 --- a/Admin.NET/Admin.NET.Core/Service/Message/SysSmsService.cs +++ b/Admin.NET/Admin.NET.Core/Service/Message/SysSmsService.cs @@ -162,11 +162,11 @@ public class SysSmsService : IDynamicApiController, ITransient // 实例化一个请求对象,每个接口都会对应一个request对象 var req = new TencentCloud.Sms.V20190711.Models.SendSmsRequest { - PhoneNumberSet = new string[] { "+86" + phoneNumber.Trim(',') }, + PhoneNumberSet = ["+86" + phoneNumber.Trim(',')], SmsSdkAppid = _smsOptions.Tencentyun.SdkAppId, Sign = template.SignName, TemplateID = template.TemplateCode, - TemplateParamSet = new string[] { verifyCode.ToString() } + TemplateParamSet = [verifyCode.ToString()] }; // 返回的resp是一个SendSmsResponse的实例,与请求对象对应 diff --git a/Admin.NET/Admin.NET.Core/Service/OpenAccess/SysOpenAccessService.cs b/Admin.NET/Admin.NET.Core/Service/OpenAccess/SysOpenAccessService.cs index 1f02b853..387a7309 100644 --- a/Admin.NET/Admin.NET.Core/Service/OpenAccess/SysOpenAccessService.cs +++ b/Admin.NET/Admin.NET.Core/Service/OpenAccess/SysOpenAccessService.cs @@ -38,7 +38,7 @@ public class SysOpenAccessService : IDynamicApiController, ITransient { // 时间戳 if (input.Timestamp == 0) - input.Timestamp = DateTimeUtil.ToUnixTimestampByMilliseconds(DateTime.Now); + input.Timestamp = new DateTimeOffset(DateTime.Now).ToUnixTimeMilliseconds(); // 密钥 var appSecretByte = Encoding.UTF8.GetBytes(input.AccessSecret); diff --git a/Admin.NET/Admin.NET.Core/Service/Region/SysRegionService.cs b/Admin.NET/Admin.NET.Core/Service/Region/SysRegionService.cs index 68f023a4..39ee1eb6 100644 --- a/Admin.NET/Admin.NET.Core/Service/Region/SysRegionService.cs +++ b/Admin.NET/Admin.NET.Core/Service/Region/SysRegionService.cs @@ -432,7 +432,7 @@ public class SysRegionService : IDynamicApiController, ITransient var parent = res.District[0]; var areaList = new List() { - new SysRegion + new() { Id = Convert.ToInt64(parent.Gb), Pid = 0, diff --git a/Admin.NET/Admin.NET.Core/Service/Server/SysServerService.cs b/Admin.NET/Admin.NET.Core/Service/Server/SysServerService.cs index c78ebbc1..f5f4982f 100644 --- a/Admin.NET/Admin.NET.Core/Service/Server/SysServerService.cs +++ b/Admin.NET/Admin.NET.Core/Service/Server/SysServerService.cs @@ -17,131 +17,32 @@ public class SysServerService : IDynamicApiController, ITransient } /// - /// 获取服务器配置信息 🔖 + /// 获取服务器硬件信息 /// /// - [DisplayName("获取服务器配置信息")] - public dynamic GetServerBase() + [DisplayName("获取服务器硬件信息")] + public SystemHardwareInfo GetHardwareInfo() { - return new - { - HostName = Environment.MachineName, // 主机名称 - SystemOs = ComputerUtil.GetOSInfo(),//RuntimeInformation.OSDescription, // 操作系统 - OsArchitecture = Environment.OSVersion.Platform.ToString() + " " + RuntimeInformation.OSArchitecture.ToString(), // 系统架构 - ProcessorCount = Environment.ProcessorCount + " 核", // CPU核心数 - SysRunTime = ComputerUtil.GetRunTime(), // 系统运行时间 - RemoteIp = ComputerUtil.GetIpFromOnline(), // 外网地址 - LocalIp = App.HttpContext?.Connection?.LocalIpAddress!.MapToIPv4().ToString(), // 本地地址 - FrameworkDescription = RuntimeInformation.FrameworkDescription + " / " + App.GetOptions().ConnectionConfigs[0].DbType.ToString(), // NET框架 + 数据库类型 - Environment = App.HostEnvironment.IsDevelopment() ? "Development" : "Production", - Wwwroot = App.WebHostEnvironment.WebRootPath, // 网站根目录 - Stage = App.HostEnvironment.IsStaging() ? "Stage环境" : "非Stage环境", // 是否Stage环境 - }; + return HardwareInfoManager.GetSystemHardwareInfo(); } /// - /// 获取服务器使用信息 🔖 + /// 获取服务器运行时信息 /// /// - [DisplayName("获取服务器使用信息")] - public dynamic GetServerUsed() + [DisplayName("获取服务器运行时信息")] + public SystemRuntimeInfo GetRuntimeInfo() { - var programStartTime = Process.GetCurrentProcess().StartTime; - var totalMilliseconds = (DateTime.Now - programStartTime).TotalMilliseconds.ToString(); - var ts = totalMilliseconds.Contains('.') ? totalMilliseconds.Split('.')[0] : totalMilliseconds; - var programRunTime = DateTimeUtil.FormatTime(ts.ToLong()); - - var memoryMetrics = ComputerUtil.GetComputerInfo(); - return new - { - memoryMetrics.FreeRam, // 空闲内存 - memoryMetrics.UsedRam, // 已用内存 - memoryMetrics.TotalRam, // 总内存 - memoryMetrics.RamRate, // 内存使用率 - memoryMetrics.CpuRates, // Cpu使用率 - StartTime = programStartTime.ToString("yyyy-MM-dd HH:mm:ss"), // 服务启动时间 - RunTime = programRunTime, // 服务运行时间 - }; + return RuntimeInfoManger.GetSystemRuntimeInfo(); } /// - /// 获取服务器磁盘信息 🔖 - /// - /// - [DisplayName("获取服务器磁盘信息")] - public dynamic GetServerDisk() - { - return ComputerUtil.GetDiskInfos(); - } - - /// - /// 获取框架主要程序集 🔖 + /// 获取框架主要程序集 /// /// [DisplayName("获取框架主要程序集")] - public dynamic GetAssemblyList() + public List GetNuGetPackageInfo() { - var furionAssembly = typeof(App).Assembly.GetName(); - var sqlSugarAssembly = typeof(ISqlSugarClient).Assembly.GetName(); - var yitIdAssembly = typeof(YitIdHelper).Assembly.GetName(); - var redisAssembly = typeof(Redis).Assembly.GetName(); - var jsonAssembly = typeof(NewtonsoftJsonMvcCoreBuilderExtensions).Assembly.GetName(); - var excelAssembly = typeof(IExcelImporter).Assembly.GetName(); - var pdfAssembly = typeof(Magicodes.ExporterAndImporter.Pdf.IPdfExporter).Assembly.GetName(); - var wordAssembly = typeof(Magicodes.ExporterAndImporter.Word.IWordExporter).Assembly.GetName(); - var captchaAssembly = typeof(Lazy.Captcha.Core.ICaptcha).Assembly.GetName(); - var wechatApiAssembly = typeof(WechatApiClient).Assembly.GetName(); - var wechatTenpayAssembly = typeof(WechatTenpayClient).Assembly.GetName(); - var ossAssembly = typeof(OnceMi.AspNetCore.OSS.IOSSServiceFactory).Assembly.GetName(); - var parserAssembly = typeof(Parser).Assembly.GetName(); - var elasticsearchClientAssembly = typeof(Elastic.Clients.Elasticsearch.ElasticsearchClient).Assembly.GetName(); - var limitAssembly = typeof(AspNetCoreRateLimit.IpRateLimitMiddleware).Assembly.GetName(); - var htmlParserAssembly = typeof(AngleSharp.Html.Parser.HtmlParser).Assembly.GetName(); - var fluentEmailAssembly = typeof(MailKit.Net.Smtp.SmtpClient).Assembly.GetName(); - var qRCodeGeneratorAssembly = typeof(QRCoder.QRCodeGenerator).Assembly.GetName(); - var alibabaSendSmsRequestAssembly = typeof(AlibabaCloud.SDK.Dysmsapi20170525.Models.SendSmsRequest).Assembly.GetName(); - var tencentSendSmsRequestAssembly = typeof(TencentCloud.Sms.V20190711.Models.SendSmsRequest).Assembly.GetName(); - var imageAssembly = typeof(Image).Assembly.GetName(); - var rabbitMQAssembly = typeof(RabbitMQEventSourceStore).Assembly.GetName(); - var ldapConnectionAssembly = typeof(Novell.Directory.Ldap.LdapConnection).Assembly.GetName(); - var ipToolAssembly = typeof(IPTools.Core.IpTool).Assembly.GetName(); - var weixinAuthenticationOptionsAssembly = typeof(AspNet.Security.OAuth.Weixin.WeixinAuthenticationOptions).Assembly.GetName(); - var giteeAuthenticationOptionsAssembly = typeof(AspNet.Security.OAuth.Gitee.GiteeAuthenticationOptions).Assembly.GetName(); - var hashidsAssembly = typeof(HashidsNet.Hashids).Assembly.GetName(); - var sftpClientAssembly = typeof(Renci.SshNet.SftpClient).Assembly.GetName(); - var hardwareInfoAssembly = typeof(Hardware.Info.HardwareInfo).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 { wordAssembly.Name, wordAssembly.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 { elasticsearchClientAssembly.Name, elasticsearchClientAssembly.Version }, - new { limitAssembly.Name, limitAssembly.Version }, - new { htmlParserAssembly.Name, htmlParserAssembly.Version }, - new { fluentEmailAssembly.Name, fluentEmailAssembly.Version }, - new { qRCodeGeneratorAssembly.Name, qRCodeGeneratorAssembly.Version }, - new { alibabaSendSmsRequestAssembly.Name, alibabaSendSmsRequestAssembly.Version }, - new { tencentSendSmsRequestAssembly.Name, tencentSendSmsRequestAssembly.Version }, - new { imageAssembly.Name, imageAssembly.Version }, - new { rabbitMQAssembly.Name, rabbitMQAssembly.Version }, - new { ldapConnectionAssembly.Name, ldapConnectionAssembly.Version }, - new { ipToolAssembly.Name, ipToolAssembly.Version }, - new { weixinAuthenticationOptionsAssembly.Name, weixinAuthenticationOptionsAssembly.Version }, - new { giteeAuthenticationOptionsAssembly.Name, giteeAuthenticationOptionsAssembly.Version }, - new { hashidsAssembly.Name, hashidsAssembly.Version }, - new { sftpClientAssembly.Name, sftpClientAssembly.Version }, - new { hardwareInfoAssembly.Name, hardwareInfoAssembly.Version }, - }; + return ReflectionHelper.GetNuGetPackages("Admin.NET"); } } \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Tenant/SysTenantService.cs b/Admin.NET/Admin.NET.Core/Service/Tenant/SysTenantService.cs index 3818a289..60a5f1c7 100644 --- a/Admin.NET/Admin.NET.Core/Service/Tenant/SysTenantService.cs +++ b/Admin.NET/Admin.NET.Core/Service/Tenant/SysTenantService.cs @@ -237,7 +237,7 @@ public class SysTenantService : IDynamicApiController, ITransient Id = tenantId, TenantId = tenantId, Account = tenant.AdminAccount, - Password = CryptogramUtil.Encrypt(password), + Password = CryptogramHelper.Encrypt(password), RealName = tenant.RealName + "-租管", NickName = tenant.RealName + "-租管", AccountType = AccountTypeEnum.SysAdmin, @@ -409,7 +409,7 @@ public class SysTenantService : IDynamicApiController, ITransient public async Task ResetPwd(TenantUserInput input) { var password = await _sysConfigService.GetConfigValueByCode(ConfigConst.SysPassword); - var encryptPassword = CryptogramUtil.Encrypt(password); + var encryptPassword = CryptogramHelper.Encrypt(password); await _sysUserRep.UpdateAsync(u => new SysUser() { Password = encryptPassword }, u => u.Id == input.UserId); return password; } @@ -445,7 +445,7 @@ public class SysTenantService : IDynamicApiController, ITransient foreach (var tenant in tenantList) { if (!string.IsNullOrWhiteSpace(tenant.Connection)) - tenant.Connection = CryptogramUtil.SM2Encrypt(tenant.Connection); + tenant.Connection = CryptogramHelper.SM2Encrypt(tenant.Connection); } _sysCacheService.Set(CacheConst.KeyTenant, tenantList); @@ -544,7 +544,7 @@ public class SysTenantService : IDynamicApiController, ITransient ConfigId = tenant.Id.ToString(), DbType = tenant.DbType, IsAutoCloseConnection = true, - ConnectionString = CryptogramUtil.SM2Decrypt(tenant.Connection), // 对租户库连接进行SM2解密 + ConnectionString = CryptogramHelper.SM2Decrypt(tenant.Connection), // 对租户库连接进行SM2解密 DbSettings = new DbSettings() { EnableUnderLine = mainConnConfig.DbSettings.EnableUnderLine, diff --git a/Admin.NET/Admin.NET.Core/Service/User/SysUserService.cs b/Admin.NET/Admin.NET.Core/Service/User/SysUserService.cs index 2a3bf433..003f111d 100644 --- a/Admin.NET/Admin.NET.Core/Service/User/SysUserService.cs +++ b/Admin.NET/Admin.NET.Core/Service/User/SysUserService.cs @@ -113,7 +113,7 @@ public class SysUserService : IDynamicApiController, ITransient var password = await _sysConfigService.GetConfigValueByCode(ConfigConst.SysPassword); var user = input.Adapt(); - user.Password = CryptogramUtil.Encrypt(password); + user.Password = CryptogramHelper.Encrypt(password); var newUser = await _sysUserRep.AsInsertable(user).ExecuteReturnEntityAsync(); input.Id = newUser.Id; @@ -311,18 +311,18 @@ public class SysUserService : IDynamicApiController, ITransient public virtual async Task ChangePwd(ChangePwdInput input) { // 国密SM2解密(前端密码传输SM2加密后的) - input.PasswordOld = CryptogramUtil.SM2Decrypt(input.PasswordOld); - input.PasswordNew = CryptogramUtil.SM2Decrypt(input.PasswordNew); + input.PasswordOld = CryptogramHelper.SM2Decrypt(input.PasswordOld); + input.PasswordNew = CryptogramHelper.SM2Decrypt(input.PasswordNew); var user = await _sysUserRep.GetByIdAsync(_userManager.UserId) ?? throw Oops.Oh(ErrorCodeEnum.D0009); - if (CryptogramUtil.CryptoType == CryptogramEnum.MD5.ToString()) + if (CryptogramHelper.CryptoType == CryptogramEnum.MD5.ToString()) { if (user.Password != MD5Encryption.Encrypt(input.PasswordOld)) throw Oops.Oh(ErrorCodeEnum.D1004); } else { - if (CryptogramUtil.Decrypt(user.Password) != input.PasswordOld) + if (CryptogramHelper.Decrypt(user.Password) != input.PasswordOld) throw Oops.Oh(ErrorCodeEnum.D1004); } @@ -334,12 +334,12 @@ public class SysUserService : IDynamicApiController, ITransient { var sysConfig = await _sysConfigService.GetConfig(ConfigConst.SysPasswordStrengthExpression); user.Password = input.PasswordNew.TryValidate(sysConfig.Value) - ? CryptogramUtil.Encrypt(input.PasswordNew) + ? CryptogramHelper.Encrypt(input.PasswordNew) : throw Oops.Oh(sysConfig.Remark); } else { - user.Password = CryptogramUtil.Encrypt(input.PasswordNew); + user.Password = CryptogramHelper.Encrypt(input.PasswordNew); } // 验证历史密码记录 @@ -355,7 +355,7 @@ public class SysUserService : IDynamicApiController, ITransient // 更新密码和最新修改时间 user.LastChangePasswordTime = DateTime.Now; - user.TokenVersion = user.TokenVersion + 1; + user.TokenVersion++; var rows = await _sysUserRep.AsUpdateable(user).UpdateColumns(u => new { u.Password, u.LastChangePasswordTime, u.TokenVersion }).ExecuteCommandAsync(); // 强制下线账号和失效Token @@ -379,9 +379,9 @@ public class SysUserService : IDynamicApiController, ITransient var password = await _sysConfigService.GetConfigValueByCode(ConfigConst.SysPassword); if (!string.IsNullOrEmpty(input.NewPassword)) password = input.NewPassword; - user.Password = CryptogramUtil.Encrypt(password); + user.Password = CryptogramHelper.Encrypt(password); user.LastChangePasswordTime = null; - user.TokenVersion = user.TokenVersion + 1; + user.TokenVersion++; await _sysUserRep.AsUpdateable(user).UpdateColumns(u => new { u.Password, u.LastChangePasswordTime, u.TokenVersion }).ExecuteCommandAsync(); // 清空密码错误次数缓存 diff --git a/Admin.NET/Admin.NET.Core/Service/Wechat/SysWxOpenService.cs b/Admin.NET/Admin.NET.Core/Service/Wechat/SysWxOpenService.cs index 4bda9876..4c60f8ba 100644 --- a/Admin.NET/Admin.NET.Core/Service/Wechat/SysWxOpenService.cs +++ b/Admin.NET/Admin.NET.Core/Service/Wechat/SysWxOpenService.cs @@ -119,10 +119,7 @@ public class SysWxOpenService : IDynamicApiController, ITransient [DisplayName("微信小程序登录OpenId")] public async Task WxOpenIdLogin(WxOpenIdLoginInput input) { - var wxUser = await _sysOAuthUserRep.GetFirstAsync(p => p.OpenId == input.OpenId); - if (wxUser == null) - throw Oops.Oh("微信小程序登录失败"); - + var wxUser = await _sysOAuthUserRep.GetFirstAsync(p => p.OpenId == input.OpenId) ?? throw Oops.Oh("微信小程序登录失败"); var tokenExpire = await _sysConfigService.GetTokenExpire(); return new { @@ -145,9 +142,7 @@ public class SysWxOpenService : IDynamicApiController, ITransient [DisplayName("上传小程序头像")] public async Task UploadAvatar([FromForm] UploadAvatarInput input) { - var wxUser = await _sysOAuthUserRep.GetFirstAsync(u => u.OpenId == input.OpenId); - if (wxUser == null) - throw Oops.Oh("未找到用户上传失败"); + var wxUser = await _sysOAuthUserRep.GetFirstAsync(u => u.OpenId == input.OpenId) ?? throw Oops.Oh("未找到用户上传失败"); var res = await _sysFileService.UploadFile(new UploadFileInput { File = input.File, FileType = input.FileType }); wxUser.Avatar = res.Url; @@ -165,9 +160,7 @@ public class SysWxOpenService : IDynamicApiController, ITransient [DisplayName("设置小程序用户昵称"), HttpPost] public async Task SetNickName(SetNickNameInput input) { - var wxUser = await _sysOAuthUserRep.GetFirstAsync(u => u.OpenId == input.OpenId); - if (wxUser == null) - throw Oops.Oh("未找到用户信息设置失败"); + var wxUser = await _sysOAuthUserRep.GetFirstAsync(u => u.OpenId == input.OpenId) ?? throw Oops.Oh("未找到用户信息设置失败"); wxUser.NickName = input.NickName; await _sysOAuthUserRep.AsUpdateable(wxUser).IgnoreColumns(true).ExecuteCommandAsync(); @@ -183,10 +176,7 @@ public class SysWxOpenService : IDynamicApiController, ITransient [DisplayName("获取小程序用户信息")] public async Task GetUserInfo(string openid) { - var wxUser = await _sysOAuthUserRep.GetFirstAsync(u => u.OpenId == openid); - if (wxUser == null) - throw Oops.Oh("未找到用户信息获取失败"); - + var wxUser = await _sysOAuthUserRep.GetFirstAsync(u => u.OpenId == openid) ?? throw Oops.Oh("未找到用户信息获取失败"); return new { nickName = wxUser.NickName, avator = wxUser.Avatar }; } @@ -345,7 +335,7 @@ public class SysWxOpenService : IDynamicApiController, ITransient [ApiDescriptionSettings(Name = "GenerateQRImageUnlimit")] public async Task GenerateQRImageUnlimitAsync(GenerateQRImageUnLimitInput input) { - GenerateQRImageOutput generateQRImageOutInput = new GenerateQRImageOutput(); + GenerateQRImageOutput generateQRImageOutInput = new(); if (input.PagePath.IsNullOrEmpty()) { generateQRImageOutInput.Message = $"生成失败,页面路径不能为空"; diff --git a/Admin.NET/Admin.NET.Core/SignatureAuth/SignatureAuthenticationHandler.cs b/Admin.NET/Admin.NET.Core/SignatureAuth/SignatureAuthenticationHandler.cs index 2ad5bd69..adae32bb 100644 --- a/Admin.NET/Admin.NET.Core/SignatureAuth/SignatureAuthenticationHandler.cs +++ b/Admin.NET/Admin.NET.Core/SignatureAuth/SignatureAuthenticationHandler.cs @@ -55,7 +55,12 @@ public sealed class SignatureAuthenticationHandler : AuthenticationHandler 9999999999; + var requestDate = isMilliseconds + ? DateTimeOffset.FromUnixTimeMilliseconds(timestamp).ToLocalTime().DateTime + : DateTimeOffset.FromUnixTimeSeconds(timestamp).ToLocalTime().DateTime; + var utcNow = TimeProvider.GetUtcNow(); if (requestDate > utcNow.Add(Options.AllowedDateDrift).LocalDateTime || requestDate < utcNow.Subtract(Options.AllowedDateDrift).LocalDateTime) return await AuthenticateResultFailAsync("timestamp 值已超过允许的偏差范围"); @@ -133,11 +138,8 @@ public sealed class SignatureAuthenticationHandler : AuthenticationHandler private static string SignData(byte[] secret, string data) { - if (secret == null) - throw new ArgumentNullException(nameof(secret)); - - if (data == null) - throw new ArgumentNullException(nameof(data)); + ArgumentNullException.ThrowIfNull(secret); + ArgumentNullException.ThrowIfNull(data); using HMAC hmac = new HMACSHA256(); hmac.Key = secret; diff --git a/Admin.NET/Admin.NET.Core/SqlSugar/SqlSugarExtension.cs b/Admin.NET/Admin.NET.Core/SqlSugar/SqlSugarExtension.cs index 2a857570..71e9dd15 100644 --- a/Admin.NET/Admin.NET.Core/SqlSugar/SqlSugarExtension.cs +++ b/Admin.NET/Admin.NET.Core/SqlSugar/SqlSugarExtension.cs @@ -411,7 +411,7 @@ public static class SqlSugarExtension var instance = Activator.CreateInstance(seedType); var hasDataMethod = seedType.GetMethod("HasData"); var seedData = ((IEnumerable)hasDataMethod?.Invoke(instance, null))?.Cast().ToArray() ?? []; - if (!seedData.Any()) + if (seedData.Length == 0) { Console.WriteLine($" 忽略 {seedType.FullName,-58} ({dbProvider.CurrentConnectionConfig.ConfigId}) 原因:没有数据"); return default; diff --git a/Admin.NET/Admin.NET.Core/SqlSugar/SqlSugarFilter.cs b/Admin.NET/Admin.NET.Core/SqlSugar/SqlSugarFilter.cs index 16a9a604..19db5f1a 100644 --- a/Admin.NET/Admin.NET.Core/SqlSugar/SqlSugarFilter.cs +++ b/Admin.NET/Admin.NET.Core/SqlSugar/SqlSugarFilter.cs @@ -235,7 +235,7 @@ public static class SqlSugarFilter right = Expression.OrElse(Expression.Call( typeof(Enumerable), nameof(Enumerable.Contains), - new[] { memberExp.Type }, + [memberExp.Type], ownersCollection, memberExp ), right); diff --git a/Admin.NET/Admin.NET.Core/SqlSugar/SqlSugarSetup.cs b/Admin.NET/Admin.NET.Core/SqlSugar/SqlSugarSetup.cs index 07ad7a7d..6fc34798 100644 --- a/Admin.NET/Admin.NET.Core/SqlSugar/SqlSugarSetup.cs +++ b/Admin.NET/Admin.NET.Core/SqlSugar/SqlSugarSetup.cs @@ -73,18 +73,18 @@ public static class SqlSugarSetup { // 解密数据库连接串 if (config.DbSettings.EnableConnEncrypt) - config.ConnectionString = CryptogramUtil.SM2Decrypt(config.ConnectionString); + config.ConnectionString = CryptogramHelper.SM2Decrypt(config.ConnectionString); // SqlFunc 扩展函数 var sqlFuncServices = new List { // 密码解密 - new SqlFuncExternal() + new() { UniqueMethodName = "SqlFuncDecrypt", MethodValue = (expInfo, dbType, expContext) => { - return CryptogramUtil.Decrypt(expInfo.Args[0].MemberName.ToString()); + return CryptogramHelper.Decrypt(expInfo.Args[0].MemberName.ToString()); } } }; @@ -451,13 +451,13 @@ public static class SqlSugarSetup if (dbProvider.CurrentConnectionConfig.ConfigId.ToString() == SqlSugarConst.MainConfigId && dbProvider.DbMaintenance.IsAnyTable(dbProvider.EntityMaintenance.GetTableName(typeof(SysConfig)))) { var versionCfg = dbProvider.Queryable().Where(u => u.Code == ConfigConst.SysVersion).First(); - oldVerion = versionCfg != null ? CommonUtil.ConvertVersionToLong(versionCfg.Value) : 0; + oldVerion = versionCfg != null ? CommonHelper.ConvertVersionToLong(versionCfg.Value) : 0; } else { oldVerion = -1; } - currentVersion = CommonUtil.ConvertVersionToLong(ConfigConst.SysCurrentVersion); + currentVersion = CommonHelper.ConvertVersionToLong(ConfigConst.SysCurrentVersion); foreach (var type in startups) { @@ -489,8 +489,7 @@ public static class SqlSugarSetup { foreach (var type in startups) { - var startup = Activator.CreateInstance(type) as AppStartup; - if (startup == null) continue; + if (Activator.CreateInstance(type) is not AppStartup startup) continue; var initDataMethod = type.GetMethod("AfterInitSeed"); if (initDataMethod == null) continue; initDataMethod?.Invoke(startup, [dbProvider, oldVerion, currentVersion]); diff --git a/Admin.NET/Admin.NET.Core/Utils/AggregationBuilder.cs b/Admin.NET/Admin.NET.Core/Utils/AggregationBuilder.cs deleted file mode 100644 index aa0c02e9..00000000 --- a/Admin.NET/Admin.NET.Core/Utils/AggregationBuilder.cs +++ /dev/null @@ -1,149 +0,0 @@ -// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 -// -// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 -// -// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! - -namespace Admin.NET.Core; - -/// -/// 聚合配置增强版(独立类) -/// -public class AggregationBuilder -{ - private readonly List _configs; - private readonly Type _entityType; - private readonly Type _outputType; - - public List SelectParts { get; } = []; - public List HavingConditions { get; } = []; - - public AggregationBuilder(IEnumerable configs, Type entityType, Type outputType) - { - _configs = configs.ToList(); - _entityType = entityType; - _outputType = outputType; - Build(); - } - - private void Build() - { - foreach (var config in _configs.Where(IsValidConfig)) - { - // 处理SELECT部分 - var expression = string.IsNullOrEmpty(config.CustomExpression) - ? $"{config.Function.ToString().ToUpper()}({config.Field})" - : config.CustomExpression; - - SelectParts.Add($"{expression} AS {config.Alias}"); - - // 处理HAVING条件 - if (!string.IsNullOrEmpty(config.HavingCondition)) - { - HavingConditions.Add($"{expression} {config.HavingCondition}"); - } - } - } - - private bool IsValidConfig(AggregationConfig config) - { - // 字段基础验证 - if (!string.IsNullOrEmpty(config.Field) && - _entityType.GetProperty(config.Field) == null) - return false; - - // 输出属性验证 - return _outputType.GetProperty(config.Alias) != null; - } - - /// - /// 验证聚合配置有效性 - /// - private static bool ValidateAggregation(AggregationConfig config, Type entityType, Type outputType) - { - // 验证实体字段存在性 - var entityProp = entityType.GetProperty(config.Field); - if (entityProp == null) return false; - - // 验证输出字段存在性 - var outputProp = outputType.GetProperty(config.Alias); - if (outputProp == null) return false; - - // 验证类型兼容性 - return config.Function switch - { - AggregateFunction.Count => outputProp.PropertyType == typeof(int), - _ => outputProp.PropertyType == entityProp.PropertyType || - IsNumericType(outputProp.PropertyType) - }; - } - - private static bool IsNumericType(Type type) - { - return Type.GetTypeCode(type) switch - { - TypeCode.Byte or TypeCode.SByte or TypeCode.UInt16 - or TypeCode.UInt32 or TypeCode.UInt64 or TypeCode.Int16 - or TypeCode.Int32 or TypeCode.Int64 or TypeCode.Decimal - or TypeCode.Double or TypeCode.Single => true, - _ => false - }; - } - - /// - /// 验证字段有效性 - /// - public static List ValidateFields(string[] fields, Type targetType) - { - if (fields == null || fields.Length == 0) return []; - - return fields.Where(u => !string.IsNullOrWhiteSpace(u)) - .Select(u => u.Trim()) - .Where(u => targetType.GetProperty(targetType == typeof(T) ? $"{u}Sum" : u) != null) - .ToList(); - } -} - -/// -/// 增强版聚合配置类 -/// -public class AggregationConfig -{ - /// - /// 数据库字段名(与CustomExpression二选一) - /// - public string Field { get; set; } - - /// - /// 自定义聚合表达式(优先级高于Field+Function) - /// - public string CustomExpression { get; set; } - - /// - /// 聚合函数类型(使用CustomExpression时可不填) - /// - public AggregateFunction Function { get; set; } = AggregateFunction.Sum; - - /// - /// 输出字段别名(必须与DTO属性名一致) - /// - public string Alias { get; set; } - - /// - /// HAVING条件表达式(如"> 100") - /// - public string HavingCondition { get; set; } -} - -/// -/// 函数枚举 -/// -public enum AggregateFunction -{ - Sum, - Avg, - Count, - Max, - Min, - // 可扩展其他函数 -} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Utils/Collections/CollectionExtensions.cs b/Admin.NET/Admin.NET.Core/Utils/Collections/CollectionExtensions.cs new file mode 100644 index 00000000..0cd40b74 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Utils/Collections/CollectionExtensions.cs @@ -0,0 +1,198 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using System.Diagnostics.CodeAnalysis; + +namespace Admin.NET.Core; + +/// +/// 集合扩展方法 +/// +public static class CollectionExtensions +{ + /// + /// 检查给定的集合对象是否为空或者没有任何项 + /// + /// 集合元素类型 + /// 要检查的集合 + /// 如果集合为null或空则返回true,否则返回false + public static bool IsNullOrEmpty(this ICollection? source) + { + return source is not { Count: > 0 }; + } + + /// + /// 如果条件成立,添加项 + /// + /// 集合元素类型 + /// 要操作的集合 + /// 要添加的值 + /// 条件标志,为true时添加项 + public static void AddIf(this ICollection source, T value, bool flag) + { + _ = NotNull(source, nameof(source)); + + if (flag) + { + source.Add(value); + } + } + + /// + /// 如果条件成立,添加项 + /// + /// 集合元素类型 + /// 要操作的集合 + /// 要添加的值 + /// 条件函数,返回true时添加项 + public static void AddIf(this ICollection source, T value, Func func) + { + _ = NotNull(source, nameof(source)); + + if (func()) + { + source.Add(value); + } + } + + /// + /// 如果给定的集合对象不为空,则添加一个项 + /// + /// 集合元素类型 + /// 要操作的集合 + /// 要添加的值(如果不为null) + public static void AddIfNotNull(this ICollection source, T value) + { + _ = NotNull(source, nameof(source)); + + if (value is not null) + { + source.Add(value); + } + } + + /// + /// 如果集合中尚未包含该项,则将其添加到集合中 + /// + /// 集合中项的类型 + /// 集合对象 + /// 要检查并添加的项 + /// 如果添加了项,则返回真(True);如果没有添加(即项已存在)则返回假(False) + public static bool AddIfNotContains(this ICollection source, T item) + { + _ = NotNull(source, nameof(source)); + + if (source.Contains(item)) + { + return false; + } + + source.Add(item); + return true; + } + + /// + /// 向集合中添加尚未包含的项 + /// + /// 集合中项的类型 + /// 集合对象 + /// 要检查并添加的项的集合 + /// 返回添加的项的集合 + public static IEnumerable AddIfNotContains(this ICollection source, IEnumerable items) + { + _ = NotNull(source, nameof(source)); + var enumerable = items as T[] ?? [.. items]; + _ = NotNull(enumerable, nameof(items)); + + List addedItems = []; + + foreach (var item in enumerable) + { + if (source.Contains(item)) + { + continue; + } + + source.Add(item); + addedItems.Add(item); + } + + return addedItems; + } + + /// + /// 如果集合中尚未包含满足给定谓词条件的项,则将项添加到集合中 + /// + /// 集合中项的类型 + /// 集合对象 + /// 决定项是否已存在于集合中的条件 + /// 返回项的工厂函数 + /// 如果添加了项,则返回真(True);如果没有添加(即项已存在)则返回假(False) + public static bool AddIfNotContains(this ICollection source, Func predicate, Func itemFactory) + { + _ = NotNull(source, nameof(source)); + + if (source.Any(predicate)) + { + return false; + } + + source.Add(itemFactory()); + return true; + } + + /// + /// 移除集合中所有满足给定谓词条件的项 + /// + /// 集合中项的类型 + /// 集合对象 + /// 用于移除项的条件 + /// 被移除项的列表 + public static IList RemoveAllWhere(this ICollection source, Func predicate) + { + _ = NotNull(source, nameof(source)); + + var items = source.Where(predicate).ToList(); + + foreach (var item in items) + { + _ = source.Remove(item); + } + + return items; + } + + /// + /// 从集合中移除所有指定的项 + /// + /// 集合中项的类型 + /// 集合对象 + /// 要移除的项的集合 + public static void RemoveAll(this ICollection source, IEnumerable items) + { + _ = NotNull(source, nameof(source)); + var enumerable = items as T[] ?? [.. items]; + _ = NotNull(enumerable, nameof(items)); + + foreach (var item in enumerable) + { + _ = source.Remove(item); + } + } + + /// + /// 数据不为空判断 + /// + /// + /// + /// + /// + /// + public static T NotNull([NotNull] T? value, string parameterName) + { + return value is null ? throw new ArgumentNullException(parameterName) : value; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Utils/Collections/DictionaryExtensions.cs b/Admin.NET/Admin.NET.Core/Utils/Collections/DictionaryExtensions.cs new file mode 100644 index 00000000..5608ccda --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Utils/Collections/DictionaryExtensions.cs @@ -0,0 +1,171 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using System.Dynamic; + +namespace Admin.NET.Core; + +/// +/// 字典扩展方法 +/// +public static class DictionaryExtensions +{ + /// + /// 使用给定的键从字典中获取值。如果找不到,则返回默认值 + /// + /// 键的类型 + /// 值的类型 + /// 要检查和获取的字典 + /// 要查找值的键 + /// 如果找到,返回值;如果找不到,返回默认值 + public static TValue? GetOrDefault(this Dictionary dictionary, TKey key) where TKey : notnull + { + return dictionary.GetValueOrDefault(key); + } + + /// + /// 使用给定的键从字典中获取值。如果找不到,则返回默认值 + /// + /// 键的类型 + /// 值的类型 + /// 要检查和获取的字典 + /// 要查找值的键 + /// 如果找到,返回值;如果找不到,返回默认值 + public static TValue? GetOrDefault(this IDictionary dictionary, TKey key) + { + return dictionary.TryGetValue(key, out var obj) ? obj : default; + } + + /// + /// 使用给定的键从只读字典中获取值。如果找不到,则返回默认值 + /// + /// 键的类型 + /// 值的类型 + /// 要检查和获取的只读字典 + /// 要查找值的键 + /// 如果找到,返回值;如果找不到,返回默认值 + public static TValue? GetOrDefault(this IReadOnlyDictionary dictionary, TKey key) + { + return dictionary.GetValueOrDefault(key); + } + + /// + /// 使用给定的键从并发字典中获取值。如果找不到,则返回默认值 + /// + /// 键的类型,不能为空 + /// 值的类型 + /// 要检查和获取的并发字典 + /// 要查找值的键 + /// 如果找到,返回值;如果找不到,返回默认值 + public static TValue? GetOrDefault(this ConcurrentDictionary dictionary, TKey key) where TKey : notnull + { + return dictionary.GetValueOrDefault(key); + } + + /// + /// 使用给定的键从字典中获取值。如果找不到,则使用工厂方法创建并添加值 + /// + /// 键的类型 + /// 值的类型 + /// 要检查和获取的字典 + /// 要查找值的键 + /// 如果字典中未找到,则用于创建值的工厂方法 + /// 如果找到,返回值;如果找不到,使用工厂方法创建并返回默认值 + public static TValue GetOrAdd(this IDictionary dictionary, TKey key, Func factory) + { + return dictionary.TryGetValue(key, out var obj) ? obj : dictionary[key] = factory(key); + } + + /// + /// 使用给定的键从字典中获取值。如果找不到,则使用工厂方法创建并添加值 + /// + /// 键的类型 + /// 值的类型 + /// 要检查和获取的字典 + /// 要查找值的键 + /// 如果字典中未找到,则用于创建值的工厂方法 + /// 如果找到,返回值;如果找不到,使用工厂方法创建并返回默认值 + public static TValue GetOrAdd(this IDictionary dictionary, TKey key, Func factory) + { + return dictionary.GetOrAdd(key, _ => factory()); + } + + /// + /// 使用给定的键从并发字典中获取值。如果找不到,则使用工厂方法创建并添加值 + /// + /// 键的类型,不能为空 + /// 值的类型 + /// 要检查和获取的并发字典 + /// 要查找值的键 + /// 如果并发字典中未找到,则用于创建值的工厂方法 + /// 如果找到,返回值;如果找不到,使用工厂方法创建并返回默认值 + public static TValue GetOrAdd(this ConcurrentDictionary dictionary, TKey key, Func factory) where TKey : notnull + { + return dictionary.GetOrAdd(key, _ => factory()); + } + + /// + /// 将字典转换为动态对象,以便在运行时添加和移除 + /// + /// 要转换的字典对象 + /// 如果值正确,返回表示对象的 ExpandoObject + public static dynamic ConvertToDynamicObject(this Dictionary dictionary) + { + ExpandoObject expandoObject = new(); + ICollection> expendObjectCollection = expandoObject!; + + foreach (var keyValuePair in dictionary) + { + expendObjectCollection.Add(keyValuePair); + } + + return expandoObject; + } + + /// + /// 如果字典中存在指定的键,则尝试获取其值 + /// + /// 值的类型 + /// 字典对象 + /// 要查找的键 + /// 键对应的值,如果键不存在,则为默认值 + /// 如果字典中存在该键,则返回真(True);否则返回假(False) + public static bool TryGetValue(this IDictionary dictionary, string key, out T? value) + { + if (dictionary.TryGetValue(key, out var valueObj) && valueObj is T t) + { + value = t; + return true; + } + + value = default; + return false; + } + + /// + /// 字典根据 key 删除,返回一个新的字典 + /// + /// + /// + /// 移除后新的字典 + public static IDictionary RemoveByKeys(this IDictionary dictionary, params string[] keys) + { + ArgumentNullException.ThrowIfNull(dictionary); + + if (keys.Length == 0) + { + return dictionary; + } + + // 创建一个新的字典,避免修改原始字典 + var newDic = new Dictionary(dictionary); + foreach (var key in keys) + { + newDic.Remove(key); + } + return newDic; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Utils/Collections/LinkedListExtensions.cs b/Admin.NET/Admin.NET.Core/Utils/Collections/LinkedListExtensions.cs new file mode 100644 index 00000000..6a15b5b0 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Utils/Collections/LinkedListExtensions.cs @@ -0,0 +1,586 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 链表扩展方法 +/// +public static class LinkedListExtensions +{ + /// + /// 批量添加元素到链表末尾 + /// + /// 链表元素类型 + /// 链表实例 + /// 要添加的元素集合 + /// 链表或元素集合为空时抛出 + public static void AddRange(this LinkedList list, IEnumerable items) + { + ArgumentNullException.ThrowIfNull(list); + ArgumentNullException.ThrowIfNull(items); + + foreach (var item in items) + { + list.AddLast(item); + } + } + + /// + /// 批量添加元素到链表开头 + /// + /// 链表元素类型 + /// 链表实例 + /// 要添加的元素集合 + /// 链表或元素集合为空时抛出 + public static void AddRangeFirst(this LinkedList list, IEnumerable items) + { + ArgumentNullException.ThrowIfNull(list); + ArgumentNullException.ThrowIfNull(items); + + // 从末尾开始添加,保持顺序 + var itemsArray = items.ToArray(); + for (var i = itemsArray.Length - 1; i >= 0; i--) + { + list.AddFirst(itemsArray[i]); + } + } + + /// + /// 在指定节点后批量插入元素 + /// + /// 链表元素类型 + /// 链表实例 + /// 参考节点 + /// 要插入的元素集合 + /// 链表、节点或元素集合为空时抛出 + public static void AddRangeAfter(this LinkedList list, LinkedListNode node, IEnumerable items) + { + ArgumentNullException.ThrowIfNull(list); + ArgumentNullException.ThrowIfNull(node); + ArgumentNullException.ThrowIfNull(items); + + var currentNode = node; + foreach (var item in items) + { + currentNode = list.AddAfter(currentNode, item); + } + } + + /// + /// 在指定节点前批量插入元素 + /// + /// 链表元素类型 + /// 链表实例 + /// 参考节点 + /// 要插入的元素集合 + /// 链表、节点或元素集合为空时抛出 + public static void AddRangeBefore(this LinkedList list, LinkedListNode node, IEnumerable items) + { + ArgumentNullException.ThrowIfNull(list); + ArgumentNullException.ThrowIfNull(node); + ArgumentNullException.ThrowIfNull(items); + + var currentNode = node; + // 从末尾开始添加,保持顺序 + var itemsArray = items.ToArray(); + for (var i = itemsArray.Length - 1; i >= 0; i--) + { + currentNode = list.AddBefore(currentNode, itemsArray[i]); + } + } + + /// + /// 查找满足条件的第一个节点 + /// + /// 链表元素类型 + /// 链表实例 + /// 匹配条件 + /// 满足条件的第一个节点,如果未找到则返回null + /// 链表或条件为空时抛出 + public static LinkedListNode? FindFirst(this LinkedList list, Func predicate) + { + ArgumentNullException.ThrowIfNull(list); + ArgumentNullException.ThrowIfNull(predicate); + + var current = list.First; + while (current != null) + { + if (predicate(current.Value)) + { + return current; + } + current = current.Next; + } + return null; + } + + /// + /// 查找满足条件的最后一个节点 + /// + /// 链表元素类型 + /// 链表实例 + /// 匹配条件 + /// 满足条件的最后一个节点,如果未找到则返回null + /// 链表或条件为空时抛出 + public static LinkedListNode? FindLast(this LinkedList list, Func predicate) + { + ArgumentNullException.ThrowIfNull(list); + ArgumentNullException.ThrowIfNull(predicate); + + var current = list.Last; + while (current != null) + { + if (predicate(current.Value)) + { + return current; + } + current = current.Previous; + } + return null; + } + + /// + /// 查找所有满足条件的节点 + /// + /// 链表元素类型 + /// 链表实例 + /// 匹配条件 + /// 满足条件的所有节点 + /// 链表或条件为空时抛出 + public static IEnumerable> FindAll(this LinkedList list, Func predicate) + { + ArgumentNullException.ThrowIfNull(list); + ArgumentNullException.ThrowIfNull(predicate); + + var result = new List>(); + var current = list.First; + while (current != null) + { + if (predicate(current.Value)) + { + result.Add(current); + } + current = current.Next; + } + return result; + } + + /// + /// 移除所有满足条件的节点 + /// + /// 链表元素类型 + /// 链表实例 + /// 移除条件 + /// 移除的节点数量 + /// 链表或条件为空时抛出 + public static int RemoveAll(this LinkedList list, Func predicate) + { + ArgumentNullException.ThrowIfNull(list); + ArgumentNullException.ThrowIfNull(predicate); + + var count = 0; + var current = list.First; + while (current != null) + { + var next = current.Next; + if (predicate(current.Value)) + { + list.Remove(current); + count++; + } + current = next; + } + return count; + } + + /// + /// 反转链表 + /// + /// 链表元素类型 + /// 链表实例 + /// 链表为空时抛出 + public static void Reverse(this LinkedList list) + { + ArgumentNullException.ThrowIfNull(list); + + if (list.Count <= 1) + { + return; + } + + var items = new T[list.Count]; + var index = list.Count - 1; + + var current = list.First; + while (current != null) + { + items[index--] = current.Value; + current = current.Next; + } + + list.Clear(); + foreach (var item in items) + { + list.AddLast(item); + } + } + + /// + /// 获取指定索引处的节点 + /// + /// 链表元素类型 + /// 链表实例 + /// 索引位置 + /// 指定索引处的节点 + /// 链表为空时抛出 + /// 索引超出范围时抛出 + public static LinkedListNode GetNodeAt(this LinkedList list, int index) + { + ArgumentNullException.ThrowIfNull(list); + + if (index < 0 || index >= list.Count) + { + throw new ArgumentOutOfRangeException(nameof(index), "索引超出范围"); + } + + LinkedListNode? current; + var count = list.Count; + + // 根据索引位置选择从头还是从尾开始遍历 + if (index < count / 2) + { + current = list.First; + for (var i = 0; i < index; i++) + { + current = current!.Next; + } + } + else + { + current = list.Last; + for (var i = count - 1; i > index; i--) + { + current = current!.Previous; + } + } + + return current!; + } + + /// + /// 安全获取指定索引处的节点 + /// + /// 链表元素类型 + /// 链表实例 + /// 索引位置 + /// 获取到的节点 + /// 是否成功获取节点 + public static bool TryGetNodeAt(this LinkedList list, int index, out LinkedListNode? node) + { + node = null; + + if (index < 0 || index >= list.Count) + { + return false; + } + + try + { + node = list.GetNodeAt(index); + return true; + } + catch + { + return false; + } + } + + /// + /// 检查链表是否为空 + /// + /// 链表元素类型 + /// 链表实例 + /// 链表是否为空 + public static bool IsEmpty(this LinkedList list) + { + return list?.Count == 0; + } + + /// + /// 检查链表是否不为空 + /// + /// 链表元素类型 + /// 链表实例 + /// 链表是否不为空 + public static bool IsNotEmpty(this LinkedList list) + { + return list?.Count > 0; + } + + /// + /// 将链表转换为数组,保持顺序 + /// + /// 链表元素类型 + /// 链表实例 + /// 包含链表所有元素的数组 + /// 链表为空时抛出 + public static T[] ToArrayPreserveOrder(this LinkedList list) + { + ArgumentNullException.ThrowIfNull(list); + return [.. list]; + } + + /// + /// 复制链表 + /// + /// 链表元素类型 + /// 原链表 + /// 复制的新链表 + /// 链表为空时抛出 + public static LinkedList Clone(this LinkedList list) + { + ArgumentNullException.ThrowIfNull(list); + return new LinkedList(list); + } + + /// + /// 对链表中的每个元素执行指定操作 + /// + /// 链表元素类型 + /// 链表实例 + /// 要执行的操作 + /// 链表或操作为空时抛出 + public static void ForEach(this LinkedList list, Action action) + { + ArgumentNullException.ThrowIfNull(list); + ArgumentNullException.ThrowIfNull(action); + + var current = list.First; + while (current != null) + { + action(current.Value); + current = current.Next; + } + } + + /// + /// 对链表中的每个元素执行指定操作(带索引) + /// + /// 链表元素类型 + /// 链表实例 + /// 要执行的操作,参数为元素和索引 + /// 链表或操作为空时抛出 + public static void ForEach(this LinkedList list, Action action) + { + ArgumentNullException.ThrowIfNull(list); + ArgumentNullException.ThrowIfNull(action); + + var index = 0; + var current = list.First; + while (current != null) + { + action(current.Value, index++); + current = current.Next; + } + } + + /// + /// 对链表中的每个节点执行指定操作 + /// + /// 链表元素类型 + /// 链表实例 + /// 要执行的操作 + /// 链表或操作为空时抛出 + public static void ForEachNode(this LinkedList list, Action> action) + { + ArgumentNullException.ThrowIfNull(list); + ArgumentNullException.ThrowIfNull(action); + + var current = list.First; + while (current != null) + { + var next = current.Next; // 保存下一个节点,防止操作中删除当前节点 + action(current); + current = next; + } + } + + /// + /// 统计满足条件的元素数量 + /// + /// 链表元素类型 + /// 链表实例 + /// 匹配条件 + /// 满足条件的元素数量 + /// 链表或条件为空时抛出 + public static int Count(this LinkedList list, Func predicate) + { + ArgumentNullException.ThrowIfNull(list); + ArgumentNullException.ThrowIfNull(predicate); + + return list.Count(predicate); + } + + /// + /// 检查是否包含满足条件的元素 + /// + /// 链表元素类型 + /// 链表实例 + /// 匹配条件 + /// 是否包含满足条件的元素 + /// 链表或条件为空时抛出 + public static bool Any(this LinkedList list, Func predicate) + { + ArgumentNullException.ThrowIfNull(list); + ArgumentNullException.ThrowIfNull(predicate); + + return list.Any(predicate); + } + + /// + /// 检查是否所有元素都满足条件 + /// + /// 链表元素类型 + /// 链表实例 + /// 匹配条件 + /// 是否所有元素都满足条件 + /// 链表或条件为空时抛出 + public static bool All(this LinkedList list, Func predicate) + { + ArgumentNullException.ThrowIfNull(list); + ArgumentNullException.ThrowIfNull(predicate); + + return list.All(predicate); + } + + /// + /// 创建一个新链表,包含满足条件的元素 + /// + /// 链表元素类型 + /// 原链表 + /// 筛选条件 + /// 包含满足条件元素的新链表 + /// 链表或条件为空时抛出 + public static LinkedList Where(this LinkedList list, Func predicate) + { + ArgumentNullException.ThrowIfNull(list); + ArgumentNullException.ThrowIfNull(predicate); + + var result = new LinkedList(); + var current = list.First; + while (current != null) + { + if (predicate(current.Value)) + { + result.AddLast(current.Value); + } + current = current.Next; + } + return result; + } + + /// + /// 创建一个新链表,包含转换后的元素 + /// + /// 原链表元素类型 + /// 目标链表元素类型 + /// 原链表 + /// 转换函数 + /// 包含转换后元素的新链表 + /// 链表或转换函数为空时抛出 + public static LinkedList Select(this LinkedList list, Func selector) + { + ArgumentNullException.ThrowIfNull(list); + ArgumentNullException.ThrowIfNull(selector); + + var result = new LinkedList(); + var current = list.First; + while (current != null) + { + result.AddLast(selector(current.Value)); + current = current.Next; + } + return result; + } + + /// + /// 合并两个链表 + /// + /// 链表元素类型 + /// 第一个链表 + /// 第二个链表 + /// 合并后的新链表 + /// 任一链表为空时抛出 + public static LinkedList Concat(this LinkedList first, LinkedList second) + { + ArgumentNullException.ThrowIfNull(first); + ArgumentNullException.ThrowIfNull(second); + + var result = new LinkedList(first); + var current = second.First; + while (current != null) + { + result.AddLast(current.Value); + current = current.Next; + } + return result; + } + + /// + /// 限制链表的最大长度,超出时移除头部元素 + /// + /// 链表元素类型 + /// 链表实例 + /// 最大长度 + /// 链表为空时抛出 + /// 最大长度小于0时抛出 + public static void LimitSize(this LinkedList list, int maxSize) + { + ArgumentNullException.ThrowIfNull(list); + + if (maxSize < 0) + { + throw new ArgumentOutOfRangeException(nameof(maxSize), "最大长度不能小于0"); + } + + while (list.Count > maxSize) + { + list.RemoveFirst(); + } + } + + /// + /// 安全地添加元素到末尾,如果链表已满则移除头部元素 + /// + /// 链表元素类型 + /// 链表实例 + /// 要添加的元素 + /// 链表最大长度 + /// 被移除的元素(如果有) + /// 链表为空时抛出 + /// 最大长度小于1时抛出 + public static T? AddLastWithLimit(this LinkedList list, T item, int maxSize) + { + ArgumentNullException.ThrowIfNull(list); + + if (maxSize < 1) + { + throw new ArgumentOutOfRangeException(nameof(maxSize), "最大长度不能小于1"); + } + + T? removedItem = default; + + if (list.Count >= maxSize) + { + removedItem = list.First!.Value; + list.RemoveFirst(); + } + + list.AddLast(item); + return removedItem; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Utils/Collections/ListExtensions.cs b/Admin.NET/Admin.NET.Core/Utils/Collections/ListExtensions.cs new file mode 100644 index 00000000..b2cd6504 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Utils/Collections/ListExtensions.cs @@ -0,0 +1,374 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using System.Diagnostics.CodeAnalysis; + +namespace Admin.NET.Core; + +/// +/// 列表扩展方法 +/// +public static class ListExtensions +{ + /// + /// 在指定索引的位置插入一个序列的项到列表中 + /// + /// 列表中项的类型 + /// 要操作的列表 + /// 要插入序列的起始索引 + /// 要插入的项的集合 + public static void InsertRange(this IList source, int index, IEnumerable items) + { + foreach (var item in items) + { + source.Insert(index++, item); + } + } + + /// + /// 查找列表中满足特定条件的第一个项的索引 + /// + /// 列表中项的类型 + /// 要搜索的列表 + /// 用于测试列表中每个项的条件 + /// 满足条件项的索引,如果未找到则返回 -1 + public static int FindIndex(this IList source, Predicate selector) + { + for (var i = 0; i < source.Count; ++i) + { + if (selector(source[i])) + { + return i; + } + } + + return -1; + } + + /// + /// 在列表开头添加一个项 + /// + /// 列表中项的类型 + /// 要操作的列表 + /// 要添加的项 + public static void AddFirst(this IList source, T item) + { + source.Insert(0, item); + } + + /// + /// 在列表末尾添加一个项 + /// + /// 列表中项的类型 + /// 要操作的列表 + /// 要添加的项 + public static void AddLast(this IList source, T item) + { + source.Insert(source.Count, item); + } + + /// + /// 在列表中指定项之后插入一个新项 + /// + /// 列表中项的类型 + /// 要操作的列表 + /// 列表中已存在的项 + /// 要插入的新项 + public static void InsertAfter(this IList source, T existingItem, T item) + { + var index = source.IndexOf(existingItem); + if (index < 0) + { + source.AddFirst(item); + return; + } + + source.Insert(index + 1, item); + } + + /// + /// 根据选择器找到的项之后插入一个新项 + /// + /// 列表中项的类型 + /// 要操作的列表 + /// 用于查找应插入新项之后项的选择器 + /// 要插入的新项 + public static void InsertAfter(this IList source, Predicate selector, T item) + { + var index = source.FindIndex(selector); + if (index < 0) + { + source.AddFirst(item); + return; + } + + source.Insert(index + 1, item); + } + + /// + /// 在列表中指定项之前插入一个新项 + /// + /// 列表中项的类型 + /// 要操作的列表 + /// 列表中已存在的项 + /// 要插入的新项 + public static void InsertBefore(this IList source, T existingItem, T item) + { + var index = source.IndexOf(existingItem); + if (index < 0) + { + source.AddLast(item); + return; + } + + source.Insert(index, item); + } + + /// + /// 根据选择器找到的项之前插入一个新项 + /// + /// 列表中项的类型 + /// 要操作的列表 + /// 用于查找应插入新项之前项的选择器 + /// 要插入的新项 + public static void InsertBefore(this IList source, Predicate selector, T item) + { + var index = source.FindIndex(selector); + if (index < 0) + { + source.AddLast(item); + return; + } + + source.Insert(index, item); + } + + /// + /// 遍历列表,替换所有满足特定条件的项为指定的新项 + /// + /// 列表中项的类型 + /// 要操作的列表 + /// 用于测试列表中每个项的条件 + /// 要替换的新项 + public static void ReplaceWhile(this IList source, Predicate selector, T item) + { + for (var i = 0; i < source.Count; i++) + { + if (selector(source[i])) + { + source[i] = item; + } + } + } + + /// + /// 遍历列表,替换所有满足特定条件的项为由工厂方法生成的新项 + /// + /// 列表中项的类型 + /// 要操作的列表 + /// 用于测试列表中每个项的条件 + /// 一个工厂方法,用于生成要替换的新项 + public static void ReplaceWhile(this IList source, Predicate selector, Func itemFactory) + { + for (var i = 0; i < source.Count; i++) + { + var item = source[i]; + if (selector(item)) + { + source[i] = itemFactory(item); + } + } + } + + /// + /// 遍历列表,替换第一个满足特定条件的项为指定的新项 + /// + /// 列表中项的类型 + /// 要操作的列表 + /// 用于测试列表中每个项的条件 + /// 要替换的新项 + public static void ReplaceOne(this IList source, Predicate selector, T item) + { + for (var i = 0; i < source.Count; i++) + { + if (!selector(source[i])) + { + continue; + } + + source[i] = item; + return; + } + } + + /// + /// 遍历列表,替换第一个满足特定条件的项为由工厂方法生成的新项 + /// + /// 列表中项的类型 + /// 要操作的列表 + /// 用于测试列表中每个项的条件 + /// 一个工厂方法,用于生成要替换的新项 + public static void ReplaceOne(this IList source, Predicate selector, Func itemFactory) + { + for (var i = 0; i < source.Count; i++) + { + var item = source[i]; + if (!selector(item)) + { + continue; + } + + source[i] = itemFactory(item); + return; + } + } + + /// + /// 遍历列表,替换第一个匹配指定项的项为新项 + /// + /// 列表中项的类型 + /// 要操作的列表 + /// 要被替换的项 + /// 新项 + public static void ReplaceOne(this IList source, T item, T replaceWith) + { + for (var i = 0; i < source.Count; i++) + { + if (Comparer.Default.Compare(source[i], item) != 0) + { + continue; + } + + source[i] = replaceWith; + return; + } + } + + /// + /// 根据给定的选择器找到列表中的项,并将其移动到目标索引位置 + /// + /// 列表中项的类型 + /// 要操作的列表 + /// 用于选择要移动的项的选择器 + /// 项移动到的目标索引位置 + public static void MoveItem(this List source, Predicate selector, int targetIndex) + { + // 检查目标索引是否在有效范围内 + if (!targetIndex.IsBetween(0, source.Count - 1)) + { + throw new IndexOutOfRangeException("目标索引应在 0 到 " + (source.Count - 1) + " 之间"); + } + + // 查找当前项的索引 + var currentIndex = source.FindIndex(0, selector); + if (currentIndex == targetIndex) + { + return; + } + + // 移除当前项并插入到目标索引位置 + var item = source[currentIndex]; + source.RemoveAt(currentIndex); + source.Insert(targetIndex, item); + } + + /// + /// 尝试获取列表中满足特定条件的第一个项,如果没有找到则添加新项 + /// + /// 列表中项的类型 + /// 要操作的列表 + /// 用于选择项的谓词 + /// 如果没有找到匹配项,则用于创建新项的工厂方法 + /// 返回找到的项或新添加的项 + public static T GetOrAdd(this IList source, Func selector, Func factory) + { + _ = NotNull(source, nameof(source)); + + var item = source.FirstOrDefault(selector); + + if (item is not null && !EqualityComparer.Default.Equals(item, default)) + { + return item; + } + + item = factory(); + source.Add(item); + + return item; + } + + /// + /// 从列表中随机获取一个元素 + /// + /// 列表中项的类型 + /// 要操作的列表 + /// 随机选中的元素 + /// 当列表为空时抛出异常 + public static T GetRandom(this IList source) + { + _ = NotNull(source, nameof(source)); + + if (source.Count == 0) + { + throw new ArgumentException("列表不能为空", nameof(source)); + } + + var randomIndex = Random.Shared.Next(source.Count); + return source[randomIndex]; + } + + /// + /// 尝试从列表中随机获取一个元素 + /// + /// 列表中项的类型 + /// 要操作的列表 + /// 输出参数,包含随机选中的元素(如果成功) + /// 如果列表不为空则返回 true,否则返回 false + public static bool TryGetRandom(this IList source, out T? result) + { + _ = NotNull(source, nameof(source)); + + if (source.Count == 0) + { + result = default; + return false; + } + + var randomIndex = Random.Shared.Next(source.Count); + result = source[randomIndex]; + return true; + } + + /// + /// 数据不为空判断 + /// + /// + /// + /// + /// + /// + public static T NotNull([NotNull] T? value, string parameterName) + { + return value is null ? throw new ArgumentNullException(parameterName) : value; + } + + /// + /// 判断当前值是否介于指定范围内 + /// + /// 泛型 + /// 泛型对象 + /// 范围起点 + /// 范围终点 + /// 是否可等于上限(默认等于) + /// 是否可等于下限(默认等于) + /// 是否介于 + public static bool IsBetween(this IComparable value, T start, T end, bool leftEqual = true, bool rightEqual = true) + where T : IComparable + { + var flag = leftEqual ? value.CompareTo(start) >= 0 : value.CompareTo(start) > 0; + return flag && (rightEqual ? value.CompareTo(end) <= 0 : value.CompareTo(end) < 0); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Utils/Collections/QueueExtensions.cs b/Admin.NET/Admin.NET.Core/Utils/Collections/QueueExtensions.cs new file mode 100644 index 00000000..22497d9a --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Utils/Collections/QueueExtensions.cs @@ -0,0 +1,361 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 队列扩展方法 +/// +public static class QueueExtensions +{ + /// + /// 批量入队 + /// + /// 队列元素类型 + /// 队列实例 + /// 要入队的元素集合 + /// 队列或元素集合为空时抛出 + public static void EnqueueRange(this Queue queue, IEnumerable items) + { + ArgumentNullException.ThrowIfNull(queue); + ArgumentNullException.ThrowIfNull(items); + + foreach (var item in items) + { + queue.Enqueue(item); + } + } + + /// + /// 批量出队 + /// + /// 队列元素类型 + /// 队列实例 + /// 要出队的元素数量 + /// 出队的元素集合 + /// 队列为空时抛出 + /// 数量小于0或大于队列长度时抛出 + public static IEnumerable DequeueRange(this Queue queue, int count) + { + ArgumentNullException.ThrowIfNull(queue); + + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count), "数量不能小于0"); + } + + if (count > queue.Count) + { + throw new ArgumentOutOfRangeException(nameof(count), "数量不能大于队列长度"); + } + + var result = new List(count); + for (var i = 0; i < count; i++) + { + result.Add(queue.Dequeue()); + } + return result; + } + + /// + /// 尝试出队多个元素 + /// + /// 队列元素类型 + /// 队列实例 + /// 要出队的元素数量 + /// 出队的元素集合 + /// 是否成功出队指定数量的元素 + public static bool TryDequeueRange(this Queue queue, int count, out IEnumerable items) + { + items = []; + + if (count < 0 || count > queue.Count) + { + return false; + } + + var result = new List(count); + for (var i = 0; i < count; i++) + { + if (queue.TryDequeue(out var item)) + { + result.Add(item); + } + else + { + // 恢复已出队的元素 + foreach (var restoredItem in result.AsEnumerable().Reverse()) + { + var tempQueue = new Queue(); + tempQueue.Enqueue(restoredItem); + while (queue.Count > 0) + { + tempQueue.Enqueue(queue.Dequeue()); + } + while (tempQueue.Count > 0) + { + queue.Enqueue(tempQueue.Dequeue()); + } + } + return false; + } + } + + items = result; + return true; + } + + /// + /// 清空队列并返回所有元素 + /// + /// 队列元素类型 + /// 队列实例 + /// 队列中的所有元素 + /// 队列为空时抛出 + public static IEnumerable DrainToList(this Queue queue) + { + ArgumentNullException.ThrowIfNull(queue); + + var result = new List(queue.Count); + while (queue.Count > 0) + { + result.Add(queue.Dequeue()); + } + return result; + } + + /// + /// 安全地查看队列头部元素 + /// + /// 队列元素类型 + /// 队列实例 + /// 队列头部元素 + /// 是否成功查看 + public static bool TryPeek(this Queue queue, out T? item) + { + item = default; + if (queue.Count == 0) + { + return false; + } + + item = queue.Peek(); + return true; + } + + /// + /// 检查队列是否为空 + /// + /// 队列元素类型 + /// 队列实例 + /// 队列是否为空 + public static bool IsEmpty(this Queue queue) + { + return queue?.Count == 0; + } + + /// + /// 检查队列是否不为空 + /// + /// 队列元素类型 + /// 队列实例 + /// 队列是否不为空 + public static bool IsNotEmpty(this Queue queue) + { + return queue?.Count > 0; + } + + /// + /// 将队列转换为数组 + /// + /// 队列元素类型 + /// 队列实例 + /// 包含队列所有元素的数组 + /// 队列为空时抛出 + public static T[] ToArrayPreserveOrder(this Queue queue) + { + ArgumentNullException.ThrowIfNull(queue); + return [.. queue]; + } + + /// + /// 复制队列 + /// + /// 队列元素类型 + /// 原队列 + /// 复制的新队列 + /// 队列为空时抛出 + public static Queue Clone(this Queue queue) + { + ArgumentNullException.ThrowIfNull(queue); + return new Queue(queue); + } + + /// + /// 查找队列中是否包含满足条件的元素 + /// + /// 队列元素类型 + /// 队列实例 + /// 匹配条件 + /// 是否包含满足条件的元素 + /// 队列或条件为空时抛出 + public static bool Contains(this Queue queue, Func predicate) + { + ArgumentNullException.ThrowIfNull(queue); + ArgumentNullException.ThrowIfNull(predicate); + + return queue.Any(predicate); + } + + /// + /// 统计队列中满足条件的元素数量 + /// + /// 队列元素类型 + /// 队列实例 + /// 匹配条件 + /// 满足条件的元素数量 + /// 队列或条件为空时抛出 + public static int Count(this Queue queue, Func predicate) + { + ArgumentNullException.ThrowIfNull(queue); + ArgumentNullException.ThrowIfNull(predicate); + + return queue.Count(predicate); + } + + /// + /// 对队列中的每个元素执行指定操作 + /// + /// 队列元素类型 + /// 队列实例 + /// 要执行的操作 + /// 队列或操作为空时抛出 + public static void ForEach(this Queue queue, Action action) + { + ArgumentNullException.ThrowIfNull(queue); + ArgumentNullException.ThrowIfNull(action); + + foreach (var item in queue) + { + action(item); + } + } + + /// + /// 对队列中的每个元素执行指定操作(带索引) + /// + /// 队列元素类型 + /// 队列实例 + /// 要执行的操作,参数为元素和索引 + /// 队列或操作为空时抛出 + public static void ForEach(this Queue queue, Action action) + { + ArgumentNullException.ThrowIfNull(queue); + ArgumentNullException.ThrowIfNull(action); + + var index = 0; + foreach (var item in queue) + { + action(item, index++); + } + } + + /// + /// 创建一个新队列,包含满足条件的元素 + /// + /// 队列元素类型 + /// 原队列 + /// 筛选条件 + /// 包含满足条件元素的新队列 + /// 队列或条件为空时抛出 + public static Queue Where(this Queue queue, Func predicate) + { + ArgumentNullException.ThrowIfNull(queue); + ArgumentNullException.ThrowIfNull(predicate); + + var result = new Queue(); + foreach (var item in queue.Where(predicate)) + { + result.Enqueue(item); + } + return result; + } + + /// + /// 创建一个新队列,包含转换后的元素 + /// + /// 原队列元素类型 + /// 目标队列元素类型 + /// 原队列 + /// 转换函数 + /// 包含转换后元素的新队列 + /// 队列或转换函数为空时抛出 + public static Queue Select(this Queue queue, Func selector) + { + ArgumentNullException.ThrowIfNull(queue); + ArgumentNullException.ThrowIfNull(selector); + + var result = new Queue(); + foreach (var item in queue.Select(selector)) + { + result.Enqueue(item); + } + return result; + } + + /// + /// 限制队列的最大长度,超出时移除最旧的元素 + /// + /// 队列元素类型 + /// 队列实例 + /// 最大长度 + /// 队列为空时抛出 + /// 最大长度小于0时抛出 + public static void LimitSize(this Queue queue, int maxSize) + { + ArgumentNullException.ThrowIfNull(queue); + + if (maxSize < 0) + { + throw new ArgumentOutOfRangeException(nameof(maxSize), "最大长度不能小于0"); + } + + while (queue.Count > maxSize) + { + queue.Dequeue(); + } + } + + /// + /// 安全地入队元素,如果队列已满则移除最旧的元素 + /// + /// 队列元素类型 + /// 队列实例 + /// 要入队的元素 + /// 队列最大长度 + /// 被移除的元素(如果有) + /// 队列为空时抛出 + /// 最大长度小于1时抛出 + public static T? EnqueueWithLimit(this Queue queue, T item, int maxSize) + { + ArgumentNullException.ThrowIfNull(queue); + + if (maxSize < 1) + { + throw new ArgumentOutOfRangeException(nameof(maxSize), "最大长度不能小于1"); + } + + T? removedItem = default; + + if (queue.Count >= maxSize) + { + removedItem = queue.Dequeue(); + } + + queue.Enqueue(item); + return removedItem; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Utils/Collections/StackExtensions.cs b/Admin.NET/Admin.NET.Core/Utils/Collections/StackExtensions.cs new file mode 100644 index 00000000..17052a5d --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Utils/Collections/StackExtensions.cs @@ -0,0 +1,542 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 堆栈扩展方法 +/// +public static class StackExtensions +{ + /// + /// 批量入栈 + /// + /// 堆栈元素类型 + /// 堆栈实例 + /// 要入栈的元素集合 + /// 堆栈或元素集合为空时抛出 + public static void PushRange(this Stack stack, IEnumerable items) + { + ArgumentNullException.ThrowIfNull(stack); + ArgumentNullException.ThrowIfNull(items); + + foreach (var item in items) + { + stack.Push(item); + } + } + + /// + /// 批量入栈(保持集合的原始顺序) + /// + /// 堆栈元素类型 + /// 堆栈实例 + /// 要入栈的元素集合 + /// 堆栈或元素集合为空时抛出 + public static void PushRangeReversed(this Stack stack, IEnumerable items) + { + ArgumentNullException.ThrowIfNull(stack); + ArgumentNullException.ThrowIfNull(items); + + // 反转顺序入栈,使得弹出时保持原始顺序 + var itemsArray = items.ToArray(); + for (var i = itemsArray.Length - 1; i >= 0; i--) + { + stack.Push(itemsArray[i]); + } + } + + /// + /// 批量出栈 + /// + /// 堆栈元素类型 + /// 堆栈实例 + /// 要出栈的元素数量 + /// 出栈的元素集合 + /// 堆栈为空时抛出 + /// 数量小于0或大于堆栈长度时抛出 + public static IEnumerable PopRange(this Stack stack, int count) + { + ArgumentNullException.ThrowIfNull(stack); + + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count), "数量不能小于0"); + } + + if (count > stack.Count) + { + throw new ArgumentOutOfRangeException(nameof(count), "数量不能大于堆栈长度"); + } + + var result = new List(count); + for (var i = 0; i < count; i++) + { + result.Add(stack.Pop()); + } + return result; + } + + /// + /// 尝试出栈多个元素 + /// + /// 堆栈元素类型 + /// 堆栈实例 + /// 要出栈的元素数量 + /// 出栈的元素集合 + /// 是否成功出栈指定数量的元素 + public static bool TryPopRange(this Stack stack, int count, out IEnumerable items) + { + items = []; + + if (count < 0 || count > stack.Count) + { + return false; + } + + var result = new List(count); + var tempItems = new List(); + + for (var i = 0; i < count; i++) + { + if (stack.TryPop(out var item)) + { + result.Add(item); + tempItems.Add(item); + } + else + { + // 恢复已出栈的元素 + for (var j = tempItems.Count - 1; j >= 0; j--) + { + stack.Push(tempItems[j]); + } + return false; + } + } + + items = result; + return true; + } + + /// + /// 清空堆栈并返回所有元素 + /// + /// 堆栈元素类型 + /// 堆栈实例 + /// 堆栈中的所有元素 + /// 堆栈为空时抛出 + public static IEnumerable DrainToList(this Stack stack) + { + ArgumentNullException.ThrowIfNull(stack); + + var result = new List(stack.Count); + while (stack.Count > 0) + { + result.Add(stack.Pop()); + } + return result; + } + + /// + /// 安全地查看堆栈顶部元素 + /// + /// 堆栈元素类型 + /// 堆栈实例 + /// 堆栈顶部元素 + /// 是否成功查看 + public static bool TryPeek(this Stack stack, out T? item) + { + item = default; + if (stack.Count == 0) + { + return false; + } + + item = stack.Peek(); + return true; + } + + /// + /// 安全地查看多个顶部元素(不出栈) + /// + /// 堆栈元素类型 + /// 堆栈实例 + /// 要查看的元素数量 + /// 顶部指定数量的元素 + /// 堆栈为空时抛出 + /// 数量小于0或大于堆栈长度时抛出 + public static IEnumerable PeekRange(this Stack stack, int count) + { + ArgumentNullException.ThrowIfNull(stack); + + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count), "数量不能小于0"); + } + + if (count > stack.Count) + { + throw new ArgumentOutOfRangeException(nameof(count), "数量不能大于堆栈长度"); + } + + var items = stack.ToArray(); + return items.Take(count); + } + + /// + /// 检查堆栈是否为空 + /// + /// 堆栈元素类型 + /// 堆栈实例 + /// 堆栈是否为空 + public static bool IsEmpty(this Stack stack) + { + return stack?.Count == 0; + } + + /// + /// 检查堆栈是否不为空 + /// + /// 堆栈元素类型 + /// 堆栈实例 + /// 堆栈是否不为空 + public static bool IsNotEmpty(this Stack stack) + { + return stack?.Count > 0; + } + + /// + /// 将堆栈转换为数组,保持堆栈顺序 + /// + /// 堆栈元素类型 + /// 堆栈实例 + /// 包含堆栈所有元素的数组 + /// 堆栈为空时抛出 + public static T[] ToArrayPreserveOrder(this Stack stack) + { + ArgumentNullException.ThrowIfNull(stack); + return [.. stack]; + } + + /// + /// 复制堆栈 + /// + /// 堆栈元素类型 + /// 原堆栈 + /// 复制的新堆栈 + /// 堆栈为空时抛出 + public static Stack Clone(this Stack stack) + { + ArgumentNullException.ThrowIfNull(stack); + // 保持原始堆栈的顺序 + var items = stack.ToArray(); + return new Stack(items); + } + + /// + /// 查找堆栈中是否包含满足条件的元素 + /// + /// 堆栈元素类型 + /// 堆栈实例 + /// 匹配条件 + /// 是否包含满足条件的元素 + /// 堆栈或条件为空时抛出 + public static bool Contains(this Stack stack, Func predicate) + { + ArgumentNullException.ThrowIfNull(stack); + ArgumentNullException.ThrowIfNull(predicate); + + return stack.Any(predicate); + } + + /// + /// 统计堆栈中满足条件的元素数量 + /// + /// 堆栈元素类型 + /// 堆栈实例 + /// 匹配条件 + /// 满足条件的元素数量 + /// 堆栈或条件为空时抛出 + public static int Count(this Stack stack, Func predicate) + { + ArgumentNullException.ThrowIfNull(stack); + ArgumentNullException.ThrowIfNull(predicate); + + return stack.Count(predicate); + } + + /// + /// 对堆栈中的每个元素执行指定操作(从顶部到底部) + /// + /// 堆栈元素类型 + /// 堆栈实例 + /// 要执行的操作 + /// 堆栈或操作为空时抛出 + public static void ForEach(this Stack stack, Action action) + { + ArgumentNullException.ThrowIfNull(stack); + ArgumentNullException.ThrowIfNull(action); + + foreach (var item in stack) + { + action(item); + } + } + + /// + /// 对堆栈中的每个元素执行指定操作(带索引,从顶部到底部) + /// + /// 堆栈元素类型 + /// 堆栈实例 + /// 要执行的操作,参数为元素和索引 + /// 堆栈或操作为空时抛出 + public static void ForEach(this Stack stack, Action action) + { + ArgumentNullException.ThrowIfNull(stack); + ArgumentNullException.ThrowIfNull(action); + + var index = 0; + foreach (var item in stack) + { + action(item, index++); + } + } + + /// + /// 创建一个新堆栈,包含满足条件的元素 + /// + /// 堆栈元素类型 + /// 原堆栈 + /// 筛选条件 + /// 包含满足条件元素的新堆栈 + /// 堆栈或条件为空时抛出 + public static Stack Where(this Stack stack, Func predicate) + { + ArgumentNullException.ThrowIfNull(stack); + ArgumentNullException.ThrowIfNull(predicate); + + var filteredItems = stack.Where(predicate).ToArray(); + return new Stack(filteredItems); + } + + /// + /// 创建一个新堆栈,包含转换后的元素 + /// + /// 原堆栈元素类型 + /// 目标堆栈元素类型 + /// 原堆栈 + /// 转换函数 + /// 包含转换后元素的新堆栈 + /// 堆栈或转换函数为空时抛出 + public static Stack Select(this Stack stack, Func selector) + { + ArgumentNullException.ThrowIfNull(stack); + ArgumentNullException.ThrowIfNull(selector); + + var transformedItems = stack.Select(selector).ToArray(); + return new Stack(transformedItems); + } + + /// + /// 反转堆栈中的元素顺序 + /// + /// 堆栈元素类型 + /// 堆栈实例 + /// 堆栈为空时抛出 + public static void Reverse(this Stack stack) + { + ArgumentNullException.ThrowIfNull(stack); + + if (stack.Count <= 1) + { + return; + } + + var items = new T[stack.Count]; + var index = 0; + + while (stack.Count > 0) + { + items[index++] = stack.Pop(); + } + + foreach (var item in items) + { + stack.Push(item); + } + } + + /// + /// 获取堆栈的深度副本(递归反转以保持原始顺序) + /// + /// 堆栈元素类型 + /// 原堆栈 + /// 深度副本的新堆栈 + /// 堆栈为空时抛出 + public static Stack DeepClone(this Stack stack) + { + ArgumentNullException.ThrowIfNull(stack); + + var tempStack = new Stack(); + var result = new Stack(); + + // 第一次反转到临时堆栈 + foreach (var item in stack) + { + tempStack.Push(item); + } + + // 第二次反转到结果堆栈,恢复原始顺序 + while (tempStack.Count > 0) + { + result.Push(tempStack.Pop()); + } + + return result; + } + + /// + /// 限制堆栈的最大长度,超出时移除底部元素 + /// + /// 堆栈元素类型 + /// 堆栈实例 + /// 最大长度 + /// 堆栈为空时抛出 + /// 最大长度小于0时抛出 + public static void LimitSize(this Stack stack, int maxSize) + { + ArgumentNullException.ThrowIfNull(stack); + + if (maxSize < 0) + { + throw new ArgumentOutOfRangeException(nameof(maxSize), "最大长度不能小于0"); + } + + if (stack.Count <= maxSize) + { + return; + } + + // 将堆栈内容转移到数组,保留最新的maxSize个元素 + var items = stack.ToArray(); + stack.Clear(); + + // 重新入栈最新的maxSize个元素 + for (var i = Math.Min(maxSize - 1, items.Length - 1); i >= 0; i--) + { + stack.Push(items[i]); + } + } + + /// + /// 安全地入栈元素,如果堆栈已满则移除底部元素 + /// + /// 堆栈元素类型 + /// 堆栈实例 + /// 要入栈的元素 + /// 堆栈最大长度 + /// 被移除的元素(如果有) + /// 堆栈为空时抛出 + /// 最大长度小于1时抛出 + public static T? PushWithLimit(this Stack stack, T item, int maxSize) + { + ArgumentNullException.ThrowIfNull(stack); + + if (maxSize < 1) + { + throw new ArgumentOutOfRangeException(nameof(maxSize), "最大长度不能小于1"); + } + + T? removedItem = default; + + if (stack.Count >= maxSize) + { + // 将所有元素转移到数组 + var items = stack.ToArray(); + stack.Clear(); + + // 保存被移除的底部元素 + if (items.Length > 0) + { + removedItem = items[items.Length - 1]; + } + + // 重新入栈,跳过底部元素 + for (var i = Math.Min(maxSize - 2, items.Length - 2); i >= 0; i--) + { + stack.Push(items[i]); + } + } + + stack.Push(item); + return removedItem; + } + + /// + /// 检查是否所有元素都满足条件 + /// + /// 堆栈元素类型 + /// 堆栈实例 + /// 匹配条件 + /// 是否所有元素都满足条件 + /// 堆栈或条件为空时抛出 + public static bool All(this Stack stack, Func predicate) + { + ArgumentNullException.ThrowIfNull(stack); + ArgumentNullException.ThrowIfNull(predicate); + + return stack.All(predicate); + } + + /// + /// 检查是否至少有一个元素满足条件 + /// + /// 堆栈元素类型 + /// 堆栈实例 + /// 匹配条件 + /// 是否至少有一个元素满足条件 + /// 堆栈或条件为空时抛出 + public static bool Any(this Stack stack, Func predicate) + { + ArgumentNullException.ThrowIfNull(stack); + ArgumentNullException.ThrowIfNull(predicate); + + return stack.Any(predicate); + } + + /// + /// 合并两个堆栈(第二个堆栈的元素将位于顶部) + /// + /// 堆栈元素类型 + /// 第一个堆栈 + /// 第二个堆栈 + /// 合并后的新堆栈 + /// 任一堆栈为空时抛出 + public static Stack Concat(this Stack first, Stack second) + { + ArgumentNullException.ThrowIfNull(first); + ArgumentNullException.ThrowIfNull(second); + + var result = new Stack(); + + // 先添加第一个堆栈的元素(从底部到顶部) + var firstItems = first.ToArray(); + for (var i = firstItems.Length - 1; i >= 0; i--) + { + result.Push(firstItems[i]); + } + + // 再添加第二个堆栈的元素(从底部到顶部) + var secondItems = second.ToArray(); + for (var i = secondItems.Length - 1; i >= 0; i--) + { + result.Push(secondItems[i]); + } + + return result; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Utils/Collections/TreeExtensions.cs b/Admin.NET/Admin.NET.Core/Utils/Collections/TreeExtensions.cs new file mode 100644 index 00000000..c44680f4 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Utils/Collections/TreeExtensions.cs @@ -0,0 +1,375 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 树扩展方法 +/// +public static class TreeExtensions +{ + /// + /// 转为树形结构 + /// + /// 树节点数据类型 + /// 源数据集合 + /// 判断父子关系的函数,第一个参数为父级,第二个参数为子级 + /// 转换后的树形结构根节点集合 + public static IEnumerable> ToTree(this IEnumerable source, Func isChild) + { + var nodes = source.Select(value => new TreeNode(value)).ToList(); + var visited = new HashSet(); + + foreach (var node in nodes) + { + if (visited.Contains(node.Value)) + { + continue; + } + + var stack = new Stack>(); + stack.Push(node); + + while (stack.Count > 0) + { + var current = stack.Pop(); + if (visited.Contains(current.Value)) + { + throw new InvalidOperationException("转为树形结构时,循环依赖检测到"); + } + + _ = visited.Add(current.Value); + + foreach (var child in nodes.Where(child => isChild(current.Value, child.Value))) + { + current.Children.Add(child); + stack.Push(child); + } + } + } + + return nodes.Where(node => !nodes.Any(n => n.Children.Contains(node))); + } + + /// + /// 根据主键和父级主键生成树形结构 + /// + /// 树节点数据类型 + /// 源数据集合 + /// 主键选择器 + /// 父级主键选择器 + /// 树形结构 + public static IEnumerable> ToTree(this IEnumerable source, Func keySelector, Func parentKeySelector) + { + var nodes = source.Select(value => new TreeNode(value)).ToList(); + var lookup = nodes.ToLookup(node => parentKeySelector(node.Value), node => node); + + foreach (var node in nodes) + { + node.Children.AddRange(lookup[keySelector(node.Value)]); + } + + return nodes.Where(node => !nodes.Any(n => keySelector(n.Value).Equals(parentKeySelector(node.Value)))); + } + + /// + /// 添加子节点 + /// + /// 树节点数据类型 + /// 父节点 + /// 要添加的子节点值 + /// + public static void AddChild(this TreeNode parent, T value) + { + ArgumentNullException.ThrowIfNull(parent); + + parent.Children.Add(new TreeNode(value)); + } + + /// + /// 添加子节点到指定的父节点 + /// + /// 树节点数据类型 + /// 源数据集合 + /// 父节点对象 + /// 子节点对象 + /// 主键选择器 + /// 父级主键选择器 + public static void AddChild(this IEnumerable> source, T parent, T child, Func keySelector, Func parentKeySelector) + { + if (parent is null) + { + throw new ArgumentNullException(nameof(parent), "父节点不能为空"); + } + + if (child is null) + { + throw new ArgumentNullException(nameof(child), "子节点不能为空"); + } + + var parentNode = source + .DepthFirstTraversal() + .FirstOrDefault(node => keySelector(node.Value).Equals(keySelector(parent))) + ?? throw new InvalidOperationException("在树中未找到父节点"); + + _ = parentKeySelector.Invoke(child).SetPropertyValue("Children", keySelector(parent)); + parentNode.Children.Add(new TreeNode(child)); + } + + /// + /// 删除节点 + /// + /// 树节点数据类型 + /// 根节点 + /// 要删除的节点值 + /// 如果成功删除节点则返回 true,否则返回 false + public static bool RemoveNode(this TreeNode? root, T value) + { + if (root is null) + { + return false; + } + + foreach (var child in root.Children.ToList()) + { + if (EqualityComparer.Default.Equals(child.Value, value)) + { + _ = root.Children.Remove(child); + return true; + } + + if (RemoveNode(child, value)) + { + return true; + } + } + + return false; + } + + /// + /// 深度优先遍历 (DFS) + /// + /// 树节点数据类型 + /// 根节点 + /// 深度优先遍历的节点序列 + public static IEnumerable> DepthFirstTraversal(this TreeNode? root) + { + if (root is null) + { + yield break; + } + + yield return root; + + foreach (var child in root.Children) + { + foreach (var descendant in child.DepthFirstTraversal()) + { + yield return descendant; + } + } + } + + /// + /// 深度优先遍历 (DFS) - 遍历树形结构中所有节点 + /// + /// 树节点数据类型 + /// 树形结构根节点集合 + /// 深度优先遍历的节点序列 + public static IEnumerable> DepthFirstTraversal(this IEnumerable>? source) + { + if (source is null) + { + yield break; + } + + foreach (var root in source) + { + foreach (var node in root.DepthFirstTraversal()) + { + yield return node; + } + } + } + + /// + /// 广度优先遍历 (BFS) + /// + /// 树节点数据类型 + /// 根节点 + /// 广度优先遍历的节点序列 + public static IEnumerable> BreadthFirstTraversal(this TreeNode? root) + { + if (root is null) + { + yield break; + } + + var queue = new Queue>(); + queue.Enqueue(root); + + while (queue.Count > 0) + { + var current = queue.Dequeue(); + yield return current; + + foreach (var child in current.Children) + { + queue.Enqueue(child); + } + } + } + + /// + /// 查找节点 (DFS) + /// + /// 树节点数据类型 + /// 根节点 + /// 要查找的节点值 + /// 找到的节点,如果未找到则返回 null + public static TreeNode? FindNode(this TreeNode root, T value) + { + return root.DepthFirstTraversal().FirstOrDefault(node => EqualityComparer.Default.Equals(node.Value, value)); + } + + /// + /// 获取节点路径 + /// + /// 树节点数据类型 + /// 根节点 + /// 要获取路径的节点值 + /// 从根节点到目标节点的路径,如果未找到则返回 null + public static List>? GetPath(this TreeNode root, T value) + { + var path = new List>(); + return FindPath(root, value, path) ? path : null; + } + + /// + /// 获取树的高度 + /// + /// 树节点数据类型 + /// 根节点 + /// 树的高度,空树返回 0 + public static int GetHeight(this TreeNode? root) + { + return root is null ? 0 : 1 + root.Children.Select(child => child.GetHeight()).DefaultIfEmpty(0).Max(); + } + + /// + /// 获取叶子节点 + /// + /// 树节点数据类型 + /// 根节点 + /// 所有叶子节点的集合 + public static IEnumerable> GetLeafNodes(this TreeNode root) + { + return root.DepthFirstTraversal().Where(node => node.Children.Count == 0); + } + + #region 私有方法 + + /// + /// 查找路径 + /// + /// 树节点数据类型 + /// 当前节点 + /// 要查找的节点值 + /// 路径记录 + /// 如果找到目标节点则返回 true,否则返回 false + private static bool FindPath(TreeNode? node, T value, List> path) + { + if (node is null) + { + return false; + } + + path.Add(node); + + if (EqualityComparer.Default.Equals(node.Value, value)) + { + return true; + } + + foreach (var child in node.Children) + { + if (FindPath(child, value, path)) + { + return true; + } + } + + path.RemoveAt(path.Count - 1); + return false; + } + + #endregion 私有方法 + + /// + /// 设置对象属性值 + /// + /// + /// + /// + /// + /// + /// + public static bool SetPropertyValue(this TEntity entity, string propertyName, TValue value) + { + var objectType = typeof(TEntity); + var propertyInfo = objectType.GetProperty(propertyName); + if (propertyInfo is null || !propertyInfo.PropertyType.IsGenericType) + { + throw new ArgumentException($"属性 '{propertyName}' 不存在,或者不是类型 '{objectType.Name}' 中的泛型类型。"); + } + + var paramObj = Expression.Parameter(objectType); + var paramVal = Expression.Parameter(typeof(TValue)); + var bodyVal = Expression.Convert(paramVal, propertyInfo.PropertyType); + + // 获取设置属性的值的方法 + var setMethod = propertyInfo.GetSetMethod(true); + + // 如果只是只读,则 setMethod==null + if (setMethod is null) + { + return false; + } + + var body = Expression.Call(paramObj, setMethod, bodyVal); + var setValue = Expression.Lambda>(body, paramObj, paramVal).Compile(); + setValue(entity, value); + + return true; + } +} + +/// +/// 树节点数据传输对象 +/// +/// +public class TreeNode +{ + /// + /// 构造函数 + /// + /// 节点值 + public TreeNode(T value) + { + Value = value; + } + + /// + /// 节点值 + /// + public T Value { get; set; } + + /// + /// 子节点 + /// + public List> Children { get; set; } = []; +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Utils/CommandLine/ScriptExecutor.cs b/Admin.NET/Admin.NET.Core/Utils/CommandLine/ScriptExecutor.cs new file mode 100644 index 00000000..b8e1fc52 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Utils/CommandLine/ScriptExecutor.cs @@ -0,0 +1,146 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 脚本执行助手类,支持执行 .sh、.ps1、.bat 脚本 +/// +public static class ScriptExecutor +{ + /// + /// 执行脚本 + /// + /// 脚本文件的完整路径 + /// 传递给脚本的参数 + /// 执行结果(标准输出和标准错误) + public static string ExecuteScript(string scriptFilePath, string arguments = "") + { + if (string.IsNullOrWhiteSpace(scriptFilePath) || !File.Exists(scriptFilePath)) + { + throw new ArgumentException("脚本文件不存在", nameof(scriptFilePath)); + } + + var fileExtension = Path.GetExtension(scriptFilePath).ToLower(); + return fileExtension switch + { + ".sh" => ExecuteShellScript(scriptFilePath, arguments), + ".ps1" => ExecutePowerShellScript(scriptFilePath, arguments), + ".bat" => ExecuteBatchScript(scriptFilePath, arguments), + _ => throw new NotSupportedException("不支持的脚本类型") + }; + } + + /// + /// 执行 Shell 脚本(Linux/macOS) + /// + /// 脚本文件的完整路径 + /// 传递给脚本的参数 + /// 执行结果 + private static string ExecuteShellScript(string scriptFilePath, string arguments) + { + var processStartInfo = new ProcessStartInfo + { + FileName = "/bin/bash", // Linux/macOS 使用 bash 执行脚本 + Arguments = $"{scriptFilePath} {arguments}", + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + return ExecuteProcess(processStartInfo); + } + + /// + /// 执行 PowerShell 脚本(Windows) + /// + /// 脚本文件的完整路径 + /// 传递给脚本的参数 + /// 执行结果 + private static string ExecutePowerShellScript(string scriptFilePath, string arguments) + { + var processStartInfo = new ProcessStartInfo + { + FileName = "powershell", // Windows 使用 PowerShell 执行脚本 + Arguments = $"-ExecutionPolicy Bypass -File \"{scriptFilePath}\" {arguments}", + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + return ExecuteProcess(processStartInfo); + } + + /// + /// 执行批处理脚本(Windows) + /// + /// 脚本文件的完整路径 + /// 传递给脚本的参数 + /// 执行结果 + private static string ExecuteBatchScript(string scriptFilePath, string arguments) + { + var processStartInfo = new ProcessStartInfo + { + FileName = scriptFilePath, // Windows 使用 cmd 执行批处理脚本 + Arguments = arguments, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + return ExecuteProcess(processStartInfo); + } + + /// + /// 执行进程并获取输出结果 + /// + /// 进程启动信息 + /// 执行结果 + private static string ExecuteProcess(ProcessStartInfo processStartInfo) + { + var output = new StringBuilder(); + var error = new StringBuilder(); + + try + { + using var process = Process.Start(processStartInfo) ?? throw new InvalidOperationException("无法启动进程"); + process.OutputDataReceived += (o, e) => + { + if (e.Data is not null) + { + o = output.AppendLine(e.Data); + } + }; + + process.ErrorDataReceived += (o, e) => + { + if (e.Data is not null) + { + o = error.AppendLine(e.Data); + } + }; + + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + + process.WaitForExit(); + + if (process.ExitCode != 0) + { + throw new Exception($"脚本执行失败,错误信息:{error}"); + } + } + catch (Exception ex) + { + return $"执行过程中发生错误:{ex.Message}"; + } + + return output.ToString(); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Utils/CommandLine/ShellHelper.cs b/Admin.NET/Admin.NET.Core/Utils/CommandLine/ShellHelper.cs new file mode 100644 index 00000000..37a07bc1 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Utils/CommandLine/ShellHelper.cs @@ -0,0 +1,76 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// ShellHelper +/// +public static class ShellHelper +{ + /// + /// Unix 系统命令 + /// + /// 要执行的 Unix/Linux 命令 + /// 命令执行后的标准输出结果 + public static string Bash(string command) + { + var output = string.Empty; + var escapedArgs = command.Replace(@"""", @"\"""); + + ProcessStartInfo info = new() + { + FileName = @"/bin/bash", + // /bin/bash -c 后面接命令 ,而 /bin/bash 后面接执行的脚本 + Arguments = $""" + -c "{escapedArgs}" + """, + RedirectStandardOutput = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + using var process = Process.Start(info); + if (process is null) + { + return output; + } + + output = process.StandardOutput.ReadToEnd(); + return output; + } + + /// + /// Windows 系统命令 + /// + /// 要执行的程序或命令文件名 + /// 传递给程序的命令行参数 + /// 命令执行后的标准输出结果 + public static string Cmd(string fileName, string args) + { + var output = string.Empty; + + ProcessStartInfo info = new() + { + FileName = fileName, + Arguments = args, + RedirectStandardOutput = true, + // 指定是否使用操作系统的外壳程序来启动进程,如果设置为 false,则使用 CreateProcess 函数直接启动进程 + UseShellExecute = false, + // 指定是否在启动进程时创建一个新的窗口 + CreateNoWindow = true + }; + + using var process = Process.Start(info); + if (process is null) + { + return output; + } + + output = process.StandardOutput.ReadToEnd(); + return output; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Utils/CommonUtil.cs b/Admin.NET/Admin.NET.Core/Utils/CommonHelper.cs similarity index 99% rename from Admin.NET/Admin.NET.Core/Utils/CommonUtil.cs rename to Admin.NET/Admin.NET.Core/Utils/CommonHelper.cs index 984f0cf5..965c38d1 100644 --- a/Admin.NET/Admin.NET.Core/Utils/CommonUtil.cs +++ b/Admin.NET/Admin.NET.Core/Utils/CommonHelper.cs @@ -15,7 +15,7 @@ namespace Admin.NET.Core; /// /// 通用工具类 /// -public static class CommonUtil +public static class CommonHelper { /// /// 根据字符串获取固定整型哈希值 @@ -52,7 +52,7 @@ public static class CommonUtil string noLetters = Regex.Replace(version, "[a-zA-Z]", ""); // 2. 按 '.' 分割版本号 - string[] parts = noLetters.Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries); + string[] parts = noLetters.Split(['.'], StringSplitOptions.RemoveEmptyEntries); // 3. 确保至少有3部分,不足则补 "0" if (parts.Length < 3) diff --git a/Admin.NET/Admin.NET.Core/Utils/ComputerUtil.cs b/Admin.NET/Admin.NET.Core/Utils/ComputerUtil.cs deleted file mode 100644 index d3cd0408..00000000 --- a/Admin.NET/Admin.NET.Core/Utils/ComputerUtil.cs +++ /dev/null @@ -1,521 +0,0 @@ -// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 -// -// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 -// -// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! - -namespace Admin.NET.Core; - -public static class ComputerUtil -{ - /// - /// 内存信息 - /// - /// - public static MemoryMetrics GetComputerInfo() - { - MemoryMetrics memoryMetrics; - if (IsMacOS()) - { - memoryMetrics = MemoryMetricsClient.GetMacOSMetrics(); - } - else if (IsUnix()) - { - memoryMetrics = MemoryMetricsClient.GetUnixMetrics(); - } - else - { - memoryMetrics = MemoryMetricsClient.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) + "%"; - var cpuRates = GetCPURates(); - if (cpuRates != null) - { - memoryMetrics.CpuRates = cpuRates.Select(u => Math.Ceiling(u.ToDouble()) + "%").ToList(); - } - return memoryMetrics; - } - - /// - /// 获取正确的操作系统版本(Linux获取发行版本) - /// - /// - public static String GetOSInfo() - { - string operation = string.Empty; - if (IsMacOS()) - { - var output = ShellUtil.Bash("sw_vers | awk 'NR<=2{printf \"%s \", $NF}'"); - if (output != null) - { - operation = output.Replace("%", string.Empty); - } - } - else if (IsUnix()) - { - var output = ShellUtil.Bash("awk -F= '/^VERSION_ID/ {print $2}' /etc/os-release | tr -d '\"'"); - operation = output ?? string.Empty; - } - else - { - operation = RuntimeInformation.OSDescription; - } - return operation; - } - - /// - /// 磁盘信息 - /// - /// - public static List GetDiskInfos() - { - var diskInfos = new List(); - if (IsMacOS()) - { - var output = ShellUtil.Bash(@"df -m | awk '/^\/dev\/disk/ {print $1,$2,$3,$4,$5}'"); - var disks = output.Split('\n', StringSplitOptions.RemoveEmptyEntries); - if (disks.Length < 1) return diskInfos; - foreach (var item in disks) - { - var disk = item.Split(' ', (char)StringSplitOptions.RemoveEmptyEntries); - if (disk.Length < 5) continue; - - var diskInfo = new DiskInfo() - { - DiskName = disk[0], - TypeName = ShellUtil.Bash("diskutil info " + disk[0] + " | awk '/File System Personality/ {print $4}'").Replace("\n", string.Empty), - TotalSize = Math.Round(long.Parse(disk[1]) / 1024.0m, 2, MidpointRounding.AwayFromZero), - Used = Math.Round(long.Parse(disk[2]) / 1024.0m, 2, MidpointRounding.AwayFromZero), - AvailableFreeSpace = Math.Round(long.Parse(disk[3]) / 1024.0m, 2, MidpointRounding.AwayFromZero), - AvailablePercent = decimal.Parse(disk[4].Replace("%", "")) - }; - diskInfos.Add(diskInfo); - } - } - else if (IsUnix()) - { - var output = ShellUtil.Bash(@"df -mT | awk '/^\/dev\/(sd|vd|xvd|nvme|sda|vda|mapper)/ {print $1,$2,$3,$4,$5,$6}'"); - var disks = output.Split('\n', StringSplitOptions.RemoveEmptyEntries); - if (disks.Length < 1) return diskInfos; - - //var rootDisk = disks[1].Split(' ', (char)StringSplitOptions.RemoveEmptyEntries); - //if (rootDisk == null || rootDisk.Length < 1) - // return diskInfos; - - foreach (var item in disks) - { - var disk = item.Split(' ', (char)StringSplitOptions.RemoveEmptyEntries); - if (disk.Length < 6) continue; - - var diskInfo = new DiskInfo() - { - DiskName = disk[0], - TypeName = disk[1], - TotalSize = Math.Round(long.Parse(disk[2]) / 1024.0m, 2, MidpointRounding.AwayFromZero), - Used = Math.Round(long.Parse(disk[3]) / 1024.0m, 2, MidpointRounding.AwayFromZero), - AvailableFreeSpace = Math.Round(long.Parse(disk[4]) / 1024.0m, 2, MidpointRounding.AwayFromZero), - AvailablePercent = decimal.Parse(disk[5].Replace("%", "")) - }; - diskInfos.Add(diskInfo); - } - } - else - { - var driveList = DriveInfo.GetDrives().Where(u => u.IsReady); - foreach (var item in driveList) - { - if (item.DriveType == DriveType.CDRom) continue; - var diskInfo = new DiskInfo() - { - DiskName = item.Name, - TypeName = item.DriveType.ToString(), - TotalSize = Math.Round(item.TotalSize / 1024 / 1024 / 1024.0m, 2, MidpointRounding.AwayFromZero), - AvailableFreeSpace = Math.Round(item.AvailableFreeSpace / 1024 / 1024 / 1024.0m, 2, MidpointRounding.AwayFromZero), - }; - diskInfo.Used = diskInfo.TotalSize - diskInfo.AvailableFreeSpace; - diskInfo.AvailablePercent = decimal.Ceiling(diskInfo.Used / (decimal)diskInfo.TotalSize * 100); - diskInfos.Add(diskInfo); - } - } - return diskInfos; - } - - /// - /// 获取外网IP地址 - /// - /// - public static string GetIpFromOnline() - { - try - { - var url = "https://www.ip.cn/api/index?ip&type=0"; - var httpRemoteService = App.GetRequiredService(); - var str = httpRemoteService.GetAsStringAsync(url).GetAwaiter().GetResult(); - var resp = JSON.Deserialize(str); - return resp.Ip + " " + resp.Address; - } - catch - { - return "unknow"; - } - } - - public static bool IsUnix() - { - return RuntimeInformation.IsOSPlatform(OSPlatform.Linux); - } - - public static bool IsMacOS() - { - return RuntimeInformation.IsOSPlatform(OSPlatform.OSX); - } - - public static List GetCPURates() - { - var cpuRates = new List(); - if (IsMacOS()) - { - string output = ShellUtil.Bash("top -l 1 | grep \"CPU usage\" | awk '{print $3 + $5}'"); - cpuRates.Add(output.Trim()); - } - else if (IsUnix()) - { - string output = ShellUtil.Bash("awk '{u=$2+$4; t=$2+$4+$5; if (NR==1){u1=u; t1=t;} else print ($2+$4-u1) * 100 / (t-t1); }' <(grep 'cpu ' /proc/stat) <(sleep 1;grep 'cpu ' /proc/stat)"); - cpuRates.Add(output.Trim()); - } - else - { - string output = ShellUtil.Cmd("wmic", "cpu get LoadPercentage"); - cpuRates.AddRange(output.Replace("LoadPercentage", string.Empty).Trim().Split("\r\r\n")); - } - return cpuRates; - } - - /// - /// 获取系统运行时间 - /// - /// - public static string GetRunTime() - { - string runTime = string.Empty; - if (IsMacOS()) - { - // macOS 获取系统启动时间: - // sysctl -n kern.boottime | awk '{print $4}' | tr -d ',' - // 返回:1705379131 - // 使用date格式化即可 - string output = ShellUtil.Bash("date -r $(sysctl -n kern.boottime | awk '{print $4}' | tr -d ',') +\"%Y-%m-%d %H:%M:%S\"").Trim(); - runTime = DateTimeUtil.FormatTime((DateTime.Now - output.ToDateTime()).TotalMilliseconds.ToString().Split('.')[0].ToLong()); - } - else if (IsUnix()) - { - string output = ShellUtil.Bash("date -d \"$(awk -F. '{print $1}' /proc/uptime) second ago\" +\"%Y-%m-%d %H:%M:%S\"").Trim(); - runTime = DateTimeUtil.FormatTime((DateTime.Now - output.ToDateTime()).TotalMilliseconds.ToString().Split('.')[0].ToLong()); - } - 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].ToDateTime()).TotalMilliseconds.ToString().Split('.')[0].ToLong()); - } - return runTime; - } -} - -/// -/// IP信息 -/// -public class IpCnResp -{ - public string Ip { get; set; } - - public string Address { get; set; } -} - -/// -/// 内存信息 -/// -public class MemoryMetrics -{ - [Newtonsoft.Json.JsonIgnore] - [System.Text.Json.Serialization.JsonIgnore] - public double Total { get; set; } - - [Newtonsoft.Json.JsonIgnore] - [System.Text.Json.Serialization.JsonIgnore] - public double Used { get; set; } - - [Newtonsoft.Json.JsonIgnore] - [System.Text.Json.Serialization.JsonIgnore] - public double Free { get; set; } - - /// - /// 已用内存 - /// - public string UsedRam { get; set; } - - /// - /// CPU使用率% - /// - public List CpuRates { get; set; } - - /// - /// 总内存 GB - /// - public string TotalRam { get; set; } - - /// - /// 内存使用率 % - /// - public string RamRate { get; set; } - - /// - /// 空闲内存 - /// - public string FreeRam { get; set; } -} - -/// -/// 磁盘信息 -/// -public class DiskInfo -{ - /// - /// 磁盘名 - /// - public string DiskName { get; set; } - - /// - /// 类型名 - /// - public string TypeName { get; set; } - - /// - /// 总剩余 - /// - public decimal TotalFree { get; set; } - - /// - /// 总量 - /// - public decimal TotalSize { get; set; } - - /// - /// 已使用 - /// - public decimal Used { get; set; } - - /// - /// 可使用 - /// - public decimal AvailableFreeSpace { get; set; } - - /// - /// 使用百分比 - /// - public decimal AvailablePercent { get; set; } -} - -public class MemoryMetricsClient -{ - /// - /// windows系统获取内存信息 - /// - /// - public static 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 <= 1) 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; - } - - /// - /// Unix系统获取 - /// - /// - public static MemoryMetrics GetUnixMetrics() - { - string output = ShellUtil.Bash("awk '/MemTotal/ {total=$2} /MemAvailable/ {available=$2} END {print total,available}' /proc/meminfo"); - var metrics = new MemoryMetrics(); - var memory = output.Split(' ', (char)StringSplitOptions.RemoveEmptyEntries); - if (memory.Length != 2) return metrics; - - metrics.Total = double.Parse(memory[0]) / 1024; - metrics.Free = double.Parse(memory[1]) / 1024; - metrics.Used = metrics.Total - metrics.Free; - return metrics; - } - - /// - /// macOS系统获取 - /// - /// - public static MemoryMetrics GetMacOSMetrics() - { - var metrics = new MemoryMetrics(); - //物理内存大小 - var total = ShellUtil.Bash("sysctl -n hw.memsize | awk '{printf \"%.2f\", $1/1024/1024}'"); - metrics.Total = float.Parse(total.Replace("%", string.Empty)); - //TODO:占用内存,检查效率 - var free = ShellUtil.Bash("top -l 1 -s 0 | awk '/PhysMem/ {print $6+$8}'"); - metrics.Free = float.Parse(free); - metrics.Used = metrics.Total - metrics.Free; - return metrics; - } -} - -public class ShellUtil -{ - /// - /// linux 系统命令 - /// - /// - /// - 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; - } - - /// - /// windows CMD 系统命令 - /// - /// - /// - /// - public static string Cmd(string fileName, string args) - { - var info = new ProcessStartInfo - { - FileName = fileName, - Arguments = args, - RedirectStandardOutput = true - }; - - var output = string.Empty; - using (var process = Process.Start(info)) - { - output = process.StandardOutput.ReadToEnd(); - } - return output; - } - - /// - /// Windows POWERSHELL 系统命令 - /// - /// - /// - public static string PowerShell(string script) - { - using var PowerShellInstance = System.Management.Automation.PowerShell.Create(); - PowerShellInstance.AddScript(script); - var PSOutput = PowerShellInstance.Invoke(); - - var output = new StringBuilder(); - foreach (var outputItem in PSOutput) - { - output.AppendLine(outputItem.BaseObject.ToString()); - } - return output.ToString(); - } -} - -public class ShellHelper -{ - /// - /// Linux 系统命令 - /// - /// - /// - 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; - } - - /// - /// Windows CMD 系统命令 - /// - /// - /// - /// - public static string Cmd(string fileName, string args) - { - var info = new ProcessStartInfo - { - FileName = fileName, - Arguments = args, - RedirectStandardOutput = true - }; - - var output = string.Empty; - using (var process = Process.Start(info)) - { - output = process.StandardOutput.ReadToEnd(); - } - return output; - } - - /// - /// Windows POWERSHELL 系统命令 - /// - /// - /// - public static string PowerShell(string script) - { - using var PowerShellInstance = System.Management.Automation.PowerShell.Create(); - PowerShellInstance.AddScript(script); - var PSOutput = PowerShellInstance.Invoke(); - - var output = new StringBuilder(); - foreach (var outputItem in PSOutput) - { - output.AppendLine(outputItem.BaseObject.ToString()); - } - return output.ToString(); - } -} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Utils/ChinaDateTimeConverter.cs b/Admin.NET/Admin.NET.Core/Utils/DateTime/ChinaDateTimeConverter.cs similarity index 100% rename from Admin.NET/Admin.NET.Core/Utils/ChinaDateTimeConverter.cs rename to Admin.NET/Admin.NET.Core/Utils/DateTime/ChinaDateTimeConverter.cs diff --git a/Admin.NET/Admin.NET.Core/Utils/DateTime/DateTimeFormatExtensions.cs b/Admin.NET/Admin.NET.Core/Utils/DateTime/DateTimeFormatExtensions.cs new file mode 100644 index 00000000..f98bd2e7 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Utils/DateTime/DateTimeFormatExtensions.cs @@ -0,0 +1,303 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using System.Globalization; + +namespace Admin.NET.Core; + +/// +/// DateTime 扩展方法 +/// +public static class DateTimeFormatExtensions +{ + /// + /// 获取 Unix 时间戳 + /// + /// + /// + public static long GetUnixTimeStamp(this DateTime dateTime) + { + return ((DateTimeOffset)dateTime).ToUnixTimeMilliseconds(); + } + + /// + /// 获取 Unix 时间戳 + /// + /// + /// + public static long GetUnixTimeStamp(this DateTimeOffset dateTime) + { + return dateTime.ToUnixTimeMilliseconds(); + } + + /// + /// 获取当前时间的时间戳 + /// + /// + /// + public static long GetDateToTimeStamp(this DateTime dateTime) + { + var ts = dateTime - new DateTime(1970, 1, 1, 0, 0, 0, 0); + return Convert.ToInt64(ts.TotalSeconds); + } + + /// + /// 获取日期天的最小时间 + /// + /// + /// + public static DateTime GetDayMinDate(this DateTime dateTime) + { + return new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, 0, 0, 0); + } + + /// + /// 获取日期天的最大时间 + /// + /// + /// + public static DateTime GetDayMaxDate(this DateTime dateTime) + { + return new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, 23, 59, 59); + } + + /// + /// 获取一天的范围 + /// + /// + /// + public static List GetDayDateRange(this DateTime dateTime) + { + return + [ + dateTime.GetDayMinDate(), + dateTime.GetDayMaxDate() + ]; + } + + /// + /// 获取日期开始时间 + /// + /// + /// + /// + public static DateTime GetBeginTime(this DateTime? dateTime, int days = 0) + { + return dateTime == DateTime.MinValue || dateTime is null ? DateTime.Now.AddDays(days) : (DateTime)dateTime; + } + + /// + /// 获取星期几 + /// + /// + /// + public static string GetWeekByDate(this DateTime dateTime) + { + string[] day = ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"]; + return day[Convert.ToInt32(dateTime.DayOfWeek.ToString("d"))]; + } + + /// + /// 获取这个月的第几周 + /// + /// + /// + public static int GetWeekNumInMonth(this DateTime daytime) + { + var dayInMonth = daytime.Day; + // 本月第一天 + var firstDay = daytime.AddDays(1 - daytime.Day); + // 本月第一天是周几 + var weekday = firstDay.DayOfWeek == 0 ? 7 : (int)firstDay.DayOfWeek; + // 本月第一周有几天 + var firstWeekEndDay = 7 - (weekday - 1); + // 当前日期和第一周之差 + var diffDay = dayInMonth - firstWeekEndDay; + diffDay = diffDay > 0 ? diffDay : 1; + // 当前是第几周,若整除7就减一天 + return (diffDay % 7 == 0 ? (diffDay / 7) - 1 : diffDay / 7) + 1 + (dayInMonth > firstWeekEndDay ? 1 : 0); + } + + /// + /// 时间转换字符串 + /// + /// + /// + public static string FormatDateTimeToString(this DateTime dateTime) + { + return dateTime.ToString(dateTime.Year == DateTime.Now.Year ? "MM-dd HH:mm" : "yyyy-MM-dd HH:mm"); + } + + /// + /// 时间转换字符串 + /// + /// + /// + /// + public static string FormatDateTimeToString(this DateTime dateTimeBefore, DateTime dateTimeAfter) + { + if (dateTimeBefore >= dateTimeAfter) + { + throw new Exception("开始日期必须小于结束日期"); + } + + var timeSpan = dateTimeAfter - dateTimeBefore; + return timeSpan.FormatTimeSpanToString(); + } + + /// + /// 毫秒转换字符串 + /// + /// + /// + public static string FormatMilliSecondsToString(this long milliseconds) + { + var timeSpan = TimeSpan.FromMilliseconds(milliseconds); + return timeSpan.FormatTimeSpanToString(); + } + + /// + /// 时刻转换字符串 + /// + /// + /// + public static string FormatTimeTicksToString(this long ticks) + { + var timeSpan = TimeSpan.FromTicks(ticks); + return timeSpan.FormatTimeSpanToString(); + } + + /// + /// 毫秒转换字符串 + /// + /// + /// + public static string FormatTimeMilliSecondToString(this long ms) + { + const int Ss = 1000; + const int Mi = Ss * 60; + const int Hh = Mi * 60; + const int Dd = Hh * 24; + + var day = ms / Dd; + var hour = (ms - (day * Dd)) / Hh; + var minute = (ms - (day * Dd) - (hour * Hh)) / Mi; + var second = (ms - (day * Dd) - (hour * Hh) - (minute * Mi)) / Ss; + var milliSecond = ms - (day * Dd) - (hour * Hh) - (minute * Mi) - (second * Ss); + + // 天 + var sDay = day < 10 ? "0" + day : string.Empty + day; + // 小时 + var sHour = hour < 10 ? "0" + hour : string.Empty + hour; + // 分钟 + var sMinute = minute < 10 ? "0" + minute : string.Empty + minute; + // 秒 + var sSecond = second < 10 ? "0" + second : string.Empty + second; + // 毫秒 + var sMilliSecond = milliSecond < 10 ? "0" + milliSecond : string.Empty + milliSecond; + sMilliSecond = milliSecond < 100 ? "0" + sMilliSecond : string.Empty + sMilliSecond; + + return $"{sDay} 天 {sHour} 小时 {sMinute} 分 {sSecond} 秒 {sMilliSecond} 毫秒"; + } + + /// + /// 时间跨度转换字符串 + /// + /// + /// + public static string FormatTimeSpanToString(this TimeSpan timeSpan) + { + var day = timeSpan.Days; + var hour = timeSpan.Hours; + var minute = timeSpan.Minutes; + var second = timeSpan.Seconds; + var milliSecond = timeSpan.Milliseconds; + + // 天 + var sDay = day < 10 ? "0" + day : string.Empty + day; + // 小时 + var sHour = hour < 10 ? "0" + hour : string.Empty + hour; + // 分钟 + var sMinute = minute < 10 ? "0" + minute : string.Empty + minute; + // 秒 + var sSecond = second < 10 ? "0" + second : string.Empty + second; + // 毫秒 + var sMilliSecond = milliSecond < 10 ? "0" + milliSecond : string.Empty + milliSecond; + sMilliSecond = milliSecond < 100 ? "0" + sMilliSecond : string.Empty + sMilliSecond; + + return $"{sDay} 天 {sHour} 小时 {sMinute} 分 {sSecond} 秒 {sMilliSecond} 毫秒"; + } + + /// + /// 时间转换简易字符串 + /// + /// + /// + public static string FormatDateTimeToEasyString(this DateTime value) + { + var now = DateTime.Now; + + if (now < value) + { + var strDate = value.ToString("yyyy-MM-dd HH:mm:ss"); + return strDate; + } + + var dep = now - value; + + return dep.TotalSeconds < 10 + ? "刚刚" + : dep.TotalSeconds is >= 10 and < 60 + ? (int)dep.TotalSeconds + "秒前" + : dep.TotalMinutes is >= 1 and < 60 + ? (int)dep.TotalMinutes + "分钟前" + : dep.TotalHours < 24 + ? (int)dep.TotalHours + "小时前" + : dep.TotalDays < 7 + ? (int)dep.TotalDays + "天前" + : dep.TotalDays is >= 7 and < 30 + ? ((int)dep.TotalDays / 7) + "周前" + : dep.TotalDays is >= 30 and < 365 + ? ((int)dep.TotalDays / 30) + "个月前" + : now.Year - value.Year + "年前"; + } + + /// + /// 字符串转日期 + /// + /// + /// + public static DateTime FormatStringToDate(this string thisValue) + { + try + { + if (string.IsNullOrWhiteSpace(thisValue)) + { + return DateTime.MinValue; + } + + if (thisValue.Contains('-') || thisValue.Contains('/')) + { + return DateTime.Parse(thisValue); + } + + var length = thisValue.Length; + return length switch + { + 4 => DateTime.ParseExact(thisValue, "yyyy", CultureInfo.CurrentCulture), + 6 => DateTime.ParseExact(thisValue, "yyyyMM", CultureInfo.CurrentCulture), + 8 => DateTime.ParseExact(thisValue, "yyyyMMdd", CultureInfo.CurrentCulture), + 10 => DateTime.ParseExact(thisValue, "yyyyMMddHH", CultureInfo.CurrentCulture), + 12 => DateTime.ParseExact(thisValue, "yyyyMMddHHmm", CultureInfo.CurrentCulture), + _ => DateTime.ParseExact(thisValue, "yyyyMMddHHmmss", CultureInfo.CurrentCulture) + }; + } + catch + { + return DateTime.MinValue; + } + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Utils/DateTime/DateTimeRange.cs b/Admin.NET/Admin.NET.Core/Utils/DateTime/DateTimeRange.cs new file mode 100644 index 00000000..6b29e9df --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Utils/DateTime/DateTimeRange.cs @@ -0,0 +1,280 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 表示一个时间范围 +/// +[Serializable] +public class DateTimeRange +{ + /// + /// 初始化一个类型的新实例 + /// + public DateTimeRange() : this(DateTime.MinValue, DateTime.MaxValue) + { + } + + /// + /// 初始化一个类型的新实例 + /// + public DateTimeRange(DateTime startTime, DateTime endTime) + { + StartTime = startTime; + EndTime = endTime; + } + + /// + /// 获取昨天的时间范围 + /// + public static DateTimeRange Yesterday + { + get + { + var now = DateTime.Now; + return new DateTimeRange(now.Date.AddDays(-1), now.Date.AddMilliseconds(-1)); + } + } + + /// + /// 获取今天的时间范围 + /// + public static DateTimeRange Today + { + get + { + var now = DateTime.Now; + return new DateTimeRange(now.Date.Date, now.Date.AddDays(1).AddMilliseconds(-1)); + } + } + + /// + /// 获取明天的时间范围 + /// + public static DateTimeRange Tomorrow + { + get + { + var now = DateTime.Now; + return new DateTimeRange(now.Date.AddDays(1), now.Date.AddDays(2).AddMilliseconds(-1)); + } + } + + /// + /// 获取上周的时间范围 + /// + public static DateTimeRange LastWeek + { + get + { + var now = DateTime.Now; + DayOfWeek[] weeks = + [ + DayOfWeek.Sunday, + DayOfWeek.Monday, + DayOfWeek.Tuesday, + DayOfWeek.Wednesday, + DayOfWeek.Thursday, + DayOfWeek.Friday, + DayOfWeek.Saturday + ]; + var index = Array.IndexOf(weeks, now.DayOfWeek); + return new DateTimeRange(now.Date.AddDays(-index - 7), now.Date.AddDays(-index).AddMilliseconds(-1)); + } + } + + /// + /// 获取本周的时间范围 + /// + public static DateTimeRange ThisWeek + { + get + { + var now = DateTime.Now; + DayOfWeek[] weeks = + [ + DayOfWeek.Sunday, + DayOfWeek.Monday, + DayOfWeek.Tuesday, + DayOfWeek.Wednesday, + DayOfWeek.Thursday, + DayOfWeek.Friday, + DayOfWeek.Saturday + ]; + var index = Array.IndexOf(weeks, now.DayOfWeek); + return new DateTimeRange(now.Date.AddDays(-index), now.Date.AddDays(7 - index).AddMilliseconds(-1)); + } + } + + /// + /// 获取下周的时间范围 + /// + public static DateTimeRange NextWeek + { + get + { + var now = DateTime.Now; + DayOfWeek[] weeks = + [ + DayOfWeek.Sunday, + DayOfWeek.Monday, + DayOfWeek.Tuesday, + DayOfWeek.Wednesday, + DayOfWeek.Thursday, + DayOfWeek.Friday, + DayOfWeek.Saturday + ]; + var index = Array.IndexOf(weeks, now.DayOfWeek); + return new DateTimeRange(now.Date.AddDays(-index + 7), now.Date.AddDays(14 - index).AddMilliseconds(-1)); + } + } + + /// + /// 获取上个月的时间范围 + /// + public static DateTimeRange LastMonth + { + get + { + var now = DateTime.Now; + var startTime = now.Date.AddDays(-now.Day + 1).AddMonths(-1); + var endTime = startTime.AddMonths(1).AddMilliseconds(-1); + return new DateTimeRange(startTime, endTime); + } + } + + /// + /// 获取本月的时间范围 + /// + public static DateTimeRange ThisMonth + { + get + { + var now = DateTime.Now; + var startTime = now.Date.AddDays(-now.Day + 1); + var endTime = startTime.AddMonths(1).AddMilliseconds(-1); + return new DateTimeRange(startTime, endTime); + } + } + + /// + /// 获取下个月的时间范围 + /// + public static DateTimeRange NextMonth + { + get + { + var now = DateTime.Now; + var startTime = now.Date.AddDays(-now.Day + 1).AddMonths(1); + var endTime = startTime.AddMonths(1).AddMilliseconds(-1); + return new DateTimeRange(startTime, endTime); + } + } + + /// + /// 获取上一年的时间范围 + /// + public static DateTimeRange LastYear + { + get + { + var now = DateTime.Now; + return new DateTimeRange(new DateTime(now.Year - 1, 1, 1), new DateTime(now.Year, 1, 1).AddMilliseconds(-1)); + } + } + + /// + /// 获取本年的时间范围 + /// + public static DateTimeRange ThisYear + { + get + { + var now = DateTime.Now; + return new DateTimeRange(new DateTime(now.Year, 1, 1), new DateTime(now.Year + 1, 1, 1).AddMilliseconds(-1)); + } + } + + /// + /// 获取下一年的时间范围 + /// + public static DateTimeRange NextYear + { + get + { + var now = DateTime.Now; + return new DateTimeRange(new DateTime(now.Year + 1, 1, 1), new DateTime(now.Year + 2, 1, 1).AddMilliseconds(-1)); + } + } + + /// + /// 获取相对当前时间过去30天的时间范围 + /// + public static DateTimeRange Last30Days + { + get + { + var now = DateTime.Now; + return new DateTimeRange(now.AddDays(-30), now); + } + } + + /// + /// 获取截止到昨天的最近30天的天数范围 + /// + public static DateTimeRange Last30DaysExceptToday + { + get + { + var now = DateTime.Now; + return new DateTimeRange(now.Date.AddDays(-30), now.Date.AddMilliseconds(-1)); + } + } + + /// + /// 获取相对当前时间过去7天的时间范围 + /// + public static DateTimeRange Last7Days + { + get + { + var now = DateTime.Now; + return new DateTimeRange(now.AddDays(-7), now); + } + } + + /// + /// 获取截止到昨天的最近7天的天数范围 + /// + public static DateTimeRange Last7DaysExceptToday + { + get + { + var now = DateTime.Now; + return new DateTimeRange(now.Date.AddDays(-7), now.Date.AddMilliseconds(-1)); + } + } + + /// + /// 起始时间 + /// + public DateTime StartTime { get; set; } + + /// + /// 结束时间 + /// + public DateTime EndTime { get; set; } + + /// + /// ToString + /// + /// + public override string ToString() + { + return $"[{StartTime} - {EndTime}]"; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Utils/DateTime/LunarCalendarHelper.cs b/Admin.NET/Admin.NET.Core/Utils/DateTime/LunarCalendarHelper.cs new file mode 100644 index 00000000..bdf24907 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Utils/DateTime/LunarCalendarHelper.cs @@ -0,0 +1,866 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 农历辅助工具类 +/// +/// +/// 提供公历与农历互转、天干地支、生肖、节气、农历节日等功能, +/// 支持1900年至2100年的农历计算 +/// +public static class LunarCalendarHelper +{ + #region 常量定义 + + /// + /// 农历数据起始年份 + /// + private const int MinYear = 1900; + + /// + /// 农历数据结束年份 + /// + private const int MaxYear = 2100; + + /// + /// 农历基准日期 (1900年1月31日为农历1900年正月初一) + /// + private static readonly DateTime BaseDate = new(1900, 1, 31); + + /// + /// 天干数组 + /// + private static readonly string[] Tiangan = ["甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸"]; + + /// + /// 地支数组 + /// + private static readonly string[] Dizhi = ["子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥"]; + + /// + /// 生肖数组 + /// + private static readonly string[] Zodiac = ["鼠", "牛", "虎", "兔", "龙", "蛇", "马", "羊", "猴", "鸡", "狗", "猪"]; + + /// + /// 农历月份名称 + /// + private static readonly string[] LunarMonths = ["正月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "冬月", "腊月"]; + + /// + /// 农历日期名称 + /// + private static readonly string[] LunarDays = + [ + "初一", "初二", "初三", "初四", "初五", "初六", "初七", "初八", "初九", "初十", + "十一", "十二", "十三", "十四", "十五", "十六", "十七", "十八", "十九", "二十", + "廿一", "廿二", "廿三", "廿四", "廿五", "廿六", "廿七", "廿八", "廿九", "三十" + ]; + + /// + /// 二十四节气名称 + /// + private static readonly string[] SolarTerms = + [ + "立春", "雨水", "惊蛰", "春分", "清明", "谷雨", "立夏", "小满", "芒种", "夏至", "小暑", "大暑", + "立秋", "处暑", "白露", "秋分", "寒露", "霜降", "立冬", "小雪", "大雪", "冬至", "小寒", "大寒" + ]; + + #endregion 常量定义 + + #region 农历数据表 + + /// + /// 农历年份数据 (1900-2100年) + /// 每个数值的低12位表示12个月的大小月(1为大月30天,0为小月29天) + /// 第13位表示闰月的大小月 + /// 第14-17位表示闰月月份(0表示无闰月) + /// + private static readonly int[] LunarYearData = + [ + 0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2, + 0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977, + 0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970, + 0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950, + 0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557, + 0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5d0, 0x14573, 0x052d0, 0x0a9a8, 0x0e950, 0x06aa0, + 0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0, + 0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b5a0, 0x195a6, + 0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570, + 0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x055c0, 0x0ab60, 0x096d5, 0x092e0, + 0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5, + 0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930, + 0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530, + 0x05aa0, 0x076a3, 0x096d0, 0x04bd7, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45, + 0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0, + 0x14b63, 0x09370, 0x049f8, 0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06b20, 0x1a6c4, 0x0aae0, + 0x0a2e0, 0x0d2e3, 0x0c960, 0x0d557, 0x0d4a0, 0x0da50, 0x05d55, 0x056a0, 0x0a6d0, 0x055d4, + 0x052d0, 0x0a9b8, 0x0a950, 0x0b4a0, 0x0b6a6, 0x0ad50, 0x055a0, 0x0aba4, 0x0a5b0, 0x052b0, + 0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160, + 0x0e968, 0x0d520, 0x0daa0, 0x16aa6, 0x056d0, 0x04ae0, 0x0a9d4, 0x0a2d0, 0x0d150, 0x0f252, + 0x0d520 + ]; + + #endregion 农历数据表 + + #region 公开方法 - 公历转农历 + + /// + /// 将公历日期转换为农历日期 + /// + /// 公历日期 + /// 农历日期信息 + /// 日期超出支持范围时抛出 + public static LunarDate ConvertToLunar(DateTime date) + { + if (date.Year is < MinYear or > MaxYear) + { + throw new ArgumentOutOfRangeException(nameof(date), $"仅支持{MinYear}年至{MaxYear}年的日期转换"); + } + + var daysDiff = (date - BaseDate).Days; + var lunarYear = MinYear; + + // 计算农历年份 + while (lunarYear < MaxYear) + { + var daysInYear = GetLunarYearDays(lunarYear); + if (daysDiff < daysInYear) + { + break; + } + daysDiff -= daysInYear; + lunarYear++; + } + + // 计算农历月份和日期 + var lunarMonth = 1; + var isLeapMonth = false; + var leapMonth = GetLeapMonth(lunarYear); + + while (lunarMonth <= 12) + { + var daysInMonth = GetLunarMonthDays(lunarYear, lunarMonth); + + if (daysDiff < daysInMonth) + { + break; + } + + daysDiff -= daysInMonth; + + // 检查闰月 + if (lunarMonth == leapMonth && !isLeapMonth) + { + isLeapMonth = true; + daysInMonth = GetLeapMonthDays(lunarYear); + if (daysDiff < daysInMonth) + { + break; + } + daysDiff -= daysInMonth; + isLeapMonth = false; + } + + lunarMonth++; + } + + var lunarDay = daysDiff + 1; + + return new LunarDate + { + Year = lunarYear, + Month = lunarMonth, + Day = lunarDay, + IsLeapMonth = isLeapMonth, + YearName = GetLunarYearName(lunarYear), + MonthName = GetLunarMonthName(lunarMonth, isLeapMonth), + DayName = GetLunarDayName(lunarDay), + Zodiac = GetZodiac(lunarYear), + TianganDizhi = GetTianganDizhi(lunarYear), + SolarDate = date + }; + } + + /// + /// 将农历日期转换为公历日期 + /// + /// 农历年 + /// 农历月 + /// 农历日 + /// 是否闰月 + /// 公历日期 + public static DateTime ConvertToSolar(int lunarYear, int lunarMonth, int lunarDay, bool isLeapMonth = false) + { + if (lunarYear is < MinYear or > MaxYear) + { + throw new ArgumentOutOfRangeException(nameof(lunarYear), $"仅支持{MinYear}年至{MaxYear}年的农历年份"); + } + + var totalDays = 0; + + // 计算从基准年到目标年的总天数 + for (var year = MinYear; year < lunarYear; year++) + { + totalDays += GetLunarYearDays(year); + } + + // 计算目标年中到目标月的天数 + var leapMonth = GetLeapMonth(lunarYear); + for (var month = 1; month < lunarMonth; month++) + { + totalDays += GetLunarMonthDays(lunarYear, month); + if (month == leapMonth) + { + totalDays += GetLeapMonthDays(lunarYear); + } + } + + // 如果是闰月,还需要加上正常月的天数 + if (isLeapMonth && lunarMonth == leapMonth) + { + totalDays += GetLunarMonthDays(lunarYear, lunarMonth); + } + + // 加上目标日的天数 + totalDays += lunarDay - 1; + + return BaseDate.AddDays(totalDays); + } + + #endregion 公开方法 - 公历转农历 + + #region 公开方法 - 天干地支与生肖 + + /// + /// 获取指定年份的生肖 + /// + /// 年份(农历年) + /// 生肖名称 + public static string GetZodiac(int year) + { + var index = (year - 1900) % 12; + return Zodiac[index]; + } + + /// + /// 获取指定年份的天干地支 + /// + /// 年份(农历年) + /// 天干地支组合 + public static string GetTianganDizhi(int year) + { + var tianganIndex = (year - 1900) % 10; + var dizhiIndex = (year - 1900) % 12; + return Tiangan[tianganIndex] + Dizhi[dizhiIndex]; + } + + /// + /// 获取指定公历日期的天干地支 + /// + /// 公历日期 + /// 日期天干地支 + public static string GetDayTianganDizhi(DateTime date) + { + // 以1900年1月1日为甲子日计算 + var baseDay = new DateTime(1900, 1, 1); + var daysDiff = (date - baseDay).Days; + var tianganIndex = (daysDiff + 6) % 10; // 1900年1月1日为甲子日,甲为第0位 + var dizhiIndex = (daysDiff + 6) % 12; + return Tiangan[tianganIndex] + Dizhi[dizhiIndex]; + } + + #endregion 公开方法 - 天干地支与生肖 + + #region 公开方法 - 节气计算 + + /// + /// 获取指定年份的所有节气日期 + /// + /// 公历年份 + /// 节气日期列表 + public static List GetSolarTerms(int year) + { + var solarTerms = new List(); + + for (var i = 0; i < 24; i++) + { + var date = GetSolarTermDate(year, i); + solarTerms.Add(new SolarTerm + { + Name = SolarTerms[i], + Date = date, + Order = i + 1 + }); + } + + return solarTerms; + } + + /// + /// 获取指定日期所属的节气 + /// + /// 公历日期 + /// 节气信息,如果不是节气日则返回null + public static SolarTerm? GetSolarTerm(DateTime date) + { + var solarTerms = GetSolarTerms(date.Year); + return solarTerms.FirstOrDefault(st => st.Date.Date == date.Date); + } + + /// + /// 判断指定日期是否为节气 + /// + /// 公历日期 + /// 是否为节气日 + public static bool IsSolarTerm(DateTime date) + { + return GetSolarTerm(date) != null; + } + + #endregion 公开方法 - 节气计算 + + #region 公开方法 - 农历节日 + + /// + /// 获取指定农历日期的传统节日名称 + /// + /// 农历月 + /// 农历日 + /// 是否闰月 + /// 节日名称,如果不是节日则返回null + public static string? GetLunarFestival(int lunarMonth, int lunarDay, bool isLeapMonth = false) + { + if (isLeapMonth) + { + return null; // 闰月一般不过传统节日 + } + + return (lunarMonth, lunarDay) switch + { + (1, 1) => "春节", + (1, 15) => "元宵节", + (2, 2) => "龙抬头", + (5, 5) => "端午节", + (7, 7) => "七夕节", + (7, 15) => "中元节", + (8, 15) => "中秋节", + (9, 9) => "重阳节", + (10, 1) => "寒衣节", + (10, 15) => "下元节", + (12, 8) => "腊八节", + (12, 23) => "小年", + (12, 24) => "小年", + (12, 30) => "除夕", + (12, 29) => GetLunarMonthDays(DateTime.Now.Year, 12) == 29 ? "除夕" : null, + _ => null + }; + } + + /// + /// 获取指定公历日期的传统节日名称 + /// + /// 公历日期 + /// 节日名称,如果不是节日则返回null + public static string? GetSolarFestival(DateTime date) + { + return (date.Month, date.Day) switch + { + (1, 1) => "元旦", + (2, 14) => "情人节", + (3, 8) => "妇女节", + (3, 12) => "植树节", + (4, 1) => "愚人节", + (5, 1) => "劳动节", + (5, 4) => "青年节", + (6, 1) => "儿童节", + (7, 1) => "建党节", + (8, 1) => "建军节", + (9, 10) => "教师节", + (10, 1) => "国庆节", + (12, 25) => "圣诞节", + _ => null + }; + } + + #endregion 公开方法 - 农历节日 + + #region 公开方法 - 工具方法 + + /// + /// 获取农历年份的中文名称 + /// + /// 农历年份 + /// 中文年份名称 + public static string GetLunarYearName(int year) + { + var yearStr = year.ToString(); + var chineseNumbers = new[] { "零", "一", "二", "三", "四", "五", "六", "七", "八", "九" }; + var result = ""; + + foreach (var digit in yearStr) + { + result += chineseNumbers[digit - '0']; + } + + return result + "年"; + } + + /// + /// 获取农历月份的中文名称 + /// + /// 农历月份 + /// 是否闰月 + /// 中文月份名称 + public static string GetLunarMonthName(int month, bool isLeapMonth = false) + { + var monthName = LunarMonths[month - 1]; + return isLeapMonth ? "闰" + monthName : monthName; + } + + /// + /// 获取农历日期的中文名称 + /// + /// 农历日期 + /// 中文日期名称 + public static string GetLunarDayName(int day) + { + return day is >= 1 and <= 30 ? LunarDays[day - 1] : day.ToString(); + } + + /// + /// 判断指定农历年份是否有闰月 + /// + /// 农历年份 + /// 是否有闰月 + public static bool HasLeapMonth(int year) + { + return GetLeapMonth(year) > 0; + } + + /// + /// 获取农历年份的总天数 + /// + /// 农历年份 + /// 总天数 + public static int GetLunarYearDays(int year) + { + var days = 0; + for (var month = 1; month <= 12; month++) + { + days += GetLunarMonthDays(year, month); + } + + // 如果有闰月,加上闰月的天数 + if (HasLeapMonth(year)) + { + days += GetLeapMonthDays(year); + } + + return days; + } + + #endregion 公开方法 - 工具方法 + + #region 私有方法 + + /// + /// 获取农历年份的闰月月份 + /// + /// 农历年份 + /// 闰月月份,0表示无闰月 + private static int GetLeapMonth(int year) + { + return year is < MinYear or > MaxYear ? 0 : (LunarYearData[year - MinYear] & 0xf0000) >> 16; + } + + /// + /// 获取农历月份的天数 + /// + /// 农历年份 + /// 农历月份 + /// 月份天数 + private static int GetLunarMonthDays(int year, int month) + { + if (year is < MinYear or > MaxYear) + { + return 29; + } + + var monthData = LunarYearData[year - MinYear] & 0xfff; + return (monthData & (1 << (12 - month))) != 0 ? 30 : 29; + } + + /// + /// 获取农历年份闰月的天数 + /// + /// 农历年份 + /// 闰月天数 + private static int GetLeapMonthDays(int year) + { + return !HasLeapMonth(year) ? 0 : (LunarYearData[year - MinYear] & 0x10000) != 0 ? 30 : 29; + } + + /// + /// 计算指定年份第n个节气的日期(基于太阳黄经的真实算法) + /// + /// 公历年份 + /// 节气索引(0-23) + /// 节气日期 + private static DateTime GetSolarTermDate(int year, int termIndex) + { + // 每个节气对应的太阳黄经度数 + var solarLongitudes = new double[] + { + 315, 330, 345, 0, 15, 30, 45, 60, 75, 90, 105, 120, + 135, 150, 165, 180, 195, 210, 225, 240, 255, 270, 285, 300 + }; + + var targetLongitude = solarLongitudes[termIndex]; + + // 计算当年1月1日的儒略日数 + var jan1 = new DateTime(year, 1, 1); + var julianDay = GetJulianDay(jan1); + + // 估算节气可能的日期范围 + var estimatedDay = GetEstimatedSolarTermDay(year, termIndex); + var searchStart = julianDay + estimatedDay - 15; + var searchEnd = julianDay + estimatedDay + 15; + + // 二分法搜索精确的节气时刻 + var result = BinarySearchSolarTerm(searchStart, searchEnd, targetLongitude); + + return JulianDayToDateTime(result); + } + + /// + /// 计算儒略日数 + /// + /// 日期 + /// 儒略日数 + private static double GetJulianDay(DateTime date) + { + var year = date.Year; + var month = date.Month; + var day = date.Day + (date.Hour / 24.0) + (date.Minute / 1440.0) + (date.Second / 86400.0); + + if (month <= 2) + { + year -= 1; + month += 12; + } + + var a = year / 100; + var b = 2 - a + (a / 4); + + return Math.Floor(365.25 * (year + 4716)) + Math.Floor(30.6001 * (month + 1)) + day + b - 1524.5; + } + + /// + /// 将儒略日数转换为DateTime + /// + /// 儒略日数 + /// 日期时间 + private static DateTime JulianDayToDateTime(double julianDay) + { + var z = Math.Floor(julianDay + 0.5); + var f = julianDay + 0.5 - z; + + double a; + if (z < 2299161) + { + a = z; + } + else + { + var alpha = Math.Floor((z - 1867216.25) / 36524.25); + a = z + 1 + alpha - Math.Floor(alpha / 4); + } + + var b = a + 1524; + var c = Math.Floor((b - 122.1) / 365.25); + var d = Math.Floor(365.25 * c); + var e = Math.Floor((b - d) / 30.6001); + + var day = b - d - Math.Floor(30.6001 * e) + f; + var month = e < 14 ? e - 1 : e - 13; + var year = month > 2 ? c - 4716 : c - 4715; + + var wholeDays = Math.Floor(day); + var fractionalDay = day - wholeDays; + + var hours = fractionalDay * 24; + var wholeHours = Math.Floor(hours); + var fractionalHours = hours - wholeHours; + + var minutes = fractionalHours * 60; + var wholeMinutes = Math.Floor(minutes); + var fractionalMinutes = minutes - wholeMinutes; + + var seconds = Math.Floor(fractionalMinutes * 60); + + return new DateTime((int)year, (int)month, (int)wholeDays, (int)wholeHours, (int)wholeMinutes, (int)seconds); + } + + /// + /// 计算太阳黄经(简化版VSOP87算法) + /// + /// 儒略日数 + /// 太阳黄经(度) + private static double CalculateSolarLongitude(double julianDay) + { + // 儒略世纪数 + var t = (julianDay - 2451545.0) / 36525.0; + + // 太阳的平黄经 + var l0 = 280.46646 + (36000.76983 * t) + (0.0003032 * t * t); + + // 太阳的平近点角 + var m = 357.52911 + (35999.05029 * t) - (0.0001537 * t * t); + + // 转换为弧度 + var mRad = m * Math.PI / 180.0; + + // 黄经修正项(主要项) + var c = ((1.914602 - (0.004817 * t) - (0.000014 * t * t)) * Math.Sin(mRad)) + + ((0.019993 - (0.000101 * t)) * Math.Sin(2 * mRad)) + + (0.000289 * Math.Sin(3 * mRad)); + + // 真黄经 + var lambda = l0 + c; + + // 章动修正(简化) + var omega = 125.04452 - (1934.136261 * t); + var omegaRad = omega * Math.PI / 180.0; + var deltaPsi = -17.20 * Math.Sin(omegaRad) / 3600.0; + + lambda += deltaPsi; + + // 确保角度在0-360度范围内 + lambda %= 360.0; + if (lambda < 0) + { + lambda += 360.0; + } + + return lambda; + } + + /// + /// 获取节气的估算日期(从年初开始的天数) + /// + /// 年份 + /// 节气索引 + /// 估算天数 + private static int GetEstimatedSolarTermDay(int year, int termIndex) + { + // 基于统计平均值的估算表(从1月1日开始的天数) + var estimatedDays = new[] + { + 4, 19, 35, 51, 66, 81, 96, 112, 128, 144, 160, 176, + 192, 208, 224, 240, 256, 272, 288, 304, 320, 336, 352, 3 + }; + + var baseDay = estimatedDays[termIndex]; + + // 对于小寒,如果是下一年的,需要调整 + if (termIndex == 23 && baseDay < 10) + { + baseDay += 365; + if (IsLeapYear(year)) + { + baseDay += 1; + } + } + + return baseDay; + } + + /// + /// 二分法搜索节气精确时刻 + /// + /// 搜索开始的儒略日 + /// 搜索结束的儒略日 + /// 目标黄经 + /// 精确的儒略日数 + private static double BinarySearchSolarTerm(double startJd, double endJd, double targetLongitude) + { + const double Precision = 1.0 / 86400.0; // 1秒的精度 + const int MaxIterations = 50; + + var iterations = 0; + while (endJd - startJd > Precision && iterations < MaxIterations) + { + var midJd = (startJd + endJd) / 2.0; + var longitude = CalculateSolarLongitude(midJd); + + // 处理角度跨越0度的情况 + var diff = GetAngleDifference(longitude, targetLongitude); + + if (Math.Abs(diff) < 0.01) // 0.01度的精度 + { + return midJd; + } + + // 判断太阳是否还未到达目标黄经 + if (diff > 0) + { + endJd = midJd; + } + else + { + startJd = midJd; + } + + iterations++; + } + + return (startJd + endJd) / 2.0; + } + + /// + /// 计算两个角度之间的差值(考虑360度循环) + /// + /// 角度1 + /// 角度2 + /// 角度差 + private static double GetAngleDifference(double angle1, double angle2) + { + var diff = angle1 - angle2; + while (diff > 180) + { + diff -= 360; + } + + while (diff < -180) + { + diff += 360; + } + + return diff; + } + + /// + /// 判断是否为闰年 + /// + /// 年份 + /// 是否为闰年 + private static bool IsLeapYear(int year) + { + return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); + } + + #endregion 私有方法 +} + +/// +/// 农历日期信息 +/// +public class LunarDate +{ + /// + /// 农历年份 + /// + public int Year { get; set; } + + /// + /// 农历月份 + /// + public int Month { get; set; } + + /// + /// 农历日期 + /// + public int Day { get; set; } + + /// + /// 是否闰月 + /// + public bool IsLeapMonth { get; set; } + + /// + /// 农历年份中文名称 + /// + public string YearName { get; set; } = string.Empty; + + /// + /// 农历月份中文名称 + /// + public string MonthName { get; set; } = string.Empty; + + /// + /// 农历日期中文名称 + /// + public string DayName { get; set; } = string.Empty; + + /// + /// 生肖 + /// + public string Zodiac { get; set; } = string.Empty; + + /// + /// 天干地支 + /// + public string TianganDizhi { get; set; } = string.Empty; + + /// + /// 对应的公历日期 + /// + public DateTime SolarDate { get; set; } + + /// + /// 农历节日名称 + /// + public string? Festival => LunarCalendarHelper.GetLunarFestival(Month, Day, IsLeapMonth); + + /// + /// 农历日期的完整中文表示 + /// + public string FullName => $"{YearName}{MonthName}{DayName}"; + + /// + /// 转换为字符串表示 + /// + /// 格式化的农历日期 + public override string ToString() + { + var festival = Festival; + var festivalText = !string.IsNullOrEmpty(festival) ? $" ({festival})" : ""; + return $"{FullName} {Zodiac}年 {TianganDizhi}{festivalText}"; + } +} + +/// +/// 节气信息 +/// +public class SolarTerm +{ + /// + /// 节气名称 + /// + public string Name { get; set; } = string.Empty; + + /// + /// 节气日期 + /// + public DateTime Date { get; set; } + + /// + /// 节气序号(1-24) + /// + public int Order { get; set; } + + /// + /// 转换为字符串表示 + /// + /// 格式化的节气信息 + public override string ToString() + { + return $"{Name} ({Date:yyyy年MM月dd日})"; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Utils/DateTimeUtil.cs b/Admin.NET/Admin.NET.Core/Utils/DateTimeUtil.cs deleted file mode 100644 index cb403087..00000000 --- a/Admin.NET/Admin.NET.Core/Utils/DateTimeUtil.cs +++ /dev/null @@ -1,413 +0,0 @@ -// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 -// -// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 -// -// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! - -namespace Admin.NET.Core; - -public class DateTimeUtil -{ - public readonly DateTime Date; - - private DateTimeUtil(TimeSpan timeSpan = default) - { - Date = DateTime.Now.AddTicks(timeSpan.Ticks); - } - - private DateTimeUtil(DateTime time) - { - Date = time; - } - - /// - /// 实例化类 - /// - /// - /// - public static DateTimeUtil Init(TimeSpan timeSpan = default) - { - return new DateTimeUtil(timeSpan); - } - - /// - /// 实例化类 - /// - /// - /// - public static DateTimeUtil Init(DateTime time) - { - return new DateTimeUtil(time); - } - - /// - /// 根据unix时间戳的长度自动判断是秒还是以毫秒为单位 - /// - /// - /// - public static DateTime ConvertUnixTime(long unixTime) - { - // 判断时间戳长度 - bool isMilliseconds = unixTime > 9999999999; - - if (isMilliseconds) - { - return DateTimeOffset.FromUnixTimeMilliseconds(unixTime).ToLocalTime().DateTime; - } - else - { - return DateTimeOffset.FromUnixTimeSeconds(unixTime).ToLocalTime().DateTime; - } - } - - /// - /// 获取开始时间 - /// - /// - /// - /// - public static DateTime GetBeginTime(DateTime? dateTime, int days = 0) - { - return dateTime == DateTime.MinValue || dateTime == null ? DateTime.Now.AddDays(days) : (DateTime)dateTime; - } - - /// - /// 时间戳转本地时间-时间戳精确到秒 - /// - public static DateTime ToLocalTimeDateBySeconds(long unix) - { - return DateTimeOffset.FromUnixTimeSeconds(unix).ToLocalTime().DateTime; - } - - /// - /// 时间转时间戳Unix-时间戳精确到秒 - /// - public static long ToUnixTimestampBySeconds(DateTime dt) - { - return new DateTimeOffset(dt).ToUnixTimeSeconds(); - } - - /// - /// 时间戳转本地时间-时间戳精确到毫秒 - /// - public static DateTime ToLocalTimeDateByMilliseconds(long unix) - { - return DateTimeOffset.FromUnixTimeMilliseconds(unix).ToLocalTime().DateTime; - } - - /// - /// 时间转时间戳Unix-时间戳精确到毫秒 - /// - public static long ToUnixTimestampByMilliseconds(DateTime dt) - { - return new DateTimeOffset(dt).ToUnixTimeMilliseconds(); - } - - /// - /// 毫秒转天时分秒 - /// - /// TotalMilliseconds - /// 是否简化显示 - /// - public static string FormatTime(long ms, bool isSimplify = false) - { - 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; - - if (!isSimplify) - return $"{sDay} 天 {sHour} 小时 {sMinute} 分 {sSecond} 秒"; - else - { - string result = string.Empty; - if (day > 0) - result = $"{sDay}天"; - if (hour > 0) - result = $"{result}{sHour}小时"; - if (minute > 0) - result = $"{result}{sMinute}分"; - if (!result.IsNullOrEmpty()) - result = $"{result}{sSecond}秒"; - else - result = $"{sSecond}秒"; - return result; - } - } - - /// - /// 获取unix时间戳 - /// - /// - /// - public static long GetUnixTimeStamp(DateTime dt) - { - return ((DateTimeOffset)dt).ToUnixTimeMilliseconds(); - } - - /// - /// 获取日期天的最小时间 - /// - /// - /// - public static DateTime GetDayMinDate(DateTime dt) - { - return new DateTime(dt.Year, dt.Month, dt.Day, 0, 0, 0); - } - - /// - /// 获取日期天的最大时间 - /// - /// - /// - - public static DateTime GetDayMaxDate(DateTime dt) - { - return new DateTime(dt.Year, dt.Month, dt.Day, 23, 59, 59); - } - - /// - /// 判断日期是否在当前年份并格式化日期 - /// - /// - /// - public static string FormatDateTime(DateTime? dt) - { - return dt == null ? string.Empty : dt.Value.ToString(dt.Value.Year == DateTime.Now.Year ? "MM-dd HH:mm" : "yyyy-MM-dd HH:mm"); - } - - /// - /// 获取日期范围00:00:00 - 23:59:59 - /// - /// - public static List GetTodayTimeList(DateTime time) - { - return new List - { - Convert.ToDateTime(time.ToString("D")), - Convert.ToDateTime(time.AddDays(1).ToString("D")).AddSeconds(-1) - }; - } - - /// - /// 获取星期几 - /// - /// - /// - public static string GetWeekByDate(DateTime dt) - { - var day = new[] { "星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六" }; - return day[Convert.ToInt32(dt.DayOfWeek.ToString("d"))]; - } - - /// - /// 获取这个月的第几周 - /// - /// - /// - public static int GetWeekNumInMonth(DateTime daytime) - { - int dayInMonth = daytime.Day; - // 本月第一天 - DateTime firstDay = daytime.AddDays(1 - daytime.Day); - // 本月第一天是周几 - int weekday = (int)firstDay.DayOfWeek == 0 ? 7 : (int)firstDay.DayOfWeek; - // 本月第一周有几天 - int firstWeekEndDay = 7 - (weekday - 1); - // 当前日期和第一周之差 - int diffday = dayInMonth - firstWeekEndDay; - diffday = diffday > 0 ? diffday : 1; - // 当前是第几周,若整除7就减一天 - return ((diffday % 7) == 0 ? (diffday / 7 - 1) : (diffday / 7)) + 1 + (dayInMonth > firstWeekEndDay ? 1 : 0); - } - - /// - /// 获取今天的时间范围 - /// - /// 返回包含开始时间和结束时间的元组 - public (DateTime Start, DateTime End) GetTodayRange() - { - var start = Date.Date; // 当天开始时间 - var end = start.AddDays(1).AddSeconds(-1); // 当天结束时间 - return (start, end); - } - - /// - /// 获取本月的时间范围 - /// - /// 返回包含开始时间和结束时间的元组 - public (DateTime Start, DateTime End) GetMonthRange() - { - return (GetFirstDayOfMonth(), GetLastDayOfMonth()); - } - - /// - /// 获取本月的第一天开始时间 - /// - /// 返回当月的第一天 - public DateTime GetFirstDayOfMonth() - { - return new DateTime(Date.Year, Date.Month, 1); - } - - /// - /// 获取本月的最后一天截至时间 - /// - /// 返回当月的最后一天 - public DateTime GetLastDayOfMonth() - { - var firstDayOfNextMonth = new DateTime(Date.Year, Date.Month, 1).AddMonths(1); - return firstDayOfNextMonth.AddSeconds(-1); - } - - /// - /// 获取今年的时间范围 - /// - public (DateTime Start, DateTime End) GetYearRange() - { - return (GetFirstDayOfYear(), GetLastDayOfYear()); - } - - /// - /// 获取今年的第一天时间范围 - /// - public DateTime GetFirstDayOfYear() - { - return new DateTime(Date.Year, 1, 1); - } - - /// - /// 获取今年的最后一天时间范围 - /// - public DateTime GetLastDayOfYear() - { - return new DateTime(Date.Year, 12, 31, 23, 59, 59); - } - - /// - /// 获取前天时间范围 - /// - public (DateTime Start, DateTime End) GetDayBeforeYesterdayRange() - { - var start = Date.Date.AddDays(-2); // 前天开始时间 - var end = start.AddDays(1).AddSeconds(-1); // 前天结束时间 - return (start, end); - } - - /// - /// 获取昨天时间范围 - /// - public (DateTime Start, DateTime End) GetYesterdayRange() - { - var start = Date.Date.AddDays(-1); // 昨天开始时间 - var end = start.AddDays(1).AddSeconds(-1); // 昨天结束时间 - return (start, end); - } - - /// - /// 获取上一周时间范围 - /// - public (DateTime Start, DateTime End) GetLastWeekRange() - { - // 计算上周的天数差 - var daysToSubtract = (int)Date.DayOfWeek + 7; // 确保周日也能正确计算 - var start = Date.Date.AddDays(-daysToSubtract); // 上周第一天 - var end = start.AddDays(7).AddSeconds(-1); // 上周最后一天 - return (start, end); - } - - /// - /// 获取本周时间范围 - /// - public (DateTime Start, DateTime End) GetThisWeekRange() - { - // 计算本周的天数差 - var daysToSubtract = (int)Date.DayOfWeek; - var start = Date.Date.AddDays(-daysToSubtract); // 本周第一天 - var end = start.AddDays(7).AddSeconds(-1); // 本周最后一天 - return (start, end); - } - - /// - /// 获取上月时间范围 - /// - public (DateTime Start, DateTime End) GetLastMonthRange() - { - var firstDayOfLastMonth = new DateTime(Date.Year, Date.Month, 1).AddMonths(-1); // 上月第一天 - var lastDayOfLastMonth = firstDayOfLastMonth.AddMonths(1).AddSeconds(-1); // 上月最后一天 - return (firstDayOfLastMonth, lastDayOfLastMonth); - } - - /// - /// 获取近3天的时间范围 - /// - public (DateTime Start, DateTime End) GetLast3DaysRange() - { - var start = Date.Date.AddDays(-2); // 3天前的开始时间 - var end = Date.Date.AddDays(1).AddSeconds(-1); // 当前日期的结束时间 - return (start, end); - } - - /// - /// 获取近7天的时间范围 - /// - public (DateTime Start, DateTime End) GetLast7DaysRange() - { - var start = Date.Date.AddDays(-6); // 7天前的开始时间 - var end = Date.Date.AddDays(1).AddSeconds(-1); // 当前日期的结束时间 - return (start, end); - } - - /// - /// 获取近15天的时间范围 - /// - public (DateTime Start, DateTime End) GetLast15DaysRange() - { - var start = Date.Date.AddDays(-14); // 15天前的开始时间 - var end = Date.Date.AddDays(1).AddSeconds(-1); // 当前日期的结束时间 - return (start, end); - } - - /// - /// 获取近3个月的时间范围 - /// - public (DateTime Start, DateTime End) GetLast3MonthsRange() - { - var start = Date.Date.AddMonths(-3); // 3个月前的开始时间 - var end = Date.Date.AddDays(1).AddSeconds(-1); // 当前日期的结束时间 - return (start, end); - } - - /// - /// 获取上半年的时间范围 - /// - public (DateTime Start, DateTime End) GetFirstHalfYearRange() - { - var start = new DateTime(Date.Year, 1, 1); // 上半年开始时间 - var end = new DateTime(Date.Year, 6, 30, 23, 59, 59); // 上半年结束时间 - return (start, end); - } - - /// - /// 获取下半年的时间范围 - /// - public (DateTime Start, DateTime End) GetSecondHalfYearRange() - { - var start = new DateTime(Date.Year, 7, 1); // 下半年开始时间 - var end = new DateTime(Date.Year, 12, 31, 23, 59, 59); // 下半年结束时间 - return (start, end); - } -} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Utils/ExcelHelper.cs b/Admin.NET/Admin.NET.Core/Utils/Excel/ExcelHelper.cs similarity index 98% rename from Admin.NET/Admin.NET.Core/Utils/ExcelHelper.cs rename to Admin.NET/Admin.NET.Core/Utils/Excel/ExcelHelper.cs index c93e1e69..aead4e3f 100644 --- a/Admin.NET/Admin.NET.Core/Utils/ExcelHelper.cs +++ b/Admin.NET/Admin.NET.Core/Utils/Excel/ExcelHelper.cs @@ -20,7 +20,7 @@ public class ExcelHelper { try { - var result = CommonUtil.ImportExcelDataAsync(file).Result ?? throw Oops.Oh("有效数据为空"); + var result = CommonHelper.ImportExcelDataAsync(file).Result ?? throw Oops.Oh("有效数据为空"); result.ForEach(u => u.Id = YitIdHelper.NextId()); var tasks = new List(); diff --git a/Admin.NET/Admin.NET.Core/Utils/MiniExcelUtil.cs b/Admin.NET/Admin.NET.Core/Utils/Excel/MiniExcelHelper.cs similarity index 95% rename from Admin.NET/Admin.NET.Core/Utils/MiniExcelUtil.cs rename to Admin.NET/Admin.NET.Core/Utils/Excel/MiniExcelHelper.cs index d6a9cd6a..2574db9d 100644 --- a/Admin.NET/Admin.NET.Core/Utils/MiniExcelUtil.cs +++ b/Admin.NET/Admin.NET.Core/Utils/Excel/MiniExcelHelper.cs @@ -8,7 +8,7 @@ using MiniExcelLibs; namespace Admin.NET.Core; -public static class MiniExcelUtil +public static class MiniExcelHelper { private const string SheetName = "ImportTemplate"; private const string DirectoryName = "export"; @@ -39,7 +39,7 @@ public static class MiniExcelUtil /// public static async Task> GetImportExcelData([Required] IFormFile file) where T : class, new() { - using MemoryStream stream = new MemoryStream(); + using MemoryStream stream = new(); await file.CopyToAsync(stream); var res = await stream.QueryAsync(sheetName: SheetName); return res.ToArray(); @@ -64,7 +64,7 @@ public static class MiniExcelUtil { throw Oops.Oh("出现错误:" + error); } - var host = CommonUtil.GetLocalhost(); + var host = CommonHelper.GetLocalhost(); return $"{host}/{DirectoryName}/{fileName}"; } } \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Utils/XlsxFileResult.cs b/Admin.NET/Admin.NET.Core/Utils/Excel/XlsxFileResult.cs similarity index 100% rename from Admin.NET/Admin.NET.Core/Utils/XlsxFileResult.cs rename to Admin.NET/Admin.NET.Core/Utils/Excel/XlsxFileResult.cs diff --git a/Admin.NET/Admin.NET.Core/Utils/CryptogramUtil.cs b/Admin.NET/Admin.NET.Core/Utils/GM/CryptogramHelper.cs similarity index 90% rename from Admin.NET/Admin.NET.Core/Utils/CryptogramUtil.cs rename to Admin.NET/Admin.NET.Core/Utils/GM/CryptogramHelper.cs index a9f23c10..98f6d652 100644 --- a/Admin.NET/Admin.NET.Core/Utils/CryptogramUtil.cs +++ b/Admin.NET/Admin.NET.Core/Utils/GM/CryptogramHelper.cs @@ -9,7 +9,7 @@ using Org.BouncyCastle.Utilities.Encoders; namespace Admin.NET.Core; -public class CryptogramUtil +public class CryptogramHelper { public static readonly string CryptoType = App.GetConfig("Cryptogram:CryptoType"); // 加密类型 public static readonly string PublicKey = App.GetConfig("Cryptogram:PublicKey"); // 公钥 @@ -82,7 +82,7 @@ public class CryptogramUtil /// public static string SM2Encrypt(string plainText) { - return GMUtil.SM2Encrypt(PublicKey, plainText); + return GMHelper.SM2Encrypt(PublicKey, plainText); } /// @@ -94,7 +94,7 @@ public class CryptogramUtil { try { - return GMUtil.SM2Decrypt(PrivateKey, cipherText); + return GMHelper.SM2Decrypt(PrivateKey, cipherText); } catch { @@ -109,7 +109,7 @@ public class CryptogramUtil /// public static string SM4EncryptECB(string plainText) { - return GMUtil.SM4EncryptECB(SM4_key, plainText); + return GMHelper.SM4EncryptECB(SM4_key, plainText); } /// @@ -119,7 +119,7 @@ public class CryptogramUtil /// public static string SM4DecryptECB(string cipherText) { - return GMUtil.SM4DecryptECB(SM4_key, cipherText); + return GMHelper.SM4DecryptECB(SM4_key, cipherText); } /// @@ -129,7 +129,7 @@ public class CryptogramUtil /// public static string SM4EncryptCBC(string plainText) { - return GMUtil.SM4EncryptCBC(SM4_key, SM4_iv, plainText); + return GMHelper.SM4EncryptCBC(SM4_key, SM4_iv, plainText); } /// @@ -139,6 +139,6 @@ public class CryptogramUtil /// public static string SM4DecryptCBC(string cipherText) { - return GMUtil.SM4DecryptCBC(SM4_key, SM4_iv, cipherText); + return GMHelper.SM4DecryptCBC(SM4_key, SM4_iv, cipherText); } } \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Utils/GM/GMUtil.cs b/Admin.NET/Admin.NET.Core/Utils/GM/GMHelper.cs similarity index 99% rename from Admin.NET/Admin.NET.Core/Utils/GM/GMUtil.cs rename to Admin.NET/Admin.NET.Core/Utils/GM/GMHelper.cs index d92853ac..7f5c2d28 100644 --- a/Admin.NET/Admin.NET.Core/Utils/GM/GMUtil.cs +++ b/Admin.NET/Admin.NET.Core/Utils/GM/GMHelper.cs @@ -13,7 +13,7 @@ namespace Admin.NET.Core; /// /// GM工具类 /// -public class GMUtil +public class GMHelper { /// /// SM2加密 @@ -129,7 +129,7 @@ public class GMUtil { return null; } - byte[] ret = (byte[])null; + byte[] ret; if (mode == 1) { int p = 16 - input.Length % 16; diff --git a/Admin.NET/Admin.NET.Core/Utils/HardwareInfo/BoardHelper.cs b/Admin.NET/Admin.NET.Core/Utils/HardwareInfo/BoardHelper.cs new file mode 100644 index 00000000..78f50cc4 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Utils/HardwareInfo/BoardHelper.cs @@ -0,0 +1,141 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 主板帮助类 +/// +public static class BoardHelper +{ + /// + /// 主板信息 + /// + /// + /// 推荐使用,默认有缓存 + /// + public static BoardInfo BoardInfos => Cache.Default.GetOrAdd("BoardInfos", _ => GetBoardInfos(), 120 * 60); + + /// + /// 获取主板信息 + /// + /// + public static BoardInfo GetBoardInfos() + { + BoardInfo boardInfo = new(); + + try + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + // 首先尝试使用 dmidecode 获取主板信息 + var output = ShellHelper.Bash("dmidecode -t baseboard").Trim(); + var lines = output.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()).ToArray(); + + if (lines.Length != 0) + { + boardInfo.Product = GetParmValueSafe(lines, "Product Name", ':'); + boardInfo.Manufacturer = GetParmValueSafe(lines, "Manufacturer", ':'); + boardInfo.SerialNumber = GetParmValueSafe(lines, "Serial Number", ':'); + boardInfo.Version = GetParmValueSafe(lines, "Version", ':'); + } + + // 如果主板信息为空或无效(常见于虚拟化环境),尝试获取系统信息 + if (string.IsNullOrWhiteSpace(boardInfo.Product) || + string.IsNullOrWhiteSpace(boardInfo.Manufacturer) || + boardInfo.Product.Equals("Not Specified", StringComparison.OrdinalIgnoreCase) || + boardInfo.Manufacturer.Equals("Not Specified", StringComparison.OrdinalIgnoreCase)) + { + var systemOutput = ShellHelper.Bash("dmidecode -t system").Trim(); + var systemLines = systemOutput.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()).ToArray(); + + if (systemLines.Length != 0) + { + boardInfo.Product = GetParmValueSafe(systemLines, "Product Name", ':'); + boardInfo.Manufacturer = GetParmValueSafe(systemLines, "Manufacturer", ':'); + boardInfo.SerialNumber = GetParmValueSafe(systemLines, "Serial Number", ':'); + boardInfo.Version = GetParmValueSafe(systemLines, "Version", ':'); + } + } + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + var output = ShellHelper.Bash("system_profiler SPHardwareDataType").Trim(); + var lines = output.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()).ToArray(); + if (lines.Length != 0) + { + boardInfo.Product = GetParmValueSafe(lines, "Model Identifier", ':'); + boardInfo.Manufacturer = GetParmValueSafe(lines, "Chip", ':'); + boardInfo.SerialNumber = GetParmValueSafe(lines, "Serial Number (system)", ':'); + boardInfo.Version = GetParmValueSafe(lines, "Hardware UUID", ':'); + } + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + var output = ShellHelper.Cmd("wmic", "baseboard get Product,Manufacturer,SerialNumber,Version /Value").Trim(); + var lines = output.Split(Environment.NewLine); + if (lines.Length != 0) + { + boardInfo.Product = GetParmValueSafe(lines, "Product", '='); + boardInfo.Manufacturer = GetParmValueSafe(lines, "Manufacturer", '='); + boardInfo.SerialNumber = GetParmValueSafe(lines, "SerialNumber", '='); + boardInfo.Version = GetParmValueSafe(lines, "Version", '='); + } + } + } + catch (Exception ex) + { + Log.Error("获取主板信息出错," + ex.Message); + } + + return boardInfo; + + // 安全的参数值获取方法,避免异常 + string GetParmValueSafe(string[] lines, string parm, char separator) + { + try + { + var line = lines.FirstOrDefault(s => s.StartsWith(parm)); + if (line != null && line.Contains(separator)) + { + var parts = line.Split(separator); + return parts.Length > 1 ? parts[1].Trim() : string.Empty; + } + return string.Empty; + } + catch + { + return string.Empty; + } + } + } +} + +/// +/// 主板信息 +/// +public record BoardInfo +{ + /// + /// 型号 + /// + public string Product { get; set; } = string.Empty; + + /// + /// 制造商 + /// + public string Manufacturer { get; set; } = string.Empty; + + /// + /// 序列号 + /// + public string SerialNumber { get; set; } = string.Empty; + + /// + /// 版本号 + /// + public string Version { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Utils/HardwareInfo/CpuHelper.cs b/Admin.NET/Admin.NET.Core/Utils/HardwareInfo/CpuHelper.cs new file mode 100644 index 00000000..d55eacd7 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Utils/HardwareInfo/CpuHelper.cs @@ -0,0 +1,269 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 处理器帮助类 +/// +public static class CpuHelper +{ + /// + /// 处理器信息 + /// + /// + /// 推荐使用,默认有缓存 + /// + public static CpuInfo CpuInfos => Cache.Default.GetOrAdd("CpuInfos", _ => GetCpuInfos(), 5 * 60); + + /// + /// 获取处理器信息 + /// + /// + public static CpuInfo GetCpuInfos() + { + var cpuInfo = new CpuInfo + { + LogicalCoreCount = Environment.ProcessorCount, + ProcessorArchitecture = RuntimeInformation.ProcessArchitecture.ToString() + }; + + try + { + // 获取CPU使用率 + GetCpuUsage(cpuInfo); + + // 获取CPU详细信息 + GetCpuDetails(cpuInfo); + + // 获取温度信息(如果可用) + GetCpuTemperature(cpuInfo); + } + catch (Exception ex) + { + Log.Error("获取处理器信息出错," + ex.Message); + } + + return cpuInfo; + } + + /// + /// 获取CPU使用率 + /// + /// + private static void GetCpuUsage(CpuInfo cpuInfo) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + var output = ShellHelper.Bash(@"top -b -n1 | grep ""Cpu(s)""").Trim(); + var lines = output.Split(','); + if (lines.Length > 3) + { + var loadPercentage = lines[3].Trim().Split(' ')[0].Replace("%", ""); + if (double.TryParse(loadPercentage, out var usage)) + { + cpuInfo.UsagePercentage = Math.Round(100 - usage, 2); // idle转换为使用率 + } + } + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + var output = ShellHelper.Bash(@"top -l 1 -F | awk '/CPU usage/ {gsub(""%"", """"); print $7}'").Trim(); + if (double.TryParse(output, out var usage)) + { + cpuInfo.UsagePercentage = Math.Round(100 - usage, 2); // idle转换为使用率 + } + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + // 使用WMI获取CPU使用率 + try + { + var output = ShellHelper.Cmd("wmic", "cpu get LoadPercentage /Value").Trim(); + var lines = output.Split(Environment.NewLine); + var loadLine = lines.FirstOrDefault(s => s.StartsWith("LoadPercentage=")); + if (loadLine != null) + { + var loadPercentage = loadLine.Split('=')[1].Trim(); + if (double.TryParse(loadPercentage, out var usage)) + { + cpuInfo.UsagePercentage = Math.Round(usage, 2); + } + } + } + catch + { + // 如果WMI失败,使用默认值 + cpuInfo.UsagePercentage = 0; + } + } + } + + /// + /// 获取CPU温度信息(如果可用) + /// + /// + private static void GetCpuTemperature(CpuInfo cpuInfo) + { + try + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + // 尝试从thermal_zone获取温度 + var thermalFiles = Directory.GetFiles("/sys/class/thermal", "thermal_zone*"); + foreach (var file in thermalFiles) + { + var tempFile = Path.Combine(file, "temp"); + if (File.Exists(tempFile)) + { + var tempStr = File.ReadAllText(tempFile).Trim(); + if (int.TryParse(tempStr, out var temp)) + { + cpuInfo.Temperature = Math.Round(temp / 1000.0, 1); + break; + } + } + } + } + // Windows和macOS的温度获取需要特殊权限或第三方工具,这里暂不实现 + } + catch + { + // 温度获取失败不影响其他信息 + } + } + + /// + /// 获取CPU详细信息 + /// + /// + private static void GetCpuDetails(CpuInfo cpuInfo) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + var cpuInfoOutput = ShellHelper.Bash("cat /proc/cpuinfo").Trim(); + var lines = cpuInfoOutput.Split('\n'); + + foreach (var line in lines) + { + if (line.StartsWith("model name")) + { + cpuInfo.ProcessorName = line.Split(':')[1].Trim(); + } + else if (line.StartsWith("cpu MHz")) + { + if (double.TryParse(line.Split(':')[1].Trim(), out var mhz)) + { + cpuInfo.BaseClockSpeed = Math.Round(mhz / 1000, 2); + } + } + else if (line.StartsWith("cache size")) + { + cpuInfo.CacheBytes = line.Split(':')[1].Trim().ParseToLong(); + } + else if (line.StartsWith("cpu cores")) + { + if (int.TryParse(line.Split(':')[1].Trim(), out var cores)) + { + cpuInfo.PhysicalCoreCount = cores; + } + } + } + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + var nameOutput = ShellHelper.Bash("sysctl -n machdep.cpu.brand_string").Trim(); + cpuInfo.ProcessorName = nameOutput; + + var coreOutput = ShellHelper.Bash("sysctl -n hw.physicalcpu").Trim(); + if (int.TryParse(coreOutput, out var cores)) + { + cpuInfo.PhysicalCoreCount = cores; + } + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + var output = ShellHelper.Cmd("wmic", "cpu get Name,NumberOfCores,MaxClockSpeed,L3CacheSize /Value").Trim(); + var lines = output.Split(Environment.NewLine); + + foreach (var line in lines) + { + if (line.StartsWith("Name=")) + { + cpuInfo.ProcessorName = line.Split('=')[1].Trim(); + } + else if (line.StartsWith("NumberOfCores=")) + { + if (int.TryParse(line.Split('=')[1], out var cores)) + { + cpuInfo.PhysicalCoreCount = cores; + } + } + else if (line.StartsWith("MaxClockSpeed=")) + { + if (double.TryParse(line.Split('=')[1], out var mhz)) + { + cpuInfo.BaseClockSpeed = Math.Round(mhz / 1000, 2); + } + } + else if (line.StartsWith("L3CacheSize=")) + { + var cache = line.Split('=')[1].Trim(); + if (!string.IsNullOrEmpty(cache) && cache != "0") + { + cpuInfo.CacheBytes = cache.ParseToLong() * 1024; + } + } + } + } + } +} + +/// +/// 处理器信息 +/// +public record CpuInfo +{ + /// + /// 处理器名称 + /// + public string ProcessorName { get; set; } = string.Empty; + + /// + /// 处理器架构 + /// + public string ProcessorArchitecture { get; set; } = string.Empty; + + /// + /// 物理核心数 + /// + public int PhysicalCoreCount { get; set; } + + /// + /// 逻辑核心数(超线程) + /// + public int LogicalCoreCount { get; set; } + + /// + /// 基础时钟频率(GHz) + /// + public double BaseClockSpeed { get; set; } + + /// + /// 缓存大小 + /// + public long CacheBytes { get; set; } + + /// + /// CPU使用率(%) + /// + public double UsagePercentage { get; set; } + + /// + /// CPU温度(°C) + /// + public double? Temperature { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Utils/HardwareInfo/DiskHelper.cs b/Admin.NET/Admin.NET.Core/Utils/HardwareInfo/DiskHelper.cs new file mode 100644 index 00000000..b94ffdb7 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Utils/HardwareInfo/DiskHelper.cs @@ -0,0 +1,137 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 磁盘帮助类 +/// +public static class DiskHelper +{ + /// + /// 磁盘信息 + /// + /// + /// 推荐使用,默认有缓存 + /// + public static List DiskInfos => Cache.Default.GetOrAdd("DiskInfos", _ => GetDiskInfos(), 60 * 60); + + /// + /// 获取磁盘信息 + /// + /// + public static List GetDiskInfos() + { + List diskInfos = []; + + try + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + var output = ShellHelper.Bash(@"df -mT | awk '/^\/dev\/(sd|vd|xvd|nvme|sda|vda|mapper)/ {print $1,$2,$3,$4,$5,$6}'").Trim(); + var lines = output.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries).ToList(); + if (lines.Count != 0) + { + diskInfos.AddRange(from line in lines + select line.Split(' ', (char)StringSplitOptions.RemoveEmptyEntries) + into rootDisk + where rootDisk.Length >= 6 + select new DiskInfo + { + DiskName = rootDisk[0].Trim(), + TypeName = rootDisk[1].Trim(), + TotalSpace = rootDisk[2].ParseToLong() * 1024 * 1024, // MB转换为字节 + UsedSpace = rootDisk[3].ParseToLong() * 1024 * 1024, + FreeSpace = rootDisk[4].ParseToLong() * 1024 * 1024, + AvailableRate = rootDisk[2].ParseToLong() == 0 + ? 0 + : Math.Round((double)rootDisk[4].ParseToLong() / rootDisk[2].ParseToLong() * 100, 3) + }); + } + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + var output = ShellHelper.Bash(@"df -k | awk '/^\/dev\/disk/ {print $1,$2,$3,$4,$6}' | tail -n +2").Trim(); + var lines = output.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries).ToList(); + if (lines.Count != 0) + { + diskInfos.AddRange(from line in lines + select line.Split(' ', (char)StringSplitOptions.RemoveEmptyEntries) + into rootDisk + where rootDisk.Length >= 5 + select new DiskInfo + { + TypeName = rootDisk[0].Trim(), + TotalSpace = rootDisk[1].ParseToLong() * 1024, + UsedSpace = rootDisk[2].ParseToLong() * 1024, + DiskName = rootDisk[4].Trim(), + FreeSpace = (rootDisk[1].ParseToLong() - rootDisk[2].ParseToLong()) * 1024, + AvailableRate = rootDisk[1].ParseToLong() == 0 + ? 0 + : Math.Round((double)rootDisk[3].ParseToLong() / rootDisk[1].ParseToLong() * 100, 3) + }); + } + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + var drives = DriveInfo.GetDrives().Where(d => d.IsReady).ToList(); + diskInfos.AddRange(drives.Select(item => new DiskInfo + { + DiskName = item.Name, + TypeName = item.DriveType.ToString(), + TotalSpace = item.TotalSize, + FreeSpace = item.TotalFreeSpace, + UsedSpace = item.TotalSize - item.TotalFreeSpace, + AvailableRate = item.TotalSize == 0 + ? 0 + : Math.Round((double)item.TotalFreeSpace / item.TotalSize * 100, 3) + })); + } + } + catch (Exception ex) + { + Log.Error("获取磁盘信息出错," + ex.Message); + } + + return diskInfos; + } +} + +/// +/// 磁盘信息 +/// +public record DiskInfo +{ + /// + /// 磁盘名称 + /// + public string DiskName { get; set; } = string.Empty; + + /// + /// 磁盘类型 + /// + public string TypeName { get; set; } = string.Empty; + + /// + /// 总大小 + /// + public long TotalSpace { get; set; } + + /// + /// 空闲大小 + /// + public long FreeSpace { get; set; } + + /// + /// 已用大小 + /// + public long UsedSpace { get; set; } + + /// + /// 可用占比 + /// + public double AvailableRate { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Utils/HardwareInfo/GpuHelper.cs b/Admin.NET/Admin.NET.Core/Utils/HardwareInfo/GpuHelper.cs new file mode 100644 index 00000000..a99eca32 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Utils/HardwareInfo/GpuHelper.cs @@ -0,0 +1,347 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// GPU帮助类 +/// +public static class GpuHelper +{ + /// + /// GPU信息 + /// + /// + /// 推荐使用,默认有缓存 + /// + public static List GpuInfos => Cache.Default.GetOrAdd("GpuInfos", _ => GetGpuInfos(), 60 * 60); + + /// + /// 获取GPU信息 + /// + /// + public static List GetGpuInfos() + { + List gpuInfos = []; + + try + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + GetLinuxGpuInfo(gpuInfos); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + GetMacOsGpuInfo(gpuInfos); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + GetWindowsGpuInfo(gpuInfos); + } + } + catch (Exception ex) + { + Log.Error("获取GPU信息出错," + ex.Message); + gpuInfos.Add(new GpuInfo + { + Name = "Error", + Description = "Failed to retrieve GPU information" + }); + } + + return gpuInfos; + } + + /// + /// 获取Windows GPU信息 + /// + /// + private static void GetWindowsGpuInfo(List gpuInfos) + { + var output = ShellHelper.Cmd("wmic", "path win32_VideoController get Name,DriverVersion,AdapterRAM,VideoModeDescription,Status /Value").Trim(); + var lines = output.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries); + + GpuInfo? currentGpu = null; + + foreach (var line in lines) + { + if (string.IsNullOrWhiteSpace(line)) + { + continue; + } + + if (line.Contains('=')) + { + var parts = line.Split('=', 2); + if (parts.Length != 2) + { + continue; + } + + var key = parts[0].Trim(); + var value = parts[1].Trim(); + + switch (key) + { + case "Name" when !string.IsNullOrEmpty(value): + if (currentGpu != null) + { + gpuInfos.Add(currentGpu); + } + currentGpu = new GpuInfo + { + Name = value + }; + break; + + case "DriverVersion" when currentGpu != null: + currentGpu.DriverVersion = value; + break; + + case "AdapterRAM" when currentGpu != null && long.TryParse(value, out var ram): + currentGpu.MemoryBytes = ram; + break; + + case "VideoModeDescription" when currentGpu != null: + currentGpu.VideoModeDescription = value; + break; + + case "Status" when currentGpu != null: + currentGpu.Status = value; + break; + } + } + } + + if (currentGpu != null) + { + gpuInfos.Add(currentGpu); + } + } + + /// + /// 获取Linux GPU信息 + /// + /// + /// + private static void GetLinuxGpuInfo(List gpuInfos) + { + try + { + // 尝试使用lspci获取GPU信息 + var output = ShellHelper.Bash("lspci | grep -i vga").Trim(); + var lines = output.Split('\n', StringSplitOptions.RemoveEmptyEntries); + + foreach (var line in lines) + { + var gpuInfo = new GpuInfo(); + + // 解析lspci输出 + var parts = line.Split(':', StringSplitOptions.RemoveEmptyEntries); + if (parts.Length >= 2) + { + gpuInfo.Name = parts[1].Trim(); + gpuInfo.BusInfo = parts[0].Trim(); + } + + // 尝试获取更多详细信息 + try + { + var detailOutput = ShellHelper.Bash($"lspci -v -s {gpuInfo.BusInfo}").Trim(); + var detailLines = detailOutput.Split('\n'); + + foreach (var detailLine in detailLines) + { + if (detailLine.Contains("Kernel driver in use:")) + { + gpuInfo.DriverVersion = detailLine.Split(':')[1].Trim(); + } + } + } + catch + { + // 获取详细信息失败,继续处理其他GPU + } + + gpuInfos.Add(gpuInfo); + } + + // 如果支持nvidia-smi,获取NVIDIA GPU的更多信息 + try + { + var nvidiaOutput = ShellHelper.Bash("nvidia-smi --query-gpu=name,driver_version,memory.total,temperature.gpu --format=csv,noheader,nounits").Trim(); + var nvidiaLines = nvidiaOutput.Split('\n', StringSplitOptions.RemoveEmptyEntries); + + for (var i = 0; i < nvidiaLines.Length && i < gpuInfos.Count; i++) + { + var parts = nvidiaLines[i].Split(','); + if (parts.Length >= 4) + { + var gpuInfo = gpuInfos[i]; + gpuInfo.Name = parts[0].Trim(); + gpuInfo.DriverVersion = parts[1].Trim(); + + if (long.TryParse(parts[2].Trim(), out var memory)) + { + gpuInfo.MemoryBytes = memory * 1024 * 1024; // 转换为字节 + } + + if (double.TryParse(parts[3].Trim(), out var temp)) + { + gpuInfo.Temperature = temp; + } + } + } + } + catch + { + // nvidia-smi不可用或执行失败 + } + } + catch (Exception ex) + { + throw new Exception($"获取Linux GPU信息失败: {ex.Message}"); + } + } + + /// + /// 获取macOS GPU信息 + /// + /// + /// + private static void GetMacOsGpuInfo(List gpuInfos) + { + try + { + var output = ShellHelper.Bash("system_profiler SPDisplaysDataType").Trim(); + var lines = output.Split('\n'); + + GpuInfo? currentGpu = null; + + foreach (var line in lines) + { + var trimmedLine = line.Trim(); + + if (trimmedLine.EndsWith(':') && !trimmedLine.Contains(' ')) + { + // 这可能是GPU名称 + if (currentGpu != null) + { + gpuInfos.Add(currentGpu); + } + + currentGpu = new GpuInfo + { + Name = trimmedLine.TrimEnd(':') + }; + } + else if (currentGpu != null && trimmedLine.Contains(':')) + { + var parts = trimmedLine.Split(':', 2); + if (parts.Length == 2) + { + var key = parts[0].Trim(); + var value = parts[1].Trim(); + + switch (key.ToLower()) + { + case "vram (total)" when value.Contains("MB"): + var mbValue = value.Replace("MB", "").Trim(); + if (long.TryParse(mbValue, out var memory)) + { + currentGpu.MemoryBytes = memory * 1024 * 1024; + } + break; + + case "vendor": + currentGpu.Vendor = value; + break; + + case "device id": + currentGpu.DeviceId = value; + break; + } + } + } + } + + if (currentGpu != null) + { + gpuInfos.Add(currentGpu); + } + } + catch (Exception ex) + { + throw new Exception($"获取macOS GPU信息失败: {ex.Message}"); + } + } +} + +/// +/// GPU信息 +/// +public record GpuInfo +{ + /// + /// GPU名称 + /// + public string Name { get; set; } = string.Empty; + + /// + /// 描述 + /// + public string Description { get; set; } = string.Empty; + + /// + /// 厂商 + /// + public string Vendor { get; set; } = string.Empty; + + /// + /// 设备ID + /// + public string DeviceId { get; set; } = string.Empty; + + /// + /// 总线信息 + /// + public string BusInfo { get; set; } = string.Empty; + + /// + /// 驱动版本 + /// + public string DriverVersion { get; set; } = string.Empty; + + /// + /// 显存大小(字节) + /// + public long MemoryBytes { get; set; } + + /// + /// GPU温度(°C) + /// + public double? Temperature { get; set; } + + /// + /// 视频模式描述 + /// + public string VideoModeDescription { get; set; } = string.Empty; + + /// + /// 状态 + /// + public string Status { get; set; } = string.Empty; + + /// + /// GPU使用率(%) + /// + public double? UtilizationPercentage { get; set; } + + /// + /// 显存使用率(%) + /// + public double? MemoryUtilizationPercentage { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Utils/HardwareInfo/HardwareInfoManager.cs b/Admin.NET/Admin.NET.Core/Utils/HardwareInfo/HardwareInfoManager.cs new file mode 100644 index 00000000..4ed6309d --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Utils/HardwareInfo/HardwareInfoManager.cs @@ -0,0 +1,227 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 硬件信息管理器 +/// +public static class HardwareInfoManager +{ + /// + /// 获取完整的系统硬件信息 + /// + /// + /// 推荐使用,默认有缓存 + /// + /// 系统硬件信息 + public static SystemHardwareInfo GetSystemHardwareInfo() + { + return new SystemHardwareInfo + { + CpuInfo = CpuHelper.CpuInfos, + RamInfo = RamHelper.RamInfos, + DiskInfos = DiskHelper.DiskInfos, + NetworkInfos = NetworkHelper.NetworkInfos, + GpuInfos = GpuHelper.GpuInfos, + BoardInfo = BoardHelper.BoardInfos + }; + } + + /// + /// 获取系统硬件摘要信息 + /// + /// 系统硬件摘要 + public static SystemHardwareSummary GetSystemHardwareSummary() + { + try + { + var cpuInfo = CpuHelper.CpuInfos; + var ramInfo = RamHelper.RamInfos; + var diskInfos = DiskHelper.DiskInfos; + var networkInfos = NetworkHelper.NetworkInfos; + var gpuInfos = GpuHelper.GpuInfos; + + return new SystemHardwareSummary + { + CpuName = cpuInfo.ProcessorName, + CpuCores = $"{cpuInfo.PhysicalCoreCount}C/{cpuInfo.LogicalCoreCount}T", + CpuUsage = $"{cpuInfo.UsagePercentage:F1}%", + TotalMemory = ramInfo.TotalBytes.FormatFileSizeToString(), + MemoryUsage = $"{ramInfo.UsagePercentage:F1}%", + TotalDiskSpace = diskInfos.Sum(d => d.TotalSpace).FormatFileSizeToString(), + NetworkInterfaceCount = networkInfos.Count, + GpuCount = gpuInfos.Count, + }; + } + catch (Exception ex) + { + Log.Error($"获取系统硬件摘要失败: {ex.Message}"); + return new SystemHardwareSummary(); + } + } + + /// + /// 获取硬件信息诊断报告 + /// + /// 诊断报告 + public static HardwareDiagnosticReport GetDiagnosticReport() + { + var report = new HardwareDiagnosticReport(); + + try + { + var cpuInfo = CpuHelper.CpuInfos; + var ramInfo = RamHelper.RamInfos; + var diskInfos = DiskHelper.DiskInfos; + + // CPU诊断 + if (cpuInfo.UsagePercentage > 90) + { + report.Issues.Add("CPU使用率过高 (>90%)"); + } + if (cpuInfo.Temperature.HasValue && cpuInfo.Temperature > 80) + { + report.Issues.Add($"CPU温度过高 ({cpuInfo.Temperature:F1}°C)"); + } + + // 内存诊断 + if (ramInfo.UsagePercentage > 90) + { + report.Issues.Add("内存使用率过高 (>90%)"); + } + if (ramInfo.AvailablePercentage < 5) + { + report.Issues.Add("可用内存不足 (<5%)"); + } + + // 磁盘诊断 + foreach (var disk in diskInfos) + { + if (disk.AvailableRate < 10) + { + report.Issues.Add($"磁盘 {disk.DiskName} 可用空间不足 (<10%)"); + } + } + + report.Status = report.Issues.Count == 0 ? "正常" : "发现问题"; + } + catch (Exception) + { + report.Status = "诊断失败"; + } + + return report; + } +} + +/// +/// 系统硬件信息 +/// +public record SystemHardwareInfo +{ + /// + /// CPU信息 + /// + public CpuInfo CpuInfo { get; set; } = new(); + + /// + /// 内存信息 + /// + public RamInfo RamInfo { get; set; } = new(); + + /// + /// 磁盘信息列表 + /// + public List DiskInfos { get; set; } = []; + + /// + /// 网络接口信息列表 + /// + public List NetworkInfos { get; set; } = []; + + /// + /// GPU信息列表 + /// + public List GpuInfos { get; set; } = []; + + /// + /// 主板信息 + /// + public BoardInfo BoardInfo { get; set; } = new(); +} + +/// +/// 系统硬件摘要信息 +/// +public record SystemHardwareSummary +{ + /// + /// CPU名称 + /// + public string CpuName { get; set; } = string.Empty; + + /// + /// CPU核心数 + /// + public string CpuCores { get; set; } = string.Empty; + + /// + /// CPU使用率 + /// + public string CpuUsage { get; set; } = string.Empty; + + /// + /// 总内存 + /// + public string TotalMemory { get; set; } = string.Empty; + + /// + /// 内存使用率 + /// + public string MemoryUsage { get; set; } = string.Empty; + + /// + /// 总磁盘空间 + /// + public string TotalDiskSpace { get; set; } = string.Empty; + + /// + /// 网络接口数量 + /// + public int NetworkInterfaceCount { get; set; } + + /// + /// GPU数量 + /// + public int GpuCount { get; set; } + + /// + /// 主要GPU + /// + public string PrimaryGpu { get; set; } = string.Empty; +} + +/// +/// 硬件诊断报告 +/// +public record HardwareDiagnosticReport +{ + /// + /// 诊断状态 + /// + public string Status { get; set; } = string.Empty; + + /// + /// 发现的问题列表 + /// + public List Issues { get; set; } = []; + + /// + /// 建议列表 + /// + public List Recommendations { get; set; } = []; +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Utils/HardwareInfo/NetworkHelper.cs b/Admin.NET/Admin.NET.Core/Utils/HardwareInfo/NetworkHelper.cs new file mode 100644 index 00000000..a72131d3 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Utils/HardwareInfo/NetworkHelper.cs @@ -0,0 +1,303 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using System.Net.NetworkInformation; +using System.Net.Sockets; + +namespace Admin.NET.Core; + +/// +/// 网卡信息帮助类 +/// +public static class NetworkHelper +{ + /// + /// 网卡信息 + /// + /// + /// 推荐使用,默认有缓存 + /// + public static List NetworkInfos => Cache.Default.GetOrAdd("NetworkInfos", _ => GetNetworkInfos(), 5 * 60); + + /// + /// 获取网卡信息 + /// + /// + public static List GetNetworkInfos() + { + List networkInfos = []; + + try + { + // 获取所有网络接口 + var interfaces = NetworkInterface.GetAllNetworkInterfaces().ToList(); + + foreach (var ni in interfaces) + { + var networkInfo = new NetworkInfo + { + Name = ni.Name, + Description = ni.Description, + Type = ni.NetworkInterfaceType.ToString(), + OperationalStatus = ni.OperationalStatus.ToString(), + Speed = ni.Speed > 0 ? ni.Speed.ToString("#,##0") + " bps" : "Unknown", + PhysicalAddress = BitConverter.ToString(ni.GetPhysicalAddress().GetAddressBytes()), + SupportsMulticast = ni.SupportsMulticast, + IsReceiveOnly = ni.IsReceiveOnly + }; + + try + { + var properties = ni.GetIPProperties(); + networkInfo.DnsAddresses = [.. properties.DnsAddresses.Select(ip => ip.ToString())]; + networkInfo.GatewayAddresses = [.. properties.GatewayAddresses.Select(gw => gw.Address.ToString())]; + + // DHCP服务器地址在macOS上不受支持 + if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + networkInfo.DhcpServerAddresses = [.. properties.DhcpServerAddresses.Select(ip => ip.ToString())]; + } + + // IPv4 地址信息 + var unicastAddresses = properties.UnicastAddresses.ToList(); + networkInfo.IPv4Addresses = [.. unicastAddresses + .Where(addr => addr.Address.AddressFamily == AddressFamily.InterNetwork) + .Select(addr => new IpAddressInfo + { + Address = addr.Address.ToString(), + SubnetMask = addr.IPv4Mask?.ToString() ?? "", + PrefixLength = addr.PrefixLength + })]; + + networkInfo.IPv6Addresses = [.. unicastAddresses + .Where(addr => addr.Address.AddressFamily == AddressFamily.InterNetworkV6) + .Select(addr => new IpAddressInfo + { + Address = addr.Address.ToString(), + PrefixLength = addr.PrefixLength + })]; + + // 获取网络统计信息 + if (ni.OperationalStatus == OperationalStatus.Up) + { + var stats = ni.GetIPv4Statistics(); + networkInfo.Statistics = new NetworkInterfaceStatistics + { + BytesReceived = stats.BytesReceived, + BytesSent = stats.BytesSent, + PacketsReceived = stats.UnicastPacketsReceived + stats.NonUnicastPacketsReceived, + PacketsSent = stats.UnicastPacketsSent + stats.NonUnicastPacketsSent, + IncomingPacketsDiscarded = stats.IncomingPacketsDiscarded, + IncomingPacketsWithErrors = stats.IncomingPacketsWithErrors, + OutgoingPacketsWithErrors = stats.OutgoingPacketsWithErrors + }; + + // OutgoingPacketsDiscarded在macOS上不受支持 + if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + networkInfo.Statistics.OutgoingPacketsDiscarded = stats.OutgoingPacketsDiscarded; + } + } + } + catch (Exception ex) + { + // 如果获取IP属性失败,记录错误信息 + Log.Error($"获取网卡 {ni.Name} 的IP属性出错,{ex.Message}"); + } + + if (networkInfo.DhcpServerAddresses.Count == 0 && networkInfo.DnsAddresses.Count == 0 && + networkInfo.GatewayAddresses.Count == 0 && networkInfo.IPv4Addresses.Count == 0 && + networkInfo.IPv6Addresses.Count == 0) + { + continue; + } + + networkInfos.Add(networkInfo); + } + } + catch (Exception ex) + { + // 如果完全失败,返回包含错误信息的空列表 + Log.Error("获取网卡信息出错," + ex.Message); + networkInfos.Add(new NetworkInfo + { + Name = "Error", + Description = "Failed to retrieve network information" + }); + } + + return networkInfos; + } +} + +/// +/// 网卡信息 +/// +public record NetworkInfo +{ + /// + /// 名称 + /// + public string Name { get; set; } = string.Empty; + + /// + /// 描述 + /// + public string Description { get; set; } = string.Empty; + + /// + /// 类型 + /// + public string Type { get; set; } = string.Empty; + + /// + /// 操作状态 + /// + public string OperationalStatus { get; set; } = string.Empty; + + /// + /// 速度 + /// + public string Speed { get; set; } = string.Empty; + + /// + /// 物理地址(mac 地址) + /// + public string PhysicalAddress { get; set; } = string.Empty; + + /// + /// 是否支持多播 + /// + public bool SupportsMulticast { get; set; } + + /// + /// 是否只接收 + /// + public bool IsReceiveOnly { get; set; } + + /// + /// DNS 地址 + /// + public List DnsAddresses { get; set; } = []; + + /// + /// 网关地址 + /// + public List GatewayAddresses { get; set; } = []; + + /// + /// DHCP服务器地址 + /// + public List DhcpServerAddresses { get; set; } = []; + + /// + /// IPv4 地址详细信息 + /// + public List IPv4Addresses { get; set; } = []; + + /// + /// IPv6 地址详细信息 + /// + public List IPv6Addresses { get; set; } = []; + + /// + /// 网络接口统计信息 + /// + public NetworkInterfaceStatistics? Statistics { get; set; } +} + +/// +/// IP地址信息 +/// +public record IpAddressInfo +{ + /// + /// IP地址 + /// + public string Address { get; set; } = string.Empty; + + /// + /// 子网掩码 + /// + public string SubnetMask { get; set; } = string.Empty; + + /// + /// 前缀长度 + /// + public int PrefixLength { get; set; } +} + +/// +/// 网络接口统计信息 +/// +public record NetworkInterfaceStatistics +{ + /// + /// 接收字节数 + /// + public long BytesReceived { get; set; } + + /// + /// 发送字节数 + /// + public long BytesSent { get; set; } + + /// + /// 接收数据包数 + /// + public long PacketsReceived { get; set; } + + /// + /// 发送数据包数 + /// + public long PacketsSent { get; set; } + + /// + /// 丢弃的传入数据包数 + /// + public long IncomingPacketsDiscarded { get; set; } + + /// + /// 丢弃的传出数据包数 + /// + public long OutgoingPacketsDiscarded { get; set; } + + /// + /// 传入错误数据包数 + /// + public long IncomingPacketsWithErrors { get; set; } + + /// + /// 传出错误数据包数 + /// + public long OutgoingPacketsWithErrors { get; set; } +} + +/// +/// 网络统计信息 +/// +public record NetworkStatistics +{ + /// + /// 总接收字节数 + /// + public long TotalBytesReceived { get; set; } + + /// + /// 总发送字节数 + /// + public long TotalBytesSent { get; set; } + + /// + /// 总接收数据包数 + /// + public long TotalPacketsReceived { get; set; } + + /// + /// 总发送数据包数 + /// + public long TotalPacketsSent { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Utils/HardwareInfo/RamHelper.cs b/Admin.NET/Admin.NET.Core/Utils/HardwareInfo/RamHelper.cs new file mode 100644 index 00000000..f58fdd36 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Utils/HardwareInfo/RamHelper.cs @@ -0,0 +1,211 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 内存帮助类 +/// +public static class RamHelper +{ + /// + /// 内存信息 + /// + /// + /// 推荐使用,默认有缓存 + /// + public static RamInfo RamInfos => Cache.Default.GetOrAdd("RamInfos", _ => GetRamInfos(), 5 * 60); + + /// + /// 获取内存信息 + /// + /// + public static RamInfo GetRamInfos() + { + var ramInfo = new RamInfo(); + + try + { + // 单位是 Byte + var totalMemoryParts = 0L; + var usedMemoryParts = 0L; + var freeMemoryParts = 0L; + var availableMemoryParts = 0L; + var buffersCached = 0L; + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + // 使用更详细的内存信息获取 + var output = ShellHelper.Bash("cat /proc/meminfo").Trim(); + var lines = output.Split('\n'); + + foreach (var line in lines) + { + var parts = line.Split(':', StringSplitOptions.RemoveEmptyEntries); + if (parts.Length >= 2) + { + var key = parts[0].Trim(); + var valueStr = parts[1].Trim().Replace(" kB", ""); + if (long.TryParse(valueStr, out var value)) + { + var bytes = value * 1024; + switch (key) + { + case "MemTotal": + totalMemoryParts = bytes; + break; + + case "MemFree": + freeMemoryParts = bytes; + break; + + case "MemAvailable": + availableMemoryParts = bytes; + break; + + case "Buffers": + buffersCached += bytes; + break; + + case "Cached": + buffersCached += bytes; + break; + } + } + } + } + + usedMemoryParts = totalMemoryParts - freeMemoryParts - buffersCached; + if (availableMemoryParts == 0) + { + availableMemoryParts = freeMemoryParts; + } + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + // 获取总内存 + var totalOutput = ShellHelper.Bash("sysctl -n hw.memsize").Trim(); + if (long.TryParse(totalOutput, out totalMemoryParts)) + { + // 获取内存压力信息 + var vmStatOutput = ShellHelper.Bash("vm_stat").Trim(); + var lines = vmStatOutput.Split('\n'); + + long pageSize = 4096; // 默认页面大小 + var pageSizeOutput = ShellHelper.Bash("sysctl -n hw.pagesize").Trim(); + if (long.TryParse(pageSizeOutput, out var ps)) + { + pageSize = ps; + } + + long freePages = 0, wiredPages = 0, activePages = 0, inactivePages = 0; + + foreach (var line in lines) + { + if (line.Contains("Pages free:")) + { + var match = RegexHelper.OneOrMoreNumbersRegex().Match(line); + if (match.Success && long.TryParse(match.Value, out freePages)) { } + } + else if (line.Contains("Pages wired down:")) + { + var match = RegexHelper.OneOrMoreNumbersRegex().Match(line); + if (match.Success && long.TryParse(match.Value, out wiredPages)) { } + } + else if (line.Contains("Pages active:")) + { + var match = RegexHelper.OneOrMoreNumbersRegex().Match(line); + if (match.Success && long.TryParse(match.Value, out activePages)) { } + } + else if (line.Contains("Pages inactive:")) + { + var match = RegexHelper.OneOrMoreNumbersRegex().Match(line); + if (match.Success && long.TryParse(match.Value, out inactivePages)) { } + } + } + + freeMemoryParts = freePages * pageSize; + usedMemoryParts = (wiredPages + activePages) * pageSize; + availableMemoryParts = (freePages + inactivePages) * pageSize; + } + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + var output = ShellHelper.Cmd("wmic", "OS get FreePhysicalMemory,TotalVisibleMemorySize /Value").Trim(); + var lines = output.Split(Environment.NewLine); + if (lines.Length != 0) + { + totalMemoryParts = lines.First(s => s.StartsWith("TotalVisibleMemorySize")).Split('=')[1].ParseToLong() * 1024; + freeMemoryParts = lines.First(s => s.StartsWith("FreePhysicalMemory")).Split('=')[1].ParseToLong() * 1024; + usedMemoryParts = totalMemoryParts - freeMemoryParts; + availableMemoryParts = freeMemoryParts; + } + } + + // 设置内存信息 + ramInfo.TotalBytes = totalMemoryParts; + ramInfo.UsedBytes = usedMemoryParts; + ramInfo.FreeBytes = freeMemoryParts; + ramInfo.AvailableBytes = availableMemoryParts; + ramInfo.BuffersCachedBytes = buffersCached; + + ramInfo.UsagePercentage = totalMemoryParts > 0 + ? Math.Round((double)usedMemoryParts / totalMemoryParts * 100, 2) + : 0; + + ramInfo.AvailablePercentage = totalMemoryParts > 0 + ? Math.Round((double)availableMemoryParts / totalMemoryParts * 100, 2) + : 0; + } + catch (Exception ex) + { + Log.Error("获取内存信息出错," + ex.Message); + } + + return ramInfo; + } +} + +/// +/// 内存信息 +/// +public record RamInfo +{ + /// + /// 总内存大小(字节) + /// + public long TotalBytes { get; set; } + + /// + /// 已用内存大小(字节) + /// + public long UsedBytes { get; set; } + + /// + /// 空闲内存大小(字节) + /// + public long FreeBytes { get; set; } + + /// + /// 可用内存大小(字节) + /// + public long AvailableBytes { get; set; } + + /// + /// 缓冲区和缓存大小(字节) + /// + public long BuffersCachedBytes { get; set; } + + /// + /// 内存使用率(%) + /// + public double UsagePercentage { get; set; } + + /// + /// 可用内存占比(%) + /// + public double AvailablePercentage { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Utils/HashHelper.cs b/Admin.NET/Admin.NET.Core/Utils/HashHelper.cs new file mode 100644 index 00000000..90ff4f9e --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Utils/HashHelper.cs @@ -0,0 +1,132 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using System.Security.Cryptography; + +namespace Admin.NET.Core; + +/// +/// 哈希生成辅助类 +/// +/// +/// 是一系列加密哈希函数,主要用于生成数据的固定长度散列值,以确保数据完整性和安全性。 +/// +public static class HashHelper +{ + /// + /// 生成 SHA1 哈希值 + /// + /// 待加密的数据 + /// 生成的哈希值 + public static string Sha1(string data) + { + // 创建 SHA256 加密算法实例,将字符串数据转换为字节数组,并生成相应的哈希值 + var hashBytes = SHA1.HashData(Encoding.UTF8.GetBytes(data)); + return Convert.ToHexString(hashBytes); + } + + /// + /// 生成 SHA256 哈希值 + /// + /// 待加密的数据 + /// 生成的哈希值 + public static string Sha256(string data) + { + // 创建 SHA256 加密算法实例,将字符串数据转换为字节数组,并生成相应的哈希值 + var hashBytes = SHA256.HashData(Encoding.UTF8.GetBytes(data)); + return Convert.ToHexString(hashBytes); + } + + /// + /// 生成 SHA384 哈希值 + /// + /// 待加密的数据 + /// 生成的哈希值 + public static string Sha384(string data) + { + // 创建 SHA384 加密算法实例,将字符串数据转换为字节数组,并生成相应的哈希值 + var hashBytes = SHA384.HashData(Encoding.UTF8.GetBytes(data)); + return Convert.ToHexString(hashBytes); + } + + /// + /// 生成 SHA512 哈希值 + /// + /// 待加密的数据 + /// 生成的哈希值 + public static string Sha512(string data) + { + // 创建 SHA512 加密算法实例,将字符串数据转换为字节数组,并生成相应的哈希值 + var hashBytes = SHA512.HashData(Encoding.UTF8.GetBytes(data)); + return Convert.ToHexString(hashBytes); + } + + /// + /// 对字符串进行 MD5 生成哈希 + /// + /// 待加密的明文字符串 + /// 生成的哈希值 + public static string Md5(string input) + { + var hashBytes = MD5.HashData(Encoding.UTF8.GetBytes(input)); + return Convert.ToHexString(hashBytes); + } + + /// + /// 对数据流进行 MD5 生成哈希 + /// + /// 待加密的数据流路径 + /// 生成的哈希值 + public static string StreamMd5(string inputPath) + { + using FileStream stream = new(inputPath, FileMode.Open, FileAccess.Read, FileShare.Read); + return StreamMd5(stream); + } + + /// + /// 对数据流进行 MD5 生成哈希 + /// + /// 待加密的数据流 + /// 生成的哈希值 + public static string StreamMd5(Stream stream) + { + var hashBytes = MD5.HashData(stream); + return Convert.ToHexString(hashBytes); + } + + /// + /// 对数据流进行 SHA256 生成哈希 + /// + /// + /// + public static string StreamHash(Stream data) + { + var hashBytes = SHA256.HashData(data); + return Convert.ToHexString(hashBytes); + } + + /// + /// 对二进制数据进行 MD5 生成哈希 + /// + /// + /// + public static string ByteMd5(byte[] data) + { + var hashBytes = MD5.HashData(data); + return Convert.ToHexString(hashBytes); + } + + /// + /// 对二进制数据进行 SHA256 生成哈希 + /// + /// + /// + public static string ByteHash(byte[] data) + { + var hashBytes = SHA256.HashData(data); + return Convert.ToHexString(hashBytes); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Utils/IO/CompressHelper.cs b/Admin.NET/Admin.NET.Core/Utils/IO/CompressHelper.cs new file mode 100644 index 00000000..b0f87cb7 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Utils/IO/CompressHelper.cs @@ -0,0 +1,170 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using System.IO.Compression; + +namespace Admin.NET.Core; + +/// +/// 压缩帮助类 +/// +public static class CompressHelper +{ + /// + /// 解压文件 + /// + /// 压缩文件路径 + /// 解压目标路径 + /// 压缩格式 + /// 文件不存在时抛出 + /// 目录不存在时抛出 + public static void Extract(string archivePath, string extractPath, CompressionFormat format = CompressionFormat.Zip) + { + if (!File.Exists(archivePath)) + { + throw new FileNotFoundException("没有找到文件。", archivePath); + } + + if (!Directory.Exists(extractPath)) + { + _ = Directory.CreateDirectory(extractPath); + } + + switch (format) + { + case CompressionFormat.Zip: + ZipFile.ExtractToDirectory(archivePath, extractPath); + break; + + case CompressionFormat.GZip: + ExtractGZip(archivePath, extractPath); + break; + + case CompressionFormat.Deflate: + ExtractDeflate(archivePath, extractPath); + break; + + default: + throw new ArgumentException("不支持的压缩格式。", nameof(format)); + } + } + + /// + /// 压缩文件或目录 + /// + /// 源文件或目录路径 + /// 压缩文件保存路径 + /// 压缩格式 + /// 压缩级别 + /// 文件不存在时抛出 + /// 目录不存在时抛出 + public static void Compress(string sourcePath, string archivePath, CompressionFormat format = CompressionFormat.Zip, CompressionLevel level = CompressionLevel.Optimal) + { + if (!File.Exists(sourcePath) && !Directory.Exists(sourcePath)) + { + throw new FileNotFoundException("源文件或目录不存在。", sourcePath); + } + + switch (format) + { + case CompressionFormat.Zip: + if (Directory.Exists(sourcePath)) + { + ZipFile.CreateFromDirectory(sourcePath, archivePath, level, false); + } + else + { + CompressFileToZip(sourcePath, archivePath, level); + } + break; + + case CompressionFormat.GZip: + CompressToGZip(sourcePath, archivePath, level); + break; + + case CompressionFormat.Deflate: + CompressToDeflate(sourcePath, archivePath, level); + break; + + default: + throw new ArgumentException("不支持的压缩格式。", nameof(format)); + } + } + + /// + /// 压缩单个文件到ZIP + /// + private static void CompressFileToZip(string sourceFile, string archivePath, CompressionLevel level) + { + using var archive = ZipFile.Open(archivePath, ZipArchiveMode.Create); + archive.CreateEntryFromFile(sourceFile, Path.GetFileName(sourceFile), level); + } + + /// + /// 压缩到GZIP + /// + private static void CompressToGZip(string sourcePath, string archivePath, CompressionLevel level) + { + using var sourceStream = File.OpenRead(sourcePath); + using var destinationStream = File.Create(archivePath); + using var gzipStream = new GZipStream(destinationStream, level); + sourceStream.CopyTo(gzipStream); + } + + /// + /// 从GZIP解压 + /// + private static void ExtractGZip(string archivePath, string extractPath) + { + using var sourceStream = File.OpenRead(archivePath); + using var gzipStream = new GZipStream(sourceStream, CompressionMode.Decompress); + using var destinationStream = File.Create(Path.Combine(extractPath, Path.GetFileNameWithoutExtension(archivePath))); + gzipStream.CopyTo(destinationStream); + } + + /// + /// 压缩到DEFLATE + /// + private static void CompressToDeflate(string sourcePath, string archivePath, CompressionLevel level) + { + using var sourceStream = File.OpenRead(sourcePath); + using var destinationStream = File.Create(archivePath); + using var deflateStream = new DeflateStream(destinationStream, level); + sourceStream.CopyTo(deflateStream); + } + + /// + /// 从DEFLATE解压 + /// + private static void ExtractDeflate(string archivePath, string extractPath) + { + using var sourceStream = File.OpenRead(archivePath); + using var deflateStream = new DeflateStream(sourceStream, CompressionMode.Decompress); + using var destinationStream = File.Create(Path.Combine(extractPath, Path.GetFileNameWithoutExtension(archivePath))); + deflateStream.CopyTo(destinationStream); + } + + /// + /// 压缩格式 + /// + public enum CompressionFormat + { + /// + /// ZIP格式 + /// + Zip, + + /// + /// GZIP格式 + /// + GZip, + + /// + /// DEFLATE格式 + /// + Deflate + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Utils/IO/DirectoryHelper.cs b/Admin.NET/Admin.NET.Core/Utils/IO/DirectoryHelper.cs new file mode 100644 index 00000000..dfd6942b --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Utils/IO/DirectoryHelper.cs @@ -0,0 +1,286 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 目录帮助类 +/// +public static class DirectoryHelper +{ + #region 目录操作 + + /// + /// 创建一个新目录,如果目录已存在则不执行任何操作 + /// + /// 要创建的目录的路径 + public static void CreateIfNotExists(string directoryPath) + { + if (!Directory.Exists(directoryPath)) + { + _ = Directory.CreateDirectory(directoryPath); + } + } + + /// + /// 删除一个目录,如果目录存在 + /// + /// 要删除的目录的路径 + public static void DeleteIfExists(string directoryPath) + { + if (Directory.Exists(directoryPath)) + { + // true 表示删除目录及其所有子目录和文件 + Directory.Delete(directoryPath, true); + } + } + + /// + /// 清空一个目录,不删除目录本身,只删除其中的所有文件和子目录 + /// + /// 要清空的目录的路径 + public static void Clear(string directoryPath) + { + foreach (var file in Directory.GetFiles(directoryPath)) + { + File.Delete(file); + } + + foreach (var dir in Directory.GetDirectories(directoryPath)) + { + DeleteIfExists(dir); + } + } + + /// + /// 移动目录到另一个位置 + /// + /// 当前目录的路径 + /// 目标目录的路径 + public static void Move(string sourcePath, string destinationPath) + { + Directory.Move(sourcePath, destinationPath); + } + + /// + /// 复制一个目录到另一个位置 + /// + /// 当前目录的路径 + /// 目标目录的路径 + /// 如果目标位置已经存在同名目录,是否覆盖 + public static void Copy(string sourcePath, string destinationPath, bool overwrite = false) + { + try + { + // 检查目标目录是否存在,如果存在且 overwrite 为 false,则不执行复制 + if (Directory.Exists(destinationPath) && !overwrite) + { + throw new IOException("目标目录已存在且 overwrite 参数为 false "); + } + + // 复制目录 + DirectoryInfo sourceDir = new(sourcePath); + if (!Directory.Exists(destinationPath)) + { + _ = Directory.CreateDirectory(destinationPath); + } + + var files = sourceDir.GetFiles(); + foreach (var file in files) + { + _ = file.CopyTo(Path.Combine(destinationPath, file.Name), overwrite); + } + + var dirs = sourceDir.GetDirectories(); + foreach (var dir in dirs) + { + var newDirPath = Path.Combine(destinationPath, dir.Name); + Copy(dir.FullName, newDirPath, overwrite); + } + } + catch (Exception ex) + { + throw new Exception("复制目录出错", ex); + } + } + + #endregion 目录操作 + + #region 目录信息 + + /// + /// 获取当前目录中所有文件的路径 + /// + /// 目录的路径 + /// 包含目录中文件路径的数组 + public static string[] GetFiles(string directoryPath) + { + return Directory.GetFiles(directoryPath); + } + + /// + /// 获取目录中所有文件的路径 + /// + /// 目录的路径 + /// 模式字符串,"*"代表0或 N 个字符,"?"代表1个字符 范例:"Log*.xml"表示搜索所有以 Log 开头的 Xml 文件 + /// 是否搜索子目录 + /// 包含目录中所有文件路径的数组 + public static string[] GetFiles(string directoryPath, string searchPattern, bool isSearchChild) + { + return Directory.GetFiles(directoryPath, searchPattern, + isSearchChild ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly); + } + + /// + /// 获取当前目录中所有子目录的路径 + /// + /// 目录的路径 + /// 包含目录中所有子目录路径的数组 + public static string[] GetDirectories(string directoryPath) + { + return Directory.GetDirectories(directoryPath); + } + + /// + /// 获取指定目录及子目录中所有子目录列表 + /// + /// 指定目录的绝对路径 + /// 模式字符串,"*"代表0或 N 个字符,"?"代表1个字符 范例:"Log*.xml"表示搜索所有以 Log 开头的 Xml 目录 + /// 是否搜索子目录 + /// 包含目录中所有文件路径的数组 + public static string[] GetDirectories(string directoryPath, string searchPattern, bool isSearchChild) + { + return Directory.GetDirectories(directoryPath, searchPattern, + isSearchChild ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly); + } + + /// + /// 获取指定目录大小 + /// + /// + /// + /// + public static long GetSize(string dirPath) + { + // 定义一个 DirectoryInfo 对象 + DirectoryInfo di = new(dirPath); + // 通过 GetFiles 方法,获取 di 目录中的所有文件的大小 + var len = di.GetFiles().Sum(fi => fi.Length); + // 获取 di 中所有的文件夹,并存到一个新的对象数组中,以进行递归 + var dis = di.GetDirectories(); + if (dis.Length <= 0) + { + return len; + } + + len += dis.Sum(t => GetSize(t.FullName)); + return len; + } + + /// + /// 获取随机文件名 + /// + /// + public static string GetRandomName() + { + return Path.GetRandomFileName(); + } + + /// + /// 根据时间得到文件名 + /// yyyyMMddHHmmssfff + /// + /// + public static string GetDateName() + { + return DateTime.Now.ToString("yyyyMMddHHmmssfff"); + } + + #endregion 目录信息 + + #region 目录检查 + + /// + /// 检查给定路径是否为目录 + /// + /// 要检查的路径 + /// true 如果路径是一个目录,否则 false + public static bool Exists(string path) + { + return Directory.Exists(path); + } + + /// + /// 检测指定目录中是否存在指定的文件(搜索子目录) + /// + /// 指定目录的绝对路径 + /// 模式字符串,"*"代表0或 N 个字符,"?"代表1个字符 范例:"Log*.xml"表示搜索所有以 Log 开头的 Xml 文件 + /// 是否搜索子目录 + /// + /// + public static bool IsContainsFiles(string directoryPath, string searchPattern, bool isSearchChild) + { + // 获取指定的文件列表 + var fileNames = GetFiles(directoryPath, searchPattern, isSearchChild); + // 判断指定文件是否存在 + return fileNames.Length != 0; + } + + /// + /// 检测指定目录是否为空 + /// + /// 指定目录的绝对路径 + /// + public static bool IsEmpty(string directoryPath) + { + // 判断是否存在文件 + var fileNames = GetFiles(directoryPath); + if (fileNames.Length != 0) + { + return false; + } + + // 判断是否存在文件夹 + var directoryNames = GetDirectories(directoryPath); + return directoryNames.Length == 0; + } + + /// + /// 返回应用程序的基目录 + /// 程序内部使用的路径,通常是程序所在的文件夹 + /// 举例:如果你的程序安装在 C:\MyApp\ 下,那么该属性通常返回 C:\MyApp\ + /// + /// + public static string GetBaseDirectory() + { + //return AppDomain.CurrentDomain.BaseDirectory; + // 在大多数情况下,它和 AppDomain.CurrentDomain.BaseDirectory 是一样的 + return AppContext.BaseDirectory; + } + + /// + /// 当前进程的工作目录,即程序启动时或运行过程中当前的“活动目录” + /// 这个目录可以在程序运行过程中被修改,所以它不一定是程序所在的文件夹 + /// 举例:如果你从命令行的 D:\Projects 目录启动了程序,即使程序实际文件在 C:\MyApp\ 下,这个方法返回的就是 D:\Projects + /// + /// + public static string GetCurrentDirectory() + { + return Directory.GetCurrentDirectory(); + } + + /// + /// 返回应用程序的默认静态文件目录 + /// 用于存放静态文件,如图片、CSS、JS 等 + /// + /// + public static string GetWwwrootDirectory() + { + return Path.Combine(GetBaseDirectory(), "wwwroot"); + } + + #endregion 目录检查 +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Utils/ReflectionUtil.cs b/Admin.NET/Admin.NET.Core/Utils/IO/FileFormatExtensions.cs similarity index 52% rename from Admin.NET/Admin.NET.Core/Utils/ReflectionUtil.cs rename to Admin.NET/Admin.NET.Core/Utils/IO/FileFormatExtensions.cs index 0797a1d5..fd2b7b65 100644 --- a/Admin.NET/Admin.NET.Core/Utils/ReflectionUtil.cs +++ b/Admin.NET/Admin.NET.Core/Utils/IO/FileFormatExtensions.cs @@ -7,22 +7,32 @@ namespace Admin.NET.Core; /// -/// 反射工具类 +/// File 扩展方法 /// -public static class ReflectionUtil +public static class FileFormatExtensions { - /// - /// 获取字段特性 - /// - /// - /// - /// - public static T GetDescriptionValue(this FieldInfo field) where T : Attribute - { - // 获取字段的指定特性,不包含继承中的特性 - object[] customAttributes = field.GetCustomAttributes(typeof(T), false); + private static readonly string[] Suffixes = ["B", "KB", "MB", "GB", "TB", "PB"]; - // 如果没有数据返回null - return customAttributes.Length > 0 ? (T)customAttributes[0] : null; + /// + /// 格式化文件大小显示为字符串 + /// + /// + /// + public static string FormatFileSizeToString(this long bytes) + { + double last = 1; + for (var i = 0; i < Suffixes.Length; i++) + { + var current = Math.Pow(1024, i + 1); + var temp = bytes / current; + if (temp < 1) + { + return (bytes / last).ToString("f3") + Suffixes[i]; + } + + last = current; + } + + return bytes.ToString(); } } \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Utils/IO/FileHelper.cs b/Admin.NET/Admin.NET.Core/Utils/IO/FileHelper.cs new file mode 100644 index 00000000..51bad886 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Utils/IO/FileHelper.cs @@ -0,0 +1,332 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 文件帮助类 +/// +public static class FileHelper +{ + #region 文件操作 + + /// + /// 读取文件内容到字符串 + /// + /// 要读取的文件路径 + /// 文件内容为字符串 + public static string ReadAllText(string filePath) + { + return File.ReadAllText(filePath); + } + + /// + /// 打开一个文本文件,读取文件的所有行,然后关闭文件 + /// + /// 要打开以进行读取的文件路径 + /// 包含文件所有行的字符串 + public static async Task ReadAllTextAsync(string filePath) + { + using var reader = File.OpenText(filePath); + return await reader.ReadToEndAsync(); + } + + /// + /// 打开一个文本文件,读取文件的所有字节,然后关闭文件 + /// + /// 要打开以进行读取的文件路径 + /// 包含文件所有字节的字节数组 + public static async Task ReadAllBytesAsync(string filePath) + { + await using var stream = File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); + var result = new byte[stream.Length]; + _ = await stream.ReadAsync(result.AsMemory(0, (int)stream.Length)); + return result; + } + + /// + /// 打开一个文本文件,读取文件的所有行,然后关闭文件 + /// + /// 要打开以进行读取的文件 + /// 文件的编码默认为 UTF8 + /// 指定操作系统应如何打开文件默认为 Open + /// 定义对文件的读取、写入或读写访问的常量默认为 Read + /// 包含控制其他 FileStream 对象可以对同一文件拥有的访问类型的常量默认为 Read + /// StreamReader 缓冲区的长度默认为 4096 + /// 指示 FileStream 选项默认为 Asynchronous(文件将用于异步读取)和 SequentialScan(文件将从开始到末尾顺序访问) + /// 包含文件所有行的字符串数组 + public static async Task ReadAllLinesAsync(string path, + Encoding? encoding = null, + FileMode fileMode = FileMode.Open, + FileAccess fileAccess = FileAccess.Read, + FileShare fileShare = FileShare.Read, + int bufferSize = 4096, + FileOptions fileOptions = FileOptions.Asynchronous | FileOptions.SequentialScan) + { + encoding ??= Encoding.UTF8; + List lines = []; + await using (FileStream stream = new(path, fileMode, fileAccess, fileShare, bufferSize, fileOptions)) + { + using StreamReader reader = new(stream, encoding); + while (await reader.ReadLineAsync() is { } line) + { + lines.Add(line); + } + } + + return [.. lines]; + } + + /// + /// 打开一个文本文件,读取不包含 BOM 的内容 + /// + /// 要打开以进行读取的文件 + /// 包含文件所有行的字符串 + public static async Task ReadWithoutBomAsync(string path) + { + var content = await ReadAllBytesAsync(path); + return ConvertFromBytesWithoutBom(content)!; + } + + /// + /// 将字节数组 byte[]转换为不包含字节顺序标记(BOM)的字符串 + /// + /// 要转换为字符串的 byte[]数组 + /// 获取字符串的编码默认为 UTF8 + /// 转换得到的字符串 + private static string? ConvertFromBytesWithoutBom(byte[]? bytes, Encoding? encoding = null) + { + if (bytes is null) + { + return null; + } + + encoding ??= Encoding.UTF8; + + var hasBom = bytes is [0xEF, 0xBB, 0xBF, ..]; + + return hasBom ? encoding.GetString(bytes, 3, bytes.Length - 3) : encoding.GetString(bytes); + } + + /// + /// 将文本内容写入到文件 + /// + /// 要写入的文件路径 + /// 要写入的文本内容 + public static void WriteAllText(string filePath, string content) + { + File.WriteAllText(filePath, content); + } + + /// + /// 将文本内容追加到文件 + /// + /// 要追加的文件路径 + /// 要追加的文本内容 + public static void AppendAllText(string filePath, string content) + { + File.AppendAllText(filePath, content); + } + + /// + /// 创建文件,如果文件不存在 + /// + /// 文件路径 + public static void CreateIfNotExists(string filePath) + { + if (!File.Exists(filePath)) + { + _ = File.Create(filePath); + } + } + + /// + /// 删除一个文件,如果文件存在 + /// + /// 要删除的文件路径 + public static void DeleteIfExists(string filePath) + { + if (File.Exists(filePath)) + { + File.Delete(filePath); + } + } + + /// + /// 移动文件到另一个位置 + /// + /// 当前文件的路径 + /// 目标文件的路径 + public static void Move(string sourcePath, string destinationPath) + { + File.Move(sourcePath, destinationPath); + } + + /// + /// 复制文件到另一个位置 + /// + /// 当前文件的路径 + /// 目标文件的路径 + /// 如果目标位置已经存在同名文件,是否覆盖 + public static void Copy(string sourcePath, string destinationPath, bool overwrite = false) + { + File.Copy(sourcePath, destinationPath, overwrite); + } + + /// + /// 清空文件内容 + /// + /// 文件的绝对路径 + public static void Clean(string filePath) + { + if (!File.Exists(filePath)) + { + return; + } + + // 删除文件 + File.Delete(filePath); + // 重新创建该文件 + _ = File.Create(filePath); + } + + #endregion 文件操作 + + #region 文件信息 + + /// + /// 获取文件的哈希值 + /// + /// 要计算哈希值的文件路径 + /// 文件的哈希值 + public static string GetHash(string filePath) + { + using var stream = File.OpenRead(filePath); + return HashHelper.StreamMd5(stream); + } + + /// + /// 获取指定文件大小 + /// + /// + /// + public static long GetSize(string filePath) + { + return new FileInfo(filePath).Length; + } + + /// + /// 从文件的绝对路径中获取文件名(包含扩展方法名) + /// + /// + /// + public static string GetName(string filePath) + { + return Path.GetFileName(filePath); + } + + /// + /// 获取随机文件名 + /// + /// + public static string GetRandomName() + { + return Path.GetRandomFileName(); + } + + /// + /// 根据时间得到文件名 + /// yyyyMMddHHmmssfff + /// + /// + public static string GetDateName() + { + return DateTime.Now.ToString("yyyyMMddHHmmssfff"); + } + + /// + /// 从文件的绝对路径中获取扩展方法名 + /// 文件扩展方法名是包含点(.)的 + /// + /// + /// + public static string GetExtension(string filePath) + { + return Path.GetExtension(filePath); + } + + /// + /// 从文件的绝对路径中获取文件名(不包含扩展方法名) + /// + /// + /// + public static string GetNameWithoutExtension(string filePath) + { + return Path.GetFileNameWithoutExtension(filePath); + } + + /// + /// 生成唯一的文件名,上传文件使用 + /// + /// 包含扩展方法名的源文件名 + /// + public static string GetUniqueName(string fileName) + { + var fileNameWithoutExtension = GetNameWithoutExtension(fileName); + var fileExtension = GetExtension(fileName); + var uniqueFileName = $"{fileNameWithoutExtension}_{GetDateName()}_{GetRandomName()}"; + return uniqueFileName + fileExtension; + } + + /// + /// 获取文本文件的行数 + /// + /// 文件的绝对路径 + /// + public static int GetTextLineCount(string filePath) + { + // 将文本文件的各行读到一个字符串数组中 + var rows = File.ReadAllLines(filePath); + // 返回行数 + return rows.Length; + } + + #endregion 文件信息 + + #region 文件检查 + + /// + /// 检查文件是否存在 + /// + /// 要检查的文件路径 + /// 如果文件存在返回 true,否则返回 false + public static bool Exists(string filePath) + { + return File.Exists(filePath); + } + + /// + /// 检查文件是否被锁定 + /// + /// 要检查的文件路径 + /// true 如果文件没有被锁定,可以进行读写操作,否则 false + public static bool IsUnlocked(string filePath) + { + try + { + using var fileStream = File.Open(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None); + // 如果没有异常,文件没有被锁定 + return true; + } + catch (IOException) + { + // 如果发生异常,文件可能被锁定 + return false; + } + } + + #endregion 文件检查 +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Utils/IO/StreamExtensions.cs b/Admin.NET/Admin.NET.Core/Utils/IO/StreamExtensions.cs new file mode 100644 index 00000000..3b859e1d --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Utils/IO/StreamExtensions.cs @@ -0,0 +1,113 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 文件流扩展方法 +/// +public static class StreamExtensions +{ + /// + /// 获取文件流字节 + /// + /// + /// + public static byte[] GetAllBytes(this Stream stream) + { + if (stream is MemoryStream memoryStream) + { + return memoryStream.ToArray(); + } + + using var ms = stream.CreateMemoryStream(); + return ms.ToArray(); + } + + /// + /// 获取文件流字节,异步 + /// + /// + /// + /// + public static async Task GetAllBytesAsync(this Stream stream, CancellationToken cancellationToken = default) + { + if (stream is MemoryStream memoryStream) + { + return memoryStream.ToArray(); + } + + using var ms = await stream.CreateMemoryStreamAsync(cancellationToken); + return ms.ToArray(); + } + + /// + /// 复制文件流,异步 + /// + /// + /// + /// + /// + public static Task CopyToAsync(this Stream stream, Stream destination, CancellationToken cancellationToken) + { + if (stream.CanSeek) + { + stream.Position = 0; + } + + // 81920 已经是默认值,但是需要设置才能传递 cancellationToken + return stream.CopyToAsync(destination, 81920, cancellationToken); + } + + /// + /// 创建内存流 + /// + /// + /// + public static MemoryStream CreateMemoryStream(this Stream stream) + { + if (stream.CanSeek) + { + stream.Position = 0; + } + + MemoryStream memoryStream = new(); + stream.CopyTo(memoryStream); + + if (stream.CanSeek) + { + stream.Position = 0; + } + + memoryStream.Position = 0; + return memoryStream; + } + + /// + /// 创建内存流,异步 + /// + /// + /// + /// + public static async Task CreateMemoryStreamAsync(this Stream stream, CancellationToken cancellationToken = default) + { + if (stream.CanSeek) + { + stream.Position = 0; + } + + MemoryStream memoryStream = new(); + await stream.CopyToAsync(memoryStream, cancellationToken); + + if (stream.CanSeek) + { + stream.Position = 0; + } + + memoryStream.Position = 0; + return memoryStream; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Utils/PathTreeBuilder.cs b/Admin.NET/Admin.NET.Core/Utils/PathTreeBuilder.cs index d93415a2..7fcffe4b 100644 --- a/Admin.NET/Admin.NET.Core/Utils/PathTreeBuilder.cs +++ b/Admin.NET/Admin.NET.Core/Utils/PathTreeBuilder.cs @@ -14,7 +14,7 @@ public class TreeNode public int Id { get; set; } public int Pid { get; set; } public string Name { get; set; } - public List Children { get; set; } = new(); + public List Children { get; set; } = []; } /// @@ -31,13 +31,13 @@ public class PathTreeBuilder foreach (var path in paths) { - var parts = path.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); + var parts = path.Split(['/'], StringSplitOptions.RemoveEmptyEntries); TreeNode currentNode = root; foreach (var part in parts) { var key = currentNode.Id + "_" + part; // 生成唯一键 - if (!dict.ContainsKey(key)) + if (!dict.TryGetValue(key, out TreeNode value)) { var newNode = new TreeNode { @@ -46,9 +46,10 @@ public class PathTreeBuilder Name = part }; currentNode.Children.Add(newNode); - dict[key] = newNode; + value = newNode; + dict[key] = value; } - currentNode = dict[key]; // 更新当前节点 + currentNode = value; // 更新当前节点 } } diff --git a/Admin.NET/Admin.NET.Core/Utils/ReflectionHelper.cs b/Admin.NET/Admin.NET.Core/Utils/ReflectionHelper.cs new file mode 100644 index 00000000..3656d3ff --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Utils/ReflectionHelper.cs @@ -0,0 +1,1031 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; +using System.Runtime.Loader; +using SysReflection = System.Reflection.IntrospectionExtensions; + +namespace Admin.NET.Core; + +/// +/// 反射帮助类 +/// 提供程序集、类型、属性等反射操作的工具方法 +/// +public static class ReflectionHelper +{ + #region 程序集 + + /// + /// 获取应用程序的入口程序集 + /// + /// 入口程序集,如果无法确定则返回 null + public static Assembly? GetEntryAssembly() + { + return Assembly.GetEntryAssembly(); + } + + /// + /// 获取入口程序集的名称 + /// + /// 程序集名称,如果无法获取则返回 null + public static string? GetEntryAssemblyName() + { + return GetEntryAssembly()?.GetName().Name; + } + + /// + /// 获取入口程序集的版本信息 + /// + /// 程序集版本,如果无法获取则返回 null + public static Version? GetEntryAssemblyVersion() + { + return GetEntryAssembly()?.GetName().Version; + } + + /// + /// 获取指定目录下的程序集文件路径 + /// + /// 要搜索的文件夹路径 + /// 搜索选项(是否包含子目录) + /// 程序集文件路径集合(.dll 和 .exe 文件) + /// 当文件夹路径为空或 null 时 + public static IEnumerable GetAssemblyFiles(string folderPath, SearchOption searchOption) + { + ArgumentException.ThrowIfNullOrWhiteSpace(folderPath); + + return Directory + .EnumerateFiles(folderPath, "*.*", searchOption) + .Where(s => s.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) || + s.EndsWith(".exe", StringComparison.OrdinalIgnoreCase)); + } + + /// + /// 从指定目录加载所有程序集 + /// + /// 要搜索的文件夹路径 + /// 搜索选项(是否包含子目录) + /// 加载的程序集列表 + /// 当文件夹路径为空或 null 时 + public static List LoadAssemblies(string folderPath, SearchOption searchOption) + { + ArgumentException.ThrowIfNullOrWhiteSpace(folderPath); + + return [.. GetAssemblyFiles(folderPath, searchOption).Select(AssemblyLoadContext.Default.LoadFromAssemblyPath)]; + } + + /// + /// 获取当前应用程序域中加载的所有程序集 + /// + /// 已加载的程序集集合 + public static IEnumerable GetAllAssemblies() + { + return AssemblyLoadContext.Default.Assemblies; + } + + /// + /// 获取所有被引用的程序集(递归获取所有依赖项) + /// + /// 是否跳过系统程序集(默认为 true) + /// 被引用的程序集集合 + public static IEnumerable GetAllReferencedAssemblies(bool skipSystemAssemblies = true) + { + var rootAssembly = Assembly.GetEntryAssembly(); + rootAssembly ??= Assembly.GetCallingAssembly(); + + HashSet returnAssemblies = new(new AssemblyEquality()); + HashSet loadedAssemblies = []; + Queue assembliesToCheck = new(); + assembliesToCheck.Enqueue(rootAssembly); + + if (skipSystemAssemblies && IsSystemAssembly(rootAssembly)) + { + if (IsValid(rootAssembly)) + { + _ = returnAssemblies.Add(rootAssembly); + } + } + + while (assembliesToCheck.Count != 0) + { + var assemblyToCheck = assembliesToCheck.Dequeue(); + foreach (var reference in assemblyToCheck.GetReferencedAssemblies()) + { + if (loadedAssemblies.Contains(reference.FullName)) + { + continue; + } + + var assembly = Assembly.Load(reference); + if (skipSystemAssemblies && IsSystemAssembly(assembly)) + { + continue; + } + + assembliesToCheck.Enqueue(assembly); + _ = loadedAssemblies.Add(reference.FullName); + if (IsValid(assembly)) + { + _ = returnAssemblies.Add(assembly); + } + } + } + + var asmsInBaseDir = Directory.EnumerateFiles(AppContext.BaseDirectory, "*.dll", new EnumerationOptions + { + RecurseSubdirectories = true + }); + foreach (var assemblyPath in asmsInBaseDir) + { + if (!IsManagedAssembly(assemblyPath)) + { + continue; + } + + var asmName = AssemblyName.GetAssemblyName(assemblyPath); + + // 如果程序集已经加载过了就不再加载 + if (returnAssemblies.Any(x => AssemblyName.ReferenceMatchesDefinition(x.GetName(), asmName))) + { + continue; + } + + if (skipSystemAssemblies && IsSystemAssembly(assemblyPath)) + { + continue; + } + + var asm = TryLoadAssembly(assemblyPath); + if (asm is null) + { + continue; + } + + if (!IsValid(asm)) + { + continue; + } + + if (skipSystemAssemblies && IsSystemAssembly(asm)) + { + continue; + } + + _ = returnAssemblies.Add(asm); + } + + return [.. returnAssemblies]; + } + + /// + /// 获取符合条件名称的程序集 + /// + /// 前缀名 + /// 后缀名 + /// 包含名 + /// + public static IEnumerable GetEffectiveAssemblies(string prefix, string suffix, string contain) + { + ArgumentException.ThrowIfNullOrWhiteSpace(prefix); + ArgumentException.ThrowIfNullOrWhiteSpace(suffix); + ArgumentException.ThrowIfNullOrWhiteSpace(contain); + + return GetAllAssemblies() + .Where(assembly => assembly.ManifestModule.Name.EndsWith(suffix, StringComparison.InvariantCultureIgnoreCase)) + .Where(assembly => assembly.ManifestModule.Name.StartsWith(prefix, StringComparison.InvariantCultureIgnoreCase)) + .Where(assembly => assembly.ManifestModule.Name.Contains(contain, StringComparison.InvariantCultureIgnoreCase)) + .Distinct(); + } + + /// + /// 获取符合条件前后缀名称的程序集 + /// + /// 前缀名 + /// 后缀名 + /// + public static IEnumerable GetEffectivePatchAssemblies(string prefix, string suffix) + { + ArgumentException.ThrowIfNullOrWhiteSpace(prefix); + ArgumentException.ThrowIfNullOrWhiteSpace(suffix); + + return GetAllAssemblies() + .Where(assembly => assembly.ManifestModule.Name.EndsWith(suffix, StringComparison.InvariantCultureIgnoreCase)) + .Where(assembly => assembly.ManifestModule.Name.StartsWith(prefix, StringComparison.InvariantCultureIgnoreCase)) + .Distinct(); + } + + /// + /// 获取符合条件包含名称的程序集 + /// + /// 包含名 + /// + public static IEnumerable GetEffectiveCenterAssemblies(string contain) + { + ArgumentException.ThrowIfNullOrWhiteSpace(contain); + + return GetAllAssemblies() + .Where(assembly => assembly.ManifestModule.Name.Contains(contain, StringComparison.InvariantCultureIgnoreCase)) + .Distinct(); + } + + /// + /// 获取曦寒程序集 + /// + /// + public static IEnumerable GetXiHanAssemblies() + { + return GetEffectivePatchAssemblies("XiHan", "dll"); + } + + /// + /// 获取应用程序集 + /// + /// + public static IEnumerable GetApplicationAssemblies() + { + return GetEffectiveCenterAssemblies("Application"); + } + + /// + /// 获取曦寒应用程序集 + /// + /// + public static IEnumerable GetXiHanApplicationAssemblies() + { + return GetEffectiveAssemblies("XiHan", "dll", "Application"); + } + + #endregion 程序集 + + #region 程序集类 + + /// + /// 获取所有已加载程序集中的所有类型 + /// + /// 所有类型的集合 + public static IEnumerable GetAllTypes() + { + return GetAllAssemblies() + .SelectMany(GetAllTypes) + .Distinct(); + } + + /// + /// 获取指定程序集中的所有类型(安全获取,处理加载异常) + /// + /// 目标程序集 + /// 程序集中的类型集合 + /// 当程序集为 null 时 + public static IEnumerable GetAllTypes(Assembly assembly) + { + ArgumentNullException.ThrowIfNull(assembly); + + try + { + return assembly.GetTypes(); + } + catch (ReflectionTypeLoadException ex) + { + return ex.Types.Where(type => type != null)!; + } + } + + /// + /// 获取曦寒框架程序集中的所有类型 + /// + /// 曦寒框架类型集合 + public static IEnumerable GetXiHanTypes() + { + return GetXiHanAssemblies() + .SelectMany(GetAllTypes) + .Distinct(); + } + + /// + /// 获取应用程序集中的所有类型 + /// + /// 应用程序类型集合 + public static IEnumerable GetApplicationTypes() + { + return GetApplicationAssemblies() + .SelectMany(GetAllTypes) + .Distinct(); + } + + /// + /// 获取曦寒应用程序集中的所有类型 + /// + /// 曦寒应用程序类型集合 + public static IEnumerable GetXiHanApplicationTypes() + { + return GetXiHanApplicationAssemblies() + .SelectMany(GetAllTypes) + .Distinct(); + } + + #endregion 程序集类 + + #region 类型 + + /// + /// 检查给定类型是否实现或继承了指定的泛型类型 + /// + /// 要检查的类型 + /// 泛型类型定义 + /// 如果给定类型实现或继承了泛型类型则返回 true,否则返回 false + public static bool IsAssignableToGenericType(Type givenType, Type genericType) + { + ArgumentNullException.ThrowIfNull(givenType); + ArgumentNullException.ThrowIfNull(genericType); + + var givenTypeInfo = SysReflection.GetTypeInfo(givenType); + + if (givenTypeInfo.IsGenericType && givenType.GetGenericTypeDefinition() == genericType) + { + return true; + } + + foreach (var interfaceType in givenTypeInfo.GetInterfaces()) + { + if (SysReflection.GetTypeInfo(interfaceType).IsGenericType && interfaceType.GetGenericTypeDefinition() == genericType) + { + return true; + } + } + + return givenTypeInfo.BaseType != null && IsAssignableToGenericType(givenTypeInfo.BaseType, genericType); + } + + /// + /// 获取给定类型实现的所有泛型类型 + /// + /// 要检查的类型 + /// 泛型类型定义 + /// 实现的泛型类型列表 + public static List GetImplementedGenericTypes(Type givenType, Type genericType) + { + ArgumentNullException.ThrowIfNull(givenType); + ArgumentNullException.ThrowIfNull(genericType); + + var result = new List(); + AddImplementedGenericTypes(result, givenType, genericType); + return result; + } + + /// + /// 尝试获取类成员及其声明类型上定义的单个特性,包括继承的特性 + /// 如果未找到则返回默认值 + /// + /// 特性类型 + /// 成员信息 + /// 默认值(默认为 null) + /// 是否从基类继承特性 + /// 找到的特性实例或默认值 + public static TAttribute? GetSingleAttributeOrDefault(MemberInfo memberInfo, TAttribute? defaultValue = default, bool inherit = true) + where TAttribute : Attribute + { + ArgumentNullException.ThrowIfNull(memberInfo); + + // 使用 GetCustomAttributes 方法获取特性 + return memberInfo.IsDefined(typeof(TAttribute), inherit) + ? memberInfo.GetCustomAttributes(typeof(TAttribute), inherit).Cast().First() + : defaultValue; + } + + /// + /// 尝试获取类成员或其声明类型上定义的单个特性,包括继承的特性 + /// 如果未找到则返回默认值 + /// + /// 特性类型 + /// 成员信息 + /// 默认值(默认为 null) + /// 是否从基类继承特性 + /// 找到的特性实例或默认值 + public static TAttribute? GetSingleAttributeOfMemberOrDeclaringTypeOrDefault(MemberInfo memberInfo, TAttribute? defaultValue = default, bool inherit = true) + where TAttribute : class + { + ArgumentNullException.ThrowIfNull(memberInfo); + + return memberInfo.GetCustomAttributes(inherit).OfType().FirstOrDefault() + ?? SysReflection.GetTypeInfo(memberInfo.DeclaringType).GetCustomAttributes(inherit).OfType().FirstOrDefault() + ?? defaultValue; + } + + /// + /// 获取类成员及其声明类型上定义的所有特性,包括继承的特性 + /// + /// 特性类型 + /// 成员信息 + /// 是否从基类继承特性 + /// 特性集合 + public static IEnumerable GetAttributesOfMemberOrDeclaringType(MemberInfo memberInfo, bool inherit = true) + where TAttribute : class + { + ArgumentNullException.ThrowIfNull(memberInfo); + + var customAttributes = memberInfo.GetCustomAttributes(inherit).OfType(); + var declaringTypeCustomAttributes = + SysReflection.GetTypeInfo(memberInfo.DeclaringType).GetCustomAttributes(inherit).OfType(); + return declaringTypeCustomAttributes != null + ? customAttributes.Concat(declaringTypeCustomAttributes).Distinct() + : customAttributes; + } + + /// + /// 通过完整属性路径从给定对象获取属性值 + /// + /// 目标对象 + /// 对象类型 + /// 属性路径(支持嵌套属性,用点分隔) + /// 属性值,如果属性不存在则返回 null + public static object? GetValueByPath(object obj, Type objectType, string propertyPath) + { + ArgumentNullException.ThrowIfNull(obj); + ArgumentNullException.ThrowIfNull(objectType); + ArgumentException.ThrowIfNullOrWhiteSpace(propertyPath); + + var value = obj; + var currentType = objectType; + var objectPath = currentType.FullName; + var absolutePropertyPath = propertyPath; + if (objectPath != null && absolutePropertyPath.StartsWith(objectPath)) + { + absolutePropertyPath = absolutePropertyPath.Replace(objectPath + ".", ""); + } + + foreach (var propertyName in absolutePropertyPath.Split('.')) + { + var property = currentType.GetProperty(propertyName); + if (property != null) + { + if (value != null) + { + value = property.GetValue(value, null); + } + currentType = property.PropertyType; + } + else + { + value = null; + break; + } + } + + return value; + } + + /// + /// 递归获取指定类型中的所有公共常量值(包括基类型) + /// + /// 目标类型 + /// 常量值数组 + public static string[] GetPublicConstantsRecursively(Type type) + { + ArgumentNullException.ThrowIfNull(type); + + const int MaxRecursiveParameterValidationDepth = 8; + var publicConstants = new List(); + + static void Recursively(List constants, Type targetType, int currentDepth) + { + if (currentDepth > MaxRecursiveParameterValidationDepth) + { + return; + } + + constants.AddRange(targetType.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy) + .Where(x => x.IsLiteral && !x.IsInitOnly) + .Select(x => x.GetValue(null)!.ToString()!)); + + var nestedTypes = targetType.GetNestedTypes(BindingFlags.Public); + + foreach (var nestedType in nestedTypes) + { + Recursively(constants, nestedType, currentDepth + 1); + } + } + + Recursively(publicConstants, type, 1); + + return [.. publicConstants]; + } + + /// + /// 通过完整属性路径设置给定对象的属性值 + /// + /// 目标对象 + /// 对象类型 + /// 属性路径(支持嵌套属性,用点分隔) + /// 要设置的值 + internal static void SetValueByPath(object obj, Type objectType, string propertyPath, object value) + { + ArgumentNullException.ThrowIfNull(obj); + ArgumentNullException.ThrowIfNull(objectType); + ArgumentException.ThrowIfNullOrWhiteSpace(propertyPath); + + var currentType = objectType; + PropertyInfo property; + var objectPath = currentType.FullName!; + var absolutePropertyPath = propertyPath; + if (absolutePropertyPath.StartsWith(objectPath)) + { + absolutePropertyPath = absolutePropertyPath.Replace(objectPath + ".", ""); + } + + var properties = absolutePropertyPath.Split('.'); + + if (properties.Length == 1) + { + property = objectType.GetProperty(properties.First())!; + property.SetValue(obj, value); + return; + } + + for (var i = 0; i < properties.Length - 1; i++) + { + property = currentType.GetProperty(properties[i])!; + obj = property.GetValue(obj, null)!; + currentType = property.PropertyType; + } + + property = currentType.GetProperty(properties.Last())!; + property.SetValue(obj, value); + } + + /// + /// 添加实现的泛型类型到结果列表 + /// + /// 结果列表 + /// 给定类型 + /// 泛型类型 + private static void AddImplementedGenericTypes(List result, Type givenType, Type genericType) + { + var givenTypeInfo = SysReflection.GetTypeInfo(givenType); + + if (givenTypeInfo.IsGenericType && givenType.GetGenericTypeDefinition() == genericType) + { + result.AddIfNotContains(givenType); + } + + foreach (var interfaceType in givenTypeInfo.GetInterfaces()) + { + if (SysReflection.GetTypeInfo(interfaceType).IsGenericType && interfaceType.GetGenericTypeDefinition() == genericType) + { + result.AddIfNotContains(interfaceType); + } + } + + if (givenTypeInfo.BaseType == null) + { + return; + } + + AddImplementedGenericTypes(result, givenTypeInfo.BaseType, genericType); + } + + #endregion 类型 + + #region 获取包含有某特性的类 + + /// + /// 获取包含有某特性的类 + /// 第一种实现 + /// + /// + /// + public static IEnumerable GetContainsAttributeTypes() + where TAttribute : Attribute + { + return GetAllTypes() + .Where(e => e.CustomAttributes.Any(g => g.AttributeType == typeof(TAttribute))); + } + + /// + /// 获取包含有某特性的类 + /// 第二种实现 + /// + /// + /// + public static IEnumerable GetContainsAttributeTypes(Attribute attribute) + { + return GetAllTypes() + .Where(e => e.CustomAttributes.Any(g => g.AttributeType == attribute.GetType())); + } + + #endregion 获取包含有某特性的类 + + #region 获取不包含有某特性的类 + + /// + /// 获取不包含有某特性的类 + /// 第一种实现 + /// + /// + /// + public static IEnumerable GetFilterAttributeTypes() + where TAttribute : Attribute + { + return GetAllTypes() + .Where(e => e.CustomAttributes.All(g => g.AttributeType != typeof(TAttribute))); + } + + /// + /// 获取包含有某特性的类 + /// 第二种实现 + /// + /// + /// + public static IEnumerable GetFilterAttributeTypes(Attribute attribute) + { + return GetAllTypes() + .Where(e => e.CustomAttributes.All(g => g.AttributeType != attribute.GetType())); + } + + #endregion 获取不包含有某特性的类 + + #region 获取某类的子类(非抽象类) + + /// + /// 获取某类的子类(非抽象类) + /// 第一种实现 + /// + /// + /// + public static IEnumerable GetSubClasses() + where T : class + { + return GetAllTypes() + .Where(t => t is { IsInterface: false, IsClass: true, IsAbstract: false }) + .Where(t => typeof(T).IsAssignableFrom(t)); + } + + /// + /// 获取某类的子类(非抽象类) + /// 第二种实现 + /// + /// + /// + public static IEnumerable GetSubClasses(Type type) + { + return GetAllTypes() + .Where(t => t is { IsInterface: false, IsClass: true, IsAbstract: false }) + .Where(type.IsAssignableFrom); + } + + /// + /// 获取某泛型接口的子类(非抽象类) + /// + /// + /// + public static IEnumerable GetSubClassesByGenericInterface(Type interfaceType) + { + return [.. GetAllTypes() + .Where(type => type is { IsInterface: false, IsClass: true, IsAbstract: false } + && type.GetInterfaces().Any(i => i.IsGenericType + && i.GetGenericTypeDefinition() == interfaceType))]; + } + + #endregion 获取某类的子类(非抽象类) + + #region 获取继承自某类的包含有某特性的接口、类的子类(非抽象类) + + /// + /// 获取继承自某类的包含有某特性的接口、类的子类(非抽象类) + /// 第一种实现 + /// + /// + /// + /// + public static IEnumerable GetContainsAttributeSubClasses() + where T : class + where TAttribute : Attribute + { + return GetSubClasses().Intersect(GetContainsAttributeTypes()); + } + + /// + /// 获取继承自某类的包含有某特性的接口、类的子类(非抽象类) + /// 第二种实现 + /// + /// + /// + /// + public static IEnumerable GetContainsAttributeSubClasses(Type type) + where TAttribute : Attribute + { + return GetSubClasses(type).Intersect(GetContainsAttributeTypes()); + } + + #endregion 获取继承自某类的包含有某特性的接口、类的子类(非抽象类) + + #region 获取继承自某类的不包含有某特性的接口、类的子类(非抽象类) + + /// + /// 获取继承自某类的不包含有某特性的接口、类的子类(非抽象类) + /// 第一种实现 + /// + /// + /// + /// + public static IEnumerable GetFilterAttributeSubClass() + where T : class + where TAttribute : Attribute + { + return GetSubClasses().Intersect(GetFilterAttributeTypes()); + } + + /// + /// 获取继承自某类的不包含有某特性的接口、类的子类(非抽象类) + /// 第二种实现 + /// + /// + /// + /// + public static IEnumerable GetFilterAttributeSubClass(Type type) + where TAttribute : Attribute + { + return GetSubClasses(type).Intersect(GetFilterAttributeTypes()); + } + + #endregion 获取继承自某类的不包含有某特性的接口、类的子类(非抽象类) + + #region 程序集依赖包 + + /// + /// 获取当前应用程序的 NuGet 程序包依赖项 + /// + /// 前缀名 + /// NuGet 包信息列表 + public static List GetNuGetPackages(string prefix) + { + var nugetPackages = new Dictionary(); + + // 获取当前应用所有程序集 + var assemblies = GetEffectivePatchAssemblies(prefix, "dll"); + + // 查找被引用程序集中的 NuGet 库依赖项 + foreach (var assembly in assemblies) + { + try + { + var referencedAssemblies = assembly.GetReferencedAssemblies() + .Where(s => !s.FullName.StartsWith("Microsoft", StringComparison.OrdinalIgnoreCase) && + !s.FullName.StartsWith("System", StringComparison.OrdinalIgnoreCase)) + .Where(s => !string.IsNullOrEmpty(s.Name) && s.Version != null); + + foreach (var referencedAssembly in referencedAssemblies) + { + // 检查引用的程序集是否来自 NuGet + if (string.IsNullOrEmpty(referencedAssembly.Name) || referencedAssembly.Version == null) + { + continue; + } + + var packageName = referencedAssembly.Name; + var packageVersion = referencedAssembly.Version.ToString(); + + // 避免重复添加相同的 NuGet 包,保留版本更高的 + if (!nugetPackages.TryGetValue(packageName, out var value)) + { + nugetPackages[packageName] = new NuGetPackage + { + PackageName = packageName, + PackageVersion = packageVersion + }; + } + else + { + var existingVersion = Version.Parse(value.PackageVersion); + var currentVersion = referencedAssembly.Version; + + if (currentVersion > existingVersion) + { + nugetPackages[packageName] = new NuGetPackage + { + PackageName = packageName, + PackageVersion = packageVersion + }; + } + } + } + } + catch (Exception ex) + { + Debug.WriteLine($"Error processing assembly {assembly.FullName}: {ex.Message}"); + } + } + + return [.. nugetPackages.Values.OrderBy(p => p.PackageName)]; + } + + #endregion 程序集依赖包 + + #region 私有方法 + + /// + /// 判断程序集是否为系统程序集(如微软官方程序集) + /// + /// 要检查的程序集 + /// 如果是系统程序集则返回 true,否则返回 false + private static bool IsSystemAssembly(Assembly assembly) + { + ArgumentNullException.ThrowIfNull(assembly); + + var asmCompanyAttr = assembly.GetCustomAttribute(); + if (asmCompanyAttr is null) + { + return false; + } + + var companyName = asmCompanyAttr.Company; + return companyName.Contains("Microsoft", StringComparison.OrdinalIgnoreCase); + } + + /// + /// 通过程序集路径判断是否为系统程序集 + /// + /// 程序集文件路径 + /// 如果是系统程序集则返回 true,否则返回 false + private static bool IsSystemAssembly(string assemblyPath) + { + ArgumentException.ThrowIfNullOrWhiteSpace(assemblyPath); + + try + { + var assembly = Assembly.LoadFrom(assemblyPath); + return IsSystemAssembly(assembly); + } + catch + { + return false; + } + } + + /// + /// 判断程序集是否有效(能否正常加载类型) + /// + /// 要验证的程序集 + /// 如果程序集有效则返回 true,否则返回 false + private static bool IsValid(Assembly assembly) + { + ArgumentNullException.ThrowIfNull(assembly); + + try + { + _ = assembly.GetTypes(); + _ = assembly.DefinedTypes.ToList(); + return true; + } + catch (ReflectionTypeLoadException) + { + return false; + } + catch (Exception) + { + return false; + } + } + + /// + /// 判断指定文件是否为托管程序集 + /// + /// 程序集文件路径 + /// 如果是托管程序集则返回 true,否则返回 false + private static bool IsManagedAssembly(string file) + { + ArgumentException.ThrowIfNullOrWhiteSpace(file); + + try + { + using var fs = File.OpenRead(file); + using PEReader peReader = new(fs); + return peReader.HasMetadata && peReader.GetMetadataReader().IsAssembly; + } + catch + { + return false; + } + } + + /// + /// 安全地尝试加载程序集,处理各种加载异常 + /// + /// 程序集文件路径 + /// 成功加载的程序集,失败则返回 null + private static Assembly? TryLoadAssembly(string assemblyPath) + { + ArgumentException.ThrowIfNullOrWhiteSpace(assemblyPath); + + if (!File.Exists(assemblyPath)) + { + return null; + } + + Assembly? assembly = null; + + try + { + var assemblyName = AssemblyName.GetAssemblyName(assemblyPath); + assembly = Assembly.Load(assemblyName); + } + catch (BadImageFormatException ex) + { + Debug.WriteLine($"BadImageFormatException loading assembly {assemblyPath}: {ex.Message}"); + } + catch (FileLoadException ex) + { + Debug.WriteLine($"FileLoadException loading assembly {assemblyPath}: {ex.Message}"); + } + catch (Exception ex) + { + Debug.WriteLine($"Exception loading assembly {assemblyPath}: {ex.Message}"); + } + + if (assembly is not null) + { + return assembly; + } + + try + { + assembly = Assembly.LoadFile(assemblyPath); + } + catch (BadImageFormatException ex) + { + Debug.WriteLine($"BadImageFormatException loading file {assemblyPath}: {ex.Message}"); + } + catch (FileLoadException ex) + { + Debug.WriteLine($"FileLoadException loading file {assemblyPath}: {ex.Message}"); + } + catch (Exception ex) + { + Debug.WriteLine($"Exception loading file {assemblyPath}: {ex.Message}"); + } + + return assembly; + } + + #endregion 私有方法 +} + +/// +/// 程序集相等性比较器 +/// 用于比较两个程序集是否相等,基于程序集名称进行匹配 +/// +internal class AssemblyEquality : EqualityComparer +{ + /// + /// 比较两个程序集是否相等 + /// + /// 第一个程序集 + /// 第二个程序集 + /// 如果两个程序集相等则返回 true,否则返回 false + public override bool Equals(Assembly? x, Assembly? y) + { + return (x is null && y is null) || + (x is not null && y is not null && + AssemblyName.ReferenceMatchesDefinition(x.GetName(), y.GetName())); + } + + /// + /// 获取程序集的哈希代码 + /// + /// 程序集对象 + /// 程序集的哈希代码 + /// 当程序集为 null 时 + public override int GetHashCode(Assembly obj) + { + ArgumentNullException.ThrowIfNull(obj); + return obj.GetName().FullName.GetHashCode(StringComparison.OrdinalIgnoreCase); + } +} + +/// +/// NuGet 程序包信息记录 +/// 表示一个 NuGet 包的基本信息,包括包名和版本 +/// +public record NuGetPackage +{ + /// + /// NuGet 包名称 + /// + public string PackageName { get; init; } = string.Empty; + + /// + /// NuGet 包版本号 + /// + public string PackageVersion { get; init; } = string.Empty; + + /// + /// 获取包的完整标识符 + /// + /// 格式为 "PackageName@PackageVersion" 的字符串 + public override string ToString() + { + return $"{PackageName}@{PackageVersion}"; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Utils/RegexHelper.cs b/Admin.NET/Admin.NET.Core/Utils/RegexHelper.cs new file mode 100644 index 00000000..c08988fd --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Utils/RegexHelper.cs @@ -0,0 +1,254 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 字符验证帮助类 +/// +public static partial class RegexHelper +{ + /// + /// 验证输入字符串是否与模式字符串匹配,匹配返回 true + /// + [GeneratedRegex(@"^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$", RegexOptions.IgnoreCase)] + public static partial Regex GuidRegex(); + + /// + /// 验证电话号码是否符合格式 + /// + [GeneratedRegex(@"^(\d{3,4})\d{7,8}$", RegexOptions.IgnoreCase)] + public static partial Regex NumberTelRegex(); + + /// + /// 验证邮箱地址是否符合格式 + /// + [GeneratedRegex(@"^[A-Za-z0-9](([\.\-]?[a-zA-Z0-9]+)*)@([A-Za-z0-9]+)(([\.\-]?[a-zA-Z0-9]+)*)\.([A-Za-z]{2,})$", RegexOptions.IgnoreCase)] + public static partial Regex EmailRegex(); + + /// + /// 验证一个或多个数字 + /// + [GeneratedRegex(@"(\d+)", RegexOptions.IgnoreCase)] + public static partial Regex OneOrMoreNumbersRegex(); + + /// + /// 验证是否为整数 + /// + [GeneratedRegex(@"^(-){0,1}\d+$", RegexOptions.IgnoreCase)] + public static partial Regex IntRegex(); + + /// + /// 验证是否为数字 + /// + [GeneratedRegex(@"^[0-9]*$", RegexOptions.IgnoreCase)] + public static partial Regex NumberRegex(); + + /// + /// 验证是否为整数或小数 + /// + [GeneratedRegex(@"^[0-9]+\.{0,1}[0-9]{0,2}$", RegexOptions.IgnoreCase)] + public static partial Regex NumberIntOrDoubleRegex(); + + /// + /// 验证是否为N位数字 + /// + [GeneratedRegex(@"^\d{n}$", RegexOptions.IgnoreCase)] + public static partial Regex NumberSeveralNRegex(); + + /// + /// 验证是否为至少N位数字 + /// + [GeneratedRegex(@"^\d{n,}$", RegexOptions.IgnoreCase)] + public static partial Regex NumberSeveralAtLeastNRegex(); + + /// + /// 验证是否为M至N位数字 + /// + [GeneratedRegex(@"^\d{m,n}$", RegexOptions.IgnoreCase)] + public static partial Regex NumberSeveralMnRegex(); + + /// + /// 验证是否为零或非零开头的数字 + /// + [GeneratedRegex(@"^(0|[1-9] [0-9]*)$", RegexOptions.IgnoreCase)] + public static partial Regex NumberBeginZeroOrNotZeroRegex(); + + /// + /// 验证是否为2位小数的正实数 + /// + [GeneratedRegex(@"^[0-9]+(.[0-9]{2})?$", RegexOptions.IgnoreCase)] + public static partial Regex NumberPositiveRealTwoDoubleRegex(); + + /// + /// 验证是否为1-3位小数的正实数 + /// + [GeneratedRegex(@"^[0-9]+(.[0-9]{1,3})?$", RegexOptions.IgnoreCase)] + public static partial Regex NumberPositiveRealOneOrThreeDoubleRegex(); + + /// + /// 验证是否为非零的正整数 + /// + [GeneratedRegex(@"^\+?[1-9][0-9]*$", RegexOptions.IgnoreCase)] + public static partial Regex NumberPositiveIntNotZeroRegex(); + + /// + /// 验证是否为非零的负整数 + /// + [GeneratedRegex(@"^\-?[1-9][0-9]*$", RegexOptions.IgnoreCase)] + public static partial Regex NumberNegativeIntNotZeroRegex(); + + /// + /// 验证是否为字母 + /// + [GeneratedRegex(@"^[A-Za-z]+$", RegexOptions.IgnoreCase)] + public static partial Regex LetterRegex(); + + /// + /// 验证是否为大写字母 + /// + [GeneratedRegex(@"^[A-Z]+$", RegexOptions.IgnoreCase)] + public static partial Regex LetterCapitalRegex(); + + /// + /// 验证是否为小写字母 + /// + [GeneratedRegex(@"^[a-z]+$", RegexOptions.IgnoreCase)] + public static partial Regex LetterLowerRegex(); + + /// + /// 验证是否为数字或英文字母 + /// + [GeneratedRegex(@"^[A-Za-z0-9]+$", RegexOptions.IgnoreCase)] + public static partial Regex NumberOrLetterRegex(); + + /// + /// 验证字符串长度是否在限定范围内 + /// + [GeneratedRegex(@"[^\x00-\xff]", RegexOptions.IgnoreCase)] + public static partial Regex LengthStrRegex(); + + /// + /// 验证是否为长度为3的字符 + /// + [GeneratedRegex(@"^.{3}$", RegexOptions.IgnoreCase)] + public static partial Regex CharThreeRegex(); + + /// + /// 验证是否为邮政编码 + /// + [GeneratedRegex(@"^\d{6}$", RegexOptions.IgnoreCase)] + public static partial Regex PostCodeRegex(); + + /// + /// 验证是否含有特殊字符 + /// + [GeneratedRegex(@"[^%&',;=?$\x22]+", RegexOptions.IgnoreCase)] + public static partial Regex CharSpecialRegex(); + + /// + /// 验证是否包含汉字 + /// + [GeneratedRegex(@"^[\u4e00-\u9fa5]{0,}$", RegexOptions.IgnoreCase)] + public static partial Regex ContainChineseRegex(); + + /// + /// 验证是否为汉字 + /// + [GeneratedRegex(@"[一-龥]", RegexOptions.IgnoreCase, "zh-CN")] + public static partial Regex ChineseRegex(); + + /// + /// 验证是否为网址 + /// + [GeneratedRegex(@"^(((file|gopher|news|nntp|telnet|http|ftp|https|ftps|sftp)://)|(www\.))+(([a-zA-Z0-9\.-]+\.[a-zA-Z]{2,6})|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(/[a-zA-Z0-9\&%\./-~-]*)?$", RegexOptions.IgnoreCase)] + public static partial Regex UrlRegex(); + + /// + /// 验证是否为请求安全参数字符串 + /// + [GeneratedRegex(@"(?<=password=|passwd=|pwd=|secret=|token=)[^&]+", RegexOptions.IgnoreCase)] + public static partial Regex RequestSecurityParamsRegex(); + + /// + /// 验证是否为月份 + /// + [GeneratedRegex(@"^^(0?[1-9]|1[0-2])$", RegexOptions.IgnoreCase)] + public static partial Regex MonthRegex(); + + /// + /// 验证是否为日期 + /// + [GeneratedRegex(@"^((0?[1-9])|((1|2)[0-9])|30|31)$", RegexOptions.IgnoreCase)] + public static partial Regex DayRegex(); + + /// + /// 验证是否为IP地址 + /// + [GeneratedRegex(@"^(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])$", RegexOptions.IgnoreCase)] + public static partial Regex IpRegex(); + + /// + /// 验证是否为Cron表达式 + /// + [GeneratedRegex(@"^\\s*($|#|\\w+\\s*=|(\\?|\\*|(?:[0-5]?\\d)(?:(?:-|\\/|\\,)(?:[0-5]?\\d))?(?:,(?:[0-5]?\\d)(?:(?:-|\\/|\\,)(?:[0-5]?\\d))?)*)\\s+(\\?|\\*|(?:[0-5]?\\d)(?:(?:-|\\/|\\,)(?:[0-5]?\\d))?(?:,(?:[0-5]?\\d)(?:(?:-|\\/|\\,)(?:[0-5]?\\d))?)*)\\s+(\\?|\\*|(?:[01]?\\d|2[0-3])(?:(?:-|\\/|\\,)(?:[01]?\\d|2[0-3]))?(?:,(?:[01]?\\d|2[0-3])(?:(?:-|\\/|\\,)(?:[01]?\\d|2[0-3]))?)*)\\s+(\\?|\\*|(?:0?[1-9]|[12]\\d|3[01])(?:(?:-|\\/|\\,)(?:0?[1-9]|[12]\\d|3[01]))?(?:,(?:0?[1-9]|[12]\\d|3[01])(?:(?:-|\\/|\\,)(?:0?[1-9]|[12]\\d|3[01]))?)*)\\s+(\\?|\\*|(?:[1-9]|1[012])(?:(?:-|\\/|\\,)(?:[1-9]|1[012]))?(?:L|W)?(?:,(?:[1-9]|1[012])(?:(?:-|\\/|\\,)(?:[1-9]|1[012]))?(?:L|W)?)*|\\?|\\*|(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(?:(?:-)(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?(?:,(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(?:(?:-)(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?)*)\\s+(\\?|\\*|(?:[0-6])(?:(?:-|\\/|\\,|#)(?:[0-6]))?(?:L)?(?:,(?:[0-6])(?:(?:-|\\/|\\,|#)(?:[0-6]))?(?:L)?)*|\\?|\\*|(?:MON|TUE|WED|THU|FRI|SAT|SUN)(?:(?:-)(?:MON|TUE|WED|THU|FRI|SAT|SUN))?(?:,(?:MON|TUE|WED|THU|FRI|SAT|SUN)(?:(?:-)(?:MON|TUE|WED|THU|FRI|SAT|SUN))?)*)(|\\s)+(\\?|\\*|(?:|\\d{4})(?:(?:-|\\/|\\,)(?:|\\d{4}))?(?:,(?:|\\d{4})(?:(?:-|\\/|\\,)(?:|\\d{4}))?)*))$", RegexOptions.IgnoreCase)] + public static partial Regex CronRegex(); + + /// + /// 验证是否为 Windows 普通文件路径 + /// + /// + [GeneratedRegex(@"^(?:[a-zA-Z]:\\|\\\\)?(?:[^\\\/:*?""<>|\r\n]+\\)*[^\\\/:*?""<>|\r\n]+\\?$", RegexOptions.IgnoreCase)] + public static partial Regex WindowsPathRegex(); + + /// + /// 验证是否为 Linux 普通文件路径 + /// + /// + [GeneratedRegex(@"^(\/|\/?([^/\0]+(\/[^/\0]+)*\/?))$", RegexOptions.IgnoreCase)] + public static partial Regex LinuxPathRegex(); + + /// + /// 验证是否为虚拟文件路径 + /// + /// + [GeneratedRegex(@"^(~\/|\/)([a-zA-Z0-9_\-\.]+(\/[a-zA-Z0-9_\-\.]+)*)\/?$", RegexOptions.IgnoreCase)] + public static partial Regex VirtualPathRegex(); + + /// + /// 验证是否为嵌入文件路径 + /// + [GeneratedRegex(@"^embedded://(?[^/]+)/(?.*)$", RegexOptions.IgnoreCase)] + public static partial Regex EmbeddedPathRegex(); + + /// + /// 验证是否为内存文件路径 + /// + /// + [GeneratedRegex(@"^(?i:(?:memory|mem):\/\/).+$", RegexOptions.IgnoreCase)] + public static partial Regex MemoryPathRegex(); + + /// + /// 验证是否为 Html 标签 + /// + /// + [GeneratedRegex(@">([^<>]*)<", RegexOptions.IgnoreCase)] + public static partial Regex HtmlTagContentRegex(); + + /// + /// 验证是否文本分割为句子 + /// + /// + [GeneratedRegex(@"[^.!?。!?]+[.!?。!?]?", RegexOptions.IgnoreCase)] + public static partial Regex SentenceSplitterRegex(); + + /// + /// 验证是否为 Unicode 字符 + /// + /// + [GeneratedRegex(@"\\u([0-9A-Za-z]{4})")] + public static partial Regex UnicodeRegex(); +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Utils/RegularValidate.cs b/Admin.NET/Admin.NET.Core/Utils/RegularValidate.cs deleted file mode 100644 index b7c8bfe3..00000000 --- a/Admin.NET/Admin.NET.Core/Utils/RegularValidate.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 -// -// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 -// -// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! - -namespace Admin.NET.Core; - -/// -/// 正则校验 -/// -public static class RegularValidate -{ - /// - /// 验证密码规则 - /// - /// - /// - public static bool ValidatePassword(string password) - { - var regex = new Regex(@" -(?=.*[0-9]) #必须包含数字 -(?=.*[a-z]) #必须包含小写 -(?=.*[A-Z]) #必须包含大写 -(?=([\x21-\x7e]+)[^a-zA-Z0-9]) #必须包含特殊符号 -.{8,30} #至少8个字符,最多30个字符 -", RegexOptions.Multiline | RegexOptions.IgnorePatternWhitespace); - - //如果要求必须包含小写、大写字母,则上面的(?=.*[a-zA-Z]) 要改为: - /* - * (?=.*[a-z]) - * (?=.*[A-Z]) - */ - return regex.IsMatch(password); - } -} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Utils/Runtime/OSPlatformHelper.cs b/Admin.NET/Admin.NET.Core/Utils/Runtime/OSPlatformHelper.cs new file mode 100644 index 00000000..f1fd925f --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Utils/Runtime/OSPlatformHelper.cs @@ -0,0 +1,348 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 操作系统平台帮助类 +/// +public static class OsPlatformHelper +{ + /// + /// 是否为 Unix 系统 + /// + public static bool IsUnixSystem { get; } = GetIsUnixSystem(); + + /// + /// 操作系统平台类型 + /// + public static OSPlatform PlatformType { get; } = GetPlatformType(); + + /// + /// 是否 Windows 平台 + /// + public static bool IsWindows => PlatformType == OSPlatform.Windows; + + /// + /// 是否 Linux 平台 + /// + public static bool IsLinux => PlatformType == OSPlatform.Linux; + + /// + /// 是否 macOS 平台 + /// + public static bool IsMacOs => PlatformType == OSPlatform.OSX; + + /// + /// 是否 FreeBSD 平台 + /// + public static bool IsFreeBsd => PlatformType == OSPlatform.FreeBSD; + + /// + /// 处理器信息 + /// + /// + /// 推荐使用,默认有缓存 + /// + public static RuntimeInfo RuntimeInfos => Cache.Default.GetOrAdd("RuntimeInfos", _ => GetRuntimeInfo(), 60); + + /// + /// 收集运行时信息 + /// + public static RuntimeInfo GetRuntimeInfo() + { + try + { + var currentProcess = Process.GetCurrentProcess(); + var runtimeInfo = new RuntimeInfo + { + OsName = GetOperatingSystemName(), + OsDescription = RuntimeInformation.OSDescription, + OsVersion = Environment.OSVersion.Version.ToString(), + OsArchitecture = RuntimeInformation.OSArchitecture.ToString(), + ProcessArchitecture = RuntimeInformation.ProcessArchitecture.ToString(), + FrameworkDescription = RuntimeInformation.FrameworkDescription, + RuntimeVersion = Environment.Version.ToString(), + DatabaseType = App.GetOptions().ConnectionConfigs[0].DbType.ToString(), + Is64BitOperatingSystem = Environment.Is64BitOperatingSystem, + Is64BitProcess = Environment.Is64BitProcess, + IsInteractive = Environment.UserInteractive, + ProcessorCount = Environment.ProcessorCount, + SystemDirectory = Environment.SystemDirectory, + CurrentDirectory = Environment.CurrentDirectory, + MachineName = Environment.MachineName, + UserName = Environment.UserName, + UserDomainName = Environment.UserDomainName, + WorkingSet = Environment.WorkingSet, + SystemStartTime = DateTime.Now - TimeSpan.FromMilliseconds(Environment.TickCount64), + SystemUptime = TimeSpan.FromMilliseconds(Environment.TickCount64), + ProcessStartTime = currentProcess.StartTime, + ProcessUptime = DateTime.Now - currentProcess.StartTime, + ProcessId = currentProcess.Id, + ProcessName = currentProcess.ProcessName, + ClrVersion = Environment.Version.ToString(), + EnvironmentVariableCount = Environment.GetEnvironmentVariables().Count, + CommandLineArgs = Environment.GetCommandLineArgs() + }; + + return runtimeInfo; + } + catch (Exception ex) + { + // 异常时返回基本信息 + return new RuntimeInfo + { + OsName = "Unknown", + OsDescription = $"Error collecting info: {ex.Message}" + }; + } + } + + /// + /// 获取环境变量 + /// + /// 变量名 + /// 环境变量目标 + /// 环境变量值 + public static string? GetEnvironmentVariable(string variableName, EnvironmentVariableTarget target = EnvironmentVariableTarget.Process) + { + return Environment.GetEnvironmentVariable(variableName, target); + } + + /// + /// 获取所有环境变量 + /// + /// 环境变量目标 + /// 环境变量字典 + public static Dictionary GetAllEnvironmentVariables(EnvironmentVariableTarget target = EnvironmentVariableTarget.Process) + { + var variables = Environment.GetEnvironmentVariables(target); + return variables.Cast() + .ToDictionary(entry => entry.Key.ToString()!, entry => entry.Value?.ToString()); + } + + /// + /// 检查特定的操作系统版本 + /// + /// 最小版本要求 + /// 是否满足版本要求 + public static bool CheckOsVersion(Version minimumVersion) + { + try + { + return Environment.OSVersion.Version >= minimumVersion; + } + catch + { + return false; + } + } + + /// + /// 获取.NET运行时位置 + /// + /// 运行时路径 + public static string GetRuntimeLocation() + { + try + { + return Path.GetDirectoryName(typeof(object).Assembly.Location) ?? string.Empty; + } + catch + { + return string.Empty; + } + } + + /// + /// 判断当前操作系统是否为 Unix 系统 + /// + /// + public static bool GetIsUnixSystem() + { + return RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || + RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || + RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD); + } + + /// + /// 获取操作系统平台类型 + /// + private static OSPlatform GetPlatformType() + { + return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? OSPlatform.Windows + : RuntimeInformation.IsOSPlatform(OSPlatform.Linux) + ? OSPlatform.Linux + : RuntimeInformation.IsOSPlatform(OSPlatform.OSX) + ? OSPlatform.OSX + : RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD) + ? OSPlatform.FreeBSD + : OSPlatform.Create("Unknown"); + } + + /// + /// 获取操作系统名称 + /// + private static string GetOperatingSystemName() + { + var platformType = GetPlatformType(); + return platformType.ToString() switch + { + "Windows" => "Windows", + "Linux" => "Linux", + "OSX" => "MacOS", + "FreeBSD" => "FreeBSD", + _ => "Unknown" + }; + } +} + +/// +/// 系统运行时信息 +/// +public record RuntimeInfo +{ + /// + /// 操作系统名称 + /// + public string OsName { get; set; } = string.Empty; + + /// + /// 操作系统描述 + /// + public string OsDescription { get; set; } = string.Empty; + + /// + /// 操作系统版本 + /// + public string OsVersion { get; set; } = string.Empty; + + /// + /// 操作系统架构 + /// + public string OsArchitecture { get; set; } = string.Empty; + + /// + /// 进程架构 + /// + public string ProcessArchitecture { get; set; } = string.Empty; + + /// + /// 运行时框架描述 + /// + public string FrameworkDescription { get; set; } = string.Empty; + + /// + /// 运行时版本 + /// + public string RuntimeVersion { get; set; } = string.Empty; + + /// + /// 数据库类型 + /// + public string DatabaseType { get; set; } = string.Empty; + + /// + /// 是否64位操作系统 + /// + public bool Is64BitOperatingSystem { get; set; } + + /// + /// 是否64位进程 + /// + public bool Is64BitProcess { get; set; } + + /// + /// 是否交互模式 + /// + public bool IsInteractive { get; set; } + + /// + /// 交互模式描述 + /// + public string InteractiveMode => IsInteractive ? "交互运行" : "非交互运行"; + + /// + /// 处理器数量 + /// + public int ProcessorCount { get; set; } + + /// + /// 系统目录 + /// + public string SystemDirectory { get; set; } = string.Empty; + + /// + /// 当前目录 + /// + public string CurrentDirectory { get; set; } = string.Empty; + + /// + /// 机器名称 + /// + public string MachineName { get; set; } = string.Empty; + + /// + /// 用户名 + /// + public string UserName { get; set; } = string.Empty; + + /// + /// 用户域名 + /// + public string UserDomainName { get; set; } = string.Empty; + + /// + /// 工作集大小(字节) + /// + public long WorkingSet { get; set; } + + /// + /// 系统启动时间 + /// + public DateTime SystemStartTime { get; set; } + + /// + /// 系统运行时间 + /// + public TimeSpan SystemUptime { get; set; } + + /// + /// 进程启动时间 + /// + public DateTime ProcessStartTime { get; set; } + + /// + /// 进程运行时间 + /// + public TimeSpan ProcessUptime { get; set; } + + /// + /// 进程ID + /// + public int ProcessId { get; set; } + + /// + /// 进程名称 + /// + public string ProcessName { get; set; } = string.Empty; + + /// + /// CLR版本 + /// + public string ClrVersion { get; set; } = string.Empty; + + /// + /// 环境变量数量 + /// + public int EnvironmentVariableCount { get; set; } + + /// + /// 命令行参数 + /// + public string[] CommandLineArgs { get; set; } = []; +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Utils/Runtime/RunningTimeHelper.cs b/Admin.NET/Admin.NET.Core/Utils/Runtime/RunningTimeHelper.cs new file mode 100644 index 00000000..7592d387 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Utils/Runtime/RunningTimeHelper.cs @@ -0,0 +1,95 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 系统运行时间 +/// +public static class RunningTimeHelper +{ + /// + /// 处理器信息 + /// + /// + /// 推荐使用,默认有缓存 + /// + public static string RunningTime => Cache.Default.GetOrAdd("RunningTime", _ => GetRunningTime(), 60); + + /// + /// 获取系统运行时间 + /// + public static string GetRunningTime() + { + var runTime = string.Empty; + + try + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + var output = ShellHelper.Bash("uptime -s").Trim(); + var timeSpan = DateTime.Now - output.Trim().ParseToDateTime(); + runTime = timeSpan.FormatTimeSpanToString(); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + var output = ShellHelper.Bash("uptime | tail -n -1").Trim(); + // 提取运行时间部分 + var startIndex = output.IndexOf("up ", StringComparison.Ordinal) + 3; + var endIndex = output.IndexOf(" user", StringComparison.Ordinal); + var uptime = output[startIndex..endIndex].Trim(); + // 解析运行时间并转换为标准格式 + var uptimeSpan = ParseUptime(uptime); + runTime = uptimeSpan.FormatTimeSpanToString(); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + var output = ShellHelper.Cmd("wmic", "OS get LastBootUpTime /Value").Trim(); + var outputArr = output.Split('='); + if (outputArr.Length != 0) + { + var timeSpan = DateTime.Now - outputArr[1].Split('.')[0].FormatStringToDate(); + runTime = timeSpan.FormatTimeSpanToString(); + } + } + } + catch (Exception ex) + { + Log.Error("获取系统运行时间出错," + ex.Message); + } + + return runTime; + } + + /// + /// 解析运行时间 + /// + /// + /// + private static TimeSpan ParseUptime(string uptime) + { + var parts = uptime.Split(','); + int days = 0, hours = 0, minutes = 0; + + foreach (var part in parts) + { + var trimmedPart = part.Trim(); + + if (trimmedPart.Contains("day")) + { + days = int.Parse(trimmedPart.Split(' ')[0]); + } + else if (trimmedPart.Contains(':')) + { + var timeParts = trimmedPart.Split(':'); + hours = int.Parse(timeParts[0]); + minutes = int.Parse(timeParts[1]); + } + } + + return new TimeSpan(days, hours, minutes, 0); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Utils/Runtime/RuntimeInfoManger.cs b/Admin.NET/Admin.NET.Core/Utils/Runtime/RuntimeInfoManger.cs new file mode 100644 index 00000000..8737de7b --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Utils/Runtime/RuntimeInfoManger.cs @@ -0,0 +1,45 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 运行时信息管理器 +/// +public static class RuntimeInfoManger +{ + /// + /// 获取当前运行时信息 + /// + /// + /// 推荐使用,默认有缓存 + /// + /// 运行时信息 + public static SystemRuntimeInfo GetSystemRuntimeInfo() + { + return new SystemRuntimeInfo + { + RuntimeInfo = OsPlatformHelper.RuntimeInfos, + RunningTime = RunningTimeHelper.RunningTime + }; + } +} + +/// +/// 系统运行时信息 +/// +public class SystemRuntimeInfo +{ + /// + /// 运行时信息 + /// + public RuntimeInfo RuntimeInfo { get; set; } = new(); + + /// + /// 运行时间 + /// + public string RunningTime { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Utils/Runtime/RuntimeMonitor.cs b/Admin.NET/Admin.NET.Core/Utils/Runtime/RuntimeMonitor.cs new file mode 100644 index 00000000..467b9624 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Utils/Runtime/RuntimeMonitor.cs @@ -0,0 +1,545 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 运行时监控器 +/// +public class RuntimeMonitor : IDisposable +{ + private readonly Process _currentProcess; + private readonly ConcurrentQueue _snapshots; + private readonly Timer? _monitorTimer; + private readonly object _lockObject = new(); + private readonly int _maxSnapshotCount; + private volatile bool _isDisposed; + private DateTime _lastCpuTime; + private TimeSpan _lastTotalProcessorTime; + + /// + /// 构造函数 + /// + /// 监控间隔,默认5秒 + /// 最大快照数量,默认1000 + public RuntimeMonitor(TimeSpan? monitorInterval = null, int maxSnapshotCount = 1000) + { + _currentProcess = Process.GetCurrentProcess(); + _snapshots = new ConcurrentQueue(); + _maxSnapshotCount = maxSnapshotCount; + MonitorInterval = monitorInterval ?? TimeSpan.FromSeconds(5); + + // 初始化CPU时间基准 + _lastCpuTime = DateTime.UtcNow; + _lastTotalProcessorTime = _currentProcess.TotalProcessorTime; + + // 创建定时器但不立即启动 + _monitorTimer = new Timer(CollectSnapshot, null, Timeout.Infinite, Timeout.Infinite); + } + + /// + /// 监控间隔 + /// + public TimeSpan MonitorInterval { get; } + + /// + /// 是否正在监控 + /// + public bool IsMonitoring { get; private set; } + + /// + /// 快照历史记录数量 + /// + public int SnapshotCount => _snapshots.Count; + + /// + /// 监控开始时间 + /// + public DateTime? MonitorStartTime { get; private set; } + + /// + /// 强制垃圾回收 + /// + /// GC代数,-1表示全部代 + /// GC模式 + public static void ForceGarbageCollection(int generation = -1, GCCollectionMode mode = GCCollectionMode.Default) + { + if (generation == -1) + { + GC.Collect(); + } + else + { + GC.Collect(generation, mode); + } + + GC.WaitForPendingFinalizers(); + GC.Collect(); + } + + /// + /// 获取内存压力信息 + /// + /// 内存压力信息 + public static string GetMemoryPressureInfo() + { + var info = GC.GetGCMemoryInfo(); + return $"总内存: {info.TotalAvailableMemoryBytes / 1024 / 1024} MB, " + + $"高内存负载阈值: {info.HighMemoryLoadThresholdBytes / 1024 / 1024} MB, " + + $"堆大小: {info.HeapSizeBytes / 1024 / 1024} MB, " + + $"内存负载: {info.MemoryLoadBytes / 1024 / 1024} MB"; + } + + /// + /// 开始监控 + /// + public void StartMonitoring() + { + if (IsMonitoring || _isDisposed) + { + return; + } + + lock (_lockObject) + { + if (IsMonitoring || _isDisposed) + { + return; + } + + IsMonitoring = true; + MonitorStartTime = DateTime.Now; + + // 立即收集一次快照 + CollectSnapshot(null); + + // 启动定时器 + _monitorTimer?.Change(MonitorInterval, MonitorInterval); + } + } + + /// + /// 停止监控 + /// + public void StopMonitoring() + { + if (!IsMonitoring) + { + return; + } + + lock (_lockObject) + { + if (!IsMonitoring) + { + return; + } + + IsMonitoring = false; + _monitorTimer?.Change(Timeout.Infinite, Timeout.Infinite); + } + } + + /// + /// 获取当前性能快照 + /// + /// 当前性能快照 + public PerformanceSnapshot GetCurrentSnapshot() + { + return CreateSnapshot(); + } + + /// + /// 获取最新的性能快照 + /// + /// 最新的性能快照,如果没有则返回null + public PerformanceSnapshot? GetLatestSnapshot() + { + return _snapshots.TryPeek(out var snapshot) ? snapshot : null; + } + + /// + /// 获取所有性能快照 + /// + /// 性能快照列表 + public List GetAllSnapshots() + { + return [.. _snapshots]; + } + + /// + /// 获取指定时间范围内的快照 + /// + /// 时间范围 + /// 快照列表 + public List GetSnapshotsInTimeRange(TimeSpan timeRange) + { + var cutoffTime = DateTime.Now - timeRange; + return [.. _snapshots.Where(s => s.Timestamp >= cutoffTime)]; + } + + /// + /// 分析性能趋势 + /// + /// 计数器类型 + /// 分析时间范围,null表示分析所有数据 + /// 性能趋势分析结果 + public PerformanceTrend AnalyzeTrend(PerformanceCounterType counterType, TimeSpan? timeRange = null) + { + var snapshots = timeRange.HasValue + ? GetSnapshotsInTimeRange(timeRange.Value) + : GetAllSnapshots(); + + if (snapshots.Count == 0) + { + return new PerformanceTrend + { + CounterType = counterType, + SampleCount = 0, + AnalysisTimespan = timeRange ?? TimeSpan.Zero + }; + } + + var values = snapshots.Select(s => GetCounterValue(s, counterType)).ToList(); + + var trend = new PerformanceTrend + { + CounterType = counterType, + CurrentValue = values.LastOrDefault(), + AverageValue = values.Average(), + MinValue = values.Min(), + MaxValue = values.Max(), + SampleCount = values.Count, + AnalysisTimespan = timeRange ?? (snapshots.Last().Timestamp - snapshots.First().Timestamp) + }; + + // 计算标准差 + var variance = values.Select(v => Math.Pow(v - trend.AverageValue, 2)).Average(); + trend.StandardDeviation = Math.Sqrt(variance); + + // 计算趋势(简单线性回归的斜率) + if (values.Count >= 2) + { + var n = values.Count; + var sumX = Enumerable.Range(0, n).Sum(); + var sumY = values.Sum(); + var sumXy = values.Select((v, i) => i * v).Sum(); + var sumX2 = Enumerable.Range(0, n).Select(i => i * i).Sum(); + + trend.Trend = ((n * sumXy) - (sumX * sumY)) / ((n * sumX2) - (sumX * sumX)); + } + + return trend; + } + + /// + /// 清空所有快照 + /// + public void ClearSnapshots() + { + while (_snapshots.TryDequeue(out _)) { } + } + + /// + /// 获取性能摘要报告 + /// + /// 性能摘要字符串 + public string GetPerformanceSummary() + { + var current = GetCurrentSnapshot(); + var cpuTrend = AnalyzeTrend(PerformanceCounterType.CpuUsage, TimeSpan.FromMinutes(5)); + var memoryTrend = AnalyzeTrend(PerformanceCounterType.MemoryUsage, TimeSpan.FromMinutes(5)); + + return $"运行时性能摘要:\n" + + $" CPU使用率: {current.CpuUsage:F1}% (5分钟平均: {cpuTrend.AverageValue:F1}%)\n" + + $" 内存使用: {current.MemoryUsage / 1024 / 1024} MB (托管内存: {current.ManagedMemory / 1024 / 1024} MB)\n" + + $" GC收集: Gen0={current.GcGen0Collections}, Gen1={current.GcGen1Collections}, Gen2={current.GcGen2Collections}\n" + + $" 线程数: {current.ThreadCount}, 句柄数: {current.HandleCount}\n" + + $" 运行时间: {current.ProcessUptime:dd\\.hh\\:mm\\:ss}\n" + + $" 监控状态: {(IsMonitoring ? "运行中" : "已停止")}, 快照数: {SnapshotCount}"; + } + + /// + /// 释放资源 + /// + public void Dispose() + { + if (_isDisposed) + { + return; + } + + _isDisposed = true; + + StopMonitoring(); + _monitorTimer?.Dispose(); + _currentProcess?.Dispose(); + + GC.SuppressFinalize(this); + } + + /// + /// 获取计数器值 + /// + /// 性能快照 + /// 计数器类型 + /// 计数器值 + private static double GetCounterValue(PerformanceSnapshot snapshot, PerformanceCounterType counterType) + { + return counterType switch + { + PerformanceCounterType.CpuUsage => snapshot.CpuUsage, + PerformanceCounterType.MemoryUsage => snapshot.MemoryUsage, + PerformanceCounterType.GcCollections => snapshot.GcGen0Collections + snapshot.GcGen1Collections + snapshot.GcGen2Collections, + PerformanceCounterType.ThreadCount => snapshot.ThreadCount, + PerformanceCounterType.HandleCount => snapshot.HandleCount, + _ => 0 + }; + } + + /// + /// 收集性能快照 + /// + /// 定时器状态 + private void CollectSnapshot(object? state) + { + if (_isDisposed) + { + return; + } + + try + { + var snapshot = CreateSnapshot(); + _snapshots.Enqueue(snapshot); + + // 限制快照数量 + while (_snapshots.Count > _maxSnapshotCount) + { + _snapshots.TryDequeue(out _); + } + } + catch (Exception ex) + { + // 记录异常但继续监控 + Debug.WriteLine($"Failed to collect performance snapshot: {ex.Message}"); + } + } + + /// + /// 创建性能快照 + /// + /// 性能快照 + private PerformanceSnapshot CreateSnapshot() + { + _currentProcess.Refresh(); + + // 计算CPU使用率 + var currentTime = DateTime.UtcNow; + var currentTotalProcessorTime = _currentProcess.TotalProcessorTime; + + var cpuUsage = 0.0; + var timeDelta = currentTime - _lastCpuTime; + var cpuTimeDelta = currentTotalProcessorTime - _lastTotalProcessorTime; + + if (timeDelta.TotalMilliseconds > 0) + { + cpuUsage = cpuTimeDelta.TotalMilliseconds / (Environment.ProcessorCount * timeDelta.TotalMilliseconds) * 100; + cpuUsage = Math.Max(0, Math.Min(100, cpuUsage)); // 限制在0-100%范围内 + } + + _lastCpuTime = currentTime; + _lastTotalProcessorTime = currentTotalProcessorTime; + + return new PerformanceSnapshot + { + Timestamp = currentTime, + CpuUsage = cpuUsage, + MemoryUsage = _currentProcess.WorkingSet64, + PrivateMemorySize = _currentProcess.PrivateMemorySize64, + VirtualMemorySize = _currentProcess.VirtualMemorySize64, + WorkingSet = _currentProcess.WorkingSet64, + GcGen0Collections = GC.CollectionCount(0), + GcGen1Collections = GC.CollectionCount(1), + GcGen2Collections = GC.CollectionCount(2), + ManagedMemory = GC.GetTotalMemory(false), + ThreadCount = _currentProcess.Threads.Count, + HandleCount = _currentProcess.HandleCount, + UserProcessorTime = _currentProcess.UserProcessorTime, + PrivilegedProcessorTime = _currentProcess.PrivilegedProcessorTime, + TotalProcessorTime = _currentProcess.TotalProcessorTime, + ProcessUptime = DateTime.Now - _currentProcess.StartTime + }; + } +} + +/// +/// 性能指标快照 +/// +public record PerformanceSnapshot +{ + /// + /// 快照时间 + /// + public DateTime Timestamp { get; set; } + + /// + /// CPU使用率百分比 + /// + public double CpuUsage { get; set; } + + /// + /// 内存使用量(字节) + /// + public long MemoryUsage { get; set; } + + /// + /// 私有内存使用量(字节) + /// + public long PrivateMemorySize { get; set; } + + /// + /// 虚拟内存使用量(字节) + /// + public long VirtualMemorySize { get; set; } + + /// + /// 工作集大小(字节) + /// + public long WorkingSet { get; set; } + + /// + /// GC代0收集次数 + /// + public int GcGen0Collections { get; set; } + + /// + /// GC代1收集次数 + /// + public int GcGen1Collections { get; set; } + + /// + /// GC代2收集次数 + /// + public int GcGen2Collections { get; set; } + + /// + /// 托管内存使用量(字节) + /// + public long ManagedMemory { get; set; } + + /// + /// 线程数量 + /// + public int ThreadCount { get; set; } + + /// + /// 句柄数量 + /// + public int HandleCount { get; set; } + + /// + /// 用户处理器时间 + /// + public TimeSpan UserProcessorTime { get; set; } + + /// + /// 特权处理器时间 + /// + public TimeSpan PrivilegedProcessorTime { get; set; } + + /// + /// 总处理器时间 + /// + public TimeSpan TotalProcessorTime { get; set; } + + /// + /// 进程运行时间 + /// + public TimeSpan ProcessUptime { get; set; } +} + +/// +/// 性能趋势分析结果 +/// +public record PerformanceTrend +{ + /// + /// 计数器类型 + /// + public PerformanceCounterType CounterType { get; set; } + + /// + /// 当前值 + /// + public double CurrentValue { get; set; } + + /// + /// 平均值 + /// + public double AverageValue { get; set; } + + /// + /// 最小值 + /// + public double MinValue { get; set; } + + /// + /// 最大值 + /// + public double MaxValue { get; set; } + + /// + /// 变化趋势(正数表示上升,负数表示下降) + /// + public double Trend { get; set; } + + /// + /// 标准差 + /// + public double StandardDeviation { get; set; } + + /// + /// 样本数量 + /// + public int SampleCount { get; set; } + + /// + /// 分析时间范围 + /// + public TimeSpan AnalysisTimespan { get; set; } +} + +/// +/// 性能计数器类型枚举 +/// +public enum PerformanceCounterType +{ + /// + /// CPU使用率 + /// + CpuUsage = 1, + + /// + /// 内存使用量 + /// + MemoryUsage = 2, + + /// + /// GC收集次数 + /// + GcCollections = 3, + + /// + /// 线程数量 + /// + ThreadCount = 4, + + /// + /// 句柄数量 + /// + HandleCount = 5 +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Utils/SSHHelper.cs b/Admin.NET/Admin.NET.Core/Utils/SSHHelper.cs index 82050436..45910113 100644 --- a/Admin.NET/Admin.NET.Core/Utils/SSHHelper.cs +++ b/Admin.NET/Admin.NET.Core/Utils/SSHHelper.cs @@ -61,10 +61,8 @@ namespace Admin.NET.Core { Connect(); - using (Stream fileStream = File.OpenWrite(localFileName)) - { - _sftp.DownloadFile(ftpFileName, fileStream); - } + using Stream fileStream = File.OpenWrite(localFileName); + _sftp.DownloadFile(ftpFileName, fileStream); } /// @@ -140,10 +138,8 @@ namespace Admin.NET.Core var dir = Path.GetDirectoryName(ftpFileName); CreateDir(_sftp, dir); - using (var fileStream = new FileStream(localFileName, FileMode.Open)) - { - _sftp.UploadFile(fileStream, ftpFileName); - } + using var fileStream = new FileStream(localFileName, FileMode.Open); + _sftp.UploadFile(fileStream, ftpFileName); } /// @@ -187,7 +183,7 @@ namespace Admin.NET.Core if (sftp.Exists(dir)) return; - var index = dir.LastIndexOfAny(new char[] { '/', '\\' }); + var index = dir.LastIndexOfAny(['/', '\\']); if (index > 0) { var p = dir[..index]; diff --git a/Admin.NET/Admin.NET.Core/Utils/AdminNETResultProvider.cs b/Admin.NET/Admin.NET.Core/Utils/System/AdminNETResultProvider.cs similarity index 100% rename from Admin.NET/Admin.NET.Core/Utils/AdminNETResultProvider.cs rename to Admin.NET/Admin.NET.Core/Utils/System/AdminNETResultProvider.cs diff --git a/Admin.NET/Admin.NET.Core/Utils/BaseFilter.cs b/Admin.NET/Admin.NET.Core/Utils/System/BaseFilter.cs similarity index 100% rename from Admin.NET/Admin.NET.Core/Utils/BaseFilter.cs rename to Admin.NET/Admin.NET.Core/Utils/System/BaseFilter.cs diff --git a/Admin.NET/Admin.NET.Core/Utils/BaseImportInput.cs b/Admin.NET/Admin.NET.Core/Utils/System/BaseImportInput.cs similarity index 100% rename from Admin.NET/Admin.NET.Core/Utils/BaseImportInput.cs rename to Admin.NET/Admin.NET.Core/Utils/System/BaseImportInput.cs diff --git a/Admin.NET/Admin.NET.Core/Utils/BaseInput.cs b/Admin.NET/Admin.NET.Core/Utils/System/BaseInput.cs similarity index 100% rename from Admin.NET/Admin.NET.Core/Utils/BaseInput.cs rename to Admin.NET/Admin.NET.Core/Utils/System/BaseInput.cs diff --git a/Admin.NET/Admin.NET.Core/Utils/BaseOutput.cs b/Admin.NET/Admin.NET.Core/Utils/System/BaseOutput.cs similarity index 100% rename from Admin.NET/Admin.NET.Core/Utils/BaseOutput.cs rename to Admin.NET/Admin.NET.Core/Utils/System/BaseOutput.cs diff --git a/Admin.NET/Admin.NET.Core/Utils/BasePageInput.cs b/Admin.NET/Admin.NET.Core/Utils/System/BasePageInput.cs similarity index 100% rename from Admin.NET/Admin.NET.Core/Utils/BasePageInput.cs rename to Admin.NET/Admin.NET.Core/Utils/System/BasePageInput.cs diff --git a/Admin.NET/Admin.NET.Core/Utils/BaseStatusInput.cs b/Admin.NET/Admin.NET.Core/Utils/System/BaseStatusInput.cs similarity index 100% rename from Admin.NET/Admin.NET.Core/Utils/BaseStatusInput.cs rename to Admin.NET/Admin.NET.Core/Utils/System/BaseStatusInput.cs diff --git a/Admin.NET/Admin.NET.Core/Utils/CodeGenUtil.cs b/Admin.NET/Admin.NET.Core/Utils/System/CodeGenHelper.cs similarity index 77% rename from Admin.NET/Admin.NET.Core/Utils/CodeGenUtil.cs rename to Admin.NET/Admin.NET.Core/Utils/System/CodeGenHelper.cs index 5b2fc195..e905ae50 100644 --- a/Admin.NET/Admin.NET.Core/Utils/CodeGenUtil.cs +++ b/Admin.NET/Admin.NET.Core/Utils/System/CodeGenHelper.cs @@ -11,7 +11,7 @@ namespace Admin.NET.Core; /// /// 代码生成帮助类 /// -public static class CodeGenUtil +public static class CodeGenHelper { /// /// 转换大驼峰法命名 @@ -34,7 +34,7 @@ public static class CodeGenUtil } else { - var propertyName = dbColumnNames.FirstOrDefault(c => c.ToLower() == columnName.ToLower()); + var propertyName = dbColumnNames.FirstOrDefault(u => u.ToLower() == columnName.ToLower()); if (!string.IsNullOrEmpty(propertyName)) { columnName = propertyName; @@ -134,85 +134,22 @@ public static class CodeGenUtil // PostgreSQL数据类型对应的字段类型 public static string ConvertDataType_PostgreSQL(string dataType) { - switch (dataType) + return dataType switch { - case "int2": - case "smallint": - return "Int16"; - - case "int4": - case "integer": - return "int"; - - case "int8": - case "bigint": - return "long"; - - case "float4": - case "real": - return "float"; - - case "float8": - case "double precision": - return "double"; - - case "numeric": - case "decimal": - case "path": - case "point": - case "polygon": - case "interval": - case "lseg": - case "macaddr": - case "money": - return "decimal"; - - case "boolean": - case "bool": - case "box": - case "bytea": - return "bool"; - - case "varchar": - case "character varying": - case "geometry": - case "name": - case "text": - case "char": - case "character": - case "cidr": - case "circle": - case "tsquery": - case "tsvector": - case "txid_snapshot": - case "xml": - case "json": - return "string"; - - case "uuid": - return "Guid"; - - case "timestamp": - case "timestamp with time zone": - case "timestamptz": - case "timestamp without time zone": - case "date": - case "time": - case "time with time zone": - case "timetz": - case "time without time zone": - return "DateTime"; - - case "bit": - case "bit varying": - return "byte[]"; - - case "varbit": - return "byte"; - - default: - return "object"; - } + "int2" or "smallint" => "Int16", + "int4" or "integer" => "int", + "int8" or "bigint" => "long", + "float4" or "real" => "float", + "float8" or "double precision" => "double", + "numeric" or "decimal" or "path" or "point" or "polygon" or "interval" or "lseg" or "macaddr" or "money" => "decimal", + "boolean" or "bool" or "box" or "bytea" => "bool", + "varchar" or "character varying" or "geometry" or "name" or "text" or "char" or "character" or "cidr" or "circle" or "tsquery" or "tsvector" or "txid_snapshot" or "xml" or "json" => "string", + "uuid" => "Guid", + "timestamp" or "timestamp with time zone" or "timestamptz" or "timestamp without time zone" or "date" or "time" or "time with time zone" or "timetz" or "time without time zone" => "DateTime", + "bit" or "bit varying" => "byte[]", + "varbit" => "byte", + _ => "object", + }; } // 默认数据类型 diff --git a/Admin.NET/Admin.NET.Core/Utils/NewtonsoftJsonSerializerProvider.cs b/Admin.NET/Admin.NET.Core/Utils/System/NewtonsoftJsonSerializerProvider.cs similarity index 100% rename from Admin.NET/Admin.NET.Core/Utils/NewtonsoftJsonSerializerProvider.cs rename to Admin.NET/Admin.NET.Core/Utils/System/NewtonsoftJsonSerializerProvider.cs diff --git a/Admin.NET/Admin.NET.Core/Utils/TripleDES.cs b/Admin.NET/Admin.NET.Core/Utils/TripleDES.cs deleted file mode 100644 index fedec3c9..00000000 --- a/Admin.NET/Admin.NET.Core/Utils/TripleDES.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 -// -// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 -// -// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! - -using System.Security.Cryptography; - -namespace Admin.NET.Core; - -/// -/// 3DES文件加解密 -/// -public static class TripleDES -{ - /// - /// 加密文件 - /// - /// 待加密文件路径 - /// 加密后的文件路径 - /// 密码 (24位长度) - [Obsolete] - public static void EncryptFile(string inputFile, string outputFile, string password) - { - using var ties = new TripleDESCryptoServiceProvider(); - ties.Mode = CipherMode.ECB; - ties.Padding = PaddingMode.PKCS7; - ties.Key = Encoding.UTF8.GetBytes(password); - using var inputFileStream = new FileStream(inputFile, FileMode.Open); - using var encryptedFileStream = new FileStream(outputFile, FileMode.Create); - using var cryptoStream = new CryptoStream(encryptedFileStream, ties.CreateEncryptor(), CryptoStreamMode.Write); - inputFileStream.CopyTo(cryptoStream); - } - - /// - /// 加密文件 - /// - /// 加密的文件路径 - /// 解密后的文件路径 - /// 密码 (24位长度) - [Obsolete] - public static void DecryptFile(string inputFile, string outputFile, string password) - { - using var ties = new TripleDESCryptoServiceProvider(); - ties.Mode = CipherMode.ECB; - ties.Padding = PaddingMode.PKCS7; - ties.Key = Encoding.UTF8.GetBytes(password); - using var encryptedFileStream = new FileStream(inputFile, FileMode.Open); - using var decryptedFileStream = new FileStream(outputFile, FileMode.Create); - using var cryptoStream = new CryptoStream(encryptedFileStream, ties.CreateDecryptor(), CryptoStreamMode.Read); - cryptoStream.CopyTo(decryptedFileStream); - } -} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Utils/VerifyFileExtensionName.cs b/Admin.NET/Admin.NET.Core/Utils/VerifyFileExtensionName.cs index 2c8e8bee..e7c40ebe 100644 --- a/Admin.NET/Admin.NET.Core/Utils/VerifyFileExtensionName.cs +++ b/Admin.NET/Admin.NET.Core/Utils/VerifyFileExtensionName.cs @@ -82,10 +82,10 @@ public static class VerifyFileExtensionName { foreach (var ext in dics.Value.Split(",")) { - if (!ExtDics.ContainsKey(ext)) - ExtDics.Add(ext, new HashSet { dics.Key.Length / 2 }); + if (!ExtDics.TryGetValue(ext, out HashSet value)) + ExtDics.Add(ext, [dics.Key.Length / 2]); else - ExtDics[ext].Add(dics.Key.Length / 2); + value.Add(dics.Key.Length / 2); } } } diff --git a/Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/DingTalkService.cs b/Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/DingTalkService.cs index 7c0847fe..31b9fd87 100644 --- a/Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/DingTalkService.cs +++ b/Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/DingTalkService.cs @@ -68,6 +68,7 @@ public class DingTalkService : IDynamicApiController, IScoped /// /// [DisplayName("给指定用户发送钉钉互动卡片")] + [Obsolete] public async Task DingTalkSendInteractiveCards(string token, DingTalkSendInteractiveCardsInput input) { return await _dingTalkApi.DingTalkSendInteractiveCards(token, input); diff --git a/Admin.NET/Plugins/Admin.NET.Plugin.GoView/Service/GoViewSys/GoViewSysService.cs b/Admin.NET/Plugins/Admin.NET.Plugin.GoView/Service/GoViewSys/GoViewSysService.cs index 2c1359c7..53c8b31b 100644 --- a/Admin.NET/Plugins/Admin.NET.Plugin.GoView/Service/GoViewSys/GoViewSysService.cs +++ b/Admin.NET/Plugins/Admin.NET.Plugin.GoView/Service/GoViewSys/GoViewSysService.cs @@ -46,7 +46,7 @@ public class GoViewSysService : IDynamicApiController tenant.Captcha = false; _sysCacheService.Set(CacheConst.KeyTenant, tenantList); - input.Password = CryptogramUtil.SM2Encrypt(input.Password); + input.Password = CryptogramHelper.SM2Encrypt(input.Password); var loginResult = await _sysAuthService.Login(new LoginInput() { Account = input.Username, diff --git a/Web/src/api-services/apis/sys-database-api.ts b/Web/src/api-services/apis/sys-database-api.ts index 8e8da888..cb22486f 100644 --- a/Web/src/api-services/apis/sys-database-api.ts +++ b/Web/src/api-services/apis/sys-database-api.ts @@ -32,6 +32,7 @@ import { DeleteDbColumnInput } from '../models'; import { DeleteDbTableInput } from '../models'; import { InitSeedDataInput } from '../models'; import { InitTableInput } from '../models'; +import { MoveDbColumnInput } from '../models'; import { UpdateDbColumnInput } from '../models'; import { UpdateDbTableInput } from '../models'; /** @@ -729,6 +730,54 @@ export const SysDatabaseApiAxiosParamCreator = function (configuration?: Configu options: localVarRequestOptions, }; }, + /** + * + * @summary 移动列顺序 🔖 + * @param {MoveDbColumnInput} [body] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + apiSysDatabaseMoveColumnPost: async (body?: MoveDbColumnInput, options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/api/sysDatabase/moveColumn`; + // 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: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication Bearer required + // http bearer authentication required + if (configuration && configuration.accessToken) { + const accessToken = typeof configuration.accessToken === 'function' + ? await configuration.accessToken() + : await configuration.accessToken; + localVarHeaderParameter["Authorization"] = "Bearer " + accessToken; + } + + localVarHeaderParameter['Content-Type'] = 'application/json-patch+json'; + + 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}; + const needsSerialization = (typeof body !== "string") || localVarRequestOptions.headers['Content-Type'] === 'application/json'; + localVarRequestOptions.data = needsSerialization ? JSON.stringify(body !== undefined ? body : {}) : (body || ""); + + return { + url: localVarUrlObj.pathname + localVarUrlObj.search + localVarUrlObj.hash, + options: localVarRequestOptions, + }; + }, /** * * @summary 获取种子数据列表 🔖 @@ -1172,6 +1221,20 @@ export const SysDatabaseApiFp = function(configuration?: Configuration) { return axios.request(axiosRequestArgs); }; }, + /** + * + * @summary 移动列顺序 🔖 + * @param {MoveDbColumnInput} [body] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async apiSysDatabaseMoveColumnPost(body?: MoveDbColumnInput, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise>> { + const localVarAxiosArgs = await SysDatabaseApiAxiosParamCreator(configuration).apiSysDatabaseMoveColumnPost(body, options); + return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => { + const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url}; + return axios.request(axiosRequestArgs); + }; + }, /** * * @summary 获取种子数据列表 🔖 @@ -1392,6 +1455,16 @@ export const SysDatabaseApiFactory = function (configuration?: Configuration, ba async apiSysDatabaseListGet(options?: AxiosRequestConfig): Promise> { return SysDatabaseApiFp(configuration).apiSysDatabaseListGet(options).then((request) => request(axios, basePath)); }, + /** + * + * @summary 移动列顺序 🔖 + * @param {MoveDbColumnInput} [body] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async apiSysDatabaseMoveColumnPost(body?: MoveDbColumnInput, options?: AxiosRequestConfig): Promise> { + return SysDatabaseApiFp(configuration).apiSysDatabaseMoveColumnPost(body, options).then((request) => request(axios, basePath)); + }, /** * * @summary 获取种子数据列表 🔖 @@ -1607,6 +1680,17 @@ export class SysDatabaseApi extends BaseAPI { public async apiSysDatabaseListGet(options?: AxiosRequestConfig) : Promise> { return SysDatabaseApiFp(this.configuration).apiSysDatabaseListGet(options).then((request) => request(this.axios, this.basePath)); } + /** + * + * @summary 移动列顺序 🔖 + * @param {MoveDbColumnInput} [body] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof SysDatabaseApi + */ + public async apiSysDatabaseMoveColumnPost(body?: MoveDbColumnInput, options?: AxiosRequestConfig) : Promise> { + return SysDatabaseApiFp(this.configuration).apiSysDatabaseMoveColumnPost(body, options).then((request) => request(this.axios, this.basePath)); + } /** * * @summary 获取种子数据列表 🔖 diff --git a/Web/src/api-services/apis/sys-server-api.ts b/Web/src/api-services/apis/sys-server-api.ts index 98be14e9..d6ffe841 100644 --- a/Web/src/api-services/apis/sys-server-api.ts +++ b/Web/src/api-services/apis/sys-server-api.ts @@ -17,7 +17,9 @@ import { Configuration } from '../configuration'; // Some imports not used depending on template conditions // @ts-ignore import { BASE_PATH, COLLECTION_FORMATS, RequestArgs, BaseAPI, RequiredError } from '../base'; -import { AdminNETResultObject } from '../models'; +import { AdminNETResultListNuGetPackage } from '../models'; +import { AdminNETResultSystemHardwareInfo } from '../models'; +import { AdminNETResultSystemRuntimeInfo } from '../models'; /** * SysServerApi - axios parameter creator * @export @@ -26,12 +28,12 @@ export const SysServerApiAxiosParamCreator = function (configuration?: Configura return { /** * - * @summary 获取框架主要程序集 🔖 + * @summary 获取服务器硬件信息 * @param {*} [options] Override http request option. * @throws {RequiredError} */ - apiSysServerAssemblyListGet: async (options: AxiosRequestConfig = {}): Promise => { - const localVarPath = `/api/sysServer/assemblyList`; + apiSysServerHardwareInfoGet: async (options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/api/sysServer/hardwareInfo`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, 'https://example.com'); let baseOptions; @@ -69,12 +71,12 @@ export const SysServerApiAxiosParamCreator = function (configuration?: Configura }, /** * - * @summary 获取服务器配置信息 🔖 + * @summary 获取框架主要程序集 * @param {*} [options] Override http request option. * @throws {RequiredError} */ - apiSysServerServerBaseGet: async (options: AxiosRequestConfig = {}): Promise => { - const localVarPath = `/api/sysServer/serverBase`; + apiSysServerNuGetPackageInfoGet: async (options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/api/sysServer/nuGetPackageInfo`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, 'https://example.com'); let baseOptions; @@ -112,55 +114,12 @@ export const SysServerApiAxiosParamCreator = function (configuration?: Configura }, /** * - * @summary 获取服务器磁盘信息 🔖 + * @summary 获取服务器运行时信息 * @param {*} [options] Override http request option. * @throws {RequiredError} */ - apiSysServerServerDiskGet: async (options: AxiosRequestConfig = {}): Promise => { - const localVarPath = `/api/sysServer/serverDisk`; - // use dummy base URL string because the URL constructor only accepts absolute URLs. - const localVarUrlObj = new URL(localVarPath, 'https://example.com'); - let baseOptions; - if (configuration) { - baseOptions = configuration.baseOptions; - } - const localVarRequestOptions :AxiosRequestConfig = { method: 'GET', ...baseOptions, ...options}; - const localVarHeaderParameter = {} as any; - const localVarQueryParameter = {} as any; - - // authentication Bearer required - // http bearer authentication required - if (configuration && configuration.accessToken) { - const accessToken = typeof configuration.accessToken === 'function' - ? await configuration.accessToken() - : await configuration.accessToken; - localVarHeaderParameter["Authorization"] = "Bearer " + accessToken; - } - - const query = new URLSearchParams(localVarUrlObj.search); - for (const key in localVarQueryParameter) { - query.set(key, localVarQueryParameter[key]); - } - for (const key in options.params) { - query.set(key, options.params[key]); - } - localVarUrlObj.search = (new URLSearchParams(query)).toString(); - let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; - localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; - - return { - url: localVarUrlObj.pathname + localVarUrlObj.search + localVarUrlObj.hash, - options: localVarRequestOptions, - }; - }, - /** - * - * @summary 获取服务器使用信息 🔖 - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - apiSysServerServerUsedGet: async (options: AxiosRequestConfig = {}): Promise => { - const localVarPath = `/api/sysServer/serverUsed`; + apiSysServerRuntimeInfoGet: async (options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/api/sysServer/runtimeInfo`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, 'https://example.com'); let baseOptions; @@ -207,12 +166,12 @@ export const SysServerApiFp = function(configuration?: Configuration) { return { /** * - * @summary 获取框架主要程序集 🔖 + * @summary 获取服务器硬件信息 * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async apiSysServerAssemblyListGet(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise>> { - const localVarAxiosArgs = await SysServerApiAxiosParamCreator(configuration).apiSysServerAssemblyListGet(options); + async apiSysServerHardwareInfoGet(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise>> { + const localVarAxiosArgs = await SysServerApiAxiosParamCreator(configuration).apiSysServerHardwareInfoGet(options); return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => { const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url}; return axios.request(axiosRequestArgs); @@ -220,12 +179,12 @@ export const SysServerApiFp = function(configuration?: Configuration) { }, /** * - * @summary 获取服务器配置信息 🔖 + * @summary 获取框架主要程序集 * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async apiSysServerServerBaseGet(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise>> { - const localVarAxiosArgs = await SysServerApiAxiosParamCreator(configuration).apiSysServerServerBaseGet(options); + async apiSysServerNuGetPackageInfoGet(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise>> { + const localVarAxiosArgs = await SysServerApiAxiosParamCreator(configuration).apiSysServerNuGetPackageInfoGet(options); return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => { const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url}; return axios.request(axiosRequestArgs); @@ -233,25 +192,12 @@ export const SysServerApiFp = function(configuration?: Configuration) { }, /** * - * @summary 获取服务器磁盘信息 🔖 + * @summary 获取服务器运行时信息 * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async apiSysServerServerDiskGet(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise>> { - const localVarAxiosArgs = await SysServerApiAxiosParamCreator(configuration).apiSysServerServerDiskGet(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} - */ - async apiSysServerServerUsedGet(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise>> { - const localVarAxiosArgs = await SysServerApiAxiosParamCreator(configuration).apiSysServerServerUsedGet(options); + async apiSysServerRuntimeInfoGet(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise>> { + const localVarAxiosArgs = await SysServerApiAxiosParamCreator(configuration).apiSysServerRuntimeInfoGet(options); return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => { const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url}; return axios.request(axiosRequestArgs); @@ -268,39 +214,30 @@ export const SysServerApiFactory = function (configuration?: Configuration, base return { /** * - * @summary 获取框架主要程序集 🔖 + * @summary 获取服务器硬件信息 * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async apiSysServerAssemblyListGet(options?: AxiosRequestConfig): Promise> { - return SysServerApiFp(configuration).apiSysServerAssemblyListGet(options).then((request) => request(axios, basePath)); + async apiSysServerHardwareInfoGet(options?: AxiosRequestConfig): Promise> { + return SysServerApiFp(configuration).apiSysServerHardwareInfoGet(options).then((request) => request(axios, basePath)); }, /** * - * @summary 获取服务器配置信息 🔖 + * @summary 获取框架主要程序集 * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async apiSysServerServerBaseGet(options?: AxiosRequestConfig): Promise> { - return SysServerApiFp(configuration).apiSysServerServerBaseGet(options).then((request) => request(axios, basePath)); + async apiSysServerNuGetPackageInfoGet(options?: AxiosRequestConfig): Promise> { + return SysServerApiFp(configuration).apiSysServerNuGetPackageInfoGet(options).then((request) => request(axios, basePath)); }, /** * - * @summary 获取服务器磁盘信息 🔖 + * @summary 获取服务器运行时信息 * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async apiSysServerServerDiskGet(options?: AxiosRequestConfig): Promise> { - return SysServerApiFp(configuration).apiSysServerServerDiskGet(options).then((request) => request(axios, basePath)); - }, - /** - * - * @summary 获取服务器使用信息 🔖 - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - async apiSysServerServerUsedGet(options?: AxiosRequestConfig): Promise> { - return SysServerApiFp(configuration).apiSysServerServerUsedGet(options).then((request) => request(axios, basePath)); + async apiSysServerRuntimeInfoGet(options?: AxiosRequestConfig): Promise> { + return SysServerApiFp(configuration).apiSysServerRuntimeInfoGet(options).then((request) => request(axios, basePath)); }, }; }; @@ -314,42 +251,32 @@ 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 apiSysServerAssemblyListGet(options?: AxiosRequestConfig) : Promise> { - return SysServerApiFp(this.configuration).apiSysServerAssemblyListGet(options).then((request) => request(this.axios, this.basePath)); + public async apiSysServerHardwareInfoGet(options?: AxiosRequestConfig) : Promise> { + return SysServerApiFp(this.configuration).apiSysServerHardwareInfoGet(options).then((request) => request(this.axios, this.basePath)); } /** * - * @summary 获取服务器配置信息 🔖 + * @summary 获取框架主要程序集 * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof SysServerApi */ - public async apiSysServerServerBaseGet(options?: AxiosRequestConfig) : Promise> { - return SysServerApiFp(this.configuration).apiSysServerServerBaseGet(options).then((request) => request(this.axios, this.basePath)); + public async apiSysServerNuGetPackageInfoGet(options?: AxiosRequestConfig) : Promise> { + return SysServerApiFp(this.configuration).apiSysServerNuGetPackageInfoGet(options).then((request) => request(this.axios, this.basePath)); } /** * - * @summary 获取服务器磁盘信息 🔖 + * @summary 获取服务器运行时信息 * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof SysServerApi */ - public async apiSysServerServerDiskGet(options?: AxiosRequestConfig) : Promise> { - return SysServerApiFp(this.configuration).apiSysServerServerDiskGet(options).then((request) => request(this.axios, this.basePath)); - } - /** - * - * @summary 获取服务器使用信息 🔖 - * @param {*} [options] Override http request option. - * @throws {RequiredError} - * @memberof SysServerApi - */ - public async apiSysServerServerUsedGet(options?: AxiosRequestConfig) : Promise> { - return SysServerApiFp(this.configuration).apiSysServerServerUsedGet(options).then((request) => request(this.axios, this.basePath)); + public async apiSysServerRuntimeInfoGet(options?: AxiosRequestConfig) : Promise> { + return SysServerApiFp(this.configuration).apiSysServerRuntimeInfoGet(options).then((request) => request(this.axios, this.basePath)); } } diff --git a/Web/src/api-services/models/admin-netresult-list-nu-get-package.ts b/Web/src/api-services/models/admin-netresult-list-nu-get-package.ts new file mode 100644 index 00000000..7223de31 --- /dev/null +++ b/Web/src/api-services/models/admin-netresult-list-nu-get-package.ts @@ -0,0 +1,71 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Admin.NET 通用权限开发平台 + * 让 .NET 开发更简单、更通用、更流行。整合最新技术,模块插件式开发,前后端分离,开箱即用。
👮不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + * + * OpenAPI spec version: 1.0.0 + * + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +import { NuGetPackage } from './nu-get-package'; + /** + * 全局返回结果 + * + * @export + * @interface AdminNETResultListNuGetPackage + */ +export interface AdminNETResultListNuGetPackage { + + /** + * 状态码 + * + * @type {number} + * @memberof AdminNETResultListNuGetPackage + */ + code?: number; + + /** + * 类型success、warning、error + * + * @type {string} + * @memberof AdminNETResultListNuGetPackage + */ + type?: string | null; + + /** + * 错误信息 + * + * @type {string} + * @memberof AdminNETResultListNuGetPackage + */ + message?: string | null; + + /** + * 数据 + * + * @type {Array} + * @memberof AdminNETResultListNuGetPackage + */ + result?: Array | null; + + /** + * 附加数据 + * + * @type {any} + * @memberof AdminNETResultListNuGetPackage + */ + extras?: any | null; + + /** + * 时间 + * + * @type {Date} + * @memberof AdminNETResultListNuGetPackage + */ + time?: Date; +} diff --git a/Web/src/api-services/models/admin-netresult-system-hardware-info.ts b/Web/src/api-services/models/admin-netresult-system-hardware-info.ts new file mode 100644 index 00000000..9b1a1db0 --- /dev/null +++ b/Web/src/api-services/models/admin-netresult-system-hardware-info.ts @@ -0,0 +1,69 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Admin.NET 通用权限开发平台 + * 让 .NET 开发更简单、更通用、更流行。整合最新技术,模块插件式开发,前后端分离,开箱即用。
👮不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + * + * OpenAPI spec version: 1.0.0 + * + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +import { SystemHardwareInfo } from './system-hardware-info'; + /** + * 全局返回结果 + * + * @export + * @interface AdminNETResultSystemHardwareInfo + */ +export interface AdminNETResultSystemHardwareInfo { + + /** + * 状态码 + * + * @type {number} + * @memberof AdminNETResultSystemHardwareInfo + */ + code?: number; + + /** + * 类型success、warning、error + * + * @type {string} + * @memberof AdminNETResultSystemHardwareInfo + */ + type?: string | null; + + /** + * 错误信息 + * + * @type {string} + * @memberof AdminNETResultSystemHardwareInfo + */ + message?: string | null; + + /** + * @type {SystemHardwareInfo} + * @memberof AdminNETResultSystemHardwareInfo + */ + result?: SystemHardwareInfo; + + /** + * 附加数据 + * + * @type {any} + * @memberof AdminNETResultSystemHardwareInfo + */ + extras?: any | null; + + /** + * 时间 + * + * @type {Date} + * @memberof AdminNETResultSystemHardwareInfo + */ + time?: Date; +} diff --git a/Web/src/api-services/models/admin-netresult-system-runtime-info.ts b/Web/src/api-services/models/admin-netresult-system-runtime-info.ts new file mode 100644 index 00000000..8202cdc7 --- /dev/null +++ b/Web/src/api-services/models/admin-netresult-system-runtime-info.ts @@ -0,0 +1,69 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Admin.NET 通用权限开发平台 + * 让 .NET 开发更简单、更通用、更流行。整合最新技术,模块插件式开发,前后端分离,开箱即用。
👮不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + * + * OpenAPI spec version: 1.0.0 + * + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +import { SystemRuntimeInfo } from './system-runtime-info'; + /** + * 全局返回结果 + * + * @export + * @interface AdminNETResultSystemRuntimeInfo + */ +export interface AdminNETResultSystemRuntimeInfo { + + /** + * 状态码 + * + * @type {number} + * @memberof AdminNETResultSystemRuntimeInfo + */ + code?: number; + + /** + * 类型success、warning、error + * + * @type {string} + * @memberof AdminNETResultSystemRuntimeInfo + */ + type?: string | null; + + /** + * 错误信息 + * + * @type {string} + * @memberof AdminNETResultSystemRuntimeInfo + */ + message?: string | null; + + /** + * @type {SystemRuntimeInfo} + * @memberof AdminNETResultSystemRuntimeInfo + */ + result?: SystemRuntimeInfo; + + /** + * 附加数据 + * + * @type {any} + * @memberof AdminNETResultSystemRuntimeInfo + */ + extras?: any | null; + + /** + * 时间 + * + * @type {Date} + * @memberof AdminNETResultSystemRuntimeInfo + */ + time?: Date; +} diff --git a/Web/src/api-services/models/board-info.ts b/Web/src/api-services/models/board-info.ts new file mode 100644 index 00000000..49c94873 --- /dev/null +++ b/Web/src/api-services/models/board-info.ts @@ -0,0 +1,54 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Admin.NET 通用权限开发平台 + * 让 .NET 开发更简单、更通用、更流行。整合最新技术,模块插件式开发,前后端分离,开箱即用。
👮不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + * + * OpenAPI spec version: 1.0.0 + * + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + /** + * 主板信息 + * + * @export + * @interface BoardInfo + */ +export interface BoardInfo { + + /** + * 型号 + * + * @type {string} + * @memberof BoardInfo + */ + product?: string | null; + + /** + * 制造商 + * + * @type {string} + * @memberof BoardInfo + */ + manufacturer?: string | null; + + /** + * 序列号 + * + * @type {string} + * @memberof BoardInfo + */ + serialNumber?: string | null; + + /** + * 版本号 + * + * @type {string} + * @memberof BoardInfo + */ + version?: string | null; +} diff --git a/Web/src/api-services/models/cpu-info.ts b/Web/src/api-services/models/cpu-info.ts new file mode 100644 index 00000000..9fec229d --- /dev/null +++ b/Web/src/api-services/models/cpu-info.ts @@ -0,0 +1,86 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Admin.NET 通用权限开发平台 + * 让 .NET 开发更简单、更通用、更流行。整合最新技术,模块插件式开发,前后端分离,开箱即用。
👮不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + * + * OpenAPI spec version: 1.0.0 + * + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + /** + * 处理器信息 + * + * @export + * @interface CpuInfo + */ +export interface CpuInfo { + + /** + * 处理器名称 + * + * @type {string} + * @memberof CpuInfo + */ + processorName?: string | null; + + /** + * 处理器架构 + * + * @type {string} + * @memberof CpuInfo + */ + processorArchitecture?: string | null; + + /** + * 物理核心数 + * + * @type {number} + * @memberof CpuInfo + */ + physicalCoreCount?: number; + + /** + * 逻辑核心数(超线程) + * + * @type {number} + * @memberof CpuInfo + */ + logicalCoreCount?: number; + + /** + * 基础时钟频率(GHz) + * + * @type {number} + * @memberof CpuInfo + */ + baseClockSpeed?: number; + + /** + * 缓存大小 + * + * @type {number} + * @memberof CpuInfo + */ + cacheBytes?: number; + + /** + * CPU使用率(%) + * + * @type {number} + * @memberof CpuInfo + */ + usagePercentage?: number; + + /** + * CPU温度(°C) + * + * @type {number} + * @memberof CpuInfo + */ + temperature?: number | null; +} diff --git a/Web/src/api-services/models/db-column-input.ts b/Web/src/api-services/models/db-column-input.ts index 350c0df5..71d01992 100644 --- a/Web/src/api-services/models/db-column-input.ts +++ b/Web/src/api-services/models/db-column-input.ts @@ -79,4 +79,10 @@ export interface DbColumnInput { * @memberof DbColumnInput */ decimalDigits?: number; + + /** + * @type {string} + * @memberof DbColumnInput + */ + defaultValue?: string | null; } diff --git a/Web/src/api-services/models/disk-info.ts b/Web/src/api-services/models/disk-info.ts new file mode 100644 index 00000000..8f9eeece --- /dev/null +++ b/Web/src/api-services/models/disk-info.ts @@ -0,0 +1,70 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Admin.NET 通用权限开发平台 + * 让 .NET 开发更简单、更通用、更流行。整合最新技术,模块插件式开发,前后端分离,开箱即用。
👮不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + * + * OpenAPI spec version: 1.0.0 + * + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + /** + * 磁盘信息 + * + * @export + * @interface DiskInfo + */ +export interface DiskInfo { + + /** + * 磁盘名称 + * + * @type {string} + * @memberof DiskInfo + */ + diskName?: string | null; + + /** + * 磁盘类型 + * + * @type {string} + * @memberof DiskInfo + */ + typeName?: string | null; + + /** + * 总大小 + * + * @type {number} + * @memberof DiskInfo + */ + totalSpace?: number; + + /** + * 空闲大小 + * + * @type {number} + * @memberof DiskInfo + */ + freeSpace?: number; + + /** + * 已用大小 + * + * @type {number} + * @memberof DiskInfo + */ + usedSpace?: number; + + /** + * 可用占比 + * + * @type {number} + * @memberof DiskInfo + */ + availableRate?: number; +} diff --git a/Web/src/api-services/models/gpu-info.ts b/Web/src/api-services/models/gpu-info.ts new file mode 100644 index 00000000..b4a4888f --- /dev/null +++ b/Web/src/api-services/models/gpu-info.ts @@ -0,0 +1,118 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Admin.NET 通用权限开发平台 + * 让 .NET 开发更简单、更通用、更流行。整合最新技术,模块插件式开发,前后端分离,开箱即用。
👮不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + * + * OpenAPI spec version: 1.0.0 + * + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + /** + * GPU信息 + * + * @export + * @interface GpuInfo + */ +export interface GpuInfo { + + /** + * GPU名称 + * + * @type {string} + * @memberof GpuInfo + */ + name?: string | null; + + /** + * 描述 + * + * @type {string} + * @memberof GpuInfo + */ + description?: string | null; + + /** + * 厂商 + * + * @type {string} + * @memberof GpuInfo + */ + vendor?: string | null; + + /** + * 设备ID + * + * @type {string} + * @memberof GpuInfo + */ + deviceId?: string | null; + + /** + * 总线信息 + * + * @type {string} + * @memberof GpuInfo + */ + busInfo?: string | null; + + /** + * 驱动版本 + * + * @type {string} + * @memberof GpuInfo + */ + driverVersion?: string | null; + + /** + * 显存大小(字节) + * + * @type {number} + * @memberof GpuInfo + */ + memoryBytes?: number; + + /** + * GPU温度(°C) + * + * @type {number} + * @memberof GpuInfo + */ + temperature?: number | null; + + /** + * 视频模式描述 + * + * @type {string} + * @memberof GpuInfo + */ + videoModeDescription?: string | null; + + /** + * 状态 + * + * @type {string} + * @memberof GpuInfo + */ + status?: string | null; + + /** + * GPU使用率(%) + * + * @type {number} + * @memberof GpuInfo + */ + utilizationPercentage?: number | null; + + /** + * 显存使用率(%) + * + * @type {number} + * @memberof GpuInfo + */ + memoryUtilizationPercentage?: number | null; +} diff --git a/Web/src/api-services/models/index.ts b/Web/src/api-services/models/index.ts index ac651e40..996b799f 100644 --- a/Web/src/api-services/models/index.ts +++ b/Web/src/api-services/models/index.ts @@ -55,6 +55,7 @@ export * from './admin-netresult-list-int64'; export * from './admin-netresult-list-list-string'; export * from './admin-netresult-list-log-vis-output'; export * from './admin-netresult-list-menu-output'; +export * from './admin-netresult-list-nu-get-package'; export * from './admin-netresult-list-pos-output'; export * from './admin-netresult-list-role-output'; export * from './admin-netresult-list-role-table-output'; @@ -133,6 +134,8 @@ export * from './admin-netresult-sys-schedule'; export * from './admin-netresult-sys-upgrade'; export * from './admin-netresult-sys-user'; export * from './admin-netresult-sys-wechat-pay'; +export * from './admin-netresult-system-hardware-info'; +export * from './admin-netresult-system-runtime-info'; export * from './admin-netresult-visual-db-table'; export * from './admin-netresult-wechat-pay-output'; export * from './admin-netresult-wechat-pay-para-output'; @@ -149,6 +152,7 @@ export * from './base-proc-input'; export * from './base-status-input'; export * from './batch-config-input'; export * from './batch-config-tenant-input'; +export * from './board-info'; export * from './calendar'; export * from './calendar-algorithm-type'; export * from './calendar-week-rule'; @@ -165,6 +169,7 @@ export * from './compare-info'; export * from './const-output'; export * from './constructor-info'; export * from './copy-role-input'; +export * from './cpu-info'; export * from './create-entity-input'; export * from './create-pay-transaction-native-output'; export * from './create-pay-transaction-output'; @@ -220,6 +225,7 @@ export * from './delete-user-input'; export * from './dict-data-input'; export * from './dict-type-input'; export * from './digit-shapes'; +export * from './disk-info'; export * from './end-point'; export * from './entity-column-info'; export * from './entity-info'; @@ -250,6 +256,7 @@ export * from './generate-signature-output'; export * from './generic-parameter-attributes'; export * from './get-refund-domestic-refund-by-out-refund-number-response'; export * from './goods-detail'; +export * from './gpu-info'; export * from './grant-role-output'; export * from './http-method-enum'; export * from './iaction-result'; @@ -263,6 +270,7 @@ export * from './init-table-input'; export * from './int-ptr'; export * from './invoice-info'; export * from './invoice-key-info'; +export * from './ip-address-info'; export * from './jtoken'; export * from './job-create-type-enum'; export * from './job-detail-input'; @@ -293,16 +301,20 @@ export * from './method-impl-attributes'; export * from './method-info'; export * from './module'; export * from './module-handle'; +export * from './move-db-column-input'; export * from './mqtt-client-status'; export * from './mqtt-protocol-version'; export * from './mqtt-session-status'; export * from './mzb-input'; export * from './navigate'; +export * from './network-info'; +export * from './network-interface-statistics'; export * from './notice-input'; export * from './notice-output'; export * from './notice-status-enum'; export * from './notice-type-enum'; export * from './notice-user-status-enum'; +export * from './nu-get-package'; export * from './number-format-info'; export * from './oauth-user-input'; export * from './oauth-user-output'; @@ -346,6 +358,7 @@ export * from './property-attributes'; export * from './property-info'; export * from './public-message-input'; export * from './query-region-input'; +export * from './ram-info'; export * from './refund-request-input'; export * from './reset-pwd-user-input'; export * from './role-api-input'; @@ -357,6 +370,7 @@ export * from './role-table-input'; export * from './role-table-output'; export * from './role-user-input'; export * from './runtime-field-handle'; +export * from './runtime-info'; export * from './runtime-method-handle'; export * from './runtime-type-handle'; export * from './schedule-input'; @@ -450,6 +464,8 @@ export * from './sys-user-ldap'; export * from './sys-wechat-pay'; export * from './sys-wechat-refund'; export * from './sys-wx-open-upload-avatar-body'; +export * from './system-hardware-info'; +export * from './system-runtime-info'; export * from './table-column-output'; export * from './table-output'; export * from './table-unique-config-item'; diff --git a/Web/src/api-services/models/ip-address-info.ts b/Web/src/api-services/models/ip-address-info.ts new file mode 100644 index 00000000..869ff2d2 --- /dev/null +++ b/Web/src/api-services/models/ip-address-info.ts @@ -0,0 +1,46 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Admin.NET 通用权限开发平台 + * 让 .NET 开发更简单、更通用、更流行。整合最新技术,模块插件式开发,前后端分离,开箱即用。
👮不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + * + * OpenAPI spec version: 1.0.0 + * + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + /** + * IP地址信息 + * + * @export + * @interface IpAddressInfo + */ +export interface IpAddressInfo { + + /** + * IP地址 + * + * @type {string} + * @memberof IpAddressInfo + */ + address?: string | null; + + /** + * 子网掩码 + * + * @type {string} + * @memberof IpAddressInfo + */ + subnetMask?: string | null; + + /** + * 前缀长度 + * + * @type {number} + * @memberof IpAddressInfo + */ + prefixLength?: number; +} diff --git a/Web/src/api-services/models/move-db-column-input.ts b/Web/src/api-services/models/move-db-column-input.ts new file mode 100644 index 00000000..0c188de1 --- /dev/null +++ b/Web/src/api-services/models/move-db-column-input.ts @@ -0,0 +1,54 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Admin.NET 通用权限开发平台 + * 让 .NET 开发更简单、更通用、更流行。整合最新技术,模块插件式开发,前后端分离,开箱即用。
👮不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + * + * OpenAPI spec version: 1.0.0 + * + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + /** + * + * + * @export + * @interface MoveDbColumnInput + */ +export interface MoveDbColumnInput { + + /** + * 数据库配置ID + * + * @type {string} + * @memberof MoveDbColumnInput + */ + configId?: string | null; + + /** + * 目标表名 + * + * @type {string} + * @memberof MoveDbColumnInput + */ + tableName?: string | null; + + /** + * 要移动的列名 + * + * @type {string} + * @memberof MoveDbColumnInput + */ + columnName?: string | null; + + /** + * 移动到该列后方(为空时移动到首列) + * + * @type {string} + * @memberof MoveDbColumnInput + */ + afterColumnName?: string | null; +} diff --git a/Web/src/api-services/models/network-info.ts b/Web/src/api-services/models/network-info.ts new file mode 100644 index 00000000..c8d621ef --- /dev/null +++ b/Web/src/api-services/models/network-info.ts @@ -0,0 +1,134 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Admin.NET 通用权限开发平台 + * 让 .NET 开发更简单、更通用、更流行。整合最新技术,模块插件式开发,前后端分离,开箱即用。
👮不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + * + * OpenAPI spec version: 1.0.0 + * + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +import { IpAddressInfo } from './ip-address-info'; +import { NetworkInterfaceStatistics } from './network-interface-statistics'; + /** + * 网卡信息 + * + * @export + * @interface NetworkInfo + */ +export interface NetworkInfo { + + /** + * 名称 + * + * @type {string} + * @memberof NetworkInfo + */ + name?: string | null; + + /** + * 描述 + * + * @type {string} + * @memberof NetworkInfo + */ + description?: string | null; + + /** + * 类型 + * + * @type {string} + * @memberof NetworkInfo + */ + type?: string | null; + + /** + * 操作状态 + * + * @type {string} + * @memberof NetworkInfo + */ + operationalStatus?: string | null; + + /** + * 速度 + * + * @type {string} + * @memberof NetworkInfo + */ + speed?: string | null; + + /** + * 物理地址(mac 地址) + * + * @type {string} + * @memberof NetworkInfo + */ + physicalAddress?: string | null; + + /** + * 是否支持多播 + * + * @type {boolean} + * @memberof NetworkInfo + */ + supportsMulticast?: boolean; + + /** + * 是否只接收 + * + * @type {boolean} + * @memberof NetworkInfo + */ + isReceiveOnly?: boolean; + + /** + * DNS 地址 + * + * @type {Array} + * @memberof NetworkInfo + */ + dnsAddresses?: Array | null; + + /** + * 网关地址 + * + * @type {Array} + * @memberof NetworkInfo + */ + gatewayAddresses?: Array | null; + + /** + * DHCP服务器地址 + * + * @type {Array} + * @memberof NetworkInfo + */ + dhcpServerAddresses?: Array | null; + + /** + * IPv4 地址详细信息 + * + * @type {Array} + * @memberof NetworkInfo + */ + iPv4Addresses?: Array | null; + + /** + * IPv6 地址详细信息 + * + * @type {Array} + * @memberof NetworkInfo + */ + iPv6Addresses?: Array | null; + + /** + * @type {NetworkInterfaceStatistics} + * @memberof NetworkInfo + */ + statistics?: NetworkInterfaceStatistics; +} diff --git a/Web/src/api-services/models/network-interface-statistics.ts b/Web/src/api-services/models/network-interface-statistics.ts new file mode 100644 index 00000000..e37b5bc3 --- /dev/null +++ b/Web/src/api-services/models/network-interface-statistics.ts @@ -0,0 +1,86 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Admin.NET 通用权限开发平台 + * 让 .NET 开发更简单、更通用、更流行。整合最新技术,模块插件式开发,前后端分离,开箱即用。
👮不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + * + * OpenAPI spec version: 1.0.0 + * + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + /** + * 网络接口统计信息 + * + * @export + * @interface NetworkInterfaceStatistics + */ +export interface NetworkInterfaceStatistics { + + /** + * 接收字节数 + * + * @type {number} + * @memberof NetworkInterfaceStatistics + */ + bytesReceived?: number; + + /** + * 发送字节数 + * + * @type {number} + * @memberof NetworkInterfaceStatistics + */ + bytesSent?: number; + + /** + * 接收数据包数 + * + * @type {number} + * @memberof NetworkInterfaceStatistics + */ + packetsReceived?: number; + + /** + * 发送数据包数 + * + * @type {number} + * @memberof NetworkInterfaceStatistics + */ + packetsSent?: number; + + /** + * 丢弃的传入数据包数 + * + * @type {number} + * @memberof NetworkInterfaceStatistics + */ + incomingPacketsDiscarded?: number; + + /** + * 丢弃的传出数据包数 + * + * @type {number} + * @memberof NetworkInterfaceStatistics + */ + outgoingPacketsDiscarded?: number; + + /** + * 传入错误数据包数 + * + * @type {number} + * @memberof NetworkInterfaceStatistics + */ + incomingPacketsWithErrors?: number; + + /** + * 传出错误数据包数 + * + * @type {number} + * @memberof NetworkInterfaceStatistics + */ + outgoingPacketsWithErrors?: number; +} diff --git a/Web/src/api-services/models/nu-get-package.ts b/Web/src/api-services/models/nu-get-package.ts new file mode 100644 index 00000000..bba9819b --- /dev/null +++ b/Web/src/api-services/models/nu-get-package.ts @@ -0,0 +1,38 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Admin.NET 通用权限开发平台 + * 让 .NET 开发更简单、更通用、更流行。整合最新技术,模块插件式开发,前后端分离,开箱即用。
👮不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + * + * OpenAPI spec version: 1.0.0 + * + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + /** + * NuGet 程序包信息记录 表示一个 NuGet 包的基本信息,包括包名和版本 + * + * @export + * @interface NuGetPackage + */ +export interface NuGetPackage { + + /** + * NuGet 包名称 + * + * @type {string} + * @memberof NuGetPackage + */ + packageName?: string | null; + + /** + * NuGet 包版本号 + * + * @type {string} + * @memberof NuGetPackage + */ + packageVersion?: string | null; +} diff --git a/Web/src/api-services/models/ram-info.ts b/Web/src/api-services/models/ram-info.ts new file mode 100644 index 00000000..be0f5628 --- /dev/null +++ b/Web/src/api-services/models/ram-info.ts @@ -0,0 +1,78 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Admin.NET 通用权限开发平台 + * 让 .NET 开发更简单、更通用、更流行。整合最新技术,模块插件式开发,前后端分离,开箱即用。
👮不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + * + * OpenAPI spec version: 1.0.0 + * + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + /** + * 内存信息 + * + * @export + * @interface RamInfo + */ +export interface RamInfo { + + /** + * 总内存大小(字节) + * + * @type {number} + * @memberof RamInfo + */ + totalBytes?: number; + + /** + * 已用内存大小(字节) + * + * @type {number} + * @memberof RamInfo + */ + usedBytes?: number; + + /** + * 空闲内存大小(字节) + * + * @type {number} + * @memberof RamInfo + */ + freeBytes?: number; + + /** + * 可用内存大小(字节) + * + * @type {number} + * @memberof RamInfo + */ + availableBytes?: number; + + /** + * 缓冲区和缓存大小(字节) + * + * @type {number} + * @memberof RamInfo + */ + buffersCachedBytes?: number; + + /** + * 内存使用率(%) + * + * @type {number} + * @memberof RamInfo + */ + usagePercentage?: number; + + /** + * 可用内存占比(%) + * + * @type {number} + * @memberof RamInfo + */ + availablePercentage?: number; +} diff --git a/Web/src/api-services/models/runtime-info.ts b/Web/src/api-services/models/runtime-info.ts new file mode 100644 index 00000000..c0a8086a --- /dev/null +++ b/Web/src/api-services/models/runtime-info.ts @@ -0,0 +1,246 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Admin.NET 通用权限开发平台 + * 让 .NET 开发更简单、更通用、更流行。整合最新技术,模块插件式开发,前后端分离,开箱即用。
👮不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + * + * OpenAPI spec version: 1.0.0 + * + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + /** + * 系统运行时信息 + * + * @export + * @interface RuntimeInfo + */ +export interface RuntimeInfo { + + /** + * 操作系统名称 + * + * @type {string} + * @memberof RuntimeInfo + */ + osName?: string | null; + + /** + * 操作系统描述 + * + * @type {string} + * @memberof RuntimeInfo + */ + osDescription?: string | null; + + /** + * 操作系统版本 + * + * @type {string} + * @memberof RuntimeInfo + */ + osVersion?: string | null; + + /** + * 操作系统架构 + * + * @type {string} + * @memberof RuntimeInfo + */ + osArchitecture?: string | null; + + /** + * 进程架构 + * + * @type {string} + * @memberof RuntimeInfo + */ + processArchitecture?: string | null; + + /** + * 运行时框架描述 + * + * @type {string} + * @memberof RuntimeInfo + */ + frameworkDescription?: string | null; + + /** + * 运行时版本 + * + * @type {string} + * @memberof RuntimeInfo + */ + runtimeVersion?: string | null; + + /** + * 数据库类型 + * + * @type {string} + * @memberof RuntimeInfo + */ + databaseType?: string | null; + + /** + * 是否64位操作系统 + * + * @type {boolean} + * @memberof RuntimeInfo + */ + is64BitOperatingSystem?: boolean; + + /** + * 是否64位进程 + * + * @type {boolean} + * @memberof RuntimeInfo + */ + is64BitProcess?: boolean; + + /** + * 是否交互模式 + * + * @type {boolean} + * @memberof RuntimeInfo + */ + isInteractive?: boolean; + + /** + * 交互模式描述 + * + * @type {string} + * @memberof RuntimeInfo + */ + interactiveMode?: string | null; + + /** + * 处理器数量 + * + * @type {number} + * @memberof RuntimeInfo + */ + processorCount?: number; + + /** + * 系统目录 + * + * @type {string} + * @memberof RuntimeInfo + */ + systemDirectory?: string | null; + + /** + * 当前目录 + * + * @type {string} + * @memberof RuntimeInfo + */ + currentDirectory?: string | null; + + /** + * 机器名称 + * + * @type {string} + * @memberof RuntimeInfo + */ + machineName?: string | null; + + /** + * 用户名 + * + * @type {string} + * @memberof RuntimeInfo + */ + userName?: string | null; + + /** + * 用户域名 + * + * @type {string} + * @memberof RuntimeInfo + */ + userDomainName?: string | null; + + /** + * 工作集大小(字节) + * + * @type {number} + * @memberof RuntimeInfo + */ + workingSet?: number; + + /** + * 系统启动时间 + * + * @type {Date} + * @memberof RuntimeInfo + */ + systemStartTime?: Date; + + /** + * 系统运行时间 + * + * @type {string} + * @memberof RuntimeInfo + */ + systemUptime?: string; + + /** + * 进程启动时间 + * + * @type {Date} + * @memberof RuntimeInfo + */ + processStartTime?: Date; + + /** + * 进程运行时间 + * + * @type {string} + * @memberof RuntimeInfo + */ + processUptime?: string; + + /** + * 进程ID + * + * @type {number} + * @memberof RuntimeInfo + */ + processId?: number; + + /** + * 进程名称 + * + * @type {string} + * @memberof RuntimeInfo + */ + processName?: string | null; + + /** + * CLR版本 + * + * @type {string} + * @memberof RuntimeInfo + */ + clrVersion?: string | null; + + /** + * 环境变量数量 + * + * @type {number} + * @memberof RuntimeInfo + */ + environmentVariableCount?: number; + + /** + * 命令行参数 + * + * @type {Array} + * @memberof RuntimeInfo + */ + commandLineArgs?: Array | null; +} diff --git a/Web/src/api-services/models/system-hardware-info.ts b/Web/src/api-services/models/system-hardware-info.ts new file mode 100644 index 00000000..0291461c --- /dev/null +++ b/Web/src/api-services/models/system-hardware-info.ts @@ -0,0 +1,70 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Admin.NET 通用权限开发平台 + * 让 .NET 开发更简单、更通用、更流行。整合最新技术,模块插件式开发,前后端分离,开箱即用。
👮不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + * + * OpenAPI spec version: 1.0.0 + * + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +import { BoardInfo } from './board-info'; +import { CpuInfo } from './cpu-info'; +import { DiskInfo } from './disk-info'; +import { GpuInfo } from './gpu-info'; +import { NetworkInfo } from './network-info'; +import { RamInfo } from './ram-info'; + /** + * 系统硬件信息 + * + * @export + * @interface SystemHardwareInfo + */ +export interface SystemHardwareInfo { + + /** + * @type {CpuInfo} + * @memberof SystemHardwareInfo + */ + cpuInfo?: CpuInfo; + + /** + * @type {RamInfo} + * @memberof SystemHardwareInfo + */ + ramInfo?: RamInfo; + + /** + * 磁盘信息列表 + * + * @type {Array} + * @memberof SystemHardwareInfo + */ + diskInfos?: Array | null; + + /** + * 网络接口信息列表 + * + * @type {Array} + * @memberof SystemHardwareInfo + */ + networkInfos?: Array | null; + + /** + * GPU信息列表 + * + * @type {Array} + * @memberof SystemHardwareInfo + */ + gpuInfos?: Array | null; + + /** + * @type {BoardInfo} + * @memberof SystemHardwareInfo + */ + boardInfo?: BoardInfo; +} diff --git a/Web/src/api-services/models/system-runtime-info.ts b/Web/src/api-services/models/system-runtime-info.ts new file mode 100644 index 00000000..edcc010c --- /dev/null +++ b/Web/src/api-services/models/system-runtime-info.ts @@ -0,0 +1,37 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Admin.NET 通用权限开发平台 + * 让 .NET 开发更简单、更通用、更流行。整合最新技术,模块插件式开发,前后端分离,开箱即用。
👮不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + * + * OpenAPI spec version: 1.0.0 + * + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +import { RuntimeInfo } from './runtime-info'; + /** + * 系统运行时信息 + * + * @export + * @interface SystemRuntimeInfo + */ +export interface SystemRuntimeInfo { + + /** + * @type {RuntimeInfo} + * @memberof SystemRuntimeInfo + */ + runtimeInfo?: RuntimeInfo; + + /** + * 运行时间 + * + * @type {string} + * @memberof SystemRuntimeInfo + */ + runningTime?: string | null; +} diff --git a/Web/src/api-services/models/update-db-column-input.ts b/Web/src/api-services/models/update-db-column-input.ts index 01f17c41..7710899b 100644 --- a/Web/src/api-services/models/update-db-column-input.ts +++ b/Web/src/api-services/models/update-db-column-input.ts @@ -49,4 +49,10 @@ export interface UpdateDbColumnInput { * @memberof UpdateDbColumnInput */ description?: string | null; + + /** + * @type {string} + * @memberof UpdateDbColumnInput + */ + defaultValue?: string | null; } diff --git a/Web/src/views/system/database/component/addColumn.vue b/Web/src/views/system/database/component/addColumn.vue index 04af72a3..d048c973 100644 --- a/Web/src/views/system/database/component/addColumn.vue +++ b/Web/src/views/system/database/component/addColumn.vue @@ -57,6 +57,11 @@ + + + + +