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() ?? "";
}
}