Merge branch 'SciSharp:master' into master
This commit is contained in:
commit
3459c963af
|
|
@ -1,4 +1,4 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>$(TargetFramework)</TargetFramework>
|
||||
|
|
@ -37,8 +37,7 @@
|
|||
<PackageReference Include="System.Text.Json" Version="8.0.5" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
<PackageReference Include="Rougamo.Fody" Version="4.0.0" />
|
||||
<PackageReference Include="AspectInjector" Version="2.8.2" />
|
||||
<PackageReference Include="Rougamo.Fody" Version="4.0.4" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BotSharp.Abstraction.Infrastructures.Enums
|
||||
{
|
||||
public enum CacheType
|
||||
{
|
||||
MemoryCache,
|
||||
RedisCache
|
||||
}
|
||||
}
|
||||
|
|
@ -6,4 +6,5 @@ public interface ICacheService
|
|||
Task<object> GetAsync(string key, Type type);
|
||||
Task SetAsync<T>(string key, T value, TimeSpan? expiry);
|
||||
Task RemoveAsync(string key);
|
||||
Task ClearCacheAsync(string prefix);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,33 +6,37 @@ using Rougamo.Context;
|
|||
|
||||
namespace BotSharp.Core.Infrastructures;
|
||||
|
||||
public class SharpCacheAttribute : MoAttribute
|
||||
public class SharpCacheAttribute : AsyncMoAttribute
|
||||
{
|
||||
public static IServiceProvider Services { get; set; } = null!;
|
||||
private static readonly object NullMarker = new { __is_null = "$_is_null" };
|
||||
|
||||
private int _minutes;
|
||||
private readonly int _minutes;
|
||||
private readonly bool _perInstanceCache;
|
||||
private readonly ICacheService _cache;
|
||||
private readonly SharpCacheSettings _settings;
|
||||
|
||||
public SharpCacheAttribute(int minutes = 60)
|
||||
public SharpCacheAttribute(int minutes = 60, bool perInstanceCache = false)
|
||||
{
|
||||
_minutes = minutes;
|
||||
_perInstanceCache = perInstanceCache;
|
||||
_cache = Services.GetRequiredService<ICacheService>();
|
||||
_settings = Services.GetRequiredService<SharpCacheSettings>();
|
||||
}
|
||||
|
||||
public override void OnEntry(MethodContext context)
|
||||
public override async ValueTask OnEntryAsync(MethodContext context)
|
||||
{
|
||||
var settings = Services.GetRequiredService<SharpCacheSettings>();
|
||||
if (!settings.Enabled)
|
||||
if (!_settings.Enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var cache = Services.GetRequiredService<ICacheService>();
|
||||
|
||||
var key = GetCacheKey(settings, context);
|
||||
var value = cache.GetAsync(key, context.TaskReturnType).Result;
|
||||
var key = GetCacheKey(context);
|
||||
var value = await _cache.GetAsync(key, context.TaskReturnType);
|
||||
if (value != null)
|
||||
{
|
||||
// check if the cache is out of date
|
||||
var isOutOfDate = IsOutOfDate(context, value).Result;
|
||||
var isOutOfDate = await IsOutOfDate(context, value);
|
||||
|
||||
if (!isOutOfDate)
|
||||
{
|
||||
|
|
@ -41,10 +45,9 @@ public class SharpCacheAttribute : MoAttribute
|
|||
}
|
||||
}
|
||||
|
||||
public override void OnSuccess(MethodContext context)
|
||||
public override async ValueTask OnSuccessAsync(MethodContext context)
|
||||
{
|
||||
var settings = Services.GetRequiredService<SharpCacheSettings>();
|
||||
if (!settings.Enabled)
|
||||
if (!_settings.Enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
@ -57,12 +60,10 @@ public class SharpCacheAttribute : MoAttribute
|
|||
return;
|
||||
}
|
||||
|
||||
var cache = Services.GetRequiredService<ICacheService>();
|
||||
|
||||
if (context.ReturnValue != null)
|
||||
{
|
||||
var key = GetCacheKey(settings, context);
|
||||
cache.SetAsync(key, context.ReturnValue, new TimeSpan(0, _minutes, 0)).Wait();
|
||||
var key = GetCacheKey(context);
|
||||
await _cache.SetAsync(key, context.ReturnValue, new TimeSpan(0, _minutes, 0));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -71,25 +72,45 @@ public class SharpCacheAttribute : MoAttribute
|
|||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
private string GetCacheKey(SharpCacheSettings settings, MethodContext context)
|
||||
{
|
||||
var key = settings.Prefix + ":" + context.Method.Name;
|
||||
foreach (var arg in context.Arguments)
|
||||
{
|
||||
if (arg is null)
|
||||
{
|
||||
key += "-" + "<NULL>";
|
||||
}
|
||||
else if (arg is ICacheKey withCacheKey)
|
||||
{
|
||||
key += "-" + withCacheKey.GetCacheKey();
|
||||
}
|
||||
else
|
||||
{
|
||||
key += "-" + arg.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
return key;
|
||||
private string GetCacheKey(MethodContext context)
|
||||
{
|
||||
var prefixKey = GetPrefixKey(context.Method.Name);
|
||||
var argsKey = string.Join("_", context.Arguments.Select(arg => GetCacheKeyByArg(arg)));
|
||||
|
||||
if (_perInstanceCache && context.Target != null)
|
||||
{
|
||||
return $"{prefixKey}-{context.Target.GetHashCode()}_{argsKey}";
|
||||
}
|
||||
else
|
||||
{
|
||||
return $"{prefixKey}_{argsKey}";
|
||||
}
|
||||
}
|
||||
|
||||
private string GetPrefixKey(string name)
|
||||
{
|
||||
return _settings.Prefix + ":" + name;
|
||||
}
|
||||
|
||||
private string GetCacheKeyByArg(object? arg)
|
||||
{
|
||||
if (arg is null)
|
||||
{
|
||||
return NullMarker.GetHashCode().ToString();
|
||||
}
|
||||
else if (arg is ICacheKey withCacheKey)
|
||||
{
|
||||
return withCacheKey.GetCacheKey();
|
||||
}
|
||||
else
|
||||
{
|
||||
return arg.GetHashCode().ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ClearCacheAsync()
|
||||
{
|
||||
await _cache.ClearCacheAsync(_settings.Prefix);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ namespace BotSharp.Abstraction.Infrastructures;
|
|||
|
||||
public class SharpCacheSettings
|
||||
{
|
||||
public bool Enabled { get; set; } = false;
|
||||
public string Prefix { get; set; } = "cache";
|
||||
public bool Enabled { get; set; } = true;
|
||||
public CacheType CacheType { get; set; } = Enums.CacheType.MemoryCache;
|
||||
public string Prefix { get; set; } = "cache";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,180 +0,0 @@
|
|||
using AspectInjector.Broker;
|
||||
using BotSharp.Abstraction.Shared;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Reflection;
|
||||
|
||||
namespace BotSharp.Abstraction.SideCar.Attributes;
|
||||
|
||||
[Aspect(Scope.PerInstance)]
|
||||
public class SideCarAspect
|
||||
{
|
||||
[Advice(Kind.Around)]
|
||||
public object Handle(
|
||||
[Argument(Source.Target)] Func<object[], object> target,
|
||||
[Argument(Source.Arguments)] object[] args,
|
||||
[Argument(Source.Instance)] object instance,
|
||||
[Argument(Source.ReturnType)] Type retType,
|
||||
[Argument(Source.Name)] string name,
|
||||
[Argument(Source.Metadata)] MethodBase metaData,
|
||||
[Argument(Source.Triggers)] Attribute[] triggers)
|
||||
{
|
||||
object value;
|
||||
var serviceProvider = ((IHaveServiceProvider)instance).ServiceProvider;
|
||||
|
||||
if (typeof(Task).IsAssignableFrom(retType))
|
||||
{
|
||||
var syncResultType = retType.IsConstructedGenericType ? retType.GenericTypeArguments[0] : typeof(void);
|
||||
value = CallAsyncMethod(serviceProvider, syncResultType, name, target, args);
|
||||
}
|
||||
else
|
||||
{
|
||||
value = CallSyncMethod(serviceProvider, retType, name, target, args);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
private static MethodInfo GetMethod(string name)
|
||||
{
|
||||
return typeof(SideCarAspect).GetMethod(name, BindingFlags.NonPublic | BindingFlags.Static);
|
||||
}
|
||||
|
||||
|
||||
private (IConversationSideCar?, MethodInfo?) GetSideCarMethod(IServiceProvider serviceProvider, string methodName, object[] args)
|
||||
{
|
||||
var sidecar = serviceProvider.GetService<IConversationSideCar>();
|
||||
var argTypes = args.Select(x => x.GetType()).ToArray();
|
||||
var sidecarMethod = sidecar?.GetType()?.GetMethods(BindingFlags.Public | BindingFlags.Instance)
|
||||
.FirstOrDefault(x => x.Name == methodName
|
||||
&& x.GetParameters().Length == argTypes.Length
|
||||
&& x.GetParameters().Select(p => p.ParameterType)
|
||||
.Zip(argTypes, (paramType, argType) => paramType.IsAssignableFrom(argType)).All(y => y));
|
||||
|
||||
return (sidecar, sidecarMethod);
|
||||
}
|
||||
|
||||
private object CallAsyncMethod(IServiceProvider serviceProvider, Type retType, string methodName, Func<object[], object> target, object[] args)
|
||||
{
|
||||
object value;
|
||||
|
||||
var (sidecar, sidecarMethod) = GetSideCarMethod(serviceProvider, methodName, args);
|
||||
var enabled = sidecar != null && sidecar.IsEnabled() && sidecarMethod != null;
|
||||
|
||||
if (retType == typeof(void))
|
||||
{
|
||||
if (enabled)
|
||||
{
|
||||
|
||||
value = GetMethod(nameof(CallAsync)).Invoke(this, [sidecar, sidecarMethod, args]);
|
||||
}
|
||||
else
|
||||
{
|
||||
value = GetMethod(nameof(WrapAsync)).Invoke(this, [target, args]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (enabled)
|
||||
{
|
||||
value = GetMethod(nameof(CallGenericAsync)).MakeGenericMethod(retType).Invoke(this, [sidecar, sidecarMethod, args]);
|
||||
}
|
||||
else
|
||||
{
|
||||
value = GetMethod(nameof(WrapGenericAsync)).MakeGenericMethod(retType).Invoke(this, [target, args]);
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private object CallSyncMethod(IServiceProvider serviceProvider, Type retType, string methodName, Func<object[], object> target, object[] args)
|
||||
{
|
||||
object value;
|
||||
|
||||
var (sidecar, sidecarMethod) = GetSideCarMethod(serviceProvider, methodName, args);
|
||||
var enabled = sidecar != null && sidecar.IsEnabled() && sidecarMethod != null;
|
||||
|
||||
if (retType == typeof(void))
|
||||
{
|
||||
if (enabled)
|
||||
{
|
||||
value = GetMethod(nameof(CallSync)).Invoke(this, [sidecar, sidecarMethod, args]);
|
||||
}
|
||||
else
|
||||
{
|
||||
value = GetMethod(nameof(WrapSync)).Invoke(this, [target, args]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (enabled)
|
||||
{
|
||||
value = GetMethod(nameof(CallGenericSync)).MakeGenericMethod(retType).Invoke(this, [sidecar, sidecarMethod, args]);
|
||||
}
|
||||
else
|
||||
{
|
||||
value = GetMethod(nameof(WrapGenericSync)).MakeGenericMethod(retType).Invoke(this, [target, args]);
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
#region Call Side car method
|
||||
private static async Task<T> CallGenericAsync<T>(object instance, MethodInfo method, object[] args)
|
||||
{
|
||||
var res = await (Task<T>)method.Invoke(instance, args);
|
||||
return res;
|
||||
}
|
||||
|
||||
private static async Task CallAsync(object instance, MethodInfo method, object[] args)
|
||||
{
|
||||
await (Task)method.Invoke(instance, args);
|
||||
return;
|
||||
}
|
||||
|
||||
private static T CallGenericSync<T>(object instance, MethodInfo method, object[] args)
|
||||
{
|
||||
var res = (T)method.Invoke(instance, args);
|
||||
return res;
|
||||
}
|
||||
|
||||
private static void CallSync(object instance, MethodInfo method, object[] args)
|
||||
{
|
||||
method.Invoke(instance, args);
|
||||
return;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Call original method
|
||||
private static T WrapGenericSync<T>(Func<object[], object> target, object[] args)
|
||||
{
|
||||
T res;
|
||||
res = (T)target(args);
|
||||
return res;
|
||||
}
|
||||
|
||||
private static async Task<T> WrapGenericAsync<T>(Func<object[], object> target, object[] args)
|
||||
{
|
||||
T res;
|
||||
res = await (Task<T>)target(args);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
private static void WrapSync(Func<object[], object> target, object[] args)
|
||||
{
|
||||
target(args);
|
||||
return;
|
||||
}
|
||||
|
||||
private static async Task WrapAsync(Func<object[], object> target, object[] args)
|
||||
{
|
||||
await (Task)target(args);
|
||||
return;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
|
@ -1,13 +1,145 @@
|
|||
using AspectInjector.Broker;
|
||||
using System.Reflection;
|
||||
using Rougamo;
|
||||
using Rougamo.Context;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using BotSharp.Abstraction.Shared;
|
||||
|
||||
|
||||
namespace BotSharp.Abstraction.SideCar.Attributes;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Method, Inherited = true)]
|
||||
[Injection(typeof(SideCarAspect))]
|
||||
public class SideCarAttribute : Attribute
|
||||
public class SideCarAttribute : AsyncMoAttribute
|
||||
{
|
||||
public SideCarAttribute()
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
|
||||
public override async ValueTask OnEntryAsync(MethodContext context)
|
||||
{
|
||||
object? value = null;
|
||||
var isHandled = false;
|
||||
var methodName = context.Method.Name;
|
||||
var methodArgs = context.Arguments ?? [];
|
||||
var instance = context.Target;
|
||||
var retType = context.ReturnType;
|
||||
|
||||
var serviceProvider = ((IHaveServiceProvider)instance).ServiceProvider;
|
||||
var (sidecar, sidecarMethod) = GetSideCarMethod(serviceProvider, methodName, retType, methodArgs);
|
||||
if (sidecar == null || sidecarMethod == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof(Task).IsAssignableFrom(retType))
|
||||
{
|
||||
var syncResultType = retType.IsConstructedGenericType ? retType.GenericTypeArguments[0] : typeof(void);
|
||||
(isHandled, value) = CallAsyncMethod(sidecar, sidecarMethod, syncResultType, methodArgs);
|
||||
}
|
||||
else
|
||||
{
|
||||
(isHandled, value) = CallSyncMethod(sidecar, sidecarMethod, retType, methodArgs);
|
||||
}
|
||||
|
||||
if (isHandled)
|
||||
{
|
||||
context.ReplaceReturnValue(this, value);
|
||||
}
|
||||
}
|
||||
|
||||
private static MethodInfo GetMethod(string name)
|
||||
{
|
||||
return typeof(SideCarAttribute).GetMethod(name, BindingFlags.NonPublic | BindingFlags.Static);
|
||||
}
|
||||
|
||||
|
||||
private (IConversationSideCar?, MethodInfo?) GetSideCarMethod(IServiceProvider serviceProvider, string methodName, Type retType, object[] args)
|
||||
{
|
||||
var sidecar = serviceProvider.GetService<IConversationSideCar>();
|
||||
var argTypes = args.Select(x => x.GetType()).ToArray();
|
||||
var sidecarMethod = sidecar?.GetType()?.GetMethods(BindingFlags.Public | BindingFlags.Instance)
|
||||
.FirstOrDefault(x => x.Name == methodName
|
||||
&& x.ReturnType == retType
|
||||
&& x.GetParameters().Length == argTypes.Length
|
||||
&& x.GetParameters().Select(p => p.ParameterType)
|
||||
.Zip(argTypes, (paramType, argType) => paramType.IsAssignableFrom(argType)).All(y => y));
|
||||
|
||||
return (sidecar, sidecarMethod);
|
||||
}
|
||||
|
||||
private (bool, object?) CallAsyncMethod(IConversationSideCar instance, MethodInfo method, Type retType, object[] args)
|
||||
{
|
||||
object? value = null;
|
||||
var isHandled = false;
|
||||
|
||||
var enabled = instance != null && instance.IsEnabled() && method != null;
|
||||
if (!enabled)
|
||||
{
|
||||
return (isHandled, value);
|
||||
}
|
||||
|
||||
isHandled = true;
|
||||
if (retType == typeof(void))
|
||||
{
|
||||
value = GetMethod(nameof(CallAsync)).Invoke(this, [instance, method, args]);
|
||||
}
|
||||
else
|
||||
{
|
||||
var task = GetMethod(nameof(CallGenericAsync)).MakeGenericMethod(retType).Invoke(this, [instance, method, args]);
|
||||
value = task?.GetType().GetProperty("Result")?.GetValue(task);
|
||||
}
|
||||
|
||||
return (isHandled, value);
|
||||
}
|
||||
|
||||
private (bool, object?) CallSyncMethod(IConversationSideCar instance, MethodInfo method, Type retType, object[] args)
|
||||
{
|
||||
object? value = null;
|
||||
var isHandled = false;
|
||||
|
||||
var enabled = instance != null && instance.IsEnabled() && method != null;
|
||||
if (!enabled)
|
||||
{
|
||||
return (isHandled, value);
|
||||
}
|
||||
|
||||
isHandled = true;
|
||||
if (retType == typeof(void))
|
||||
{
|
||||
value = GetMethod(nameof(CallSync)).Invoke(this, [instance, method, args]);
|
||||
}
|
||||
else
|
||||
{
|
||||
value = GetMethod(nameof(CallGenericSync)).MakeGenericMethod(retType).Invoke(this, [instance, method, args]);
|
||||
}
|
||||
|
||||
return (isHandled, value);
|
||||
}
|
||||
|
||||
|
||||
#region Call Side car method
|
||||
private static async Task<T> CallGenericAsync<T>(object instance, MethodInfo method, object[] args)
|
||||
{
|
||||
var res = await (Task<T>)method.Invoke(instance, args);
|
||||
return res;
|
||||
}
|
||||
|
||||
private static async Task CallAsync(object instance, MethodInfo method, object[] args)
|
||||
{
|
||||
await (Task)method.Invoke(instance, args);
|
||||
return;
|
||||
}
|
||||
|
||||
private static T CallGenericSync<T>(object instance, MethodInfo method, object[] args)
|
||||
{
|
||||
var res = (T)method.Invoke(instance, args);
|
||||
return res;
|
||||
}
|
||||
|
||||
private static void CallSync(object instance, MethodInfo method, object[] args)
|
||||
{
|
||||
method.Invoke(instance, args);
|
||||
return;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ namespace BotSharp.Core.Agents.Services;
|
|||
public partial class AgentService
|
||||
{
|
||||
#if !DEBUG
|
||||
[MemoryCache(10 * 60, perInstanceCache: true)]
|
||||
[SharpCache(10, perInstanceCache: true)]
|
||||
#endif
|
||||
public async Task<PagedItems<Agent>> GetAgents(AgentFilter filter)
|
||||
{
|
||||
|
|
@ -27,7 +27,7 @@ public partial class AgentService
|
|||
}
|
||||
|
||||
#if !DEBUG
|
||||
[MemoryCache(10 * 60, perInstanceCache: true)]
|
||||
[SharpCache(10, perInstanceCache: true)]
|
||||
#endif
|
||||
public async Task<Agent> GetAgent(string id)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ public partial class AgentService
|
|||
{
|
||||
public static ConcurrentDictionary<string, Dictionary<string, string>> AgentParameterTypes = new();
|
||||
|
||||
[MemoryCache(10 * 60, perInstanceCache: true)]
|
||||
[SharpCache(10, perInstanceCache: true)]
|
||||
public async Task<Agent> LoadAgent(string id, bool loadUtility = true)
|
||||
{
|
||||
if (string.IsNullOrEmpty(id) || id == Guid.Empty.ToString())
|
||||
|
|
|
|||
|
|
@ -188,13 +188,13 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Aspects.Cache" Version="2.0.4" />
|
||||
<PackageReference Include="DistributedLock.Redis" Version="1.0.3" />
|
||||
<PackageReference Include="EntityFrameworkCore.BootKit" Version="8.8.0" />
|
||||
<PackageReference Include="Fluid.Core" Version="2.11.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.1" />
|
||||
<PackageReference Include="Nanoid" Version="3.1.0" />
|
||||
<PackageReference Include="Rougamo.Fody" Version="4.0.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ using BotSharp.Core.Infrastructures.Events;
|
|||
using BotSharp.Core.Roles.Services;
|
||||
using BotSharp.Abstraction.Templating;
|
||||
using BotSharp.Core.Templating;
|
||||
using BotSharp.Abstraction.Infrastructures.Enums;
|
||||
|
||||
namespace BotSharp.Core;
|
||||
|
||||
|
|
@ -34,15 +35,9 @@ public static class BotSharpCoreExtensions
|
|||
services.AddScoped<IUserService, UserService>();
|
||||
services.AddScoped<ProcessorFactory>();
|
||||
|
||||
// Register cache service
|
||||
var cacheSettings = new SharpCacheSettings();
|
||||
config.Bind("SharpCache", cacheSettings);
|
||||
services.AddSingleton(x => cacheSettings);
|
||||
services.AddSingleton<ICacheService, RedisCacheService>();
|
||||
|
||||
AddRedisEvents(services, config);
|
||||
|
||||
services.AddMemoryCache();
|
||||
// Register cache service
|
||||
AddCacheServices(services, config);
|
||||
|
||||
RegisterPlugins(services, config);
|
||||
AddBotSharpOptions(services, configOptions);
|
||||
|
|
@ -50,6 +45,20 @@ public static class BotSharpCoreExtensions
|
|||
return services;
|
||||
}
|
||||
|
||||
private static void AddCacheServices(IServiceCollection services, IConfiguration config)
|
||||
{
|
||||
services.AddMemoryCache();
|
||||
var cacheSettings = new SharpCacheSettings();
|
||||
config.Bind("SharpCache", cacheSettings);
|
||||
services.AddSingleton(x => cacheSettings);
|
||||
|
||||
services.AddSingleton<ICacheService>(sp => cacheSettings.CacheType switch
|
||||
{
|
||||
CacheType.RedisCache => ActivatorUtilities.CreateInstance<RedisCacheService>(sp),
|
||||
_ => ActivatorUtilities.CreateInstance<MemoryCacheService>(sp),
|
||||
});
|
||||
}
|
||||
|
||||
public static IServiceCollection UsingSqlServer(this IServiceCollection services, IConfiguration config)
|
||||
{
|
||||
services.AddScoped<IBotSharpRepository>(sp =>
|
||||
|
|
|
|||
|
|
@ -1,18 +1,15 @@
|
|||
using BotSharp.Abstraction.Infrastructures;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace BotSharp.Core.Infrastructures;
|
||||
|
||||
public class MemoryCacheService : ICacheService
|
||||
{
|
||||
private static IMemoryCache _cache = new MemoryCache(new MemoryCacheOptions
|
||||
{
|
||||
});
|
||||
private readonly BotSharpDatabaseSettings _settings;
|
||||
private static readonly MemoryCache _cache = new MemoryCache(new OptionsWrapper<MemoryCacheOptions>(new MemoryCacheOptions()));
|
||||
|
||||
public MemoryCacheService(BotSharpDatabaseSettings settings)
|
||||
public MemoryCacheService()
|
||||
{
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
public async Task<T?> GetAsync<T>(string key)
|
||||
|
|
@ -37,4 +34,9 @@ public class MemoryCacheService : ICacheService
|
|||
{
|
||||
_cache.Remove(key);
|
||||
}
|
||||
|
||||
public async Task ClearCacheAsync(string prefix)
|
||||
{
|
||||
_cache.Compact(1.0);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
using BotSharp.Abstraction.Infrastructures;
|
||||
using Newtonsoft.Json;
|
||||
using StackExchange.Redis;
|
||||
|
||||
namespace BotSharp.Core.Infrastructures;
|
||||
|
||||
public class RedisCacheService : ICacheService
|
||||
{
|
||||
private readonly IConnectionMultiplexer _redis;
|
||||
|
||||
public RedisCacheService(IConnectionMultiplexer redis)
|
||||
{
|
||||
_redis = redis;
|
||||
}
|
||||
|
||||
public async Task<T?> GetAsync<T>(string key)
|
||||
{
|
||||
var db = _redis.GetDatabase();
|
||||
var value = await db.StringGetAsync(key);
|
||||
|
||||
if (value.HasValue)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<T>(value);
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
public async Task<object> GetAsync(string key, Type type)
|
||||
{
|
||||
var db = _redis.GetDatabase();
|
||||
var value = await db.StringGetAsync(key);
|
||||
|
||||
if (value.HasValue)
|
||||
{
|
||||
return JsonConvert.DeserializeObject(value, type);
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
|
||||
public async Task SetAsync<T>(string key, T value, TimeSpan? expiry)
|
||||
{
|
||||
var db = _redis.GetDatabase();
|
||||
await db.StringSetAsync(key, JsonConvert.SerializeObject(value), expiry);
|
||||
}
|
||||
|
||||
public async Task RemoveAsync(string key)
|
||||
{
|
||||
var db = _redis.GetDatabase();
|
||||
await db.KeyDeleteAsync(key);
|
||||
}
|
||||
|
||||
public async Task ClearCacheAsync(string prefix)
|
||||
{
|
||||
var db = _redis.GetDatabase();
|
||||
var server = _redis.GetServer(_redis.GetEndPoints().First());
|
||||
const int pageSize = 1000;
|
||||
var keys = server.Keys(pattern: $"{prefix}*", pageSize: pageSize).ToList();
|
||||
|
||||
for (int i = 0; i < keys.Count; i += pageSize)
|
||||
{
|
||||
var batch = keys.Skip(i).Take(pageSize).ToArray();
|
||||
await db.KeyDeleteAsync(batch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,95 +0,0 @@
|
|||
using BotSharp.Abstraction.Infrastructures;
|
||||
using Newtonsoft.Json;
|
||||
using StackExchange.Redis;
|
||||
|
||||
namespace BotSharp.Core.Infrastructures;
|
||||
|
||||
public class RedisCacheService : ICacheService
|
||||
{
|
||||
private readonly BotSharpDatabaseSettings _settings;
|
||||
private static ConnectionMultiplexer redis = null!;
|
||||
|
||||
public RedisCacheService(BotSharpDatabaseSettings settings)
|
||||
{
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
public async Task<T?> GetAsync<T>(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<T>(value);
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
public async Task<object> 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<T>(string key, T value, TimeSpan? expiry)
|
||||
{
|
||||
if (string.IsNullOrEmpty(_settings.Redis))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (redis == null)
|
||||
{
|
||||
redis = ConnectionMultiplexer.Connect(_settings.Redis);
|
||||
}
|
||||
|
||||
var db = redis.GetDatabase();
|
||||
await db.StringSetAsync(key, JsonConvert.SerializeObject(value), expiry);
|
||||
}
|
||||
|
||||
public async Task RemoveAsync(string key)
|
||||
{
|
||||
if (string.IsNullOrEmpty(_settings.Redis))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (redis == null)
|
||||
{
|
||||
redis = ConnectionMultiplexer.Connect(_settings.Redis);
|
||||
}
|
||||
|
||||
var db = redis.GetDatabase();
|
||||
await db.KeyDeleteAsync(key);
|
||||
}
|
||||
}
|
||||
|
|
@ -48,10 +48,8 @@ public static class Utilities
|
|||
public static void ClearCache()
|
||||
{
|
||||
// Clear whole cache.
|
||||
if (new MemoryCacheAttribute(0).Cache is MemoryCache memcache)
|
||||
{
|
||||
memcache.Compact(100);
|
||||
}
|
||||
var sharpCache = new SharpCacheAttribute(0);
|
||||
sharpCache.ClearCacheAsync().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public static string HideMiddleDigits(string input, bool isEmail = false)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using BotSharp.Abstraction.Routing.Models;
|
||||
using BotSharp.Abstraction.Routing.Settings;
|
||||
using BotSharp.Core.Infrastructures;
|
||||
|
||||
namespace BotSharp.Core.Routing;
|
||||
|
||||
|
|
@ -74,7 +75,7 @@ public partial class RoutingService : IRoutingService
|
|||
}
|
||||
|
||||
#if !DEBUG
|
||||
[MemoryCache(10 * 60)]
|
||||
[SharpCache(10)]
|
||||
#endif
|
||||
protected RoutingRule[] GetRoutingRecords()
|
||||
{
|
||||
|
|
@ -99,7 +100,7 @@ public partial class RoutingService : IRoutingService
|
|||
}
|
||||
|
||||
#if !DEBUG
|
||||
[MemoryCache(10 * 60)]
|
||||
[SharpCache(10)]
|
||||
#endif
|
||||
public RoutableAgent[] GetRoutableAgents(List<string> profiles)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -377,7 +377,7 @@ public class UserService : IUserService
|
|||
return await _cacheService.GetAsync<DateTime>(GetUserTokenExpiresCacheKey(_user.Id));
|
||||
}
|
||||
|
||||
[MemoryCache(10 * 60, perInstanceCache: true)]
|
||||
[SharpCache(10, perInstanceCache: true)]
|
||||
public async Task<User> GetMyProfile()
|
||||
{
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
|
|
@ -398,7 +398,7 @@ public class UserService : IUserService
|
|||
return user;
|
||||
}
|
||||
|
||||
[MemoryCache(10 * 60, perInstanceCache: true)]
|
||||
[SharpCache(10, perInstanceCache: true)]
|
||||
public async Task<User> GetUser(string id)
|
||||
{
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
|
|
|
|||
|
|
@ -42,5 +42,4 @@ global using BotSharp.Core.Agents.Services;
|
|||
global using BotSharp.Core.Conversations.Services;
|
||||
global using BotSharp.Core.Infrastructures;
|
||||
global using BotSharp.Core.Users.Services;
|
||||
global using Aspects.Cache;
|
||||
global using BotSharp.Abstraction.Infrastructures.Events;
|
||||
Loading…
Reference in a new issue