diff --git a/src/Infrastructure/BotSharp.Abstraction/BotSharp.Abstraction.csproj b/src/Infrastructure/BotSharp.Abstraction/BotSharp.Abstraction.csproj index 2e3aa24c..94c9e27c 100644 --- a/src/Infrastructure/BotSharp.Abstraction/BotSharp.Abstraction.csproj +++ b/src/Infrastructure/BotSharp.Abstraction/BotSharp.Abstraction.csproj @@ -36,6 +36,7 @@ + diff --git a/src/Infrastructure/BotSharp.Abstraction/Infrastructures/ICacheKey.cs b/src/Infrastructure/BotSharp.Abstraction/Infrastructures/ICacheKey.cs new file mode 100644 index 00000000..87c3bc69 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Infrastructures/ICacheKey.cs @@ -0,0 +1,6 @@ +namespace BotSharp.Abstraction.Infrastructures; + +public interface ICacheKey +{ + string GetCacheKey(); +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Infrastructures/ICacheService.cs b/src/Infrastructure/BotSharp.Abstraction/Infrastructures/ICacheService.cs new file mode 100644 index 00000000..692e9e4a --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Infrastructures/ICacheService.cs @@ -0,0 +1,8 @@ +namespace BotSharp.Abstraction.Infrastructures; + +public interface ICacheService +{ + Task GetAsync(string key); + Task GetAsync(string key, Type type); + Task SetAsync(string key, T value, TimeSpan? expiry); +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Infrastructures/SharpCacheAttribute.cs b/src/Infrastructure/BotSharp.Abstraction/Infrastructures/SharpCacheAttribute.cs new file mode 100644 index 00000000..5b7ac961 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Infrastructures/SharpCacheAttribute.cs @@ -0,0 +1,63 @@ +using BotSharp.Abstraction.Infrastructures; +using Microsoft.Extensions.DependencyInjection; +using Rougamo; +using Rougamo.Context; + +namespace BotSharp.Core.Infrastructures; + +public class SharpCacheAttribute : MoAttribute +{ + public static IServiceProvider Services { get; set; } = null!; + + private int _minutes; + public SharpCacheAttribute(int minutes) + { + _minutes = minutes; + } + + public override void OnEntry(MethodContext context) + { + var cache = Services.GetRequiredService(); + + var key = GetCacheKey(context); + var value = cache.GetAsync(key, context.TaskReturnType).Result; + if (value != null) + { + context.ReplaceReturnValue(this, value); + } + } + + public override void OnSuccess(MethodContext context) + { + var cache = Services.GetRequiredService(); + + if (context.ReturnValue != null) + { + var key = GetCacheKey(context); + cache.SetAsync(key, context.ReturnValue, new TimeSpan(0, _minutes, 0)).Wait(); + } + } + + private string GetCacheKey(MethodContext context) + { + // 根据用户级别生成缓存key + var key = string.Empty; + foreach (var arg in context.Arguments) + { + if (arg is null) + { + key += "-" + ""; + } + else if (arg is ICacheKey withCacheKey) + { + key += "-" + withCacheKey.GetCacheKey(); + } + else + { + key += "-" + arg.ToString(); + } + } + + return key; + } +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Utilities/Pagination.cs b/src/Infrastructure/BotSharp.Abstraction/Utilities/Pagination.cs index ad4f2e3f..bb5d74e4 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Utilities/Pagination.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Utilities/Pagination.cs @@ -1,6 +1,8 @@ +using BotSharp.Abstraction.Infrastructures; + namespace BotSharp.Abstraction.Utilities; -public class Pagination +public class Pagination : ICacheKey { private int _page; private int _size; @@ -39,6 +41,9 @@ public class Pagination } public bool ReturnTotal { get; set; } = true; + + public string GetCacheKey() + => $"{_page}_{_size}_{Sort}_{Order}"; } public class PagedItems diff --git a/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs b/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs index 61c45c58..3d8503e1 100644 --- a/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs +++ b/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs @@ -1,11 +1,12 @@ -using BotSharp.Abstraction.Functions; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; +using BotSharp.Abstraction.Functions; using BotSharp.Core.Plugins; using BotSharp.Abstraction.Settings; using BotSharp.Abstraction.Options; using BotSharp.Abstraction.Messaging.JsonConverters; using BotSharp.Abstraction.Users.Settings; +using BotSharp.Abstraction.Infrastructures; namespace BotSharp.Core; @@ -15,6 +16,7 @@ public static class BotSharpCoreExtensions { services.AddScoped(); services.AddScoped(); + services.AddSingleton(); services.AddSingleton(); RegisterPlugins(services, config); @@ -54,6 +56,9 @@ public static class BotSharpCoreExtensions app.ApplicationServices.GetRequiredService().Configure(app); + // Set root services for SharpCacheAttribute + SharpCacheAttribute.Services = app.ApplicationServices; + return app; } diff --git a/src/Infrastructure/BotSharp.Core/Infrastructures/CacheService.cs b/src/Infrastructure/BotSharp.Core/Infrastructures/CacheService.cs new file mode 100644 index 00000000..45767fc2 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Infrastructures/CacheService.cs @@ -0,0 +1,74 @@ +using BotSharp.Abstraction.Infrastructures; +using Newtonsoft.Json; +using StackExchange.Redis; + +namespace BotSharp.Core.Infrastructures; + +public class CacheService : ICacheService +{ + private readonly BotSharpDatabaseSettings _settings; + private static ConnectionMultiplexer redis = null!; + + public CacheService(BotSharpDatabaseSettings settings) + { + _settings = settings; + } + + public async Task GetAsync(string key) + { + if (string.IsNullOrEmpty(_settings.Redis)) + { + return default; + } + + if (redis == null) + { + redis = ConnectionMultiplexer.Connect(_settings.Redis); + } + + var db = redis.GetDatabase(); + var value = await db.StringGetAsync(key); + + if (value.HasValue) + { + return JsonConvert.DeserializeObject(value); + } + + return default; + } + + public async Task GetAsync(string key, Type type) + { + if (string.IsNullOrEmpty(_settings.Redis)) + { + return default; + } + + if (redis == null) + { + redis = ConnectionMultiplexer.Connect(_settings.Redis); + } + + var db = redis.GetDatabase(); + var value = await db.StringGetAsync(key); + + if (value.HasValue) + { + return JsonConvert.DeserializeObject(value, type); + } + + return default; + } + + + public async Task SetAsync(string key, T value, TimeSpan? expiry) + { + if (string.IsNullOrEmpty(_settings.Redis)) + { + return; + } + + var db = redis.GetDatabase(); + await db.StringSetAsync(key, JsonConvert.SerializeObject(value), expiry); + } +}