网站首页 网站源码
website
站点相关全部源代码,隐藏了一些关于服务器的信息
using System.Collections;
using System.Collections.Concurrent;
using System.Linq.Expressions;

namespace Dpz.Core.Service;

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分析的结果,请根据实际情况进行判断。

上述代码实现了一个用于生成缓存键的工具类 GenerateCacheKey,主要用于在缓存系统中创建唯一的键,以便于存储和检索数据。以下是代码的主要功能和结构的详细解释:

主要功能

  1. 生成缓存键

    • Build 方法接受一个缓存前缀键、方法名和可选的参数对象。它根据这些信息生成一个唯一的缓存键。
    • 如果没有提供参数,返回的键仅由前缀和方法名组成。
  2. 参数处理

    • 如果提供了参数,代码会使用反射生成一个表达式树,以提取参数对象的属性值,并将这些值转换为字符串形式。
    • 生成的字符串会被用作缓存键的一部分,确保相同的参数生成相同的缓存键。
  3. 表达式树

    • 使用 Expression 类构建表达式树,以动态生成提取属性值的代码。这种方式比反射更高效。
    • 代码会处理集合类型的属性,确保将集合中的每个元素转换为字符串,并按字母顺序排序。
  4. 字符串处理

    • 生成的字符串会进行处理,例如将逗号替换为 URL 编码形式(%2C),以确保生成的缓存键在 URL 中是安全的。
  5. 反射缓存

    • 代码中使用了多个 Lazy<MethodInfo> 来缓存反射获取的方法信息,以提高性能,避免重复的反射调用。
  6. 可空类型处理

    • 代码能够识别可空类型,并在生成字符串时进行适当的处理,确保不会抛出异常。

代码结构

  • GetterCache:一个 ConcurrentDictionary,用于缓存不同类型的 getter 函数,以避免重复生成相同类型的表达式树。

  • Build 方法:主入口方法,负责生成缓存键。

  • BuildGetterExpression 方法:根据类型生成一个提取属性值的表达式。

  • BuildPropertyExpression 方法:构建用于提取单个属性值的表达式,处理集合类型和基本类型的不同情况。

  • 反射缓存:多个 Lazy<MethodInfo> 用于缓存常用方法的信息,以提高性能。

  • IsNullableType 方法:检查给定类型是否为可空类型。

总结

整体而言,这段代码提供了一种高效的方式来生成基于对象属性的缓存键,适用于需要缓存的场景,如 API 响应缓存、数据库查询结果缓存等。通过使用表达式树和反射缓存,代码在性能上进行了优化,确保在高并发环境下也能高效运行。

loading