using System.Collections;
using System.Collections.Concurrent;
using System.Linq.Expressions;

namespace Dpz.Core.Service;

[Obsolete("请使用 CacheAttribute 配合源生成缓存装饰器。")]
public static class GenerateCacheKey
{
    private static readonly ConcurrentDictionary<
        Type,
        Func<object, IEnumerable<string>>
    > GetterCache = new();

    public static string Build(string cachePrefixKey, string method, object? parameters = null)
    {
        var baseKey = $"{cachePrefixKey}:{method}";

        if (parameters == null)
        {
            return baseKey;
        }

        var getter = GetterCache.GetOrAdd(parameters.GetType(), BuildGetterExpression);

        var queryParams = getter(parameters).Where(p => !string.IsNullOrEmpty(p));
        return $"{baseKey}?{string.Join("&", queryParams)}";
    }

    private static Func<object, IEnumerable<string>> BuildGetterExpression(Type type)
    {
        var param = Expression.Parameter(typeof(object), "obj");
        var typedParam = Expression.Convert(param, type);

        var expression = type.GetProperties()
            .Where(p => p.CanRead)
            .Select(p => BuildPropertyExpression(typedParam, p));

        var arrayExpr = Expression.NewArrayInit(typeof(string), expression);
        var lambda = Expression.Lambda<Func<object, IEnumerable<string>>>(arrayExpr, param);
        return lambda.Compile();
    }

    #region 反射缓存

    private static readonly Lazy<MethodInfo> ConcatMethod = new(() =>
        typeof(string).GetMethod(
            nameof(string.Concat),
            BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy,
            Type.DefaultBinder,
            [typeof(string), typeof(string)],
            null
        ) ?? throw new NotSupportedException("string.Concat not found")
    );

    private static readonly Lazy<MethodInfo> JoinMethod = new(() =>
        typeof(string).GetMethod(nameof(string.Join), [typeof(string), typeof(IEnumerable<string>)])
        ?? throw new NotSupportedException("string.Join not found")
    );

    private static readonly Lazy<MethodInfo> CastMethod = new(() =>
        typeof(Enumerable)
            .GetMethod(nameof(Enumerable.Cast), BindingFlags.Static | BindingFlags.Public)
            ?.MakeGenericMethod(typeof(object))
        ?? throw new NotSupportedException("Enumerable.Cast not found")
    );

    private static readonly Lazy<MethodInfo> SelectMethod = new(() =>
        typeof(Enumerable)
            .GetMethods()
            .FirstOrDefault(x =>
                x.Name == nameof(Enumerable.Select)
                && x.GetParameters().Length == 2
                && x.GetParameters()[0].ParameterType.GetGenericTypeDefinition()
                    == typeof(IEnumerable<>)
                && x.GetParameters()[1].ParameterType.GetGenericTypeDefinition() == typeof(Func<,>)
            )
            ?.MakeGenericMethod(typeof(object), typeof(string))
        ?? throw new NotSupportedException("Enumerable.Select not found")
    );

    private static readonly Lazy<MethodInfo> WhereMethod = new(() =>
        typeof(Enumerable)
            .GetMethods()
            .FirstOrDefault(x =>
                x.Name == nameof(Enumerable.Where)
                && x.GetParameters().Length == 2
                && x.GetParameters()[0].ParameterType.GetGenericTypeDefinition()
                    == typeof(IEnumerable<>)
                && x.GetParameters()[1].ParameterType.GetGenericTypeDefinition() == typeof(Func<,>)
            )
            ?.MakeGenericMethod(typeof(string))
        ?? throw new NotSupportedException("Enumerable.Where not found")
    );

    private static readonly Lazy<MethodInfo> OrderByMethod = new(() =>
        typeof(Enumerable)
            .GetMethods()
            .FirstOrDefault(x =>
                x.Name == nameof(Enumerable.OrderBy)
                && x.GetParameters().Length == 2
                && x.GetParameters()[0].ParameterType.GetGenericTypeDefinition()
                    == typeof(IEnumerable<>)
            )
            ?.MakeGenericMethod(typeof(string), typeof(string))
        ?? throw new NotSupportedException("Enumerable.OrderBy not found")
    );

    private static readonly Lazy<MethodInfo> IsNullOrEmptyMethod = new(() =>
        typeof(string).GetMethod(nameof(string.IsNullOrEmpty), [typeof(string)])
        ?? throw new InvalidOperationException("string.IsNullOrEmpty method not found")
    );

    private static readonly Lazy<MethodInfo> ToStringMethod = new(() =>
        typeof(object).GetMethod(nameof(ToString), [])
        ?? throw new InvalidOperationException("object.ToString method not found")
    );

    private static readonly Lazy<MethodInfo> ReplaceMethod = new(() =>
        typeof(string).GetMethod(nameof(string.Replace), [typeof(string), typeof(string)])
        ?? throw new InvalidOperationException("string.Replace method not found")
    );

    #endregion

    private static readonly ConstantExpression EmptyStringExpr = Expression.Constant(string.Empty);
    private static readonly ConstantExpression CommaExpr = Expression.Constant(",");
    private static readonly ConstantExpression EncodedCommaExpr = Expression.Constant("%2C");

    /// <summary>
    /// 构建属性表达式用于生成缓存键
    /// </summary>
    /// <param name="paramExpression">参数表达式</param>
    /// <param name="property">属性信息</param>
    /// <returns>方法调用表达式</returns>
    private static MethodCallExpression BuildPropertyExpression(
        UnaryExpression paramExpression,
        PropertyInfo property
    )
    {
        var propExpr = Expression.Property(paramExpression, property);
        var nameExpr = Expression.Constant($"{property.Name}=");

        // 检查属性是否派生自IEnumerable(排除字符串类型)
        if (
            typeof(IEnumerable).IsAssignableFrom(property.PropertyType)
            && property.PropertyType != typeof(string)
        )
        {
            // 将属性转换为IEnumerable
            var castToObject = Expression.Call(CastMethod.Value, propExpr);

            // 创建ToString的lambda表达式,并在此处进行转义
            var itemParam = Expression.Parameter(typeof(object), "item");
            var nullCheck = Expression.Condition(
                Expression.Equal(itemParam, Expression.Constant(null)),
                EmptyStringExpr,
                Expression.Call(
                    Expression.Call(itemParam, ToStringMethod.Value),
                    ReplaceMethod.Value,
                    CommaExpr,
                    EncodedCommaExpr
                )
            );
            var toStringLambda = Expression.Lambda<Func<object, string>>(nullCheck, itemParam);

            // 对集合中的每个元素调用ToString并转义
            var selectCall = Expression.Call(SelectMethod.Value, castToObject, toStringLambda);

            // 创建 x => !string.IsNullOrEmpty(x) 表达式
            var whereParam = Expression.Parameter(typeof(string), "x");
            var whereBody = Expression.Not(Expression.Call(IsNullOrEmptyMethod.Value, whereParam));
            var whereLambda = Expression.Lambda<Func<string, bool>>(whereBody, whereParam);

            // 添加 Where 调用
            var whereCall = Expression.Call(WhereMethod.Value, selectCall, whereLambda);

            // 添加 OrderBy 调用,按字符串本身排序
            var orderByParam = Expression.Parameter(typeof(string), "s");
            var orderByLambda = Expression.Lambda<Func<string, string>>(orderByParam, orderByParam);
            var orderByCall = Expression.Call(OrderByMethod.Value, whereCall, orderByLambda);

            // 将集合连接为字符串
            var joinedString = Expression.Call(JoinMethod.Value, CommaExpr, orderByCall);

            var collectionNullCheck = Expression.Condition(
                Expression.Equal(propExpr, Expression.Constant(null)),
                EmptyStringExpr,
                joinedString
            );
            var concatExpr = Expression.Call(ConcatMethod.Value, nameExpr, collectionNullCheck);

            return concatExpr;
        }
        else
        {
            // 根据属性类型处理null检查
            Expression valueExpr;
            if (IsNullableType(property.PropertyType))
            {
                // 可空类型
                valueExpr = Expression.Condition(
                    Expression.Equal(propExpr, Expression.Constant(null)),
                    EmptyStringExpr,
                    Expression.Call(propExpr, ToStringMethod.Value)
                );
            }
            else if (property.PropertyType.IsValueType)
            {
                // 值类型,直接调用ToString
                valueExpr = Expression.Call(propExpr, ToStringMethod.Value);
            }
            else
            {
                // 引用类型
                valueExpr = Expression.Condition(
                    Expression.Equal(propExpr, Expression.Constant(null)),
                    EmptyStringExpr,
                    Expression.Call(propExpr, ToStringMethod.Value)
                );
            }
            var concatExpr = Expression.Call(ConcatMethod.Value, nameExpr, valueExpr);
            return concatExpr;
        }
    }

    /// <summary>
    /// 检查类型是否为可空值类型
    /// </summary>
    private static bool IsNullableType(Type type)
    {
        return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
    }
}
⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

代码解释

这是一个缓存键生成器的工具类,用于根据方法名和参数对象动态生成缓存键字符串。该类已被标记为过时(Obsolete),建议使用新的 CacheAttribute 配合源生成器。

核心功能

1. 主入口方法 Build

public static string Build(string cachePrefixKey, string method, object? parameters = null)
  • 生成格式:{前缀}:{方法名}?参数1=值1&参数2=值2
  • 如果参数为 null,返回 {前缀}:{方法名}
  • 使用缓存的 getter 函数提取参数对象的所有属性值

2. 表达式树缓存机制

private static readonly ConcurrentDictionary<Type, Func<object, IEnumerable<string>>> GetterCache
  • 使用 ConcurrentDictionary 缓存每种参数类型的属性读取委托
  • 首次遇到某类型时,通过 BuildGetterExpression 动态编译表达式树生成高性能 getter

3. 动态编译属性读取器

BuildGetterExpression 方法:

  • 使用 Expression API 为参数类型的所有可读属性生成读取逻辑
  • 编译为 Func<object, IEnumerable<string>> 委托(避免反射性能损耗)
  • 返回格式为 属性名=值 的字符串数组

关键技术点

集合属性处理(BuildPropertyExpression 方法)

当属性是 IEnumerable 类型(非 string)时:

  1. 类型转换:Cast 为 IEnumerable<object>
  2. 逗号转义:将元素的逗号替换为 %2C(避免破坏缓存键格式)
  3. 过滤空值:Where 过滤空字符串
  4. 排序:OrderBy 确保集合元素顺序一致(重要:避免 [1,2] 和 [2,1] 生成不同缓存键)
  5. 连接:用逗号连接为单个字符串

普通属性处理

  • 值类型:直接调用 ToString()
  • 可空类型/引用类型:空值检查,null 返回空字符串

反射缓存优化

使用 Lazy<MethodInfo> 延迟缓存常用方法:

  • string.Concat / string.Join
  • Enumerable.Cast / Select / Where / OrderBy
  • string.IsNullOrEmpty / Replace

示例

假设调用:

var param = new { UserId = 123, Tags = new[] { "A", "B" }, Name = (string?)null };
var key = GenerateCacheKey.Build("user", "GetProfile", param);

生成结果:

user:GetProfile?UserId=123&Tags=A,B&Name=

设计优点

  1. 高性能:表达式树编译 + 类型级缓存,避免反射开销
  2. 一致性:集合排序确保参数顺序不影响缓存键
  3. 安全性:逗号转义防止键格式混淆

注意事项

  • 已过时,建议迁移到源生成方案
  • 集合元素需支持 ToString()
  • 不支持嵌套对象(仅处理顶层属性)
评论加载中...