😎1、调整系统监控页面 2、清理与调整自带工具类 3、优化数据库操作相关 4、代码清理及升级依赖
This commit is contained in:
parent
4c71084e63
commit
d76cde34c6
@ -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();
|
||||
|
||||
@ -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 通用权限开发平台"];
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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];
|
||||
|
||||
@ -126,7 +126,7 @@ public partial class SysUser : EntityTenant
|
||||
|
||||
/// <summary>
|
||||
/// 毕业院校
|
||||
/// </summary>COLLEGE
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDescription = "毕业院校", Length = 128)]
|
||||
[MaxLength(128)]
|
||||
public string? College { get; set; }
|
||||
|
||||
@ -530,7 +530,7 @@ public enum ErrorCodeEnum
|
||||
D8000,
|
||||
|
||||
/// <summary>
|
||||
/// 不允许的文件类型
|
||||
/// 不允许的文件类型
|
||||
/// </summary>
|
||||
[ErrorCodeItemMetadata("不允许的文件类型:{0}")]
|
||||
D8001,
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Utils;
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
public static class EnumerableExtensions
|
||||
{
|
||||
@ -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>
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
459
Admin.NET/Admin.NET.Core/Extension/ParseExtensions.cs
Normal file
459
Admin.NET/Admin.NET.Core/Extension/ParseExtensions.cs
Normal 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 e−45 至 ±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 e−45 至 ±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 e−324 到 ±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 e−324 到 ±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
|
||||
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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 = "";
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
[
|
||||
|
||||
@ -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
|
||||
});
|
||||
|
||||
|
||||
@ -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>();
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -44,7 +44,7 @@ public class TableOutput
|
||||
/// <summary>
|
||||
/// 表字段个数
|
||||
/// </summary>
|
||||
public int ColumnCount { get; set; }
|
||||
public int ColumnCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 程序集名称
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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; }
|
||||
}
|
||||
@ -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);
|
||||
|
||||
@ -52,7 +52,6 @@ public class SysDbBackupService : IDynamicApiController, ITransient
|
||||
}
|
||||
|
||||
dbBackupList = dbBackupList.OrderByDescending(u => u.CreateTime).ToList();
|
||||
|
||||
return dbBackupList;
|
||||
}
|
||||
catch
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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的实例,与请求对象对应
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
|
||||
@ -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();
|
||||
|
||||
// 清空密码错误次数缓存
|
||||
|
||||
@ -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 = $"生成失败,页面路径不能为空";
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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]);
|
||||
|
||||
@ -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,
|
||||
// 可扩展其他函数
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
374
Admin.NET/Admin.NET.Core/Utils/Collections/ListExtensions.cs
Normal file
374
Admin.NET/Admin.NET.Core/Utils/Collections/ListExtensions.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
361
Admin.NET/Admin.NET.Core/Utils/Collections/QueueExtensions.cs
Normal file
361
Admin.NET/Admin.NET.Core/Utils/Collections/QueueExtensions.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
542
Admin.NET/Admin.NET.Core/Utils/Collections/StackExtensions.cs
Normal file
542
Admin.NET/Admin.NET.Core/Utils/Collections/StackExtensions.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
375
Admin.NET/Admin.NET.Core/Utils/Collections/TreeExtensions.cs
Normal file
375
Admin.NET/Admin.NET.Core/Utils/Collections/TreeExtensions.cs
Normal 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; } = [];
|
||||
}
|
||||
146
Admin.NET/Admin.NET.Core/Utils/CommandLine/ScriptExecutor.cs
Normal file
146
Admin.NET/Admin.NET.Core/Utils/CommandLine/ScriptExecutor.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
76
Admin.NET/Admin.NET.Core/Utils/CommandLine/ShellHelper.cs
Normal file
76
Admin.NET/Admin.NET.Core/Utils/CommandLine/ShellHelper.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
280
Admin.NET/Admin.NET.Core/Utils/DateTime/DateTimeRange.cs
Normal file
280
Admin.NET/Admin.NET.Core/Utils/DateTime/DateTimeRange.cs
Normal 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}]";
|
||||
}
|
||||
}
|
||||
866
Admin.NET/Admin.NET.Core/Utils/DateTime/LunarCalendarHelper.cs
Normal file
866
Admin.NET/Admin.NET.Core/Utils/DateTime/LunarCalendarHelper.cs
Normal 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日})";
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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>();
|
||||
@ -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}";
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
141
Admin.NET/Admin.NET.Core/Utils/HardwareInfo/BoardHelper.cs
Normal file
141
Admin.NET/Admin.NET.Core/Utils/HardwareInfo/BoardHelper.cs
Normal 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;
|
||||
}
|
||||
269
Admin.NET/Admin.NET.Core/Utils/HardwareInfo/CpuHelper.cs
Normal file
269
Admin.NET/Admin.NET.Core/Utils/HardwareInfo/CpuHelper.cs
Normal 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; }
|
||||
}
|
||||
137
Admin.NET/Admin.NET.Core/Utils/HardwareInfo/DiskHelper.cs
Normal file
137
Admin.NET/Admin.NET.Core/Utils/HardwareInfo/DiskHelper.cs
Normal 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; }
|
||||
}
|
||||
347
Admin.NET/Admin.NET.Core/Utils/HardwareInfo/GpuHelper.cs
Normal file
347
Admin.NET/Admin.NET.Core/Utils/HardwareInfo/GpuHelper.cs
Normal 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; }
|
||||
}
|
||||
@ -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; } = [];
|
||||
}
|
||||
303
Admin.NET/Admin.NET.Core/Utils/HardwareInfo/NetworkHelper.cs
Normal file
303
Admin.NET/Admin.NET.Core/Utils/HardwareInfo/NetworkHelper.cs
Normal 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; }
|
||||
}
|
||||
211
Admin.NET/Admin.NET.Core/Utils/HardwareInfo/RamHelper.cs
Normal file
211
Admin.NET/Admin.NET.Core/Utils/HardwareInfo/RamHelper.cs
Normal 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; }
|
||||
}
|
||||
132
Admin.NET/Admin.NET.Core/Utils/HashHelper.cs
Normal file
132
Admin.NET/Admin.NET.Core/Utils/HashHelper.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
170
Admin.NET/Admin.NET.Core/Utils/IO/CompressHelper.cs
Normal file
170
Admin.NET/Admin.NET.Core/Utils/IO/CompressHelper.cs
Normal 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
|
||||
}
|
||||
}
|
||||
286
Admin.NET/Admin.NET.Core/Utils/IO/DirectoryHelper.cs
Normal file
286
Admin.NET/Admin.NET.Core/Utils/IO/DirectoryHelper.cs
Normal 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 目录检查
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
332
Admin.NET/Admin.NET.Core/Utils/IO/FileHelper.cs
Normal file
332
Admin.NET/Admin.NET.Core/Utils/IO/FileHelper.cs
Normal 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 文件检查
|
||||
}
|
||||
113
Admin.NET/Admin.NET.Core/Utils/IO/StreamExtensions.cs
Normal file
113
Admin.NET/Admin.NET.Core/Utils/IO/StreamExtensions.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@ -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; // 更新当前节点
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
1031
Admin.NET/Admin.NET.Core/Utils/ReflectionHelper.cs
Normal file
1031
Admin.NET/Admin.NET.Core/Utils/ReflectionHelper.cs
Normal file
File diff suppressed because it is too large
Load Diff
254
Admin.NET/Admin.NET.Core/Utils/RegexHelper.cs
Normal file
254
Admin.NET/Admin.NET.Core/Utils/RegexHelper.cs
Normal 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\&%\./-~-]*)?$", 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();
|
||||
}
|
||||
@ -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} #至少8个字符,最多30个字符
|
||||
", RegexOptions.Multiline | RegexOptions.IgnorePatternWhitespace);
|
||||
|
||||
//如果要求必须包含小写、大写字母,则上面的(?=.*[a-zA-Z]) 要改为:
|
||||
/*
|
||||
* (?=.*[a-z])
|
||||
* (?=.*[A-Z])
|
||||
*/
|
||||
return regex.IsMatch(password);
|
||||
}
|
||||
}
|
||||
348
Admin.NET/Admin.NET.Core/Utils/Runtime/OSPlatformHelper.cs
Normal file
348
Admin.NET/Admin.NET.Core/Utils/Runtime/OSPlatformHelper.cs
Normal 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; } = [];
|
||||
}
|
||||
95
Admin.NET/Admin.NET.Core/Utils/Runtime/RunningTimeHelper.cs
Normal file
95
Admin.NET/Admin.NET.Core/Utils/Runtime/RunningTimeHelper.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
45
Admin.NET/Admin.NET.Core/Utils/Runtime/RuntimeInfoManger.cs
Normal file
45
Admin.NET/Admin.NET.Core/Utils/Runtime/RuntimeInfoManger.cs
Normal 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;
|
||||
}
|
||||
545
Admin.NET/Admin.NET.Core/Utils/Runtime/RuntimeMonitor.cs
Normal file
545
Admin.NET/Admin.NET.Core/Utils/Runtime/RuntimeMonitor.cs
Normal 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
|
||||
}
|
||||
@ -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];
|
||||
|
||||
@ -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",
|
||||
};
|
||||
}
|
||||
|
||||
// 默认数据类型
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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 获取种子数据列表 🔖
|
||||
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
/**
|
||||
* 类型success、warning、error
|
||||
*
|
||||
* @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
Loading…
Reference in New Issue
Block a user