Selaa lähdekoodia

Merge branch 'master' of https://github.com/ldqk/Masuit.Tools

lazy y chen 2 vuotta sitten
vanhempi
sitoutus
7aaee808ae
48 muutettua tiedostoa jossa 2882 lisäystä ja 1758 poistoa
  1. 1 1
      BenchmarkTest/BenchmarkTest.csproj
  2. 275 202
      Masuit.Tools.Abstractions/Extensions/BaseType/IConvertibleExtensions.cs
  3. 1 1
      Masuit.Tools.Abstractions/Extensions/BaseType/IDictionaryExtensions.cs
  4. 2 3
      Masuit.Tools.Abstractions/Extensions/BaseType/IEnumerableExtensions.cs
  5. 34 0
      Masuit.Tools.Abstractions/Extensions/BaseType/ObjectExtensions.cs
  6. 1 14
      Masuit.Tools.Abstractions/Html/HtmlTools.cs
  7. 3 9
      Masuit.Tools.Abstractions/Masuit.Tools.Abstractions.csproj
  8. 204 204
      Masuit.Tools.Abstractions/Media/ImageWatermarker.cs
  9. 62 27
      Masuit.Tools.Abstractions/RandomSelector/Extensions.cs
  10. 43 43
      Masuit.Tools.Abstractions/RandomSelector/MultipleSelector.cs
  11. 57 56
      Masuit.Tools.Abstractions/RandomSelector/SelectorBase.cs
  12. 18 18
      Masuit.Tools.Abstractions/RandomSelector/SingleSelector.cs
  13. 2 1
      Masuit.Tools.Abstractions/RandomSelector/WeightedSelector.cs
  14. 109 108
      Masuit.Tools.Abstractions/Strings/ValidateCode.cs
  15. 1 1
      Masuit.Tools.Abstractions/Systems/DeserializeOnlyJsonPropertyAttribute.cs
  16. 203 245
      Masuit.Tools.Abstractions/Systems/EnumExt.cs
  17. 27 0
      Masuit.Tools.Abstractions/Win32/AntiVirus/AmsiResult.cs
  18. 185 0
      Masuit.Tools.Abstractions/Win32/AntiVirus/AmsiScanService.cs
  19. 25 0
      Masuit.Tools.Abstractions/Win32/AntiVirus/AmsiWrapper.cs
  20. 35 0
      Masuit.Tools.Abstractions/Win32/AntiVirus/ScanResult.cs
  21. 8 0
      Masuit.Tools.Abstractions/Win32/AntiVirus/SystemParameter.cs
  22. 10 0
      Masuit.Tools.Abstractions/Win32/AntiVirus/WindowsDefenderScanException.cs
  23. 171 0
      Masuit.Tools.Abstractions/Win32/AntiVirus/WindowsDefenderScanService.cs
  24. 3 3
      Masuit.Tools.AspNetCore/Masuit.Tools.AspNetCore.csproj
  25. 48 0
      Masuit.Tools.AspNetCore/ModelBinder/BindType.cs
  26. 95 0
      Masuit.Tools.AspNetCore/ModelBinder/BodyOrDefaultBinderMiddleware.cs
  27. 0 17
      Masuit.Tools.AspNetCore/ModelBinder/BodyOrDefaultBindingSource.cs
  28. 0 93
      Masuit.Tools.AspNetCore/ModelBinder/BodyOrDefaultModelBinder.cs
  29. 0 37
      Masuit.Tools.AspNetCore/ModelBinder/BodyOrDefaultModelBinderProvider.cs
  30. 0 24
      Masuit.Tools.AspNetCore/ModelBinder/BodyOrDefaultModelBinderProviderSetup.cs
  31. 66 6
      Masuit.Tools.AspNetCore/ModelBinder/FromBodyOrDefaultAttribute.cs
  32. 300 0
      Masuit.Tools.AspNetCore/ModelBinder/FromBodyOrDefaultModelBinder.cs
  33. 14 0
      Masuit.Tools.AspNetCore/ModelBinder/MiddlewareExtensions.cs
  34. 159 0
      Masuit.Tools.AspNetCore/ModelBinder/ModelBindingContextExtension.cs
  35. 3 3
      Masuit.Tools.Core/Masuit.Tools.Core.csproj
  36. 40 0
      Masuit.Tools.Core/Windows/RegisterFileSecurityScanSersvice.cs
  37. 609 609
      Masuit.Tools.Excel/ExcelExtension.cs
  38. 3 3
      Masuit.Tools.Excel/Masuit.Tools.Excel.csproj
  39. 1 1
      Masuit.Tools.Net45/package.nuspec
  40. 1 1
      Masuit.Tools.NoSQL.MongoDBClient/Masuit.Tools.NoSQL.MongoDBClient.csproj
  41. 4 1
      Masuit.Tools/Masuit.Tools.csproj
  42. 1 1
      Masuit.Tools/package.nuspec
  43. 14 9
      NetCoreTest/Controllers/HomeController.cs
  44. 4 5
      NetCoreTest/Program.cs
  45. 36 8
      README.md
  46. 1 1
      Test/Masuit.Tools.Abstractions.Test/Masuit.Tools.Abstractions.Test.csproj
  47. 2 2
      Test/Masuit.Tools.Core.Test/Masuit.Tools.Core.Test.csproj
  48. 1 1
      Test/Masuit.Tools.Test/Masuit.Tools.Test.csproj

+ 1 - 1
BenchmarkTest/BenchmarkTest.csproj

@@ -7,7 +7,7 @@
   </PropertyGroup>
   </PropertyGroup>
 
 
   <ItemGroup>
   <ItemGroup>
-    <PackageReference Include="BenchmarkDotNet" Version="0.13.6" />
+    <PackageReference Include="BenchmarkDotNet" Version="0.13.7" />
   </ItemGroup>
   </ItemGroup>
 
 
   <ItemGroup>
   <ItemGroup>

+ 275 - 202
Masuit.Tools.Abstractions/Extensions/BaseType/IConvertibleExtensions.cs

@@ -1,211 +1,284 @@
 using System;
 using System;
 using System.ComponentModel;
 using System.ComponentModel;
 using System.Globalization;
 using System.Globalization;
+using System.Linq;
 
 
 namespace Masuit.Tools;
 namespace Masuit.Tools;
 
 
 public static class IConvertibleExtensions
 public static class IConvertibleExtensions
 {
 {
-    public static bool IsNumeric(this Type type)
-    {
-        switch (Type.GetTypeCode(type))
-        {
-            case TypeCode.Byte:
-            case TypeCode.SByte:
-            case TypeCode.UInt16:
-            case TypeCode.UInt32:
-            case TypeCode.UInt64:
-            case TypeCode.Int16:
-            case TypeCode.Int32:
-            case TypeCode.Int64:
-            case TypeCode.Decimal:
-            case TypeCode.Double:
-            case TypeCode.Single:
-                return true;
-
-            default:
-                return false;
-        }
-    }
-
-    /// <summary>
-    /// 类型直转
-    /// </summary>
-    /// <typeparam name="T"></typeparam>
-    /// <param name="value"></param>
-    /// <returns></returns>
-
-    public static T ConvertTo<T>(this IConvertible value) where T : IConvertible
-    {
-        if (value != null)
-        {
-            var type = typeof(T);
-            if (value.GetType() == type)
-            {
-                return (T)value;
-            }
-
-            if (type.IsNumeric())
-            {
-                return (T)value.ToType(type, new NumberFormatInfo());
-            }
-
-            if (value == DBNull.Value)
-            {
-                return default;
-            }
-
-            if (type.IsEnum)
-            {
-                return (T)Enum.Parse(type, value.ToString(CultureInfo.InvariantCulture));
-            }
-
-            if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
-            {
-                var underlyingType = Nullable.GetUnderlyingType(type);
-                return (T)(underlyingType!.IsEnum ? Enum.Parse(underlyingType, value.ToString(CultureInfo.CurrentCulture)) : Convert.ChangeType(value, underlyingType));
-            }
-
-            TypeConverter converter = TypeDescriptor.GetConverter(value);
-            if (converter != null)
-            {
-                if (converter.CanConvertTo(type))
-                {
-                    return (T)converter.ConvertTo(value, type);
-                }
-            }
-
-            converter = TypeDescriptor.GetConverter(type);
-            if (converter != null)
-            {
-                if (converter.CanConvertFrom(value.GetType()))
-                {
-                    return (T)converter.ConvertFrom(value);
-                }
-            }
-            return (T)Convert.ChangeType(value, type);
-        }
-
-        return (T)value;
-    }
-
-    /// <summary>
-    /// 类型直转
-    /// </summary>
-    /// <typeparam name="T"></typeparam>
-    /// <param name="value"></param>
-    /// <param name="defaultValue">转换失败的默认值</param>
-    /// <returns></returns>
-    public static T TryConvertTo<T>(this IConvertible value, T defaultValue = default) where T : IConvertible
-    {
-        try
-        {
-            return ConvertTo<T>(value);
-        }
-        catch
-        {
-            return defaultValue;
-        }
-    }
-
-    /// <summary>
-    /// 类型直转
-    /// </summary>
-    /// <typeparam name="T"></typeparam>
-    /// <param name="value"></param>
-    /// <param name="result">转换失败的默认值</param>
-    /// <returns></returns>
-    public static bool TryConvertTo<T>(this IConvertible value, out T result) where T : IConvertible
-    {
-        try
-        {
-            result = ConvertTo<T>(value);
-            return true;
-        }
-        catch
-        {
-            result = default;
-            return false;
-        }
-    }
-
-    /// <summary>
-    /// 类型直转
-    /// </summary>
-    /// <param name="value"></param>
-    /// <param name="type">目标类型</param>
-    /// <param name="result">转换失败的默认值</param>
-    /// <returns></returns>
-    public static bool TryConvertTo(this IConvertible value, Type type, out object result)
-    {
-        try
-        {
-            result = ConvertTo(value, type);
-            return true;
-        }
-        catch
-        {
-            result = default;
-            return false;
-        }
-    }
-
-    /// <summary>
-    /// 类型直转
-    /// </summary>
-    /// <param name="value"></param>
-    /// <param name="type">目标类型</param>
-    /// <returns></returns>
-    public static object ConvertTo(this IConvertible value, Type type)
-    {
-        if (value == null)
-        {
-            return default;
-        }
-
-        if (value.GetType() == type)
-        {
-            return value;
-        }
-
-        if (value == DBNull.Value)
-        {
-            return null;
-        }
-
-        if (type.IsEnum)
-        {
-            return Enum.Parse(type, value.ToString(CultureInfo.InvariantCulture));
-        }
-
-        if (type.IsNumeric())
-        {
-            return value.ToType(type, new NumberFormatInfo());
-        }
-
-        if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
-        {
-            var underlyingType = Nullable.GetUnderlyingType(type);
-            return underlyingType!.IsEnum ? Enum.Parse(underlyingType, value.ToString(CultureInfo.CurrentCulture)) : Convert.ChangeType(value, underlyingType);
-        }
-
-        var converter = TypeDescriptor.GetConverter(value);
-        if (converter != null)
-        {
-            if (converter.CanConvertTo(type))
-            {
-                return converter.ConvertTo(value, type);
-            }
-        }
-
-        converter = TypeDescriptor.GetConverter(type);
-        if (converter != null)
-        {
-            if (converter.CanConvertFrom(value.GetType()))
-            {
-                return converter.ConvertFrom(value);
-            }
-        }
-        return Convert.ChangeType(value, type);
-    }
+	public static bool IsNumeric(this Type type)
+	{
+		switch (Type.GetTypeCode(type))
+		{
+			case TypeCode.Byte:
+			case TypeCode.SByte:
+			case TypeCode.UInt16:
+			case TypeCode.UInt32:
+			case TypeCode.UInt64:
+			case TypeCode.Int16:
+			case TypeCode.Int32:
+			case TypeCode.Int64:
+			case TypeCode.Decimal:
+			case TypeCode.Double:
+			case TypeCode.Single:
+				return true;
+
+			default:
+				return false;
+		}
+	}
+
+	/// <summary>
+	/// 类型直转
+	/// </summary>
+	/// <typeparam name="T"></typeparam>
+	/// <param name="value"></param>
+	/// <returns></returns>
+
+	public static T ConvertTo<T>(this IConvertible value) where T : IConvertible
+	{
+		if (value != null)
+		{
+			var type = typeof(T);
+			if (value.GetType() == type)
+			{
+				return (T)value;
+			}
+
+			if (type.IsNumeric())
+			{
+				return (T)value.ToType(type, new NumberFormatInfo());
+			}
+
+			if (value == DBNull.Value)
+			{
+				return default;
+			}
+
+			if (type.IsEnum)
+			{
+				return (T)Enum.Parse(type, value.ToString(CultureInfo.InvariantCulture));
+			}
+
+			if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
+			{
+				var underlyingType = Nullable.GetUnderlyingType(type);
+				return (T)(underlyingType!.IsEnum ? Enum.Parse(underlyingType, value.ToString(CultureInfo.CurrentCulture)) : Convert.ChangeType(value, underlyingType));
+			}
+
+			var converter = TypeDescriptor.GetConverter(value);
+			if (converter.CanConvertTo(type))
+			{
+				return (T)converter.ConvertTo(value, type);
+			}
+
+			converter = TypeDescriptor.GetConverter(type);
+			if (converter.CanConvertFrom(value.GetType()))
+			{
+				return (T)converter.ConvertFrom(value);
+			}
+
+			return (T)Convert.ChangeType(value, type);
+		}
+
+		return (T)value;
+	}
+
+	/// <summary>
+	/// 类型直转
+	/// </summary>
+	/// <typeparam name="T"></typeparam>
+	/// <param name="value"></param>
+	/// <param name="defaultValue">转换失败的默认值</param>
+	/// <returns></returns>
+	public static T TryConvertTo<T>(this IConvertible value, T defaultValue = default) where T : IConvertible
+	{
+		try
+		{
+			return ConvertTo<T>(value);
+		}
+		catch
+		{
+			return defaultValue;
+		}
+	}
+
+	/// <summary>
+	/// 类型直转
+	/// </summary>
+	/// <typeparam name="T"></typeparam>
+	/// <param name="value"></param>
+	/// <param name="result">转换失败的默认值</param>
+	/// <returns></returns>
+	public static bool TryConvertTo<T>(this IConvertible value, out T result) where T : IConvertible
+	{
+		try
+		{
+			result = ConvertTo<T>(value);
+			return true;
+		}
+		catch
+		{
+			result = default;
+			return false;
+		}
+	}
+
+	/// <summary>
+	/// 类型直转
+	/// </summary>
+	/// <param name="value"></param>
+	/// <param name="type">目标类型</param>
+	/// <param name="result">转换失败的默认值</param>
+	/// <returns></returns>
+	public static bool TryConvertTo(this IConvertible value, Type type, out object result)
+	{
+		try
+		{
+			result = ConvertTo(value, type);
+			return true;
+		}
+		catch
+		{
+			result = default;
+			return false;
+		}
+	}
+
+	/// <summary>
+	/// 类型直转
+	/// </summary>
+	/// <param name="value"></param>
+	/// <param name="type">目标类型</param>
+	/// <returns></returns>
+	public static object ConvertTo(this IConvertible value, Type type)
+	{
+		if (value == null)
+		{
+			return default;
+		}
+
+		if (value.GetType() == type)
+		{
+			return value;
+		}
+
+		if (value == DBNull.Value)
+		{
+			return null;
+		}
+
+		if (type.IsAssignableFrom(typeof(string)))
+		{
+			return value.ToString();
+		}
+
+		if (type.IsEnum)
+		{
+			return Enum.Parse(type, value.ToString(CultureInfo.InvariantCulture));
+		}
+
+		if (type.IsAssignableFrom(typeof(Guid)))
+		{
+			return Guid.Parse(value.ToString());
+		}
+
+		if (type.IsAssignableFrom(typeof(DateTime)))
+		{
+			return DateTime.Parse(value.ToString());
+		}
+
+		if (type.IsAssignableFrom(typeof(DateTimeOffset)))
+		{
+			return DateTimeOffset.Parse(value.ToString());
+		}
+
+		if (type.IsNumeric())
+		{
+			return value.ToType(type, new NumberFormatInfo());
+		}
+
+		if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
+		{
+			var underlyingType = Nullable.GetUnderlyingType(type);
+			return underlyingType!.IsEnum ? Enum.Parse(underlyingType, value.ToString(CultureInfo.CurrentCulture)) : Convert.ChangeType(value, underlyingType);
+		}
+
+		var converter = TypeDescriptor.GetConverter(value);
+		if (converter.CanConvertTo(type))
+		{
+			return converter.ConvertTo(value, type);
+		}
+
+		converter = TypeDescriptor.GetConverter(type);
+		return converter.CanConvertFrom(value.GetType()) ? converter.ConvertFrom(value) : Convert.ChangeType(value, type);
+	}
+
+	/// <summary>
+	/// 对象类型转换
+	/// </summary>
+	/// <param name="this">当前值</param>
+	/// <returns>转换后的对象</returns>
+	public static T ChangeTypeTo<T>(this object @this)
+	{
+		return (T)ChangeType(@this, typeof(T));
+	}
+
+	/// <summary>
+	/// 对象类型转换
+	/// </summary>
+	/// <param name="this">当前值</param>
+	/// <param name="type">指定类型的类型</param>
+	/// <returns>转换后的对象</returns>
+	public static object ChangeType(this object @this, Type type)
+	{
+		var currType = Nullable.GetUnderlyingType(@this.GetType()) ?? @this.GetType();
+		type = Nullable.GetUnderlyingType(type) ?? type;
+		if (@this == DBNull.Value)
+		{
+			if (!type.IsValueType)
+			{
+				return null;
+			}
+
+			throw new Exception("不能将null值转换为" + type.Name + "类型!");
+		}
+
+		if (currType == type)
+		{
+			return @this;
+		}
+
+		if (type.IsAssignableFrom(typeof(string)))
+		{
+			return @this.ToString();
+		}
+
+		if (type.IsEnum)
+		{
+			return Enum.Parse(type, @this.ToString(), true);
+		}
+
+		if (type.IsAssignableFrom(typeof(Guid)))
+		{
+			return Guid.Parse(@this.ToString());
+		}
+
+		if (!type.IsArray || !currType.IsArray)
+		{
+			return Convert.ChangeType(@this, type);
+		}
+
+		var length = ((Array)@this).Length;
+		var targetType = Type.GetType(type.FullName.Trim('[', ']'));
+		var array = Array.CreateInstance(targetType, length);
+		for (int j = 0; j < length; j++)
+		{
+			var tmp = ((Array)@this).GetValue(j);
+			array.SetValue(ChangeType(tmp, targetType), j);
+		}
+
+		return array;
+	}
 }
 }

+ 1 - 1
Masuit.Tools.Abstractions/Extensions/BaseType/IDictionaryExtensions.cs

@@ -660,7 +660,7 @@ namespace Masuit.Tools
         /// <param name="key"></param>
         /// <param name="key"></param>
         /// <param name="addValueFactory"></param>
         /// <param name="addValueFactory"></param>
         /// <returns></returns>
         /// <returns></returns>
-        public static TValue GetOrAdd<TKey, TValue>(this Dictionary<TKey, TValue> @this, TKey key, Func<TValue> addValueFactory)
+        public static TValue GetOrAdd<TKey, TValue>(this IDictionary<TKey, TValue> @this, TKey key, Func<TValue> addValueFactory)
         {
         {
             if ([email protected](key))
             if ([email protected](key))
             {
             {

+ 2 - 3
Masuit.Tools.Abstractions/Extensions/BaseType/IEnumerableExtensions.cs

@@ -10,7 +10,7 @@ using System.Threading.Tasks;
 namespace Masuit.Tools;
 namespace Masuit.Tools;
 
 
 /// <summary>
 /// <summary>
-/// 
+///
 /// </summary>
 /// </summary>
 public static class IEnumerableExtensions
 public static class IEnumerableExtensions
 {
 {
@@ -73,7 +73,6 @@ public static class IEnumerableExtensions
         }
         }
     }
     }
 
 
-
     /// <summary>
     /// <summary>
     /// 多个集合取交集元素
     /// 多个集合取交集元素
     /// </summary>
     /// </summary>
@@ -1138,4 +1137,4 @@ public static class IEnumerableExtensions
         list.Remove(item);
         list.Remove(item);
         list.Insert(index, item);
         list.Insert(index, item);
     }
     }
-}
+}

+ 34 - 0
Masuit.Tools.Abstractions/Extensions/BaseType/ObjectExtensions.cs

@@ -24,6 +24,40 @@ namespace Masuit.Tools
 			return type.IsValueType & type.IsPrimitive;
 			return type.IsValueType & type.IsPrimitive;
 		}
 		}
 
 
+		/// <summary>
+		/// 判断类型是否是常见的简单类型
+		/// </summary>
+		/// <param name="type"></param>
+		/// <returns></returns>
+		public static bool IsSimpleType(this Type type)
+		{
+			//IsPrimitive 判断是否为基础类型。
+			//基元类型为 Boolean、 Byte、 SByte、 Int16、 UInt16、 Int32、 UInt32、 Int64、 UInt64、 IntPtr、 UIntPtr、 Char、 Double 和 Single。
+			var t = Nullable.GetUnderlyingType(type) ?? type;
+			return t.IsPrimitive || t.IsEnum || t == typeof(decimal) || t == typeof(string) || t == typeof(Guid) || t == typeof(TimeSpan) || t == typeof(Uri);
+		}
+
+		/// <summary>
+		/// 是否是常见类型的 数组形式 类型
+		/// </summary>
+		/// <param name="type"></param>
+		/// <returns></returns>
+		public static bool IsSimpleArrayType(this Type type)
+		{
+			return type.IsArray && Type.GetType(type.FullName!.Trim('[', ']')).IsSimpleType();
+		}
+
+		/// <summary>
+		/// 是否是常见类型的 泛型形式 类型
+		/// </summary>
+		/// <param name="type"></param>
+		/// <returns></returns>
+		public static bool IsSimpleListType(this Type type)
+		{
+			type = Nullable.GetUnderlyingType(type) ?? type;
+			return type.IsGenericType && type.GetGenericArguments().Length == 1 && type.GetGenericArguments().FirstOrDefault().IsSimpleType();
+		}
+
 		public static bool IsDefaultValue(this object value)
 		public static bool IsDefaultValue(this object value)
 		{
 		{
 			if (value == default)
 			if (value == default)

+ 1 - 14
Masuit.Tools.Abstractions/Html/HtmlTools.cs

@@ -172,20 +172,7 @@ namespace Masuit.Tools.Html
         /// <returns></returns>
         /// <returns></returns>
         public static string MatchRandomImgSrc(this string html)
         public static string MatchRandomImgSrc(this string html)
         {
         {
-            var srcs = MatchImgSrcs(html).ToList();
-            var rnd = new Random();
-            return srcs.Count > 0 ? srcs[rnd.Next(srcs.Count)] : default;
-        }
-
-        /// <summary>
-        /// 按顺序优先获取html代码中的img标签的src属性
-        /// </summary>
-        /// <param name="html"></param>
-        /// <returns></returns>
-        public static string MatchSeqRandomImgSrc(this string html)
-        {
-            var srcs = MatchImgSrcs(html).ToList();
-            return srcs.Count > 0 ? srcs.Select((s, i) => new WeightedItem<string>(s, srcs.Count - i)).WeightedItem() : default;
+            return MatchImgSrcs(html).OrderByRandom().FirstOrDefault();
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 3 - 9
Masuit.Tools.Abstractions/Masuit.Tools.Abstractions.csproj

@@ -3,7 +3,7 @@
         <TargetFrameworks>netstandard2.0;netstandard2.1;net461;net5;net6;net7</TargetFrameworks>
         <TargetFrameworks>netstandard2.0;netstandard2.1;net461;net5;net6;net7</TargetFrameworks>
         <LangVersion>latest</LangVersion>
         <LangVersion>latest</LangVersion>
         <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
         <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
-        <Version>2.6.3</Version>
+        <Version>2.6.5</Version>
         <Authors>懒得勤快</Authors>
         <Authors>懒得勤快</Authors>
         <Description>新手友好的C#万能工具库,码数吐司库,Masuit.Tools基础公共库(适用于.NET4.6.1/.NET Standard2.0及以上项目),包含一些常用的操作类,大都是静态类,加密解密,反射操作,Excel简单导出,权重随机筛选算法,分布式短id,表达式树,linq扩展,文件压缩,多线程下载和FTP客户端,硬件信息,字符串扩展方法,日期时间扩展操作,中国农历,大文件拷贝,图像裁剪,验证码,断点续传,集合扩展等常用封装。
         <Description>新手友好的C#万能工具库,码数吐司库,Masuit.Tools基础公共库(适用于.NET4.6.1/.NET Standard2.0及以上项目),包含一些常用的操作类,大都是静态类,加密解密,反射操作,Excel简单导出,权重随机筛选算法,分布式短id,表达式树,linq扩展,文件压缩,多线程下载和FTP客户端,硬件信息,字符串扩展方法,日期时间扩展操作,中国农历,大文件拷贝,图像裁剪,验证码,断点续传,集合扩展等常用封装。
             官网教程:https://tools.masuit.org
             官网教程:https://tools.masuit.org
@@ -48,11 +48,11 @@
     <ItemGroup>
     <ItemGroup>
         <PackageReference Include="Castle.Core" Version="5.1.1" />
         <PackageReference Include="Castle.Core" Version="5.1.1" />
         <PackageReference Include="DnsClient" Version="1.7.0" />
         <PackageReference Include="DnsClient" Version="1.7.0" />
-        <PackageReference Include="HtmlSanitizer" Version="8.0.645" />
+        <PackageReference Include="HtmlSanitizer" Version="8.0.692" />
         <PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
         <PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
         <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0" />
         <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0" />
         <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
         <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
-        <PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta15" />
+        <PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0" />
         <PackageReference Include="System.Configuration.ConfigurationManager" Version="7.0.0" />
         <PackageReference Include="System.Configuration.ConfigurationManager" Version="7.0.0" />
         <PackageReference Include="System.Management" Version="7.0.2" />
         <PackageReference Include="System.Management" Version="7.0.2" />
         <PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.7.0" />
         <PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.7.0" />
@@ -60,35 +60,29 @@
     </ItemGroup>
     </ItemGroup>
 
 
     <ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0'">
     <ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0'">
-        <PackageReference Include="SixLabors.ImageSharp" Version="[2.1.3]" />
         <PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
         <PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
         <PackageReference Include="System.Diagnostics.PerformanceCounter" Version="7.0.0" />
         <PackageReference Include="System.Diagnostics.PerformanceCounter" Version="7.0.0" />
     </ItemGroup>
     </ItemGroup>
 
 
     <ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.1'">
     <ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.1'">
-        <PackageReference Include="SixLabors.ImageSharp" Version="[2.1.3]" />
         <PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
         <PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
         <PackageReference Include="System.Diagnostics.PerformanceCounter" Version="7.0.0" />
         <PackageReference Include="System.Diagnostics.PerformanceCounter" Version="7.0.0" />
     </ItemGroup>
     </ItemGroup>
 
 
     <ItemGroup Condition=" '$(TargetFramework)' == 'net5'">
     <ItemGroup Condition=" '$(TargetFramework)' == 'net5'">
-        <PackageReference Include="SixLabors.ImageSharp" Version="[2.1.3]" />
         <PackageReference Include="System.Diagnostics.PerformanceCounter" Version="7.0.0" />
         <PackageReference Include="System.Diagnostics.PerformanceCounter" Version="7.0.0" />
     </ItemGroup>
     </ItemGroup>
 
 
     <ItemGroup Condition=" '$(TargetFramework)' == 'net6'">
     <ItemGroup Condition=" '$(TargetFramework)' == 'net6'">
-        <PackageReference Include="SixLabors.ImageSharp" Version="3.0.1" />
         <PackageReference Include="System.Diagnostics.PerformanceCounter" Version="7.0.0" />
         <PackageReference Include="System.Diagnostics.PerformanceCounter" Version="7.0.0" />
     </ItemGroup>
     </ItemGroup>
 
 
     <ItemGroup Condition=" '$(TargetFramework)' == 'net7'">
     <ItemGroup Condition=" '$(TargetFramework)' == 'net7'">
-        <PackageReference Include="SixLabors.ImageSharp" Version="3.0.1" />
         <PackageReference Include="System.Diagnostics.PerformanceCounter" Version="7.0.0" />
         <PackageReference Include="System.Diagnostics.PerformanceCounter" Version="7.0.0" />
     </ItemGroup>
     </ItemGroup>
 
 
     <ItemGroup Condition=" '$(TargetFramework)' == 'net461' ">
     <ItemGroup Condition=" '$(TargetFramework)' == 'net461' ">
         <Reference Include="System.Web" />
         <Reference Include="System.Web" />
-        <PackageReference Include="SixLabors.ImageSharp" Version="[2.1.3]" />
         <PackageReference Include="System.Buffers" version="[4.5.1]" targetFramework="net461" />
         <PackageReference Include="System.Buffers" version="[4.5.1]" targetFramework="net461" />
         <PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
         <PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
         <PackageReference Include="System.Diagnostics.PerformanceCounter" Version="[4.5]" />
         <PackageReference Include="System.Diagnostics.PerformanceCounter" Version="[4.5]" />

+ 204 - 204
Masuit.Tools.Abstractions/Media/ImageWatermarker.cs

@@ -10,208 +10,208 @@ using SixLabors.ImageSharp.Formats;
 
 
 namespace Masuit.Tools.Media
 namespace Masuit.Tools.Media
 {
 {
-    public class ImageWatermarker
-    {
-        /// <summary>
-        /// 是否跳过小缩略图
-        /// </summary>
-        public bool SkipWatermarkForSmallImages { get; set; }
-
-        /// <summary>
-        /// 小图像素大小
-        /// </summary>
-        public int SmallImagePixelsThreshold { get; set; }
-
-        public IImageEncoder ImageEncoder { get; set; }
-
-        private readonly Stream _stream;
-
-        /// <summary>
-        ///
-        /// </summary>
-        /// <param name="originStream">图片流</param>
-        public ImageWatermarker(Stream originStream)
-        {
-            _stream = originStream;
-        }
-
-        /// <summary>
-        ///
-        /// </summary>
-        /// <param name="originStream">图片流</param>
-        /// <param name="encoder">指定编码器</param>
-        public ImageWatermarker(Stream originStream, IImageEncoder encoder) : this(originStream)
-        {
-            ImageEncoder = encoder;
-        }
-
-        /// <summary>
-        ///
-        /// </summary>
-        /// <param name="originStream">图片流</param>
-        /// <param name="encoder">指定编码器</param>
-        /// <param name="skipWatermarkForSmallImages">是否跳过小缩略图</param>
-        /// <param name="smallImagePixelsThreshold">小图像素大小</param>
-        public ImageWatermarker(Stream originStream, IImageEncoder encoder, bool skipWatermarkForSmallImages, int smallImagePixelsThreshold) : this(originStream, encoder)
-        {
-            SkipWatermarkForSmallImages = skipWatermarkForSmallImages;
-            SmallImagePixelsThreshold = smallImagePixelsThreshold;
-        }
-
-        /// <summary>
-        /// 添加水印
-        /// </summary>
-        /// <param name="watermarkText">水印文字</param>
-        /// <param name="ttfFontPath">ttf字体文件路径</param>
-        /// <param name="fontSize">字体大小</param>
-        /// <param name="color">字体颜色</param>
-        /// <param name="watermarkPosition">水印位置</param>
-        /// <param name="textPadding">水印边距</param>
-        /// <returns></returns>
-        public PooledMemoryStream AddWatermark(string watermarkText, string ttfFontPath, int fontSize, Color color, WatermarkPosition watermarkPosition = WatermarkPosition.BottomRight, int textPadding = 10)
-        {
-            var fonts = new FontCollection();
-            var fontFamily = fonts.Add(ttfFontPath); //字体的路径(电脑自带字体库,去copy出来)
-            var font = new Font(fontFamily, fontSize, FontStyle.Bold);
-            return AddWatermark(watermarkText, font, color, watermarkPosition, textPadding);
-        }
-
-        /// <summary>
-        /// 添加水印
-        /// </summary>
-        /// <param name="watermarkText">水印文字</param>
-        /// <param name="color">水印颜色</param>
-        /// <param name="watermarkPosition">水印位置</param>
-        /// <param name="textPadding">边距</param>
-        /// <param name="font">字体</param>
-        public PooledMemoryStream AddWatermark(string watermarkText, Font font, Color color, WatermarkPosition watermarkPosition = WatermarkPosition.BottomRight, int textPadding = 10)
-        {
-            var imageFormat = Image.DetectFormat(_stream);
-            _stream.Seek(0, SeekOrigin.Begin);
-            using var img = Image.Load(_stream);
-            var textMeasure = TextMeasurer.Measure(watermarkText, new TextOptions(font));
-            if (SkipWatermarkForSmallImages && (img.Height < Math.Sqrt(SmallImagePixelsThreshold) || img.Width < Math.Sqrt(SmallImagePixelsThreshold) || img.Width <= textMeasure.Width))
-            {
-                return _stream as PooledMemoryStream ?? _stream.SaveAsMemoryStream();
-            }
-
-            if (img.Width / font.Size > 50)
-            {
-                font = font.Family.CreateFont(img.Width * 1f / 50);
-            }
-
-            float x, y;
-            textPadding += (img.Width - 1000) / 100;
-            switch (watermarkPosition)
-            {
-                case WatermarkPosition.TopRight:
-                    x = img.Width - textMeasure.Width - textPadding;
-                    y = textPadding;
-                    break;
-
-                case WatermarkPosition.BottomLeft:
-                    x = textPadding;
-                    y = img.Height - textMeasure.Height - textPadding;
-                    break;
-
-                case WatermarkPosition.BottomRight:
-                    x = img.Width - textMeasure.Width - textPadding;
-                    y = img.Height - textMeasure.Height - textPadding;
-                    break;
-
-                case WatermarkPosition.Center:
-                    x = (img.Width - textMeasure.Width) / 2;
-                    y = (img.Height - textMeasure.Height) / 2;
-                    break;
-
-                default:
-                    x = textPadding;
-                    y = textPadding;
-                    break;
-            }
-
-            img.Mutate(c => c.DrawText(watermarkText, font, color, new PointF(x, y)));
-            var ms = new PooledMemoryStream();
-            if (ImageEncoder == null)
-            {
-                img.Save(ms, imageFormat);
-            }
-            else
-            {
-                img.Save(ms, ImageEncoder);
-            }
-            ms.Position = 0;
-            return ms;
-        }
-
-        /// <summary>
-        /// 添加水印
-        /// </summary>
-        /// <param name="watermarkImage">水印图片</param>
-        /// <param name="opacity">水印图片</param>
-        /// <param name="watermarkPosition">水印位置</param>
-        /// <param name="padding">水印边距</param>
-        /// <returns></returns>
-        public PooledMemoryStream AddWatermark(Stream watermarkImage, float opacity = 1f, WatermarkPosition watermarkPosition = WatermarkPosition.BottomRight, int padding = 20)
-        {
-            using var img = Image.Load(_stream);
-            var height = img.Height;
-            var width = img.Width;
-            if (SkipWatermarkForSmallImages && (height < Math.Sqrt(SmallImagePixelsThreshold) || width < Math.Sqrt(SmallImagePixelsThreshold)))
-            {
-                return _stream as PooledMemoryStream ?? _stream.SaveAsMemoryStream();
-            }
-
-            var watermark = Image.Load(watermarkImage);
-            watermark.Mutate(c => c.Resize(new ResizeOptions()
-            {
-                Size = new Size
-                {
-                    Width = width / 10,
-                    Height = height / 10,
-                },
-                Mode = ResizeMode.Pad,
-                Sampler = new BicubicResampler()
-            }));
-            int x, y;
-            padding += (width - 1000) / 100;
-            switch (watermarkPosition)
-            {
-                case WatermarkPosition.TopRight:
-                    x = width - watermark.Width - padding;
-                    y = padding;
-                    break;
-
-                case WatermarkPosition.BottomLeft:
-                    x = padding;
-                    y = height - watermark.Height - padding;
-                    break;
-
-                case WatermarkPosition.BottomRight:
-                    x = width - watermark.Width - padding;
-                    y = height - watermark.Height - padding;
-                    break;
-
-                case WatermarkPosition.Center:
-                    x = (img.Width - watermark.Width) / 2;
-                    y = (img.Height - watermark.Height) / 2;
-                    break;
-
-                default:
-                    x = padding;
-                    y = padding;
-                    break;
-            }
-
-            img.Mutate(c =>
-            {
-                c.DrawImage(watermark, new Point(x, y), opacity);
-                watermark.Dispose();
-            });
-            var ms = new PooledMemoryStream();
-            img.SaveAsWebp(ms);
-            ms.Position = 0;
-            return ms;
-        }
-    }
+	public class ImageWatermarker
+	{
+		/// <summary>
+		/// 是否跳过小缩略图
+		/// </summary>
+		public bool SkipWatermarkForSmallImages { get; set; }
+
+		/// <summary>
+		/// 小图像素大小
+		/// </summary>
+		public int SmallImagePixelsThreshold { get; set; }
+
+		public IImageEncoder ImageEncoder { get; set; }
+
+		private readonly Stream _stream;
+
+		/// <summary>
+		///
+		/// </summary>
+		/// <param name="originStream">图片流</param>
+		public ImageWatermarker(Stream originStream)
+		{
+			_stream = originStream;
+		}
+
+		/// <summary>
+		///
+		/// </summary>
+		/// <param name="originStream">图片流</param>
+		/// <param name="encoder">指定编码器</param>
+		public ImageWatermarker(Stream originStream, IImageEncoder encoder) : this(originStream)
+		{
+			ImageEncoder = encoder;
+		}
+
+		/// <summary>
+		///
+		/// </summary>
+		/// <param name="originStream">图片流</param>
+		/// <param name="encoder">指定编码器</param>
+		/// <param name="skipWatermarkForSmallImages">是否跳过小缩略图</param>
+		/// <param name="smallImagePixelsThreshold">小图像素大小</param>
+		public ImageWatermarker(Stream originStream, IImageEncoder encoder, bool skipWatermarkForSmallImages, int smallImagePixelsThreshold) : this(originStream, encoder)
+		{
+			SkipWatermarkForSmallImages = skipWatermarkForSmallImages;
+			SmallImagePixelsThreshold = smallImagePixelsThreshold;
+		}
+
+		/// <summary>
+		/// 添加水印
+		/// </summary>
+		/// <param name="watermarkText">水印文字</param>
+		/// <param name="ttfFontPath">ttf字体文件路径</param>
+		/// <param name="fontSize">字体大小</param>
+		/// <param name="color">字体颜色</param>
+		/// <param name="watermarkPosition">水印位置</param>
+		/// <param name="textPadding">水印边距</param>
+		/// <returns></returns>
+		public PooledMemoryStream AddWatermark(string watermarkText, string ttfFontPath, int fontSize, Color color, WatermarkPosition watermarkPosition = WatermarkPosition.BottomRight, int textPadding = 10)
+		{
+			var fonts = new FontCollection();
+			var fontFamily = fonts.Add(ttfFontPath); //字体的路径(电脑自带字体库,去copy出来)
+			var font = new Font(fontFamily, fontSize, FontStyle.Bold);
+			return AddWatermark(watermarkText, font, color, watermarkPosition, textPadding);
+		}
+
+		/// <summary>
+		/// 添加水印
+		/// </summary>
+		/// <param name="watermarkText">水印文字</param>
+		/// <param name="color">水印颜色</param>
+		/// <param name="watermarkPosition">水印位置</param>
+		/// <param name="textPadding">边距</param>
+		/// <param name="font">字体</param>
+		public PooledMemoryStream AddWatermark(string watermarkText, Font font, Color color, WatermarkPosition watermarkPosition = WatermarkPosition.BottomRight, int textPadding = 10)
+		{
+			var imageFormat = Image.DetectFormat(_stream);
+			_stream.Seek(0, SeekOrigin.Begin);
+			using var img = Image.Load(_stream);
+			var textMeasure = TextMeasurer.MeasureSize(watermarkText, new TextOptions(font));
+			if (SkipWatermarkForSmallImages && (img.Height < Math.Sqrt(SmallImagePixelsThreshold) || img.Width < Math.Sqrt(SmallImagePixelsThreshold) || img.Width <= textMeasure.Width))
+			{
+				return _stream as PooledMemoryStream ?? _stream.SaveAsMemoryStream();
+			}
+
+			if (img.Width / font.Size > 50)
+			{
+				font = font.Family.CreateFont(img.Width * 1f / 50);
+			}
+
+			float x, y;
+			textPadding += (img.Width - 1000) / 100;
+			switch (watermarkPosition)
+			{
+				case WatermarkPosition.TopRight:
+					x = img.Width - textMeasure.Width - textPadding;
+					y = textPadding;
+					break;
+
+				case WatermarkPosition.BottomLeft:
+					x = textPadding;
+					y = img.Height - textMeasure.Height - textPadding;
+					break;
+
+				case WatermarkPosition.BottomRight:
+					x = img.Width - textMeasure.Width - textPadding;
+					y = img.Height - textMeasure.Height - textPadding;
+					break;
+
+				case WatermarkPosition.Center:
+					x = (img.Width - textMeasure.Width) / 2;
+					y = (img.Height - textMeasure.Height) / 2;
+					break;
+
+				default:
+					x = textPadding;
+					y = textPadding;
+					break;
+			}
+
+			img.Mutate(c => c.DrawText(watermarkText, font, color, new PointF(x, y)));
+			var ms = new PooledMemoryStream();
+			if (ImageEncoder == null)
+			{
+				img.Save(ms, imageFormat);
+			}
+			else
+			{
+				img.Save(ms, ImageEncoder);
+			}
+			ms.Position = 0;
+			return ms;
+		}
+
+		/// <summary>
+		/// 添加水印
+		/// </summary>
+		/// <param name="watermarkImage">水印图片</param>
+		/// <param name="opacity">水印图片</param>
+		/// <param name="watermarkPosition">水印位置</param>
+		/// <param name="padding">水印边距</param>
+		/// <returns></returns>
+		public PooledMemoryStream AddWatermark(Stream watermarkImage, float opacity = 1f, WatermarkPosition watermarkPosition = WatermarkPosition.BottomRight, int padding = 20)
+		{
+			using var img = Image.Load(_stream);
+			var height = img.Height;
+			var width = img.Width;
+			if (SkipWatermarkForSmallImages && (height < Math.Sqrt(SmallImagePixelsThreshold) || width < Math.Sqrt(SmallImagePixelsThreshold)))
+			{
+				return _stream as PooledMemoryStream ?? _stream.SaveAsMemoryStream();
+			}
+
+			var watermark = Image.Load(watermarkImage);
+			watermark.Mutate(c => c.Resize(new ResizeOptions()
+			{
+				Size = new Size
+				{
+					Width = width / 10,
+					Height = height / 10,
+				},
+				Mode = ResizeMode.Pad,
+				Sampler = new BicubicResampler()
+			}));
+			int x, y;
+			padding += (width - 1000) / 100;
+			switch (watermarkPosition)
+			{
+				case WatermarkPosition.TopRight:
+					x = width - watermark.Width - padding;
+					y = padding;
+					break;
+
+				case WatermarkPosition.BottomLeft:
+					x = padding;
+					y = height - watermark.Height - padding;
+					break;
+
+				case WatermarkPosition.BottomRight:
+					x = width - watermark.Width - padding;
+					y = height - watermark.Height - padding;
+					break;
+
+				case WatermarkPosition.Center:
+					x = (img.Width - watermark.Width) / 2;
+					y = (img.Height - watermark.Height) / 2;
+					break;
+
+				default:
+					x = padding;
+					y = padding;
+					break;
+			}
+
+			img.Mutate(c =>
+			{
+				c.DrawImage(watermark, new Point(x, y), opacity);
+				watermark.Dispose();
+			});
+			var ms = new PooledMemoryStream();
+			img.SaveAsWebp(ms);
+			ms.Position = 0;
+			return ms;
+		}
+	}
 }
 }

+ 62 - 27
Masuit.Tools.Abstractions/RandomSelector/Extensions.cs

@@ -1,34 +1,69 @@
 using Masuit.Tools.RandomSelector;
 using Masuit.Tools.RandomSelector;
+using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
 
 
 namespace Masuit.Tools
 namespace Masuit.Tools
 {
 {
-    public static partial class Extensions
-    {
-        public static int TotalWeight<T>(this WeightedSelector<T> selector)
-        {
-            return selector.Items.Count == 0 ? 0 : selector.Items.Sum(t => t.Weight);
-        }
-
-        public static List<WeightedItem<T>> OrderByWeightDescending<T>(this WeightedSelector<T> selector)
-        {
-            return selector.Items.OrderByDescending(item => item.Weight).ToList();
-        }
-
-        public static List<WeightedItem<T>> OrderByWeightAscending<T>(this WeightedSelector<T> selector)
-        {
-            return selector.Items.OrderBy(item => item.Weight).ToList();
-        }
-
-        public static T WeightedItem<T>(this IEnumerable<WeightedItem<T>> list)
-        {
-            return new WeightedSelector<T>(list).Select();
-        }
-
-        public static List<T> WeightedItems<T>(this IEnumerable<WeightedItem<T>> list, int count)
-        {
-            return new WeightedSelector<T>(list).SelectMultiple(count);
-        }
-    }
+	public static partial class Extensions
+	{
+		public static int TotalWeight<T>(this WeightedSelector<T> selector)
+		{
+			return selector.Items.Count == 0 ? 0 : selector.Items.Sum(t => t.Weight);
+		}
+
+		public static List<WeightedItem<T>> OrderByWeightDescending<T>(this WeightedSelector<T> selector)
+		{
+			return selector.Items.OrderByDescending(item => item.Weight).ToList();
+		}
+
+		public static List<WeightedItem<T>> OrderByWeightAscending<T>(this WeightedSelector<T> selector)
+		{
+			return selector.Items.OrderBy(item => item.Weight).ToList();
+		}
+
+		public static T WeightedItem<T>(this IEnumerable<WeightedItem<T>> list)
+		{
+			return new WeightedSelector<T>(list).Select();
+		}
+
+		public static IEnumerable<T> WeightedItems<T>(this IEnumerable<WeightedItem<T>> list, int count)
+		{
+			return new WeightedSelector<T>(list).SelectMultiple(count);
+		}
+
+		/// <summary>
+		/// 执行权重筛选,取多个元素
+		/// </summary>
+		/// <typeparam name="T"></typeparam>
+		/// <param name="source">原始数据</param>
+		/// <param name="count">目标个数</param>
+		/// <param name="keySelector">按哪个属性进行权重筛选</param>
+		/// <param name="option">抽取选项</param>
+		/// <returns></returns>
+		public static IEnumerable<T> WeightedItems<T>(this IEnumerable<T> source, int count, Func<T, int> keySelector, SelectorOption option = null)
+		{
+			if (!source.Any())
+			{
+				return source;
+			}
+
+			var selector = new WeightedSelector<T>(source.Select(t => new WeightedItem<T>(t, keySelector(t))), option);
+			return selector.SelectMultiple(count);
+		}
+
+		/// <summary>
+		/// 执行权重筛选,取1个元素
+		/// </summary>
+		/// <typeparam name="T"></typeparam>
+		/// <param name="source">原始数据</param>
+		/// <param name="keySelector">按哪个属性进行权重筛选</param>
+		/// <param name="option">抽取选项</param>
+		/// <returns></returns>
+		public static T WeightedBy<T>(this IEnumerable<T> source, Func<T, int> keySelector, SelectorOption option = null)
+		{
+			var selector = new WeightedSelector<T>(source.Select(t => new WeightedItem<T>(t, keySelector(t))), option);
+			return selector.Select();
+		}
+	}
 }
 }

+ 43 - 43
Masuit.Tools.Abstractions/RandomSelector/MultipleSelector.cs

@@ -3,52 +3,52 @@ using System.Collections.Generic;
 
 
 namespace Masuit.Tools.RandomSelector
 namespace Masuit.Tools.RandomSelector
 {
 {
-    /// <summary>
-    /// 多选器
-    /// </summary>
-    /// <typeparam name="T"></typeparam>
-    internal class MultipleSelector<T> : SelectorBase<T>
-    {
-        internal MultipleSelector(WeightedSelector<T> weightedSelector) : base(weightedSelector)
-        {
-        }
+	/// <summary>
+	/// 多选器
+	/// </summary>
+	/// <typeparam name="T"></typeparam>
+	internal class MultipleSelector<T> : SelectorBase<T>
+	{
+		internal MultipleSelector(WeightedSelector<T> weightedSelector) : base(weightedSelector)
+		{
+		}
 
 
-        internal List<T> Select(int count)
-        {
-            Validate(ref count);
-            var items = new List<WeightedItem<T>>(WeightedSelector.Items);
-            var resultList = new List<T>();
+		internal IEnumerable<T> Select(int count)
+		{
+			Validate(ref count);
+			var items = new List<WeightedItem<T>>(WeightedSelector.Items);
+			int result = 0;
+			do
+			{
+				var item = WeightedSelector.Option.AllowDuplicate ? BinarySelect(items) : LinearSelect(items);
+				yield return item.Value;
+				result++;
+				if (!WeightedSelector.Option.AllowDuplicate)
+				{
+					items.Remove(item);
+				}
+			} while (result < count);
+		}
 
 
-            do
-            {
-                var item = WeightedSelector.Option.AllowDuplicate ? BinarySelect(items) : LinearSelect(items);
-                resultList.Add(item.Value);
-                if (!WeightedSelector.Option.AllowDuplicate)
-                {
-                    items.Remove(item);
-                }
-            } while (resultList.Count < count);
-            return resultList;
-        }
+		private void Validate(ref int count)
+		{
+			if (count <= 0)
+			{
+				throw new InvalidOperationException("筛选个数必须大于0");
+			}
 
 
-        private void Validate(ref int count)
-        {
-            if (count <= 0)
-            {
-                throw new InvalidOperationException("筛选个数必须大于0");
-            }
+			var items = WeightedSelector.Items;
 
 
-            var items = WeightedSelector.Items;
+			if (items.Count == 0)
+			{
+				count = 0;
+				return;
+			}
 
 
-            if (items.Count == 0)
-            {
-                throw new InvalidOperationException("没有元素可以被筛选");
-            }
-
-            if (!WeightedSelector.Option.AllowDuplicate && items.Count < count)
-            {
-                count = items.Count;
-            }
-        }
-    }
+			if (!WeightedSelector.Option.AllowDuplicate && items.Count < count)
+			{
+				count = items.Count;
+			}
+		}
+	}
 }
 }

+ 57 - 56
Masuit.Tools.Abstractions/RandomSelector/SelectorBase.cs

@@ -4,60 +4,61 @@ using System.Linq;
 
 
 namespace Masuit.Tools.RandomSelector
 namespace Masuit.Tools.RandomSelector
 {
 {
-    internal abstract class SelectorBase<T>
-    {
-        protected readonly WeightedSelector<T> WeightedSelector;
-
-        internal SelectorBase(WeightedSelector<T> weightedSelector)
-        {
-            WeightedSelector = weightedSelector;
-        }
-
-        /// <summary>
-        /// 执行二进制筛选
-        /// </summary>
-        internal WeightedItem<T> BinarySelect(List<WeightedItem<T>> items)
-        {
-            if (items.Count == 0)
-            {
-                throw new InvalidOperationException("没有元素可以筛选");
-            }
-
-            int index = Array.BinarySearch(WeightedSelector.CumulativeWeights, new Random().Next(1, items.Sum(i => i.Weight) + 1));
-            //如果存在接近的匹配项,二进制搜索返回的负数会比搜索的第1个索引少1。
-            if (index < 0)
-            {
-                index = -index - 1;
-            }
-
-            return items[index];
-        }
-
-        /// <summary>
-        /// 线性筛选
-        /// </summary>
-        /// <param name="items"></param>
-        /// <returns></returns>
-        internal WeightedItem<T> LinearSelect(List<WeightedItem<T>> items)
-        {
-            // 只对具有允许重复项的多选功能有用,它会随着时间从列表中删除项目。 在这些条件下没有消耗更多性能让二进制搜索起作用。
-            if (!items.Any())
-            {
-                throw new InvalidOperationException("没有元素可以筛选");
-            }
-
-            var count = 0;
-            var seed = new Random().Next(1, items.Sum(i => i.Weight) + 1);
-            foreach (var item in items)
-            {
-                count += item.Weight;
-                if (seed <= count)
-                {
-                    return item;
-                }
-            }
-
-            return items.FirstOrDefault();
-        }
-    }
+	internal abstract class SelectorBase<T>
+	{
+		protected readonly WeightedSelector<T> WeightedSelector;
+
+		internal SelectorBase(WeightedSelector<T> weightedSelector)
+		{
+			WeightedSelector = weightedSelector;
+		}
+
+		/// <summary>
+		/// 执行二进制筛选
+		/// </summary>
+		internal WeightedItem<T> BinarySelect(List<WeightedItem<T>> items)
+		{
+			if (items.Count == 0)
+			{
+				throw new InvalidOperationException("没有元素可以筛选");
+			}
+
+			int index = Array.BinarySearch(WeightedSelector.CumulativeWeights, new Random().Next(1, items.Sum(i => i.Weight) + 1));
+
+			//如果存在接近的匹配项,二进制搜索返回的负数会比搜索的第1个索引少1。
+			if (index < 0)
+			{
+				index = -index - 1;
+			}
+
+			return items[index];
+		}
+
+		/// <summary>
+		/// 线性筛选
+		/// </summary>
+		/// <param name="items"></param>
+		/// <returns></returns>
+		internal WeightedItem<T> LinearSelect(List<WeightedItem<T>> items)
+		{
+			// 只对具有允许重复项的多选功能有用,它会随着时间从列表中删除项目。 在这些条件下没有消耗更多性能让二进制搜索起作用。
+			if (!items.Any())
+			{
+				return new WeightedItem<T>(default(T), 0);
+			}
+
+			var count = 0;
+			var seed = new Random().Next(1, items.Sum(i => i.Weight) + 1);
+			foreach (var item in items)
+			{
+				count += item.Weight;
+				if (seed <= count)
+				{
+					return item;
+				}
+			}
+
+			return items.FirstOrDefault();
+		}
+	}
 }
 }

+ 18 - 18
Masuit.Tools.Abstractions/RandomSelector/SingleSelector.cs

@@ -2,24 +2,24 @@
 
 
 namespace Masuit.Tools.RandomSelector
 namespace Masuit.Tools.RandomSelector
 {
 {
-    /// <summary>
-    /// 单选器
-    /// </summary>
-    /// <typeparam name="T"></typeparam>
-    internal class SingleSelector<T> : SelectorBase<T>
-    {
-        internal SingleSelector(WeightedSelector<T> weightedSelector) : base(weightedSelector)
-        {
-        }
+	/// <summary>
+	/// 单选器
+	/// </summary>
+	/// <typeparam name="T"></typeparam>
+	internal class SingleSelector<T> : SelectorBase<T>
+	{
+		internal SingleSelector(WeightedSelector<T> weightedSelector) : base(weightedSelector)
+		{
+		}
 
 
-        internal T Select()
-        {
-            if (WeightedSelector.Items.Count == 0)
-            {
-                throw new InvalidOperationException("没有元素可以筛选");
-            }
+		internal T Select()
+		{
+			if (WeightedSelector.Items.Count == 0)
+			{
+				return default(T);
+			}
 
 
-            return BinarySelect(WeightedSelector.Items).Value;
-        }
-    }
+			return BinarySelect(WeightedSelector.Items).Value;
+		}
+	}
 }
 }

+ 2 - 1
Masuit.Tools.Abstractions/RandomSelector/WeightedSelector.cs

@@ -103,7 +103,7 @@ namespace Masuit.Tools.RandomSelector
         /// <summary>
         /// <summary>
         /// 执行权重筛选,取多个元素
         /// 执行权重筛选,取多个元素
         /// </summary>
         /// </summary>
-        public List<T> SelectMultiple(int count)
+        public IEnumerable<T> SelectMultiple(int count)
         {
         {
             CalculateCumulativeWeights();
             CalculateCumulativeWeights();
             var selector = new MultipleSelector<T>(this);
             var selector = new MultipleSelector<T>(this);
@@ -145,6 +145,7 @@ namespace Masuit.Tools.RandomSelector
 
 
             return results;
             return results;
         }
         }
+
         public IEnumerator<T> GetEnumerator()
         public IEnumerator<T> GetEnumerator()
         {
         {
             return Items.GetEnumerator() as IEnumerator<T>;
             return Items.GetEnumerator() as IEnumerator<T>;

+ 109 - 108
Masuit.Tools.Abstractions/Strings/ValidateCode.cs

@@ -5,6 +5,7 @@ using System.Text;
 using Masuit.Tools.Systems;
 using Masuit.Tools.Systems;
 using SixLabors.Fonts;
 using SixLabors.Fonts;
 using SixLabors.ImageSharp;
 using SixLabors.ImageSharp;
+using SixLabors.ImageSharp.Drawing;
 using SixLabors.ImageSharp.PixelFormats;
 using SixLabors.ImageSharp.PixelFormats;
 using SixLabors.ImageSharp.Processing;
 using SixLabors.ImageSharp.Processing;
 using SixLabors.ImageSharp.Drawing.Processing;
 using SixLabors.ImageSharp.Drawing.Processing;
@@ -12,126 +13,126 @@ using SixLabors.ImageSharp.Formats.Webp;
 
 
 namespace Masuit.Tools.Strings
 namespace Masuit.Tools.Strings
 {
 {
-    /// <summary>
-    /// 画验证码
-    /// </summary>
-    public static class ValidateCode
-    {
-        /// <summary>
-        /// 生成验证码
-        /// </summary>
-        /// <param name="length">指定验证码的长度</param>
-        /// <returns>验证码字符串</returns>
-        public static string CreateValidateCode(int length)
-        {
-            string ch = "abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ1234567890@#$%&?";
-            byte[] b = new byte[4];
-            using var cpt = RandomNumberGenerator.Create();
-            cpt.GetBytes(b);
-            var r = new Random(BitConverter.ToInt32(b, 0));
-            var sb = new StringBuilder();
-            for (int i = 0; i < length; i++)
-            {
-                sb.Append(ch[r.StrictNext(ch.Length)]);
-            }
+	/// <summary>
+	/// 画验证码
+	/// </summary>
+	public static class ValidateCode
+	{
+		/// <summary>
+		/// 生成验证码
+		/// </summary>
+		/// <param name="length">指定验证码的长度</param>
+		/// <returns>验证码字符串</returns>
+		public static string CreateValidateCode(int length)
+		{
+			string ch = "abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ1234567890@#$%&?";
+			byte[] b = new byte[4];
+			using var cpt = RandomNumberGenerator.Create();
+			cpt.GetBytes(b);
+			var r = new Random(BitConverter.ToInt32(b, 0));
+			var sb = new StringBuilder();
+			for (int i = 0; i < length; i++)
+			{
+				sb.Append(ch[r.StrictNext(ch.Length)]);
+			}
 
 
-            return sb.ToString();
-        }
+			return sb.ToString();
+		}
 
 
-        /// <summary>
-        /// 创建验证码的图片
-        /// </summary>
-        /// <param name="validateCode">验证码序列</param>
-        /// <param name="fontSize">字体大小,默认值28px</param>
-        /// <exception cref="Exception">The operation failed.</exception>
-        public static PooledMemoryStream CreateValidateGraphic(this string validateCode, int fontSize = 28)
-        {
-            var fontFamily = SystemFonts.Families.Where(f => new[] { "Consolas", "DejaVu Sans", "KaiTi", "NSimSun", "SimSun", "SimHei", "Microsoft YaHei UI", "Arial" }.Contains(f.Name)).OrderByRandom().FirstOrDefault();
-            if (fontFamily == default)
-            {
-                fontFamily = SystemFonts.Families.OrderByRandom().FirstOrDefault();
-            }
+		/// <summary>
+		/// 创建验证码的图片
+		/// </summary>
+		/// <param name="validateCode">验证码序列</param>
+		/// <param name="fontSize">字体大小,默认值28px</param>
+		/// <exception cref="Exception">The operation failed.</exception>
+		public static PooledMemoryStream CreateValidateGraphic(this string validateCode, int fontSize = 28)
+		{
+			var fontFamily = SystemFonts.Families.Where(f => new[] { "Consolas", "DejaVu Sans", "KaiTi", "NSimSun", "SimSun", "SimHei", "Microsoft YaHei UI", "Arial" }.Contains(f.Name)).OrderByRandom().FirstOrDefault();
+			if (fontFamily == default)
+			{
+				fontFamily = SystemFonts.Families.OrderByRandom().FirstOrDefault();
+			}
 
 
-            var font = fontFamily.CreateFont(fontSize);
-            var measure = TextMeasurer.Measure(validateCode, new TextOptions(font));
-            var width = (int)Math.Ceiling(measure.Width * 1.5);
-            var height = (int)Math.Ceiling(measure.Height + 5);
-            using var image = new Image<Rgba32>(width, height);
+			var font = fontFamily.CreateFont(fontSize);
+			var measure = TextMeasurer.MeasureSize(validateCode, new TextOptions(font));
+			var width = (int)Math.Ceiling(measure.Width + 15);
+			var height = (int)Math.Ceiling(measure.Height + 15);
+			using var image = new Image<Rgba32>(width, height);
 
 
-            //生成随机生成器
-            Random random = new Random();
+			//生成随机生成器
+			Random random = new Random();
 
 
-            //清空图片背景色
-            image.Mutate(g =>
-            {
-                g.BackgroundColor(Color.White);
+			//清空图片背景色
+			image.Mutate(g =>
+			{
+				g.BackgroundColor(Color.White);
 
 
-                //画图片的干扰线
-                for (int i = 0; i < 75; i++)
-                {
-                    int x1 = random.StrictNext(width);
-                    int x2 = random.StrictNext(width);
-                    int y1 = random.StrictNext(height);
-                    int y2 = random.StrictNext(height);
-                    g.DrawLines(new Pen(new Color(new Rgba32((byte)random.StrictNext(255), (byte)random.StrictNext(255), (byte)random.StrictNext(255))), 1), new PointF(x1, y1), new PointF(x2, y2));
-                }
+				//画图片的干扰线
+				for (int i = 0; i < 65; i++)
+				{
+					int x1 = random.StrictNext(width);
+					int x2 = random.StrictNext(width);
+					int y1 = random.StrictNext(height);
+					int y2 = random.StrictNext(height);
+					g.Draw(new SolidPen(new Color(new Rgba32((byte)random.StrictNext(255), (byte)random.StrictNext(255), (byte)random.StrictNext(255))), 1), new Path(new[] { new PointF(x1, y1), new PointF(x2, y2) }));
+				}
 
 
-                //渐变.
-                var brush = new LinearGradientBrush(new PointF(0, 0), new PointF(width, height), GradientRepetitionMode.Repeat, new ColorStop(0.5f, Color.Blue), new ColorStop(0.5f, Color.DarkRed));
-                g.DrawText(validateCode, font, brush, new PointF(3, 2));
+				//渐变.
+				var brush = new LinearGradientBrush(new PointF(0, 0), new PointF(width, height), GradientRepetitionMode.Repeat, new ColorStop(0.5f, Color.Blue), new ColorStop(0.5f, Color.DarkRed));
+				g.DrawText(validateCode, font, brush, new PointF(3, 2));
 
 
-                //画图片的边框线
-                g.DrawLines(new Pen(Color.Silver, 1), new PointF(0, 0), new PointF(width - 1, height - 1));
-            });
+				//画图片的边框线
+				g.Draw(Color.Silver, 1, new RectangleF(0, 0, width - 1, height - 1));
+			});
 
 
-            //画图片的前景干扰点
-            for (int i = 0; i < 350; i++)
-            {
-                int x = random.StrictNext(image.Width);
-                int y = random.StrictNext(image.Height);
-                image[x, y] = new Rgba32(random.StrictNext(255), random.StrictNext(255), random.StrictNext(255));
-            }
+			//画图片的前景干扰点
+			for (int i = 0; i < 350; i++)
+			{
+				int x = random.StrictNext(image.Width);
+				int y = random.StrictNext(image.Height);
+				image[x, y] = new Rgba32(random.StrictNext(255), random.StrictNext(255), random.StrictNext(255));
+			}
 
 
-            //保存图片数据
-            var stream = new PooledMemoryStream();
-            image.Save(stream, WebpFormat.Instance);
-            stream.Position = 0;
-            return stream;
-        }
+			//保存图片数据
+			var stream = new PooledMemoryStream();
+			image.Save(stream, WebpFormat.Instance);
+			stream.Position = 0;
+			return stream;
+		}
 
 
-        /// <summary>
-        /// 字符串宽度
-        /// </summary>
-        /// <param name="s"></param>
-        /// <param name="fontSize"></param>
-        /// <returns></returns>
-        public static float StringWidth(this string s, int fontSize = 1)
-        {
-            var fontFamily = SystemFonts.Families.FirstOrDefault(f => f.Name == "Microsoft YaHei UI");
-            if (fontFamily == default)
-            {
-                fontFamily = SystemFonts.Families.FirstOrDefault();
-            }
+		/// <summary>
+		/// 字符串宽度
+		/// </summary>
+		/// <param name="s"></param>
+		/// <param name="fontSize"></param>
+		/// <returns></returns>
+		public static float StringWidth(this string s, int fontSize = 1)
+		{
+			var fontFamily = SystemFonts.Families.FirstOrDefault(f => f.Name == "Microsoft YaHei UI");
+			if (fontFamily == default)
+			{
+				fontFamily = SystemFonts.Families.FirstOrDefault();
+			}
 
 
-            return TextMeasurer.Measure(s, new TextOptions(fontFamily.CreateFont(fontSize))).Width;
-        }
+			return TextMeasurer.MeasureSize(s, new TextOptions(fontFamily.CreateFont(fontSize))).Width;
+		}
 
 
-        /// <summary>
-        /// 字符串宽度
-        /// </summary>
-        /// <param name="s"></param>
-        /// <param name="fontName">字体名字,如:Microsoft YaHei UI</param>
-        /// <param name="fontSize"></param>
-        /// <returns></returns>
-        public static float StringWidth(this string s, string fontName, int fontSize = 1)
-        {
-            var fontFamily = SystemFonts.Families.FirstOrDefault(f => f.Name == fontName);
-            if (fontFamily == default)
-            {
-                throw new ArgumentException($"字体 {fontName} 不存在,请尝试其它字体!");
-            }
+		/// <summary>
+		/// 字符串宽度
+		/// </summary>
+		/// <param name="s"></param>
+		/// <param name="fontName">字体名字,如:Microsoft YaHei UI</param>
+		/// <param name="fontSize"></param>
+		/// <returns></returns>
+		public static float StringWidth(this string s, string fontName, int fontSize = 1)
+		{
+			var fontFamily = SystemFonts.Families.FirstOrDefault(f => f.Name == fontName);
+			if (fontFamily == default)
+			{
+				throw new ArgumentException($"字体 {fontName} 不存在,请尝试其它字体!");
+			}
 
 
-            return TextMeasurer.Measure(s, new TextOptions(fontFamily.CreateFont(fontSize))).Width;
-        }
-    }
+			return TextMeasurer.MeasureSize(s, new TextOptions(fontFamily.CreateFont(fontSize))).Width;
+		}
+	}
 }
 }

+ 1 - 1
Masuit.Tools.Abstractions/Systems/DeserializeOnlyJsonPropertyAttribute.cs

@@ -11,7 +11,7 @@ public class DeserializeOnlyJsonPropertyAttribute : Attribute
 }
 }
 
 
 /// <summary>
 /// <summary>
-/// 只允许序列化
+/// 只允许序列化
 /// </summary>
 /// </summary>
 [AttributeUsage(AttributeTargets.Property)]
 [AttributeUsage(AttributeTargets.Property)]
 public class SerializeOnlyJsonPropertyAttribute : Attribute
 public class SerializeOnlyJsonPropertyAttribute : Attribute

+ 203 - 245
Masuit.Tools.Abstractions/Systems/EnumExt.cs

@@ -9,249 +9,207 @@ using System.Reflection;
 
 
 namespace Masuit.Tools.Systems
 namespace Masuit.Tools.Systems
 {
 {
-	/// <summary>
-	/// 枚举扩展类
-	/// </summary>
-	public static partial class EnumExt
-	{
-		private static readonly ConcurrentDictionary<Type, Dictionary<int, string>> EnumNameValueDict = new ConcurrentDictionary<Type, Dictionary<int, string>>();
-		private static readonly ConcurrentDictionary<Type, Dictionary<string, int>> EnumValueNameDict = new ConcurrentDictionary<Type, Dictionary<string, int>>();
-		private static ConcurrentDictionary<string, Type> _enumTypeDict;
-
-		/// <summary>
-		/// 获取枚举对象Key与显示名称的字典
-		/// </summary>
-		/// <param name="enumType"></param>
-		/// <returns></returns>
-		public static Dictionary<int, string> GetDictionary(this Type enumType)
-		{
-			if (!enumType.IsEnum)
-			{
-				throw new Exception("给定的类型不是枚举类型");
-			}
-
-			var names = EnumNameValueDict.ContainsKey(enumType) ? EnumNameValueDict[enumType] : new Dictionary<int, string>();
-			if (names.Count == 0)
-			{
-				names = GetDictionaryItems(enumType);
-				EnumNameValueDict[enumType] = names;
-			}
-
-			return names;
-		}
-
-		private static Dictionary<int, string> GetDictionaryItems(Type enumType)
-		{
-			var enumItems = enumType.GetFields(BindingFlags.Public | BindingFlags.Static);
-			var names = new Dictionary<int, string>(enumItems.Length);
-			foreach (FieldInfo enumItem in enumItems)
-			{
-				int intValue = (int)enumItem.GetValue(enumType);
-				names[intValue] = enumItem.Name;
-			}
-
-			return names;
-		}
-
-		/// <summary>
-		/// 获取枚举对象显示名称与Key的字典
-		/// </summary>
-		/// <param name="enumType"></param>
-		/// <returns></returns>
-		public static Dictionary<string, int> GetValueItems(this Type enumType)
-		{
-			if (!enumType.IsEnum)
-			{
-				throw new Exception("给定的类型不是枚举类型");
-			}
-
-			var values = EnumValueNameDict.ContainsKey(enumType) ? EnumValueNameDict[enumType] : new Dictionary<string, int>();
-			if (values.Count == 0)
-			{
-				values = GetValueNameItems(enumType);
-				EnumValueNameDict[enumType] = values;
-			}
-
-			return values;
-		}
-
-		private static Dictionary<string, int> GetValueNameItems(Type enumType)
-		{
-			var enumItems = enumType.GetFields(BindingFlags.Public | BindingFlags.Static);
-			var values = new Dictionary<string, int>(enumItems.Length);
-			foreach (var enumItem in enumItems)
-			{
-				values[enumItem.Name] = (int)enumItem.GetValue(enumType);
-			}
-
-			return values;
-		}
-
-		/// <summary>
-		/// 获取枚举对象的值内容
-		/// </summary>
-		/// <param name="enumType"></param>
-		/// <param name="name"></param>
-		/// <returns></returns>
-		public static int GetValue(this Type enumType, string name)
-		{
-			if (!enumType.IsEnum)
-			{
-				throw new Exception("给定的类型不是枚举类型");
-			}
-
-			Dictionary<string, int> enumDict = GetValueNameItems(enumType);
-			return enumDict.ContainsKey(name) ? enumDict[name] : enumDict.Select(d => d.Value).FirstOrDefault();
-		}
-
-		/// <summary>
-		/// 获取枚举类型
-		/// </summary>
-		/// <param name="assembly"></param>
-		/// <param name="typeName"></param>
-		/// <returns></returns>
-		public static Type GetEnumType(Assembly assembly, string typeName)
-		{
-			_enumTypeDict ??= LoadEnumTypeDict(assembly);
-			return _enumTypeDict.ContainsKey(typeName) ? _enumTypeDict[typeName] : null;
-		}
-
-		/// <summary>
-		/// 根据枚举成员获取Display的属性Name
-		/// </summary>
-		/// <returns></returns>
-		public static string GetDisplay(this Enum en)
-		{
-			var type = en.GetType(); //获取类型
-			var memberInfos = type.GetMember(en.ToString()); //获取成员
-			if (memberInfos.Any())
-			{
-				if (memberInfos[0].GetCustomAttributes(typeof(DisplayAttribute), false) is DisplayAttribute[] attrs && attrs.Length > 0)
-				{
-					return attrs[0].Name; //返回当前描述
-				}
-			}
-
-			return en.ToString();
-		}
-
-		private static ConcurrentDictionary<string, Type> LoadEnumTypeDict(Assembly assembly)
-		{
-			return new ConcurrentDictionary<string, Type>(assembly.GetTypes().Where(o => o.IsEnum).ToDictionary(o => o.Name, o => o));
-		}
-
-		/// <summary>
-		/// 获取枚举值的Description信息
-		/// </summary>
-		/// <param name ="value">枚举值</param>
-		/// <param name ="args">要格式化的对象</param>
-		/// <returns>如果未找到DescriptionAttribute则返回null或返回类型描述</returns>
-		public static string GetDescription(this Enum value, params object[] args)
-		{
-			var type = value.GetType();
-			if (!Enum.IsDefined(type, value))
-			{
-				return Enum.GetValues(type).OfType<Enum>().Where(value.HasFlag).Select(e =>
-				{
-					var member = type.GetField(e.ToString());
-					var description = member.GetCustomAttributes(typeof(DescriptionAttribute), false) is DescriptionAttribute[] attrs && attrs.Length != 0 ? attrs[0].Description : e.ToString();
-					return args.Length > 0 ? string.Format(description, args) : description;
-				}).Join(",");
-			}
-
-			var member = type.GetField(value.ToString());
-			var description = member.GetCustomAttributes(typeof(DescriptionAttribute), false) is DescriptionAttribute[] attributes && attributes.Length != 0 ? attributes[0].Description : value.ToString();
-			return args.Length > 0 ? string.Format(description, args) : description;
-		}
-
-		/// <summary>
-		/// 获取枚举值的Description信息
-		/// </summary>
-		/// <param name ="value">枚举值</param>
-		/// <param name ="args">要格式化的对象</param>
-		/// <returns>如果未找到DescriptionAttribute则返回null或返回类型描述</returns>
-		public static IEnumerable<TAttribute> GetAttributes<TAttribute>(this Enum value) where TAttribute : Attribute
-		{
-			var type = value.GetType();
-			if (!Enum.IsDefined(type, value))
-			{
-				return Enum.GetValues(type).OfType<Enum>().Where(value.HasFlag).SelectMany(e => type.GetField(e.ToString()).GetCustomAttributes<TAttribute>(false));
-			}
-
-			return type.GetField(value.ToString()).GetCustomAttributes<TAttribute>(false);
-		}
-
-		/// <summary>
-		/// 获取枚举值的Description信息
-		/// </summary>
-		/// <param name ="value">枚举值</param>
-		/// <returns>如果未找到DescriptionAttribute则返回null或返回类型描述</returns>
-		public static EnumDescriptionAttribute GetEnumDescription(this Enum value)
-		{
-			return GetEnumDescriptions(value).FirstOrDefault();
-		}
-
-		/// <summary>
-		/// 获取枚举值的Description信息
-		/// </summary>
-		/// <param name ="value">枚举值</param>
-		/// <returns>如果未找到DescriptionAttribute则返回null或返回类型描述</returns>
-		public static NullableDictionary<string, (string Description, string Display)> GetTypedEnumDescriptions(this Enum value)
-		{
-			return GetEnumDescriptions(value).ToDictionarySafety(a => a.Language, a => (a.Description, a.Display));
-		}
-
-		/// <summary>
-		/// 获取枚举值的Description信息
-		/// </summary>
-		/// <param name ="value">枚举值</param>
-		/// <returns>如果未找到DescriptionAttribute则返回null或返回类型描述</returns>
-		public static IEnumerable<EnumDescriptionAttribute> GetEnumDescriptions(this Enum value)
-		{
-			if (value == null)
-			{
-				throw new ArgumentNullException(nameof(value));
-			}
-
-			var type = value.GetType();
-			if (!Enum.IsDefined(type, value))
-			{
-				return Enum.GetValues(type).OfType<Enum>().Where(value.HasFlag).SelectMany(e => type.GetField(e.ToString()).GetCustomAttributes(typeof(EnumDescriptionAttribute), false).OfType<EnumDescriptionAttribute>());
-			}
-
-			return type.GetField(value.ToString()).GetCustomAttributes(typeof(EnumDescriptionAttribute), false).OfType<EnumDescriptionAttribute>();
-		}
-
-		/// <summary>
-		/// 扩展方法:根据枚举值得到相应的枚举定义字符串
-		/// </summary>
-		/// <param name="value"></param>
-		/// <param name="enumType"></param>
-		/// <returns></returns>
-		public static String ToEnumString(this int value, Type enumType)
-		{
-			return GetEnumStringFromEnumValue(enumType)[value.ToString()];
-		}
-
-		/// <summary>
-		/// 根据枚举类型得到其所有的 值 与 枚举定义字符串 的集合
-		/// </summary>
-		/// <param name="enumType"></param>
-		/// <returns></returns>
-		public static NameValueCollection GetEnumStringFromEnumValue(Type enumType)
-		{
-			var nvc = new NameValueCollection();
-			var fields = enumType.GetFields();
-			foreach (FieldInfo field in fields)
-			{
-				if (field.FieldType.IsEnum)
-				{
-					var strValue = ((int)enumType.InvokeMember(field.Name, BindingFlags.GetField, null, null, null)).ToString();
-					nvc.Add(strValue, field.Name);
-				}
-			}
-
-			return nvc;
-		}
-	}
+    /// <summary>
+    /// 枚举扩展类
+    /// </summary>
+    public static partial class EnumExt
+    {
+        private static readonly ConcurrentDictionary<Type, Dictionary<int, string>> EnumNameValueDict = new ConcurrentDictionary<Type, Dictionary<int, string>>();
+        private static NullableDictionary<string, Type> _enumTypeDict;
+
+        /// <summary>
+        /// 获取枚举对象Key与显示名称的字典
+        /// </summary>
+        /// <param name="enumType"></param>
+        /// <returns></returns>
+        public static Dictionary<int, string> GetDictionary(this Type enumType)
+        {
+            if (!enumType.IsEnum)
+            {
+                throw new Exception("给定的类型不是枚举类型");
+            }
+
+            return EnumNameValueDict.GetOrAdd(enumType, _ => GetDictionaryItems(enumType));
+        }
+
+        private static Dictionary<int, string> GetDictionaryItems(Type enumType)
+        {
+            var enumItems = enumType.GetFields(BindingFlags.Public | BindingFlags.Static);
+            var names = new Dictionary<int, string>(enumItems.Length);
+            foreach (FieldInfo enumItem in enumItems)
+            {
+                names[(int)enumItem.GetValue(enumType)] = enumItem.Name;
+            }
+
+            return names;
+        }
+
+        /// <summary>
+        /// 获取枚举类型
+        /// </summary>
+        /// <param name="assembly"></param>
+        /// <param name="typeName"></param>
+        /// <returns></returns>
+        public static Type GetEnumType(this Assembly assembly, string typeName)
+        {
+            _enumTypeDict ??= LoadEnumTypeDict(assembly);
+            return _enumTypeDict[typeName];
+        }
+
+        /// <summary>
+        /// 根据枚举成员获取Display的属性Name
+        /// </summary>
+        /// <returns></returns>
+        public static string GetDisplay(this Enum en)
+        {
+            var type = en.GetType(); //获取类型
+            var memberInfos = type.GetMember(en.ToString()); //获取成员
+            if (memberInfos.Any())
+            {
+                if (memberInfos[0].GetCustomAttributes(typeof(DisplayAttribute), false) is DisplayAttribute[] attrs && attrs.Length > 0)
+                {
+                    return attrs[0].Name; //返回当前描述
+                }
+            }
+
+            return en.ToString();
+        }
+
+        private static NullableDictionary<string, Type> LoadEnumTypeDict(Assembly assembly)
+        {
+            return assembly.GetTypes().Where(o => o.IsEnum).ToDictionarySafety(o => o.Name, o => o);
+        }
+
+        /// <summary>
+        /// 获取枚举值的Description信息
+        /// </summary>
+        /// <param name ="value">枚举值</param>
+        /// <param name ="args">要格式化的对象</param>
+        /// <returns>如果未找到DescriptionAttribute则返回null或返回类型描述</returns>
+        public static string GetDescription(this Enum value, params object[] args)
+        {
+            var type = value.GetType();
+            if (!Enum.IsDefined(type, value))
+            {
+                return Enum.GetValues(type).OfType<Enum>().Where(value.HasFlag).Select(e =>
+                {
+                    var member = type.GetField(e.ToString());
+                    var description = member.GetCustomAttributes(typeof(DescriptionAttribute), false) is DescriptionAttribute[] attrs && attrs.Length != 0 ? attrs[0].Description : e.ToString();
+                    return args.Length > 0 ? string.Format(description, args) : description;
+                }).Join(",");
+            }
+
+            var member = type.GetField(value.ToString());
+            var description = member.GetCustomAttributes(typeof(DescriptionAttribute), false) is DescriptionAttribute[] attributes && attributes.Length != 0 ? attributes[0].Description : value.ToString();
+            return args.Length > 0 ? string.Format(description, args) : description;
+        }
+
+        /// <summary>
+        /// 获取枚举值的Description信息
+        /// </summary>
+        /// <param name ="value">枚举值</param>
+        /// <param name ="args">要格式化的对象</param>
+        /// <returns>如果未找到DescriptionAttribute则返回null或返回类型描述</returns>
+        public static IEnumerable<TAttribute> GetAttributes<TAttribute>(this Enum value) where TAttribute : Attribute
+        {
+            var type = value.GetType();
+            if (!Enum.IsDefined(type, value))
+            {
+                return Enum.GetValues(type).OfType<Enum>().Where(value.HasFlag).SelectMany(e => type.GetField(e.ToString()).GetCustomAttributes<TAttribute>(false));
+            }
+
+            return type.GetField(value.ToString()).GetCustomAttributes<TAttribute>(false);
+        }
+
+        /// <summary>
+        /// 拆分枚举值
+        /// </summary>
+        /// <param name ="value">枚举值</param>
+        public static IEnumerable<TEnum> Split<TEnum>(this TEnum value) where TEnum : Enum
+        {
+            var type = typeof(TEnum);
+            return Enum.IsDefined(type, value) ? new[]
+            {
+                value
+            } : Enum.GetValues(type).Cast<TEnum>().Where(e => value.HasFlag(e));
+        }
+
+        /// <summary>
+        /// 获取枚举值的Description信息
+        /// </summary>
+        /// <param name ="value">枚举值</param>
+        /// <returns>如果未找到DescriptionAttribute则返回null或返回类型描述</returns>
+        public static EnumDescriptionAttribute GetEnumDescription(this Enum value)
+        {
+            return GetEnumDescriptions(value).FirstOrDefault();
+        }
+
+        /// <summary>
+        /// 获取枚举值的Description信息
+        /// </summary>
+        /// <param name ="value">枚举值</param>
+        /// <returns>如果未找到DescriptionAttribute则返回null或返回类型描述</returns>
+        public static NullableDictionary<string, (string Description, string Display)> GetTypedEnumDescriptions(this Enum value)
+        {
+            return GetEnumDescriptions(value).ToDictionarySafety(a => a.Language, a => (a.Description, a.Display));
+        }
+
+        /// <summary>
+        /// 获取枚举值的Description信息
+        /// </summary>
+        /// <param name ="value">枚举值</param>
+        /// <returns>如果未找到DescriptionAttribute则返回null或返回类型描述</returns>
+        public static IEnumerable<EnumDescriptionAttribute> GetEnumDescriptions(this Enum value)
+        {
+            if (value == null)
+            {
+                throw new ArgumentNullException(nameof(value));
+            }
+
+            var type = value.GetType();
+            if (!Enum.IsDefined(type, value))
+            {
+                return Enum.GetValues(type).OfType<Enum>().Where(value.HasFlag).SelectMany(e => type.GetField(e.ToString()).GetCustomAttributes(typeof(EnumDescriptionAttribute), false).OfType<EnumDescriptionAttribute>());
+            }
+
+            return type.GetField(value.ToString()).GetCustomAttributes(typeof(EnumDescriptionAttribute), false).OfType<EnumDescriptionAttribute>();
+        }
+
+        /// <summary>
+        /// 扩展方法:根据枚举值得到相应的枚举定义字符串
+        /// </summary>
+        /// <param name="value"></param>
+        /// <param name="enumType"></param>
+        /// <returns></returns>
+        public static String ToEnumString(this int value, Type enumType)
+        {
+            return GetEnumStringFromEnumValue(enumType)[value.ToString()];
+        }
+
+        /// <summary>
+        /// 根据枚举类型得到其所有的 值 与 枚举定义字符串 的集合
+        /// </summary>
+        /// <param name="enumType"></param>
+        /// <returns></returns>
+        public static NameValueCollection GetEnumStringFromEnumValue(Type enumType)
+        {
+            var nvc = new NameValueCollection();
+            var fields = enumType.GetFields();
+            foreach (FieldInfo field in fields)
+            {
+                if (field.FieldType.IsEnum)
+                {
+                    var strValue = ((int)enumType.InvokeMember(field.Name, BindingFlags.GetField, null, null, null)).ToString();
+                    nvc.Add(strValue, field.Name);
+                }
+            }
+
+            return nvc;
+        }
+
+        /// <summary>
+        /// 根据枚举成员获取自定义属性EnumDisplayNameAttribute的属性DisplayName
+        /// </summary>
+        public static Dictionary<string, int> GetDescriptionAndValue(this Type enumType) => Enum.GetValues(enumType).Cast<object>().ToDictionary(e => (e as Enum).GetDescription(), e => (int)e);
+    }
 }
 }

+ 27 - 0
Masuit.Tools.Abstractions/Win32/AntiVirus/AmsiResult.cs

@@ -0,0 +1,27 @@
+namespace Masuit.Tools.Win32.AntiVirus;
+
+public enum AmsiResult
+{
+    /// <summary>
+    /// 正常文件
+    /// </summary>
+    AmsiResultClean = 0,
+
+    /// <summary>
+    /// 正常文件
+    /// </summary>
+    AmsiResultNotDetected = 1,
+
+    AmsiResultBlockedByAdminStart = 16384,
+    AmsiResultBlockedByAdminEnd = 20479,
+
+    /// <summary>
+    /// 非正常文件
+    /// </summary>
+    AmsiResultDetected = 32768,
+
+    /// <summary>
+    /// 扫描失败
+    /// </summary>
+    ScanException = -1
+}

+ 185 - 0
Masuit.Tools.Abstractions/Win32/AntiVirus/AmsiScanService.cs

@@ -0,0 +1,185 @@
+using System;
+using System.Buffers;
+using System.IO;
+using static Masuit.Tools.Win32.AntiVirus.AmsiWrapper;
+
+namespace Masuit.Tools.Win32.AntiVirus;
+
+public class AmsiScanService
+{
+    private readonly string AppName = "AMSI";
+
+    public AmsiScanService()
+    {
+        if (!File.Exists(SystemParameter.AmsiDllPath))
+        {
+            throw new PlatformNotSupportedException("amsi.dll not found");
+        }
+    }
+
+    /// <summary>
+    /// 扫描文件
+    /// </summary>
+    /// <param name="filePath"></param>
+    /// <returns></returns>
+    public ScanResult Scan(string filePath)
+    {
+        if (!File.Exists(filePath))
+        {
+            throw new FileNotFoundException();
+        }
+
+        try
+        {
+            AmsiInitialize(AppName, out var amsiContext);
+            AmsiOpenSession(amsiContext, out var amsiSession);
+
+            //读取文件(如文件有可疑,会引发Exception)
+            var fileData = File.ReadAllBytes(filePath);
+            return ScanBuffer(amsiContext, amsiSession, fileData, Path.GetFileName(filePath));
+        }
+        catch (Exception ex)
+        {
+            if (ex.Message.Contains("Operation did not complete successfully because the file contains a virus or potentially unwanted software"))
+            {
+                return new ScanResult
+                {
+                    Result = ResultCode.Detected
+                };
+            }
+
+            return new ScanResult
+            {
+                Result = ResultCode.Exception,
+                Msg = ex.Message
+            };
+        }
+    }
+
+    /// <summary>
+    /// 扫描文件流
+    /// </summary>
+    /// <param name="fileStream"></param>
+    /// <returns></returns>
+    public ScanResult Scan(Stream fileStream)
+    {
+        if (fileStream == null)
+        {
+            throw new ArgumentNullException(nameof(fileStream));
+        }
+
+        var fileData = ArrayPool<byte>.Shared.Rent((int)fileStream.Length);
+        try
+        {
+            AmsiInitialize(AppName, out var amsiContext);
+            AmsiOpenSession(amsiContext, out var amsiSession);
+            _ = fileStream.Read(fileData, 0, (int)fileStream.Length);
+            return ScanBuffer(amsiContext, amsiSession, fileData, "");
+        }
+        catch (Exception ex)
+        {
+            if (ex.Message.Contains("Operation did not complete successfully because the file contains a virus or potentially unwanted software"))
+            {
+                return new ScanResult
+                {
+                    Result = ResultCode.Detected
+                };
+            }
+
+            return new ScanResult
+            {
+                Result = ResultCode.Exception,
+                Msg = ex.Message
+            };
+        }
+        finally
+        {
+            ArrayPool<byte>.Shared.Return(fileData);
+        }
+    }
+
+    /// <summary>
+    /// 扫描文件
+    /// </summary>
+    /// <param name="fileData"></param>
+    /// <returns></returns>
+    public ScanResult Scan(byte[] fileData)
+    {
+        if (fileData == null)
+        {
+            throw new ArgumentNullException(nameof(fileData));
+        }
+
+        try
+        {
+            AmsiInitialize(AppName, out nint amsiContext);
+            AmsiOpenSession(amsiContext, out nint amsiSession);
+            return ScanBuffer(amsiContext, amsiSession, fileData, "");
+        }
+        catch (Exception ex)
+        {
+            if (ex.Message.Contains("Operation did not complete successfully because the file contains a virus or potentially unwanted software"))
+            {
+                return new ScanResult
+                {
+                    Result = ResultCode.Detected
+                };
+            }
+
+            return new ScanResult
+            {
+                Result = ResultCode.Exception,
+                Msg = ex.Message
+            };
+        }
+    }
+
+    /// <summary>
+    /// 扫描文件Buffer
+    /// </summary>
+    /// <param name="amsiContext"></param>
+    /// <param name="amsiSession"></param>
+    /// <param name="bufferData"></param>
+    /// <param name="fileName"></param>
+    /// <returns></returns>
+    private ScanResult ScanBuffer(nint amsiContext, nint amsiSession, byte[] bufferData, string fileName)
+    {
+        AmsiScanBuffer(amsiContext, bufferData, (uint)bufferData.Length, fileName, amsiSession, out AmsiResult amsiResult);
+        var result = ConvertToScanResult(amsiResult);
+
+        //if (result.Result == ResultCode.NotDetected)
+        //{
+        //    var content = Encoding.ASCII.GetString(bufferData);
+        //    AmsiScanString(amsiContext, content, fileName, amsiSession, out amsiResult);
+        //    return ConvertToScanResult(amsiResult);
+        //}
+
+        return result;
+    }
+
+    private ScanResult ConvertToScanResult(AmsiResult amsiResult)
+    {
+        switch (amsiResult)
+        {
+            case AmsiResult.AmsiResultClean:
+            case AmsiResult.AmsiResultNotDetected:
+                return new ScanResult
+                {
+                    Result = ResultCode.NotDetected
+                };
+
+            case AmsiResult.AmsiResultDetected:
+                return new ScanResult
+                {
+                    Result = ResultCode.Detected
+                };
+
+            default:
+                return new ScanResult
+                {
+                    Result = ResultCode.Exception,
+                    Msg = "Blocked behavior,scan not allowed!"
+                };
+        }
+    }
+}

+ 25 - 0
Masuit.Tools.Abstractions/Win32/AntiVirus/AmsiWrapper.cs

@@ -0,0 +1,25 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Masuit.Tools.Win32.AntiVirus;
+
+internal static class AmsiWrapper
+{
+    [DllImport("Amsi.dll", EntryPoint = "AmsiInitialize", CallingConvention = CallingConvention.StdCall)]
+    public static extern int AmsiInitialize([MarshalAs(UnmanagedType.LPWStr)] string appName, out nint amsiContext);
+
+    [DllImport("Amsi.dll", EntryPoint = "AmsiUninitialize", CallingConvention = CallingConvention.StdCall)]
+    public static extern void AmsiUninitialize(nint amsiContext);
+
+    [DllImport("Amsi.dll", EntryPoint = "AmsiOpenSession", CallingConvention = CallingConvention.StdCall)]
+    public static extern int AmsiOpenSession(nint amsiContext, out nint session);
+
+    [DllImport("Amsi.dll", EntryPoint = "AmsiCloseSession", CallingConvention = CallingConvention.StdCall)]
+    public static extern void AmsiCloseSession(nint amsiContext, nint session);
+
+    [DllImport("Amsi.dll", EntryPoint = "AmsiScanBuffer", CallingConvention = CallingConvention.StdCall)]
+    public static extern int AmsiScanBuffer(nint amsiContext, byte[] buffer, uint length, string contentName, nint session, out AmsiResult result);
+
+    [DllImport("Amsi.dll", EntryPoint = "AmsiScanString", CallingConvention = CallingConvention.StdCall)]
+    public static extern int AmsiScanString(nint amsiContext, [In()][MarshalAs(UnmanagedType.LPWStr)] string @string, [In()][MarshalAs(UnmanagedType.LPWStr)] string contentName, nint session, out AmsiResult result);
+}

+ 35 - 0
Masuit.Tools.Abstractions/Win32/AntiVirus/ScanResult.cs

@@ -0,0 +1,35 @@
+using System;
+
+namespace Masuit.Tools.Win32.AntiVirus;
+
+public class ScanResult
+{
+    /// <summary>
+    /// 扫描结果
+    /// </summary>
+    public ResultCode Result { get; set; }
+
+    /// <summary>
+    ///
+    /// </summary>
+    public string Msg { get; set; }
+}
+
+[Flags]
+public enum ResultCode
+{
+    /// <summary>
+    /// 未检测到
+    /// </summary>
+    NotDetected,
+
+    /// <summary>
+    /// 检测到
+    /// </summary>
+    Detected,
+
+    /// <summary>
+    /// 异常
+    /// </summary>
+    Exception
+}

+ 8 - 0
Masuit.Tools.Abstractions/Win32/AntiVirus/SystemParameter.cs

@@ -0,0 +1,8 @@
+namespace Masuit.Tools.Win32.AntiVirus;
+
+public class SystemParameter
+{
+    public const string WindowsDefenderPath = @"C:\Program Files\Windows Defender";
+    public const string WindowsDefenderExeName = "MpCmdRun.exe";
+    public const string AmsiDllPath = @"C:\Windows\System32\amsi.dll";
+}

+ 10 - 0
Masuit.Tools.Abstractions/Win32/AntiVirus/WindowsDefenderScanException.cs

@@ -0,0 +1,10 @@
+using System;
+
+namespace Masuit.Tools.Win32.AntiVirus;
+
+public class WindowsDefenderScanException : Exception
+{
+    public WindowsDefenderScanException() : base("Execute scan command exception")
+    {
+    }
+}

+ 171 - 0
Masuit.Tools.Abstractions/Win32/AntiVirus/WindowsDefenderScanService.cs

@@ -0,0 +1,171 @@
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using Masuit.Tools.Files;
+using Masuit.Tools.Systems;
+
+namespace Masuit.Tools.Win32.AntiVirus;
+
+public class WindowsDefenderScanService
+{
+	public WindowsDefenderScanService()
+	{
+		if (!Directory.Exists(SystemParameter.WindowsDefenderPath))
+		{
+			throw new PlatformNotSupportedException("Windows Defender not found");
+		}
+
+		if (!File.Exists($"{SystemParameter.WindowsDefenderPath}\\{SystemParameter.WindowsDefenderExeName}"))
+		{
+			throw new PlatformNotSupportedException("Windows Defender not found");
+		}
+	}
+
+	/// <summary>
+	/// 扫描文件流
+	/// </summary>
+	/// <param name="filePath"></param>
+	public ScanResult ScanStream(Stream stream)
+	{
+		var temp = Path.Combine(Environment.GetEnvironmentVariable("temp"), SnowFlake.NewId);
+		stream.SaveFile(temp);
+		if (stream.CanSeek)
+		{
+			stream.Position = 0;
+		}
+		return ScanFile(temp);
+	}
+
+	/// <summary>
+	/// 扫描文件
+	/// </summary>
+	/// <param name="filePath"></param>
+	public ScanResult ScanFile(string filePath)
+	{
+		if (!File.Exists(filePath))
+		{
+			throw new FileNotFoundException();
+		}
+		try
+		{
+			//Scanning xxxxx  found 1 threats.
+			//Scanning xxxxx  found no threats.
+			var result = RunScanCommand(filePath);
+			if (result.Contains("found no threats"))
+			{
+				return new ScanResult
+				{
+					Result = ResultCode.NotDetected,
+				};
+			}
+
+			return new ScanResult
+			{
+				Result = ResultCode.Detected,
+			};
+		}
+		catch (Exception ex)
+		{
+			return new ScanResult
+			{
+				Result = ResultCode.Exception,
+				Msg = ex.Message
+			};
+		}
+	}
+
+	/// <summary>
+	/// 扫描文件夹(不支持递文件夹嵌套文件夹的扫描)
+	/// </summary>
+	/// <param name="directoryPath"></param>
+	/// <returns>如有威胁文件,只返回文件夹中有威胁的文件</returns>
+	public ScanResult ScanDirectory(string directoryPath)
+	{
+		if (!Directory.Exists(directoryPath))
+		{
+			throw new DirectoryNotFoundException();
+		}
+		try
+		{
+			var files = Directory.GetFiles(directoryPath);
+
+			//文件夹扫描
+			var result = RunScanCommand(directoryPath);
+
+			if (result.Contains("found no threats"))
+			{
+				return new ScanResult
+				{
+					Result = ResultCode.NotDetected,
+				};
+			}
+
+			result = result.ToLower();
+			var detectedFile = (from file in files
+								let filePath = $"{file}\r\n"
+								where result.Contains(filePath.ToLower())
+								select file.Replace(directoryPath, "").Replace("\\", "")).ToList();
+
+			//解析文件,找到有威胁的文件
+			return new ScanResult
+			{
+				Result = ResultCode.Detected,
+				Msg = string.Join(";", detectedFile)
+			};
+		}
+		catch (Exception ex)
+		{
+			return new ScanResult
+			{
+				Result = ResultCode.Exception,
+				Msg = ex.Message
+			};
+		}
+	}
+
+	/// <summary>
+	/// 运行命令
+	/// </summary>
+	/// <param name="path"></param>
+	/// <returns></returns>
+	private string RunScanCommand(string path)
+	{
+		if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+		{
+			throw new PlatformNotSupportedException();
+		}
+
+		var proc = new Process();
+		try
+		{
+			proc.StartInfo.FileName = "cmd.exe";
+			proc.StartInfo.UseShellExecute = false;
+			proc.StartInfo.RedirectStandardInput = true;
+			proc.StartInfo.RedirectStandardOutput = true;
+			proc.StartInfo.RedirectStandardError = true;
+			proc.StartInfo.CreateNoWindow = true;
+			proc.Start();
+
+			var command = $"\"{SystemParameter.WindowsDefenderPath}\\{SystemParameter.WindowsDefenderExeName}\" -Scan -ScanType 3 -File \"{path}\" -DisableRemediation";
+			proc.StandardInput.WriteLine(command);
+			proc.StandardInput.WriteLine("exit");
+			while (!proc.HasExited)
+			{
+				proc.WaitForExit(1000);
+			}
+
+			return proc.StandardOutput.ReadToEnd();
+		}
+		catch (Exception ex)
+		{
+			throw new WindowsDefenderScanException();
+		}
+		finally
+		{
+			proc.Close();
+			proc.Dispose();
+		}
+	}
+}

+ 3 - 3
Masuit.Tools.AspNetCore/Masuit.Tools.AspNetCore.csproj

@@ -18,7 +18,7 @@
         <Product>Masuit.Tools.AspNetCore</Product>
         <Product>Masuit.Tools.AspNetCore</Product>
         <PackageId>Masuit.Tools.AspNetCore</PackageId>
         <PackageId>Masuit.Tools.AspNetCore</PackageId>
         <LangVersion>latest</LangVersion>
         <LangVersion>latest</LangVersion>
-        <Version>1.2.3</Version>
+        <Version>1.2.5</Version>
         <RepositoryType></RepositoryType>
         <RepositoryType></RepositoryType>
         <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
         <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
         <FileVersion>1.1.9</FileVersion>
         <FileVersion>1.1.9</FileVersion>
@@ -59,9 +59,9 @@
         <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="[5.0.17]" />
         <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="[5.0.17]" />
     </ItemGroup>
     </ItemGroup>
     <ItemGroup Condition=" '$(TargetFramework)' == 'net6'">
     <ItemGroup Condition=" '$(TargetFramework)' == 'net6'">
-        <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="[6.0.20]" />
+        <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="[6.0.21]" />
     </ItemGroup>
     </ItemGroup>
     <ItemGroup Condition=" '$(TargetFramework)' == 'net7'">
     <ItemGroup Condition=" '$(TargetFramework)' == 'net7'">
-        <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="7.0.9" />
+        <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="7.0.10" />
     </ItemGroup>
     </ItemGroup>
 </Project>
 </Project>

+ 48 - 0
Masuit.Tools.AspNetCore/ModelBinder/BindType.cs

@@ -0,0 +1,48 @@
+namespace Masuit.Tools.AspNetCore.ModelBinder;
+
+/// <summary>
+/// 取值方式
+/// </summary>
+[Flags]
+public enum BindType
+{
+	/// <summary>
+	/// 自动取值(1.取请求数据中的某个值,2.请求数据当成一个对象取值)
+	/// </summary>
+	Default = Body | Query | Form | Header | Cookie | Route | Services,
+
+	/// <summary>
+	/// 从查询字符串获取值
+	/// </summary>
+	Query = 1,
+
+	/// <summary>
+	/// 从请求正文中获取值
+	/// </summary>
+	Body = 2,
+
+	/// <summary>
+	/// 从已发布的表单字段中获取值
+	/// </summary>
+	Form = 4,
+
+	/// <summary>
+	/// 从 HTTP 标头中获取值
+	/// </summary>
+	Header = 8,
+
+	/// <summary>
+	/// 从 Cookie 中取值
+	/// </summary>
+	Cookie = 16,
+
+	/// <summary>
+	/// 从路由数据中获取值
+	/// </summary>
+	Route = 32,
+
+	/// <summary>
+	/// 从依赖关系注入容器中获取类型的实例
+	/// </summary>
+	Services = 64
+}

+ 95 - 0
Masuit.Tools.AspNetCore/ModelBinder/BodyOrDefaultBinderMiddleware.cs

@@ -0,0 +1,95 @@
+using System.Net.Mime;
+using System.Text;
+using System.Xml.Linq;
+using Newtonsoft.Json.Linq;
+
+namespace Masuit.Tools.AspNetCore.ModelBinder;
+
+public sealed class BodyOrDefaultBinderMiddleware
+{
+	private readonly RequestDelegate _next;
+	private readonly ILogger<BodyOrDefaultBinderMiddleware> _logger;
+
+	public BodyOrDefaultBinderMiddleware(RequestDelegate next, ILogger<BodyOrDefaultBinderMiddleware> logger)
+	{
+		_next = next;
+		_logger = logger;
+	}
+
+	public Task Invoke(HttpContext context)
+	{
+		var contentType = context.Request.ContentType;
+		string mediaType;
+		var charSet = "utf-8";
+		if (string.IsNullOrWhiteSpace(contentType))
+		{
+			//表单提交
+			mediaType = "application/x-www-form-urlencoded";
+		}
+		else
+		{
+			var type = new ContentType(contentType);
+			if (!string.IsNullOrWhiteSpace(type.CharSet))
+			{
+				charSet = type.CharSet;
+			}
+
+			mediaType = type.MediaType.ToLower();
+		}
+
+		var encoding = Encoding.GetEncoding(charSet);
+		if (mediaType == "application/x-www-form-urlencoded")
+		{
+			//普通表单提交
+		}
+		else if (mediaType == "multipart/form-data")
+		{
+			//带有文件的表单提交
+		}
+		else if (mediaType == "application/json")
+		{
+			var body = context.GetBodyString(encoding)?.Trim();
+			if (string.IsNullOrWhiteSpace(body))
+			{
+				return _next(context);
+			}
+
+			if (!(body.StartsWith("{") && body.EndsWith("}")))
+			{
+				return _next(context);
+			}
+
+			try
+			{
+				context.Items.AddOrUpdate("BodyOrDefaultModelBinder@JsonBody", _ => JObject.Parse(body), (_, _) => JObject.Parse(body));
+				return _next(context);
+			}
+			catch (Exception ex)
+			{
+				_logger.LogError(ex, "Parsing json failed:" + body);
+				return _next(context);
+			}
+		}
+		else if (mediaType == "application/xml")
+		{
+			var body = context.GetBodyString(encoding)?.Trim();
+			if (string.IsNullOrWhiteSpace(body))
+			{
+				return _next(context);
+			}
+
+			try
+			{
+				context.Items.AddOrUpdate("BodyOrDefaultModelBinder@XmlBody", _ => XDocument.Parse(body), (_, _) => XDocument.Parse(body));
+				return _next(context);
+			}
+			catch (Exception ex)
+			{
+				_logger.LogError(ex, "Parsing xml failed:" + body);
+				return _next(context);
+			}
+		}
+
+		return _next(context);
+	}
+}

+ 0 - 17
Masuit.Tools.AspNetCore/ModelBinder/BodyOrDefaultBindingSource.cs

@@ -1,17 +0,0 @@
-using Microsoft.AspNetCore.Mvc.ModelBinding;
-
-namespace Masuit.Tools.AspNetCore.ModelBinder;
-
-public class BodyOrDefaultBindingSource : BindingSource
-{
-    public static readonly BindingSource BodyOrDefault = new BodyOrDefaultBindingSource("BodyOrDefault", "BodyOrDefault", true, true);
-
-    public BodyOrDefaultBindingSource(string id, string displayName, bool isGreedy, bool isFromRequest) : base(id, displayName, isGreedy, isFromRequest)
-    {
-    }
-
-    public override bool CanAcceptDataFrom(BindingSource bindingSource)
-    {
-        return bindingSource == Body || bindingSource == this;
-    }
-}

+ 0 - 93
Masuit.Tools.AspNetCore/ModelBinder/BodyOrDefaultModelBinder.cs

@@ -1,93 +0,0 @@
-using Microsoft.AspNetCore.Mvc.ModelBinding;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
-using System.Text;
-
-namespace Masuit.Tools.AspNetCore.ModelBinder;
-
-public class BodyOrDefaultModelBinder : IModelBinder
-{
-    private readonly IModelBinder _bodyBinder;
-    private readonly IModelBinder _complexBinder;
-
-    public BodyOrDefaultModelBinder(IModelBinder bodyBinder, IModelBinder complexBinder)
-    {
-        _bodyBinder = bodyBinder;
-        _complexBinder = complexBinder;
-    }
-
-    public async Task BindModelAsync(ModelBindingContext bindingContext)
-    {
-        var request = bindingContext.HttpContext.Request;
-        request.EnableBuffering();
-        var buffer = new byte[Convert.ToInt32(request.ContentLength)];
-        _ = await request.Body.ReadAsync(buffer, 0, buffer.Length);
-        var text = Encoding.UTF8.GetString(buffer);
-        request.Body.Position = 0;
-
-        if (bindingContext.ModelType.IsPrimitive || bindingContext.ModelType == typeof(string) || bindingContext.ModelType.IsEnum || bindingContext.ModelType == typeof(DateTime) || bindingContext.ModelType == typeof(Guid) || (bindingContext.ModelType.IsGenericType && bindingContext.ModelType.GetGenericTypeDefinition() == typeof(Nullable<>)))
-        {
-            var parameter = bindingContext.ModelMetadata.ParameterName;
-            var value = "";
-            if (request.Query.ContainsKey(parameter))
-            {
-                value = request.Query[parameter] + "";
-            }
-            else if (request.ContentType is not null && request.ContentType.StartsWith("application/json"))
-            {
-                try
-                {
-                    value = JObject.Parse(text)[parameter] + "";
-                }
-                catch
-                {
-                    value = text.Trim('"');
-                }
-            }
-            else if (request.HasFormContentType)
-            {
-                value = request.Form[bindingContext.ModelMetadata.ParameterName] + "";
-            }
-
-            if (value.TryConvertTo(bindingContext.ModelType, out var result))
-            {
-                bindingContext.Result = ModelBindingResult.Success(result);
-            }
-            return;
-        }
-
-        if (request.HasFormContentType)
-        {
-            if (bindingContext.ModelType.IsClass)
-            {
-                await DefaultBindModel(bindingContext);
-            }
-            else
-            {
-                bindingContext.Result = ModelBindingResult.Success(request.Form[bindingContext.ModelMetadata.ParameterName].ToString().ConvertTo(bindingContext.ModelType));
-            }
-            return;
-        }
-
-        try
-        {
-            bindingContext.Result = ModelBindingResult.Success(JsonConvert.DeserializeObject(text, bindingContext.ModelType) ?? request.Query[bindingContext.ModelMetadata.ParameterName!].ToString().ConvertTo(bindingContext.ModelType));
-        }
-        catch
-        {
-            await DefaultBindModel(bindingContext);
-        }
-    }
-
-    private async Task DefaultBindModel(ModelBindingContext bindingContext)
-    {
-        await _bodyBinder.BindModelAsync(bindingContext);
-        if (bindingContext.Result.IsModelSet)
-        {
-            return;
-        }
-
-        bindingContext.ModelState.Clear();
-        await _complexBinder.BindModelAsync(bindingContext);
-    }
-}

+ 0 - 37
Masuit.Tools.AspNetCore/ModelBinder/BodyOrDefaultModelBinderProvider.cs

@@ -1,37 +0,0 @@
-using Microsoft.AspNetCore.Mvc.ModelBinding;
-using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
-
-#if NET5_0_OR_GREATER
-
-using ComplexDataModelBinderProvider = Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ComplexObjectModelBinderProvider;
-
-#else
-
-using ComplexDataModelBinderProvider = Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ComplexTypeModelBinderProvider;
-
-#endif
-
-namespace Masuit.Tools.AspNetCore.ModelBinder;
-
-public class BodyOrDefaultModelBinderProvider : IModelBinderProvider
-{
-    private readonly BodyModelBinderProvider _bodyModelBinderProvider;
-    private readonly ComplexDataModelBinderProvider _complexDataModelBinderProvider;
-
-    public BodyOrDefaultModelBinderProvider(BodyModelBinderProvider bodyModelBinderProvider, ComplexDataModelBinderProvider complexDataModelBinderProvider)
-    {
-        _bodyModelBinderProvider = bodyModelBinderProvider;
-        _complexDataModelBinderProvider = complexDataModelBinderProvider;
-    }
-
-    public IModelBinder GetBinder(ModelBinderProviderContext context)
-    {
-        if (context.BindingInfo.BindingSource != null && (context.BindingInfo.BindingSource.CanAcceptDataFrom(BindingSource.Body) || context.BindingInfo.BindingSource.CanAcceptDataFrom(BodyOrDefaultBindingSource.BodyOrDefault)))
-        {
-            var bodyBinder = _bodyModelBinderProvider.GetBinder(context);
-            var complexBinder = _complexDataModelBinderProvider.GetBinder(context);
-            return new BodyOrDefaultModelBinder(bodyBinder, complexBinder);
-        }
-        return null;
-    }
-}

+ 0 - 24
Masuit.Tools.AspNetCore/ModelBinder/BodyOrDefaultModelBinderProviderSetup.cs

@@ -1,24 +0,0 @@
-using Microsoft.AspNetCore.Mvc.ModelBinding;
-using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
-
-#if NET5_0_OR_GREATER
-
-using ComplexDataModelBinderProvider = Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ComplexObjectModelBinderProvider;
-
-#else
-
-using ComplexDataModelBinderProvider = Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ComplexTypeModelBinderProvider;
-
-#endif
-
-namespace Masuit.Tools.AspNetCore.ModelBinder;
-
-public static class BodyOrDefaultModelBinderProviderSetup
-{
-    public static void InsertBodyOrDefaultBinding(this IList<IModelBinderProvider> providers)
-    {
-        var bodyProvider = providers.OfType<BodyModelBinderProvider>().Single();
-        var complexDataProvider = providers.OfType<ComplexDataModelBinderProvider>().Single();
-        providers.Insert(0, new BodyOrDefaultModelBinderProvider(bodyProvider, complexDataProvider));
-    }
-}

+ 66 - 6
Masuit.Tools.AspNetCore/ModelBinder/FromBodyOrDefaultAttribute.cs

@@ -1,9 +1,69 @@
-using Microsoft.AspNetCore.Mvc.ModelBinding;
+using Microsoft.AspNetCore.Mvc;
 
 
-namespace Masuit.Tools.AspNetCore.ModelBinder;
-
-[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)]
-public class FromBodyOrDefaultAttribute : Attribute, IBindingSourceMetadata
+namespace Masuit.Tools.AspNetCore.ModelBinder
 {
 {
-    public BindingSource BindingSource => BodyOrDefaultBindingSource.BodyOrDefault;
+	/// <summary>
+	/// 自动装配声明参数值
+	/// <list type="bullet">
+	/// <item>
+	/// 布尔
+	/// <description>bool</description>
+	/// </item>
+	/// <item>
+	/// 字符/字符串
+	/// <description>char/string</description>
+	/// </item>
+	/// <item>
+	/// 浮点型
+	/// <description>float/double/decimal</description>
+	/// </item>
+	/// <item>
+	/// 整型
+	/// <description>byte/sbyte/short/ushort/int/uint/long/ulong</description>
+	/// </item>
+	/// <item>
+	/// 枚举
+	/// <description>Enum</description>
+	/// </item>
+	/// <item>
+	/// 其他类型
+	/// <description>JObject/XDocument/Uri/Guid/TimeSpan</description>
+	/// </item>
+	/// </list>
+	/// </summary>
+	[AttributeUsage(AttributeTargets.Parameter)]
+	public class FromBodyOrDefaultAttribute : ModelBinderAttribute
+	{
+		public FromBodyOrDefaultAttribute() : this(BindType.Default, null, null)
+		{
+		}
+
+		public FromBodyOrDefaultAttribute(IConvertible defaultValue) : this(BindType.Default, null, defaultValue)
+		{
+		}
+
+		public FromBodyOrDefaultAttribute(BindType type) : this(type, null, null)
+		{
+		}
+
+		public FromBodyOrDefaultAttribute(BindType type, string fieldname) : this(type, fieldname, null)
+		{
+		}
+
+		public FromBodyOrDefaultAttribute(BindType type, string fieldname, IConvertible defaultValue) : base(typeof(FromBodyOrDefaultModelBinder))
+		{
+			Type = type;
+			FieldName = fieldname;
+			DefaultValue = defaultValue;
+		}
+
+		public string FieldName { get; set; }
+
+		public IConvertible DefaultValue { get; set; }
+
+		/// <summary>
+		/// 取值方式
+		/// </summary>
+		public BindType Type { get; set; }
+	}
 }
 }

+ 300 - 0
Masuit.Tools.AspNetCore/ModelBinder/FromBodyOrDefaultModelBinder.cs

@@ -0,0 +1,300 @@
+using System.Collections;
+using System.Net.Mime;
+using System.Reflection;
+using System.Xml.Linq;
+using Masuit.Tools.Systems;
+using Microsoft.AspNetCore.Mvc.ModelBinding;
+using Microsoft.Extensions.Primitives;
+using Newtonsoft.Json.Linq;
+
+namespace Masuit.Tools.AspNetCore.ModelBinder;
+
+public class FromBodyOrDefaultModelBinder : IModelBinder
+{
+	private static readonly List<BindType> BindTypes;
+
+	static FromBodyOrDefaultModelBinder()
+	{
+		BindTypes = Enum.GetNames(typeof(BindType)).Select(Enum.Parse<BindType>).Where(t => t != BindType.Default).ToList();
+	}
+
+	private readonly ILogger<FromBodyOrDefaultModelBinder> _logger;
+
+	public FromBodyOrDefaultModelBinder(ILogger<FromBodyOrDefaultModelBinder> logger)
+	{
+		_logger = logger;
+	}
+
+	public Task BindModelAsync(ModelBindingContext bindingContext)
+	{
+		var context = bindingContext.HttpContext;
+		var attr = bindingContext.GetAttribute<FromBodyOrDefaultAttribute>();
+		var field = attr?.FieldName ?? bindingContext.FieldName;
+		var modelType = bindingContext.ModelType;
+		object value = null;
+		if (attr != null)
+		{
+			if (modelType.IsSimpleType() || modelType.IsSimpleArrayType() || modelType.IsSimpleListType())
+			{
+				if (attr.Type == BindType.Default)
+				{
+					foreach (var type in BindTypes)
+					{
+						value = GetBindingValue(bindingContext, type, field, modelType);
+						if (value != null)
+						{
+							break;
+						}
+					}
+				}
+				else
+				{
+					foreach (var type in attr.Type.Split())
+					{
+						value = GetBindingValue(bindingContext, type, field, modelType);
+						if (value != null)
+						{
+							break;
+						}
+					}
+				}
+			}
+			else
+			{
+				if (bindingContext.HttpContext.Items.TryGetValue("BodyOrDefaultModelBinder@JsonBody", out var obj) && obj is JObject json)
+				{
+					if (modelType.IsArray || modelType.IsGenericType && modelType.GenericTypeArguments.Length == 1)
+					{
+						if (json.TryGetValue(field, StringComparison.OrdinalIgnoreCase, out var jtoken))
+						{
+							value = jtoken.ToObject(modelType);
+						}
+						else
+						{
+							_logger.LogWarning($"TraceIdentifier:{context.TraceIdentifier},BodyOrDefaultModelBinder从{json}中获取{field}失败!");
+						}
+					}
+					else
+					{
+						// 可能是 字典或者实体 类型,尝试将modeltype 当初整个请求参数对象
+						try
+						{
+							value = json.ToObject(modelType);
+						}
+						catch (Exception e)
+						{
+							_logger.LogError(e, e.Message, json.ToString());
+						}
+					}
+				}
+
+				if (value == null)
+				{
+					var (requestData, keys) = GetRequestData(bindingContext, modelType);
+					if (keys.Any())
+					{
+						var instance = Activator.CreateInstance(modelType);
+						switch (requestData)
+						{
+							case IEnumerable<KeyValuePair<string, StringValues>> stringValues:
+								{
+									foreach (var item in stringValues)
+									{
+										var property = modelType.GetProperty(item.Key, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
+										if (property != null)
+										{
+											property.SetValue(instance, item.Value.ConvertObject(property.PropertyType));
+										}
+									}
+
+									break;
+								}
+							case IEnumerable<KeyValuePair<string, string>> strs:
+								{
+									//处理Cookie
+									foreach (var item in strs)
+									{
+										var property = modelType.GetProperty(item.Key, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
+										if (property != null)
+										{
+											property.SetValue(instance, item.Value.ConvertObject(property.PropertyType));
+										}
+									}
+
+									break;
+								}
+							case IEnumerable<KeyValuePair<string, object>> objects:
+								{
+									//处理路由
+									foreach (var item in objects)
+									{
+										var property = modelType.GetProperty(item.Key, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
+										if (property != null)
+										{
+											property.SetValue(instance, item.Value.ConvertObject(property.PropertyType));
+										}
+									}
+
+									break;
+								}
+						}
+
+						value = instance;
+					}
+				}
+			}
+
+			if (value == null && attr.DefaultValue != null)
+			{
+				value = attr.DefaultValue.ChangeType(modelType);
+			}
+		}
+
+		if (value != null)
+		{
+			bindingContext.Result = ModelBindingResult.Success(value);
+		}
+
+		return Task.CompletedTask;
+	}
+
+	private static (IEnumerable data, List<string> keys) GetRequestData(ModelBindingContext bindingContext, Type type)
+	{
+		var request = bindingContext.HttpContext.Request;
+		var props = type.GetProperties().Select(t => t.Name).ToList();
+		var query = props.Except(request.Query.Keys, StringComparer.OrdinalIgnoreCase).ToList();
+		var headers = props.Except(request.Headers.Keys, StringComparer.OrdinalIgnoreCase).ToList();
+		var cookies = props.Except(request.Cookies.Keys, StringComparer.OrdinalIgnoreCase).ToList();
+		var routes = props.Except(bindingContext.ActionContext.RouteData.Values.Keys, StringComparer.OrdinalIgnoreCase).ToList();
+		var list = new List<KeyValuePair<List<string>, IEnumerable>>()
+		{
+			new(query, request.Query),
+			new(headers, request.Headers),
+			new(cookies, request.Cookies),
+			new(routes, bindingContext.ActionContext.RouteData.Values),
+		};
+
+		if (request.HasFormContentType && request.Form.Count > 0)
+		{
+			var forms = props.Except(request.Form.Keys, StringComparer.OrdinalIgnoreCase).ToList();
+			list.Add(new KeyValuePair<List<string>, IEnumerable>(forms, request.Form));
+		}
+
+		var kv = list.OrderBy(t => t.Key.Count).FirstOrDefault();
+		return (kv.Value, props.Except(kv.Key).ToList());
+	}
+
+	/// <summary>
+	/// 获取要绑定的值
+	/// </summary>
+	/// <param name="bindingContext"></param>
+	/// <param name="bindType"></param>
+	/// <param name="fieldName"></param>
+	/// <param name="modelType"></param>
+	private object GetBindingValue(ModelBindingContext bindingContext, BindType bindType, string fieldName, Type modelType)
+	{
+		var context = bindingContext.HttpContext;
+		var mediaType = string.Empty;
+		if (!string.IsNullOrWhiteSpace(context.Request.ContentType))
+		{
+			try
+			{
+				var contentType = new ContentType(context.Request.ContentType);
+				mediaType = contentType.MediaType.ToLower();
+			}
+			catch (Exception ex)
+			{
+				_logger.LogError(ex, ex.Message, context.Request.ContentType);
+			}
+		}
+
+		object targetValue = null;
+		switch (bindType)
+		{
+			case BindType.Body:
+				switch (mediaType)
+				{
+					case "application/json":
+						{
+							if (bindingContext.HttpContext.Items.TryGetValue("BodyOrDefaultModelBinder@JsonBody", out var obj) && obj is JObject json && json.TryGetValue(fieldName, StringComparison.OrdinalIgnoreCase, out var values))
+							{
+								targetValue = values.ConvertObject(modelType);
+							}
+						}
+
+						break;
+
+					case "application/xml":
+						{
+							if (bindingContext.HttpContext.Items.TryGetValue("BodyOrDefaultModelBinder@XmlBody", out var obj) && obj is XDocument xml)
+							{
+								var xmlElt = xml.Element(fieldName);
+								if (xmlElt != null)
+								{
+									targetValue = xmlElt.Value.ConvertObject(modelType);
+								}
+							}
+							break;
+						}
+				}
+
+				break;
+
+			case BindType.Query:
+				{
+					if (context.Request.Query is { Count: > 0 } && context.Request.Query.TryGetValue(fieldName, out var values))
+					{
+						targetValue = values.ConvertObject(modelType);
+					}
+				}
+
+				break;
+
+			case BindType.Form:
+				{
+					if (context.Request is { HasFormContentType: true, Form.Count: > 0 } && context.Request.Form.TryGetValue(fieldName, out var values))
+					{
+						targetValue = values.ConvertObject(modelType);
+					}
+				}
+
+				break;
+
+			case BindType.Header:
+				{
+					if (context.Request.Headers is { Count: > 0 } && context.Request.Headers.TryGetValue(fieldName, out var values))
+					{
+						targetValue = values.ConvertObject(modelType);
+					}
+				}
+
+				break;
+
+			case BindType.Cookie:
+				{
+					if (context.Request.Cookies is { Count: > 0 } && context.Request.Cookies.TryGetValue(fieldName, out var values))
+					{
+						targetValue = values.ConvertObject(modelType);
+					}
+				}
+
+				break;
+
+			case BindType.Route:
+				{
+					if (bindingContext.ActionContext.RouteData.Values is { Count: > 0 } && bindingContext.ActionContext.RouteData.Values.TryGetValue(fieldName, out var values))
+					{
+						targetValue = values.ConvertObject(modelType);
+					}
+				}
+
+				break;
+
+			case BindType.Services:
+				targetValue = bindingContext.ActionContext.HttpContext.RequestServices.GetRequiredService(modelType);
+				break;
+		}
+
+		return targetValue;
+	}
+}

+ 14 - 0
Masuit.Tools.AspNetCore/ModelBinder/MiddlewareExtensions.cs

@@ -0,0 +1,14 @@
+namespace Masuit.Tools.AspNetCore.ModelBinder;
+
+public static class MiddlewareExtensions
+{
+	/// <summary>
+	/// 使用自动参数绑定中间件
+	/// </summary>
+	/// <param name="appBuilder"></param>
+	/// <returns></returns>
+	public static IApplicationBuilder UseBodyOrDefaultModelBinder(this IApplicationBuilder appBuilder)
+	{
+		return appBuilder.UseMiddleware<BodyOrDefaultBinderMiddleware>();
+	}
+}

+ 159 - 0
Masuit.Tools.AspNetCore/ModelBinder/ModelBindingContextExtension.cs

@@ -0,0 +1,159 @@
+using System.Net.Mime;
+using System.Text;
+using Microsoft.AspNetCore.Mvc.Controllers;
+using Microsoft.AspNetCore.Mvc.ModelBinding;
+using Microsoft.Extensions.Primitives;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace Masuit.Tools.AspNetCore.ModelBinder;
+
+internal static class ModelBindingContextExtension
+{
+	/// <summary>
+	/// 获取绑定参数对应的 Attribute
+	/// </summary>
+	/// <typeparam name="T">Attribute对象</typeparam>
+	/// <param name="bindingContext">绑定参数上下文</param>
+	/// <param name="parameterName">参数名称</param>
+	/// <returns></returns>
+	public static T GetAttribute<T>(this ModelBindingContext bindingContext, string parameterName = null) where T : Attribute
+	{
+		var fieldName = parameterName ?? bindingContext.FieldName;
+		var ctrlActionDesc = bindingContext.ActionContext.ActionDescriptor as ControllerActionDescriptor;
+		var fieldParameter = ctrlActionDesc!.MethodInfo.GetParameters().Single(p => p.Name == fieldName);
+		return fieldParameter.GetCustomAttributes(typeof(T), false).Single() as T;
+	}
+
+	/// <summary>
+	/// 判断该次请求体Body是否是Json内容类型
+	/// </summary>
+	/// <param name="httpContext"></param>
+	/// <param name="charSet"></param>
+	/// <returns></returns>
+	public static bool IsJsonContent(this HttpContext httpContext, out string charSet)
+	{
+		string strContentType = httpContext.Request.ContentType;
+		if (string.IsNullOrEmpty(strContentType))
+		{
+			charSet = null;
+			return false;
+		}
+
+		var contentType = new ContentType(strContentType);
+		charSet = contentType.CharSet;
+		return contentType.MediaType.ToLower() == "application/json";
+	}
+
+	/// <summary>
+	/// 获取请求体Body字符串内容
+	/// </summary>
+	/// <param name="context"></param>
+	/// <param name="encoding"></param>
+	/// <returns></returns>
+	public static string GetBodyString(this HttpContext context, Encoding encoding)
+	{
+		context.Request.EnableBuffering(); //Ensure the HttpRequest.Body can be read multipletimes
+		int contentLen = 255;
+		if (context.Request.ContentLength != null)
+		{
+			contentLen = (int)context.Request.ContentLength;
+		}
+
+		var body = context.Request.Body;
+		string bodyText;
+		if (contentLen <= 0)
+		{
+			bodyText = "";
+		}
+		else
+		{
+			using var reader = new StreamReader(body, encoding, true, contentLen, true);
+			bodyText = reader.ReadToEndAsync().Result;
+		}
+
+		body.Position = 0;
+		return bodyText;
+	}
+
+	/// <summary>
+	/// 尝试设置默认值
+	/// </summary>
+	/// <param name="bindingContext"></param>
+	public static bool TrySetDefaultValue(this ModelBindingContext bindingContext)
+	{
+		var attr = bindingContext.GetAttribute<FromBodyOrDefaultAttribute>();
+		if (attr.DefaultValue != null)
+		{
+			var targetValue = attr.DefaultValue.ChangeType(bindingContext.ModelType);
+			bindingContext.Result = ModelBindingResult.Success(targetValue);
+			return true;
+		}
+
+		return false;
+	}
+
+	/// <summary>
+	/// 转换为对应类型
+	/// </summary>
+	/// <param name="this"></param>
+	public static T ConvertObjectTo<T>(this object @this)
+	{
+		return (T)ConvertObject(@this, typeof(T));
+	}
+
+	/// <summary>
+	/// 转换为对应类型
+	/// </summary>
+	/// <param name="this"></param>
+	/// <param name="type"></param>
+	public static object ConvertObject(this object @this, Type type)
+	{
+		object value;
+		if (@this is string str)
+		{
+			str = str.Trim();
+			if ((str.StartsWith("[") && str.EndsWith("]")) || str.StartsWith("{") && str.EndsWith("}"))
+			{
+				value = JsonConvert.DeserializeObject(str, type);
+			}
+			else if ((str.StartsWith("\"[") && str.EndsWith("]\"")) || str.StartsWith("\"{") && str.EndsWith("}\""))
+			{
+				// json字符串 又被 json序列化 的情况
+				var objects = JsonConvert.DeserializeObject(str);
+				value = JsonConvert.SerializeObject(objects).ConvertObject(type);
+			}
+			else
+			{
+				var text = JsonConvert.SerializeObject(@this);
+				value = JsonConvert.DeserializeObject(text, type);
+			}
+		}
+		else if (@this is StringValues values)
+		{
+			var text = values.ToString();
+			if (type.IsSimpleArrayType() || type.IsSimpleListType())
+			{
+				text = JsonConvert.SerializeObject(values);
+				value = JsonConvert.DeserializeObject(text, type);
+			}
+			else
+			{
+				text = JsonConvert.SerializeObject(text);
+				value = JsonConvert.DeserializeObject(text, type);
+			}
+		}
+		else if (@this is JToken)
+		{
+			var text = JsonConvert.SerializeObject(@this);
+			value = JsonConvert.DeserializeObject(text, type);
+		}
+		else
+		{
+			var text = JsonConvert.SerializeObject(@this);
+			value = JsonConvert.DeserializeObject(text, type);
+		}
+
+		return value;
+	}
+}

+ 3 - 3
Masuit.Tools.Core/Masuit.Tools.Core.csproj

@@ -6,7 +6,7 @@
 官网教程:https://tools.masuit.org
 官网教程:https://tools.masuit.org
 github:https://github.com/ldqk/Masuit.Tools
 github:https://github.com/ldqk/Masuit.Tools
         </Description>
         </Description>
-        <Version>2.6.3</Version>
+        <Version>2.6.5</Version>
         <Copyright>Copyright © 懒得勤快</Copyright>
         <Copyright>Copyright © 懒得勤快</Copyright>
         <PackageProjectUrl>https://github.com/ldqk/Masuit.Tools</PackageProjectUrl>
         <PackageProjectUrl>https://github.com/ldqk/Masuit.Tools</PackageProjectUrl>
         <PackageTags>Masuit.Tools,工具库,Utility,Crypt,Extensions</PackageTags>
         <PackageTags>Masuit.Tools,工具库,Utility,Crypt,Extensions</PackageTags>
@@ -48,10 +48,10 @@ github:https://github.com/ldqk/Masuit.Tools
         <PackageReference Include="Microsoft.EntityFrameworkCore" Version="[5.0.17]" />
         <PackageReference Include="Microsoft.EntityFrameworkCore" Version="[5.0.17]" />
     </ItemGroup>
     </ItemGroup>
     <ItemGroup Condition=" '$(TargetFramework)' == 'net6'">
     <ItemGroup Condition=" '$(TargetFramework)' == 'net6'">
-        <PackageReference Include="Microsoft.EntityFrameworkCore" Version="[6.0.20]" />
+        <PackageReference Include="Microsoft.EntityFrameworkCore" Version="[6.0.21]" />
     </ItemGroup>
     </ItemGroup>
     <ItemGroup Condition=" '$(TargetFramework)' == 'net7'">
     <ItemGroup Condition=" '$(TargetFramework)' == 'net7'">
-        <PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.9" />
+        <PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.10" />
     </ItemGroup>
     </ItemGroup>
     <ItemGroup>
     <ItemGroup>
       <Compile Remove="..\Masuit.Tools.Abstractions\Mapping\**" />
       <Compile Remove="..\Masuit.Tools.Abstractions\Mapping\**" />

+ 40 - 0
Masuit.Tools.Core/Windows/RegisterFileSecurityScanSersvice.cs

@@ -0,0 +1,40 @@
+using Masuit.Tools.Win32.AntiVirus;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Masuit.Tools.Windows.AntiVirus;
+
+public static class RegisterFileSecurityScanSersvice
+{
+	/// <summary>
+	///  注入Windows Defender服务
+	/// </summary>
+	/// <param name="services"></param>
+	public static void AddWindowsDefender(this IServiceCollection services)
+	{
+		if (!Directory.Exists(SystemParameter.WindowsDefenderPath))
+		{
+			throw new PlatformNotSupportedException("Windows Defender not found");
+		}
+
+		if (!File.Exists($"{SystemParameter.WindowsDefenderPath}\\{SystemParameter.WindowsDefenderExeName}"))
+		{
+			throw new PlatformNotSupportedException("Windows Defender not found");
+		}
+
+		services.AddSingleton<WindowsDefenderScanService>();
+	}
+
+	/// <summary>
+	/// 注入AMSI服务
+	/// </summary>
+	/// <param name="services"></param>
+	public static void AddAMSI(this IServiceCollection services)
+	{
+		if (!File.Exists(SystemParameter.AmsiDllPath))
+		{
+			throw new PlatformNotSupportedException("amsi.dll not found");
+		}
+
+		services.AddSingleton<AmsiScanService>();
+	}
+}

+ 609 - 609
Masuit.Tools.Excel/ExcelExtension.cs

@@ -15,617 +15,617 @@ namespace Masuit.Tools.Excel;
 
 
 public static class ExcelExtension
 public static class ExcelExtension
 {
 {
-    static ExcelExtension()
-    {
-        ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
-    }
+	static ExcelExtension()
+	{
+		ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
+	}
 
 
 #if NET5_0_OR_GREATER
 #if NET5_0_OR_GREATER
-    [System.Runtime.CompilerServices.ModuleInitializer]
-    internal static void Init()
-    {
-        ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
-    }
+	[System.Runtime.CompilerServices.ModuleInitializer]
+	internal static void Init()
+	{
+		ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
+	}
 #endif
 #endif
 
 
-    /// <summary>
-    /// 将内存表自动填充到Excel
-    /// </summary>
-    /// <param name="sheetTables">sheet名和内存表的映射</param>
-    /// <param name="password">密码</param>
-    /// <param name="settings">列设置</param>
-    /// <returns>内存流</returns>
-    public static PooledMemoryStream ToExcel(this Dictionary<string, DataTable> sheetTables, string password = null, ColumnSettings settings = null)
-    {
-        using var pkg = new ExcelPackage();
-        foreach (var pair in sheetTables)
-        {
-            pair.Value.TableName = pair.Key;
-            CreateWorksheet(pkg, pair.Value, settings);
-        }
-
-        return SaveAsStream(pkg, password);
-    }
-
-    /// <summary>
-    /// 将内存表自动填充到Excel
-    /// </summary>
-    /// <param name="sheetTables">sheet名和内存表的映射</param>
-    /// <param name="password">密码</param>
-    /// <param name="settings">列设置</param>
-    /// <returns>内存流</returns>
-    public static PooledMemoryStream ToExcel<T>(this Dictionary<string, IEnumerable<T>> sheetTables, string password = null, ColumnSettings settings = null)
-    {
-        using var pkg = new ExcelPackage();
-        foreach (var pair in sheetTables)
-        {
-            CreateWorksheet(pkg, pair, settings);
-        }
-
-        return SaveAsStream(pkg, password);
-    }
-
-    /// <summary>
-    /// 将内存表自动填充到Excel
-    /// </summary>
-    /// <param name="tables">内存表</param>
-    /// <param name="password">密码</param>
-    /// <returns>内存流</returns>
-    public static PooledMemoryStream ToExcel(this List<DataTable> tables, string password = null, ColumnSettings settings = null)
-    {
-        using var pkg = new ExcelPackage();
-        foreach (var table in tables)
-        {
-            CreateWorksheet(pkg, table, settings);
-        }
-
-        return SaveAsStream(pkg, password);
-    }
-
-    /// <summary>
-    /// 将内存表自动填充到Excel
-    /// </summary>
-    /// <param name="table">内存表</param>
-    /// <param name="password">密码</param>
-    /// <returns>内存流</returns>
-    public static PooledMemoryStream ToExcel(this DataTable table, string password = null, ColumnSettings settings = null)
-    {
-        using var pkg = new ExcelPackage();
-        CreateWorksheet(pkg, table, settings);
-        return SaveAsStream(pkg, password);
-    }
-
-    /// <summary>
-    /// 将内存表自动填充到Excel
-    /// </summary>
-    /// <param name="table">内存表</param>
-    /// <param name="password">密码</param>
-    /// <returns>内存流</returns>
-    public static PooledMemoryStream ToExcel<T>(this IEnumerable<T> table, string password = null, ColumnSettings settings = null)
-    {
-        using var pkg = new ExcelPackage();
-        CreateWorksheet(pkg, new KeyValuePair<string, IEnumerable<T>>("Sheet1", table), settings);
-        return SaveAsStream(pkg, password);
-    }
-
-    private static PooledMemoryStream SaveAsStream(ExcelPackage pkg, string password)
-    {
-        var ms = new PooledMemoryStream();
-        if (!string.IsNullOrEmpty(password))
-        {
-            pkg.SaveAs(ms, password);
-        }
-        else
-        {
-            pkg.SaveAs(ms);
-        }
-
-        ms.Position = 0;
-        return ms;
-    }
-
-    public static void CreateWorksheet(this ExcelPackage pkg, DataTable table, ColumnSettings settings = null)
-    {
-        if (string.IsNullOrEmpty(table.TableName))
-        {
-            table.TableName = "Sheet1";
-        }
-
-        var sheet = pkg.Workbook.Worksheets.Add(table.TableName);
-        sheet.Cells.Style.Font.Name = "微软雅黑";
-
-        FillWorksheet(sheet, table, settings);
-
-        sheet.Cells.Style.VerticalAlignment = ExcelVerticalAlignment.Center;
-
-        //打印方向:纵向
-        sheet.PrinterSettings.Orientation = eOrientation.Landscape;
-
-        //集中在一页里打印
-        sheet.PrinterSettings.FitToPage = true;
-
-        //使用A4纸
-        sheet.PrinterSettings.PaperSize = ePaperSize.A4;
-    }
-
-    public static void CreateWorksheet<T>(this ExcelPackage pkg, KeyValuePair<string, IEnumerable<T>> table, ColumnSettings settings = null)
-    {
-        var sheet = pkg.Workbook.Worksheets.Add(table.Key);
-        sheet.Cells.Style.Font.Name = "微软雅黑";
-
-        FillWorksheet(sheet, table.Value, settings);
-
-        sheet.Cells.Style.VerticalAlignment = ExcelVerticalAlignment.Center;
-
-        //打印方向:纵向
-        sheet.PrinterSettings.Orientation = eOrientation.Landscape;
-
-        //集中在一页里打印
-        sheet.PrinterSettings.FitToPage = true;
-
-        //使用A4纸
-        sheet.PrinterSettings.PaperSize = ePaperSize.A4;
-    }
-
-    /// <summary>
-    /// 从datatable填充工作簿
-    /// </summary>
-    /// <param name="sheet">工作簿</param>
-    /// <param name="table">数据</param>
-    /// <param name="settings">列设置</param>
-    /// <param name="startRow">起始行,默认第一行</param>
-    /// <param name="startColumn">起始列,默认第一列A列</param>
-    public static void FillWorksheet(this ExcelWorksheet sheet, DataTable table, ColumnSettings settings = null, int startRow = 1, int startColumn = 1)
-    {
-        var hasPicColumn = false;
-        if (table.Rows.Count > 0)
-        {
-            for (int i = 0; i < table.Columns.Count; i++)
-            {
-                switch (table.Rows[0][i])
-                {
-                    case Stream:
-                    case IEnumerable<Stream>:
-                    case IDictionary<string, Stream>:
-                    case IDictionary<string, MemoryStream>:
-                        hasPicColumn = true;
-                        break;
-                }
-            }
-
-            if (hasPicColumn)
-            {
-                // 填充表头
-                var maxWidth = new int[table.Columns.Count];
-                for (var j = 0; j < table.Columns.Count; j++)
-                {
-                    sheet.SetValue(startRow, j + startColumn, table.Columns[j].ColumnName);
-                    maxWidth[j] = Encoding.UTF8.GetBytes(table.Columns[j].ColumnName).Length;
-                }
-
-                sheet.Row(startRow).Style.Font.Bold = true; // 表头设置为粗体
-                sheet.Row(startRow).Style.Font.Size = sheet.Row(startRow).Style.Font.Size * 1.11f; // 表头字号放大1.11倍
-                sheet.Row(startRow).Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
-                sheet.Row(startRow).CustomHeight = true; // 自动调整行高
-                if (settings != null)
-                {
-                    foreach (var x in settings.ColumnTypes)
-                    {
-                        sheet.Column(x.Key).Style.Numberformat.Format = x.Value;
-                    }
-                }
-                sheet.Cells.AutoFitColumns(); // 表头自适应列宽
-
-                // 填充内容
-                for (int i = 0; i < table.Rows.Count; i++)
-                {
-                    sheet.Row(i + startRow + 1).CustomHeight = true; // 自动调整行高
-                    for (int j = 0; j < table.Columns.Count; j++)
-                    {
-                        switch (table.Rows[i][j])
-                        {
-                            case Stream s:
-                                {
-                                    if (s.Length > 2)
-                                    {
-                                        try
-                                        {
-                                            var picture = sheet.Drawings.AddPicture(Guid.NewGuid().ToString(), s);
-                                            picture.SetPosition(i + startRow, 3, j + startColumn - 1, 5); //设置图片显示位置
-                                            var percent = Math.Round(Math.Min(120f / picture.Image.Bounds.Height * picture.Image.Bounds.VerticalResolution, 100));
-                                            sheet.Row(i + startRow + 1).Height = 90;
-                                            sheet.Column(j + startColumn).Width = Math.Max(sheet.Column(j + startColumn).Width, picture.Image.Bounds.Width / picture.Image.Bounds.HorizontalResolution * percent / 6.4);
-                                            picture.SetSize((int)percent);
-                                        }
-                                        catch
-                                        {
-                                            throw new ArgumentException($"{i + startRow}行{j}列图像格式不受支持");
-                                        }
-                                    }
-
-                                    sheet.SetValue(i + startRow + 1, j + startColumn, "");
-
-                                    break;
-                                }
-
-                            case IEnumerable<Stream> streams:
-                                {
-                                    double sumWidth = 0;
-                                    int index = 0;
-                                    foreach (var stream in streams.Where(stream => stream.Length > 2))
-                                    {
-                                        try
-                                        {
-                                            var picture = sheet.Drawings.AddPicture(Guid.NewGuid().ToString(), stream);
-                                            var percent = Math.Round(Math.Min(120f / picture.Image.Bounds.Height * picture.Image.Bounds.VerticalResolution, 100));
-                                            picture.SetPosition(i + startRow, 3, j + startColumn - 1, (int)Math.Ceiling(picture.Image.Bounds.Width / picture.Image.Bounds.HorizontalResolution * percent * index++)); //设置图片显示位置
-                                            sheet.Row(i + startRow + 1).Height = 90;
-                                            sumWidth += picture.Image.Bounds.Width / picture.Image.Bounds.HorizontalResolution * percent / 6.4;
-                                            sheet.Column(j + startColumn).Width = Math.Max(sheet.Column(j + startColumn).Width, sumWidth);
-                                            picture.SetSize((int)percent);
-                                        }
-                                        catch
-                                        {
-                                            throw new ArgumentException($"{i + startRow}行{j}列第{index}张图像格式不受支持");
-                                        }
-                                    }
-
-                                    sheet.SetValue(i + startRow + 1, j + startColumn, "");
-                                    break;
-                                }
-
-                            case IDictionary<string, Stream> dic:
-                                {
-                                    double sumWidth = 0;
-                                    int index = 0;
-                                    foreach (var kv in dic.Where(kv => kv.Value.Length > 2))
-                                    {
-                                        try
-                                        {
-                                            var picture = sheet.Drawings.AddPicture(Guid.NewGuid().ToString(), kv.Value, new Uri(kv.Key));
-                                            var percent = Math.Round(Math.Min(120f / picture.Image.Bounds.Height * picture.Image.Bounds.VerticalResolution, 100));
-                                            picture.SetPosition(i + startRow, 3, j + startColumn - 1, (int)Math.Ceiling(picture.Image.Bounds.Width / picture.Image.Bounds.HorizontalResolution * percent * index++)); //设置图片显示位置
-                                            sheet.Row(i + startRow + 1).Height = 90;
-                                            sumWidth += picture.Image.Bounds.Width / picture.Image.Bounds.HorizontalResolution * percent / 6.4;
-                                            sheet.Column(j + startColumn).Width = Math.Max(sheet.Column(j + startColumn).Width, sumWidth);
-                                            picture.SetSize((int)percent);
-                                        }
-                                        catch
-                                        {
-                                            throw new ArgumentException($"{i + startRow}行{j}列第{index}张图像格式不受支持,图片链接:{kv.Key}");
-                                        }
-                                    }
-
-                                    sheet.SetValue(i + startRow + 1, j + startColumn, "");
-                                    break;
-                                }
-
-                            case IDictionary<string, MemoryStream> dic:
-                                {
-                                    double sumWidth = 0;
-                                    int index = 0;
-                                    foreach (var kv in dic.Where(kv => kv.Value.Length > 2))
-                                    {
-                                        try
-                                        {
-                                            var picture = sheet.Drawings.AddPicture(Guid.NewGuid().ToString(), kv.Value, new Uri(kv.Key));
-                                            var percent = Math.Round(Math.Min(120f / picture.Image.Bounds.Height * picture.Image.Bounds.VerticalResolution, 100));
-                                            picture.SetPosition(i + startRow, 3, j + startColumn - 1, (int)Math.Ceiling(picture.Image.Bounds.Width / picture.Image.Bounds.HorizontalResolution * percent * index++)); //设置图片显示位置
-                                            sheet.Row(i + startRow + 1).Height = 90;
-                                            sumWidth += picture.Image.Bounds.Width / picture.Image.Bounds.HorizontalResolution * percent / 6.4;
-                                            sheet.Column(j + startColumn).Width = Math.Max(sheet.Column(j + startColumn).Width, sumWidth);
-                                            picture.SetSize((int)percent);
-                                        }
-                                        catch
-                                        {
-                                            throw new ArgumentException($"{i + startRow}行{j}列第{index}张图像格式不受支持,图片链接:{kv.Key}");
-                                        }
-                                    }
-
-                                    sheet.SetValue(i + startRow + 1, j + startColumn, "");
-                                    break;
-                                }
-
-                            case IDictionary<string, PooledMemoryStream> dic:
-                                {
-                                    double sumWidth = 0;
-                                    int index = 0;
-                                    foreach (var kv in dic.Where(kv => kv.Value.Length > 2))
-                                    {
-                                        try
-                                        {
-                                            var picture = sheet.Drawings.AddPicture(Guid.NewGuid().ToString(), kv.Value, new Uri(kv.Key));
-                                            var percent = Math.Round(Math.Min(120f / picture.Image.Bounds.Height * picture.Image.Bounds.VerticalResolution, 100));
-                                            picture.SetPosition(i + startRow, 3, j + startColumn - 1, (int)Math.Ceiling(picture.Image.Bounds.Width / picture.Image.Bounds.HorizontalResolution * percent * index++)); //设置图片显示位置
-                                            sheet.Row(i + startRow + 1).Height = 90;
-                                            sumWidth += picture.Image.Bounds.Width / picture.Image.Bounds.HorizontalResolution * percent / 6.4;
-                                            sheet.Column(j + startColumn).Width = Math.Max(sheet.Column(j + startColumn).Width, sumWidth);
-                                            picture.SetSize((int)percent);
-                                        }
-                                        catch
-                                        {
-                                            throw new ArgumentException($"{i + startRow}行{j}列第{index}张图像格式不受支持,图片链接:{kv.Key}");
-                                        }
-                                    }
-
-                                    sheet.SetValue(i + startRow + 1, j + startColumn, "");
-                                    break;
-                                }
-
-                            default:
-                                {
-                                    sheet.SetValue(i + startRow + 1, j + startColumn, table.Rows[i][j] ?? "");
-                                    if (table.Rows[i][j] is ValueType)
-                                    {
-                                        sheet.Column(j + startColumn).AutoFit();
-                                    }
-                                    else
-                                    {
-                                        // 根据单元格内容长度来自适应调整列宽
-                                        var fontFamily = SystemFonts.Families.FirstOrDefault(f => f.Name == sheet.Cells[i + startRow + 1, j + startColumn].Style.Font.Name);
-                                        int measureSize = 1;
-                                        if (fontFamily == default)
-                                        {
-                                            fontFamily = SystemFonts.Families.FirstOrDefault();
-                                            measureSize++;
-                                        }
-
-                                        var width = TextMeasurer.Measure(table.Rows[i][j].ToString(), new TextOptions(fontFamily.CreateFont(measureSize))).Width;
-                                        sheet.Column(j + startColumn).Width = Math.Min(110, Math.Max(width, sheet.Column(j + startColumn).Width));
-                                    }
-
-                                    break;
-                                }
-                        }
-                    }
-                }
-
-                sheet.Cells.Style.WrapText = true;
-            }
-            else
-            {
-                sheet.Cells[startRow, startColumn].LoadFromDataTable(table, true, TableStyles.Light15).AutoFitColumns(12, 90);
-                sheet.Cells.Style.WrapText = true;
-            }
-        }
-    }
-
-    /// <summary>
-    /// 从datatable填充工作簿
-    /// </summary>
-    /// <param name="sheet">工作簿</param>
-    /// <param name="table">数据</param>
-    /// <param name="settings">列设置</param>
-    /// <param name="startRow">起始行,默认第一行</param>
-    /// <param name="startColumn">起始列,默认第一列A列</param>
-    public static void FillWorksheet<T>(this ExcelWorksheet sheet, IEnumerable<T> source, ColumnSettings settings = null, int startRow = 1, int startColumn = 1)
-    {
-        var hasPicColumn = false;
-        var properties = typeof(T).GetProperties();
-        if (source is ICollection<T> table)
-        {
-        }
-        else
-        {
-            table = source.ToList();
-        }
-
-        if (table.Any())
-        {
-            if (properties.Any(t => t.PropertyType.IsSubclassOf(typeof(Stream)) || typeof(IEnumerable<Stream>).IsAssignableFrom(t.PropertyType) || (typeof(IDictionary).IsAssignableFrom(t.PropertyType) && t.PropertyType.GenericTypeArguments[1].IsSubclassOf(typeof(Stream)))))
-            {
-                hasPicColumn = true;
-            }
-
-            if (hasPicColumn)
-            {
-                // 填充表头
-                var maxWidth = new int[properties.Length];
-                for (var j = 0; j < properties.Length; j++)
-                {
-                    sheet.SetValue(startRow, j + startColumn, properties[j].Name);
-                    maxWidth[j] = Encoding.UTF8.GetBytes(properties[j].Name).Length;
-                }
-
-                sheet.Row(startRow).Style.Font.Bold = true; // 表头设置为粗体
-                sheet.Row(startRow).Style.Font.Size = sheet.Row(startRow).Style.Font.Size * 1.11f; // 表头字号放大1.11倍
-                sheet.Row(startRow).Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
-                sheet.Row(startRow).CustomHeight = true; // 自动调整行高
-                if (settings != null)
-                {
-                    foreach (var x in settings.ColumnTypes)
-                    {
-                        sheet.Column(x.Key).Style.Numberformat.Format = x.Value;
-                    }
-                }
-                sheet.Cells.AutoFitColumns(); // 表头自适应列宽
-
-                // 填充内容
-                int current = 0;
-                foreach (var item in table)
-                {
-                    sheet.Row(current + startRow + 1).CustomHeight = true; // 自动调整行高
-                    for (int j = 0; j < properties.Length; j++)
-                    {
-                        switch (properties[j].GetValue(item))
-                        {
-                            case Stream s:
-                                {
-                                    if (s.Length > 2)
-                                    {
-                                        try
-                                        {
-                                            var picture = sheet.Drawings.AddPicture(Guid.NewGuid().ToString(), s);
-                                            picture.SetPosition(current + startRow, 3, j + startColumn - 1, 5); //设置图片显示位置
-                                            var percent = Math.Round(Math.Min(120f / picture.Image.Bounds.Height * picture.Image.Bounds.VerticalResolution, 100));
-                                            sheet.Row(current + startRow + 1).Height = 90;
-                                            sheet.Column(j + startColumn).Width = Math.Max(sheet.Column(j + startColumn).Width, picture.Image.Bounds.Width / picture.Image.Bounds.HorizontalResolution * percent / 6.4);
-                                            picture.SetSize((int)percent);
-                                        }
-                                        catch
-                                        {
-                                            throw new ArgumentException($"{current + startRow}行{j}列图像格式不受支持");
-                                        }
-                                    }
-
-                                    sheet.SetValue(current + startRow + 1, j + startColumn, "");
-
-                                    break;
-                                }
-
-                            case IEnumerable<Stream> streams:
-                                {
-                                    double sumWidth = 0;
-                                    int index = 0;
-                                    foreach (var stream in streams.Where(stream => stream.Length > 2))
-                                    {
-                                        try
-                                        {
-                                            var picture = sheet.Drawings.AddPicture(Guid.NewGuid().ToString(), stream);
-                                            var percent = Math.Round(Math.Min(120f / picture.Image.Bounds.Height * picture.Image.Bounds.VerticalResolution, 100));
-                                            picture.SetPosition(current + startRow, 3, j + startColumn - 1, (int)Math.Ceiling(picture.Image.Bounds.Width / picture.Image.Bounds.HorizontalResolution * percent * index++)); //设置图片显示位置
-                                            sheet.Row(current + startRow + 1).Height = 90;
-                                            sumWidth += picture.Image.Bounds.Width / picture.Image.Bounds.HorizontalResolution * percent / 6.4;
-                                            sheet.Column(j + startColumn).Width = Math.Max(sheet.Column(j + startColumn).Width, sumWidth);
-                                            picture.SetSize((int)percent);
-                                        }
-                                        catch
-                                        {
-                                            throw new ArgumentException($"{current + startRow}行{j}列第{index}张图像格式不受支持");
-                                        }
-                                    }
-
-                                    sheet.SetValue(current + startRow + 1, j + startColumn, "");
-                                    break;
-                                }
-
-                            case IDictionary<string, Stream> dic:
-                                {
-                                    double sumWidth = 0;
-                                    int index = 0;
-                                    foreach (var kv in dic.Where(kv => kv.Value.Length > 2))
-                                    {
-                                        try
-                                        {
-                                            var picture = sheet.Drawings.AddPicture(Guid.NewGuid().ToString(), kv.Value, new Uri(kv.Key));
-                                            var percent = Math.Round(Math.Min(120f / picture.Image.Bounds.Height * picture.Image.Bounds.VerticalResolution, 100));
-                                            picture.SetPosition(current + startRow, 3, j + startColumn - 1, (int)Math.Ceiling(picture.Image.Bounds.Width / picture.Image.Bounds.HorizontalResolution * percent * index++)); //设置图片显示位置
-                                            sheet.Row(current + startRow + 1).Height = 90;
-                                            sumWidth += picture.Image.Bounds.Width / picture.Image.Bounds.HorizontalResolution * percent / 6.4;
-                                            sheet.Column(j + startColumn).Width = Math.Max(sheet.Column(j + startColumn).Width, sumWidth);
-                                            picture.SetSize((int)percent);
-                                        }
-                                        catch
-                                        {
-                                            throw new ArgumentException($"{current + startRow}行{j}列第{index}张图像格式不受支持,图片链接:{kv.Key}");
-                                        }
-                                    }
-
-                                    sheet.SetValue(current + startRow + 1, j + startColumn, "");
-                                    break;
-                                }
-
-                            case IDictionary<string, MemoryStream> dic:
-                                {
-                                    double sumWidth = 0;
-                                    int index = 0;
-                                    foreach (var kv in dic.Where(kv => kv.Value.Length > 2))
-                                    {
-                                        try
-                                        {
-                                            var picture = sheet.Drawings.AddPicture(Guid.NewGuid().ToString(), kv.Value, new Uri(kv.Key));
-                                            var percent = Math.Round(Math.Min(120f / picture.Image.Bounds.Height * picture.Image.Bounds.VerticalResolution, 100));
-                                            picture.SetPosition(current + startRow, 3, j + startColumn - 1, (int)Math.Ceiling(picture.Image.Bounds.Width / picture.Image.Bounds.HorizontalResolution * percent * index++)); //设置图片显示位置
-                                            sheet.Row(current + startRow + 1).Height = 90;
-                                            sumWidth += picture.Image.Bounds.Width / picture.Image.Bounds.HorizontalResolution * percent / 6.4;
-                                            sheet.Column(j + startColumn).Width = Math.Max(sheet.Column(j + startColumn).Width, sumWidth);
-                                            picture.SetSize((int)percent);
-                                        }
-                                        catch
-                                        {
-                                            throw new ArgumentException($"{current + startRow}行{j}列第{index}张图像格式不受支持,图片链接: {kv.Key}");
-                                        }
-                                    }
-
-                                    sheet.SetValue(current + startRow + 1, j + startColumn, "");
-                                    break;
-                                }
-
-                            case IDictionary<string, PooledMemoryStream> dic:
-                                {
-                                    double sumWidth = 0;
-                                    int index = 0;
-                                    foreach (var kv in dic.Where(kv => kv.Value.Length > 2))
-                                    {
-                                        try
-                                        {
-                                            var picture = sheet.Drawings.AddPicture(Guid.NewGuid().ToString(), kv.Value, new Uri(kv.Key));
-                                            var percent = Math.Round(Math.Min(120f / picture.Image.Bounds.Height * picture.Image.Bounds.VerticalResolution, 100));
-                                            picture.SetPosition(current + startRow, 3, j + startColumn - 1, (int)Math.Ceiling(picture.Image.Bounds.Width / picture.Image.Bounds.HorizontalResolution * percent * index++)); //设置图片显示位置
-                                            sheet.Row(current + startRow + 1).Height = 90;
-                                            sumWidth += picture.Image.Bounds.Width / picture.Image.Bounds.HorizontalResolution * percent / 6.4;
-                                            sheet.Column(j + startColumn).Width = Math.Max(sheet.Column(j + startColumn).Width, sumWidth);
-                                            picture.SetSize((int)percent);
-                                        }
-                                        catch
-                                        {
-                                            throw new ArgumentException($"{current + startRow}行{j}列第{index}张图像格式不受支持,图片链接: {kv.Key}");
-                                        }
-                                    }
-
-                                    sheet.SetValue(current + startRow + 1, j + startColumn, "");
-                                    break;
-                                }
-
-                            default:
-                                {
-                                    sheet.SetValue(current + startRow + 1, j + startColumn, properties[j].GetValue(item) ?? "");
-                                    if (properties[j].GetValue(item) is ValueType)
-                                    {
-                                        sheet.Column(j + startColumn).AutoFit();
-                                    }
-                                    else
-                                    {
-                                        // 根据单元格内容长度来自适应调整列宽
-                                        var fontFamily = SystemFonts.Families.FirstOrDefault(f => f.Name == sheet.Cells[current + startRow + 1, j + startColumn].Style.Font.Name);
-                                        int measureSize = 1;
-                                        if (fontFamily == default)
-                                        {
-                                            fontFamily = SystemFonts.Families.FirstOrDefault();
-                                            measureSize++;
-                                        }
-
-                                        var width = TextMeasurer.Measure(properties[j].GetValue(item).ToString(), new TextOptions(fontFamily.CreateFont(measureSize))).Width;
-                                        sheet.Column(j + startColumn).Width = Math.Min(110, Math.Max(width, sheet.Column(j + startColumn).Width));
-                                    }
-
-                                    break;
-                                }
-                        }
-                    }
-                    current++;
-                }
-
-                sheet.Cells.Style.WrapText = true;
-            }
-            else
-            {
-                sheet.Cells[startRow, startColumn].LoadFromCollection(table, true, TableStyles.Light15).AutoFitColumns(12, 90);
-                sheet.Cells.Style.WrapText = true;
-            }
-        }
-    }
-
-    private static readonly NumberFormater NumberFormater = new("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 1);
-
-    /// <summary>
-    /// 获取字母列
-    /// </summary>
-    /// <param name="sheet"></param>
-    /// <param name="index"></param>
-    /// <returns></returns>
-    public static ExcelColumn Column(this ExcelWorksheet sheet, string index)
-    {
-        return sheet.Column((int)NumberFormater.FromString(index));
-    }
+	/// <summary>
+	/// 将内存表自动填充到Excel
+	/// </summary>
+	/// <param name="sheetTables">sheet名和内存表的映射</param>
+	/// <param name="password">密码</param>
+	/// <param name="settings">列设置</param>
+	/// <returns>内存流</returns>
+	public static PooledMemoryStream ToExcel(this Dictionary<string, DataTable> sheetTables, string password = null, ColumnSettings settings = null)
+	{
+		using var pkg = new ExcelPackage();
+		foreach (var pair in sheetTables)
+		{
+			pair.Value.TableName = pair.Key;
+			CreateWorksheet(pkg, pair.Value, settings);
+		}
+
+		return SaveAsStream(pkg, password);
+	}
+
+	/// <summary>
+	/// 将内存表自动填充到Excel
+	/// </summary>
+	/// <param name="sheetTables">sheet名和内存表的映射</param>
+	/// <param name="password">密码</param>
+	/// <param name="settings">列设置</param>
+	/// <returns>内存流</returns>
+	public static PooledMemoryStream ToExcel<T>(this Dictionary<string, IEnumerable<T>> sheetTables, string password = null, ColumnSettings settings = null)
+	{
+		using var pkg = new ExcelPackage();
+		foreach (var pair in sheetTables)
+		{
+			CreateWorksheet(pkg, pair, settings);
+		}
+
+		return SaveAsStream(pkg, password);
+	}
+
+	/// <summary>
+	/// 将内存表自动填充到Excel
+	/// </summary>
+	/// <param name="tables">内存表</param>
+	/// <param name="password">密码</param>
+	/// <returns>内存流</returns>
+	public static PooledMemoryStream ToExcel(this List<DataTable> tables, string password = null, ColumnSettings settings = null)
+	{
+		using var pkg = new ExcelPackage();
+		foreach (var table in tables)
+		{
+			CreateWorksheet(pkg, table, settings);
+		}
+
+		return SaveAsStream(pkg, password);
+	}
+
+	/// <summary>
+	/// 将内存表自动填充到Excel
+	/// </summary>
+	/// <param name="table">内存表</param>
+	/// <param name="password">密码</param>
+	/// <returns>内存流</returns>
+	public static PooledMemoryStream ToExcel(this DataTable table, string password = null, ColumnSettings settings = null)
+	{
+		using var pkg = new ExcelPackage();
+		CreateWorksheet(pkg, table, settings);
+		return SaveAsStream(pkg, password);
+	}
+
+	/// <summary>
+	/// 将内存表自动填充到Excel
+	/// </summary>
+	/// <param name="table">内存表</param>
+	/// <param name="password">密码</param>
+	/// <returns>内存流</returns>
+	public static PooledMemoryStream ToExcel<T>(this IEnumerable<T> table, string password = null, ColumnSettings settings = null)
+	{
+		using var pkg = new ExcelPackage();
+		CreateWorksheet(pkg, new KeyValuePair<string, IEnumerable<T>>("Sheet1", table), settings);
+		return SaveAsStream(pkg, password);
+	}
+
+	private static PooledMemoryStream SaveAsStream(ExcelPackage pkg, string password)
+	{
+		var ms = new PooledMemoryStream();
+		if (!string.IsNullOrEmpty(password))
+		{
+			pkg.SaveAs(ms, password);
+		}
+		else
+		{
+			pkg.SaveAs(ms);
+		}
+
+		ms.Position = 0;
+		return ms;
+	}
+
+	public static void CreateWorksheet(this ExcelPackage pkg, DataTable table, ColumnSettings settings = null)
+	{
+		if (string.IsNullOrEmpty(table.TableName))
+		{
+			table.TableName = "Sheet1";
+		}
+
+		var sheet = pkg.Workbook.Worksheets.Add(table.TableName);
+		sheet.Cells.Style.Font.Name = "微软雅黑";
+
+		FillWorksheet(sheet, table, settings);
+
+		sheet.Cells.Style.VerticalAlignment = ExcelVerticalAlignment.Center;
+
+		//打印方向:纵向
+		sheet.PrinterSettings.Orientation = eOrientation.Landscape;
+
+		//集中在一页里打印
+		sheet.PrinterSettings.FitToPage = true;
+
+		//使用A4纸
+		sheet.PrinterSettings.PaperSize = ePaperSize.A4;
+	}
+
+	public static void CreateWorksheet<T>(this ExcelPackage pkg, KeyValuePair<string, IEnumerable<T>> table, ColumnSettings settings = null)
+	{
+		var sheet = pkg.Workbook.Worksheets.Add(table.Key);
+		sheet.Cells.Style.Font.Name = "微软雅黑";
+
+		FillWorksheet(sheet, table.Value, settings);
+
+		sheet.Cells.Style.VerticalAlignment = ExcelVerticalAlignment.Center;
+
+		//打印方向:纵向
+		sheet.PrinterSettings.Orientation = eOrientation.Landscape;
+
+		//集中在一页里打印
+		sheet.PrinterSettings.FitToPage = true;
+
+		//使用A4纸
+		sheet.PrinterSettings.PaperSize = ePaperSize.A4;
+	}
+
+	/// <summary>
+	/// 从datatable填充工作簿
+	/// </summary>
+	/// <param name="sheet">工作簿</param>
+	/// <param name="table">数据</param>
+	/// <param name="settings">列设置</param>
+	/// <param name="startRow">起始行,默认第一行</param>
+	/// <param name="startColumn">起始列,默认第一列A列</param>
+	public static void FillWorksheet(this ExcelWorksheet sheet, DataTable table, ColumnSettings settings = null, int startRow = 1, int startColumn = 1)
+	{
+		var hasPicColumn = false;
+		if (table.Rows.Count > 0)
+		{
+			for (int i = 0; i < table.Columns.Count; i++)
+			{
+				switch (table.Rows[0][i])
+				{
+					case Stream:
+					case IEnumerable<Stream>:
+					case IDictionary<string, Stream>:
+					case IDictionary<string, MemoryStream>:
+						hasPicColumn = true;
+						break;
+				}
+			}
+
+			if (hasPicColumn)
+			{
+				// 填充表头
+				var maxWidth = new int[table.Columns.Count];
+				for (var j = 0; j < table.Columns.Count; j++)
+				{
+					sheet.SetValue(startRow, j + startColumn, table.Columns[j].ColumnName);
+					maxWidth[j] = Encoding.UTF8.GetBytes(table.Columns[j].ColumnName).Length;
+				}
+
+				sheet.Row(startRow).Style.Font.Bold = true; // 表头设置为粗体
+				sheet.Row(startRow).Style.Font.Size = sheet.Row(startRow).Style.Font.Size * 1.11f; // 表头字号放大1.11倍
+				sheet.Row(startRow).Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
+				sheet.Row(startRow).CustomHeight = true; // 自动调整行高
+				if (settings != null)
+				{
+					foreach (var x in settings.ColumnTypes)
+					{
+						sheet.Column(x.Key).Style.Numberformat.Format = x.Value;
+					}
+				}
+				sheet.Cells.AutoFitColumns(); // 表头自适应列宽
+
+				// 填充内容
+				for (int i = 0; i < table.Rows.Count; i++)
+				{
+					sheet.Row(i + startRow + 1).CustomHeight = true; // 自动调整行高
+					for (int j = 0; j < table.Columns.Count; j++)
+					{
+						switch (table.Rows[i][j])
+						{
+							case Stream s:
+								{
+									if (s.Length > 2)
+									{
+										try
+										{
+											var picture = sheet.Drawings.AddPicture(Guid.NewGuid().ToString(), s);
+											picture.SetPosition(i + startRow, 3, j + startColumn - 1, 5); //设置图片显示位置
+											var percent = Math.Round(Math.Min(120f / picture.Image.Bounds.Height * picture.Image.Bounds.VerticalResolution, 100));
+											sheet.Row(i + startRow + 1).Height = 90;
+											sheet.Column(j + startColumn).Width = Math.Max(sheet.Column(j + startColumn).Width, picture.Image.Bounds.Width / picture.Image.Bounds.HorizontalResolution * percent / 6.4);
+											picture.SetSize((int)percent);
+										}
+										catch
+										{
+											throw new ArgumentException($"{i + startRow}行{j}列图像格式不受支持");
+										}
+									}
+
+									sheet.SetValue(i + startRow + 1, j + startColumn, "");
+
+									break;
+								}
+
+							case IEnumerable<Stream> streams:
+								{
+									double sumWidth = 0;
+									int index = 0;
+									foreach (var stream in streams.Where(stream => stream.Length > 2))
+									{
+										try
+										{
+											var picture = sheet.Drawings.AddPicture(Guid.NewGuid().ToString(), stream);
+											var percent = Math.Round(Math.Min(120f / picture.Image.Bounds.Height * picture.Image.Bounds.VerticalResolution, 100));
+											picture.SetPosition(i + startRow, 3, j + startColumn - 1, (int)Math.Ceiling(picture.Image.Bounds.Width / picture.Image.Bounds.HorizontalResolution * percent * index++)); //设置图片显示位置
+											sheet.Row(i + startRow + 1).Height = 90;
+											sumWidth += picture.Image.Bounds.Width / picture.Image.Bounds.HorizontalResolution * percent / 6.4;
+											sheet.Column(j + startColumn).Width = Math.Max(sheet.Column(j + startColumn).Width, sumWidth);
+											picture.SetSize((int)percent);
+										}
+										catch
+										{
+											throw new ArgumentException($"{i + startRow}行{j}列第{index}张图像格式不受支持");
+										}
+									}
+
+									sheet.SetValue(i + startRow + 1, j + startColumn, "");
+									break;
+								}
+
+							case IDictionary<string, Stream> dic:
+								{
+									double sumWidth = 0;
+									int index = 0;
+									foreach (var kv in dic.Where(kv => kv.Value.Length > 2))
+									{
+										try
+										{
+											var picture = sheet.Drawings.AddPicture(Guid.NewGuid().ToString(), kv.Value, new Uri(kv.Key));
+											var percent = Math.Round(Math.Min(120f / picture.Image.Bounds.Height * picture.Image.Bounds.VerticalResolution, 100));
+											picture.SetPosition(i + startRow, 3, j + startColumn - 1, (int)Math.Ceiling(picture.Image.Bounds.Width / picture.Image.Bounds.HorizontalResolution * percent * index++)); //设置图片显示位置
+											sheet.Row(i + startRow + 1).Height = 90;
+											sumWidth += picture.Image.Bounds.Width / picture.Image.Bounds.HorizontalResolution * percent / 6.4;
+											sheet.Column(j + startColumn).Width = Math.Max(sheet.Column(j + startColumn).Width, sumWidth);
+											picture.SetSize((int)percent);
+										}
+										catch
+										{
+											throw new ArgumentException($"{i + startRow}行{j}列第{index}张图像格式不受支持,图片链接:{kv.Key}");
+										}
+									}
+
+									sheet.SetValue(i + startRow + 1, j + startColumn, "");
+									break;
+								}
+
+							case IDictionary<string, MemoryStream> dic:
+								{
+									double sumWidth = 0;
+									int index = 0;
+									foreach (var kv in dic.Where(kv => kv.Value.Length > 2))
+									{
+										try
+										{
+											var picture = sheet.Drawings.AddPicture(Guid.NewGuid().ToString(), kv.Value, new Uri(kv.Key));
+											var percent = Math.Round(Math.Min(120f / picture.Image.Bounds.Height * picture.Image.Bounds.VerticalResolution, 100));
+											picture.SetPosition(i + startRow, 3, j + startColumn - 1, (int)Math.Ceiling(picture.Image.Bounds.Width / picture.Image.Bounds.HorizontalResolution * percent * index++)); //设置图片显示位置
+											sheet.Row(i + startRow + 1).Height = 90;
+											sumWidth += picture.Image.Bounds.Width / picture.Image.Bounds.HorizontalResolution * percent / 6.4;
+											sheet.Column(j + startColumn).Width = Math.Max(sheet.Column(j + startColumn).Width, sumWidth);
+											picture.SetSize((int)percent);
+										}
+										catch
+										{
+											throw new ArgumentException($"{i + startRow}行{j}列第{index}张图像格式不受支持,图片链接:{kv.Key}");
+										}
+									}
+
+									sheet.SetValue(i + startRow + 1, j + startColumn, "");
+									break;
+								}
+
+							case IDictionary<string, PooledMemoryStream> dic:
+								{
+									double sumWidth = 0;
+									int index = 0;
+									foreach (var kv in dic.Where(kv => kv.Value.Length > 2))
+									{
+										try
+										{
+											var picture = sheet.Drawings.AddPicture(Guid.NewGuid().ToString(), kv.Value, new Uri(kv.Key));
+											var percent = Math.Round(Math.Min(120f / picture.Image.Bounds.Height * picture.Image.Bounds.VerticalResolution, 100));
+											picture.SetPosition(i + startRow, 3, j + startColumn - 1, (int)Math.Ceiling(picture.Image.Bounds.Width / picture.Image.Bounds.HorizontalResolution * percent * index++)); //设置图片显示位置
+											sheet.Row(i + startRow + 1).Height = 90;
+											sumWidth += picture.Image.Bounds.Width / picture.Image.Bounds.HorizontalResolution * percent / 6.4;
+											sheet.Column(j + startColumn).Width = Math.Max(sheet.Column(j + startColumn).Width, sumWidth);
+											picture.SetSize((int)percent);
+										}
+										catch
+										{
+											throw new ArgumentException($"{i + startRow}行{j}列第{index}张图像格式不受支持,图片链接:{kv.Key}");
+										}
+									}
+
+									sheet.SetValue(i + startRow + 1, j + startColumn, "");
+									break;
+								}
+
+							default:
+								{
+									sheet.SetValue(i + startRow + 1, j + startColumn, table.Rows[i][j] ?? "");
+									if (table.Rows[i][j] is ValueType)
+									{
+										sheet.Column(j + startColumn).AutoFit();
+									}
+									else
+									{
+										// 根据单元格内容长度来自适应调整列宽
+										var fontFamily = SystemFonts.Families.FirstOrDefault(f => f.Name == sheet.Cells[i + startRow + 1, j + startColumn].Style.Font.Name);
+										int measureSize = 1;
+										if (fontFamily == default)
+										{
+											fontFamily = SystemFonts.Families.FirstOrDefault();
+											measureSize++;
+										}
+
+										var width = TextMeasurer.MeasureSize(table.Rows[i][j].ToString(), new TextOptions(fontFamily.CreateFont(measureSize))).Width;
+										sheet.Column(j + startColumn).Width = Math.Min(110, Math.Max(width, sheet.Column(j + startColumn).Width));
+									}
+
+									break;
+								}
+						}
+					}
+				}
+
+				sheet.Cells.Style.WrapText = true;
+			}
+			else
+			{
+				sheet.Cells[startRow, startColumn].LoadFromDataTable(table, true, TableStyles.Light15).AutoFitColumns(12, 90);
+				sheet.Cells.Style.WrapText = true;
+			}
+		}
+	}
+
+	/// <summary>
+	/// 从datatable填充工作簿
+	/// </summary>
+	/// <param name="sheet">工作簿</param>
+	/// <param name="table">数据</param>
+	/// <param name="settings">列设置</param>
+	/// <param name="startRow">起始行,默认第一行</param>
+	/// <param name="startColumn">起始列,默认第一列A列</param>
+	public static void FillWorksheet<T>(this ExcelWorksheet sheet, IEnumerable<T> source, ColumnSettings settings = null, int startRow = 1, int startColumn = 1)
+	{
+		var hasPicColumn = false;
+		var properties = typeof(T).GetProperties();
+		if (source is ICollection<T> table)
+		{
+		}
+		else
+		{
+			table = source.ToList();
+		}
+
+		if (table.Any())
+		{
+			if (properties.Any(t => t.PropertyType.IsSubclassOf(typeof(Stream)) || typeof(IEnumerable<Stream>).IsAssignableFrom(t.PropertyType) || (typeof(IDictionary).IsAssignableFrom(t.PropertyType) && t.PropertyType.GenericTypeArguments[1].IsSubclassOf(typeof(Stream)))))
+			{
+				hasPicColumn = true;
+			}
+
+			if (hasPicColumn)
+			{
+				// 填充表头
+				var maxWidth = new int[properties.Length];
+				for (var j = 0; j < properties.Length; j++)
+				{
+					sheet.SetValue(startRow, j + startColumn, properties[j].Name);
+					maxWidth[j] = Encoding.UTF8.GetBytes(properties[j].Name).Length;
+				}
+
+				sheet.Row(startRow).Style.Font.Bold = true; // 表头设置为粗体
+				sheet.Row(startRow).Style.Font.Size = sheet.Row(startRow).Style.Font.Size * 1.11f; // 表头字号放大1.11倍
+				sheet.Row(startRow).Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
+				sheet.Row(startRow).CustomHeight = true; // 自动调整行高
+				if (settings != null)
+				{
+					foreach (var x in settings.ColumnTypes)
+					{
+						sheet.Column(x.Key).Style.Numberformat.Format = x.Value;
+					}
+				}
+				sheet.Cells.AutoFitColumns(); // 表头自适应列宽
+
+				// 填充内容
+				int current = 0;
+				foreach (var item in table)
+				{
+					sheet.Row(current + startRow + 1).CustomHeight = true; // 自动调整行高
+					for (int j = 0; j < properties.Length; j++)
+					{
+						switch (properties[j].GetValue(item))
+						{
+							case Stream s:
+								{
+									if (s.Length > 2)
+									{
+										try
+										{
+											var picture = sheet.Drawings.AddPicture(Guid.NewGuid().ToString(), s);
+											picture.SetPosition(current + startRow, 3, j + startColumn - 1, 5); //设置图片显示位置
+											var percent = Math.Round(Math.Min(120f / picture.Image.Bounds.Height * picture.Image.Bounds.VerticalResolution, 100));
+											sheet.Row(current + startRow + 1).Height = 90;
+											sheet.Column(j + startColumn).Width = Math.Max(sheet.Column(j + startColumn).Width, picture.Image.Bounds.Width / picture.Image.Bounds.HorizontalResolution * percent / 6.4);
+											picture.SetSize((int)percent);
+										}
+										catch
+										{
+											throw new ArgumentException($"{current + startRow}行{j}列图像格式不受支持");
+										}
+									}
+
+									sheet.SetValue(current + startRow + 1, j + startColumn, "");
+
+									break;
+								}
+
+							case IEnumerable<Stream> streams:
+								{
+									double sumWidth = 0;
+									int index = 0;
+									foreach (var stream in streams.Where(stream => stream.Length > 2))
+									{
+										try
+										{
+											var picture = sheet.Drawings.AddPicture(Guid.NewGuid().ToString(), stream);
+											var percent = Math.Round(Math.Min(120f / picture.Image.Bounds.Height * picture.Image.Bounds.VerticalResolution, 100));
+											picture.SetPosition(current + startRow, 3, j + startColumn - 1, (int)Math.Ceiling(picture.Image.Bounds.Width / picture.Image.Bounds.HorizontalResolution * percent * index++)); //设置图片显示位置
+											sheet.Row(current + startRow + 1).Height = 90;
+											sumWidth += picture.Image.Bounds.Width / picture.Image.Bounds.HorizontalResolution * percent / 6.4;
+											sheet.Column(j + startColumn).Width = Math.Max(sheet.Column(j + startColumn).Width, sumWidth);
+											picture.SetSize((int)percent);
+										}
+										catch
+										{
+											throw new ArgumentException($"{current + startRow}行{j}列第{index}张图像格式不受支持");
+										}
+									}
+
+									sheet.SetValue(current + startRow + 1, j + startColumn, "");
+									break;
+								}
+
+							case IDictionary<string, Stream> dic:
+								{
+									double sumWidth = 0;
+									int index = 0;
+									foreach (var kv in dic.Where(kv => kv.Value.Length > 2))
+									{
+										try
+										{
+											var picture = sheet.Drawings.AddPicture(Guid.NewGuid().ToString(), kv.Value, new Uri(kv.Key));
+											var percent = Math.Round(Math.Min(120f / picture.Image.Bounds.Height * picture.Image.Bounds.VerticalResolution, 100));
+											picture.SetPosition(current + startRow, 3, j + startColumn - 1, (int)Math.Ceiling(picture.Image.Bounds.Width / picture.Image.Bounds.HorizontalResolution * percent * index++)); //设置图片显示位置
+											sheet.Row(current + startRow + 1).Height = 90;
+											sumWidth += picture.Image.Bounds.Width / picture.Image.Bounds.HorizontalResolution * percent / 6.4;
+											sheet.Column(j + startColumn).Width = Math.Max(sheet.Column(j + startColumn).Width, sumWidth);
+											picture.SetSize((int)percent);
+										}
+										catch
+										{
+											throw new ArgumentException($"{current + startRow}行{j}列第{index}张图像格式不受支持,图片链接:{kv.Key}");
+										}
+									}
+
+									sheet.SetValue(current + startRow + 1, j + startColumn, "");
+									break;
+								}
+
+							case IDictionary<string, MemoryStream> dic:
+								{
+									double sumWidth = 0;
+									int index = 0;
+									foreach (var kv in dic.Where(kv => kv.Value.Length > 2))
+									{
+										try
+										{
+											var picture = sheet.Drawings.AddPicture(Guid.NewGuid().ToString(), kv.Value, new Uri(kv.Key));
+											var percent = Math.Round(Math.Min(120f / picture.Image.Bounds.Height * picture.Image.Bounds.VerticalResolution, 100));
+											picture.SetPosition(current + startRow, 3, j + startColumn - 1, (int)Math.Ceiling(picture.Image.Bounds.Width / picture.Image.Bounds.HorizontalResolution * percent * index++)); //设置图片显示位置
+											sheet.Row(current + startRow + 1).Height = 90;
+											sumWidth += picture.Image.Bounds.Width / picture.Image.Bounds.HorizontalResolution * percent / 6.4;
+											sheet.Column(j + startColumn).Width = Math.Max(sheet.Column(j + startColumn).Width, sumWidth);
+											picture.SetSize((int)percent);
+										}
+										catch
+										{
+											throw new ArgumentException($"{current + startRow}行{j}列第{index}张图像格式不受支持,图片链接: {kv.Key}");
+										}
+									}
+
+									sheet.SetValue(current + startRow + 1, j + startColumn, "");
+									break;
+								}
+
+							case IDictionary<string, PooledMemoryStream> dic:
+								{
+									double sumWidth = 0;
+									int index = 0;
+									foreach (var kv in dic.Where(kv => kv.Value.Length > 2))
+									{
+										try
+										{
+											var picture = sheet.Drawings.AddPicture(Guid.NewGuid().ToString(), kv.Value, new Uri(kv.Key));
+											var percent = Math.Round(Math.Min(120f / picture.Image.Bounds.Height * picture.Image.Bounds.VerticalResolution, 100));
+											picture.SetPosition(current + startRow, 3, j + startColumn - 1, (int)Math.Ceiling(picture.Image.Bounds.Width / picture.Image.Bounds.HorizontalResolution * percent * index++)); //设置图片显示位置
+											sheet.Row(current + startRow + 1).Height = 90;
+											sumWidth += picture.Image.Bounds.Width / picture.Image.Bounds.HorizontalResolution * percent / 6.4;
+											sheet.Column(j + startColumn).Width = Math.Max(sheet.Column(j + startColumn).Width, sumWidth);
+											picture.SetSize((int)percent);
+										}
+										catch
+										{
+											throw new ArgumentException($"{current + startRow}行{j}列第{index}张图像格式不受支持,图片链接: {kv.Key}");
+										}
+									}
+
+									sheet.SetValue(current + startRow + 1, j + startColumn, "");
+									break;
+								}
+
+							default:
+								{
+									sheet.SetValue(current + startRow + 1, j + startColumn, properties[j].GetValue(item) ?? "");
+									if (properties[j].GetValue(item) is ValueType)
+									{
+										sheet.Column(j + startColumn).AutoFit();
+									}
+									else
+									{
+										// 根据单元格内容长度来自适应调整列宽
+										var fontFamily = SystemFonts.Families.FirstOrDefault(f => f.Name == sheet.Cells[current + startRow + 1, j + startColumn].Style.Font.Name);
+										int measureSize = 1;
+										if (fontFamily == default)
+										{
+											fontFamily = SystemFonts.Families.FirstOrDefault();
+											measureSize++;
+										}
+
+										var width = TextMeasurer.MeasureSize(properties[j].GetValue(item).ToString(), new TextOptions(fontFamily.CreateFont(measureSize))).Width;
+										sheet.Column(j + startColumn).Width = Math.Min(110, Math.Max(width, sheet.Column(j + startColumn).Width));
+									}
+
+									break;
+								}
+						}
+					}
+					current++;
+				}
+
+				sheet.Cells.Style.WrapText = true;
+			}
+			else
+			{
+				sheet.Cells[startRow, startColumn].LoadFromCollection(table, true, TableStyles.Light15).AutoFitColumns(12, 90);
+				sheet.Cells.Style.WrapText = true;
+			}
+		}
+	}
+
+	private static readonly NumberFormater NumberFormater = new("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 1);
+
+	/// <summary>
+	/// 获取字母列
+	/// </summary>
+	/// <param name="sheet"></param>
+	/// <param name="index"></param>
+	/// <returns></returns>
+	public static ExcelColumn Column(this ExcelWorksheet sheet, string index)
+	{
+		return sheet.Column((int)NumberFormater.FromString(index));
+	}
 }
 }

+ 3 - 3
Masuit.Tools.Excel/Masuit.Tools.Excel.csproj

@@ -3,7 +3,7 @@
         <TargetFramework>netstandard2.0</TargetFramework>
         <TargetFramework>netstandard2.0</TargetFramework>
         <LangVersion>latest</LangVersion>
         <LangVersion>latest</LangVersion>
         <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
         <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
-        <Version>1.2.6.1</Version>
+        <Version>1.2.7</Version>
         <Authors>懒得勤快</Authors>
         <Authors>懒得勤快</Authors>
         <Description>Masuit.Tools.Excel导出库,支持一些简单数据的导出,支持图片列</Description>
         <Description>Masuit.Tools.Excel导出库,支持一些简单数据的导出,支持图片列</Description>
         <Copyright>懒得勤快</Copyright>
         <Copyright>懒得勤快</Copyright>
@@ -16,7 +16,7 @@
         <RepositoryType>Github</RepositoryType>
         <RepositoryType>Github</RepositoryType>
         <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
         <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
         <PackageRequireLicenseAcceptance>False</PackageRequireLicenseAcceptance>
         <PackageRequireLicenseAcceptance>False</PackageRequireLicenseAcceptance>
-        <FileVersion>1.2.4.2</FileVersion>
+        <FileVersion>1.2.5</FileVersion>
         <Company>masuit.org</Company>
         <Company>masuit.org</Company>
         <AssemblyVersion>1.2.3</AssemblyVersion>
         <AssemblyVersion>1.2.3</AssemblyVersion>
         <PackageLicenseUrl>https://github.com/ldqk/Masuit.Tools/blob/master/LICENSE</PackageLicenseUrl>
         <PackageLicenseUrl>https://github.com/ldqk/Masuit.Tools/blob/master/LICENSE</PackageLicenseUrl>
@@ -38,7 +38,7 @@
       </None>
       </None>
     </ItemGroup>
     </ItemGroup>
     <ItemGroup>
     <ItemGroup>
-        <PackageReference Include="EPPlus" Version="6.2.6" />
+        <PackageReference Include="EPPlus" Version="6.2.7" />
         <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
         <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
     </ItemGroup>
     </ItemGroup>
     <ItemGroup>
     <ItemGroup>

+ 1 - 1
Masuit.Tools.Net45/package.nuspec

@@ -2,7 +2,7 @@
 <package>
 <package>
   <metadata>
   <metadata>
     <id>Masuit.Tools.Net45</id>
     <id>Masuit.Tools.Net45</id>
-    <version>2.6.2.10</version>
+    <version>2.6.5</version>
     <title>Masuit.Tools</title>
     <title>Masuit.Tools</title>
     <authors>懒得勤快</authors>
     <authors>懒得勤快</authors>
     <owners>masuit.com</owners>
     <owners>masuit.com</owners>

+ 1 - 1
Masuit.Tools.NoSQL.MongoDBClient/Masuit.Tools.NoSQL.MongoDBClient.csproj

@@ -38,7 +38,7 @@
   </PropertyGroup>
   </PropertyGroup>
 
 
   <ItemGroup>
   <ItemGroup>
-    <PackageReference Include="MongoDB.Driver" Version="2.20.0" />
+    <PackageReference Include="MongoDB.Driver" Version="2.21.0" />
   </ItemGroup>
   </ItemGroup>
 
 
 </Project>
 </Project>

+ 4 - 1
Masuit.Tools/Masuit.Tools.csproj

@@ -130,6 +130,9 @@
     <Compile Include="..\Masuit.Tools.Abstractions\Win32\*.*">
     <Compile Include="..\Masuit.Tools.Abstractions\Win32\*.*">
       <Link>Win32\%(RecursiveDir)%(FileName)%(Extension)</Link>
       <Link>Win32\%(RecursiveDir)%(FileName)%(Extension)</Link>
     </Compile>
     </Compile>
+    <Compile Include="..\Masuit.Tools.Abstractions\Win32\*\*.*">
+      <Link>Win32\%(RecursiveDir)%(FileName)%(Extension)</Link>
+    </Compile>
     <Compile Include="Config\CoreConfig.cs" />
     <Compile Include="Config\CoreConfig.cs" />
     <Compile Include="Files\SevenZipCompressor.cs" />
     <Compile Include="Files\SevenZipCompressor.cs" />
     <Compile Include="Files\ExtensionAttach.cs" />
     <Compile Include="Files\ExtensionAttach.cs" />
@@ -196,7 +199,7 @@
       <Version>[2.1.3]</Version>
       <Version>[2.1.3]</Version>
     </PackageReference>
     </PackageReference>
     <PackageReference Include="SixLabors.ImageSharp.Drawing">
     <PackageReference Include="SixLabors.ImageSharp.Drawing">
-      <Version>1.0.0-beta15</Version>
+      <Version>1.0.0</Version>
     </PackageReference>
     </PackageReference>
     <PackageReference Include="StackExchange.Redis">
     <PackageReference Include="StackExchange.Redis">
       <Version>2.6.122</Version>
       <Version>2.6.122</Version>

+ 1 - 1
Masuit.Tools/package.nuspec

@@ -2,7 +2,7 @@
 <package>
 <package>
   <metadata>
   <metadata>
     <id>Masuit.Tools.Net</id>
     <id>Masuit.Tools.Net</id>
-    <version>2.6.2.10</version>
+    <version>2.6.5</version>
     <title>Masuit.Tools</title>
     <title>Masuit.Tools</title>
     <authors>懒得勤快</authors>
     <authors>懒得勤快</authors>
     <owners>masuit.com</owners>
     <owners>masuit.com</owners>

+ 14 - 9
NetCoreTest/Controllers/HomeController.cs

@@ -7,16 +7,21 @@ namespace NetCoreTest.Controllers;
 [ApiController]
 [ApiController]
 public class HomeController : Controller
 public class HomeController : Controller
 {
 {
-    [HttpPost("test")]
-    [ProducesResponseType(typeof(MyClass), (int)HttpStatusCode.OK)]
-    public async Task<ActionResult> Test([FromBodyOrDefault] MyClass mc)
-    {
-        return Ok(mc);
-    }
+	[HttpPost("test")]
+	[ProducesResponseType(typeof(MyClass), (int)HttpStatusCode.OK)]
+	public async Task<ActionResult> Test([FromBodyOrDefault] MyClass mc, [FromBodyOrDefault(BindType.Body | BindType.Query)] string name)
+	{
+		return Ok(new
+		{
+			name,
+			mc
+		});
+	}
 }
 }
 
 
 public class MyClass
 public class MyClass
 {
 {
-    public string MyProperty { get; set; }
-    public List<string> List { get; set; }
-}
+	public string MyProperty { get; set; }
+
+	public List<string> List { get; set; }
+}

+ 4 - 5
NetCoreTest/Program.cs

@@ -1,7 +1,7 @@
 using Masuit.Tools.AspNetCore.ModelBinder;
 using Masuit.Tools.AspNetCore.ModelBinder;
 
 
 var builder = WebApplication.CreateBuilder(args);
 var builder = WebApplication.CreateBuilder(args);
-builder.Services.AddControllers(options => options.ModelBinderProviders.InsertBodyOrDefaultBinding());
+builder.Services.AddControllers();
 builder.Services.AddEndpointsApiExplorer();
 builder.Services.AddEndpointsApiExplorer();
 builder.Services.AddSwaggerGen();
 builder.Services.AddSwaggerGen();
 
 
@@ -9,11 +9,10 @@ var app = builder.Build();
 
 
 if (app.Environment.IsDevelopment())
 if (app.Environment.IsDevelopment())
 {
 {
-    app.UseSwagger();
-    app.UseSwaggerUI();
+	app.UseSwagger();
+	app.UseSwaggerUI();
 }
 }
-
-app.UseAuthorization();
+app.UseBodyOrDefaultModelBinder();
 
 
 app.MapControllers();
 app.MapControllers();
 
 

+ 36 - 8
README.md

@@ -866,7 +866,7 @@ FileStream fs = new FileStream(@"D:\boot.vmdk", FileMode.OpenOrCreate, FileAcces
 memoryStream.SaveFile("filename"); // 将内存流转储成文件
 memoryStream.SaveFile("filename"); // 将内存流转储成文件
 ```
 ```
 
 
-### 34.数值转换
+### 34.类型转换
 
 
 ```csharp
 ```csharp
 1.2345678901.Digits8(); // 将小数截断为8位
 1.2345678901.Digits8(); // 将小数截断为8位
@@ -874,6 +874,14 @@ memoryStream.SaveFile("filename"); // 将内存流转储成文件
 1.23.ConvertTo<T>(); // 小数转T基本类型
 1.23.ConvertTo<T>(); // 小数转T基本类型
 bool b=1.23.TryConvertTo<T>(out result); // 小数转T基本类型
 bool b=1.23.TryConvertTo<T>(out result); // 小数转T基本类型
 var num=1.2345.ToDecimal(2); //转decimal并保留两位小数
 var num=1.2345.ToDecimal(2); //转decimal并保留两位小数
+
+1.23.ChangeTypeTo<T>(); //小数转T基本类型,ConvertTo和ChangeTypeTo的区别在于:ConvertTo只适用于基元类型的互转,ChangeTypeTo不仅适用于基元类型的互转还支持数组、字符串的转换(Parse),ConvertTo的性能更高
+
+type.IsPrimitive(); // 判断类型是否是值类型
+type.IsSimpleType(); // 判断类型是否是常见的简单类型,基元类型为 Boolean、 Byte、 SByte、 Int16、 UInt16、 Int32、 UInt32、 Int64、 UInt64、 IntPtr、 UIntPtr、 Char、 Double 、 Single、枚举、Nullable<T>。
+type.IsSimpleArrayType(); // 判断类型是否是常见类型的 数组形式 类型
+type.IsSimpleListType(); // 判断类型是否是常见类型的 泛型形式 类型
+
 ```
 ```
 
 
 ### 35.INI配置文件操作(仅支持Windows)
 ### 35.INI配置文件操作(仅支持Windows)
@@ -1014,18 +1022,23 @@ a.Next(func1).Next(func2).Next(func3);
 "123".Next(s=>s.ToInt32()).Next(x=>x*2).Next(x=>Math.Log(x));
 "123".Next(s=>s.ToInt32()).Next(x=>x*2).Next(x=>Math.Log(x));
 ```
 ```
 
 
-### 41.Newtonsoft.Json的只允许字段反序列化行为的契约解释器
+### 41.Newtonsoft.Json的只允许字段()序列化行为的契约解释器
 
 
 #### DeserializeOnlyContractResolver
 #### DeserializeOnlyContractResolver
 
 
-该解释器针对类属性被DeserializeOnlyJsonPropertyAttribute标记的,在反序列化的时候生效,在序列化的时候忽略
+该解释器针对类属性被DeserializeOnlyJsonPropertyAttribute/SerializeIgnoreAttribute标记的,在反序列化的时候生效,在序列化的时候忽略;被SerializeOnlyJsonPropertyAttribute/DeserializeIgnoreAttribute标记的,在序列化的时候生效,在反序列化的时候忽略
 
 
 ```csharp
 ```csharp
 public class ClassDto
 public class ClassDto
     {
     {
+        // 序列化时忽略这个属性/反序列化时加载这个属性
         [DeserializeOnlyJsonProperty]
         [DeserializeOnlyJsonProperty]
+        //[SerializeIgnore]
         public string MyProperty { get; set; }
         public string MyProperty { get; set; }
 
 
+        // 反序列化时忽略这个属性/序列化时加载这个属性
+        [SerializeOnlyJsonProperty]
+        //[DeserializeIgnore]
         public int Num { get; set; }
         public int Num { get; set; }
     }
     }
   
   
@@ -1082,13 +1095,10 @@ PM> Install-Package Masuit.Tools.AspNetCore
 Startup配置:
 Startup配置:
 
 
 ```csharp
 ```csharp
-    services.AddMvc(options =>
-        {
-             options.ModelBinderProviders.InsertBodyOrDefaultBinding();
-        })
+app.UseBodyOrDefaultModelBinder();
 ```
 ```
 
 
-在action的参数模型前打上标记:`[FromBodyOrDefault]`即可,当然也可以省略,示例代码如下:
+在action的参数模型前打上标记:`[FromBodyOrDefault]`即可,示例代码如下:
 
 
 ```csharp
 ```csharp
         [HttpGet("query"),HttpPost("query")]
         [HttpGet("query"),HttpPost("query")]
@@ -1247,6 +1257,24 @@ detector.FormatCategories;//格式类别
         Assert.Equal(obj.Name, obj["Name"]);
         Assert.Equal(obj.Name, obj["Name"]);
         Assert.Equal(obj["MyClass"]["X"], obj.MyClass.X);
         Assert.Equal(obj["MyClass"]["X"], obj.MyClass.X);
 ```
 ```
+### 46. 反病毒(仅支持Windows)
+```csharp
+// 要求系统WindowsDefender没有被精简掉
+var windowsDefender = new WindowsDefenderScanService();
+var result = windowsDefender.ScanFile(@"Y:\1.exe"); // 扫描文件
+var result = windowsDefender.ScanDirectory(@"Y:\"); // 扫描文件夹
+var result = windowsDefender.ScanStream(stream); // 扫描文件流
+
+// 要求C:\Windows\System32\amsi.dll文件存在
+var amsiService = new AmsiScanService();
+amsiService.Scan(stream); // 扫描文件流
+amsiService.Scan(@"Y:\1.exe"); // 扫描文件
+amsiService.Scan(bytes); // 扫描二进制数组
+
+// ASP.NET Core
+service.AddWindowsDefender();
+service.AddAMSI();
+```
 
 
 # Asp.Net MVC和Asp.Net Core的支持断点续传和多线程下载的ResumeFileResult
 # Asp.Net MVC和Asp.Net Core的支持断点续传和多线程下载的ResumeFileResult
 
 

+ 1 - 1
Test/Masuit.Tools.Abstractions.Test/Masuit.Tools.Abstractions.Test.csproj

@@ -13,7 +13,7 @@
   </ItemGroup>
   </ItemGroup>
 
 
   <ItemGroup>
   <ItemGroup>
-    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.3" />
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.0" />
     <PackageReference Include="xunit" Version="2.5.0" />
     <PackageReference Include="xunit" Version="2.5.0" />
     <PackageReference Include="xunit.runner.visualstudio" Version="2.5.0">
     <PackageReference Include="xunit.runner.visualstudio" Version="2.5.0">
       <PrivateAssets>all</PrivateAssets>
       <PrivateAssets>all</PrivateAssets>

+ 2 - 2
Test/Masuit.Tools.Core.Test/Masuit.Tools.Core.Test.csproj

@@ -9,9 +9,9 @@
   </PropertyGroup>
   </PropertyGroup>
 
 
   <ItemGroup>
   <ItemGroup>
-    <PackageReference Include="Microsoft.AspNetCore.TestHost" Version="7.0.9" />
+    <PackageReference Include="Microsoft.AspNetCore.TestHost" Version="7.0.10" />
     <PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
     <PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
-    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.3" />
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.0" />
     <PackageReference Include="xunit" Version="2.5.0" />
     <PackageReference Include="xunit" Version="2.5.0" />
     <PackageReference Include="xunit.runner.visualstudio" Version="2.5.0">
     <PackageReference Include="xunit.runner.visualstudio" Version="2.5.0">
       <PrivateAssets>all</PrivateAssets>
       <PrivateAssets>all</PrivateAssets>

+ 1 - 1
Test/Masuit.Tools.Test/Masuit.Tools.Test.csproj

@@ -94,7 +94,7 @@
       <Version>2.0.0</Version>
       <Version>2.0.0</Version>
     </PackageReference>
     </PackageReference>
     <PackageReference Include="Moq">
     <PackageReference Include="Moq">
-      <Version>4.18.4</Version>
+      <Version>4.20.69</Version>
     </PackageReference>
     </PackageReference>
     <PackageReference Include="MSTest.TestAdapter">
     <PackageReference Include="MSTest.TestAdapter">
       <Version>3.1.1</Version>
       <Version>3.1.1</Version>