😎1、调整系统监控页面 2、清理与调整自带工具类 3、优化数据库操作相关 4、代码清理及升级依赖

This commit is contained in:
zuohuaijun 2025-06-16 00:19:30 +08:00
parent 4c71084e63
commit d76cde34c6
125 changed files with 12766 additions and 1897 deletions

View File

@ -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
/// <param name="user"></param>
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<int> 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();

View File

@ -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 通用权限开发平台"];
}

View File

@ -28,9 +28,9 @@
<PackageReference Include="AspNetCoreRateLimit" Version="5.0.0" />
<PackageReference Include="BouncyCastle.Cryptography" Version="2.6.1" Aliases="BouncyCastleV2" />
<PackageReference Include="Elastic.Clients.Elasticsearch" Version="9.0.6" />
<PackageReference Include="Furion.Extras.Authentication.JwtBearer" Version="4.9.7.86" />
<PackageReference Include="Furion.Extras.ObjectMapper.Mapster" Version="4.9.7.86" />
<PackageReference Include="Furion.Pure" Version="4.9.7.86" />
<PackageReference Include="Furion.Extras.Authentication.JwtBearer" Version="4.9.7.87" />
<PackageReference Include="Furion.Extras.ObjectMapper.Mapster" Version="4.9.7.87" />
<PackageReference Include="Furion.Pure" Version="4.9.7.87" />
<PackageReference Include="Hardware.Info" Version="101.0.1.1" />
<PackageReference Include="Hashids.net" Version="1.7.0" />
<PackageReference Include="IPTools.China" Version="1.6.0" />
@ -68,7 +68,6 @@
<PackageReference Include="Microsoft.AspNetCore.DataProtection.StackExchangeRedis" Version="8.0.11" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="8.0.11" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.StackExchangeRedis" Version="8.0.11" />
<PackageReference Include="Microsoft.PowerShell.SDK" Version="7.4.7" />
<PackageReference Include="My.Extensions.Localization.Json" Version="3.3.0" />
</ItemGroup>
@ -79,7 +78,6 @@
<PackageReference Include="Microsoft.AspNetCore.DataProtection.StackExchangeRedis" Version="9.0.6" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="9.0.6" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.StackExchangeRedis" Version="9.0.6" />
<PackageReference Include="Microsoft.PowerShell.SDK" Version="7.5.1" />
<PackageReference Include="My.Extensions.Localization.Json" Version="3.4.0" />
</ItemGroup>

View File

@ -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];

View File

@ -126,7 +126,7 @@ public partial class SysUser : EntityTenant
/// <summary>
/// 毕业院校
/// </summary>COLLEGE
/// </summary>
[SugarColumn(ColumnDescription = "毕业院校", Length = 128)]
[MaxLength(128)]
public string? College { get; set; }

View File

@ -530,7 +530,7 @@ public enum ErrorCodeEnum
D8000,
/// <summary>
/// 不允许的文件类型
/// 不允许的文件类型
/// </summary>
[ErrorCodeItemMetadata("不允许的文件类型:{0}")]
D8001,

View File

@ -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<int, string>();
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<int, string>();
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<DescriptionAttribute>();
// 获取字段的指定特性,不包含继承中的特性
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;
}

View File

@ -4,7 +4,7 @@
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core.Utils;
namespace Admin.NET.Core;
public static class EnumerableExtensions
{

View File

@ -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);
}
/// <summary>

View File

@ -1,51 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core;
/// <summary>
/// List 扩展方法
/// </summary>
public static class ListExtension
{
public static async Task ForEachAsync<T>(this List<T> list, Func<T, Task> func)
{
foreach (var value in list)
{
await func(value);
}
}
public static async Task ForEachAsync<T>(this IEnumerable<T> source, Func<T, Task> action)
{
foreach (var value in source)
{
await action(value);
}
}
public static void ForEach<T>(this IEnumerable<T> enumerable, Action<T> consumer)
{
foreach (T item in enumerable)
{
consumer(item);
}
}
public static void AddRange<T>(this IList<T> list, IEnumerable<T> items)
{
if (list is List<T> list2)
{
list2.AddRange(items);
return;
}
foreach (T item in items)
{
list.Add(item);
}
}
}

View File

@ -0,0 +1,459 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core;
/// <summary>
/// 对象转换扩展方法
/// </summary>
public static class ParseExtensions
{
#region Bool
/// <summary>
/// 对象转布尔值
/// </summary>
/// <param name="thisValue"></param>
/// <returns></returns>
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
/// <summary>
/// 对象转短整数
/// </summary>
/// <param name="thisValue"></param>
/// <returns></returns>
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;
}
/// <summary>
/// 对象转短整数
/// </summary>
/// <param name="thisValue"></param>
/// <param name="errorValue"></param>
/// <returns></returns>
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
/// <summary>
/// 对象转长整数
/// </summary>
/// <param name="thisValue"></param>
/// <returns></returns>
public static long ParseToLong(this object? thisValue)
{
return thisValue is not null && thisValue != DBNull.Value &&
long.TryParse(thisValue.ToString(), out var reveal)
? reveal
: 0L;
}
/// <summary>
/// 对象转长整数
/// </summary>
/// <param name="thisValue"></param>
/// <param name="errorValue"></param>
/// <returns></returns>
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
/// <summary>
/// 对象转浮点数
/// ±1.5 x 10 e45 至 ±3.4 x 10 e38 大约 6-9 位数字
/// </summary>
/// <param name="thisValue"></param>
/// <returns></returns>
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;
}
/// <summary>
/// 对象转浮点数
/// ±1.5 x 10 e45 至 ±3.4 x 10 e38 大约 6-9 位数字
/// </summary>
/// <param name="thisValue"></param>
/// <param name="errorValue"></param>
/// <returns></returns>
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
/// <summary>
/// 对象转浮点数
/// ±5.0 × 10 e324 到 ±1.7 × 10 e308 大约 15-17 位数字
/// </summary>
/// <param name="thisValue"></param>
/// <returns></returns>
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;
}
/// <summary>
/// 对象转浮点数
/// ±5.0 × 10 e324 到 ±1.7 × 10 e308 大约 15-17 位数字
/// </summary>
/// <param name="thisValue"></param>
/// <param name="errorValue"></param>
/// <returns></returns>
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
/// <summary>
/// 对象转浮点数
/// ±1.0 x 10 e-28 至 ±7.9228 x 10 e28 28-29 位
/// </summary>
/// <param name="thisValue"></param>
/// <returns></returns>
public static decimal ParseToDecimal(this object? thisValue)
{
return thisValue is not null && thisValue != DBNull.Value &&
decimal.TryParse(thisValue.ToString(), out var reveal)
? reveal
: 0M;
}
/// <summary>
/// 对象转浮点数
/// ±1.0 x 10 e-28 至 ±7.9228 x 10 e28 28-29 位
/// </summary>
/// <param name="thisValue"></param>
/// <param name="errorValue"></param>
/// <returns></returns>
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
/// <summary>
/// 对象转数字
/// </summary>
/// <param name="thisValue"></param>
/// <returns></returns>
public static int ParseToInt(this object? thisValue)
{
return thisValue is null ? 0 :
thisValue != DBNull.Value && int.TryParse(thisValue.ToString(), out var reveal) ? reveal : 0;
}
/// <summary>
/// 对象转数字
/// </summary>
/// <param name="thisValue"></param>
/// <param name="errorValue"></param>
/// <returns></returns>
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
/// <summary>
/// 对象转金额
/// </summary>
/// <param name="thisValue"></param>
/// <returns></returns>
public static double ParseToMoney(this object? thisValue)
{
return thisValue is not null && thisValue != DBNull.Value &&
double.TryParse(thisValue.ToString(), out var reveal)
? reveal
: 0;
}
/// <summary>
/// 对象转金额
/// </summary>
/// <param name="thisValue"></param>
/// <param name="errorValue"></param>
/// <returns></returns>
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
/// <summary>
/// 对象转字符串
/// </summary>
/// <param name="thisValue"></param>
/// <returns></returns>
public static string ParseToString(this object? thisValue)
{
return thisValue is not null ? thisValue.ToString()!.Trim() : string.Empty;
}
/// <summary>
/// 对象转字符串
/// </summary>
/// <param name="thisValue"></param>
/// <param name="errorValue"></param>
/// <returns></returns>
public static string ParseToString(this object? thisValue, string errorValue)
{
return thisValue is not null ? thisValue.ToString()!.Trim() : errorValue;
}
/// <summary>
/// 判断是否为空
/// </summary>
/// <param name="thisValue"></param>
/// <returns></returns>
public static bool IsEmptyOrNull(this object? thisValue)
{
return !thisValue.IsNotEmptyOrNull();
}
/// <summary>
/// 判断是否为空
/// </summary>
/// <param name="thisValue"></param>
/// <returns></returns>
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";
}
/// <summary>
/// 判断是否为空或零
/// </summary>
/// <param name="thisValue"></param>
/// <returns></returns>
public static bool IsNullOrZero(this object? thisValue)
{
return !thisValue.IsNotNullOrZero();
}
/// <summary>
/// 判断是否为空或零
/// </summary>
/// <param name="thisValue"></param>
/// <returns></returns>
public static bool IsNotNullOrZero(this object? thisValue)
{
return thisValue.IsNotEmptyOrNull() && thisValue.ParseToString() != "0";
}
#endregion String
#region DateTime
/// <summary>
/// 对象转日期
/// </summary>
/// <param name="thisValue"></param>
/// <returns></returns>
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;
}
/// <summary>
/// 对象转日期
/// </summary>
/// <param name="thisValue"></param>
/// <param name="errorValue"></param>
/// <returns></returns>
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
/// <summary>
/// 对象转 DateTimeOffset
/// </summary>
/// <param name="thisValue"></param>
/// <returns></returns>
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;
}
/// <summary>
/// 对象转 DateTimeOffset
/// </summary>
/// <param name="thisValue"></param>
/// <param name="errorValue"></param>
/// <returns></returns>
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
/// <summary>
/// 对象转 DateTimeOffset
/// </summary>
/// <param name="thisValue"></param>
/// <returns></returns>
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;
}
/// <summary>
/// 对象转 DateTimeOffset
/// </summary>
/// <param name="thisValue"></param>
/// <param name="errorValue"></param>
/// <returns></returns>
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
/// <summary>
/// 将 string 转换为 Guid
/// 若转换失败,则返回 Guid.Empty不抛出异常。
/// </summary>
/// <param name="thisValue"></param>
/// <returns></returns>
public static Guid ParseToGuid(this object? thisValue)
{
try
{
return new Guid(thisValue.ParseToString());
}
catch
{
return Guid.Empty;
}
}
#endregion Guid
#region Dictionary
/// <summary>
/// 对象转换成字典
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public static IEnumerable<Dictionary<string, dynamic>> ParseToDictionary(this object? obj)
{
if (obj is not IEnumerable<dynamic> 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
}

View File

@ -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;

View File

@ -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 = "";

View File

@ -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
{

View File

@ -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;

View File

@ -18,7 +18,7 @@ public class SysUserSeedData : ISqlSugarEntitySeedData<SysUser>
/// <returns></returns>
public IEnumerable<SysUser> 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
[

View File

@ -81,7 +81,7 @@ public class SysAuthService : IDynamicApiController, ITransient
{
VerifyPassword(input.Password, keyPasswordErrorTimes, passwordErrorTimes, user);
}
else if (!await App.GetRequiredService<SysLdapService>().AuthAccount(user.TenantId, userLdap.Account, CryptogramUtil.Decrypt(input.Password)))
else if (!await App.GetRequiredService<SysLdapService>().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<SysLdapService>().AuthAccount(user.TenantId.Value, userLdap.Account, CryptogramUtil.Decrypt(password)))
else if (!await App.GetRequiredService<SysLdapService>().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
});

View File

@ -46,7 +46,7 @@ public class SysLdapService : IDynamicApiController, ITransient
public async Task<long> Add(AddSysLdapInput input)
{
var entity = input.Adapt<SysLdap>();
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<SysLdap>();
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<SysUserLdap>();
@ -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<SysOrg>();

View File

@ -248,7 +248,7 @@ public class SysCacheService : IDynamicApiController, ISingleton
public async Task<List<T>> GetListAsync<T>(IEnumerable<long> ids, Func<List<long>, Task<List<T>>> loadFromDb, bool cacheNull = true, TimeSpan? nullExpire = null) where T : class
{
var idList = ids.Distinct().ToList();
if (idList.Count == 0) return new List<T>();
if (idList.Count == 0) return [];
// 1. 批量获取缓存(保持同步,假设缓存操作快速)
var cachedItems = GetFromCache<T>(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<T> GetList<T>(IEnumerable<long> ids, Func<List<long>, List<T>> loadFromDb, bool cacheNull = true, TimeSpan? nullExpire = null) where T : class
{
var idList = ids.Distinct().ToList();
if (idList.Count == 0) return new List<T>();
if (idList.Count == 0) return [];
// 1. 批量获取缓存
var cachedItems = GetFromCache<T>(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<T> GetFromCache<T>(List<long> ids) where T : class
{
if (ids == null || ids.Count == 0)
return new List<T>();
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<T>(keys);

View File

@ -94,7 +94,7 @@ public class CustomViewEngine : ViewEngineModel
var config = App.GetOptions<DbConnectionOptions>().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();
}

View File

@ -44,7 +44,7 @@ public class TableOutput
/// <summary>
/// 表字段个数
/// </summary>
public int ColumnCount { get; set; }
public int ColumnCount { get; set; }
/// <summary>
/// 程序集名称

View File

@ -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<SugarColumn>()?.ColumnName ?? "").ToLower() == columnOutput.ColumnName.ToLower()) ??
entityProperties.FirstOrDefault(u => u.GetCustomAttribute<SugarColumn>() != 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);

View File

@ -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;
}

View File

@ -35,7 +35,7 @@ public class SysCommonService : IDynamicApiController, ITransient
[DisplayName("获取国密公钥私钥对")]
public SmKeyPairOutput GetSmKeyPair()
{
return CryptogramUtil.GetSmKeyPair();
return CryptogramHelper.GetSmKeyPair();
}
/// <summary>
@ -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);
}
/// <summary>
@ -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);
}
/// <summary>
@ -185,7 +185,8 @@ public class SysCommonService : IDynamicApiController, ITransient
/// <summary>
/// 生成所有移动端接口文件 🔖
/// </summary>
/// <returns></returns>
/// <param name="groupName"></param>
/// <param name="isAppApi"></param>
[HttpGet]
[DisplayName("生成所有移动端接口文件")]
public void GenerateAppApi([FromQuery] string groupName = "", [FromQuery] bool isAppApi = true)

View File

@ -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
{
/// <summary>
/// 数据库配置ID
/// </summary>
public string ConfigId { get; set; }
/// <summary>
/// 目标表名
/// </summary>
public string TableName { get; set; }
/// <summary>
///要移动的列名
/// </summary>
public string ColumnName { get; set; }
/// <summary>
/// 移动到该列后方(为空时移动到首列)
/// </summary>
public string AfterColumnName { get; set; }
}

View File

@ -122,10 +122,9 @@ public class SysDatabaseService : IDynamicApiController, ITransient
[DisplayName("获取字段列表")]
public List<DbColumnOutput> GetColumnList(string tableName, string configId = SqlSugarConst.MainConfigId)
{
var db = _db.AsTenant().GetConnectionScope(configId);
if (string.IsNullOrWhiteSpace(tableName))
return new List<DbColumnOutput>();
if (string.IsNullOrWhiteSpace(tableName)) return [];
var db = _db.AsTenant().GetConnectionScope(configId);
return db.DbMaintenance.GetColumnInfosByTableName(tableName, false).Adapt<List<DbColumnOutput>>();
}
@ -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);
}
/// <summary>
/// 移动列顺序 🔖
/// </summary>
/// <param name="input"></param>
[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} 数据库的列移动操作");
}
}
/// <summary>
/// 获取列定义
/// </summary>
/// <param name="db"></param>
/// <param name="tableName"></param>
/// <param name="columnName"></param>
/// <param name="noDefault"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
private string GetColumnDefinitionInMySQL(ISqlSugarClient db, string tableName, string columnName, bool noDefault = false)
{
var columnDef = db.Ado.SqlQuery<dynamic>($"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();
}
/// <summary>
/// MySQL 列移动实现
/// </summary>
/// <param name="db"></param>
/// <param name="tableName"></param>
/// <param name="columnName"></param>
/// <param name="afterColumnName"></param>
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());
}
/// <summary>
/// 获取表列表 🔖
/// </summary>
@ -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<DbConnectionOptions>().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>();
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<object>();
if (seedData == null) continue;
List<object> recordsToRemove = new();
List<object> 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<JsonIgnoredPropertyData> record = new();
List<JsonIgnoredPropertyData> record = [];
foreach (var item in jsonIgnoreProperties)
{
object v = item.GetValue(r);

View File

@ -52,7 +52,6 @@ public class SysDbBackupService : IDynamicApiController, ITransient
}
dbBackupList = dbBackupList.OrderByDescending(u => u.CreateTime).ToList();
return dbBackupList;
}
catch

View File

@ -85,7 +85,7 @@ public class SysMenuService : IDynamicApiController, ITransient
[DisplayName("获取菜单列表")]
public async Task<List<SysMenu>> GetList([FromQuery] MenuInput input)
{
var menuIdList = _userManager.SuperAdmin ? new List<long>() : await GetMenuIdList();
var menuIdList = _userManager.SuperAdmin ? [] : await GetMenuIdList();
// 有筛选条件时返回list列表防止构造不出树
if (!string.IsNullOrWhiteSpace(input.Title) || input.Type is > 0)

View File

@ -73,7 +73,10 @@ public class SysMessageService : IDynamicApiController, ITransient
{
var hashKey = SysCacheService.HashGetAll<SysOnlineUser>(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);
}
}
/// <summary>

View File

@ -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的实例与请求对象对应

View File

@ -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);

View File

@ -432,7 +432,7 @@ public class SysRegionService : IDynamicApiController, ITransient
var parent = res.District[0];
var areaList = new List<SysRegion>()
{
new SysRegion
new()
{
Id = Convert.ToInt64(parent.Gb),
Pid = 0,

View File

@ -17,131 +17,32 @@ public class SysServerService : IDynamicApiController, ITransient
}
/// <summary>
/// 获取服务器配置信息 🔖
/// 获取服务器硬件信息
/// </summary>
/// <returns></returns>
[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<DbConnectionOptions>().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();
}
/// <summary>
/// 获取服务器使用信息 🔖
/// 获取服务器运行时信息
/// </summary>
/// <returns></returns>
[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();
}
/// <summary>
/// 获取服务器磁盘信息 🔖
/// </summary>
/// <returns></returns>
[DisplayName("获取服务器磁盘信息")]
public dynamic GetServerDisk()
{
return ComputerUtil.GetDiskInfos();
}
/// <summary>
/// 获取框架主要程序集 🔖
/// 获取框架主要程序集
/// </summary>
/// <returns></returns>
[DisplayName("获取框架主要程序集")]
public dynamic GetAssemblyList()
public List<NuGetPackage> 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");
}
}

View File

@ -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<string> ResetPwd(TenantUserInput input)
{
var password = await _sysConfigService.GetConfigValueByCode<string>(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,

View File

@ -113,7 +113,7 @@ public class SysUserService : IDynamicApiController, ITransient
var password = await _sysConfigService.GetConfigValueByCode<string>(ConfigConst.SysPassword);
var user = input.Adapt<SysUser>();
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<int> 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<string>(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();
// 清空密码错误次数缓存

View File

@ -119,10 +119,7 @@ public class SysWxOpenService : IDynamicApiController, ITransient
[DisplayName("微信小程序登录OpenId")]
public async Task<dynamic> 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<SysFile> 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<dynamic> 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<GenerateQRImageOutput> GenerateQRImageUnlimitAsync(GenerateQRImageUnLimitInput input)
{
GenerateQRImageOutput generateQRImageOutInput = new GenerateQRImageOutput();
GenerateQRImageOutput generateQRImageOutInput = new();
if (input.PagePath.IsNullOrEmpty())
{
generateQRImageOutInput.Message = $"生成失败,页面路径不能为空";

View File

@ -55,7 +55,12 @@ public sealed class SignatureAuthenticationHandler : AuthenticationHandler<Signa
if (!long.TryParse(timestampStr, out var timestamp))
return await AuthenticateResultFailAsync("timestamp 值不合法");
var requestDate = DateTimeUtil.ConvertUnixTime(timestamp);
// 根据时间戳转成时间格式(自动判断时间戳长度是秒还是以毫秒为单位)
bool isMilliseconds = timestamp > 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<Signa
/// <returns></returns>
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;

View File

@ -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<object>().ToArray() ?? [];
if (!seedData.Any())
if (seedData.Length == 0)
{
Console.WriteLine($" 忽略 {seedType.FullName,-58} ({dbProvider.CurrentConnectionConfig.ConfigId}) 原因:没有数据");
return default;

View File

@ -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);

View File

@ -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<SqlFuncExternal>
{
// 密码解密
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<SysConfig>().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]);

View File

@ -1,149 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core;
/// <summary>
/// 聚合配置增强版(独立类)
/// </summary>
public class AggregationBuilder
{
private readonly List<AggregationConfig> _configs;
private readonly Type _entityType;
private readonly Type _outputType;
public List<string> SelectParts { get; } = [];
public List<string> HavingConditions { get; } = [];
public AggregationBuilder(IEnumerable<AggregationConfig> 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;
}
/// <summary>
/// 验证聚合配置有效性
/// </summary>
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
};
}
/// <summary>
/// 验证字段有效性
/// </summary>
public static List<string> ValidateFields<T>(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();
}
}
/// <summary>
/// 增强版聚合配置类
/// </summary>
public class AggregationConfig
{
/// <summary>
/// 数据库字段名与CustomExpression二选一
/// </summary>
public string Field { get; set; }
/// <summary>
/// 自定义聚合表达式优先级高于Field+Function
/// </summary>
public string CustomExpression { get; set; }
/// <summary>
/// 聚合函数类型使用CustomExpression时可不填
/// </summary>
public AggregateFunction Function { get; set; } = AggregateFunction.Sum;
/// <summary>
/// 输出字段别名必须与DTO属性名一致
/// </summary>
public string Alias { get; set; }
/// <summary>
/// HAVING条件表达式如"> 100"
/// </summary>
public string HavingCondition { get; set; }
}
/// <summary>
/// 函数枚举
/// </summary>
public enum AggregateFunction
{
Sum,
Avg,
Count,
Max,
Min,
// 可扩展其他函数
}

View File

@ -0,0 +1,198 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using System.Diagnostics.CodeAnalysis;
namespace Admin.NET.Core;
/// <summary>
/// 集合扩展方法
/// </summary>
public static class CollectionExtensions
{
/// <summary>
/// 检查给定的集合对象是否为空或者没有任何项
/// </summary>
/// <typeparam name="T">集合元素类型</typeparam>
/// <param name="source">要检查的集合</param>
/// <returns>如果集合为null或空则返回true否则返回false</returns>
public static bool IsNullOrEmpty<T>(this ICollection<T>? source)
{
return source is not { Count: > 0 };
}
/// <summary>
/// 如果条件成立,添加项
/// </summary>
/// <typeparam name="T">集合元素类型</typeparam>
/// <param name="source">要操作的集合</param>
/// <param name="value">要添加的值</param>
/// <param name="flag">条件标志为true时添加项</param>
public static void AddIf<T>(this ICollection<T> source, T value, bool flag)
{
_ = NotNull(source, nameof(source));
if (flag)
{
source.Add(value);
}
}
/// <summary>
/// 如果条件成立,添加项
/// </summary>
/// <typeparam name="T">集合元素类型</typeparam>
/// <param name="source">要操作的集合</param>
/// <param name="value">要添加的值</param>
/// <param name="func">条件函数返回true时添加项</param>
public static void AddIf<T>(this ICollection<T> source, T value, Func<bool> func)
{
_ = NotNull(source, nameof(source));
if (func())
{
source.Add(value);
}
}
/// <summary>
/// 如果给定的集合对象不为空,则添加一个项
/// </summary>
/// <typeparam name="T">集合元素类型</typeparam>
/// <param name="source">要操作的集合</param>
/// <param name="value">要添加的值如果不为null</param>
public static void AddIfNotNull<T>(this ICollection<T> source, T value)
{
_ = NotNull(source, nameof(source));
if (value is not null)
{
source.Add(value);
}
}
/// <summary>
/// 如果集合中尚未包含该项,则将其添加到集合中
/// </summary>
/// <typeparam name="T">集合中项的类型</typeparam>
/// <param name="source">集合对象</param>
/// <param name="item">要检查并添加的项</param>
/// <returns>如果添加了项,则返回真(True);如果没有添加(即项已存在)则返回假(False)</returns>
public static bool AddIfNotContains<T>(this ICollection<T> source, T item)
{
_ = NotNull(source, nameof(source));
if (source.Contains(item))
{
return false;
}
source.Add(item);
return true;
}
/// <summary>
/// 向集合中添加尚未包含的项
/// </summary>
/// <typeparam name="T">集合中项的类型</typeparam>
/// <param name="source">集合对象</param>
/// <param name="items">要检查并添加的项的集合</param>
/// <returns>返回添加的项的集合</returns>
public static IEnumerable<T> AddIfNotContains<T>(this ICollection<T> source, IEnumerable<T> items)
{
_ = NotNull(source, nameof(source));
var enumerable = items as T[] ?? [.. items];
_ = NotNull(enumerable, nameof(items));
List<T> addedItems = [];
foreach (var item in enumerable)
{
if (source.Contains(item))
{
continue;
}
source.Add(item);
addedItems.Add(item);
}
return addedItems;
}
/// <summary>
/// 如果集合中尚未包含满足给定谓词条件的项,则将项添加到集合中
/// </summary>
/// <typeparam name="T">集合中项的类型</typeparam>
/// <param name="source">集合对象</param>
/// <param name="predicate">决定项是否已存在于集合中的条件</param>
/// <param name="itemFactory">返回项的工厂函数</param>
/// <returns>如果添加了项,则返回真(True);如果没有添加(即项已存在)则返回假(False)</returns>
public static bool AddIfNotContains<T>(this ICollection<T> source, Func<T, bool> predicate, Func<T> itemFactory)
{
_ = NotNull(source, nameof(source));
if (source.Any(predicate))
{
return false;
}
source.Add(itemFactory());
return true;
}
/// <summary>
/// 移除集合中所有满足给定谓词条件的项
/// </summary>
/// <typeparam name="T">集合中项的类型</typeparam>
/// <param name="source">集合对象</param>
/// <param name="predicate">用于移除项的条件</param>
/// <returns>被移除项的列表</returns>
public static IList<T> RemoveAllWhere<T>(this ICollection<T> source, Func<T, bool> predicate)
{
_ = NotNull(source, nameof(source));
var items = source.Where(predicate).ToList();
foreach (var item in items)
{
_ = source.Remove(item);
}
return items;
}
/// <summary>
/// 从集合中移除所有指定的项
/// </summary>
/// <typeparam name="T">集合中项的类型</typeparam>
/// <param name="source">集合对象</param>
/// <param name="items">要移除的项的集合</param>
public static void RemoveAll<T>(this ICollection<T> source, IEnumerable<T> items)
{
_ = NotNull(source, nameof(source));
var enumerable = items as T[] ?? [.. items];
_ = NotNull(enumerable, nameof(items));
foreach (var item in enumerable)
{
_ = source.Remove(item);
}
}
/// <summary>
/// 数据不为空判断
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="value"></param>
/// <param name="parameterName"></param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public static T NotNull<T>([NotNull] T? value, string parameterName)
{
return value is null ? throw new ArgumentNullException(parameterName) : value;
}
}

View File

@ -0,0 +1,171 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using System.Dynamic;
namespace Admin.NET.Core;
/// <summary>
/// 字典扩展方法
/// </summary>
public static class DictionaryExtensions
{
/// <summary>
/// 使用给定的键从字典中获取值。如果找不到,则返回默认值
/// </summary>
/// <typeparam name="TKey">键的类型</typeparam>
/// <typeparam name="TValue">值的类型</typeparam>
/// <param name="dictionary">要检查和获取的字典</param>
/// <param name="key">要查找值的键</param>
/// <returns>如果找到,返回值;如果找不到,返回默认值</returns>
public static TValue? GetOrDefault<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key) where TKey : notnull
{
return dictionary.GetValueOrDefault(key);
}
/// <summary>
/// 使用给定的键从字典中获取值。如果找不到,则返回默认值
/// </summary>
/// <typeparam name="TKey">键的类型</typeparam>
/// <typeparam name="TValue">值的类型</typeparam>
/// <param name="dictionary">要检查和获取的字典</param>
/// <param name="key">要查找值的键</param>
/// <returns>如果找到,返回值;如果找不到,返回默认值</returns>
public static TValue? GetOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key)
{
return dictionary.TryGetValue(key, out var obj) ? obj : default;
}
/// <summary>
/// 使用给定的键从只读字典中获取值。如果找不到,则返回默认值
/// </summary>
/// <typeparam name="TKey">键的类型</typeparam>
/// <typeparam name="TValue">值的类型</typeparam>
/// <param name="dictionary">要检查和获取的只读字典</param>
/// <param name="key">要查找值的键</param>
/// <returns>如果找到,返回值;如果找不到,返回默认值</returns>
public static TValue? GetOrDefault<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> dictionary, TKey key)
{
return dictionary.GetValueOrDefault(key);
}
/// <summary>
/// 使用给定的键从并发字典中获取值。如果找不到,则返回默认值
/// </summary>
/// <typeparam name="TKey">键的类型,不能为空</typeparam>
/// <typeparam name="TValue">值的类型</typeparam>
/// <param name="dictionary">要检查和获取的并发字典</param>
/// <param name="key">要查找值的键</param>
/// <returns>如果找到,返回值;如果找不到,返回默认值</returns>
public static TValue? GetOrDefault<TKey, TValue>(this ConcurrentDictionary<TKey, TValue> dictionary, TKey key) where TKey : notnull
{
return dictionary.GetValueOrDefault(key);
}
/// <summary>
/// 使用给定的键从字典中获取值。如果找不到,则使用工厂方法创建并添加值
/// </summary>
/// <typeparam name="TKey">键的类型</typeparam>
/// <typeparam name="TValue">值的类型</typeparam>
/// <param name="dictionary">要检查和获取的字典</param>
/// <param name="key">要查找值的键</param>
/// <param name="factory">如果字典中未找到,则用于创建值的工厂方法</param>
/// <returns>如果找到,返回值;如果找不到,使用工厂方法创建并返回默认值</returns>
public static TValue GetOrAdd<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, Func<TKey, TValue> factory)
{
return dictionary.TryGetValue(key, out var obj) ? obj : dictionary[key] = factory(key);
}
/// <summary>
/// 使用给定的键从字典中获取值。如果找不到,则使用工厂方法创建并添加值
/// </summary>
/// <typeparam name="TKey">键的类型</typeparam>
/// <typeparam name="TValue">值的类型</typeparam>
/// <param name="dictionary">要检查和获取的字典</param>
/// <param name="key">要查找值的键</param>
/// <param name="factory">如果字典中未找到,则用于创建值的工厂方法</param>
/// <returns>如果找到,返回值;如果找不到,使用工厂方法创建并返回默认值</returns>
public static TValue GetOrAdd<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, Func<TValue> factory)
{
return dictionary.GetOrAdd(key, _ => factory());
}
/// <summary>
/// 使用给定的键从并发字典中获取值。如果找不到,则使用工厂方法创建并添加值
/// </summary>
/// <typeparam name="TKey">键的类型,不能为空</typeparam>
/// <typeparam name="TValue">值的类型</typeparam>
/// <param name="dictionary">要检查和获取的并发字典</param>
/// <param name="key">要查找值的键</param>
/// <param name="factory">如果并发字典中未找到,则用于创建值的工厂方法</param>
/// <returns>如果找到,返回值;如果找不到,使用工厂方法创建并返回默认值</returns>
public static TValue GetOrAdd<TKey, TValue>(this ConcurrentDictionary<TKey, TValue> dictionary, TKey key, Func<TValue> factory) where TKey : notnull
{
return dictionary.GetOrAdd(key, _ => factory());
}
/// <summary>
/// 将字典转换为动态对象,以便在运行时添加和移除
/// </summary>
/// <param name="dictionary">要转换的字典对象</param>
/// <returns>如果值正确,返回表示对象的 ExpandoObject</returns>
public static dynamic ConvertToDynamicObject(this Dictionary<string, object> dictionary)
{
ExpandoObject expandoObject = new();
ICollection<KeyValuePair<string, object>> expendObjectCollection = expandoObject!;
foreach (var keyValuePair in dictionary)
{
expendObjectCollection.Add(keyValuePair);
}
return expandoObject;
}
/// <summary>
/// 如果字典中存在指定的键,则尝试获取其值
/// </summary>
/// <typeparam name="T">值的类型</typeparam>
/// <param name="dictionary">字典对象</param>
/// <param name="key">要查找的键</param>
/// <param name="value">键对应的值,如果键不存在,则为默认值</param>
/// <returns>如果字典中存在该键,则返回真(True);否则返回假(False)</returns>
public static bool TryGetValue<T>(this IDictionary<string, object> 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;
}
/// <summary>
/// 字典根据 key 删除,返回一个新的字典
/// </summary>
/// <param name="dictionary"></param>
/// <param name="keys"></param>
/// <returns>移除后新的字典</returns>
public static IDictionary<string, object> RemoveByKeys(this IDictionary<string, object> dictionary, params string[] keys)
{
ArgumentNullException.ThrowIfNull(dictionary);
if (keys.Length == 0)
{
return dictionary;
}
// 创建一个新的字典,避免修改原始字典
var newDic = new Dictionary<string, object>(dictionary);
foreach (var key in keys)
{
newDic.Remove(key);
}
return newDic;
}
}

View File

@ -0,0 +1,586 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core;
/// <summary>
/// 链表扩展方法
/// </summary>
public static class LinkedListExtensions
{
/// <summary>
/// 批量添加元素到链表末尾
/// </summary>
/// <typeparam name="T">链表元素类型</typeparam>
/// <param name="list">链表实例</param>
/// <param name="items">要添加的元素集合</param>
/// <exception cref="ArgumentNullException">链表或元素集合为空时抛出</exception>
public static void AddRange<T>(this LinkedList<T> list, IEnumerable<T> items)
{
ArgumentNullException.ThrowIfNull(list);
ArgumentNullException.ThrowIfNull(items);
foreach (var item in items)
{
list.AddLast(item);
}
}
/// <summary>
/// 批量添加元素到链表开头
/// </summary>
/// <typeparam name="T">链表元素类型</typeparam>
/// <param name="list">链表实例</param>
/// <param name="items">要添加的元素集合</param>
/// <exception cref="ArgumentNullException">链表或元素集合为空时抛出</exception>
public static void AddRangeFirst<T>(this LinkedList<T> list, IEnumerable<T> items)
{
ArgumentNullException.ThrowIfNull(list);
ArgumentNullException.ThrowIfNull(items);
// 从末尾开始添加,保持顺序
var itemsArray = items.ToArray();
for (var i = itemsArray.Length - 1; i >= 0; i--)
{
list.AddFirst(itemsArray[i]);
}
}
/// <summary>
/// 在指定节点后批量插入元素
/// </summary>
/// <typeparam name="T">链表元素类型</typeparam>
/// <param name="list">链表实例</param>
/// <param name="node">参考节点</param>
/// <param name="items">要插入的元素集合</param>
/// <exception cref="ArgumentNullException">链表、节点或元素集合为空时抛出</exception>
public static void AddRangeAfter<T>(this LinkedList<T> list, LinkedListNode<T> node, IEnumerable<T> items)
{
ArgumentNullException.ThrowIfNull(list);
ArgumentNullException.ThrowIfNull(node);
ArgumentNullException.ThrowIfNull(items);
var currentNode = node;
foreach (var item in items)
{
currentNode = list.AddAfter(currentNode, item);
}
}
/// <summary>
/// 在指定节点前批量插入元素
/// </summary>
/// <typeparam name="T">链表元素类型</typeparam>
/// <param name="list">链表实例</param>
/// <param name="node">参考节点</param>
/// <param name="items">要插入的元素集合</param>
/// <exception cref="ArgumentNullException">链表、节点或元素集合为空时抛出</exception>
public static void AddRangeBefore<T>(this LinkedList<T> list, LinkedListNode<T> node, IEnumerable<T> 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]);
}
}
/// <summary>
/// 查找满足条件的第一个节点
/// </summary>
/// <typeparam name="T">链表元素类型</typeparam>
/// <param name="list">链表实例</param>
/// <param name="predicate">匹配条件</param>
/// <returns>满足条件的第一个节点如果未找到则返回null</returns>
/// <exception cref="ArgumentNullException">链表或条件为空时抛出</exception>
public static LinkedListNode<T>? FindFirst<T>(this LinkedList<T> list, Func<T, bool> 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;
}
/// <summary>
/// 查找满足条件的最后一个节点
/// </summary>
/// <typeparam name="T">链表元素类型</typeparam>
/// <param name="list">链表实例</param>
/// <param name="predicate">匹配条件</param>
/// <returns>满足条件的最后一个节点如果未找到则返回null</returns>
/// <exception cref="ArgumentNullException">链表或条件为空时抛出</exception>
public static LinkedListNode<T>? FindLast<T>(this LinkedList<T> list, Func<T, bool> 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;
}
/// <summary>
/// 查找所有满足条件的节点
/// </summary>
/// <typeparam name="T">链表元素类型</typeparam>
/// <param name="list">链表实例</param>
/// <param name="predicate">匹配条件</param>
/// <returns>满足条件的所有节点</returns>
/// <exception cref="ArgumentNullException">链表或条件为空时抛出</exception>
public static IEnumerable<LinkedListNode<T>> FindAll<T>(this LinkedList<T> list, Func<T, bool> predicate)
{
ArgumentNullException.ThrowIfNull(list);
ArgumentNullException.ThrowIfNull(predicate);
var result = new List<LinkedListNode<T>>();
var current = list.First;
while (current != null)
{
if (predicate(current.Value))
{
result.Add(current);
}
current = current.Next;
}
return result;
}
/// <summary>
/// 移除所有满足条件的节点
/// </summary>
/// <typeparam name="T">链表元素类型</typeparam>
/// <param name="list">链表实例</param>
/// <param name="predicate">移除条件</param>
/// <returns>移除的节点数量</returns>
/// <exception cref="ArgumentNullException">链表或条件为空时抛出</exception>
public static int RemoveAll<T>(this LinkedList<T> list, Func<T, bool> 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;
}
/// <summary>
/// 反转链表
/// </summary>
/// <typeparam name="T">链表元素类型</typeparam>
/// <param name="list">链表实例</param>
/// <exception cref="ArgumentNullException">链表为空时抛出</exception>
public static void Reverse<T>(this LinkedList<T> 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);
}
}
/// <summary>
/// 获取指定索引处的节点
/// </summary>
/// <typeparam name="T">链表元素类型</typeparam>
/// <param name="list">链表实例</param>
/// <param name="index">索引位置</param>
/// <returns>指定索引处的节点</returns>
/// <exception cref="ArgumentNullException">链表为空时抛出</exception>
/// <exception cref="ArgumentOutOfRangeException">索引超出范围时抛出</exception>
public static LinkedListNode<T> GetNodeAt<T>(this LinkedList<T> list, int index)
{
ArgumentNullException.ThrowIfNull(list);
if (index < 0 || index >= list.Count)
{
throw new ArgumentOutOfRangeException(nameof(index), "索引超出范围");
}
LinkedListNode<T>? 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!;
}
/// <summary>
/// 安全获取指定索引处的节点
/// </summary>
/// <typeparam name="T">链表元素类型</typeparam>
/// <param name="list">链表实例</param>
/// <param name="index">索引位置</param>
/// <param name="node">获取到的节点</param>
/// <returns>是否成功获取节点</returns>
public static bool TryGetNodeAt<T>(this LinkedList<T> list, int index, out LinkedListNode<T>? node)
{
node = null;
if (index < 0 || index >= list.Count)
{
return false;
}
try
{
node = list.GetNodeAt(index);
return true;
}
catch
{
return false;
}
}
/// <summary>
/// 检查链表是否为空
/// </summary>
/// <typeparam name="T">链表元素类型</typeparam>
/// <param name="list">链表实例</param>
/// <returns>链表是否为空</returns>
public static bool IsEmpty<T>(this LinkedList<T> list)
{
return list?.Count == 0;
}
/// <summary>
/// 检查链表是否不为空
/// </summary>
/// <typeparam name="T">链表元素类型</typeparam>
/// <param name="list">链表实例</param>
/// <returns>链表是否不为空</returns>
public static bool IsNotEmpty<T>(this LinkedList<T> list)
{
return list?.Count > 0;
}
/// <summary>
/// 将链表转换为数组,保持顺序
/// </summary>
/// <typeparam name="T">链表元素类型</typeparam>
/// <param name="list">链表实例</param>
/// <returns>包含链表所有元素的数组</returns>
/// <exception cref="ArgumentNullException">链表为空时抛出</exception>
public static T[] ToArrayPreserveOrder<T>(this LinkedList<T> list)
{
ArgumentNullException.ThrowIfNull(list);
return [.. list];
}
/// <summary>
/// 复制链表
/// </summary>
/// <typeparam name="T">链表元素类型</typeparam>
/// <param name="list">原链表</param>
/// <returns>复制的新链表</returns>
/// <exception cref="ArgumentNullException">链表为空时抛出</exception>
public static LinkedList<T> Clone<T>(this LinkedList<T> list)
{
ArgumentNullException.ThrowIfNull(list);
return new LinkedList<T>(list);
}
/// <summary>
/// 对链表中的每个元素执行指定操作
/// </summary>
/// <typeparam name="T">链表元素类型</typeparam>
/// <param name="list">链表实例</param>
/// <param name="action">要执行的操作</param>
/// <exception cref="ArgumentNullException">链表或操作为空时抛出</exception>
public static void ForEach<T>(this LinkedList<T> list, Action<T> action)
{
ArgumentNullException.ThrowIfNull(list);
ArgumentNullException.ThrowIfNull(action);
var current = list.First;
while (current != null)
{
action(current.Value);
current = current.Next;
}
}
/// <summary>
/// 对链表中的每个元素执行指定操作(带索引)
/// </summary>
/// <typeparam name="T">链表元素类型</typeparam>
/// <param name="list">链表实例</param>
/// <param name="action">要执行的操作,参数为元素和索引</param>
/// <exception cref="ArgumentNullException">链表或操作为空时抛出</exception>
public static void ForEach<T>(this LinkedList<T> list, Action<T, int> action)
{
ArgumentNullException.ThrowIfNull(list);
ArgumentNullException.ThrowIfNull(action);
var index = 0;
var current = list.First;
while (current != null)
{
action(current.Value, index++);
current = current.Next;
}
}
/// <summary>
/// 对链表中的每个节点执行指定操作
/// </summary>
/// <typeparam name="T">链表元素类型</typeparam>
/// <param name="list">链表实例</param>
/// <param name="action">要执行的操作</param>
/// <exception cref="ArgumentNullException">链表或操作为空时抛出</exception>
public static void ForEachNode<T>(this LinkedList<T> list, Action<LinkedListNode<T>> action)
{
ArgumentNullException.ThrowIfNull(list);
ArgumentNullException.ThrowIfNull(action);
var current = list.First;
while (current != null)
{
var next = current.Next; // 保存下一个节点,防止操作中删除当前节点
action(current);
current = next;
}
}
/// <summary>
/// 统计满足条件的元素数量
/// </summary>
/// <typeparam name="T">链表元素类型</typeparam>
/// <param name="list">链表实例</param>
/// <param name="predicate">匹配条件</param>
/// <returns>满足条件的元素数量</returns>
/// <exception cref="ArgumentNullException">链表或条件为空时抛出</exception>
public static int Count<T>(this LinkedList<T> list, Func<T, bool> predicate)
{
ArgumentNullException.ThrowIfNull(list);
ArgumentNullException.ThrowIfNull(predicate);
return list.Count(predicate);
}
/// <summary>
/// 检查是否包含满足条件的元素
/// </summary>
/// <typeparam name="T">链表元素类型</typeparam>
/// <param name="list">链表实例</param>
/// <param name="predicate">匹配条件</param>
/// <returns>是否包含满足条件的元素</returns>
/// <exception cref="ArgumentNullException">链表或条件为空时抛出</exception>
public static bool Any<T>(this LinkedList<T> list, Func<T, bool> predicate)
{
ArgumentNullException.ThrowIfNull(list);
ArgumentNullException.ThrowIfNull(predicate);
return list.Any(predicate);
}
/// <summary>
/// 检查是否所有元素都满足条件
/// </summary>
/// <typeparam name="T">链表元素类型</typeparam>
/// <param name="list">链表实例</param>
/// <param name="predicate">匹配条件</param>
/// <returns>是否所有元素都满足条件</returns>
/// <exception cref="ArgumentNullException">链表或条件为空时抛出</exception>
public static bool All<T>(this LinkedList<T> list, Func<T, bool> predicate)
{
ArgumentNullException.ThrowIfNull(list);
ArgumentNullException.ThrowIfNull(predicate);
return list.All(predicate);
}
/// <summary>
/// 创建一个新链表,包含满足条件的元素
/// </summary>
/// <typeparam name="T">链表元素类型</typeparam>
/// <param name="list">原链表</param>
/// <param name="predicate">筛选条件</param>
/// <returns>包含满足条件元素的新链表</returns>
/// <exception cref="ArgumentNullException">链表或条件为空时抛出</exception>
public static LinkedList<T> Where<T>(this LinkedList<T> list, Func<T, bool> predicate)
{
ArgumentNullException.ThrowIfNull(list);
ArgumentNullException.ThrowIfNull(predicate);
var result = new LinkedList<T>();
var current = list.First;
while (current != null)
{
if (predicate(current.Value))
{
result.AddLast(current.Value);
}
current = current.Next;
}
return result;
}
/// <summary>
/// 创建一个新链表,包含转换后的元素
/// </summary>
/// <typeparam name="TSource">原链表元素类型</typeparam>
/// <typeparam name="TResult">目标链表元素类型</typeparam>
/// <param name="list">原链表</param>
/// <param name="selector">转换函数</param>
/// <returns>包含转换后元素的新链表</returns>
/// <exception cref="ArgumentNullException">链表或转换函数为空时抛出</exception>
public static LinkedList<TResult> Select<TSource, TResult>(this LinkedList<TSource> list, Func<TSource, TResult> selector)
{
ArgumentNullException.ThrowIfNull(list);
ArgumentNullException.ThrowIfNull(selector);
var result = new LinkedList<TResult>();
var current = list.First;
while (current != null)
{
result.AddLast(selector(current.Value));
current = current.Next;
}
return result;
}
/// <summary>
/// 合并两个链表
/// </summary>
/// <typeparam name="T">链表元素类型</typeparam>
/// <param name="first">第一个链表</param>
/// <param name="second">第二个链表</param>
/// <returns>合并后的新链表</returns>
/// <exception cref="ArgumentNullException">任一链表为空时抛出</exception>
public static LinkedList<T> Concat<T>(this LinkedList<T> first, LinkedList<T> second)
{
ArgumentNullException.ThrowIfNull(first);
ArgumentNullException.ThrowIfNull(second);
var result = new LinkedList<T>(first);
var current = second.First;
while (current != null)
{
result.AddLast(current.Value);
current = current.Next;
}
return result;
}
/// <summary>
/// 限制链表的最大长度,超出时移除头部元素
/// </summary>
/// <typeparam name="T">链表元素类型</typeparam>
/// <param name="list">链表实例</param>
/// <param name="maxSize">最大长度</param>
/// <exception cref="ArgumentNullException">链表为空时抛出</exception>
/// <exception cref="ArgumentOutOfRangeException">最大长度小于0时抛出</exception>
public static void LimitSize<T>(this LinkedList<T> list, int maxSize)
{
ArgumentNullException.ThrowIfNull(list);
if (maxSize < 0)
{
throw new ArgumentOutOfRangeException(nameof(maxSize), "最大长度不能小于0");
}
while (list.Count > maxSize)
{
list.RemoveFirst();
}
}
/// <summary>
/// 安全地添加元素到末尾,如果链表已满则移除头部元素
/// </summary>
/// <typeparam name="T">链表元素类型</typeparam>
/// <param name="list">链表实例</param>
/// <param name="item">要添加的元素</param>
/// <param name="maxSize">链表最大长度</param>
/// <returns>被移除的元素(如果有)</returns>
/// <exception cref="ArgumentNullException">链表为空时抛出</exception>
/// <exception cref="ArgumentOutOfRangeException">最大长度小于1时抛出</exception>
public static T? AddLastWithLimit<T>(this LinkedList<T> 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;
}
}

View File

@ -0,0 +1,374 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using System.Diagnostics.CodeAnalysis;
namespace Admin.NET.Core;
/// <summary>
/// 列表扩展方法
/// </summary>
public static class ListExtensions
{
/// <summary>
/// 在指定索引的位置插入一个序列的项到列表中
/// </summary>
/// <typeparam name="T">列表中项的类型</typeparam>
/// <param name="source">要操作的列表</param>
/// <param name="index">要插入序列的起始索引</param>
/// <param name="items">要插入的项的集合</param>
public static void InsertRange<T>(this IList<T> source, int index, IEnumerable<T> items)
{
foreach (var item in items)
{
source.Insert(index++, item);
}
}
/// <summary>
/// 查找列表中满足特定条件的第一个项的索引
/// </summary>
/// <typeparam name="T">列表中项的类型</typeparam>
/// <param name="source">要搜索的列表</param>
/// <param name="selector">用于测试列表中每个项的条件</param>
/// <returns>满足条件项的索引,如果未找到则返回 -1</returns>
public static int FindIndex<T>(this IList<T> source, Predicate<T> selector)
{
for (var i = 0; i < source.Count; ++i)
{
if (selector(source[i]))
{
return i;
}
}
return -1;
}
/// <summary>
/// 在列表开头添加一个项
/// </summary>
/// <typeparam name="T">列表中项的类型</typeparam>
/// <param name="source">要操作的列表</param>
/// <param name="item">要添加的项</param>
public static void AddFirst<T>(this IList<T> source, T item)
{
source.Insert(0, item);
}
/// <summary>
/// 在列表末尾添加一个项
/// </summary>
/// <typeparam name="T">列表中项的类型</typeparam>
/// <param name="source">要操作的列表</param>
/// <param name="item">要添加的项</param>
public static void AddLast<T>(this IList<T> source, T item)
{
source.Insert(source.Count, item);
}
/// <summary>
/// 在列表中指定项之后插入一个新项
/// </summary>
/// <typeparam name="T">列表中项的类型</typeparam>
/// <param name="source">要操作的列表</param>
/// <param name="existingItem">列表中已存在的项</param>
/// <param name="item">要插入的新项</param>
public static void InsertAfter<T>(this IList<T> source, T existingItem, T item)
{
var index = source.IndexOf(existingItem);
if (index < 0)
{
source.AddFirst(item);
return;
}
source.Insert(index + 1, item);
}
/// <summary>
/// 根据选择器找到的项之后插入一个新项
/// </summary>
/// <typeparam name="T">列表中项的类型</typeparam>
/// <param name="source">要操作的列表</param>
/// <param name="selector">用于查找应插入新项之后项的选择器</param>
/// <param name="item">要插入的新项</param>
public static void InsertAfter<T>(this IList<T> source, Predicate<T> selector, T item)
{
var index = source.FindIndex(selector);
if (index < 0)
{
source.AddFirst(item);
return;
}
source.Insert(index + 1, item);
}
/// <summary>
/// 在列表中指定项之前插入一个新项
/// </summary>
/// <typeparam name="T">列表中项的类型</typeparam>
/// <param name="source">要操作的列表</param>
/// <param name="existingItem">列表中已存在的项</param>
/// <param name="item">要插入的新项</param>
public static void InsertBefore<T>(this IList<T> source, T existingItem, T item)
{
var index = source.IndexOf(existingItem);
if (index < 0)
{
source.AddLast(item);
return;
}
source.Insert(index, item);
}
/// <summary>
/// 根据选择器找到的项之前插入一个新项
/// </summary>
/// <typeparam name="T">列表中项的类型</typeparam>
/// <param name="source">要操作的列表</param>
/// <param name="selector">用于查找应插入新项之前项的选择器</param>
/// <param name="item">要插入的新项</param>
public static void InsertBefore<T>(this IList<T> source, Predicate<T> selector, T item)
{
var index = source.FindIndex(selector);
if (index < 0)
{
source.AddLast(item);
return;
}
source.Insert(index, item);
}
/// <summary>
/// 遍历列表,替换所有满足特定条件的项为指定的新项
/// </summary>
/// <typeparam name="T">列表中项的类型</typeparam>
/// <param name="source">要操作的列表</param>
/// <param name="selector">用于测试列表中每个项的条件</param>
/// <param name="item">要替换的新项</param>
public static void ReplaceWhile<T>(this IList<T> source, Predicate<T> selector, T item)
{
for (var i = 0; i < source.Count; i++)
{
if (selector(source[i]))
{
source[i] = item;
}
}
}
/// <summary>
/// 遍历列表,替换所有满足特定条件的项为由工厂方法生成的新项
/// </summary>
/// <typeparam name="T">列表中项的类型</typeparam>
/// <param name="source">要操作的列表</param>
/// <param name="selector">用于测试列表中每个项的条件</param>
/// <param name="itemFactory">一个工厂方法,用于生成要替换的新项</param>
public static void ReplaceWhile<T>(this IList<T> source, Predicate<T> selector, Func<T, T> itemFactory)
{
for (var i = 0; i < source.Count; i++)
{
var item = source[i];
if (selector(item))
{
source[i] = itemFactory(item);
}
}
}
/// <summary>
/// 遍历列表,替换第一个满足特定条件的项为指定的新项
/// </summary>
/// <typeparam name="T">列表中项的类型</typeparam>
/// <param name="source">要操作的列表</param>
/// <param name="selector">用于测试列表中每个项的条件</param>
/// <param name="item">要替换的新项</param>
public static void ReplaceOne<T>(this IList<T> source, Predicate<T> selector, T item)
{
for (var i = 0; i < source.Count; i++)
{
if (!selector(source[i]))
{
continue;
}
source[i] = item;
return;
}
}
/// <summary>
/// 遍历列表,替换第一个满足特定条件的项为由工厂方法生成的新项
/// </summary>
/// <typeparam name="T">列表中项的类型</typeparam>
/// <param name="source">要操作的列表</param>
/// <param name="selector">用于测试列表中每个项的条件</param>
/// <param name="itemFactory">一个工厂方法,用于生成要替换的新项</param>
public static void ReplaceOne<T>(this IList<T> source, Predicate<T> selector, Func<T, T> itemFactory)
{
for (var i = 0; i < source.Count; i++)
{
var item = source[i];
if (!selector(item))
{
continue;
}
source[i] = itemFactory(item);
return;
}
}
/// <summary>
/// 遍历列表,替换第一个匹配指定项的项为新项
/// </summary>
/// <typeparam name="T">列表中项的类型</typeparam>
/// <param name="source">要操作的列表</param>
/// <param name="item">要被替换的项</param>
/// <param name="replaceWith">新项</param>
public static void ReplaceOne<T>(this IList<T> source, T item, T replaceWith)
{
for (var i = 0; i < source.Count; i++)
{
if (Comparer<T>.Default.Compare(source[i], item) != 0)
{
continue;
}
source[i] = replaceWith;
return;
}
}
/// <summary>
/// 根据给定的选择器找到列表中的项,并将其移动到目标索引位置
/// </summary>
/// <typeparam name="T">列表中项的类型</typeparam>
/// <param name="source">要操作的列表</param>
/// <param name="selector">用于选择要移动的项的选择器</param>
/// <param name="targetIndex">项移动到的目标索引位置</param>
public static void MoveItem<T>(this List<T> source, Predicate<T> 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);
}
/// <summary>
/// 尝试获取列表中满足特定条件的第一个项,如果没有找到则添加新项
/// </summary>
/// <typeparam name="T">列表中项的类型</typeparam>
/// <param name="source">要操作的列表</param>
/// <param name="selector">用于选择项的谓词</param>
/// <param name="factory">如果没有找到匹配项,则用于创建新项的工厂方法</param>
/// <returns>返回找到的项或新添加的项</returns>
public static T GetOrAdd<T>(this IList<T> source, Func<T, bool> selector, Func<T> factory)
{
_ = NotNull(source, nameof(source));
var item = source.FirstOrDefault(selector);
if (item is not null && !EqualityComparer<T>.Default.Equals(item, default))
{
return item;
}
item = factory();
source.Add(item);
return item;
}
/// <summary>
/// 从列表中随机获取一个元素
/// </summary>
/// <typeparam name="T">列表中项的类型</typeparam>
/// <param name="source">要操作的列表</param>
/// <returns>随机选中的元素</returns>
/// <exception cref="ArgumentException">当列表为空时抛出异常</exception>
public static T GetRandom<T>(this IList<T> source)
{
_ = NotNull(source, nameof(source));
if (source.Count == 0)
{
throw new ArgumentException("列表不能为空", nameof(source));
}
var randomIndex = Random.Shared.Next(source.Count);
return source[randomIndex];
}
/// <summary>
/// 尝试从列表中随机获取一个元素
/// </summary>
/// <typeparam name="T">列表中项的类型</typeparam>
/// <param name="source">要操作的列表</param>
/// <param name="result">输出参数,包含随机选中的元素(如果成功)</param>
/// <returns>如果列表不为空则返回 true否则返回 false</returns>
public static bool TryGetRandom<T>(this IList<T> 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;
}
/// <summary>
/// 数据不为空判断
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="value"></param>
/// <param name="parameterName"></param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public static T NotNull<T>([NotNull] T? value, string parameterName)
{
return value is null ? throw new ArgumentNullException(parameterName) : value;
}
/// <summary>
/// 判断当前值是否介于指定范围内
/// </summary>
/// <typeparam name="T">泛型</typeparam>
/// <param name="value">泛型对象</param>
/// <param name="start">范围起点</param>
/// <param name="end">范围终点</param>
/// <param name="leftEqual">是否可等于上限(默认等于)</param>
/// <param name="rightEqual">是否可等于下限(默认等于)</param>
/// <returns> 是否介于 </returns>
public static bool IsBetween<T>(this IComparable<T> 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);
}
}

View File

@ -0,0 +1,361 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core;
/// <summary>
/// 队列扩展方法
/// </summary>
public static class QueueExtensions
{
/// <summary>
/// 批量入队
/// </summary>
/// <typeparam name="T">队列元素类型</typeparam>
/// <param name="queue">队列实例</param>
/// <param name="items">要入队的元素集合</param>
/// <exception cref="ArgumentNullException">队列或元素集合为空时抛出</exception>
public static void EnqueueRange<T>(this Queue<T> queue, IEnumerable<T> items)
{
ArgumentNullException.ThrowIfNull(queue);
ArgumentNullException.ThrowIfNull(items);
foreach (var item in items)
{
queue.Enqueue(item);
}
}
/// <summary>
/// 批量出队
/// </summary>
/// <typeparam name="T">队列元素类型</typeparam>
/// <param name="queue">队列实例</param>
/// <param name="count">要出队的元素数量</param>
/// <returns>出队的元素集合</returns>
/// <exception cref="ArgumentNullException">队列为空时抛出</exception>
/// <exception cref="ArgumentOutOfRangeException">数量小于0或大于队列长度时抛出</exception>
public static IEnumerable<T> DequeueRange<T>(this Queue<T> 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<T>(count);
for (var i = 0; i < count; i++)
{
result.Add(queue.Dequeue());
}
return result;
}
/// <summary>
/// 尝试出队多个元素
/// </summary>
/// <typeparam name="T">队列元素类型</typeparam>
/// <param name="queue">队列实例</param>
/// <param name="count">要出队的元素数量</param>
/// <param name="items">出队的元素集合</param>
/// <returns>是否成功出队指定数量的元素</returns>
public static bool TryDequeueRange<T>(this Queue<T> queue, int count, out IEnumerable<T> items)
{
items = [];
if (count < 0 || count > queue.Count)
{
return false;
}
var result = new List<T>(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<T>();
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;
}
/// <summary>
/// 清空队列并返回所有元素
/// </summary>
/// <typeparam name="T">队列元素类型</typeparam>
/// <param name="queue">队列实例</param>
/// <returns>队列中的所有元素</returns>
/// <exception cref="ArgumentNullException">队列为空时抛出</exception>
public static IEnumerable<T> DrainToList<T>(this Queue<T> queue)
{
ArgumentNullException.ThrowIfNull(queue);
var result = new List<T>(queue.Count);
while (queue.Count > 0)
{
result.Add(queue.Dequeue());
}
return result;
}
/// <summary>
/// 安全地查看队列头部元素
/// </summary>
/// <typeparam name="T">队列元素类型</typeparam>
/// <param name="queue">队列实例</param>
/// <param name="item">队列头部元素</param>
/// <returns>是否成功查看</returns>
public static bool TryPeek<T>(this Queue<T> queue, out T? item)
{
item = default;
if (queue.Count == 0)
{
return false;
}
item = queue.Peek();
return true;
}
/// <summary>
/// 检查队列是否为空
/// </summary>
/// <typeparam name="T">队列元素类型</typeparam>
/// <param name="queue">队列实例</param>
/// <returns>队列是否为空</returns>
public static bool IsEmpty<T>(this Queue<T> queue)
{
return queue?.Count == 0;
}
/// <summary>
/// 检查队列是否不为空
/// </summary>
/// <typeparam name="T">队列元素类型</typeparam>
/// <param name="queue">队列实例</param>
/// <returns>队列是否不为空</returns>
public static bool IsNotEmpty<T>(this Queue<T> queue)
{
return queue?.Count > 0;
}
/// <summary>
/// 将队列转换为数组
/// </summary>
/// <typeparam name="T">队列元素类型</typeparam>
/// <param name="queue">队列实例</param>
/// <returns>包含队列所有元素的数组</returns>
/// <exception cref="ArgumentNullException">队列为空时抛出</exception>
public static T[] ToArrayPreserveOrder<T>(this Queue<T> queue)
{
ArgumentNullException.ThrowIfNull(queue);
return [.. queue];
}
/// <summary>
/// 复制队列
/// </summary>
/// <typeparam name="T">队列元素类型</typeparam>
/// <param name="queue">原队列</param>
/// <returns>复制的新队列</returns>
/// <exception cref="ArgumentNullException">队列为空时抛出</exception>
public static Queue<T> Clone<T>(this Queue<T> queue)
{
ArgumentNullException.ThrowIfNull(queue);
return new Queue<T>(queue);
}
/// <summary>
/// 查找队列中是否包含满足条件的元素
/// </summary>
/// <typeparam name="T">队列元素类型</typeparam>
/// <param name="queue">队列实例</param>
/// <param name="predicate">匹配条件</param>
/// <returns>是否包含满足条件的元素</returns>
/// <exception cref="ArgumentNullException">队列或条件为空时抛出</exception>
public static bool Contains<T>(this Queue<T> queue, Func<T, bool> predicate)
{
ArgumentNullException.ThrowIfNull(queue);
ArgumentNullException.ThrowIfNull(predicate);
return queue.Any(predicate);
}
/// <summary>
/// 统计队列中满足条件的元素数量
/// </summary>
/// <typeparam name="T">队列元素类型</typeparam>
/// <param name="queue">队列实例</param>
/// <param name="predicate">匹配条件</param>
/// <returns>满足条件的元素数量</returns>
/// <exception cref="ArgumentNullException">队列或条件为空时抛出</exception>
public static int Count<T>(this Queue<T> queue, Func<T, bool> predicate)
{
ArgumentNullException.ThrowIfNull(queue);
ArgumentNullException.ThrowIfNull(predicate);
return queue.Count(predicate);
}
/// <summary>
/// 对队列中的每个元素执行指定操作
/// </summary>
/// <typeparam name="T">队列元素类型</typeparam>
/// <param name="queue">队列实例</param>
/// <param name="action">要执行的操作</param>
/// <exception cref="ArgumentNullException">队列或操作为空时抛出</exception>
public static void ForEach<T>(this Queue<T> queue, Action<T> action)
{
ArgumentNullException.ThrowIfNull(queue);
ArgumentNullException.ThrowIfNull(action);
foreach (var item in queue)
{
action(item);
}
}
/// <summary>
/// 对队列中的每个元素执行指定操作(带索引)
/// </summary>
/// <typeparam name="T">队列元素类型</typeparam>
/// <param name="queue">队列实例</param>
/// <param name="action">要执行的操作,参数为元素和索引</param>
/// <exception cref="ArgumentNullException">队列或操作为空时抛出</exception>
public static void ForEach<T>(this Queue<T> queue, Action<T, int> action)
{
ArgumentNullException.ThrowIfNull(queue);
ArgumentNullException.ThrowIfNull(action);
var index = 0;
foreach (var item in queue)
{
action(item, index++);
}
}
/// <summary>
/// 创建一个新队列,包含满足条件的元素
/// </summary>
/// <typeparam name="T">队列元素类型</typeparam>
/// <param name="queue">原队列</param>
/// <param name="predicate">筛选条件</param>
/// <returns>包含满足条件元素的新队列</returns>
/// <exception cref="ArgumentNullException">队列或条件为空时抛出</exception>
public static Queue<T> Where<T>(this Queue<T> queue, Func<T, bool> predicate)
{
ArgumentNullException.ThrowIfNull(queue);
ArgumentNullException.ThrowIfNull(predicate);
var result = new Queue<T>();
foreach (var item in queue.Where(predicate))
{
result.Enqueue(item);
}
return result;
}
/// <summary>
/// 创建一个新队列,包含转换后的元素
/// </summary>
/// <typeparam name="TSource">原队列元素类型</typeparam>
/// <typeparam name="TResult">目标队列元素类型</typeparam>
/// <param name="queue">原队列</param>
/// <param name="selector">转换函数</param>
/// <returns>包含转换后元素的新队列</returns>
/// <exception cref="ArgumentNullException">队列或转换函数为空时抛出</exception>
public static Queue<TResult> Select<TSource, TResult>(this Queue<TSource> queue, Func<TSource, TResult> selector)
{
ArgumentNullException.ThrowIfNull(queue);
ArgumentNullException.ThrowIfNull(selector);
var result = new Queue<TResult>();
foreach (var item in queue.Select(selector))
{
result.Enqueue(item);
}
return result;
}
/// <summary>
/// 限制队列的最大长度,超出时移除最旧的元素
/// </summary>
/// <typeparam name="T">队列元素类型</typeparam>
/// <param name="queue">队列实例</param>
/// <param name="maxSize">最大长度</param>
/// <exception cref="ArgumentNullException">队列为空时抛出</exception>
/// <exception cref="ArgumentOutOfRangeException">最大长度小于0时抛出</exception>
public static void LimitSize<T>(this Queue<T> queue, int maxSize)
{
ArgumentNullException.ThrowIfNull(queue);
if (maxSize < 0)
{
throw new ArgumentOutOfRangeException(nameof(maxSize), "最大长度不能小于0");
}
while (queue.Count > maxSize)
{
queue.Dequeue();
}
}
/// <summary>
/// 安全地入队元素,如果队列已满则移除最旧的元素
/// </summary>
/// <typeparam name="T">队列元素类型</typeparam>
/// <param name="queue">队列实例</param>
/// <param name="item">要入队的元素</param>
/// <param name="maxSize">队列最大长度</param>
/// <returns>被移除的元素(如果有)</returns>
/// <exception cref="ArgumentNullException">队列为空时抛出</exception>
/// <exception cref="ArgumentOutOfRangeException">最大长度小于1时抛出</exception>
public static T? EnqueueWithLimit<T>(this Queue<T> 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;
}
}

View File

@ -0,0 +1,542 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core;
/// <summary>
/// 堆栈扩展方法
/// </summary>
public static class StackExtensions
{
/// <summary>
/// 批量入栈
/// </summary>
/// <typeparam name="T">堆栈元素类型</typeparam>
/// <param name="stack">堆栈实例</param>
/// <param name="items">要入栈的元素集合</param>
/// <exception cref="ArgumentNullException">堆栈或元素集合为空时抛出</exception>
public static void PushRange<T>(this Stack<T> stack, IEnumerable<T> items)
{
ArgumentNullException.ThrowIfNull(stack);
ArgumentNullException.ThrowIfNull(items);
foreach (var item in items)
{
stack.Push(item);
}
}
/// <summary>
/// 批量入栈(保持集合的原始顺序)
/// </summary>
/// <typeparam name="T">堆栈元素类型</typeparam>
/// <param name="stack">堆栈实例</param>
/// <param name="items">要入栈的元素集合</param>
/// <exception cref="ArgumentNullException">堆栈或元素集合为空时抛出</exception>
public static void PushRangeReversed<T>(this Stack<T> stack, IEnumerable<T> items)
{
ArgumentNullException.ThrowIfNull(stack);
ArgumentNullException.ThrowIfNull(items);
// 反转顺序入栈,使得弹出时保持原始顺序
var itemsArray = items.ToArray();
for (var i = itemsArray.Length - 1; i >= 0; i--)
{
stack.Push(itemsArray[i]);
}
}
/// <summary>
/// 批量出栈
/// </summary>
/// <typeparam name="T">堆栈元素类型</typeparam>
/// <param name="stack">堆栈实例</param>
/// <param name="count">要出栈的元素数量</param>
/// <returns>出栈的元素集合</returns>
/// <exception cref="ArgumentNullException">堆栈为空时抛出</exception>
/// <exception cref="ArgumentOutOfRangeException">数量小于0或大于堆栈长度时抛出</exception>
public static IEnumerable<T> PopRange<T>(this Stack<T> 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<T>(count);
for (var i = 0; i < count; i++)
{
result.Add(stack.Pop());
}
return result;
}
/// <summary>
/// 尝试出栈多个元素
/// </summary>
/// <typeparam name="T">堆栈元素类型</typeparam>
/// <param name="stack">堆栈实例</param>
/// <param name="count">要出栈的元素数量</param>
/// <param name="items">出栈的元素集合</param>
/// <returns>是否成功出栈指定数量的元素</returns>
public static bool TryPopRange<T>(this Stack<T> stack, int count, out IEnumerable<T> items)
{
items = [];
if (count < 0 || count > stack.Count)
{
return false;
}
var result = new List<T>(count);
var tempItems = new List<T>();
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;
}
/// <summary>
/// 清空堆栈并返回所有元素
/// </summary>
/// <typeparam name="T">堆栈元素类型</typeparam>
/// <param name="stack">堆栈实例</param>
/// <returns>堆栈中的所有元素</returns>
/// <exception cref="ArgumentNullException">堆栈为空时抛出</exception>
public static IEnumerable<T> DrainToList<T>(this Stack<T> stack)
{
ArgumentNullException.ThrowIfNull(stack);
var result = new List<T>(stack.Count);
while (stack.Count > 0)
{
result.Add(stack.Pop());
}
return result;
}
/// <summary>
/// 安全地查看堆栈顶部元素
/// </summary>
/// <typeparam name="T">堆栈元素类型</typeparam>
/// <param name="stack">堆栈实例</param>
/// <param name="item">堆栈顶部元素</param>
/// <returns>是否成功查看</returns>
public static bool TryPeek<T>(this Stack<T> stack, out T? item)
{
item = default;
if (stack.Count == 0)
{
return false;
}
item = stack.Peek();
return true;
}
/// <summary>
/// 安全地查看多个顶部元素(不出栈)
/// </summary>
/// <typeparam name="T">堆栈元素类型</typeparam>
/// <param name="stack">堆栈实例</param>
/// <param name="count">要查看的元素数量</param>
/// <returns>顶部指定数量的元素</returns>
/// <exception cref="ArgumentNullException">堆栈为空时抛出</exception>
/// <exception cref="ArgumentOutOfRangeException">数量小于0或大于堆栈长度时抛出</exception>
public static IEnumerable<T> PeekRange<T>(this Stack<T> 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);
}
/// <summary>
/// 检查堆栈是否为空
/// </summary>
/// <typeparam name="T">堆栈元素类型</typeparam>
/// <param name="stack">堆栈实例</param>
/// <returns>堆栈是否为空</returns>
public static bool IsEmpty<T>(this Stack<T> stack)
{
return stack?.Count == 0;
}
/// <summary>
/// 检查堆栈是否不为空
/// </summary>
/// <typeparam name="T">堆栈元素类型</typeparam>
/// <param name="stack">堆栈实例</param>
/// <returns>堆栈是否不为空</returns>
public static bool IsNotEmpty<T>(this Stack<T> stack)
{
return stack?.Count > 0;
}
/// <summary>
/// 将堆栈转换为数组,保持堆栈顺序
/// </summary>
/// <typeparam name="T">堆栈元素类型</typeparam>
/// <param name="stack">堆栈实例</param>
/// <returns>包含堆栈所有元素的数组</returns>
/// <exception cref="ArgumentNullException">堆栈为空时抛出</exception>
public static T[] ToArrayPreserveOrder<T>(this Stack<T> stack)
{
ArgumentNullException.ThrowIfNull(stack);
return [.. stack];
}
/// <summary>
/// 复制堆栈
/// </summary>
/// <typeparam name="T">堆栈元素类型</typeparam>
/// <param name="stack">原堆栈</param>
/// <returns>复制的新堆栈</returns>
/// <exception cref="ArgumentNullException">堆栈为空时抛出</exception>
public static Stack<T> Clone<T>(this Stack<T> stack)
{
ArgumentNullException.ThrowIfNull(stack);
// 保持原始堆栈的顺序
var items = stack.ToArray();
return new Stack<T>(items);
}
/// <summary>
/// 查找堆栈中是否包含满足条件的元素
/// </summary>
/// <typeparam name="T">堆栈元素类型</typeparam>
/// <param name="stack">堆栈实例</param>
/// <param name="predicate">匹配条件</param>
/// <returns>是否包含满足条件的元素</returns>
/// <exception cref="ArgumentNullException">堆栈或条件为空时抛出</exception>
public static bool Contains<T>(this Stack<T> stack, Func<T, bool> predicate)
{
ArgumentNullException.ThrowIfNull(stack);
ArgumentNullException.ThrowIfNull(predicate);
return stack.Any(predicate);
}
/// <summary>
/// 统计堆栈中满足条件的元素数量
/// </summary>
/// <typeparam name="T">堆栈元素类型</typeparam>
/// <param name="stack">堆栈实例</param>
/// <param name="predicate">匹配条件</param>
/// <returns>满足条件的元素数量</returns>
/// <exception cref="ArgumentNullException">堆栈或条件为空时抛出</exception>
public static int Count<T>(this Stack<T> stack, Func<T, bool> predicate)
{
ArgumentNullException.ThrowIfNull(stack);
ArgumentNullException.ThrowIfNull(predicate);
return stack.Count(predicate);
}
/// <summary>
/// 对堆栈中的每个元素执行指定操作(从顶部到底部)
/// </summary>
/// <typeparam name="T">堆栈元素类型</typeparam>
/// <param name="stack">堆栈实例</param>
/// <param name="action">要执行的操作</param>
/// <exception cref="ArgumentNullException">堆栈或操作为空时抛出</exception>
public static void ForEach<T>(this Stack<T> stack, Action<T> action)
{
ArgumentNullException.ThrowIfNull(stack);
ArgumentNullException.ThrowIfNull(action);
foreach (var item in stack)
{
action(item);
}
}
/// <summary>
/// 对堆栈中的每个元素执行指定操作(带索引,从顶部到底部)
/// </summary>
/// <typeparam name="T">堆栈元素类型</typeparam>
/// <param name="stack">堆栈实例</param>
/// <param name="action">要执行的操作,参数为元素和索引</param>
/// <exception cref="ArgumentNullException">堆栈或操作为空时抛出</exception>
public static void ForEach<T>(this Stack<T> stack, Action<T, int> action)
{
ArgumentNullException.ThrowIfNull(stack);
ArgumentNullException.ThrowIfNull(action);
var index = 0;
foreach (var item in stack)
{
action(item, index++);
}
}
/// <summary>
/// 创建一个新堆栈,包含满足条件的元素
/// </summary>
/// <typeparam name="T">堆栈元素类型</typeparam>
/// <param name="stack">原堆栈</param>
/// <param name="predicate">筛选条件</param>
/// <returns>包含满足条件元素的新堆栈</returns>
/// <exception cref="ArgumentNullException">堆栈或条件为空时抛出</exception>
public static Stack<T> Where<T>(this Stack<T> stack, Func<T, bool> predicate)
{
ArgumentNullException.ThrowIfNull(stack);
ArgumentNullException.ThrowIfNull(predicate);
var filteredItems = stack.Where(predicate).ToArray();
return new Stack<T>(filteredItems);
}
/// <summary>
/// 创建一个新堆栈,包含转换后的元素
/// </summary>
/// <typeparam name="TSource">原堆栈元素类型</typeparam>
/// <typeparam name="TResult">目标堆栈元素类型</typeparam>
/// <param name="stack">原堆栈</param>
/// <param name="selector">转换函数</param>
/// <returns>包含转换后元素的新堆栈</returns>
/// <exception cref="ArgumentNullException">堆栈或转换函数为空时抛出</exception>
public static Stack<TResult> Select<TSource, TResult>(this Stack<TSource> stack, Func<TSource, TResult> selector)
{
ArgumentNullException.ThrowIfNull(stack);
ArgumentNullException.ThrowIfNull(selector);
var transformedItems = stack.Select(selector).ToArray();
return new Stack<TResult>(transformedItems);
}
/// <summary>
/// 反转堆栈中的元素顺序
/// </summary>
/// <typeparam name="T">堆栈元素类型</typeparam>
/// <param name="stack">堆栈实例</param>
/// <exception cref="ArgumentNullException">堆栈为空时抛出</exception>
public static void Reverse<T>(this Stack<T> 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);
}
}
/// <summary>
/// 获取堆栈的深度副本(递归反转以保持原始顺序)
/// </summary>
/// <typeparam name="T">堆栈元素类型</typeparam>
/// <param name="stack">原堆栈</param>
/// <returns>深度副本的新堆栈</returns>
/// <exception cref="ArgumentNullException">堆栈为空时抛出</exception>
public static Stack<T> DeepClone<T>(this Stack<T> stack)
{
ArgumentNullException.ThrowIfNull(stack);
var tempStack = new Stack<T>();
var result = new Stack<T>();
// 第一次反转到临时堆栈
foreach (var item in stack)
{
tempStack.Push(item);
}
// 第二次反转到结果堆栈,恢复原始顺序
while (tempStack.Count > 0)
{
result.Push(tempStack.Pop());
}
return result;
}
/// <summary>
/// 限制堆栈的最大长度,超出时移除底部元素
/// </summary>
/// <typeparam name="T">堆栈元素类型</typeparam>
/// <param name="stack">堆栈实例</param>
/// <param name="maxSize">最大长度</param>
/// <exception cref="ArgumentNullException">堆栈为空时抛出</exception>
/// <exception cref="ArgumentOutOfRangeException">最大长度小于0时抛出</exception>
public static void LimitSize<T>(this Stack<T> 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]);
}
}
/// <summary>
/// 安全地入栈元素,如果堆栈已满则移除底部元素
/// </summary>
/// <typeparam name="T">堆栈元素类型</typeparam>
/// <param name="stack">堆栈实例</param>
/// <param name="item">要入栈的元素</param>
/// <param name="maxSize">堆栈最大长度</param>
/// <returns>被移除的元素(如果有)</returns>
/// <exception cref="ArgumentNullException">堆栈为空时抛出</exception>
/// <exception cref="ArgumentOutOfRangeException">最大长度小于1时抛出</exception>
public static T? PushWithLimit<T>(this Stack<T> 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;
}
/// <summary>
/// 检查是否所有元素都满足条件
/// </summary>
/// <typeparam name="T">堆栈元素类型</typeparam>
/// <param name="stack">堆栈实例</param>
/// <param name="predicate">匹配条件</param>
/// <returns>是否所有元素都满足条件</returns>
/// <exception cref="ArgumentNullException">堆栈或条件为空时抛出</exception>
public static bool All<T>(this Stack<T> stack, Func<T, bool> predicate)
{
ArgumentNullException.ThrowIfNull(stack);
ArgumentNullException.ThrowIfNull(predicate);
return stack.All(predicate);
}
/// <summary>
/// 检查是否至少有一个元素满足条件
/// </summary>
/// <typeparam name="T">堆栈元素类型</typeparam>
/// <param name="stack">堆栈实例</param>
/// <param name="predicate">匹配条件</param>
/// <returns>是否至少有一个元素满足条件</returns>
/// <exception cref="ArgumentNullException">堆栈或条件为空时抛出</exception>
public static bool Any<T>(this Stack<T> stack, Func<T, bool> predicate)
{
ArgumentNullException.ThrowIfNull(stack);
ArgumentNullException.ThrowIfNull(predicate);
return stack.Any(predicate);
}
/// <summary>
/// 合并两个堆栈(第二个堆栈的元素将位于顶部)
/// </summary>
/// <typeparam name="T">堆栈元素类型</typeparam>
/// <param name="first">第一个堆栈</param>
/// <param name="second">第二个堆栈</param>
/// <returns>合并后的新堆栈</returns>
/// <exception cref="ArgumentNullException">任一堆栈为空时抛出</exception>
public static Stack<T> Concat<T>(this Stack<T> first, Stack<T> second)
{
ArgumentNullException.ThrowIfNull(first);
ArgumentNullException.ThrowIfNull(second);
var result = new Stack<T>();
// 先添加第一个堆栈的元素(从底部到顶部)
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;
}
}

View File

@ -0,0 +1,375 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core;
/// <summary>
/// 树扩展方法
/// </summary>
public static class TreeExtensions
{
/// <summary>
/// 转为树形结构
/// </summary>
/// <typeparam name="T">树节点数据类型</typeparam>
/// <param name="source">源数据集合</param>
/// <param name="isChild">判断父子关系的函数,第一个参数为父级,第二个参数为子级</param>
/// <returns>转换后的树形结构根节点集合</returns>
public static IEnumerable<TreeNode<T>> ToTree<T>(this IEnumerable<T> source, Func<T, T, bool> isChild)
{
var nodes = source.Select(value => new TreeNode<T>(value)).ToList();
var visited = new HashSet<T>();
foreach (var node in nodes)
{
if (visited.Contains(node.Value))
{
continue;
}
var stack = new Stack<TreeNode<T>>();
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)));
}
/// <summary>
/// 根据主键和父级主键生成树形结构
/// </summary>
/// <typeparam name="T">树节点数据类型</typeparam>
/// <param name="source">源数据集合</param>
/// <param name="keySelector">主键选择器</param>
/// <param name="parentKeySelector">父级主键选择器</param>
/// <returns>树形结构</returns>
public static IEnumerable<TreeNode<T>> ToTree<T>(this IEnumerable<T> source, Func<T, object> keySelector, Func<T, object> parentKeySelector)
{
var nodes = source.Select(value => new TreeNode<T>(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))));
}
/// <summary>
/// 添加子节点
/// </summary>
/// <typeparam name="T">树节点数据类型</typeparam>
/// <param name="parent">父节点</param>
/// <param name="value">要添加的子节点值</param>
/// <exception cref="ArgumentNullException"></exception>
public static void AddChild<T>(this TreeNode<T> parent, T value)
{
ArgumentNullException.ThrowIfNull(parent);
parent.Children.Add(new TreeNode<T>(value));
}
/// <summary>
/// 添加子节点到指定的父节点
/// </summary>
/// <typeparam name="T">树节点数据类型</typeparam>
/// <param name="source">源数据集合</param>
/// <param name="parent">父节点对象</param>
/// <param name="child">子节点对象</param>
/// <param name="keySelector">主键选择器</param>
/// <param name="parentKeySelector">父级主键选择器</param>
public static void AddChild<T>(this IEnumerable<TreeNode<T>> source, T parent, T child, Func<T, object> keySelector, Func<T, object> 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<T>(child));
}
/// <summary>
/// 删除节点
/// </summary>
/// <typeparam name="T">树节点数据类型</typeparam>
/// <param name="root">根节点</param>
/// <param name="value">要删除的节点值</param>
/// <returns>如果成功删除节点则返回 true否则返回 false</returns>
public static bool RemoveNode<T>(this TreeNode<T>? root, T value)
{
if (root is null)
{
return false;
}
foreach (var child in root.Children.ToList())
{
if (EqualityComparer<T>.Default.Equals(child.Value, value))
{
_ = root.Children.Remove(child);
return true;
}
if (RemoveNode(child, value))
{
return true;
}
}
return false;
}
/// <summary>
/// 深度优先遍历 (DFS)
/// </summary>
/// <typeparam name="T">树节点数据类型</typeparam>
/// <param name="root">根节点</param>
/// <returns>深度优先遍历的节点序列</returns>
public static IEnumerable<TreeNode<T>> DepthFirstTraversal<T>(this TreeNode<T>? 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;
}
}
}
/// <summary>
/// 深度优先遍历 (DFS) - 遍历树形结构中所有节点
/// </summary>
/// <typeparam name="T">树节点数据类型</typeparam>
/// <param name="source">树形结构根节点集合</param>
/// <returns>深度优先遍历的节点序列</returns>
public static IEnumerable<TreeNode<T>> DepthFirstTraversal<T>(this IEnumerable<TreeNode<T>>? source)
{
if (source is null)
{
yield break;
}
foreach (var root in source)
{
foreach (var node in root.DepthFirstTraversal())
{
yield return node;
}
}
}
/// <summary>
/// 广度优先遍历 (BFS)
/// </summary>
/// <typeparam name="T">树节点数据类型</typeparam>
/// <param name="root">根节点</param>
/// <returns>广度优先遍历的节点序列</returns>
public static IEnumerable<TreeNode<T>> BreadthFirstTraversal<T>(this TreeNode<T>? root)
{
if (root is null)
{
yield break;
}
var queue = new Queue<TreeNode<T>>();
queue.Enqueue(root);
while (queue.Count > 0)
{
var current = queue.Dequeue();
yield return current;
foreach (var child in current.Children)
{
queue.Enqueue(child);
}
}
}
/// <summary>
/// 查找节点 (DFS)
/// </summary>
/// <typeparam name="T">树节点数据类型</typeparam>
/// <param name="root">根节点</param>
/// <param name="value">要查找的节点值</param>
/// <returns>找到的节点,如果未找到则返回 null</returns>
public static TreeNode<T>? FindNode<T>(this TreeNode<T> root, T value)
{
return root.DepthFirstTraversal().FirstOrDefault(node => EqualityComparer<T>.Default.Equals(node.Value, value));
}
/// <summary>
/// 获取节点路径
/// </summary>
/// <typeparam name="T">树节点数据类型</typeparam>
/// <param name="root">根节点</param>
/// <param name="value">要获取路径的节点值</param>
/// <returns>从根节点到目标节点的路径,如果未找到则返回 null</returns>
public static List<TreeNode<T>>? GetPath<T>(this TreeNode<T> root, T value)
{
var path = new List<TreeNode<T>>();
return FindPath(root, value, path) ? path : null;
}
/// <summary>
/// 获取树的高度
/// </summary>
/// <typeparam name="T">树节点数据类型</typeparam>
/// <param name="root">根节点</param>
/// <returns>树的高度,空树返回 0</returns>
public static int GetHeight<T>(this TreeNode<T>? root)
{
return root is null ? 0 : 1 + root.Children.Select(child => child.GetHeight()).DefaultIfEmpty(0).Max();
}
/// <summary>
/// 获取叶子节点
/// </summary>
/// <typeparam name="T">树节点数据类型</typeparam>
/// <param name="root">根节点</param>
/// <returns>所有叶子节点的集合</returns>
public static IEnumerable<TreeNode<T>> GetLeafNodes<T>(this TreeNode<T> root)
{
return root.DepthFirstTraversal().Where(node => node.Children.Count == 0);
}
#region
/// <summary>
/// 查找路径
/// </summary>
/// <typeparam name="T">树节点数据类型</typeparam>
/// <param name="node">当前节点</param>
/// <param name="value">要查找的节点值</param>
/// <param name="path">路径记录</param>
/// <returns>如果找到目标节点则返回 true否则返回 false</returns>
private static bool FindPath<T>(TreeNode<T>? node, T value, List<TreeNode<T>> path)
{
if (node is null)
{
return false;
}
path.Add(node);
if (EqualityComparer<T>.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
/// <summary>
/// 设置对象属性值
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <typeparam name="TValue"></typeparam>
/// <param name="entity"></param>
/// <param name="propertyName"></param>
/// <param name="value"></param>
/// <returns></returns>
public static bool SetPropertyValue<TEntity, TValue>(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<Action<TEntity, TValue>>(body, paramObj, paramVal).Compile();
setValue(entity, value);
return true;
}
}
/// <summary>
/// 树节点数据传输对象
/// </summary>
/// <typeparam name="T"></typeparam>
public class TreeNode<T>
{
/// <summary>
/// 构造函数
/// </summary>
/// <param name="value">节点值</param>
public TreeNode(T value)
{
Value = value;
}
/// <summary>
/// 节点值
/// </summary>
public T Value { get; set; }
/// <summary>
/// 子节点
/// </summary>
public List<TreeNode<T>> Children { get; set; } = [];
}

View File

@ -0,0 +1,146 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core;
/// <summary>
/// 脚本执行助手类,支持执行 .sh、.ps1、.bat 脚本
/// </summary>
public static class ScriptExecutor
{
/// <summary>
/// 执行脚本
/// </summary>
/// <param name="scriptFilePath">脚本文件的完整路径</param>
/// <param name="arguments">传递给脚本的参数</param>
/// <returns>执行结果(标准输出和标准错误)</returns>
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("不支持的脚本类型")
};
}
/// <summary>
/// 执行 Shell 脚本(Linux/macOS)
/// </summary>
/// <param name="scriptFilePath">脚本文件的完整路径</param>
/// <param name="arguments">传递给脚本的参数</param>
/// <returns>执行结果</returns>
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);
}
/// <summary>
/// 执行 PowerShell 脚本(Windows)
/// </summary>
/// <param name="scriptFilePath">脚本文件的完整路径</param>
/// <param name="arguments">传递给脚本的参数</param>
/// <returns>执行结果</returns>
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);
}
/// <summary>
/// 执行批处理脚本(Windows)
/// </summary>
/// <param name="scriptFilePath">脚本文件的完整路径</param>
/// <param name="arguments">传递给脚本的参数</param>
/// <returns>执行结果</returns>
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);
}
/// <summary>
/// 执行进程并获取输出结果
/// </summary>
/// <param name="processStartInfo">进程启动信息</param>
/// <returns>执行结果</returns>
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();
}
}

View File

@ -0,0 +1,76 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core;
/// <summary>
/// ShellHelper
/// </summary>
public static class ShellHelper
{
/// <summary>
/// Unix 系统命令
/// </summary>
/// <param name="command">要执行的 Unix/Linux 命令</param>
/// <returns>命令执行后的标准输出结果</returns>
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;
}
/// <summary>
/// Windows 系统命令
/// </summary>
/// <param name="fileName">要执行的程序或命令文件名</param>
/// <param name="args">传递给程序的命令行参数</param>
/// <returns>命令执行后的标准输出结果</returns>
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;
}
}

View File

@ -15,7 +15,7 @@ namespace Admin.NET.Core;
/// <summary>
/// 通用工具类
/// </summary>
public static class CommonUtil
public static class CommonHelper
{
/// <summary>
/// 根据字符串获取固定整型哈希值
@ -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)

View File

@ -1,521 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core;
public static class ComputerUtil
{
/// <summary>
/// 内存信息
/// </summary>
/// <returns></returns>
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;
}
/// <summary>
/// 获取正确的操作系统版本Linux获取发行版本
/// </summary>
/// <returns></returns>
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;
}
/// <summary>
/// 磁盘信息
/// </summary>
/// <returns></returns>
public static List<DiskInfo> GetDiskInfos()
{
var diskInfos = new List<DiskInfo>();
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;
}
/// <summary>
/// 获取外网IP地址
/// </summary>
/// <returns></returns>
public static string GetIpFromOnline()
{
try
{
var url = "https://www.ip.cn/api/index?ip&type=0";
var httpRemoteService = App.GetRequiredService<IHttpRemoteService>();
var str = httpRemoteService.GetAsStringAsync(url).GetAwaiter().GetResult();
var resp = JSON.Deserialize<IpCnResp>(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<string> GetCPURates()
{
var cpuRates = new List<string>();
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;
}
/// <summary>
/// 获取系统运行时间
/// </summary>
/// <returns></returns>
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;
}
}
/// <summary>
/// IP信息
/// </summary>
public class IpCnResp
{
public string Ip { get; set; }
public string Address { get; set; }
}
/// <summary>
/// 内存信息
/// </summary>
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; }
/// <summary>
/// 已用内存
/// </summary>
public string UsedRam { get; set; }
/// <summary>
/// CPU使用率%
/// </summary>
public List<string> CpuRates { get; set; }
/// <summary>
/// 总内存 GB
/// </summary>
public string TotalRam { get; set; }
/// <summary>
/// 内存使用率 %
/// </summary>
public string RamRate { get; set; }
/// <summary>
/// 空闲内存
/// </summary>
public string FreeRam { get; set; }
}
/// <summary>
/// 磁盘信息
/// </summary>
public class DiskInfo
{
/// <summary>
/// 磁盘名
/// </summary>
public string DiskName { get; set; }
/// <summary>
/// 类型名
/// </summary>
public string TypeName { get; set; }
/// <summary>
/// 总剩余
/// </summary>
public decimal TotalFree { get; set; }
/// <summary>
/// 总量
/// </summary>
public decimal TotalSize { get; set; }
/// <summary>
/// 已使用
/// </summary>
public decimal Used { get; set; }
/// <summary>
/// 可使用
/// </summary>
public decimal AvailableFreeSpace { get; set; }
/// <summary>
/// 使用百分比
/// </summary>
public decimal AvailablePercent { get; set; }
}
public class MemoryMetricsClient
{
/// <summary>
/// windows系统获取内存信息
/// </summary>
/// <returns></returns>
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;
}
/// <summary>
/// Unix系统获取
/// </summary>
/// <returns></returns>
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;
}
/// <summary>
/// macOS系统获取
/// </summary>
/// <returns></returns>
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
{
/// <summary>
/// linux 系统命令
/// </summary>
/// <param name="command"></param>
/// <returns></returns>
public static string Bash(string command)
{
var escapedArgs = command.Replace("\"", "\\\"");
var process = new Process()
{
StartInfo = new ProcessStartInfo
{
FileName = "/bin/bash",
Arguments = $"-c \"{escapedArgs}\"",
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true,
}
};
process.Start();
string result = process.StandardOutput.ReadToEnd();
process.WaitForExit();
process.Dispose();
return result;
}
/// <summary>
/// windows CMD 系统命令
/// </summary>
/// <param name="fileName"></param>
/// <param name="args"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Windows POWERSHELL 系统命令
/// </summary>
/// <param name="script"></param>
/// <returns></returns>
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
{
/// <summary>
/// Linux 系统命令
/// </summary>
/// <param name="command"></param>
/// <returns></returns>
public static string Bash(string command)
{
var escapedArgs = command.Replace("\"", "\\\"");
var process = new Process()
{
StartInfo = new ProcessStartInfo
{
FileName = "/bin/bash",
Arguments = $"-c \"{escapedArgs}\"",
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true,
}
};
process.Start();
string result = process.StandardOutput.ReadToEnd();
process.WaitForExit();
process.Dispose();
return result;
}
/// <summary>
/// Windows CMD 系统命令
/// </summary>
/// <param name="fileName"></param>
/// <param name="args"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Windows POWERSHELL 系统命令
/// </summary>
/// <param name="script"></param>
/// <returns></returns>
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();
}
}

View File

@ -0,0 +1,303 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using System.Globalization;
namespace Admin.NET.Core;
/// <summary>
/// DateTime 扩展方法
/// </summary>
public static class DateTimeFormatExtensions
{
/// <summary>
/// 获取 Unix 时间戳
/// </summary>
/// <param name="dateTime"></param>
/// <returns></returns>
public static long GetUnixTimeStamp(this DateTime dateTime)
{
return ((DateTimeOffset)dateTime).ToUnixTimeMilliseconds();
}
/// <summary>
/// 获取 Unix 时间戳
/// </summary>
/// <param name="dateTime"></param>
/// <returns></returns>
public static long GetUnixTimeStamp(this DateTimeOffset dateTime)
{
return dateTime.ToUnixTimeMilliseconds();
}
/// <summary>
/// 获取当前时间的时间戳
/// </summary>
/// <param name="dateTime"></param>
/// <returns></returns>
public static long GetDateToTimeStamp(this DateTime dateTime)
{
var ts = dateTime - new DateTime(1970, 1, 1, 0, 0, 0, 0);
return Convert.ToInt64(ts.TotalSeconds);
}
/// <summary>
/// 获取日期天的最小时间
/// </summary>
/// <param name="dateTime"></param>
/// <returns></returns>
public static DateTime GetDayMinDate(this DateTime dateTime)
{
return new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, 0, 0, 0);
}
/// <summary>
/// 获取日期天的最大时间
/// </summary>
/// <param name="dateTime"></param>
/// <returns></returns>
public static DateTime GetDayMaxDate(this DateTime dateTime)
{
return new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, 23, 59, 59);
}
/// <summary>
/// 获取一天的范围
/// </summary>
/// <param name="dateTime"></param>
/// <returns></returns>
public static List<DateTime> GetDayDateRange(this DateTime dateTime)
{
return
[
dateTime.GetDayMinDate(),
dateTime.GetDayMaxDate()
];
}
/// <summary>
/// 获取日期开始时间
/// </summary>
/// <param name="dateTime"></param>
/// <param name="days"></param>
/// <returns></returns>
public static DateTime GetBeginTime(this DateTime? dateTime, int days = 0)
{
return dateTime == DateTime.MinValue || dateTime is null ? DateTime.Now.AddDays(days) : (DateTime)dateTime;
}
/// <summary>
/// 获取星期几
/// </summary>
/// <param name="dateTime"></param>
/// <returns></returns>
public static string GetWeekByDate(this DateTime dateTime)
{
string[] day = ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"];
return day[Convert.ToInt32(dateTime.DayOfWeek.ToString("d"))];
}
/// <summary>
/// 获取这个月的第几周
/// </summary>
/// <param name="daytime"></param>
/// <returns></returns>
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);
}
/// <summary>
/// 时间转换字符串
/// </summary>
/// <param name="dateTime"></param>
/// <returns></returns>
public static string FormatDateTimeToString(this DateTime dateTime)
{
return dateTime.ToString(dateTime.Year == DateTime.Now.Year ? "MM-dd HH:mm" : "yyyy-MM-dd HH:mm");
}
/// <summary>
/// 时间转换字符串
/// </summary>
/// <param name="dateTimeBefore"></param>
/// <param name="dateTimeAfter"></param>
/// <returns></returns>
public static string FormatDateTimeToString(this DateTime dateTimeBefore, DateTime dateTimeAfter)
{
if (dateTimeBefore >= dateTimeAfter)
{
throw new Exception("开始日期必须小于结束日期");
}
var timeSpan = dateTimeAfter - dateTimeBefore;
return timeSpan.FormatTimeSpanToString();
}
/// <summary>
/// 毫秒转换字符串
/// </summary>
/// <param name="milliseconds"></param>
/// <returns></returns>
public static string FormatMilliSecondsToString(this long milliseconds)
{
var timeSpan = TimeSpan.FromMilliseconds(milliseconds);
return timeSpan.FormatTimeSpanToString();
}
/// <summary>
/// 时刻转换字符串
/// </summary>
/// <param name="ticks"></param>
/// <returns></returns>
public static string FormatTimeTicksToString(this long ticks)
{
var timeSpan = TimeSpan.FromTicks(ticks);
return timeSpan.FormatTimeSpanToString();
}
/// <summary>
/// 毫秒转换字符串
/// </summary>
/// <param name="ms"></param>
/// <returns></returns>
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} 毫秒";
}
/// <summary>
/// 时间跨度转换字符串
/// </summary>
/// <param name="timeSpan"></param>
/// <returns></returns>
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} 毫秒";
}
/// <summary>
/// 时间转换简易字符串
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
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 + "年前";
}
/// <summary>
/// 字符串转日期
/// </summary>
/// <param name="thisValue"></param>
/// <returns></returns>
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;
}
}
}

View File

@ -0,0 +1,280 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core;
/// <summary>
/// 表示一个时间范围
/// </summary>
[Serializable]
public class DateTimeRange
{
/// <summary>
/// 初始化一个<see cref="DateTimeRange"/>类型的新实例
/// </summary>
public DateTimeRange() : this(DateTime.MinValue, DateTime.MaxValue)
{
}
/// <summary>
/// 初始化一个<see cref="DateTimeRange"/>类型的新实例
/// </summary>
public DateTimeRange(DateTime startTime, DateTime endTime)
{
StartTime = startTime;
EndTime = endTime;
}
/// <summary>
/// 获取昨天的时间范围
/// </summary>
public static DateTimeRange Yesterday
{
get
{
var now = DateTime.Now;
return new DateTimeRange(now.Date.AddDays(-1), now.Date.AddMilliseconds(-1));
}
}
/// <summary>
/// 获取今天的时间范围
/// </summary>
public static DateTimeRange Today
{
get
{
var now = DateTime.Now;
return new DateTimeRange(now.Date.Date, now.Date.AddDays(1).AddMilliseconds(-1));
}
}
/// <summary>
/// 获取明天的时间范围
/// </summary>
public static DateTimeRange Tomorrow
{
get
{
var now = DateTime.Now;
return new DateTimeRange(now.Date.AddDays(1), now.Date.AddDays(2).AddMilliseconds(-1));
}
}
/// <summary>
/// 获取上周的时间范围
/// </summary>
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));
}
}
/// <summary>
/// 获取本周的时间范围
/// </summary>
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));
}
}
/// <summary>
/// 获取下周的时间范围
/// </summary>
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));
}
}
/// <summary>
/// 获取上个月的时间范围
/// </summary>
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);
}
}
/// <summary>
/// 获取本月的时间范围
/// </summary>
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);
}
}
/// <summary>
/// 获取下个月的时间范围
/// </summary>
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);
}
}
/// <summary>
/// 获取上一年的时间范围
/// </summary>
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));
}
}
/// <summary>
/// 获取本年的时间范围
/// </summary>
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));
}
}
/// <summary>
/// 获取下一年的时间范围
/// </summary>
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));
}
}
/// <summary>
/// 获取相对当前时间过去30天的时间范围
/// </summary>
public static DateTimeRange Last30Days
{
get
{
var now = DateTime.Now;
return new DateTimeRange(now.AddDays(-30), now);
}
}
/// <summary>
/// 获取截止到昨天的最近30天的天数范围
/// </summary>
public static DateTimeRange Last30DaysExceptToday
{
get
{
var now = DateTime.Now;
return new DateTimeRange(now.Date.AddDays(-30), now.Date.AddMilliseconds(-1));
}
}
/// <summary>
/// 获取相对当前时间过去7天的时间范围
/// </summary>
public static DateTimeRange Last7Days
{
get
{
var now = DateTime.Now;
return new DateTimeRange(now.AddDays(-7), now);
}
}
/// <summary>
/// 获取截止到昨天的最近7天的天数范围
/// </summary>
public static DateTimeRange Last7DaysExceptToday
{
get
{
var now = DateTime.Now;
return new DateTimeRange(now.Date.AddDays(-7), now.Date.AddMilliseconds(-1));
}
}
/// <summary>
/// 起始时间
/// </summary>
public DateTime StartTime { get; set; }
/// <summary>
/// 结束时间
/// </summary>
public DateTime EndTime { get; set; }
/// <summary>
/// ToString
/// </summary>
/// <returns></returns>
public override string ToString()
{
return $"[{StartTime} - {EndTime}]";
}
}

View File

@ -0,0 +1,866 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core;
/// <summary>
/// 农历辅助工具类
/// </summary>
/// <remarks>
/// 提供公历与农历互转、天干地支、生肖、节气、农历节日等功能,
/// 支持1900年至2100年的农历计算
/// </remarks>
public static class LunarCalendarHelper
{
#region
/// <summary>
/// 农历数据起始年份
/// </summary>
private const int MinYear = 1900;
/// <summary>
/// 农历数据结束年份
/// </summary>
private const int MaxYear = 2100;
/// <summary>
/// 农历基准日期 (1900年1月31日为农历1900年正月初一)
/// </summary>
private static readonly DateTime BaseDate = new(1900, 1, 31);
/// <summary>
/// 天干数组
/// </summary>
private static readonly string[] Tiangan = ["甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸"];
/// <summary>
/// 地支数组
/// </summary>
private static readonly string[] Dizhi = ["子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥"];
/// <summary>
/// 生肖数组
/// </summary>
private static readonly string[] Zodiac = ["鼠", "牛", "虎", "兔", "龙", "蛇", "马", "羊", "猴", "鸡", "狗", "猪"];
/// <summary>
/// 农历月份名称
/// </summary>
private static readonly string[] LunarMonths = ["正月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "冬月", "腊月"];
/// <summary>
/// 农历日期名称
/// </summary>
private static readonly string[] LunarDays =
[
"初一", "初二", "初三", "初四", "初五", "初六", "初七", "初八", "初九", "初十",
"十一", "十二", "十三", "十四", "十五", "十六", "十七", "十八", "十九", "二十",
"廿一", "廿二", "廿三", "廿四", "廿五", "廿六", "廿七", "廿八", "廿九", "三十"
];
/// <summary>
/// 二十四节气名称
/// </summary>
private static readonly string[] SolarTerms =
[
"立春", "雨水", "惊蛰", "春分", "清明", "谷雨", "立夏", "小满", "芒种", "夏至", "小暑", "大暑",
"立秋", "处暑", "白露", "秋分", "寒露", "霜降", "立冬", "小雪", "大雪", "冬至", "小寒", "大寒"
];
#endregion
#region
/// <summary>
/// 农历年份数据 (1900-2100年)
/// 每个数值的低12位表示12个月的大小月(1为大月30天0为小月29天)
/// 第13位表示闰月的大小月
/// 第14-17位表示闰月月份(0表示无闰月)
/// </summary>
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 -
/// <summary>
/// 将公历日期转换为农历日期
/// </summary>
/// <param name="date">公历日期</param>
/// <returns>农历日期信息</returns>
/// <exception cref="ArgumentOutOfRangeException">日期超出支持范围时抛出</exception>
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
};
}
/// <summary>
/// 将农历日期转换为公历日期
/// </summary>
/// <param name="lunarYear">农历年</param>
/// <param name="lunarMonth">农历月</param>
/// <param name="lunarDay">农历日</param>
/// <param name="isLeapMonth">是否闰月</param>
/// <returns>公历日期</returns>
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 -
/// <summary>
/// 获取指定年份的生肖
/// </summary>
/// <param name="year">年份(农历年)</param>
/// <returns>生肖名称</returns>
public static string GetZodiac(int year)
{
var index = (year - 1900) % 12;
return Zodiac[index];
}
/// <summary>
/// 获取指定年份的天干地支
/// </summary>
/// <param name="year">年份(农历年)</param>
/// <returns>天干地支组合</returns>
public static string GetTianganDizhi(int year)
{
var tianganIndex = (year - 1900) % 10;
var dizhiIndex = (year - 1900) % 12;
return Tiangan[tianganIndex] + Dizhi[dizhiIndex];
}
/// <summary>
/// 获取指定公历日期的天干地支
/// </summary>
/// <param name="date">公历日期</param>
/// <returns>日期天干地支</returns>
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 -
/// <summary>
/// 获取指定年份的所有节气日期
/// </summary>
/// <param name="year">公历年份</param>
/// <returns>节气日期列表</returns>
public static List<SolarTerm> GetSolarTerms(int year)
{
var solarTerms = new List<SolarTerm>();
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;
}
/// <summary>
/// 获取指定日期所属的节气
/// </summary>
/// <param name="date">公历日期</param>
/// <returns>节气信息如果不是节气日则返回null</returns>
public static SolarTerm? GetSolarTerm(DateTime date)
{
var solarTerms = GetSolarTerms(date.Year);
return solarTerms.FirstOrDefault(st => st.Date.Date == date.Date);
}
/// <summary>
/// 判断指定日期是否为节气
/// </summary>
/// <param name="date">公历日期</param>
/// <returns>是否为节气日</returns>
public static bool IsSolarTerm(DateTime date)
{
return GetSolarTerm(date) != null;
}
#endregion -
#region -
/// <summary>
/// 获取指定农历日期的传统节日名称
/// </summary>
/// <param name="lunarMonth">农历月</param>
/// <param name="lunarDay">农历日</param>
/// <param name="isLeapMonth">是否闰月</param>
/// <returns>节日名称如果不是节日则返回null</returns>
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
};
}
/// <summary>
/// 获取指定公历日期的传统节日名称
/// </summary>
/// <param name="date">公历日期</param>
/// <returns>节日名称如果不是节日则返回null</returns>
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 -
/// <summary>
/// 获取农历年份的中文名称
/// </summary>
/// <param name="year">农历年份</param>
/// <returns>中文年份名称</returns>
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 + "年";
}
/// <summary>
/// 获取农历月份的中文名称
/// </summary>
/// <param name="month">农历月份</param>
/// <param name="isLeapMonth">是否闰月</param>
/// <returns>中文月份名称</returns>
public static string GetLunarMonthName(int month, bool isLeapMonth = false)
{
var monthName = LunarMonths[month - 1];
return isLeapMonth ? "闰" + monthName : monthName;
}
/// <summary>
/// 获取农历日期的中文名称
/// </summary>
/// <param name="day">农历日期</param>
/// <returns>中文日期名称</returns>
public static string GetLunarDayName(int day)
{
return day is >= 1 and <= 30 ? LunarDays[day - 1] : day.ToString();
}
/// <summary>
/// 判断指定农历年份是否有闰月
/// </summary>
/// <param name="year">农历年份</param>
/// <returns>是否有闰月</returns>
public static bool HasLeapMonth(int year)
{
return GetLeapMonth(year) > 0;
}
/// <summary>
/// 获取农历年份的总天数
/// </summary>
/// <param name="year">农历年份</param>
/// <returns>总天数</returns>
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
/// <summary>
/// 获取农历年份的闰月月份
/// </summary>
/// <param name="year">农历年份</param>
/// <returns>闰月月份0表示无闰月</returns>
private static int GetLeapMonth(int year)
{
return year is < MinYear or > MaxYear ? 0 : (LunarYearData[year - MinYear] & 0xf0000) >> 16;
}
/// <summary>
/// 获取农历月份的天数
/// </summary>
/// <param name="year">农历年份</param>
/// <param name="month">农历月份</param>
/// <returns>月份天数</returns>
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;
}
/// <summary>
/// 获取农历年份闰月的天数
/// </summary>
/// <param name="year">农历年份</param>
/// <returns>闰月天数</returns>
private static int GetLeapMonthDays(int year)
{
return !HasLeapMonth(year) ? 0 : (LunarYearData[year - MinYear] & 0x10000) != 0 ? 30 : 29;
}
/// <summary>
/// 计算指定年份第n个节气的日期基于太阳黄经的真实算法
/// </summary>
/// <param name="year">公历年份</param>
/// <param name="termIndex">节气索引0-23</param>
/// <returns>节气日期</returns>
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);
}
/// <summary>
/// 计算儒略日数
/// </summary>
/// <param name="date">日期</param>
/// <returns>儒略日数</returns>
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;
}
/// <summary>
/// 将儒略日数转换为DateTime
/// </summary>
/// <param name="julianDay">儒略日数</param>
/// <returns>日期时间</returns>
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);
}
/// <summary>
/// 计算太阳黄经简化版VSOP87算法
/// </summary>
/// <param name="julianDay">儒略日数</param>
/// <returns>太阳黄经(度)</returns>
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;
}
/// <summary>
/// 获取节气的估算日期(从年初开始的天数)
/// </summary>
/// <param name="year">年份</param>
/// <param name="termIndex">节气索引</param>
/// <returns>估算天数</returns>
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;
}
/// <summary>
/// 二分法搜索节气精确时刻
/// </summary>
/// <param name="startJd">搜索开始的儒略日</param>
/// <param name="endJd">搜索结束的儒略日</param>
/// <param name="targetLongitude">目标黄经</param>
/// <returns>精确的儒略日数</returns>
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;
}
/// <summary>
/// 计算两个角度之间的差值考虑360度循环
/// </summary>
/// <param name="angle1">角度1</param>
/// <param name="angle2">角度2</param>
/// <returns>角度差</returns>
private static double GetAngleDifference(double angle1, double angle2)
{
var diff = angle1 - angle2;
while (diff > 180)
{
diff -= 360;
}
while (diff < -180)
{
diff += 360;
}
return diff;
}
/// <summary>
/// 判断是否为闰年
/// </summary>
/// <param name="year">年份</param>
/// <returns>是否为闰年</returns>
private static bool IsLeapYear(int year)
{
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}
#endregion
}
/// <summary>
/// 农历日期信息
/// </summary>
public class LunarDate
{
/// <summary>
/// 农历年份
/// </summary>
public int Year { get; set; }
/// <summary>
/// 农历月份
/// </summary>
public int Month { get; set; }
/// <summary>
/// 农历日期
/// </summary>
public int Day { get; set; }
/// <summary>
/// 是否闰月
/// </summary>
public bool IsLeapMonth { get; set; }
/// <summary>
/// 农历年份中文名称
/// </summary>
public string YearName { get; set; } = string.Empty;
/// <summary>
/// 农历月份中文名称
/// </summary>
public string MonthName { get; set; } = string.Empty;
/// <summary>
/// 农历日期中文名称
/// </summary>
public string DayName { get; set; } = string.Empty;
/// <summary>
/// 生肖
/// </summary>
public string Zodiac { get; set; } = string.Empty;
/// <summary>
/// 天干地支
/// </summary>
public string TianganDizhi { get; set; } = string.Empty;
/// <summary>
/// 对应的公历日期
/// </summary>
public DateTime SolarDate { get; set; }
/// <summary>
/// 农历节日名称
/// </summary>
public string? Festival => LunarCalendarHelper.GetLunarFestival(Month, Day, IsLeapMonth);
/// <summary>
/// 农历日期的完整中文表示
/// </summary>
public string FullName => $"{YearName}{MonthName}{DayName}";
/// <summary>
/// 转换为字符串表示
/// </summary>
/// <returns>格式化的农历日期</returns>
public override string ToString()
{
var festival = Festival;
var festivalText = !string.IsNullOrEmpty(festival) ? $" ({festival})" : "";
return $"{FullName} {Zodiac}年 {TianganDizhi}{festivalText}";
}
}
/// <summary>
/// 节气信息
/// </summary>
public class SolarTerm
{
/// <summary>
/// 节气名称
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// 节气日期
/// </summary>
public DateTime Date { get; set; }
/// <summary>
/// 节气序号1-24
/// </summary>
public int Order { get; set; }
/// <summary>
/// 转换为字符串表示
/// </summary>
/// <returns>格式化的节气信息</returns>
public override string ToString()
{
return $"{Name} ({Date:yyyy年MM月dd日})";
}
}

View File

@ -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;
}
/// <summary>
/// 实例化类
/// </summary>
/// <param name="timeSpan"></param>
/// <returns></returns>
public static DateTimeUtil Init(TimeSpan timeSpan = default)
{
return new DateTimeUtil(timeSpan);
}
/// <summary>
/// 实例化类
/// </summary>
/// <param name="time"></param>
/// <returns></returns>
public static DateTimeUtil Init(DateTime time)
{
return new DateTimeUtil(time);
}
/// <summary>
/// 根据unix时间戳的长度自动判断是秒还是以毫秒为单位
/// </summary>
/// <param name="unixTime"></param>
/// <returns></returns>
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;
}
}
/// <summary>
/// 获取开始时间
/// </summary>
/// <param name="dateTime"></param>
/// <param name="days"></param>
/// <returns></returns>
public static DateTime GetBeginTime(DateTime? dateTime, int days = 0)
{
return dateTime == DateTime.MinValue || dateTime == null ? DateTime.Now.AddDays(days) : (DateTime)dateTime;
}
/// <summary>
/// 时间戳转本地时间-时间戳精确到秒
/// </summary>
public static DateTime ToLocalTimeDateBySeconds(long unix)
{
return DateTimeOffset.FromUnixTimeSeconds(unix).ToLocalTime().DateTime;
}
/// <summary>
/// 时间转时间戳Unix-时间戳精确到秒
/// </summary>
public static long ToUnixTimestampBySeconds(DateTime dt)
{
return new DateTimeOffset(dt).ToUnixTimeSeconds();
}
/// <summary>
/// 时间戳转本地时间-时间戳精确到毫秒
/// </summary>
public static DateTime ToLocalTimeDateByMilliseconds(long unix)
{
return DateTimeOffset.FromUnixTimeMilliseconds(unix).ToLocalTime().DateTime;
}
/// <summary>
/// 时间转时间戳Unix-时间戳精确到毫秒
/// </summary>
public static long ToUnixTimestampByMilliseconds(DateTime dt)
{
return new DateTimeOffset(dt).ToUnixTimeMilliseconds();
}
/// <summary>
/// 毫秒转天时分秒
/// </summary>
/// <param name="ms">TotalMilliseconds</param>
/// <param name="isSimplify">是否简化显示</param>
/// <returns></returns>
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;
}
}
/// <summary>
/// 获取unix时间戳
/// </summary>
/// <param name="dt"></param>
/// <returns></returns>
public static long GetUnixTimeStamp(DateTime dt)
{
return ((DateTimeOffset)dt).ToUnixTimeMilliseconds();
}
/// <summary>
/// 获取日期天的最小时间
/// </summary>
/// <param name="dt"></param>
/// <returns></returns>
public static DateTime GetDayMinDate(DateTime dt)
{
return new DateTime(dt.Year, dt.Month, dt.Day, 0, 0, 0);
}
/// <summary>
/// 获取日期天的最大时间
/// </summary>
/// <param name="dt"></param>
/// <returns></returns>
public static DateTime GetDayMaxDate(DateTime dt)
{
return new DateTime(dt.Year, dt.Month, dt.Day, 23, 59, 59);
}
/// <summary>
/// 判断日期是否在当前年份并格式化日期
/// </summary>
/// <param name="dt"></param>
/// <returns></returns>
public static string FormatDateTime(DateTime? dt)
{
return dt == null ? string.Empty : dt.Value.ToString(dt.Value.Year == DateTime.Now.Year ? "MM-dd HH:mm" : "yyyy-MM-dd HH:mm");
}
/// <summary>
/// 获取日期范围00:00:00 - 23:59:59
/// </summary>
/// <returns></returns>
public static List<DateTime> GetTodayTimeList(DateTime time)
{
return new List<DateTime>
{
Convert.ToDateTime(time.ToString("D")),
Convert.ToDateTime(time.AddDays(1).ToString("D")).AddSeconds(-1)
};
}
/// <summary>
/// 获取星期几
/// </summary>
/// <param name="dt"></param>
/// <returns></returns>
public static string GetWeekByDate(DateTime dt)
{
var day = new[] { "星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六" };
return day[Convert.ToInt32(dt.DayOfWeek.ToString("d"))];
}
/// <summary>
/// 获取这个月的第几周
/// </summary>
/// <param name="daytime"></param>
/// <returns></returns>
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);
}
/// <summary>
/// 获取今天的时间范围
/// </summary>
/// <returns>返回包含开始时间和结束时间的元组</returns>
public (DateTime Start, DateTime End) GetTodayRange()
{
var start = Date.Date; // 当天开始时间
var end = start.AddDays(1).AddSeconds(-1); // 当天结束时间
return (start, end);
}
/// <summary>
/// 获取本月的时间范围
/// </summary>
/// <returns>返回包含开始时间和结束时间的元组</returns>
public (DateTime Start, DateTime End) GetMonthRange()
{
return (GetFirstDayOfMonth(), GetLastDayOfMonth());
}
/// <summary>
/// 获取本月的第一天开始时间
/// </summary>
/// <returns>返回当月的第一天</returns>
public DateTime GetFirstDayOfMonth()
{
return new DateTime(Date.Year, Date.Month, 1);
}
/// <summary>
/// 获取本月的最后一天截至时间
/// </summary>
/// <returns>返回当月的最后一天</returns>
public DateTime GetLastDayOfMonth()
{
var firstDayOfNextMonth = new DateTime(Date.Year, Date.Month, 1).AddMonths(1);
return firstDayOfNextMonth.AddSeconds(-1);
}
/// <summary>
/// 获取今年的时间范围
/// </summary>
public (DateTime Start, DateTime End) GetYearRange()
{
return (GetFirstDayOfYear(), GetLastDayOfYear());
}
/// <summary>
/// 获取今年的第一天时间范围
/// </summary>
public DateTime GetFirstDayOfYear()
{
return new DateTime(Date.Year, 1, 1);
}
/// <summary>
/// 获取今年的最后一天时间范围
/// </summary>
public DateTime GetLastDayOfYear()
{
return new DateTime(Date.Year, 12, 31, 23, 59, 59);
}
/// <summary>
/// 获取前天时间范围
/// </summary>
public (DateTime Start, DateTime End) GetDayBeforeYesterdayRange()
{
var start = Date.Date.AddDays(-2); // 前天开始时间
var end = start.AddDays(1).AddSeconds(-1); // 前天结束时间
return (start, end);
}
/// <summary>
/// 获取昨天时间范围
/// </summary>
public (DateTime Start, DateTime End) GetYesterdayRange()
{
var start = Date.Date.AddDays(-1); // 昨天开始时间
var end = start.AddDays(1).AddSeconds(-1); // 昨天结束时间
return (start, end);
}
/// <summary>
/// 获取上一周时间范围
/// </summary>
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);
}
/// <summary>
/// 获取本周时间范围
/// </summary>
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);
}
/// <summary>
/// 获取上月时间范围
/// </summary>
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);
}
/// <summary>
/// 获取近3天的时间范围
/// </summary>
public (DateTime Start, DateTime End) GetLast3DaysRange()
{
var start = Date.Date.AddDays(-2); // 3天前的开始时间
var end = Date.Date.AddDays(1).AddSeconds(-1); // 当前日期的结束时间
return (start, end);
}
/// <summary>
/// 获取近7天的时间范围
/// </summary>
public (DateTime Start, DateTime End) GetLast7DaysRange()
{
var start = Date.Date.AddDays(-6); // 7天前的开始时间
var end = Date.Date.AddDays(1).AddSeconds(-1); // 当前日期的结束时间
return (start, end);
}
/// <summary>
/// 获取近15天的时间范围
/// </summary>
public (DateTime Start, DateTime End) GetLast15DaysRange()
{
var start = Date.Date.AddDays(-14); // 15天前的开始时间
var end = Date.Date.AddDays(1).AddSeconds(-1); // 当前日期的结束时间
return (start, end);
}
/// <summary>
/// 获取近3个月的时间范围
/// </summary>
public (DateTime Start, DateTime End) GetLast3MonthsRange()
{
var start = Date.Date.AddMonths(-3); // 3个月前的开始时间
var end = Date.Date.AddDays(1).AddSeconds(-1); // 当前日期的结束时间
return (start, end);
}
/// <summary>
/// 获取上半年的时间范围
/// </summary>
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);
}
/// <summary>
/// 获取下半年的时间范围
/// </summary>
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);
}
}

View File

@ -20,7 +20,7 @@ public class ExcelHelper
{
try
{
var result = CommonUtil.ImportExcelDataAsync<IN>(file).Result ?? throw Oops.Oh("有效数据为空");
var result = CommonHelper.ImportExcelDataAsync<IN>(file).Result ?? throw Oops.Oh("有效数据为空");
result.ForEach(u => u.Id = YitIdHelper.NextId());
var tasks = new List<Task>();

View File

@ -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
/// <returns></returns>
public static async Task<IEnumerable<T>> GetImportExcelData<T>([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<T>(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}";
}
}

View File

@ -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<string>("Cryptogram:CryptoType"); // 加密类型
public static readonly string PublicKey = App.GetConfig<string>("Cryptogram:PublicKey"); // 公钥
@ -82,7 +82,7 @@ public class CryptogramUtil
/// <returns></returns>
public static string SM2Encrypt(string plainText)
{
return GMUtil.SM2Encrypt(PublicKey, plainText);
return GMHelper.SM2Encrypt(PublicKey, plainText);
}
/// <summary>
@ -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
/// <returns></returns>
public static string SM4EncryptECB(string plainText)
{
return GMUtil.SM4EncryptECB(SM4_key, plainText);
return GMHelper.SM4EncryptECB(SM4_key, plainText);
}
/// <summary>
@ -119,7 +119,7 @@ public class CryptogramUtil
/// <returns></returns>
public static string SM4DecryptECB(string cipherText)
{
return GMUtil.SM4DecryptECB(SM4_key, cipherText);
return GMHelper.SM4DecryptECB(SM4_key, cipherText);
}
/// <summary>
@ -129,7 +129,7 @@ public class CryptogramUtil
/// <returns></returns>
public static string SM4EncryptCBC(string plainText)
{
return GMUtil.SM4EncryptCBC(SM4_key, SM4_iv, plainText);
return GMHelper.SM4EncryptCBC(SM4_key, SM4_iv, plainText);
}
/// <summary>
@ -139,6 +139,6 @@ public class CryptogramUtil
/// <returns></returns>
public static string SM4DecryptCBC(string cipherText)
{
return GMUtil.SM4DecryptCBC(SM4_key, SM4_iv, cipherText);
return GMHelper.SM4DecryptCBC(SM4_key, SM4_iv, cipherText);
}
}

View File

@ -13,7 +13,7 @@ namespace Admin.NET.Core;
/// <summary>
/// GM工具类
/// </summary>
public class GMUtil
public class GMHelper
{
/// <summary>
/// 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;

View File

@ -0,0 +1,141 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core;
/// <summary>
/// 主板帮助类
/// </summary>
public static class BoardHelper
{
/// <summary>
/// 主板信息
/// </summary>
/// <remarks>
/// 推荐使用,默认有缓存
/// </remarks>
public static BoardInfo BoardInfos => Cache.Default.GetOrAdd("BoardInfos", _ => GetBoardInfos(), 120 * 60);
/// <summary>
/// 获取主板信息
/// </summary>
/// <returns></returns>
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;
}
}
}
}
/// <summary>
/// 主板信息
/// </summary>
public record BoardInfo
{
/// <summary>
/// 型号
/// </summary>
public string Product { get; set; } = string.Empty;
/// <summary>
/// 制造商
/// </summary>
public string Manufacturer { get; set; } = string.Empty;
/// <summary>
/// 序列号
/// </summary>
public string SerialNumber { get; set; } = string.Empty;
/// <summary>
/// 版本号
/// </summary>
public string Version { get; set; } = string.Empty;
}

View File

@ -0,0 +1,269 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core;
/// <summary>
/// 处理器帮助类
/// </summary>
public static class CpuHelper
{
/// <summary>
/// 处理器信息
/// </summary>
/// <remarks>
/// 推荐使用,默认有缓存
/// </remarks>
public static CpuInfo CpuInfos => Cache.Default.GetOrAdd("CpuInfos", _ => GetCpuInfos(), 5 * 60);
/// <summary>
/// 获取处理器信息
/// </summary>
/// <returns></returns>
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;
}
/// <summary>
/// 获取CPU使用率
/// </summary>
/// <param name="cpuInfo"></param>
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;
}
}
}
/// <summary>
/// 获取CPU温度信息如果可用
/// </summary>
/// <param name="cpuInfo"></param>
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
{
// 温度获取失败不影响其他信息
}
}
/// <summary>
/// 获取CPU详细信息
/// </summary>
/// <param name="cpuInfo"></param>
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;
}
}
}
}
}
}
/// <summary>
/// 处理器信息
/// </summary>
public record CpuInfo
{
/// <summary>
/// 处理器名称
/// </summary>
public string ProcessorName { get; set; } = string.Empty;
/// <summary>
/// 处理器架构
/// </summary>
public string ProcessorArchitecture { get; set; } = string.Empty;
/// <summary>
/// 物理核心数
/// </summary>
public int PhysicalCoreCount { get; set; }
/// <summary>
/// 逻辑核心数(超线程)
/// </summary>
public int LogicalCoreCount { get; set; }
/// <summary>
/// 基础时钟频率(GHz)
/// </summary>
public double BaseClockSpeed { get; set; }
/// <summary>
/// 缓存大小
/// </summary>
public long CacheBytes { get; set; }
/// <summary>
/// CPU使用率(%)
/// </summary>
public double UsagePercentage { get; set; }
/// <summary>
/// CPU温度(°C)
/// </summary>
public double? Temperature { get; set; }
}

View File

@ -0,0 +1,137 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core;
/// <summary>
/// 磁盘帮助类
/// </summary>
public static class DiskHelper
{
/// <summary>
/// 磁盘信息
/// </summary>
/// <remarks>
/// 推荐使用,默认有缓存
/// </remarks>
public static List<DiskInfo> DiskInfos => Cache.Default.GetOrAdd("DiskInfos", _ => GetDiskInfos(), 60 * 60);
/// <summary>
/// 获取磁盘信息
/// </summary>
/// <returns></returns>
public static List<DiskInfo> GetDiskInfos()
{
List<DiskInfo> 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;
}
}
/// <summary>
/// 磁盘信息
/// </summary>
public record DiskInfo
{
/// <summary>
/// 磁盘名称
/// </summary>
public string DiskName { get; set; } = string.Empty;
/// <summary>
/// 磁盘类型
/// </summary>
public string TypeName { get; set; } = string.Empty;
/// <summary>
/// 总大小
/// </summary>
public long TotalSpace { get; set; }
/// <summary>
/// 空闲大小
/// </summary>
public long FreeSpace { get; set; }
/// <summary>
/// 已用大小
/// </summary>
public long UsedSpace { get; set; }
/// <summary>
/// 可用占比
/// </summary>
public double AvailableRate { get; set; }
}

View File

@ -0,0 +1,347 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core;
/// <summary>
/// GPU帮助类
/// </summary>
public static class GpuHelper
{
/// <summary>
/// GPU信息
/// </summary>
/// <remarks>
/// 推荐使用,默认有缓存
/// </remarks>
public static List<GpuInfo> GpuInfos => Cache.Default.GetOrAdd("GpuInfos", _ => GetGpuInfos(), 60 * 60);
/// <summary>
/// 获取GPU信息
/// </summary>
/// <returns></returns>
public static List<GpuInfo> GetGpuInfos()
{
List<GpuInfo> 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;
}
/// <summary>
/// 获取Windows GPU信息
/// </summary>
/// <param name="gpuInfos"></param>
private static void GetWindowsGpuInfo(List<GpuInfo> 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);
}
}
/// <summary>
/// 获取Linux GPU信息
/// </summary>
/// <param name="gpuInfos"></param>
/// <exception cref="Exception"></exception>
private static void GetLinuxGpuInfo(List<GpuInfo> 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}");
}
}
/// <summary>
/// 获取macOS GPU信息
/// </summary>
/// <param name="gpuInfos"></param>
/// <exception cref="Exception"></exception>
private static void GetMacOsGpuInfo(List<GpuInfo> 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}");
}
}
}
/// <summary>
/// GPU信息
/// </summary>
public record GpuInfo
{
/// <summary>
/// GPU名称
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// 描述
/// </summary>
public string Description { get; set; } = string.Empty;
/// <summary>
/// 厂商
/// </summary>
public string Vendor { get; set; } = string.Empty;
/// <summary>
/// 设备ID
/// </summary>
public string DeviceId { get; set; } = string.Empty;
/// <summary>
/// 总线信息
/// </summary>
public string BusInfo { get; set; } = string.Empty;
/// <summary>
/// 驱动版本
/// </summary>
public string DriverVersion { get; set; } = string.Empty;
/// <summary>
/// 显存大小(字节)
/// </summary>
public long MemoryBytes { get; set; }
/// <summary>
/// GPU温度°C
/// </summary>
public double? Temperature { get; set; }
/// <summary>
/// 视频模式描述
/// </summary>
public string VideoModeDescription { get; set; } = string.Empty;
/// <summary>
/// 状态
/// </summary>
public string Status { get; set; } = string.Empty;
/// <summary>
/// GPU使用率%
/// </summary>
public double? UtilizationPercentage { get; set; }
/// <summary>
/// 显存使用率(%
/// </summary>
public double? MemoryUtilizationPercentage { get; set; }
}

View File

@ -0,0 +1,227 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core;
/// <summary>
/// 硬件信息管理器
/// </summary>
public static class HardwareInfoManager
{
/// <summary>
/// 获取完整的系统硬件信息
/// </summary>
/// <remarks>
/// 推荐使用,默认有缓存
/// </remarks>
/// <returns>系统硬件信息</returns>
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
};
}
/// <summary>
/// 获取系统硬件摘要信息
/// </summary>
/// <returns>系统硬件摘要</returns>
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();
}
}
/// <summary>
/// 获取硬件信息诊断报告
/// </summary>
/// <returns>诊断报告</returns>
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;
}
}
/// <summary>
/// 系统硬件信息
/// </summary>
public record SystemHardwareInfo
{
/// <summary>
/// CPU信息
/// </summary>
public CpuInfo CpuInfo { get; set; } = new();
/// <summary>
/// 内存信息
/// </summary>
public RamInfo RamInfo { get; set; } = new();
/// <summary>
/// 磁盘信息列表
/// </summary>
public List<DiskInfo> DiskInfos { get; set; } = [];
/// <summary>
/// 网络接口信息列表
/// </summary>
public List<NetworkInfo> NetworkInfos { get; set; } = [];
/// <summary>
/// GPU信息列表
/// </summary>
public List<GpuInfo> GpuInfos { get; set; } = [];
/// <summary>
/// 主板信息
/// </summary>
public BoardInfo BoardInfo { get; set; } = new();
}
/// <summary>
/// 系统硬件摘要信息
/// </summary>
public record SystemHardwareSummary
{
/// <summary>
/// CPU名称
/// </summary>
public string CpuName { get; set; } = string.Empty;
/// <summary>
/// CPU核心数
/// </summary>
public string CpuCores { get; set; } = string.Empty;
/// <summary>
/// CPU使用率
/// </summary>
public string CpuUsage { get; set; } = string.Empty;
/// <summary>
/// 总内存
/// </summary>
public string TotalMemory { get; set; } = string.Empty;
/// <summary>
/// 内存使用率
/// </summary>
public string MemoryUsage { get; set; } = string.Empty;
/// <summary>
/// 总磁盘空间
/// </summary>
public string TotalDiskSpace { get; set; } = string.Empty;
/// <summary>
/// 网络接口数量
/// </summary>
public int NetworkInterfaceCount { get; set; }
/// <summary>
/// GPU数量
/// </summary>
public int GpuCount { get; set; }
/// <summary>
/// 主要GPU
/// </summary>
public string PrimaryGpu { get; set; } = string.Empty;
}
/// <summary>
/// 硬件诊断报告
/// </summary>
public record HardwareDiagnosticReport
{
/// <summary>
/// 诊断状态
/// </summary>
public string Status { get; set; } = string.Empty;
/// <summary>
/// 发现的问题列表
/// </summary>
public List<string> Issues { get; set; } = [];
/// <summary>
/// 建议列表
/// </summary>
public List<string> Recommendations { get; set; } = [];
}

View File

@ -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;
/// <summary>
/// 网卡信息帮助类
/// </summary>
public static class NetworkHelper
{
/// <summary>
/// 网卡信息
/// </summary>
/// <remarks>
/// 推荐使用,默认有缓存
/// </remarks>
public static List<NetworkInfo> NetworkInfos => Cache.Default.GetOrAdd("NetworkInfos", _ => GetNetworkInfos(), 5 * 60);
/// <summary>
/// 获取网卡信息
/// </summary>
/// <returns></returns>
public static List<NetworkInfo> GetNetworkInfos()
{
List<NetworkInfo> 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;
}
}
/// <summary>
/// 网卡信息
/// </summary>
public record NetworkInfo
{
/// <summary>
/// 名称
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// 描述
/// </summary>
public string Description { get; set; } = string.Empty;
/// <summary>
/// 类型
/// </summary>
public string Type { get; set; } = string.Empty;
/// <summary>
/// 操作状态
/// </summary>
public string OperationalStatus { get; set; } = string.Empty;
/// <summary>
/// 速度
/// </summary>
public string Speed { get; set; } = string.Empty;
/// <summary>
/// 物理地址(mac 地址)
/// </summary>
public string PhysicalAddress { get; set; } = string.Empty;
/// <summary>
/// 是否支持多播
/// </summary>
public bool SupportsMulticast { get; set; }
/// <summary>
/// 是否只接收
/// </summary>
public bool IsReceiveOnly { get; set; }
/// <summary>
/// DNS 地址
/// </summary>
public List<string> DnsAddresses { get; set; } = [];
/// <summary>
/// 网关地址
/// </summary>
public List<string> GatewayAddresses { get; set; } = [];
/// <summary>
/// DHCP服务器地址
/// </summary>
public List<string> DhcpServerAddresses { get; set; } = [];
/// <summary>
/// IPv4 地址详细信息
/// </summary>
public List<IpAddressInfo> IPv4Addresses { get; set; } = [];
/// <summary>
/// IPv6 地址详细信息
/// </summary>
public List<IpAddressInfo> IPv6Addresses { get; set; } = [];
/// <summary>
/// 网络接口统计信息
/// </summary>
public NetworkInterfaceStatistics? Statistics { get; set; }
}
/// <summary>
/// IP地址信息
/// </summary>
public record IpAddressInfo
{
/// <summary>
/// IP地址
/// </summary>
public string Address { get; set; } = string.Empty;
/// <summary>
/// 子网掩码
/// </summary>
public string SubnetMask { get; set; } = string.Empty;
/// <summary>
/// 前缀长度
/// </summary>
public int PrefixLength { get; set; }
}
/// <summary>
/// 网络接口统计信息
/// </summary>
public record NetworkInterfaceStatistics
{
/// <summary>
/// 接收字节数
/// </summary>
public long BytesReceived { get; set; }
/// <summary>
/// 发送字节数
/// </summary>
public long BytesSent { get; set; }
/// <summary>
/// 接收数据包数
/// </summary>
public long PacketsReceived { get; set; }
/// <summary>
/// 发送数据包数
/// </summary>
public long PacketsSent { get; set; }
/// <summary>
/// 丢弃的传入数据包数
/// </summary>
public long IncomingPacketsDiscarded { get; set; }
/// <summary>
/// 丢弃的传出数据包数
/// </summary>
public long OutgoingPacketsDiscarded { get; set; }
/// <summary>
/// 传入错误数据包数
/// </summary>
public long IncomingPacketsWithErrors { get; set; }
/// <summary>
/// 传出错误数据包数
/// </summary>
public long OutgoingPacketsWithErrors { get; set; }
}
/// <summary>
/// 网络统计信息
/// </summary>
public record NetworkStatistics
{
/// <summary>
/// 总接收字节数
/// </summary>
public long TotalBytesReceived { get; set; }
/// <summary>
/// 总发送字节数
/// </summary>
public long TotalBytesSent { get; set; }
/// <summary>
/// 总接收数据包数
/// </summary>
public long TotalPacketsReceived { get; set; }
/// <summary>
/// 总发送数据包数
/// </summary>
public long TotalPacketsSent { get; set; }
}

View File

@ -0,0 +1,211 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core;
/// <summary>
/// 内存帮助类
/// </summary>
public static class RamHelper
{
/// <summary>
/// 内存信息
/// </summary>
/// <remarks>
/// 推荐使用,默认有缓存
/// </remarks>
public static RamInfo RamInfos => Cache.Default.GetOrAdd("RamInfos", _ => GetRamInfos(), 5 * 60);
/// <summary>
/// 获取内存信息
/// </summary>
/// <returns></returns>
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;
}
}
/// <summary>
/// 内存信息
/// </summary>
public record RamInfo
{
/// <summary>
/// 总内存大小(字节)
/// </summary>
public long TotalBytes { get; set; }
/// <summary>
/// 已用内存大小(字节)
/// </summary>
public long UsedBytes { get; set; }
/// <summary>
/// 空闲内存大小(字节)
/// </summary>
public long FreeBytes { get; set; }
/// <summary>
/// 可用内存大小(字节)
/// </summary>
public long AvailableBytes { get; set; }
/// <summary>
/// 缓冲区和缓存大小(字节)
/// </summary>
public long BuffersCachedBytes { get; set; }
/// <summary>
/// 内存使用率(%
/// </summary>
public double UsagePercentage { get; set; }
/// <summary>
/// 可用内存占比(%
/// </summary>
public double AvailablePercentage { get; set; }
}

View File

@ -0,0 +1,132 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using System.Security.Cryptography;
namespace Admin.NET.Core;
/// <summary>
/// 哈希生成辅助类
/// </summary>
/// <remarks>
/// 是一系列加密哈希函数,主要用于生成数据的固定长度散列值,以确保数据完整性和安全性。
/// </remarks>
public static class HashHelper
{
/// <summary>
/// 生成 SHA1 哈希值
/// </summary>
/// <param name="data">待加密的数据</param>
/// <returns>生成的哈希值</returns>
public static string Sha1(string data)
{
// 创建 SHA256 加密算法实例,将字符串数据转换为字节数组,并生成相应的哈希值
var hashBytes = SHA1.HashData(Encoding.UTF8.GetBytes(data));
return Convert.ToHexString(hashBytes);
}
/// <summary>
/// 生成 SHA256 哈希值
/// </summary>
/// <param name="data">待加密的数据</param>
/// <returns>生成的哈希值</returns>
public static string Sha256(string data)
{
// 创建 SHA256 加密算法实例,将字符串数据转换为字节数组,并生成相应的哈希值
var hashBytes = SHA256.HashData(Encoding.UTF8.GetBytes(data));
return Convert.ToHexString(hashBytes);
}
/// <summary>
/// 生成 SHA384 哈希值
/// </summary>
/// <param name="data">待加密的数据</param>
/// <returns>生成的哈希值</returns>
public static string Sha384(string data)
{
// 创建 SHA384 加密算法实例,将字符串数据转换为字节数组,并生成相应的哈希值
var hashBytes = SHA384.HashData(Encoding.UTF8.GetBytes(data));
return Convert.ToHexString(hashBytes);
}
/// <summary>
/// 生成 SHA512 哈希值
/// </summary>
/// <param name="data">待加密的数据</param>
/// <returns>生成的哈希值</returns>
public static string Sha512(string data)
{
// 创建 SHA512 加密算法实例,将字符串数据转换为字节数组,并生成相应的哈希值
var hashBytes = SHA512.HashData(Encoding.UTF8.GetBytes(data));
return Convert.ToHexString(hashBytes);
}
/// <summary>
/// 对字符串进行 MD5 生成哈希
/// </summary>
/// <param name="input">待加密的明文字符串</param>
/// <returns>生成的哈希值</returns>
public static string Md5(string input)
{
var hashBytes = MD5.HashData(Encoding.UTF8.GetBytes(input));
return Convert.ToHexString(hashBytes);
}
/// <summary>
/// 对数据流进行 MD5 生成哈希
/// </summary>
/// <param name="inputPath">待加密的数据流路径</param>
/// <returns>生成的哈希值</returns>
public static string StreamMd5(string inputPath)
{
using FileStream stream = new(inputPath, FileMode.Open, FileAccess.Read, FileShare.Read);
return StreamMd5(stream);
}
/// <summary>
/// 对数据流进行 MD5 生成哈希
/// </summary>
/// <param name="stream">待加密的数据流</param>
/// <returns>生成的哈希值</returns>
public static string StreamMd5(Stream stream)
{
var hashBytes = MD5.HashData(stream);
return Convert.ToHexString(hashBytes);
}
/// <summary>
/// 对数据流进行 SHA256 生成哈希
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
public static string StreamHash(Stream data)
{
var hashBytes = SHA256.HashData(data);
return Convert.ToHexString(hashBytes);
}
/// <summary>
/// 对二进制数据进行 MD5 生成哈希
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
public static string ByteMd5(byte[] data)
{
var hashBytes = MD5.HashData(data);
return Convert.ToHexString(hashBytes);
}
/// <summary>
/// 对二进制数据进行 SHA256 生成哈希
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
public static string ByteHash(byte[] data)
{
var hashBytes = SHA256.HashData(data);
return Convert.ToHexString(hashBytes);
}
}

View File

@ -0,0 +1,170 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using System.IO.Compression;
namespace Admin.NET.Core;
/// <summary>
/// 压缩帮助类
/// </summary>
public static class CompressHelper
{
/// <summary>
/// 解压文件
/// </summary>
/// <param name="archivePath">压缩文件路径</param>
/// <param name="extractPath">解压目标路径</param>
/// <param name="format">压缩格式</param>
/// <exception cref="FileNotFoundException">文件不存在时抛出</exception>
/// <exception cref="DirectoryNotFoundException">目录不存在时抛出</exception>
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));
}
}
/// <summary>
/// 压缩文件或目录
/// </summary>
/// <param name="sourcePath">源文件或目录路径</param>
/// <param name="archivePath">压缩文件保存路径</param>
/// <param name="format">压缩格式</param>
/// <param name="level">压缩级别</param>
/// <exception cref="FileNotFoundException">文件不存在时抛出</exception>
/// <exception cref="DirectoryNotFoundException">目录不存在时抛出</exception>
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));
}
}
/// <summary>
/// 压缩单个文件到ZIP
/// </summary>
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);
}
/// <summary>
/// 压缩到GZIP
/// </summary>
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);
}
/// <summary>
/// 从GZIP解压
/// </summary>
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);
}
/// <summary>
/// 压缩到DEFLATE
/// </summary>
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);
}
/// <summary>
/// 从DEFLATE解压
/// </summary>
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);
}
/// <summary>
/// 压缩格式
/// </summary>
public enum CompressionFormat
{
/// <summary>
/// ZIP格式
/// </summary>
Zip,
/// <summary>
/// GZIP格式
/// </summary>
GZip,
/// <summary>
/// DEFLATE格式
/// </summary>
Deflate
}
}

View File

@ -0,0 +1,286 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core;
/// <summary>
/// 目录帮助类
/// </summary>
public static class DirectoryHelper
{
#region
/// <summary>
/// 创建一个新目录,如果目录已存在则不执行任何操作
/// </summary>
/// <param name="directoryPath">要创建的目录的路径</param>
public static void CreateIfNotExists(string directoryPath)
{
if (!Directory.Exists(directoryPath))
{
_ = Directory.CreateDirectory(directoryPath);
}
}
/// <summary>
/// 删除一个目录,如果目录存在
/// </summary>
/// <param name="directoryPath">要删除的目录的路径</param>
public static void DeleteIfExists(string directoryPath)
{
if (Directory.Exists(directoryPath))
{
// true 表示删除目录及其所有子目录和文件
Directory.Delete(directoryPath, true);
}
}
/// <summary>
/// 清空一个目录,不删除目录本身,只删除其中的所有文件和子目录
/// </summary>
/// <param name="directoryPath">要清空的目录的路径</param>
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);
}
}
/// <summary>
/// 移动目录到另一个位置
/// </summary>
/// <param name="sourcePath">当前目录的路径</param>
/// <param name="destinationPath">目标目录的路径</param>
public static void Move(string sourcePath, string destinationPath)
{
Directory.Move(sourcePath, destinationPath);
}
/// <summary>
/// 复制一个目录到另一个位置
/// </summary>
/// <param name="sourcePath">当前目录的路径</param>
/// <param name="destinationPath">目标目录的路径</param>
/// <param name="overwrite">如果目标位置已经存在同名目录,是否覆盖</param>
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
/// <summary>
/// 获取当前目录中所有文件的路径
/// </summary>
/// <param name="directoryPath">目录的路径 </param>
/// <returns>包含目录中文件路径的数组 </returns>
public static string[] GetFiles(string directoryPath)
{
return Directory.GetFiles(directoryPath);
}
/// <summary>
/// 获取目录中所有文件的路径
/// </summary>
/// <param name="directoryPath">目录的路径 </param>
/// <param name="searchPattern">模式字符串,"*"代表0或 N 个字符,"?"代表1个字符 范例:"Log*.xml"表示搜索所有以 Log 开头的 Xml 文件</param>
/// <param name="isSearchChild">是否搜索子目录</param>
/// <returns>包含目录中所有文件路径的数组</returns>
public static string[] GetFiles(string directoryPath, string searchPattern, bool isSearchChild)
{
return Directory.GetFiles(directoryPath, searchPattern,
isSearchChild ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
}
/// <summary>
/// 获取当前目录中所有子目录的路径
/// </summary>
/// <param name="directoryPath">目录的路径 </param>
/// <returns>包含目录中所有子目录路径的数组 </returns>
public static string[] GetDirectories(string directoryPath)
{
return Directory.GetDirectories(directoryPath);
}
/// <summary>
/// 获取指定目录及子目录中所有子目录列表
/// </summary>
/// <param name="directoryPath">指定目录的绝对路径</param>
/// <param name="searchPattern">模式字符串,"*"代表0或 N 个字符,"?"代表1个字符 范例:"Log*.xml"表示搜索所有以 Log 开头的 Xml 目录</param>
/// <param name="isSearchChild">是否搜索子目录</param>
/// <returns>包含目录中所有文件路径的数组</returns>
public static string[] GetDirectories(string directoryPath, string searchPattern, bool isSearchChild)
{
return Directory.GetDirectories(directoryPath, searchPattern,
isSearchChild ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
}
/// <summary>
/// 获取指定目录大小
/// </summary>
/// <param name="dirPath"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
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;
}
/// <summary>
/// 获取随机文件名
/// </summary>
/// <returns></returns>
public static string GetRandomName()
{
return Path.GetRandomFileName();
}
/// <summary>
/// 根据时间得到文件名
/// yyyyMMddHHmmssfff
/// </summary>
/// <returns></returns>
public static string GetDateName()
{
return DateTime.Now.ToString("yyyyMMddHHmmssfff");
}
#endregion
#region
/// <summary>
/// 检查给定路径是否为目录
/// </summary>
/// <param name="path">要检查的路径</param>
/// <returns>true 如果路径是一个目录,否则 false </returns>
public static bool Exists(string path)
{
return Directory.Exists(path);
}
/// <summary>
/// 检测指定目录中是否存在指定的文件(搜索子目录)
/// </summary>
/// <param name="directoryPath">指定目录的绝对路径</param>
/// <param name="searchPattern">模式字符串,"*"代表0或 N 个字符,"?"代表1个字符 范例:"Log*.xml"表示搜索所有以 Log 开头的 Xml 文件 </param>
/// <param name="isSearchChild">是否搜索子目录</param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public static bool IsContainsFiles(string directoryPath, string searchPattern, bool isSearchChild)
{
// 获取指定的文件列表
var fileNames = GetFiles(directoryPath, searchPattern, isSearchChild);
// 判断指定文件是否存在
return fileNames.Length != 0;
}
/// <summary>
/// 检测指定目录是否为空
/// </summary>
/// <param name="directoryPath">指定目录的绝对路径</param>
/// <returns></returns>
public static bool IsEmpty(string directoryPath)
{
// 判断是否存在文件
var fileNames = GetFiles(directoryPath);
if (fileNames.Length != 0)
{
return false;
}
// 判断是否存在文件夹
var directoryNames = GetDirectories(directoryPath);
return directoryNames.Length == 0;
}
/// <summary>
/// 返回应用程序的基目录
/// 程序内部使用的路径,通常是程序所在的文件夹
/// 举例:如果你的程序安装在 C:\MyApp\ 下,那么该属性通常返回 C:\MyApp\
/// </summary>
/// <returns></returns>
public static string GetBaseDirectory()
{
//return AppDomain.CurrentDomain.BaseDirectory;
// 在大多数情况下,它和 AppDomain.CurrentDomain.BaseDirectory 是一样的
return AppContext.BaseDirectory;
}
/// <summary>
/// 当前进程的工作目录,即程序启动时或运行过程中当前的“活动目录”
/// 这个目录可以在程序运行过程中被修改,所以它不一定是程序所在的文件夹
/// 举例:如果你从命令行的 D:\Projects 目录启动了程序,即使程序实际文件在 C:\MyApp\ 下,这个方法返回的就是 D:\Projects
/// </summary>
/// <returns></returns>
public static string GetCurrentDirectory()
{
return Directory.GetCurrentDirectory();
}
/// <summary>
/// 返回应用程序的默认静态文件目录
/// 用于存放静态文件如图片、CSS、JS 等
/// </summary>
/// <returns></returns>
public static string GetWwwrootDirectory()
{
return Path.Combine(GetBaseDirectory(), "wwwroot");
}
#endregion
}

View File

@ -7,22 +7,32 @@
namespace Admin.NET.Core;
/// <summary>
/// 反射工具类
/// File 扩展方法
/// </summary>
public static class ReflectionUtil
public static class FileFormatExtensions
{
/// <summary>
/// 获取字段特性
/// </summary>
/// <param name="field"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static T GetDescriptionValue<T>(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;
/// <summary>
/// 格式化文件大小显示为字符串
/// </summary>
/// <param name="bytes"></param>
/// <returns></returns>
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();
}
}

View File

@ -0,0 +1,332 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core;
/// <summary>
/// 文件帮助类
/// </summary>
public static class FileHelper
{
#region
/// <summary>
/// 读取文件内容到字符串
/// </summary>
/// <param name="filePath">要读取的文件路径</param>
/// <returns>文件内容为字符串</returns>
public static string ReadAllText(string filePath)
{
return File.ReadAllText(filePath);
}
/// <summary>
/// 打开一个文本文件,读取文件的所有行,然后关闭文件
/// </summary>
/// <param name="filePath">要打开以进行读取的文件路径</param>
/// <returns>包含文件所有行的字符串</returns>
public static async Task<string> ReadAllTextAsync(string filePath)
{
using var reader = File.OpenText(filePath);
return await reader.ReadToEndAsync();
}
/// <summary>
/// 打开一个文本文件,读取文件的所有字节,然后关闭文件
/// </summary>
/// <param name="filePath">要打开以进行读取的文件路径</param>
/// <returns>包含文件所有字节的字节数组</returns>
public static async Task<byte[]> 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;
}
/// <summary>
/// 打开一个文本文件,读取文件的所有行,然后关闭文件
/// </summary>
/// <param name="path">要打开以进行读取的文件</param>
/// <param name="encoding">文件的编码默认为 UTF8</param>
/// <param name="fileMode">指定操作系统应如何打开文件默认为 Open</param>
/// <param name="fileAccess">定义对文件的读取、写入或读写访问的常量默认为 Read</param>
/// <param name="fileShare">包含控制其他 FileStream 对象可以对同一文件拥有的访问类型的常量默认为 Read</param>
/// <param name="bufferSize">StreamReader 缓冲区的长度默认为 4096</param>
/// <param name="fileOptions">指示 FileStream 选项默认为 Asynchronous(文件将用于异步读取)和 SequentialScan(文件将从开始到末尾顺序访问)</param>
/// <returns>包含文件所有行的字符串数组</returns>
public static async Task<string[]> 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<string> 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];
}
/// <summary>
/// 打开一个文本文件,读取不包含 BOM 的内容
/// </summary>
/// <param name="path">要打开以进行读取的文件</param>
/// <returns>包含文件所有行的字符串</returns>
public static async Task<string> ReadWithoutBomAsync(string path)
{
var content = await ReadAllBytesAsync(path);
return ConvertFromBytesWithoutBom(content)!;
}
/// <summary>
/// 将字节数组 byte[]转换为不包含字节顺序标记(BOM)的字符串
/// </summary>
/// <param name="bytes">要转换为字符串的 byte[]数组</param>
/// <param name="encoding">获取字符串的编码默认为 UTF8</param>
/// <returns>转换得到的字符串</returns>
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);
}
/// <summary>
/// 将文本内容写入到文件
/// </summary>
/// <param name="filePath">要写入的文件路径</param>
/// <param name="content">要写入的文本内容</param>
public static void WriteAllText(string filePath, string content)
{
File.WriteAllText(filePath, content);
}
/// <summary>
/// 将文本内容追加到文件
/// </summary>
/// <param name="filePath">要追加的文件路径</param>
/// <param name="content">要追加的文本内容</param>
public static void AppendAllText(string filePath, string content)
{
File.AppendAllText(filePath, content);
}
/// <summary>
/// 创建文件,如果文件不存在
/// </summary>
/// <param name="filePath">文件路径</param>
public static void CreateIfNotExists(string filePath)
{
if (!File.Exists(filePath))
{
_ = File.Create(filePath);
}
}
/// <summary>
/// 删除一个文件,如果文件存在
/// </summary>
/// <param name="filePath">要删除的文件路径</param>
public static void DeleteIfExists(string filePath)
{
if (File.Exists(filePath))
{
File.Delete(filePath);
}
}
/// <summary>
/// 移动文件到另一个位置
/// </summary>
/// <param name="sourcePath">当前文件的路径</param>
/// <param name="destinationPath">目标文件的路径</param>
public static void Move(string sourcePath, string destinationPath)
{
File.Move(sourcePath, destinationPath);
}
/// <summary>
/// 复制文件到另一个位置
/// </summary>
/// <param name="sourcePath">当前文件的路径</param>
/// <param name="destinationPath">目标文件的路径</param>
/// <param name="overwrite">如果目标位置已经存在同名文件,是否覆盖</param>
public static void Copy(string sourcePath, string destinationPath, bool overwrite = false)
{
File.Copy(sourcePath, destinationPath, overwrite);
}
/// <summary>
/// 清空文件内容
/// </summary>
/// <param name="filePath">文件的绝对路径</param>
public static void Clean(string filePath)
{
if (!File.Exists(filePath))
{
return;
}
// 删除文件
File.Delete(filePath);
// 重新创建该文件
_ = File.Create(filePath);
}
#endregion
#region
/// <summary>
/// 获取文件的哈希值
/// </summary>
/// <param name="filePath">要计算哈希值的文件路径</param>
/// <returns>文件的哈希值</returns>
public static string GetHash(string filePath)
{
using var stream = File.OpenRead(filePath);
return HashHelper.StreamMd5(stream);
}
/// <summary>
/// 获取指定文件大小
/// </summary>
/// <param name="filePath"></param>
/// <returns></returns>
public static long GetSize(string filePath)
{
return new FileInfo(filePath).Length;
}
/// <summary>
/// 从文件的绝对路径中获取文件名(包含扩展方法名)
/// </summary>
/// <param name="filePath"></param>
/// <returns></returns>
public static string GetName(string filePath)
{
return Path.GetFileName(filePath);
}
/// <summary>
/// 获取随机文件名
/// </summary>
/// <returns></returns>
public static string GetRandomName()
{
return Path.GetRandomFileName();
}
/// <summary>
/// 根据时间得到文件名
/// yyyyMMddHHmmssfff
/// </summary>
/// <returns></returns>
public static string GetDateName()
{
return DateTime.Now.ToString("yyyyMMddHHmmssfff");
}
/// <summary>
/// 从文件的绝对路径中获取扩展方法名
/// 文件扩展方法名是包含点(.)的
/// </summary>
/// <param name="filePath"></param>
/// <returns></returns>
public static string GetExtension(string filePath)
{
return Path.GetExtension(filePath);
}
/// <summary>
/// 从文件的绝对路径中获取文件名(不包含扩展方法名)
/// </summary>
/// <param name="filePath"></param>
/// <returns></returns>
public static string GetNameWithoutExtension(string filePath)
{
return Path.GetFileNameWithoutExtension(filePath);
}
/// <summary>
/// 生成唯一的文件名,上传文件使用
/// </summary>
/// <param name="fileName">包含扩展方法名的源文件名</param>
/// <returns></returns>
public static string GetUniqueName(string fileName)
{
var fileNameWithoutExtension = GetNameWithoutExtension(fileName);
var fileExtension = GetExtension(fileName);
var uniqueFileName = $"{fileNameWithoutExtension}_{GetDateName()}_{GetRandomName()}";
return uniqueFileName + fileExtension;
}
/// <summary>
/// 获取文本文件的行数
/// </summary>
/// <param name="filePath">文件的绝对路径</param>
/// <returns></returns>
public static int GetTextLineCount(string filePath)
{
// 将文本文件的各行读到一个字符串数组中
var rows = File.ReadAllLines(filePath);
// 返回行数
return rows.Length;
}
#endregion
#region
/// <summary>
/// 检查文件是否存在
/// </summary>
/// <param name="filePath">要检查的文件路径</param>
/// <returns>如果文件存在返回 true否则返回 false</returns>
public static bool Exists(string filePath)
{
return File.Exists(filePath);
}
/// <summary>
/// 检查文件是否被锁定
/// </summary>
/// <param name="filePath">要检查的文件路径</param>
/// <returns>true 如果文件没有被锁定,可以进行读写操作,否则 false</returns>
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
}

View File

@ -0,0 +1,113 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core;
/// <summary>
/// 文件流扩展方法
/// </summary>
public static class StreamExtensions
{
/// <summary>
/// 获取文件流字节
/// </summary>
/// <param name="stream"></param>
/// <returns></returns>
public static byte[] GetAllBytes(this Stream stream)
{
if (stream is MemoryStream memoryStream)
{
return memoryStream.ToArray();
}
using var ms = stream.CreateMemoryStream();
return ms.ToArray();
}
/// <summary>
/// 获取文件流字节,异步
/// </summary>
/// <param name="stream"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static async Task<byte[]> 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();
}
/// <summary>
/// 复制文件流,异步
/// </summary>
/// <param name="stream"></param>
/// <param name="destination"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
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);
}
/// <summary>
/// 创建内存流
/// </summary>
/// <param name="stream"></param>
/// <returns></returns>
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;
}
/// <summary>
/// 创建内存流,异步
/// </summary>
/// <param name="stream"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static async Task<MemoryStream> 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;
}
}

View File

@ -14,7 +14,7 @@ public class TreeNode
public int Id { get; set; }
public int Pid { get; set; }
public string Name { get; set; }
public List<TreeNode> Children { get; set; } = new();
public List<TreeNode> Children { get; set; } = [];
}
/// <summary>
@ -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; // 更新当前节点
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,254 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core;
/// <summary>
/// 字符验证帮助类
/// </summary>
public static partial class RegexHelper
{
/// <summary>
/// 验证输入字符串是否与模式字符串匹配,匹配返回 true
/// </summary>
[GeneratedRegex(@"^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$", RegexOptions.IgnoreCase)]
public static partial Regex GuidRegex();
/// <summary>
/// 验证电话号码是否符合格式
/// </summary>
[GeneratedRegex(@"^(\d{3,4})\d{7,8}$", RegexOptions.IgnoreCase)]
public static partial Regex NumberTelRegex();
/// <summary>
/// 验证邮箱地址是否符合格式
/// </summary>
[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();
/// <summary>
/// 验证一个或多个数字
/// </summary>
[GeneratedRegex(@"(\d+)", RegexOptions.IgnoreCase)]
public static partial Regex OneOrMoreNumbersRegex();
/// <summary>
/// 验证是否为整数
/// </summary>
[GeneratedRegex(@"^(-){0,1}\d+$", RegexOptions.IgnoreCase)]
public static partial Regex IntRegex();
/// <summary>
/// 验证是否为数字
/// </summary>
[GeneratedRegex(@"^[0-9]*$", RegexOptions.IgnoreCase)]
public static partial Regex NumberRegex();
/// <summary>
/// 验证是否为整数或小数
/// </summary>
[GeneratedRegex(@"^[0-9]+\.{0,1}[0-9]{0,2}$", RegexOptions.IgnoreCase)]
public static partial Regex NumberIntOrDoubleRegex();
/// <summary>
/// 验证是否为N位数字
/// </summary>
[GeneratedRegex(@"^\d{n}$", RegexOptions.IgnoreCase)]
public static partial Regex NumberSeveralNRegex();
/// <summary>
/// 验证是否为至少N位数字
/// </summary>
[GeneratedRegex(@"^\d{n,}$", RegexOptions.IgnoreCase)]
public static partial Regex NumberSeveralAtLeastNRegex();
/// <summary>
/// 验证是否为M至N位数字
/// </summary>
[GeneratedRegex(@"^\d{m,n}$", RegexOptions.IgnoreCase)]
public static partial Regex NumberSeveralMnRegex();
/// <summary>
/// 验证是否为零或非零开头的数字
/// </summary>
[GeneratedRegex(@"^(0|[1-9] [0-9]*)$", RegexOptions.IgnoreCase)]
public static partial Regex NumberBeginZeroOrNotZeroRegex();
/// <summary>
/// 验证是否为2位小数的正实数
/// </summary>
[GeneratedRegex(@"^[0-9]+(.[0-9]{2})?$", RegexOptions.IgnoreCase)]
public static partial Regex NumberPositiveRealTwoDoubleRegex();
/// <summary>
/// 验证是否为1-3位小数的正实数
/// </summary>
[GeneratedRegex(@"^[0-9]+(.[0-9]{1,3})?$", RegexOptions.IgnoreCase)]
public static partial Regex NumberPositiveRealOneOrThreeDoubleRegex();
/// <summary>
/// 验证是否为非零的正整数
/// </summary>
[GeneratedRegex(@"^\+?[1-9][0-9]*$", RegexOptions.IgnoreCase)]
public static partial Regex NumberPositiveIntNotZeroRegex();
/// <summary>
/// 验证是否为非零的负整数
/// </summary>
[GeneratedRegex(@"^\-?[1-9][0-9]*$", RegexOptions.IgnoreCase)]
public static partial Regex NumberNegativeIntNotZeroRegex();
/// <summary>
/// 验证是否为字母
/// </summary>
[GeneratedRegex(@"^[A-Za-z]+$", RegexOptions.IgnoreCase)]
public static partial Regex LetterRegex();
/// <summary>
/// 验证是否为大写字母
/// </summary>
[GeneratedRegex(@"^[A-Z]+$", RegexOptions.IgnoreCase)]
public static partial Regex LetterCapitalRegex();
/// <summary>
/// 验证是否为小写字母
/// </summary>
[GeneratedRegex(@"^[a-z]+$", RegexOptions.IgnoreCase)]
public static partial Regex LetterLowerRegex();
/// <summary>
/// 验证是否为数字或英文字母
/// </summary>
[GeneratedRegex(@"^[A-Za-z0-9]+$", RegexOptions.IgnoreCase)]
public static partial Regex NumberOrLetterRegex();
/// <summary>
/// 验证字符串长度是否在限定范围内
/// </summary>
[GeneratedRegex(@"[^\x00-\xff]", RegexOptions.IgnoreCase)]
public static partial Regex LengthStrRegex();
/// <summary>
/// 验证是否为长度为3的字符
/// </summary>
[GeneratedRegex(@"^.{3}$", RegexOptions.IgnoreCase)]
public static partial Regex CharThreeRegex();
/// <summary>
/// 验证是否为邮政编码
/// </summary>
[GeneratedRegex(@"^\d{6}$", RegexOptions.IgnoreCase)]
public static partial Regex PostCodeRegex();
/// <summary>
/// 验证是否含有特殊字符
/// </summary>
[GeneratedRegex(@"[^%&',;=?$\x22]+", RegexOptions.IgnoreCase)]
public static partial Regex CharSpecialRegex();
/// <summary>
/// 验证是否包含汉字
/// </summary>
[GeneratedRegex(@"^[\u4e00-\u9fa5]{0,}$", RegexOptions.IgnoreCase)]
public static partial Regex ContainChineseRegex();
/// <summary>
/// 验证是否为汉字
/// </summary>
[GeneratedRegex(@"[一-龥]", RegexOptions.IgnoreCase, "zh-CN")]
public static partial Regex ChineseRegex();
/// <summary>
/// 验证是否为网址
/// </summary>
[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\&amp;%\./-~-]*)?$", RegexOptions.IgnoreCase)]
public static partial Regex UrlRegex();
/// <summary>
/// 验证是否为请求安全参数字符串
/// </summary>
[GeneratedRegex(@"(?<=password=|passwd=|pwd=|secret=|token=)[^&]+", RegexOptions.IgnoreCase)]
public static partial Regex RequestSecurityParamsRegex();
/// <summary>
/// 验证是否为月份
/// </summary>
[GeneratedRegex(@"^^(0?[1-9]|1[0-2])$", RegexOptions.IgnoreCase)]
public static partial Regex MonthRegex();
/// <summary>
/// 验证是否为日期
/// </summary>
[GeneratedRegex(@"^((0?[1-9])|((1|2)[0-9])|30|31)$", RegexOptions.IgnoreCase)]
public static partial Regex DayRegex();
/// <summary>
/// 验证是否为IP地址
/// </summary>
[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();
/// <summary>
/// 验证是否为Cron表达式
/// </summary>
[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();
/// <summary>
/// 验证是否为 Windows 普通文件路径
/// </summary>
/// <returns></returns>
[GeneratedRegex(@"^(?:[a-zA-Z]:\\|\\\\)?(?:[^\\\/:*?""<>|\r\n]+\\)*[^\\\/:*?""<>|\r\n]+\\?$", RegexOptions.IgnoreCase)]
public static partial Regex WindowsPathRegex();
/// <summary>
/// 验证是否为 Linux 普通文件路径
/// </summary>
/// <returns></returns>
[GeneratedRegex(@"^(\/|\/?([^/\0]+(\/[^/\0]+)*\/?))$", RegexOptions.IgnoreCase)]
public static partial Regex LinuxPathRegex();
/// <summary>
/// 验证是否为虚拟文件路径
/// </summary>
/// <returns></returns>
[GeneratedRegex(@"^(~\/|\/)([a-zA-Z0-9_\-\.]+(\/[a-zA-Z0-9_\-\.]+)*)\/?$", RegexOptions.IgnoreCase)]
public static partial Regex VirtualPathRegex();
/// <summary>
/// 验证是否为嵌入文件路径
/// </summary>
[GeneratedRegex(@"^embedded://(?<assembly>[^/]+)/(?<path>.*)$", RegexOptions.IgnoreCase)]
public static partial Regex EmbeddedPathRegex();
/// <summary>
/// 验证是否为内存文件路径
/// </summary>
/// <returns></returns>
[GeneratedRegex(@"^(?i:(?:memory|mem):\/\/).+$", RegexOptions.IgnoreCase)]
public static partial Regex MemoryPathRegex();
/// <summary>
/// 验证是否为 Html 标签
/// </summary>
/// <returns></returns>
[GeneratedRegex(@">([^<>]*)<", RegexOptions.IgnoreCase)]
public static partial Regex HtmlTagContentRegex();
/// <summary>
/// 验证是否文本分割为句子
/// </summary>
/// <returns></returns>
[GeneratedRegex(@"[^.!?。!?]+[.!?]?", RegexOptions.IgnoreCase)]
public static partial Regex SentenceSplitterRegex();
/// <summary>
/// 验证是否为 Unicode 字符
/// </summary>
/// <returns></returns>
[GeneratedRegex(@"\\u([0-9A-Za-z]{4})")]
public static partial Regex UnicodeRegex();
}

View File

@ -1,36 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core;
/// <summary>
/// 正则校验
/// </summary>
public static class RegularValidate
{
/// <summary>
/// 验证密码规则
/// </summary>
/// <param name="password"></param>
/// <returns></returns>
public static bool ValidatePassword(string password)
{
var regex = new Regex(@"
(?=.*[0-9]) #
(?=.*[a-z]) #
(?=.*[A-Z]) #
(?=([\x21-\x7e]+)[^a-zA-Z0-9]) #
.{8,30} #830
", RegexOptions.Multiline | RegexOptions.IgnorePatternWhitespace);
//如果要求必须包含小写、大写字母,则上面的(?=.*[a-zA-Z]) 要改为:
/*
* (?=.*[a-z])
* (?=.*[A-Z])
*/
return regex.IsMatch(password);
}
}

View File

@ -0,0 +1,348 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core;
/// <summary>
/// 操作系统平台帮助类
/// </summary>
public static class OsPlatformHelper
{
/// <summary>
/// 是否为 Unix 系统
/// </summary>
public static bool IsUnixSystem { get; } = GetIsUnixSystem();
/// <summary>
/// 操作系统平台类型
/// </summary>
public static OSPlatform PlatformType { get; } = GetPlatformType();
/// <summary>
/// 是否 Windows 平台
/// </summary>
public static bool IsWindows => PlatformType == OSPlatform.Windows;
/// <summary>
/// 是否 Linux 平台
/// </summary>
public static bool IsLinux => PlatformType == OSPlatform.Linux;
/// <summary>
/// 是否 macOS 平台
/// </summary>
public static bool IsMacOs => PlatformType == OSPlatform.OSX;
/// <summary>
/// 是否 FreeBSD 平台
/// </summary>
public static bool IsFreeBsd => PlatformType == OSPlatform.FreeBSD;
/// <summary>
/// 处理器信息
/// </summary>
/// <remarks>
/// 推荐使用,默认有缓存
/// </remarks>
public static RuntimeInfo RuntimeInfos => Cache.Default.GetOrAdd("RuntimeInfos", _ => GetRuntimeInfo(), 60);
/// <summary>
/// 收集运行时信息
/// </summary>
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<DbConnectionOptions>().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}"
};
}
}
/// <summary>
/// 获取环境变量
/// </summary>
/// <param name="variableName">变量名</param>
/// <param name="target">环境变量目标</param>
/// <returns>环境变量值</returns>
public static string? GetEnvironmentVariable(string variableName, EnvironmentVariableTarget target = EnvironmentVariableTarget.Process)
{
return Environment.GetEnvironmentVariable(variableName, target);
}
/// <summary>
/// 获取所有环境变量
/// </summary>
/// <param name="target">环境变量目标</param>
/// <returns>环境变量字典</returns>
public static Dictionary<string, string?> GetAllEnvironmentVariables(EnvironmentVariableTarget target = EnvironmentVariableTarget.Process)
{
var variables = Environment.GetEnvironmentVariables(target);
return variables.Cast<DictionaryEntry>()
.ToDictionary(entry => entry.Key.ToString()!, entry => entry.Value?.ToString());
}
/// <summary>
/// 检查特定的操作系统版本
/// </summary>
/// <param name="minimumVersion">最小版本要求</param>
/// <returns>是否满足版本要求</returns>
public static bool CheckOsVersion(Version minimumVersion)
{
try
{
return Environment.OSVersion.Version >= minimumVersion;
}
catch
{
return false;
}
}
/// <summary>
/// 获取.NET运行时位置
/// </summary>
/// <returns>运行时路径</returns>
public static string GetRuntimeLocation()
{
try
{
return Path.GetDirectoryName(typeof(object).Assembly.Location) ?? string.Empty;
}
catch
{
return string.Empty;
}
}
/// <summary>
/// 判断当前操作系统是否为 Unix 系统
/// </summary>
/// <returns></returns>
public static bool GetIsUnixSystem()
{
return RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ||
RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ||
RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD);
}
/// <summary>
/// 获取操作系统平台类型
/// </summary>
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");
}
/// <summary>
/// 获取操作系统名称
/// </summary>
private static string GetOperatingSystemName()
{
var platformType = GetPlatformType();
return platformType.ToString() switch
{
"Windows" => "Windows",
"Linux" => "Linux",
"OSX" => "MacOS",
"FreeBSD" => "FreeBSD",
_ => "Unknown"
};
}
}
/// <summary>
/// 系统运行时信息
/// </summary>
public record RuntimeInfo
{
/// <summary>
/// 操作系统名称
/// </summary>
public string OsName { get; set; } = string.Empty;
/// <summary>
/// 操作系统描述
/// </summary>
public string OsDescription { get; set; } = string.Empty;
/// <summary>
/// 操作系统版本
/// </summary>
public string OsVersion { get; set; } = string.Empty;
/// <summary>
/// 操作系统架构
/// </summary>
public string OsArchitecture { get; set; } = string.Empty;
/// <summary>
/// 进程架构
/// </summary>
public string ProcessArchitecture { get; set; } = string.Empty;
/// <summary>
/// 运行时框架描述
/// </summary>
public string FrameworkDescription { get; set; } = string.Empty;
/// <summary>
/// 运行时版本
/// </summary>
public string RuntimeVersion { get; set; } = string.Empty;
/// <summary>
/// 数据库类型
/// </summary>
public string DatabaseType { get; set; } = string.Empty;
/// <summary>
/// 是否64位操作系统
/// </summary>
public bool Is64BitOperatingSystem { get; set; }
/// <summary>
/// 是否64位进程
/// </summary>
public bool Is64BitProcess { get; set; }
/// <summary>
/// 是否交互模式
/// </summary>
public bool IsInteractive { get; set; }
/// <summary>
/// 交互模式描述
/// </summary>
public string InteractiveMode => IsInteractive ? "交互运行" : "非交互运行";
/// <summary>
/// 处理器数量
/// </summary>
public int ProcessorCount { get; set; }
/// <summary>
/// 系统目录
/// </summary>
public string SystemDirectory { get; set; } = string.Empty;
/// <summary>
/// 当前目录
/// </summary>
public string CurrentDirectory { get; set; } = string.Empty;
/// <summary>
/// 机器名称
/// </summary>
public string MachineName { get; set; } = string.Empty;
/// <summary>
/// 用户名
/// </summary>
public string UserName { get; set; } = string.Empty;
/// <summary>
/// 用户域名
/// </summary>
public string UserDomainName { get; set; } = string.Empty;
/// <summary>
/// 工作集大小(字节)
/// </summary>
public long WorkingSet { get; set; }
/// <summary>
/// 系统启动时间
/// </summary>
public DateTime SystemStartTime { get; set; }
/// <summary>
/// 系统运行时间
/// </summary>
public TimeSpan SystemUptime { get; set; }
/// <summary>
/// 进程启动时间
/// </summary>
public DateTime ProcessStartTime { get; set; }
/// <summary>
/// 进程运行时间
/// </summary>
public TimeSpan ProcessUptime { get; set; }
/// <summary>
/// 进程ID
/// </summary>
public int ProcessId { get; set; }
/// <summary>
/// 进程名称
/// </summary>
public string ProcessName { get; set; } = string.Empty;
/// <summary>
/// CLR版本
/// </summary>
public string ClrVersion { get; set; } = string.Empty;
/// <summary>
/// 环境变量数量
/// </summary>
public int EnvironmentVariableCount { get; set; }
/// <summary>
/// 命令行参数
/// </summary>
public string[] CommandLineArgs { get; set; } = [];
}

View File

@ -0,0 +1,95 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core;
/// <summary>
/// 系统运行时间
/// </summary>
public static class RunningTimeHelper
{
/// <summary>
/// 处理器信息
/// </summary>
/// <remarks>
/// 推荐使用,默认有缓存
/// </remarks>
public static string RunningTime => Cache.Default.GetOrAdd("RunningTime", _ => GetRunningTime(), 60);
/// <summary>
/// 获取系统运行时间
/// </summary>
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;
}
/// <summary>
/// 解析运行时间
/// </summary>
/// <param name="uptime"></param>
/// <returns></returns>
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);
}
}

View File

@ -0,0 +1,45 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core;
/// <summary>
/// 运行时信息管理器
/// </summary>
public static class RuntimeInfoManger
{
/// <summary>
/// 获取当前运行时信息
/// </summary>
/// <remarks>
/// 推荐使用,默认有缓存
/// </remarks>
/// <returns>运行时信息</returns>
public static SystemRuntimeInfo GetSystemRuntimeInfo()
{
return new SystemRuntimeInfo
{
RuntimeInfo = OsPlatformHelper.RuntimeInfos,
RunningTime = RunningTimeHelper.RunningTime
};
}
}
/// <summary>
/// 系统运行时信息
/// </summary>
public class SystemRuntimeInfo
{
/// <summary>
/// 运行时信息
/// </summary>
public RuntimeInfo RuntimeInfo { get; set; } = new();
/// <summary>
/// 运行时间
/// </summary>
public string RunningTime { get; set; } = string.Empty;
}

View File

@ -0,0 +1,545 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core;
/// <summary>
/// 运行时监控器
/// </summary>
public class RuntimeMonitor : IDisposable
{
private readonly Process _currentProcess;
private readonly ConcurrentQueue<PerformanceSnapshot> _snapshots;
private readonly Timer? _monitorTimer;
private readonly object _lockObject = new();
private readonly int _maxSnapshotCount;
private volatile bool _isDisposed;
private DateTime _lastCpuTime;
private TimeSpan _lastTotalProcessorTime;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="monitorInterval">监控间隔默认5秒</param>
/// <param name="maxSnapshotCount">最大快照数量默认1000</param>
public RuntimeMonitor(TimeSpan? monitorInterval = null, int maxSnapshotCount = 1000)
{
_currentProcess = Process.GetCurrentProcess();
_snapshots = new ConcurrentQueue<PerformanceSnapshot>();
_maxSnapshotCount = maxSnapshotCount;
MonitorInterval = monitorInterval ?? TimeSpan.FromSeconds(5);
// 初始化CPU时间基准
_lastCpuTime = DateTime.UtcNow;
_lastTotalProcessorTime = _currentProcess.TotalProcessorTime;
// 创建定时器但不立即启动
_monitorTimer = new Timer(CollectSnapshot, null, Timeout.Infinite, Timeout.Infinite);
}
/// <summary>
/// 监控间隔
/// </summary>
public TimeSpan MonitorInterval { get; }
/// <summary>
/// 是否正在监控
/// </summary>
public bool IsMonitoring { get; private set; }
/// <summary>
/// 快照历史记录数量
/// </summary>
public int SnapshotCount => _snapshots.Count;
/// <summary>
/// 监控开始时间
/// </summary>
public DateTime? MonitorStartTime { get; private set; }
/// <summary>
/// 强制垃圾回收
/// </summary>
/// <param name="generation">GC代数-1表示全部代</param>
/// <param name="mode">GC模式</param>
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();
}
/// <summary>
/// 获取内存压力信息
/// </summary>
/// <returns>内存压力信息</returns>
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";
}
/// <summary>
/// 开始监控
/// </summary>
public void StartMonitoring()
{
if (IsMonitoring || _isDisposed)
{
return;
}
lock (_lockObject)
{
if (IsMonitoring || _isDisposed)
{
return;
}
IsMonitoring = true;
MonitorStartTime = DateTime.Now;
// 立即收集一次快照
CollectSnapshot(null);
// 启动定时器
_monitorTimer?.Change(MonitorInterval, MonitorInterval);
}
}
/// <summary>
/// 停止监控
/// </summary>
public void StopMonitoring()
{
if (!IsMonitoring)
{
return;
}
lock (_lockObject)
{
if (!IsMonitoring)
{
return;
}
IsMonitoring = false;
_monitorTimer?.Change(Timeout.Infinite, Timeout.Infinite);
}
}
/// <summary>
/// 获取当前性能快照
/// </summary>
/// <returns>当前性能快照</returns>
public PerformanceSnapshot GetCurrentSnapshot()
{
return CreateSnapshot();
}
/// <summary>
/// 获取最新的性能快照
/// </summary>
/// <returns>最新的性能快照如果没有则返回null</returns>
public PerformanceSnapshot? GetLatestSnapshot()
{
return _snapshots.TryPeek(out var snapshot) ? snapshot : null;
}
/// <summary>
/// 获取所有性能快照
/// </summary>
/// <returns>性能快照列表</returns>
public List<PerformanceSnapshot> GetAllSnapshots()
{
return [.. _snapshots];
}
/// <summary>
/// 获取指定时间范围内的快照
/// </summary>
/// <param name="timeRange">时间范围</param>
/// <returns>快照列表</returns>
public List<PerformanceSnapshot> GetSnapshotsInTimeRange(TimeSpan timeRange)
{
var cutoffTime = DateTime.Now - timeRange;
return [.. _snapshots.Where(s => s.Timestamp >= cutoffTime)];
}
/// <summary>
/// 分析性能趋势
/// </summary>
/// <param name="counterType">计数器类型</param>
/// <param name="timeRange">分析时间范围null表示分析所有数据</param>
/// <returns>性能趋势分析结果</returns>
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;
}
/// <summary>
/// 清空所有快照
/// </summary>
public void ClearSnapshots()
{
while (_snapshots.TryDequeue(out _)) { }
}
/// <summary>
/// 获取性能摘要报告
/// </summary>
/// <returns>性能摘要字符串</returns>
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}";
}
/// <summary>
/// 释放资源
/// </summary>
public void Dispose()
{
if (_isDisposed)
{
return;
}
_isDisposed = true;
StopMonitoring();
_monitorTimer?.Dispose();
_currentProcess?.Dispose();
GC.SuppressFinalize(this);
}
/// <summary>
/// 获取计数器值
/// </summary>
/// <param name="snapshot">性能快照</param>
/// <param name="counterType">计数器类型</param>
/// <returns>计数器值</returns>
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
};
}
/// <summary>
/// 收集性能快照
/// </summary>
/// <param name="state">定时器状态</param>
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}");
}
}
/// <summary>
/// 创建性能快照
/// </summary>
/// <returns>性能快照</returns>
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
};
}
}
/// <summary>
/// 性能指标快照
/// </summary>
public record PerformanceSnapshot
{
/// <summary>
/// 快照时间
/// </summary>
public DateTime Timestamp { get; set; }
/// <summary>
/// CPU使用率百分比
/// </summary>
public double CpuUsage { get; set; }
/// <summary>
/// 内存使用量(字节)
/// </summary>
public long MemoryUsage { get; set; }
/// <summary>
/// 私有内存使用量(字节)
/// </summary>
public long PrivateMemorySize { get; set; }
/// <summary>
/// 虚拟内存使用量(字节)
/// </summary>
public long VirtualMemorySize { get; set; }
/// <summary>
/// 工作集大小(字节)
/// </summary>
public long WorkingSet { get; set; }
/// <summary>
/// GC代0收集次数
/// </summary>
public int GcGen0Collections { get; set; }
/// <summary>
/// GC代1收集次数
/// </summary>
public int GcGen1Collections { get; set; }
/// <summary>
/// GC代2收集次数
/// </summary>
public int GcGen2Collections { get; set; }
/// <summary>
/// 托管内存使用量(字节)
/// </summary>
public long ManagedMemory { get; set; }
/// <summary>
/// 线程数量
/// </summary>
public int ThreadCount { get; set; }
/// <summary>
/// 句柄数量
/// </summary>
public int HandleCount { get; set; }
/// <summary>
/// 用户处理器时间
/// </summary>
public TimeSpan UserProcessorTime { get; set; }
/// <summary>
/// 特权处理器时间
/// </summary>
public TimeSpan PrivilegedProcessorTime { get; set; }
/// <summary>
/// 总处理器时间
/// </summary>
public TimeSpan TotalProcessorTime { get; set; }
/// <summary>
/// 进程运行时间
/// </summary>
public TimeSpan ProcessUptime { get; set; }
}
/// <summary>
/// 性能趋势分析结果
/// </summary>
public record PerformanceTrend
{
/// <summary>
/// 计数器类型
/// </summary>
public PerformanceCounterType CounterType { get; set; }
/// <summary>
/// 当前值
/// </summary>
public double CurrentValue { get; set; }
/// <summary>
/// 平均值
/// </summary>
public double AverageValue { get; set; }
/// <summary>
/// 最小值
/// </summary>
public double MinValue { get; set; }
/// <summary>
/// 最大值
/// </summary>
public double MaxValue { get; set; }
/// <summary>
/// 变化趋势(正数表示上升,负数表示下降)
/// </summary>
public double Trend { get; set; }
/// <summary>
/// 标准差
/// </summary>
public double StandardDeviation { get; set; }
/// <summary>
/// 样本数量
/// </summary>
public int SampleCount { get; set; }
/// <summary>
/// 分析时间范围
/// </summary>
public TimeSpan AnalysisTimespan { get; set; }
}
/// <summary>
/// 性能计数器类型枚举
/// </summary>
public enum PerformanceCounterType
{
/// <summary>
/// CPU使用率
/// </summary>
CpuUsage = 1,
/// <summary>
/// 内存使用量
/// </summary>
MemoryUsage = 2,
/// <summary>
/// GC收集次数
/// </summary>
GcCollections = 3,
/// <summary>
/// 线程数量
/// </summary>
ThreadCount = 4,
/// <summary>
/// 句柄数量
/// </summary>
HandleCount = 5
}

View File

@ -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);
}
/// <summary>
@ -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);
}
/// <summary>
@ -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];

View File

@ -11,7 +11,7 @@ namespace Admin.NET.Core;
/// <summary>
/// 代码生成帮助类
/// </summary>
public static class CodeGenUtil
public static class CodeGenHelper
{
/// <summary>
/// 转换大驼峰法命名
@ -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",
};
}
// 默认数据类型

View File

@ -1,53 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using System.Security.Cryptography;
namespace Admin.NET.Core;
/// <summary>
/// 3DES文件加解密
/// </summary>
public static class TripleDES
{
/// <summary>
/// 加密文件
/// </summary>
/// <param name="inputFile">待加密文件路径</param>
/// <param name="outputFile">加密后的文件路径</param>
/// <param name="password">密码 24位长度</param>
[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);
}
/// <summary>
/// 加密文件
/// </summary>
/// <param name="inputFile">加密的文件路径</param>
/// <param name="outputFile">解密后的文件路径</param>
/// <param name="password">密码 24位长度</param>
[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);
}
}

View File

@ -82,10 +82,10 @@ public static class VerifyFileExtensionName
{
foreach (var ext in dics.Value.Split(","))
{
if (!ExtDics.ContainsKey(ext))
ExtDics.Add(ext, new HashSet<int> { dics.Key.Length / 2 });
if (!ExtDics.TryGetValue(ext, out HashSet<int> value))
ExtDics.Add(ext, [dics.Key.Length / 2]);
else
ExtDics[ext].Add(dics.Key.Length / 2);
value.Add(dics.Key.Length / 2);
}
}
}

View File

@ -68,6 +68,7 @@ public class DingTalkService : IDynamicApiController, IScoped
/// <param name="input"></param>
/// <returns></returns>
[DisplayName("给指定用户发送钉钉互动卡片")]
[Obsolete]
public async Task<DingTalkSendInteractiveCardsOutput> DingTalkSendInteractiveCards(string token, DingTalkSendInteractiveCardsInput input)
{
return await _dingTalkApi.DingTalkSendInteractiveCards(token, input);

View File

@ -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,

View File

@ -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<RequestArgs> => {
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<AxiosResponse<void>>> {
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<AxiosResponse<AdminNETResultListDbOutput>> {
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<AxiosResponse<void>> {
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<AxiosResponse<AdminNETResultListDbOutput>> {
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<AxiosResponse<void>> {
return SysDatabaseApiFp(this.configuration).apiSysDatabaseMoveColumnPost(body, options).then((request) => request(this.axios, this.basePath));
}
/**
*
* @summary 🔖

View File

@ -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<RequestArgs> => {
const localVarPath = `/api/sysServer/assemblyList`;
apiSysServerHardwareInfoGet: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
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<RequestArgs> => {
const localVarPath = `/api/sysServer/serverBase`;
apiSysServerNuGetPackageInfoGet: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
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<RequestArgs> => {
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<RequestArgs> => {
const localVarPath = `/api/sysServer/serverUsed`;
apiSysServerRuntimeInfoGet: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
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<AxiosResponse<AdminNETResultObject>>> {
const localVarAxiosArgs = await SysServerApiAxiosParamCreator(configuration).apiSysServerAssemblyListGet(options);
async apiSysServerHardwareInfoGet(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<AdminNETResultSystemHardwareInfo>>> {
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<AxiosResponse<AdminNETResultObject>>> {
const localVarAxiosArgs = await SysServerApiAxiosParamCreator(configuration).apiSysServerServerBaseGet(options);
async apiSysServerNuGetPackageInfoGet(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<AdminNETResultListNuGetPackage>>> {
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<AxiosResponse<AdminNETResultObject>>> {
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<AxiosResponse<AdminNETResultObject>>> {
const localVarAxiosArgs = await SysServerApiAxiosParamCreator(configuration).apiSysServerServerUsedGet(options);
async apiSysServerRuntimeInfoGet(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<AdminNETResultSystemRuntimeInfo>>> {
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<AxiosResponse<AdminNETResultObject>> {
return SysServerApiFp(configuration).apiSysServerAssemblyListGet(options).then((request) => request(axios, basePath));
async apiSysServerHardwareInfoGet(options?: AxiosRequestConfig): Promise<AxiosResponse<AdminNETResultSystemHardwareInfo>> {
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<AxiosResponse<AdminNETResultObject>> {
return SysServerApiFp(configuration).apiSysServerServerBaseGet(options).then((request) => request(axios, basePath));
async apiSysServerNuGetPackageInfoGet(options?: AxiosRequestConfig): Promise<AxiosResponse<AdminNETResultListNuGetPackage>> {
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<AxiosResponse<AdminNETResultObject>> {
return SysServerApiFp(configuration).apiSysServerServerDiskGet(options).then((request) => request(axios, basePath));
},
/**
*
* @summary 使 🔖
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async apiSysServerServerUsedGet(options?: AxiosRequestConfig): Promise<AxiosResponse<AdminNETResultObject>> {
return SysServerApiFp(configuration).apiSysServerServerUsedGet(options).then((request) => request(axios, basePath));
async apiSysServerRuntimeInfoGet(options?: AxiosRequestConfig): Promise<AxiosResponse<AdminNETResultSystemRuntimeInfo>> {
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<AxiosResponse<AdminNETResultObject>> {
return SysServerApiFp(this.configuration).apiSysServerAssemblyListGet(options).then((request) => request(this.axios, this.basePath));
public async apiSysServerHardwareInfoGet(options?: AxiosRequestConfig) : Promise<AxiosResponse<AdminNETResultSystemHardwareInfo>> {
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<AxiosResponse<AdminNETResultObject>> {
return SysServerApiFp(this.configuration).apiSysServerServerBaseGet(options).then((request) => request(this.axios, this.basePath));
public async apiSysServerNuGetPackageInfoGet(options?: AxiosRequestConfig) : Promise<AxiosResponse<AdminNETResultListNuGetPackage>> {
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<AxiosResponse<AdminNETResultObject>> {
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<AxiosResponse<AdminNETResultObject>> {
return SysServerApiFp(this.configuration).apiSysServerServerUsedGet(options).then((request) => request(this.axios, this.basePath));
public async apiSysServerRuntimeInfoGet(options?: AxiosRequestConfig) : Promise<AxiosResponse<AdminNETResultSystemRuntimeInfo>> {
return SysServerApiFp(this.configuration).apiSysServerRuntimeInfoGet(options).then((request) => request(this.axios, this.basePath));
}
}

View File

@ -0,0 +1,71 @@
/* tslint:disable */
/* eslint-disable */
/**
* Admin.NET
* .NET <br/><u><b><font color='FF0000'> 👮</font></b></u>
*
* 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;
/**
* successwarningerror
*
* @type {string}
* @memberof AdminNETResultListNuGetPackage
*/
type?: string | null;
/**
*
*
* @type {string}
* @memberof AdminNETResultListNuGetPackage
*/
message?: string | null;
/**
*
*
* @type {Array<NuGetPackage>}
* @memberof AdminNETResultListNuGetPackage
*/
result?: Array<NuGetPackage> | null;
/**
*
*
* @type {any}
* @memberof AdminNETResultListNuGetPackage
*/
extras?: any | null;
/**
*
*
* @type {Date}
* @memberof AdminNETResultListNuGetPackage
*/
time?: Date;
}

Some files were not shown because too many files have changed in this diff Show More