feat: initialize Evergreen Health backend
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 控制台logo
|
||||
/// </summary>
|
||||
public static class ConsoleLogoSetup
|
||||
{
|
||||
public static void AddConsoleLogo(this IServiceCollection services)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Blue;
|
||||
Console.WriteLine(@"
|
||||
_ _ _ _ ______ _______
|
||||
/\ | | (_) | \ | | ____|__ __|
|
||||
/ \ __| |_ __ ___ _ _ __ | \| | |__ | |
|
||||
/ /\ \ / _` | '_ ` _ \| | '_ \ | . ` | __| | |
|
||||
/ ____ \ (_| | | | | | | | | | |_| |\ | |____ | |
|
||||
/_/ \_\__,_|_| |_| |_|_|_| |_(_)_| \_|______| |_| ");
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine(@"让.NET更简单、更通用、更流行!");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 基本数据类型扩展(作为NewLife.Core的Utility的补充)
|
||||
/// </summary>
|
||||
public static class DataTypeExtension
|
||||
{
|
||||
/// <summary>转为SByte整数,转换失败时返回默认值。</summary>
|
||||
/// <remarks></remarks>
|
||||
/// <param name="value">待转换对象</param>
|
||||
/// <param name="defaultValue">默认值。待转换对象无效时使用</param>
|
||||
/// <returns></returns>
|
||||
public static sbyte ToSByte(this object value, sbyte defaultValue = default)
|
||||
{
|
||||
if (value is sbyte num) return num;
|
||||
if (value == null || value == DBNull.Value) return defaultValue;
|
||||
|
||||
if (sbyte.TryParse(value.ToString(), out var result))
|
||||
return result;
|
||||
else
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/// <summary>转为Byte整数,转换失败时返回默认值。</summary>
|
||||
/// <remarks></remarks>
|
||||
/// <param name="value">待转换对象</param>
|
||||
/// <param name="defaultValue">默认值。待转换对象无效时使用</param>
|
||||
/// <returns></returns>
|
||||
public static byte ToByte(this object value, byte defaultValue = default)
|
||||
{
|
||||
if (value is byte num) return num;
|
||||
if (value == null || value == DBNull.Value) return defaultValue;
|
||||
|
||||
if (byte.TryParse(value.ToString(), out var result))
|
||||
return result;
|
||||
else
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/// <summary>转为Int16整数,转换失败时返回默认值。</summary>
|
||||
/// <remarks></remarks>
|
||||
/// <param name="value">待转换对象</param>
|
||||
/// <param name="defaultValue">默认值。待转换对象无效时使用</param>
|
||||
/// <returns></returns>
|
||||
public static short ToInt16(this object value, short defaultValue = default)
|
||||
{
|
||||
if (value is short num) return num;
|
||||
if (value == null || value == DBNull.Value) return defaultValue;
|
||||
|
||||
if (short.TryParse(value.ToString(), out var result))
|
||||
return result;
|
||||
else
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/// <summary>转为UInt16整数,转换失败时返回默认值。</summary>
|
||||
/// <remarks></remarks>
|
||||
/// <param name="value">待转换对象</param>
|
||||
/// <param name="defaultValue">默认值。待转换对象无效时使用</param>
|
||||
/// <returns></returns>
|
||||
public static ushort ToUInt16(this object value, ushort defaultValue = default)
|
||||
{
|
||||
if (value is ushort num) return num;
|
||||
if (value == null || value == DBNull.Value) return defaultValue;
|
||||
|
||||
if (ushort.TryParse(value.ToString(), out var result))
|
||||
return result;
|
||||
else
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/// <summary>转为UInt32整数,转换失败时返回默认值。</summary>
|
||||
/// <remarks></remarks>
|
||||
/// <param name="value">待转换对象</param>
|
||||
/// <param name="defaultValue">默认值。待转换对象无效时使用</param>
|
||||
/// <returns></returns>
|
||||
public static uint ToUInt32(this object value, uint defaultValue = default)
|
||||
{
|
||||
if (value is uint num) return num;
|
||||
if (value == null || value == DBNull.Value) return defaultValue;
|
||||
|
||||
if (uint.TryParse(value.ToString(), out var result))
|
||||
return result;
|
||||
else
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/// <summary>转为UInt64整数,转换失败时返回默认值。</summary>
|
||||
/// <remarks></remarks>
|
||||
/// <param name="value">待转换对象</param>
|
||||
/// <param name="defaultValue">默认值。待转换对象无效时使用</param>
|
||||
/// <returns></returns>
|
||||
public static ulong ToUInt64(this object value, ulong defaultValue = default)
|
||||
{
|
||||
if (value is ulong num) return num;
|
||||
if (value == null || value == DBNull.Value) return defaultValue;
|
||||
|
||||
if (ulong.TryParse(value.ToString(), out var result))
|
||||
return result;
|
||||
else
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 枚举拓展
|
||||
/// </summary>
|
||||
public static class EnumExtension
|
||||
{
|
||||
// 枚举显示字典缓存
|
||||
private static readonly ConcurrentDictionary<Type, Dictionary<int, string>> EnumDisplayValueDict = new();
|
||||
|
||||
// 枚举值字典缓存
|
||||
private static readonly ConcurrentDictionary<Type, Dictionary<int, string>> EnumNameValueDict = new();
|
||||
|
||||
// 枚举类型缓存
|
||||
private static ConcurrentDictionary<string, Type> _enumTypeDict;
|
||||
|
||||
/// <summary>
|
||||
/// 获取枚举对象Key与名称的字典(缓存)
|
||||
/// </summary>
|
||||
/// <param name="enumType"></param>
|
||||
/// <returns></returns>
|
||||
public static Dictionary<int, string> GetEnumDictionary(this Type enumType)
|
||||
{
|
||||
if (!enumType.IsEnum)
|
||||
throw new ArgumentException("Type '" + enumType.Name + "' is not an enum.");
|
||||
|
||||
// 查询缓存
|
||||
var enumDic = EnumNameValueDict.TryGetValue(enumType, out var value) ? value : new Dictionary<int, string>();
|
||||
if (enumDic.Count != 0)
|
||||
return enumDic;
|
||||
// 取枚举类型的Key/Value字典集合
|
||||
enumDic = GetEnumDictionaryItems(enumType);
|
||||
|
||||
// 缓存
|
||||
EnumNameValueDict[enumType] = enumDic;
|
||||
|
||||
return enumDic;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取枚举对象Key与名称的字典
|
||||
/// </summary>
|
||||
/// <param name="enumType"></param>
|
||||
/// <returns></returns>
|
||||
private static Dictionary<int, string> GetEnumDictionaryItems(this Type enumType)
|
||||
{
|
||||
// 获取类型的字段,初始化一个有限长度的字典
|
||||
var enumFields = enumType.GetFields(BindingFlags.Public | BindingFlags.Static);
|
||||
Dictionary<int, string> enumDic = new(enumFields.Length);
|
||||
|
||||
// 遍历字段数组获取key和name
|
||||
foreach (var enumField in enumFields)
|
||||
{
|
||||
var intValue = (int)enumField.GetValue(enumType)!;
|
||||
enumDic[intValue] = enumField.Name;
|
||||
}
|
||||
|
||||
return enumDic;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取枚举类型key与描述的字典(缓存)
|
||||
/// </summary>
|
||||
/// <param name="enumType"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public static Dictionary<int, string> GetEnumDescDictionary(this Type enumType)
|
||||
{
|
||||
if (!enumType.IsEnum)
|
||||
throw new ArgumentException("Type '" + enumType.Name + "' is not an enum.");
|
||||
|
||||
// 查询缓存
|
||||
var enumDic = EnumDisplayValueDict.TryGetValue(enumType, out var value)
|
||||
? value
|
||||
: new Dictionary<int, string>();
|
||||
if (enumDic.Count != 0)
|
||||
return enumDic;
|
||||
// 取枚举类型的Key/Value字典集合
|
||||
enumDic = GetEnumDescDictionaryItems(enumType);
|
||||
|
||||
// 缓存
|
||||
EnumDisplayValueDict[enumType] = enumDic;
|
||||
|
||||
return enumDic;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取枚举类型key与描述的字典(没有描述则获取name)
|
||||
/// </summary>
|
||||
/// <param name="enumType"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
private static Dictionary<int, string> GetEnumDescDictionaryItems(this Type enumType)
|
||||
{
|
||||
// 获取类型的字段,初始化一个有限长度的字典
|
||||
var enumFields = enumType.GetFields(BindingFlags.Public | BindingFlags.Static);
|
||||
Dictionary<int, string> enumDic = new(enumFields.Length);
|
||||
|
||||
// 遍历字段数组获取key和name
|
||||
foreach (var enumField in enumFields)
|
||||
{
|
||||
var intValue = (int)enumField.GetValue(enumType)!;
|
||||
var desc = enumField.GetDescriptionValue<DescriptionAttribute>();
|
||||
enumDic[intValue] = desc != null && !string.IsNullOrEmpty(desc.Description) ? desc.Description : enumField.Name;
|
||||
}
|
||||
|
||||
return enumDic;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从程序集中查找指定枚举类型
|
||||
/// </summary>
|
||||
/// <param name="assembly"></param>
|
||||
/// <param name="typeName"></param>
|
||||
/// <returns></returns>
|
||||
public static Type TryToGetEnumType(Assembly assembly, string typeName)
|
||||
{
|
||||
// 枚举缓存为空则重新加载枚举类型字典
|
||||
_enumTypeDict ??= LoadEnumTypeDict(assembly);
|
||||
|
||||
// 按名称查找
|
||||
return _enumTypeDict.TryGetValue(typeName, out var value) ? value : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从程序集中加载所有枚举类型
|
||||
/// </summary>
|
||||
/// <param name="assembly"></param>
|
||||
/// <returns></returns>
|
||||
private static ConcurrentDictionary<string, Type> LoadEnumTypeDict(Assembly assembly)
|
||||
{
|
||||
// 取程序集中所有类型
|
||||
var typeArray = assembly.GetTypes();
|
||||
|
||||
// 过滤非枚举类型,转成字典格式并返回
|
||||
var dict = typeArray.Where(o => o.IsEnum).ToDictionary(o => o.Name, o => o);
|
||||
ConcurrentDictionary<string, Type> enumTypeDict = new(dict);
|
||||
return enumTypeDict;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取枚举的Description
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetEnumDescription(this Enum value)
|
||||
{
|
||||
return value.GetType().GetField(value.ToString())?.GetCustomAttribute<DescriptionAttribute>()?.Description;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取枚举的Description
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetEnumDescription(this object value)
|
||||
{
|
||||
return value.GetType().GetField(value.ToString()!)?.GetCustomAttribute<DescriptionAttribute>()?.Description;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取枚举的Theme
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetTheme(this object value)
|
||||
{
|
||||
return value.GetType().GetField(value.ToString()!)?.GetCustomAttribute<ThemeAttribute>()?.Theme;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将枚举转成枚举信息集合
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static List<EnumEntity> EnumToList(this Type type)
|
||||
{
|
||||
if (!type.IsEnum)
|
||||
throw new ArgumentException("Type '" + type.Name + "' is not an enum.");
|
||||
var arr = Enum.GetNames(type);
|
||||
return arr.Select(sl =>
|
||||
{
|
||||
var item = Enum.Parse(type, sl);
|
||||
return new EnumEntity
|
||||
{
|
||||
Name = item.ToString(),
|
||||
Describe = item.GetEnumDescription() ?? item.ToString(),
|
||||
Theme = item.GetTheme() ?? string.Empty,
|
||||
Value = item.GetHashCode()
|
||||
};
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 枚举ToList
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static List<T> EnumToList<T>(this Type type)
|
||||
{
|
||||
if (!type.IsEnum)
|
||||
throw new ArgumentException("Type '" + type.Name + "' is not an enum.");
|
||||
var arr = Enum.GetNames(type);
|
||||
return arr.Select(name => (T)Enum.Parse(type, name)).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 枚举实体
|
||||
/// </summary>
|
||||
public class EnumEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// 枚举的描述
|
||||
/// </summary>
|
||||
public string Describe { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 枚举的样式
|
||||
/// </summary>
|
||||
public string Theme { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 枚举名称
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 枚举对象的值
|
||||
/// </summary>
|
||||
public int Value { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 数据集合拓展类
|
||||
/// </summary>
|
||||
public static class EnumerableExtension
|
||||
{
|
||||
private static readonly ConcurrentDictionary<string, PropertyInfo> PropertyCache = new();
|
||||
|
||||
/// <summary>
|
||||
/// 查询有父子关系的数据集
|
||||
/// </summary>
|
||||
/// <param name="list">数据集</param>
|
||||
/// <param name="idExpression">主键ID字段</param>
|
||||
/// <param name="parentIdExpression">父级字段</param>
|
||||
/// <param name="topParentIdValue">顶级节点父级字段值</param>
|
||||
/// <param name="isContainOneself">是否包含顶级节点本身</param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<T> ToChildList<T, P>(this IEnumerable<T> list,
|
||||
Expression<Func<T, P>> idExpression,
|
||||
Expression<Func<T, P>> parentIdExpression,
|
||||
object topParentIdValue,
|
||||
bool isContainOneself = true)
|
||||
{
|
||||
if (list == null || !list.Any()) return Enumerable.Empty<T>();
|
||||
|
||||
var propId = GetPropertyInfo(idExpression);
|
||||
var propParentId = GetPropertyInfo(parentIdExpression);
|
||||
|
||||
// 查找所有顶级节点
|
||||
var topNodes = list.Where(item => Equals(propId.GetValue(item), topParentIdValue)).ToList();
|
||||
|
||||
return TraverseHierarchy(list, propId, propParentId, topNodes, isContainOneself);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询有父子关系的数据集
|
||||
/// </summary>
|
||||
/// <param name="list">数据集</param>
|
||||
/// <param name="idExpression">主键ID字段</param>
|
||||
/// <param name="parentIdExpression">父级字段</param>
|
||||
/// <param name="topLevelPredicate">顶级节点的选择条件</param>
|
||||
/// <param name="isContainOneself">是否包含顶级节点本身</param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<T> ToChildList<T, P>(this IEnumerable<T> list,
|
||||
Expression<Func<T, P>> idExpression,
|
||||
Expression<Func<T, P>> parentIdExpression,
|
||||
Expression<Func<T, bool>> topLevelPredicate,
|
||||
bool isContainOneself = true)
|
||||
{
|
||||
if (list == null || !list.Any()) return Enumerable.Empty<T>();
|
||||
|
||||
// 获取顶级节点
|
||||
var topNodes = list.Where(topLevelPredicate.Compile()).ToList();
|
||||
|
||||
if (!topNodes.Any()) return Enumerable.Empty<T>();
|
||||
|
||||
var idPropertyInfo = GetPropertyInfo(idExpression);
|
||||
var parentPropertyInfo = GetPropertyInfo(parentIdExpression);
|
||||
|
||||
return TraverseHierarchy(list, idPropertyInfo, parentPropertyInfo, topNodes, isContainOneself);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 辅助方法,从表达式中提取属性信息并使用临时缓存
|
||||
/// </summary>
|
||||
private static PropertyInfo GetPropertyInfo<T, P>(Expression<Func<T, P>> expression)
|
||||
{
|
||||
// 使用 ConcurrentDictionary 确保线程安全
|
||||
return PropertyCache.GetOrAdd(typeof(T).FullName + "." + ((MemberExpression)expression.Body).Member.Name, k =>
|
||||
{
|
||||
if (expression.Body is UnaryExpression { Operand: MemberExpression member }) return (PropertyInfo)member.Member;
|
||||
if (expression.Body is MemberExpression memberExpression) return (PropertyInfo)memberExpression.Member;
|
||||
throw Oops.Oh("表达式必须是一个属性访问: " + expression);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用队列遍历层级结构
|
||||
/// </summary>
|
||||
private static IEnumerable<T> TraverseHierarchy<T>(IEnumerable<T> list,
|
||||
PropertyInfo idPropertyInfo,
|
||||
PropertyInfo parentPropertyInfo,
|
||||
List<T> topNodes,
|
||||
bool isContainOneself)
|
||||
{
|
||||
var queue = new Queue<T>(topNodes);
|
||||
var result = new HashSet<T>(topNodes);
|
||||
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
var currentNode = queue.Dequeue();
|
||||
var children = list.Where(item => Equals(parentPropertyInfo.GetValue(item), idPropertyInfo.GetValue(currentNode))).ToList();
|
||||
children.Where(child => result.Add(child)).ForEach(child => queue.Enqueue(child));
|
||||
}
|
||||
if (isContainOneself) return result;
|
||||
|
||||
// 如果不需要包含顶级节点本身,则移除它们
|
||||
topNodes.ForEach(e => result.Remove(e));
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
public static class HttpContextExtension
|
||||
{
|
||||
public static async Task<AuthenticationScheme[]> GetExternalProvidersAsync(this HttpContext context)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
|
||||
var schemes = context.RequestServices.GetRequiredService<IAuthenticationSchemeProvider>();
|
||||
|
||||
return (from scheme in await schemes.GetAllSchemesAsync()
|
||||
where !string.IsNullOrEmpty(scheme.DisplayName)
|
||||
select scheme).ToArray();
|
||||
}
|
||||
|
||||
public static async Task<bool> IsProviderSupportedAsync(this HttpContext context, string provider)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
|
||||
return (from scheme in await context.GetExternalProvidersAsync()
|
||||
where string.Equals(scheme.Name, provider, StringComparison.OrdinalIgnoreCase)
|
||||
select scheme).Any();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取设备信息
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetClientDeviceInfo(this HttpContext context)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
|
||||
return CommonUtil.GetClientDeviceInfo(context.Request.Headers.UserAgent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取浏览器信息
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetClientBrowser(this HttpContext context)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
|
||||
string userAgent = context.Request.Headers.UserAgent;
|
||||
try
|
||||
{
|
||||
if (userAgent != null)
|
||||
{
|
||||
var client = Parser.GetDefault().Parse(userAgent);
|
||||
if (client.Device.IsSpider)
|
||||
return "爬虫";
|
||||
return $"{client.UA.Family} {client.UA.Major}.{client.UA.Minor} / {client.Device.Family}";
|
||||
}
|
||||
}
|
||||
catch
|
||||
{ }
|
||||
return "未知";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取操作系统信息
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetClientOs(this HttpContext context)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
|
||||
string userAgent = context.Request.Headers.UserAgent;
|
||||
try
|
||||
{
|
||||
if (userAgent != null)
|
||||
{
|
||||
var client = Parser.GetDefault().Parse(userAgent);
|
||||
if (client.Device.IsSpider)
|
||||
return "爬虫";
|
||||
return $"{client.OS.Family} {client.OS.Major} {client.OS.Minor}";
|
||||
}
|
||||
}
|
||||
catch
|
||||
{ }
|
||||
return "未知";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
public static class ListExtensions
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,521 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 对象拓展
|
||||
/// </summary>
|
||||
[SuppressSniffer]
|
||||
public static partial class ObjectExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 类型属性列表映射表
|
||||
/// </summary>
|
||||
private static readonly ConcurrentDictionary<Type, PropertyInfo[]> PropertyCache = new();
|
||||
|
||||
/// <summary>
|
||||
/// 脱敏特性缓存映射表
|
||||
/// </summary>
|
||||
private static readonly ConcurrentDictionary<PropertyInfo, DataMaskAttribute> AttributeCache = new();
|
||||
|
||||
/// <summary>
|
||||
/// 判断类型是否实现某个泛型
|
||||
/// </summary>
|
||||
/// <param name="type">类型</param>
|
||||
/// <param name="generic">泛型类型</param>
|
||||
/// <returns>bool</returns>
|
||||
public static bool HasImplementedRawGeneric(this Type type, Type generic)
|
||||
{
|
||||
// 检查接口类型
|
||||
var isTheRawGenericType = type.GetInterfaces().Any(IsTheRawGenericType);
|
||||
if (isTheRawGenericType) return true;
|
||||
|
||||
// 检查类型
|
||||
while (type != null && type != typeof(object))
|
||||
{
|
||||
isTheRawGenericType = IsTheRawGenericType(type);
|
||||
if (isTheRawGenericType) return true;
|
||||
type = type.BaseType;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
// 判断逻辑
|
||||
bool IsTheRawGenericType(Type type) => generic == (type.IsGenericType ? type.GetGenericTypeDefinition() : type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将字典转化为QueryString格式
|
||||
/// </summary>
|
||||
/// <param name="dict"></param>
|
||||
/// <param name="urlEncode"></param>
|
||||
/// <returns></returns>
|
||||
public static string ToQueryString(this Dictionary<string, string> dict, bool urlEncode = true)
|
||||
{
|
||||
return string.Join("&", dict.Select(p => $"{(urlEncode ? p.Key?.UrlEncode() : "")}={(urlEncode ? p.Value?.UrlEncode() : "")}"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将字符串URL编码
|
||||
/// </summary>
|
||||
/// <param name="str"></param>
|
||||
/// <returns></returns>
|
||||
public static string UrlEncode(this string str)
|
||||
{
|
||||
return string.IsNullOrEmpty(str) ? "" : System.Uri.EscapeDataString(str);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对象序列化成Json字符串
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
public static string ToJson(this object obj)
|
||||
{
|
||||
var jsonSettings = SetNewtonsoftJsonSetting();
|
||||
return JSON.GetJsonSerializer().Serialize(obj, jsonSettings);
|
||||
}
|
||||
|
||||
private static JsonSerializerSettings SetNewtonsoftJsonSetting()
|
||||
{
|
||||
JsonSerializerSettings setting = new JsonSerializerSettings();
|
||||
setting.DateFormatHandling = DateFormatHandling.IsoDateFormat;
|
||||
setting.DateTimeZoneHandling = DateTimeZoneHandling.Local;
|
||||
setting.DateFormatString = "yyyy-MM-dd HH:mm:ss"; // 时间格式化
|
||||
setting.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; // 忽略循环引用
|
||||
//setting.ContractResolver = new HelErpContractResolver("StartTime", customName);
|
||||
return setting;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Json字符串反序列化成对象
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="json"></param>
|
||||
/// <returns></returns>
|
||||
public static T ToObject<T>(this string json)
|
||||
{
|
||||
return JSON.GetJsonSerializer().Deserialize<T>(json);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将object转换为long,若失败则返回0
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
public static long ParseToLong(this object obj)
|
||||
{
|
||||
try
|
||||
{
|
||||
return long.Parse(obj.ToString());
|
||||
}
|
||||
catch
|
||||
{
|
||||
return 0L;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将object转换为long,若失败则返回指定值
|
||||
/// </summary>
|
||||
/// <param name="str"></param>
|
||||
/// <param name="defaultValue"></param>
|
||||
/// <returns></returns>
|
||||
public static long ParseToLong(this string str, long defaultValue)
|
||||
{
|
||||
try
|
||||
{
|
||||
return long.Parse(str);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将object转换为double,若失败则返回0
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
public static double ParseToDouble(this object obj)
|
||||
{
|
||||
try
|
||||
{
|
||||
return double.Parse(obj.ToString());
|
||||
}
|
||||
catch
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将object转换为double,若失败则返回指定值
|
||||
/// </summary>
|
||||
/// <param name="str"></param>
|
||||
/// <param name="defaultValue"></param>
|
||||
/// <returns></returns>
|
||||
public static double ParseToDouble(this object str, double defaultValue)
|
||||
{
|
||||
try
|
||||
{
|
||||
return double.Parse(str.ToString());
|
||||
}
|
||||
catch
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将string转换为DateTime,若失败则返回日期最小值
|
||||
/// </summary>
|
||||
/// <param name="str"></param>
|
||||
/// <returns></returns>
|
||||
public static DateTime ParseToDateTime(this string str)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(str))
|
||||
{
|
||||
return DateTime.MinValue;
|
||||
}
|
||||
if (str.Contains('-') || str.Contains('/'))
|
||||
{
|
||||
return DateTime.Parse(str);
|
||||
}
|
||||
else
|
||||
{
|
||||
int length = str.Length;
|
||||
switch (length)
|
||||
{
|
||||
case 4:
|
||||
return DateTime.ParseExact(str, "yyyy", System.Globalization.CultureInfo.CurrentCulture);
|
||||
|
||||
case 6:
|
||||
return DateTime.ParseExact(str, "yyyyMM", System.Globalization.CultureInfo.CurrentCulture);
|
||||
|
||||
case 8:
|
||||
return DateTime.ParseExact(str, "yyyyMMdd", System.Globalization.CultureInfo.CurrentCulture);
|
||||
|
||||
case 10:
|
||||
return DateTime.ParseExact(str, "yyyyMMddHH", System.Globalization.CultureInfo.CurrentCulture);
|
||||
|
||||
case 12:
|
||||
return DateTime.ParseExact(str, "yyyyMMddHHmm", System.Globalization.CultureInfo.CurrentCulture);
|
||||
|
||||
case 14:
|
||||
return DateTime.ParseExact(str, "yyyyMMddHHmmss", System.Globalization.CultureInfo.CurrentCulture);
|
||||
|
||||
default:
|
||||
return DateTime.ParseExact(str, "yyyyMMddHHmmss", System.Globalization.CultureInfo.CurrentCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return DateTime.MinValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将string转换为DateTime,若失败则返回默认值
|
||||
/// </summary>
|
||||
/// <param name="str"></param>
|
||||
/// <param name="defaultValue"></param>
|
||||
/// <returns></returns>
|
||||
public static DateTime ParseToDateTime(this string str, DateTime? defaultValue)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(str))
|
||||
{
|
||||
return defaultValue.GetValueOrDefault();
|
||||
}
|
||||
if (str.Contains('-') || str.Contains('/'))
|
||||
{
|
||||
return DateTime.Parse(str);
|
||||
}
|
||||
else
|
||||
{
|
||||
int length = str.Length;
|
||||
switch (length)
|
||||
{
|
||||
case 4:
|
||||
return DateTime.ParseExact(str, "yyyy", System.Globalization.CultureInfo.CurrentCulture);
|
||||
|
||||
case 6:
|
||||
return DateTime.ParseExact(str, "yyyyMM", System.Globalization.CultureInfo.CurrentCulture);
|
||||
|
||||
case 8:
|
||||
return DateTime.ParseExact(str, "yyyyMMdd", System.Globalization.CultureInfo.CurrentCulture);
|
||||
|
||||
case 10:
|
||||
return DateTime.ParseExact(str, "yyyyMMddHH", System.Globalization.CultureInfo.CurrentCulture);
|
||||
|
||||
case 12:
|
||||
return DateTime.ParseExact(str, "yyyyMMddHHmm", System.Globalization.CultureInfo.CurrentCulture);
|
||||
|
||||
case 14:
|
||||
return DateTime.ParseExact(str, "yyyyMMddHHmmss", System.Globalization.CultureInfo.CurrentCulture);
|
||||
|
||||
default:
|
||||
return DateTime.ParseExact(str, "yyyyMMddHHmmss", System.Globalization.CultureInfo.CurrentCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return defaultValue.GetValueOrDefault();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 string 时间日期格式转换成字符串 如 {yyyy} => 2024
|
||||
/// </summary>
|
||||
/// <param name="str"></param>
|
||||
/// <returns></returns>
|
||||
public static string ParseToDateTimeForRep(this string str)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(str))
|
||||
str = $"{DateTime.Now.Year}/{DateTime.Now.Month}/{DateTime.Now.Day}";
|
||||
|
||||
var date = DateTime.Now;
|
||||
var reg = new Regex(@"(\{.+?})");
|
||||
var match = reg.Matches(str);
|
||||
match.ToList().ForEach(u =>
|
||||
{
|
||||
var temp = date.ToString(u.ToString().Substring(1, u.Length - 2));
|
||||
str = str.Replace(u.ToString(), temp);
|
||||
});
|
||||
return str;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否有值
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsNullOrEmpty(this object obj)
|
||||
{
|
||||
return obj == null || string.IsNullOrEmpty(obj.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 字符串掩码
|
||||
/// </summary>
|
||||
/// <param name="str">字符串</param>
|
||||
/// <param name="mask">掩码符</param>
|
||||
/// <returns></returns>
|
||||
public static string Mask(this string str, char mask = '*')
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(str?.Trim()))
|
||||
return str;
|
||||
|
||||
str = str.Trim();
|
||||
var masks = mask.ToString().PadLeft(4, mask);
|
||||
return str.Length switch
|
||||
{
|
||||
>= 11 => Regex.Replace(str, "(.{3}).*(.{4})", $"$1{masks}$2"),
|
||||
10 => Regex.Replace(str, "(.{3}).*(.{3})", $"$1{masks}$2"),
|
||||
9 => Regex.Replace(str, "(.{2}).*(.{3})", $"$1{masks}$2"),
|
||||
8 => Regex.Replace(str, "(.{2}).*(.{2})", $"$1{masks}$2"),
|
||||
7 => Regex.Replace(str, "(.{1}).*(.{2})", $"$1{masks}$2"),
|
||||
6 => Regex.Replace(str, "(.{1}).*(.{1})", $"$1{masks}$2"),
|
||||
_ => Regex.Replace(str, "(.{1}).*", $"$1{masks}")
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 身份证号掩码
|
||||
/// </summary>
|
||||
/// <param name="idCard">身份证号</param>
|
||||
/// <param name="mask">掩码符</param>
|
||||
/// <returns></returns>
|
||||
public static string MaskIdCard(this string idCard, char mask = '*')
|
||||
{
|
||||
if (!idCard.TryValidate(ValidationTypes.IDCard).IsValid) return idCard;
|
||||
|
||||
var masks = mask.ToString().PadLeft(8, mask);
|
||||
return Regex.Replace(idCard, @"^(.{6})(.*)(.{4})$", $"$1{masks}$3");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 邮箱掩码
|
||||
/// </summary>
|
||||
/// <param name="email">邮箱</param>
|
||||
/// <param name="mask">掩码符</param>
|
||||
/// <returns></returns>
|
||||
public static string MaskEmail(this string email, char mask = '*')
|
||||
{
|
||||
if (!email.TryValidate(ValidationTypes.EmailAddress).IsValid) return email;
|
||||
|
||||
var pos = email.IndexOf("@");
|
||||
return Mask(email[..pos], mask) + email[pos..];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将字符串转为值类型,若没有得到或者错误返回为空
|
||||
/// </summary>
|
||||
/// <typeparam name="T">指定值类型</typeparam>
|
||||
/// <param name="str">传入字符串</param>
|
||||
/// <returns>可空值</returns>
|
||||
public static T? ParseTo<T>(this string str) where T : struct
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(str))
|
||||
{
|
||||
MethodInfo method = typeof(T).GetMethod("Parse", new Type[] { typeof(string) });
|
||||
if (method != null)
|
||||
{
|
||||
T result = (T)method.Invoke(null, new string[] { str });
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将字符串转为值类型,若没有得到或者错误返回为空
|
||||
/// </summary>
|
||||
/// <param name="str">传入字符串</param>
|
||||
/// <param name="type">目标类型</param>
|
||||
/// <returns>可空值</returns>
|
||||
public static object ParseTo(this string str, Type type)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (type.Name == "String")
|
||||
return str;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(str))
|
||||
{
|
||||
var _type = type;
|
||||
if (type.Name.StartsWith("Nullable"))
|
||||
_type = type.GetGenericArguments()[0];
|
||||
|
||||
MethodInfo method = _type.GetMethod("Parse", new Type[] { typeof(string) });
|
||||
if (method != null)
|
||||
return method.Invoke(null, new string[] { str });
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将一个对象属性值赋给另一个指定对象属性, 只复制相同属性的
|
||||
/// </summary>
|
||||
/// <param name="src">原数据对象</param>
|
||||
/// <param name="target">目标数据对象</param>
|
||||
/// <param name="changeProperties">属性集,键为原属性,值为目标属性</param>
|
||||
/// <param name="unChangeProperties">属性集,目标不修改的属性</param>
|
||||
public static void CopyTo(object src, object target, Dictionary<string, string> changeProperties = null, string[] unChangeProperties = null)
|
||||
{
|
||||
if (src == null || target == null)
|
||||
throw new ArgumentException("src == null || target == null ");
|
||||
|
||||
var SourceType = src.GetType();
|
||||
var TargetType = target.GetType();
|
||||
|
||||
if (changeProperties == null || changeProperties.Count == 0)
|
||||
{
|
||||
var fields = TargetType.GetProperties();
|
||||
changeProperties = fields.Select(m => m.Name).ToDictionary(m => m);
|
||||
}
|
||||
|
||||
if (unChangeProperties == null || unChangeProperties.Length == 0)
|
||||
{
|
||||
foreach (var item in changeProperties)
|
||||
{
|
||||
var srcProperty = SourceType.GetProperty(item.Key);
|
||||
if (srcProperty != null)
|
||||
{
|
||||
var sourceVal = srcProperty.GetValue(src, null);
|
||||
|
||||
var tarProperty = TargetType.GetProperty(item.Value);
|
||||
tarProperty?.SetValue(target, sourceVal, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var item in changeProperties)
|
||||
{
|
||||
if (!unChangeProperties.Any(m => m == item.Value))
|
||||
{
|
||||
var srcProperty = SourceType.GetProperty(item.Key);
|
||||
if (srcProperty != null)
|
||||
{
|
||||
var sourceVal = srcProperty.GetValue(src, null);
|
||||
|
||||
var tarProperty = TargetType.GetProperty(item.Value);
|
||||
tarProperty?.SetValue(target, sourceVal, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 深复制
|
||||
/// </summary>
|
||||
/// <typeparam name="T">深复制源对象</typeparam>
|
||||
/// <param name="obj">对象</param>
|
||||
/// <returns></returns>
|
||||
public static T DeepCopy<T>(this T obj)
|
||||
{
|
||||
var jsonSettings = SetNewtonsoftJsonSetting();
|
||||
var json = JSON.Serialize(obj, jsonSettings);
|
||||
return JSON.Deserialize<T>(json);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对带有<see cref="DataMaskAttribute"/>特性字段进行脱敏处理
|
||||
/// </summary>
|
||||
public static T MaskSensitiveData<T>(this T obj) where T : class
|
||||
{
|
||||
if (obj == null) return null;
|
||||
|
||||
var type = typeof(T);
|
||||
|
||||
// 获取或缓存属性集合
|
||||
var properties = PropertyCache.GetOrAdd(type, t =>
|
||||
t.GetProperties(BindingFlags.Public | BindingFlags.Instance)
|
||||
.Where(p => p.PropertyType == typeof(string) && p.GetCustomAttribute<DataMaskAttribute>() != null)
|
||||
.ToArray());
|
||||
|
||||
// 并行处理可写属性
|
||||
Parallel.ForEach(properties, prop =>
|
||||
{
|
||||
if (!prop.CanWrite) return;
|
||||
|
||||
// 获取或缓存特性
|
||||
var maskAttr = AttributeCache.GetOrAdd(prop, p => p.GetCustomAttribute<DataMaskAttribute>());
|
||||
|
||||
if (maskAttr == null) return;
|
||||
|
||||
// 处理非空字符串
|
||||
if (prop.GetValue(obj) is string { Length: > 0 } value)
|
||||
{
|
||||
prop.SetValue(obj, maskAttr.Mask(value));
|
||||
}
|
||||
});
|
||||
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,454 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
using MapsterMapper;
|
||||
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
public static class RepositoryExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 实体假删除 _rep.FakeDelete(entity)
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="repository"></param>
|
||||
/// <param name="entity"></param>
|
||||
/// <returns></returns>
|
||||
public static int FakeDelete<T>(this ISugarRepository repository, T entity) where T : EntityBaseDel, new()
|
||||
{
|
||||
return repository.Context.FakeDelete(entity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 实体假删除 db.FakeDelete(entity)
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="db"></param>
|
||||
/// <param name="entity"></param>
|
||||
/// <returns></returns>
|
||||
public static int FakeDelete<T>(this ISqlSugarClient db, T entity) where T : EntityBaseDel, new()
|
||||
{
|
||||
return db.Updateable(entity).AS().ReSetValue(x => { x.IsDelete = true; })
|
||||
.IgnoreColumns(ignoreAllNullColumns: true)
|
||||
.EnableDiffLogEvent() // 记录差异日志
|
||||
.UpdateColumns(x => new { x.IsDelete, x.DeleteTime, x.UpdateTime, x.UpdateUserId }) // 允许更新的字段-AOP拦截自动设置UpdateTime、UpdateUserId
|
||||
.ExecuteCommand();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 实体集合批量假删除 _rep.FakeDelete(entity)
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="repository"></param>
|
||||
/// <param name="entity"></param>
|
||||
/// <returns></returns>
|
||||
public static int FakeDelete<T>(this ISugarRepository repository, List<T> entity) where T : EntityBaseDel, new()
|
||||
{
|
||||
return repository.Context.FakeDelete(entity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 实体集合批量假删除 db.FakeDelete(entity)
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="db"></param>
|
||||
/// <param name="entity"></param>
|
||||
/// <returns></returns>
|
||||
public static int FakeDelete<T>(this ISqlSugarClient db, List<T> entity) where T : EntityBaseDel, new()
|
||||
{
|
||||
return db.Updateable(entity).AS().ReSetValue(x => { x.IsDelete = true; })
|
||||
.IgnoreColumns(ignoreAllNullColumns: true)
|
||||
.EnableDiffLogEvent() // 记录差异日志
|
||||
.UpdateColumns(x => new { x.IsDelete, x.DeleteTime, x.UpdateTime, x.UpdateUserId }) // 允许更新的字段-AOP拦截自动设置UpdateTime、UpdateUserId
|
||||
.ExecuteCommand();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 实体假删除异步 _rep.FakeDeleteAsync(entity)
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="repository"></param>
|
||||
/// <param name="entity"></param>
|
||||
/// <returns></returns>
|
||||
public static Task<int> FakeDeleteAsync<T>(this ISugarRepository repository, T entity) where T : EntityBaseDel, new()
|
||||
{
|
||||
return repository.Context.FakeDeleteAsync(entity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 实体假删除 db.FakeDelete(entity)
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="db"></param>
|
||||
/// <param name="entity"></param>
|
||||
/// <returns></returns>
|
||||
public static Task<int> FakeDeleteAsync<T>(this ISqlSugarClient db, T entity) where T : EntityBaseDel, new()
|
||||
{
|
||||
return db.Updateable(entity).AS().ReSetValue(x => { x.IsDelete = true; })
|
||||
.IgnoreColumns(ignoreAllNullColumns: true)
|
||||
.EnableDiffLogEvent() // 记录差异日志
|
||||
.UpdateColumns(x => new { x.IsDelete, x.DeleteTime, x.UpdateTime, x.UpdateUserId }) // 允许更新的字段-AOP拦截自动设置UpdateTime、UpdateUserId
|
||||
.ExecuteCommandAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 实体集合批量假删除异步 _rep.FakeDeleteAsync(entity)
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="repository"></param>
|
||||
/// <param name="entity"></param>
|
||||
/// <returns></returns>
|
||||
public static Task<int> FakeDeleteAsync<T>(this ISugarRepository repository, List<T> entity) where T : EntityBaseDel, new()
|
||||
{
|
||||
return repository.Context.FakeDeleteAsync(entity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 实体集合批量假删除 db.FakeDelete(entity)
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="db"></param>
|
||||
/// <param name="entity"></param>
|
||||
/// <returns></returns>
|
||||
public static Task<int> FakeDeleteAsync<T>(this ISqlSugarClient db, List<T> entity) where T : EntityBaseDel, new()
|
||||
{
|
||||
return db.Updateable(entity).AS().ReSetValue(x => { x.IsDelete = true; })
|
||||
.IgnoreColumns(ignoreAllNullColumns: true)
|
||||
.EnableDiffLogEvent() // 记录差异日志
|
||||
.UpdateColumns(x => new { x.IsDelete, x.DeleteTime, x.UpdateTime, x.UpdateUserId }) // 允许更新的字段-AOP拦截自动设置UpdateTime、UpdateUserId
|
||||
.ExecuteCommandAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 排序方式(默认降序)
|
||||
/// </summary>
|
||||
/// <param name="queryable"></param>
|
||||
/// <param name="pageInput"> </param>
|
||||
/// <param name="prefix"> </param>
|
||||
/// <param name="defaultSortField"> 默认排序字段 </param>
|
||||
/// <param name="descSort"> 是否降序 </param>
|
||||
/// <returns> </returns>
|
||||
public static ISugarQueryable<T> OrderBuilder<T>(this ISugarQueryable<T> queryable, BasePageInput pageInput, string prefix = "", string defaultSortField = "Id", bool descSort = true)
|
||||
{
|
||||
var iSqlBuilder = InstanceFactory.GetSqlBuilderWithContext(queryable.Context);
|
||||
|
||||
// 约定默认每张表都有Id排序
|
||||
var orderStr = string.IsNullOrWhiteSpace(defaultSortField) ? "" : $"{prefix}{iSqlBuilder.GetTranslationColumnName(defaultSortField)}" + (descSort ? " Desc" : " Asc");
|
||||
|
||||
TypeAdapterConfig typeAdapterConfig = new();
|
||||
typeAdapterConfig.ForType<T, BasePageInput>().IgnoreNullValues(true);
|
||||
Mapper mapper = new(typeAdapterConfig); // 务必将mapper设为单实例
|
||||
var nowPagerInput = mapper.Map<BasePageInput>(pageInput);
|
||||
// 排序是否可用-排序字段为非空才启用排序,排序顺序默认为倒序
|
||||
if (!string.IsNullOrEmpty(nowPagerInput.Field))
|
||||
{
|
||||
nowPagerInput.Field = Regex.Replace(nowPagerInput.Field, @"[\s;()\-'@=/%]", ""); //过滤掉一些关键字符防止构造特殊SQL语句注入
|
||||
var orderByDbName = queryable.Context.EntityMaintenance.GetDbColumnName<T>(nowPagerInput.Field);//防止注入,类中只要不存在属性名就会报错
|
||||
orderStr = $"{prefix}{iSqlBuilder.GetTranslationColumnName(orderByDbName)} {(string.IsNullOrEmpty(nowPagerInput.Order) || nowPagerInput.Order.Equals(nowPagerInput.DescStr, StringComparison.OrdinalIgnoreCase) ? "Desc" : "Asc")}";
|
||||
}
|
||||
return queryable.OrderByIF(!string.IsNullOrWhiteSpace(orderStr), orderStr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新实体并记录差异日志 _rep.UpdateWithDiffLog(entity)
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="repository"></param>
|
||||
/// <param name="entity"></param>
|
||||
/// <param name="ignoreAllNullColumns"></param>
|
||||
/// <returns></returns>
|
||||
public static int UpdateWithDiffLog<T>(this ISugarRepository repository, T entity, bool ignoreAllNullColumns = true) where T : EntityBase, new()
|
||||
{
|
||||
return repository.Context.UpdateWithDiffLog(entity, ignoreAllNullColumns);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新实体并记录差异日志 _rep.UpdateWithDiffLog(entity)
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="db"></param>
|
||||
/// <param name="entity"></param>
|
||||
/// <param name="ignoreAllNullColumns"></param>
|
||||
/// <returns></returns>
|
||||
public static int UpdateWithDiffLog<T>(this ISqlSugarClient db, T entity, bool ignoreAllNullColumns = true) where T : EntityBase, new()
|
||||
{
|
||||
return db.Updateable(entity).AS()
|
||||
.IgnoreColumns(ignoreAllNullColumns: ignoreAllNullColumns)
|
||||
.EnableDiffLogEvent()
|
||||
.ExecuteCommand();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新实体并记录差异日志 _rep.UpdateWithDiffLogAsync(entity)
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="repository"></param>
|
||||
/// <param name="entity"></param>
|
||||
/// <param name="ignoreAllNullColumns"></param>
|
||||
/// <returns></returns>
|
||||
public static Task<int> UpdateWithDiffLogAsync<T>(this ISugarRepository repository, T entity, bool ignoreAllNullColumns = true) where T : EntityBase, new()
|
||||
{
|
||||
return repository.Context.UpdateWithDiffLogAsync(entity, ignoreAllNullColumns);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新实体并记录差异日志 _rep.UpdateWithDiffLogAsync(entity)
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="db"></param>
|
||||
/// <param name="entity"></param>
|
||||
/// <param name="ignoreAllNullColumns"></param>
|
||||
/// <returns></returns>
|
||||
public static Task<int> UpdateWithDiffLogAsync<T>(this ISqlSugarClient db, T entity, bool ignoreAllNullColumns = true) where T : EntityBase, new()
|
||||
{
|
||||
return db.Updateable(entity)
|
||||
.IgnoreColumns(ignoreAllNullColumns: ignoreAllNullColumns)
|
||||
.EnableDiffLogEvent()
|
||||
.ExecuteCommandAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 新增实体并记录差异日志 _rep.InsertWithDiffLog(entity)
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="repository"></param>
|
||||
/// <param name="entity"></param>
|
||||
/// <returns></returns>
|
||||
public static int InsertWithDiffLog<T>(this ISugarRepository repository, T entity) where T : EntityBase, new()
|
||||
{
|
||||
return repository.Context.InsertWithDiffLog(entity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 新增实体并记录差异日志 _rep.InsertWithDiffLog(entity)
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="db"></param>
|
||||
/// <param name="entity"></param>
|
||||
/// <returns></returns>
|
||||
public static int InsertWithDiffLog<T>(this ISqlSugarClient db, T entity) where T : EntityBase, new()
|
||||
{
|
||||
return db.Insertable(entity).AS().EnableDiffLogEvent().ExecuteCommand();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 新增实体并记录差异日志 _rep.InsertWithDiffLogAsync(entity)
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="repository"></param>
|
||||
/// <param name="entity"></param>
|
||||
/// <returns></returns>
|
||||
public static Task<int> InsertWithDiffLogAsync<T>(this ISugarRepository repository, T entity) where T : EntityBase, new()
|
||||
{
|
||||
return repository.Context.InsertWithDiffLogAsync(entity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 新增实体并记录差异日志 _rep.InsertWithDiffLog(entity)
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="db"></param>
|
||||
/// <param name="entity"></param>
|
||||
/// <returns></returns>
|
||||
public static Task<int> InsertWithDiffLogAsync<T>(this ISqlSugarClient db, T entity) where T : EntityBase, new()
|
||||
{
|
||||
return db.Insertable(entity).AS().EnableDiffLogEvent().ExecuteCommandAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 多库查询
|
||||
/// </summary>
|
||||
/// <param name="queryable"></param>
|
||||
/// <returns> </returns>
|
||||
public static ISugarQueryable<T> AS<T>(this ISugarQueryable<T> queryable)
|
||||
{
|
||||
var info = GetTableInfo<T>();
|
||||
return queryable.AS<T>($"{info.Item1}.{info.Item2}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 多库查询
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <typeparam name="T2"></typeparam>
|
||||
/// <param name="queryable"></param>
|
||||
/// <returns></returns>
|
||||
public static ISugarQueryable<T, T2> AS<T, T2>(this ISugarQueryable<T, T2> queryable)
|
||||
{
|
||||
var info = GetTableInfo<T2>();
|
||||
return queryable.AS<T2>($"{info.Item1}.{info.Item2}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 多库更新
|
||||
/// </summary>
|
||||
/// <param name="updateable"></param>
|
||||
/// <returns> </returns>
|
||||
public static IUpdateable<T> AS<T>(this IUpdateable<T> updateable) where T : EntityBase, new()
|
||||
{
|
||||
var info = GetTableInfo<T>();
|
||||
return updateable.AS($"{info.Item1}.{info.Item2}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 多库新增
|
||||
/// </summary>
|
||||
/// <param name="insertable"></param>
|
||||
/// <returns> </returns>
|
||||
public static IInsertable<T> AS<T>(this IInsertable<T> insertable) where T : EntityBase, new()
|
||||
{
|
||||
var info = GetTableInfo<T>();
|
||||
return insertable.AS($"{info.Item1}.{info.Item2}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 多库删除
|
||||
/// </summary>
|
||||
/// <param name="deleteable"></param>
|
||||
/// <returns> </returns>
|
||||
public static IDeleteable<T> AS<T>(this IDeleteable<T> deleteable) where T : EntityBase, new()
|
||||
{
|
||||
var info = GetTableInfo<T>();
|
||||
return deleteable.AS($"{info.Item1}.{info.Item2}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据实体类型获取表信息
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
private static Tuple<string, string> GetTableInfo<T>()
|
||||
{
|
||||
var entityType = typeof(T);
|
||||
var attr = entityType.GetCustomAttribute<TenantAttribute>();
|
||||
var configId = attr == null ? SqlSugarConst.MainConfigId : attr.configId.ToString();
|
||||
var tableName = entityType.GetCustomAttribute<SugarTable>().TableName;
|
||||
return new Tuple<string, string>(configId, tableName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 禁用过滤器-适用于更新和删除操作(只对当前请求有效,禁止使用异步)
|
||||
/// </summary>
|
||||
/// <param name="repository"></param>
|
||||
/// <param name="action">禁止异步</param>
|
||||
/// <returns></returns>
|
||||
public static void RunWithoutFilter(this ISugarRepository repository, Action action)
|
||||
{
|
||||
repository.Context.QueryFilter.ClearAndBackup(); // 清空并备份过滤器
|
||||
action.Invoke();
|
||||
repository.Context.QueryFilter.Restore(); // 还原过滤器
|
||||
|
||||
// 用例
|
||||
//_rep.RunWithoutFilter(() =>
|
||||
//{
|
||||
// 执行更新或者删除
|
||||
// 禁止使用异步函数
|
||||
//});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 忽略租户
|
||||
/// </summary>
|
||||
/// <param name="queryable"></param>
|
||||
/// <param name="ignore">是否忽略 默认true</param>
|
||||
/// <returns> </returns>
|
||||
public static ISugarQueryable<T> IgnoreTenant<T>(this ISugarQueryable<T> queryable, bool ignore = true)
|
||||
{
|
||||
return ignore ? queryable.ClearFilter<ITenantIdFilter>() : queryable;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 只更新某些列
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <typeparam name="R"></typeparam>
|
||||
/// <param name="updateable"></param>
|
||||
/// <returns></returns>
|
||||
public static IUpdateable<T> OnlyUpdateColumn<T, R>(this IUpdateable<T> updateable) where T : EntityBase, new() where R : class, new()
|
||||
{
|
||||
if (updateable.UpdateBuilder.UpdateColumns == null)
|
||||
updateable.UpdateBuilder.UpdateColumns = new List<string>();
|
||||
|
||||
foreach (PropertyInfo info in typeof(R).GetProperties())
|
||||
{
|
||||
// 判断是否是相同属性
|
||||
if (typeof(T).GetProperty(info.Name) != null)
|
||||
updateable.UpdateBuilder.UpdateColumns.Add(info.Name);
|
||||
}
|
||||
return updateable;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导航只更新(主表)某些列
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <typeparam name="R"></typeparam>
|
||||
/// <param name="t"></param>
|
||||
/// <param name="r"></param>
|
||||
/// <returns></returns>
|
||||
public static UpdateNavRootOptions OnlyNavUpdateColumn<T, R>(this T t, R r)
|
||||
{
|
||||
UpdateNavRootOptions uNOption = new UpdateNavRootOptions();
|
||||
var updateColumns = new List<string>();
|
||||
|
||||
foreach (PropertyInfo info in r.GetType().GetProperties())
|
||||
{
|
||||
//判断是否是相同属性
|
||||
PropertyInfo pro = t.GetType().GetProperty(info.Name);
|
||||
var attr = pro.GetCustomAttribute<SugarColumn>();
|
||||
if (pro != null && attr != null && !attr.IsPrimaryKey)
|
||||
updateColumns.Add(info.Name);
|
||||
}
|
||||
uNOption.UpdateColumns = updateColumns.ToArray();
|
||||
return uNOption;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量列表in查询
|
||||
/// </summary>
|
||||
/// <typeparam name="T1"></typeparam>
|
||||
/// <typeparam name="T2"></typeparam>
|
||||
/// <param name="queryable"></param>
|
||||
/// <param name="exp"></param>
|
||||
/// <param name="queryList"></param>
|
||||
/// <param name="stoppingToken"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<List<T1>> BulkListQuery<T1, T2>(this ISugarQueryable<T1> queryable,
|
||||
Expression<Func<T1, SingleColumnEntity<T2>, bool>> exp,
|
||||
IEnumerable<T2> queryList,
|
||||
CancellationToken stoppingToken) where T1 : class, new()
|
||||
{
|
||||
// 创建临时表 (用真表兼容性好,表名随机)
|
||||
var tableName = "Temp" + SnowFlakeSingle.Instance.NextId();
|
||||
try
|
||||
{
|
||||
var type = queryable.Context.DynamicBuilder().CreateClass(tableName, new SugarTable())
|
||||
.CreateProperty("ColumnName", typeof(string), new SugarColumn() { IsPrimaryKey = true }) // 主键不要自增
|
||||
.BuilderType();
|
||||
// 创建表
|
||||
queryable.Context.CodeFirst.InitTables(type);
|
||||
var insertData = queryList.Select(it => new SingleColumnEntity<T2>() { ColumnName = it }).ToList();
|
||||
// 插入临时表
|
||||
queryable.Context.Fastest<SingleColumnEntity<T2>>()
|
||||
.AS(tableName)
|
||||
.BulkCopy(insertData);
|
||||
var queryTemp = queryable.Context.Queryable<SingleColumnEntity<T2>>()
|
||||
.AS(tableName);
|
||||
|
||||
var systemData = await queryable
|
||||
.InnerJoin(queryTemp, exp)
|
||||
.ToListAsync(stoppingToken);
|
||||
|
||||
queryable.Context.DbMaintenance.DropTable(tableName);
|
||||
return systemData;
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
queryable.Context.DbMaintenance.DropTable(tableName);
|
||||
throw Oops.Oh(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
public static class RequestExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取请求地址源
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetOrigin(this HttpRequest request)
|
||||
{
|
||||
string scheme = request.Scheme;
|
||||
string host = request.Host.Host;
|
||||
int port = request.Host.Port ?? (-1);
|
||||
|
||||
string url = $"{scheme}://{host}";
|
||||
if (port != 80 && port != 443 && port != -1) url += $":{port}";
|
||||
|
||||
return url;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,367 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Sqlsugar 动态查询扩展方法
|
||||
/// </summary>
|
||||
public static class SqlSugarExtension
|
||||
{
|
||||
public static ISugarQueryable<T> SearchBy<T>(this ISugarQueryable<T> queryable, BaseFilter filter)
|
||||
{
|
||||
return queryable.SearchByKeyword(filter.Keyword)
|
||||
.AdvancedSearch(filter.Search)
|
||||
.AdvancedFilter(filter.Filter);
|
||||
}
|
||||
|
||||
public static ISugarQueryable<T> SearchByKeyword<T>(this ISugarQueryable<T> queryable, string keyword)
|
||||
{
|
||||
return queryable.AdvancedSearch(new Search { Keyword = keyword });
|
||||
}
|
||||
|
||||
public static ISugarQueryable<T> AdvancedSearch<T>(this ISugarQueryable<T> queryable, Search search)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(search?.Keyword))
|
||||
{
|
||||
var paramExpr = Expression.Parameter(typeof(T));
|
||||
|
||||
Expression right = Expression.Constant(false);
|
||||
|
||||
if (search.Fields?.Any() is true)
|
||||
{
|
||||
foreach (string field in search.Fields)
|
||||
{
|
||||
MemberExpression propertyExpr = GetPropertyExpression(field, paramExpr);
|
||||
|
||||
var left = AddSearchPropertyByKeyword<T>(propertyExpr, search.Keyword);
|
||||
|
||||
right = Expression.Or(left, right);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var properties = typeof(T).GetProperties()
|
||||
.Where(prop => Nullable.GetUnderlyingType(prop.PropertyType) == null
|
||||
&& !prop.PropertyType.IsEnum
|
||||
&& Type.GetTypeCode(prop.PropertyType) != TypeCode.Object);
|
||||
|
||||
foreach (var property in properties)
|
||||
{
|
||||
var propertyExpr = Expression.Property(paramExpr, property);
|
||||
|
||||
var left = AddSearchPropertyByKeyword<T>(propertyExpr, search.Keyword);
|
||||
|
||||
right = Expression.Or(left, right);
|
||||
}
|
||||
}
|
||||
|
||||
var lambda = Expression.Lambda<Func<T, bool>>(right, paramExpr);
|
||||
|
||||
return queryable.Where(lambda);
|
||||
}
|
||||
|
||||
return queryable;
|
||||
}
|
||||
|
||||
public static ISugarQueryable<T> AdvancedFilter<T>(this ISugarQueryable<T> queryable, Filter filter)
|
||||
{
|
||||
if (filter is not null)
|
||||
{
|
||||
var parameter = Expression.Parameter(typeof(T));
|
||||
|
||||
Expression binaryExpresioFilter;
|
||||
|
||||
if (filter.Logic.HasValue)
|
||||
{
|
||||
if (filter.Filters is null) throw new ArgumentException("The Filters attribute is required when declaring a logic");
|
||||
binaryExpresioFilter = CreateFilterExpression(filter.Logic.Value, filter.Filters, parameter);
|
||||
}
|
||||
else
|
||||
{
|
||||
var filterValid = GetValidFilter(filter);
|
||||
binaryExpresioFilter = CreateFilterExpression(filterValid.Field!, filterValid.Operator.Value, filterValid.Value, parameter);
|
||||
}
|
||||
|
||||
var lambda = Expression.Lambda<Func<T, bool>>(binaryExpresioFilter, parameter);
|
||||
|
||||
return queryable.Where(lambda);
|
||||
}
|
||||
return queryable;
|
||||
}
|
||||
|
||||
private static Expression CombineFilter(
|
||||
FilterLogicEnum filterLogic,
|
||||
Expression bExpresionBase,
|
||||
Expression bExpresion)
|
||||
{
|
||||
return filterLogic switch
|
||||
{
|
||||
FilterLogicEnum.And => Expression.AndAlso(bExpresionBase, bExpresion),
|
||||
FilterLogicEnum.Or => Expression.OrElse(bExpresionBase, bExpresion),
|
||||
FilterLogicEnum.Xor => Expression.ExclusiveOr(bExpresionBase, bExpresion),
|
||||
_ => throw new ArgumentException("FilterLogic is not valid.", nameof(filterLogic)),
|
||||
};
|
||||
}
|
||||
|
||||
private static Filter GetValidFilter(Filter filter)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filter.Field)) throw new ArgumentException("The field attribute is required when declaring a filter");
|
||||
if (filter.Operator.IsNullOrEmpty()) throw new ArgumentException("The Operator attribute is required when declaring a filter");
|
||||
return filter;
|
||||
}
|
||||
|
||||
private static Expression CreateFilterExpression(
|
||||
FilterLogicEnum filterLogic,
|
||||
IEnumerable<Filter> filters,
|
||||
ParameterExpression parameter)
|
||||
{
|
||||
Expression filterExpression = default!;
|
||||
|
||||
foreach (var filter in filters)
|
||||
{
|
||||
Expression bExpresionFilter;
|
||||
|
||||
if (filter.Logic.HasValue)
|
||||
{
|
||||
if (filter.Filters is null) throw new ArgumentException("The Filters attribute is required when declaring a logic");
|
||||
bExpresionFilter = CreateFilterExpression(filter.Logic.Value, filter.Filters, parameter);
|
||||
}
|
||||
else
|
||||
{
|
||||
var filterValid = GetValidFilter(filter);
|
||||
bExpresionFilter = CreateFilterExpression(filterValid.Field!, filterValid.Operator.Value, filterValid.Value, parameter);
|
||||
}
|
||||
|
||||
filterExpression = filterExpression is null ? bExpresionFilter : CombineFilter(filterLogic, filterExpression, bExpresionFilter);
|
||||
}
|
||||
|
||||
return filterExpression;
|
||||
}
|
||||
|
||||
private static Expression CreateFilterExpression(
|
||||
string field,
|
||||
FilterOperatorEnum filterOperator,
|
||||
object? value,
|
||||
ParameterExpression parameter)
|
||||
{
|
||||
var propertyExpresion = GetPropertyExpression(field, parameter);
|
||||
var valueExpresion = GeValuetExpression(field, value, propertyExpresion.Type);
|
||||
return CreateFilterExpression(propertyExpresion, valueExpresion, filterOperator);
|
||||
}
|
||||
|
||||
private static Expression CreateFilterExpression(
|
||||
MemberExpression memberExpression,
|
||||
ConstantExpression constantExpression,
|
||||
FilterOperatorEnum filterOperator)
|
||||
{
|
||||
return filterOperator switch
|
||||
{
|
||||
FilterOperatorEnum.EQ => Expression.Equal(memberExpression, constantExpression),
|
||||
FilterOperatorEnum.NEQ => Expression.NotEqual(memberExpression, constantExpression),
|
||||
FilterOperatorEnum.LT => Expression.LessThan(memberExpression, constantExpression),
|
||||
FilterOperatorEnum.LTE => Expression.LessThanOrEqual(memberExpression, constantExpression),
|
||||
FilterOperatorEnum.GT => Expression.GreaterThan(memberExpression, constantExpression),
|
||||
FilterOperatorEnum.GTE => Expression.GreaterThanOrEqual(memberExpression, constantExpression),
|
||||
FilterOperatorEnum.Contains => Expression.Call(memberExpression, nameof(FilterOperatorEnum.Contains), null, constantExpression),
|
||||
FilterOperatorEnum.StartsWith => Expression.Call(memberExpression, nameof(FilterOperatorEnum.StartsWith), null, constantExpression),
|
||||
FilterOperatorEnum.EndsWith => Expression.Call(memberExpression, nameof(FilterOperatorEnum.EndsWith), null, constantExpression),
|
||||
_ => throw new ArgumentException("Filter Operator is not valid."),
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetStringFromJsonElement(object value)
|
||||
{
|
||||
if (value is JsonElement) return ((JsonElement)value).GetString()!;
|
||||
if (value is string) return (string)value;
|
||||
return value?.ToString();
|
||||
}
|
||||
|
||||
private static ConstantExpression GeValuetExpression(
|
||||
string field,
|
||||
object? value,
|
||||
Type propertyType)
|
||||
{
|
||||
if (value == null) return Expression.Constant(null, propertyType);
|
||||
|
||||
if (propertyType.IsEnum)
|
||||
{
|
||||
string? stringEnum = GetStringFromJsonElement(value);
|
||||
|
||||
if (!Enum.TryParse(propertyType, stringEnum, true, out object? valueparsed)) throw new ArgumentException(string.Format("Value {0} is not valid for {1}", value, field));
|
||||
|
||||
return Expression.Constant(valueparsed, propertyType);
|
||||
}
|
||||
if (propertyType == typeof(long) || propertyType == typeof(long?))
|
||||
{
|
||||
string? stringLong = GetStringFromJsonElement(value);
|
||||
|
||||
if (!long.TryParse(stringLong, out long valueparsed)) throw new ArgumentException(string.Format("Value {0} is not valid for {1}", value, field));
|
||||
|
||||
return Expression.Constant(valueparsed, propertyType);
|
||||
}
|
||||
|
||||
if (propertyType == typeof(Guid))
|
||||
{
|
||||
string? stringGuid = GetStringFromJsonElement(value);
|
||||
|
||||
if (!Guid.TryParse(stringGuid, out Guid valueparsed)) throw new ArgumentException(string.Format("Value {0} is not valid for {1}", value, field));
|
||||
|
||||
return Expression.Constant(valueparsed, propertyType);
|
||||
}
|
||||
|
||||
if (propertyType == typeof(string))
|
||||
{
|
||||
string? text = GetStringFromJsonElement(value);
|
||||
|
||||
return Expression.Constant(text, propertyType);
|
||||
}
|
||||
|
||||
if (propertyType == typeof(DateTime) || propertyType == typeof(DateTime?))
|
||||
{
|
||||
string? text = GetStringFromJsonElement(value);
|
||||
return Expression.Constant(ChangeType(text, propertyType), propertyType);
|
||||
}
|
||||
|
||||
return Expression.Constant(ChangeType(((JsonElement)value).GetRawText(), propertyType), propertyType);
|
||||
}
|
||||
|
||||
private static dynamic? ChangeType(object value, Type conversion)
|
||||
{
|
||||
var t = conversion;
|
||||
|
||||
if (t.IsGenericType && t.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
t = Nullable.GetUnderlyingType(t);
|
||||
}
|
||||
|
||||
return Convert.ChangeType(value, t!);
|
||||
}
|
||||
|
||||
private static MemberExpression GetPropertyExpression(
|
||||
string propertyName,
|
||||
ParameterExpression parameter)
|
||||
{
|
||||
Expression propertyExpression = parameter;
|
||||
foreach (string member in propertyName.Split('.'))
|
||||
{
|
||||
propertyExpression = Expression.PropertyOrField(propertyExpression, member);
|
||||
}
|
||||
|
||||
return (MemberExpression)propertyExpression;
|
||||
}
|
||||
|
||||
private static Expression AddSearchPropertyByKeyword<T>(
|
||||
Expression propertyExpr,
|
||||
string keyword,
|
||||
FilterOperatorEnum operatorSearch = FilterOperatorEnum.Contains)
|
||||
{
|
||||
if (propertyExpr is not MemberExpression memberExpr || memberExpr.Member is not PropertyInfo property)
|
||||
{
|
||||
throw new ArgumentException("propertyExpr must be a property expression.", nameof(propertyExpr));
|
||||
}
|
||||
|
||||
ConstantExpression constant = Expression.Constant(keyword);
|
||||
|
||||
MethodInfo method = operatorSearch switch
|
||||
{
|
||||
FilterOperatorEnum.Contains => typeof(string).GetMethod(nameof(FilterOperatorEnum.Contains), new Type[] { typeof(string) }),
|
||||
FilterOperatorEnum.StartsWith => typeof(string).GetMethod(nameof(FilterOperatorEnum.StartsWith), new Type[] { typeof(string) }),
|
||||
FilterOperatorEnum.EndsWith => typeof(string).GetMethod(nameof(FilterOperatorEnum.EndsWith), new Type[] { typeof(string) }),
|
||||
_ => throw new ArgumentException("Filter Operator is not valid."),
|
||||
};
|
||||
|
||||
Expression selectorExpr =
|
||||
property.PropertyType == typeof(string)
|
||||
? propertyExpr
|
||||
: Expression.Condition(
|
||||
Expression.Equal(Expression.Convert(propertyExpr, typeof(object)), Expression.Constant(null, typeof(object))),
|
||||
Expression.Constant(null, typeof(string)),
|
||||
Expression.Call(propertyExpr, "ToString", null, null));
|
||||
|
||||
return Expression.Call(selectorExpr, method, constant);
|
||||
}
|
||||
|
||||
#region 视图操作
|
||||
|
||||
/// <summary>
|
||||
/// 获取映射SQL语句, 用于创建视图
|
||||
/// </summary>
|
||||
/// <param name="queryable"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public static string ToMappedSqlString<T>(this ISugarQueryable<T> queryable) where T : class
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(queryable);
|
||||
|
||||
// 获取实体映射信息
|
||||
var entityInfo = queryable.Context.EntityMaintenance.GetEntityInfo(typeof(T));
|
||||
if (entityInfo?.Columns == null || entityInfo.Columns.Count == 0) return queryable.ToSqlString();
|
||||
|
||||
// 构建需要替换的字段名映射(只处理实际有差异的字段)
|
||||
var nameMap = entityInfo.Columns
|
||||
.Where(c => !string.Equals(c.PropertyName, c.DbColumnName, StringComparison.OrdinalIgnoreCase))
|
||||
.ToDictionary(k => k.PropertyName.ToLower(), v => v.DbColumnName, StringComparer.OrdinalIgnoreCase);
|
||||
if (nameMap.Count == 0) return queryable.ToSqlString();
|
||||
|
||||
// 预编译正则表达式提升性能
|
||||
var sql = queryable.ToSqlString();
|
||||
foreach (var kv in nameMap)
|
||||
{
|
||||
sql = Regex.Replace(sql, $@"\b{kv.Key}\b", kv.Value ?? kv.Key, RegexOptions.IgnoreCase | RegexOptions.Compiled); // 单词边界匹配
|
||||
}
|
||||
return sql;
|
||||
}
|
||||
|
||||
#endregion 视图操作
|
||||
|
||||
/// <summary>
|
||||
/// 列表转换为树形结构
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="source">列表数据</param>
|
||||
/// <param name="childrenSelector">设置子节点列表。例如:item => item.Children</param>
|
||||
/// <param name="parentIdSelector">设置元素的父级 Id。例如:item => item.ParentId</param>
|
||||
/// <param name="rootParentId">根节点的父级 Id,默认为 0 </param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public static IEnumerable<T> ToTree<T>(
|
||||
this IEnumerable<T> source,
|
||||
Func<T, List<T>> childrenSelector,
|
||||
Func<T, long> parentIdSelector,
|
||||
int rootParentId)
|
||||
where T : class
|
||||
{
|
||||
var lookup = source.ToLookup(parentIdSelector);
|
||||
List<T> BuildTree(long parentId)
|
||||
{
|
||||
return lookup[parentId].Select(item =>
|
||||
{
|
||||
var children = BuildTree(GetId(item));
|
||||
childrenSelector(item).Clear();
|
||||
childrenSelector(item).AddRange(children);
|
||||
return item;
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
// 需要提供获取Id的方法,可以用反射或者自己传参数
|
||||
long GetId(T item)
|
||||
{
|
||||
var prop = typeof(T).GetProperty("Id");
|
||||
if (prop == null) throw new Exception("没有找到Id属性");
|
||||
return (long)prop.GetValue(item);
|
||||
}
|
||||
|
||||
return BuildTree(rootParentId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
public static class SqlSugarFilterExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 根据指定Attribute获取属性
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
private static List<string> GetPropertyNames<T>(this Type type) where T : Attribute
|
||||
{
|
||||
return type.GetProperties()
|
||||
.Where(p => p.CustomAttributes.Any(x => x.AttributeType == typeof(T)))
|
||||
.Select(x => x.Name).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取过滤表达式
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="owners"></param>
|
||||
/// <returns></returns>
|
||||
public static LambdaExpression GetConditionExpression<T>(this Type type, List<long> owners) where T : Attribute
|
||||
{
|
||||
var fieldNames = type.GetPropertyNames<T>();
|
||||
ParameterExpression parameter = Expression.Parameter(type, "c");
|
||||
|
||||
Expression right = Expression.Constant(false);
|
||||
ConstantExpression ownersCollection = Expression.Constant(owners);
|
||||
foreach (var fieldName in fieldNames)
|
||||
{
|
||||
var property = type.GetProperty(fieldName);
|
||||
Expression memberExp = Expression.Property(parameter, property!);
|
||||
|
||||
// 如果属性是可为空的类型,则转换为其基础类型
|
||||
var baseType = Nullable.GetUnderlyingType(property.PropertyType);
|
||||
if (baseType != null) memberExp = Expression.Convert(memberExp, baseType);
|
||||
|
||||
// 调用ownersCollection.Contains方法,检查是否包含属性值
|
||||
right = Expression.OrElse(Expression.Call(
|
||||
typeof(Enumerable),
|
||||
nameof(Enumerable.Contains),
|
||||
new[] { memberExp.Type },
|
||||
ownersCollection,
|
||||
memberExp
|
||||
), right);
|
||||
}
|
||||
return Expression.Lambda(right, parameter);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 字符串扩展方法
|
||||
/// </summary>
|
||||
public static class StringExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 字符串截断
|
||||
/// </summary>
|
||||
public static string Truncate(this string str, int maxLength, string ellipsis = "...")
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(str)) return str;
|
||||
if (maxLength <= 0) return string.Empty;
|
||||
if (str.Length <= maxLength) return str;
|
||||
|
||||
// 确保省略号不会导致字符串超出最大长度
|
||||
int ellipsisLength = ellipsis?.Length ?? 0;
|
||||
int truncateLength = Math.Min(maxLength, str.Length - ellipsisLength);
|
||||
return str[..truncateLength] + ellipsis;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 单词首字母全部大写
|
||||
/// </summary>
|
||||
public static string ToTitleCase(this string str)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(str) ? str : System.Globalization.CultureInfo.CurrentCulture.TextInfo.ToTitleCase(str.ToLower());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否包含子串,忽略大小写
|
||||
/// </summary>
|
||||
public static bool ContainsIgnoreCase(this string str, string substring)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(str) || string.IsNullOrWhiteSpace(substring)) return false;
|
||||
return str.Contains(substring, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断是否是 JSON 数据
|
||||
/// </summary>
|
||||
public static bool IsJson(this string str)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(str)) return false;
|
||||
str = str.Trim();
|
||||
return (str.StartsWith("{") && str.EndsWith("}")) || (str.StartsWith("[") && str.EndsWith("]"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断是否是 HTML 数据
|
||||
/// </summary>
|
||||
public static bool IsHtml(this string str)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(str)) return false;
|
||||
str = str.Trim();
|
||||
|
||||
// 检查是否以 <!DOCTYPE html> 或 <html> 开头
|
||||
if (str.StartsWith("<!DOCTYPE html>", StringComparison.OrdinalIgnoreCase) || str.StartsWith("<html>", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// 检查是否包含 HTML 标签
|
||||
return Regex.IsMatch(str, @"<\s*[^>]+>.*<\s*/\s*[^>]+>|<\s*[^>]+\s*/>", RegexOptions.Singleline | RegexOptions.IgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 字符串反转
|
||||
/// </summary>
|
||||
public static string Reverse(this string str)
|
||||
{
|
||||
if (string.IsNullOrEmpty(str)) return str;
|
||||
|
||||
// 使用 Span<char> 提高性能
|
||||
Span<char> charSpan = stackalloc char[str.Length];
|
||||
for (int i = 0; i < str.Length; i++)
|
||||
{
|
||||
charSpan[str.Length - 1 - i] = str[i];
|
||||
}
|
||||
return new string(charSpan);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转首字母小写
|
||||
/// </summary>
|
||||
public static string ToFirstLetterLowerCase(this string input)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(input)) return input;
|
||||
if (input.Length == 1) return input.ToLower(); // 处理单字符字符串
|
||||
|
||||
return char.ToLower(input[0]) + input[1..];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 渲染字符串,替换占位符
|
||||
/// </summary>
|
||||
/// <param name="template">模板内容</param>
|
||||
/// <param name="parameters">参数对象</param>
|
||||
/// <returns></returns>
|
||||
public static string Render(this string template, object parameters)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(template)) return template;
|
||||
|
||||
// 将参数转换为字典(忽略大小写)
|
||||
var paramDict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
if (parameters != null)
|
||||
{
|
||||
foreach (var prop in parameters.GetType().GetProperties())
|
||||
{
|
||||
paramDict[prop.Name] = prop.GetValue(parameters)?.ToString() ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
// 使用正则表达式替换占位符
|
||||
return Regex.Replace(template, @"\{(\w+)\}", match =>
|
||||
{
|
||||
string key = match.Groups[1].Value; // 获取占位符中的 key
|
||||
return paramDict.TryGetValue(key, out string value) ? value : string.Empty;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 驼峰转下划线
|
||||
/// </summary>
|
||||
/// <param name="str"></param>
|
||||
/// <param name="isToUpper"></param>
|
||||
/// <returns></returns>
|
||||
public static string ToUnderLine(this string str, bool isToUpper = false)
|
||||
{
|
||||
if (string.IsNullOrEmpty(str) || str.Contains("_"))
|
||||
{
|
||||
return str;
|
||||
}
|
||||
|
||||
int length = str.Length;
|
||||
var result = new System.Text.StringBuilder(length + (length / 3));
|
||||
|
||||
result.Append(char.ToLowerInvariant(str[0]));
|
||||
|
||||
int lastIndex = length - 1;
|
||||
|
||||
for (int i = 1; i < length; i++)
|
||||
{
|
||||
char current = str[i];
|
||||
if (!char.IsUpper(current))
|
||||
{
|
||||
result.Append(current);
|
||||
continue;
|
||||
}
|
||||
|
||||
bool prevIsLower = char.IsLower(str[i - 1]);
|
||||
bool nextIsLower = (i < lastIndex) && char.IsLower(str[i + 1]);
|
||||
|
||||
if (prevIsLower || nextIsLower)
|
||||
{
|
||||
result.Append('_');
|
||||
}
|
||||
|
||||
result.Append((char)(current | 0x20));
|
||||
}
|
||||
|
||||
string converted = result.ToString();
|
||||
return isToUpper ? converted.ToUpperInvariant() : converted;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
using AspNetCoreRateLimit;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 配置中间件扩展
|
||||
/// </summary>
|
||||
public static class UseApplicationBuilder
|
||||
{
|
||||
// 配置限流中间件策略
|
||||
public static void UsePolicyRateLimit(this IApplicationBuilder app)
|
||||
{
|
||||
var ipPolicyStore = app.ApplicationServices.GetRequiredService<IIpPolicyStore>();
|
||||
ipPolicyStore.SeedAsync().GetAwaiter().GetResult();
|
||||
|
||||
var clientPolicyStore = app.ApplicationServices.GetRequiredService<IClientPolicyStore>();
|
||||
clientPolicyStore.SeedAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user