Sfoglia il codice sorgente

Merge branch 'v2' of https://gitee.com/zuohuaijun/Admin.NET into v2

zuohuaijun 10 mesi fa
parent
commit
aebfcccd1e

+ 59 - 0
Admin.NET/Admin.NET.Core/Attribute/DataMaskAttribute.cs

@@ -0,0 +1,59 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+// 
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+// 
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+namespace Admin.NET.Core;
+
+/// <summary>
+/// 数据脱敏特性(支持自定义脱敏位置和脱敏字符)
+/// </summary>
+[AttributeUsage(AttributeTargets.Property)]
+public class DataMaskAttribute : Attribute
+{
+    /// <summary>
+    /// 脱敏起始位置(从0开始)
+    /// </summary>
+    private int StartIndex { get; }
+
+    /// <summary>
+    /// 脱敏长度
+    /// </summary>
+    private int Length { get; }
+
+    /// <summary>
+    /// 脱敏字符(默认*)
+    /// </summary>
+    private char MaskChar { get; set; } = '*';
+
+    /// <summary>
+    /// 是否保留原始长度(默认true)
+    /// </summary>
+    private bool KeepLength { get; set; } = true;
+
+    public DataMaskAttribute(int startIndex, int length)
+    {
+        if (startIndex < 0) throw new ArgumentOutOfRangeException(nameof(startIndex));
+        if (length <= 0) throw new ArgumentOutOfRangeException(nameof(length));
+        
+        StartIndex = startIndex;
+        Length = length;
+    }
+
+    /// <summary>
+    /// 执行脱敏处理
+    /// </summary>
+    public string Mask(string input)
+    {
+        if (string.IsNullOrEmpty(input) || input.Length <= StartIndex)
+            return input;
+
+        var maskedLength = Math.Min(Length, input.Length - StartIndex);
+        var maskStr = new string(MaskChar, KeepLength ? maskedLength : Math.Min(4, maskedLength));
+
+        return input.Substring(0, StartIndex) + maskStr + 
+               (StartIndex + maskedLength < input.Length ? 
+                   input.Substring(StartIndex + maskedLength) : "");
+    }
+}

+ 45 - 0
Admin.NET/Admin.NET.Core/Extension/ObjectExtension.cs

@@ -14,6 +14,16 @@ namespace Admin.NET.Core;
 [SuppressSniffer]
 public static partial class ObjectExtension
 {
+    /// <summary>
+    /// 类型属性列表映射表
+    /// </summary>
+    private static readonly ConcurrentDictionary<Type, PropertyInfo[]> PropertyCache = new();
+
+    /// <summary>
+    /// 脱敏特性缓存映射表
+    /// </summary>
+    private static readonly ConcurrentDictionary<PropertyInfo, DataMaskAttribute> AttributeCache = new();
+
     /// <summary>
     /// 判断类型是否实现某个泛型
     /// </summary>
@@ -473,4 +483,39 @@ public static partial class ObjectExtension
         var json = JSON.Serialize(obj, jsonSettings);
         return JSON.Deserialize<T>(json);
     }
+
+    /// <summary>
+    /// 对带有<see cref="DataMaskAttribute"/>特性字段进行脱敏处理
+    /// </summary>
+    public static T MaskSensitiveData<T>(this T obj) where T : class
+    {
+        if (obj == null) return null;
+
+        var type = typeof(T);
+    
+        // 获取或缓存属性集合
+        var properties = PropertyCache.GetOrAdd(type, t => 
+            t.GetProperties(BindingFlags.Public | BindingFlags.Instance)
+                .Where(p => p.PropertyType == typeof(string) && p.GetCustomAttribute<DataMaskAttribute>() != null)
+                .ToArray());
+
+        // 并行处理可写属性
+        Parallel.ForEach(properties, prop =>
+        {
+            if (!prop.CanWrite) return;
+        
+            // 获取或缓存特性
+            var maskAttr = AttributeCache.GetOrAdd(prop, p => p.GetCustomAttribute<DataMaskAttribute>());
+            
+            if (maskAttr == null) return;
+
+            // 处理非空字符串
+            if (prop.GetValue(obj) is string { Length: > 0 } value)
+            {
+                prop.SetValue(obj, maskAttr.Mask(value));
+            }
+        });
+
+        return obj;
+    }
 }

+ 30 - 0
Admin.NET/Admin.NET.Core/SqlSugar/SqlSugarPagedList.cs

@@ -113,6 +113,36 @@ public static class SqlSugarPagedExtensions
         return CreateSqlSugarPagedList(items, total, pageIndex, pageSize);
     }
 
+    /// <summary>
+    /// 脱敏分页拓展
+    /// </summary>
+    /// <param name="query"><see cref="ISugarQueryable{TEntity}"/>对象</param>
+    /// <param name="pageIndex">当前页码,从1开始</param>
+    /// <param name="pageSize">页码容量</param>
+    /// <returns></returns>
+    public static async Task<SqlSugarPagedList<TEntity>> ToPagedListDataMaskAsync<TEntity>(this ISugarQueryable<TEntity> query, int pageIndex, int pageSize) where TEntity : class
+    {
+        RefAsync<int> total = 0;
+        var items = await query.ToPageListAsync(pageIndex, pageSize, total);
+        items.ForEach(x => x.MaskSensitiveData());
+        return CreateSqlSugarPagedList(items, total, pageIndex, pageSize);
+    }
+    
+    /// <summary>
+    /// 脱敏分页拓展
+    /// </summary>
+    /// <param name="list">集合对象</param>
+    /// <param name="pageIndex">当前页码,从1开始</param>
+    /// <param name="pageSize">页码容量</param>
+    /// <returns></returns>
+    public static SqlSugarPagedList<TEntity> ToPagedListDataMask<TEntity>(this IEnumerable<TEntity> list, int pageIndex, int pageSize) where TEntity : class
+    {
+        var total = list.Count();
+        var items = list.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToList();
+        items.ForEach(x => x.MaskSensitiveData());
+        return CreateSqlSugarPagedList(items, total, pageIndex, pageSize);
+    }
+
     /// <summary>
     /// 分页拓展
     /// </summary>