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

namespace Dpz.Core.Service;

[AttributeUsage(AttributeTargets.Method)]
public class CacheModuleAttribute : AbstractInterceptorAttribute
{
    [FromServiceContext] public ILogger<CacheModuleAttribute>? Logger { get; set; }

    [FromServiceContext] public IHybridCachingProvider? HybridCachingProvider { get; set; }

    /// <summary>
    /// 是否清除缓存 如果清除将移除同一前缀缓存,并且不会拦截方法
    /// </summary>
    public bool Clear { get; set; }

    /// <summary>
    /// 过期时间 秒,默认7天
    /// </summary>
    public int Expiration { get; set; }

    /// <summary>
    /// 缓存Key前缀 如果不设置,将自动生成
    /// </summary>
    public string? PrefixKey { get; set; }

    /// <summary>
    /// 需要移除的前缀
    /// </summary>
    public string?[]? RemovePrefixKeys { get; set; }

    public override async Task Invoke(AspectContext context, AspectDelegate next)
    {
        // var parameterInfos = context.ImplementationMethod.GetParameters();
        // var parameters = from x in parameterInfos
        //     let index = parameterInfos.IndexOf(x)
        //     select $"{x.Name}={GetValue(context.Parameters[index])}";

        var parameters = context.GetParameters()?.Select(x => $"{x.Name}={GetValue(x.Value)}") ??
                         Array.Empty<string>();
        var prefixKey = string.IsNullOrEmpty(PrefixKey) ? context.Implementation.ToString() : PrefixKey;
        var key = $"{prefixKey}:{context.ImplementationMethod.Name}?{string.Join("&", parameters)}";
        var hasCache = await BeforeAsync(context, next, key);
        if (!hasCache)
        {
            await AfterAsync(context, key);
        }
    }


    private static readonly ConcurrentDictionary<Type, MethodInfo>
        TypeofTaskResultMethod = new();

    private static object? ReturnValue(Type returnType, object value) => TypeofTaskResultMethod
        .GetOrAdd(returnType,
            _ => typeof(Task).GetMethods()
                .First(p => p is { Name: "FromResult", ContainsGenericParameters: true })
                .MakeGenericMethod(returnType)).Invoke(null, new[] { value });

    /// <summary>
    /// 执行前
    /// </summary>
    /// <param name="context"></param>
    /// <param name="next"></param>
    /// <param name="key"></param>
    /// <returns>是否有缓存</returns>
    private async Task<bool> BeforeAsync(AspectContext context, AspectDelegate next, string key)
    {
        if (Clear)
        {
            await ClearCacheAsync(context.Implementation.ToString());
            await next(context);
            return true;
        }

        try
        {
            var returnType = context.IsAsync()
                ? context.ServiceMethod.ReturnType.GetGenericArguments().First()
                : context.ServiceMethod.ReturnType;
            var cacheValue = await GetCacheAsync(key, returnType);
            if (cacheValue != null)
            {
                context.ReturnValue = context.IsAsync() ? ReturnValue(returnType, cacheValue) : cacheValue;
                await context.Break();
                return true;
            }
        }
        catch (Exception e)
        {
            Logger?.LogError(e, "AOP before invoke fail");
        }

        await next(context);
        return false;
    }

    /// <summary>
    /// 执行后
    /// </summary>
    /// <param name="context"></param>
    /// <param name="key"></param>
    private async Task AfterAsync(AspectContext context, string key)
    {
        try
        {
            var implementReturnValue = context.IsAsync() ? await context.UnwrapAsyncReturnValue() : context.ReturnValue;
            if (implementReturnValue == null)
                return;
            await SetCacheAsync(key, implementReturnValue);
        }
        catch (Exception e)
        {
            Logger?.LogError(e, "AOP after invoke fail");
        }
    }

    private async Task ClearCacheAsync(string? defaultPrefixKey)
    {
        if (RemovePrefixKeys != null && RemovePrefixKeys.Length != 0)
        {
            foreach (var key in RemovePrefixKeys)
            {
                await HybridCachingProvider?.RemoveByPrefixAsync(key)!;
                Logger?.LogInformation("{Key} Cleared",key);
            }
        }
        else
        {
            var prefixKey = string.IsNullOrEmpty(PrefixKey) ? defaultPrefixKey : PrefixKey;
            if (!string.IsNullOrEmpty(prefixKey))
            {
                await HybridCachingProvider?.RemoveByPrefixAsync(prefixKey)!;
                Logger?.LogInformation("{Key} Cleared",prefixKey);
            }
        }
    }

    private async Task<object?> GetCacheAsync(string key, Type returnType)
    {
        if (returnType.IsGenericType)
        {
            var returnTypeParameters = returnType.GetGenericArguments();
            if (returnType == typeof(PagedList<>).MakeGenericType(returnTypeParameters) ||
                returnType == typeof(IPagedList<>).MakeGenericType(returnTypeParameters))
            {
                var schemeType = typeof(PagedListSerializationScheme<>).MakeGenericType(returnTypeParameters);
                var value = await HybridCachingProvider?.GetAsync(key, schemeType)!;
                if (value == null)
                    return null;
                var currents = schemeType.GetProperty("Currents")?.GetValue(value);
                var currentPageIndex = schemeType.GetProperty("CurrentPageIndex")?.GetValue(value);
                var pageSize = schemeType.GetProperty("PageSize")?.GetValue(value);
                var totalItemCount = schemeType.GetProperty("TotalItemCount")?.GetValue(value);

                var instanceType = typeof(PagedList<>).MakeGenericType(returnTypeParameters);

                return Activator.CreateInstance(instanceType, currents, currentPageIndex, pageSize, totalItemCount);
            }
        }

        return await HybridCachingProvider?.GetAsync(key, returnType)!;
    }

    private async Task SetCacheAsync(string key, object value)
    {
        var expiration = Expiration > 0 ? TimeSpan.FromSeconds(Expiration) : TimeSpan.FromDays(7);

        var valueType = value.GetType();
        if (valueType.IsGenericType)
        {
            var typeParameters = valueType.GetGenericArguments();
            if (valueType == typeof(PagedList<>).MakeGenericType(typeParameters))
            {
                var toListMethod = typeof(System.Linq.Enumerable)
                    .GetMethods()
                    .FirstOrDefault(x =>
                    {
                        var parameters = x.GetParameters();
                        return x.Name == "ToList" && parameters.Length == 1;
                    })?.MakeGenericMethod(typeParameters);
                if (toListMethod == null)
                    return;

                var list = toListMethod.Invoke(null, new[] { value });


                var schemeType = typeof(PagedListSerializationScheme<>).MakeGenericType(typeParameters);
                var obj = Activator.CreateInstance(schemeType);
                if (obj == null)
                    return;

                foreach (var property in schemeType.GetProperties())
                {
                    var propertyValue = valueType.GetProperty(property.Name)?.GetValue(value);
                    if (property.CanWrite && propertyValue != null)
                    {
                        property.SetValue(obj, propertyValue);
                    }
                }

                schemeType.GetProperty("Currents")?.SetValue(obj, list);
                await HybridCachingProvider?.SetAsync(key, obj, expiration)!;
                Logger?.LogInformation("Invoke method after set page cache complete,cache key:{Key}",key);
                return;
            }
        }

        await HybridCachingProvider?.SetAsync(key, value, expiration)!;
        Logger?.LogInformation("Invoke method set cache complete,cache key:{Key}",key);
    }

    [MessagePackObject(keyAsPropertyName: true)]
    public class PagedListSerializationScheme<T>
    {
        /// <summary>
        /// current page data
        /// </summary>
        public List<T> Currents { get; set; } = new();

        /// <summary>
        /// 当前页索引
        /// </summary>
        public int CurrentPageIndex { get; set; }

        /// <summary>
        /// 每页显示的记录数
        /// </summary>
        public int PageSize { get; set; }

        /// <summary>
        /// 要分页的数据总数
        /// </summary>
        public int TotalItemCount { get; set; }

        /// <summary>
        /// 总页数
        /// </summary>
        public int TotalPageCount => (int)Math.Ceiling(TotalItemCount / (double)PageSize);

        /// <summary>
        /// 开始记录索引
        /// </summary>
        public int StartItemIndex => (CurrentPageIndex - 1) * PageSize + 1;

        /// <summary>
        /// 结束记录索引
        /// </summary>
        public int EndItemIndex =>
            TotalItemCount > CurrentPageIndex * PageSize ? CurrentPageIndex * PageSize : TotalItemCount;
    }

    private string GetValue(object? val)
    {
        if (val is null) return "";
        if (val is IEnumerable paramArray and not string)
        {
            var concatValue = string.Empty;
            return paramArray.Cast<object>()
                .Aggregate(concatValue, (current, paramValue) => current + (paramValue + ";"));
        }

        return val.ToString() ?? "";
    }
}
loading