diff --git a/Cnblogs.Architecture.sln b/Cnblogs.Architecture.sln index 2826a7b..520af13 100644 --- a/Cnblogs.Architecture.sln +++ b/Cnblogs.Architecture.sln @@ -62,6 +62,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cnblogs.Architecture.Ddd.In EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cnblogs.Architecture.Ddd.Infrastructure.FileProviders.AliyunOss", "src\Cnblogs.Architecture.Ddd.Infrastructure.FileProviders.AliyunOss\Cnblogs.Architecture.Ddd.Infrastructure.FileProviders.AliyunOss.csproj", "{9C76E136-1D79-408C-A17F-FD63632B00A9}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis", "src\Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis\Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis.csproj", "{1FF58B65-6C83-4F0C-909A-6606B4C754B8}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -94,6 +96,7 @@ Global {73665E32-3D10-4F71-B893-4C65F36332D0} = {D3A6DF01-017E-4088-936C-B3791F41DF53} {4BD98FBF-FB98-4172-B352-BB7BF8761FCB} = {D3A6DF01-017E-4088-936C-B3791F41DF53} {9C76E136-1D79-408C-A17F-FD63632B00A9} = {D3A6DF01-017E-4088-936C-B3791F41DF53} + {1FF58B65-6C83-4F0C-909A-6606B4C754B8} = {D3A6DF01-017E-4088-936C-B3791F41DF53} EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {54D9D850-1CFC-485E-97FE-87F41C220523}.Debug|Any CPU.ActiveCfg = Debug|Any CPU @@ -200,5 +203,9 @@ Global {9C76E136-1D79-408C-A17F-FD63632B00A9}.Debug|Any CPU.Build.0 = Debug|Any CPU {9C76E136-1D79-408C-A17F-FD63632B00A9}.Release|Any CPU.ActiveCfg = Release|Any CPU {9C76E136-1D79-408C-A17F-FD63632B00A9}.Release|Any CPU.Build.0 = Release|Any CPU + {1FF58B65-6C83-4F0C-909A-6606B4C754B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1FF58B65-6C83-4F0C-909A-6606B4C754B8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1FF58B65-6C83-4F0C-909A-6606B4C754B8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1FF58B65-6C83-4F0C-909A-6606B4C754B8}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/src/Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis/Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis.csproj b/src/Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis/Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis.csproj new file mode 100644 index 0000000..245d038 --- /dev/null +++ b/src/Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis/Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis.csproj @@ -0,0 +1,18 @@ + + + + + Provides remote cache provider that implemented with Redis + + + + + + + + + + + + + diff --git a/src/Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis/Injectors.cs b/src/Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis/Injectors.cs new file mode 100644 index 0000000..7b405cb --- /dev/null +++ b/src/Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis/Injectors.cs @@ -0,0 +1,80 @@ +using Cnblogs.Architecture.Ddd.Cqrs.Abstractions; +using Cnblogs.Architecture.Ddd.Cqrs.DependencyInjection; +using Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Options; +using StackExchange.Redis; + +// ReSharper disable once CheckNamespace +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Injectors for redis cache provider. +/// +public static class Injectors +{ + /// + /// Add redis cache as remote cache. + /// + /// The injector. + /// The root configuration. + /// The configuration section name for redis, defaults to Redis. + /// The optional configuration. + /// + public static CqrsInjector AddRedisCache( + this CqrsInjector injector, + IConfiguration configuration, + string sectionName = "Redis", + Action? configure = null) + { + return AddRedisCache(injector, configuration.GetSection(sectionName), configure); + } + + /// + /// Add redis cache as remote cache. + /// + /// The injector. + /// The configuration section for redis. + /// The optional configuration. + /// + public static CqrsInjector AddRedisCache( + this CqrsInjector injector, + IConfigurationSection section, + Action? configure = null) + { + injector.Services.Configure(section); + return AddRedisCache(injector, configure); + } + + /// + /// Add redis cache as remote cache. + /// + /// The injector. + /// The connection string. + /// Optional configuration for redis options. + /// The configure for cacheable request options. + /// + public static CqrsInjector AddRedisCache( + this CqrsInjector injector, + string connectionString, + Action? redisConfigure = null, + Action? configure = null) + { + var options = ConfigurationOptions.Parse(connectionString, true); + injector.Services.Configure(o => + { + o.Configure = options; + redisConfigure?.Invoke(o); + }); + return AddRedisCache(injector, configure); + } + + private static CqrsInjector AddRedisCache( + this CqrsInjector injector, + Action? configure = null) + { + injector.Services.AddSingleton( + sp => ConnectionMultiplexer.Connect(sp.GetRequiredService>().Value.Configure)); + return injector.AddRemoteQueryCache(configure); + } +} diff --git a/src/Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis/RedisCacheProvider.cs b/src/Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis/RedisCacheProvider.cs new file mode 100644 index 0000000..d17eae5 --- /dev/null +++ b/src/Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis/RedisCacheProvider.cs @@ -0,0 +1,84 @@ +using System.Text.Json; +using Cnblogs.Architecture.Ddd.Domain.Abstractions; +using Cnblogs.Architecture.Ddd.Infrastructure.Abstractions; +using Microsoft.Extensions.Options; +using StackExchange.Redis; + +namespace Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis; + +/// +/// Remote cache provider implemented with Redis. +/// +public class RedisCacheProvider + : IRemoteCacheProvider +{ + private readonly RedisOptions _options; + private readonly IDatabaseAsync _database; + private readonly IDateTimeProvider _dateTimeProvider; + + /// + /// Remote cache provider implemented with Redis. + /// + /// The underlying multiplexer. + /// The options for this provider. + /// The datetime provider. + public RedisCacheProvider( + ConnectionMultiplexer multiplexer, + IOptions options, + IDateTimeProvider dateTimeProvider) + { + _dateTimeProvider = dateTimeProvider; + _options = options.Value; + _database = multiplexer.GetDatabase(_options.Database); + } + + /// + public Task AddAsync(string cacheKey, TResult value) + { + return _database.StringSetAsync(GetCacheKey(cacheKey), Serialize(value)); + } + + /// + public Task AddAsync(string cacheKey, TimeSpan expires, TResult value) + { + return _database.StringSetAsync(GetCacheKey(cacheKey), Serialize(value), expires); + } + + /// + public async Task?> GetAsync(string cacheKey) + { + var json = await _database.StringGetAsync(GetCacheKey(cacheKey)); + if (json.IsNullOrEmpty) + { + return null; + } + + return DeSerialize(json!); + } + + /// + public Task RemoveAsync(string cacheKey) + { + return _database.KeyDeleteAsync(GetCacheKey(cacheKey)); + } + + /// + public Task UpdateAsync(string cacheKey, TResult value) + { + return AddAsync(cacheKey, value); + } + + /// + public Task UpdateAsync(string cacheKey, TResult value, TimeSpan expires) + { + return AddAsync(cacheKey, expires, value); + } + + private string GetCacheKey(string key) => $"{_options.Prefix}{key}"; + + private string Serialize(TResult result) + => JsonSerializer.Serialize(new CacheEntry(result, _dateTimeProvider.UnixSeconds())); + + private static CacheEntry? DeSerialize(string json) + => JsonSerializer.Deserialize>(json); +} diff --git a/src/Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis/RedisOptions.cs b/src/Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis/RedisOptions.cs new file mode 100644 index 0000000..e021af3 --- /dev/null +++ b/src/Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis/RedisOptions.cs @@ -0,0 +1,24 @@ +using StackExchange.Redis; + +namespace Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis; + +/// +/// Options for redis connection. +/// +public class RedisOptions +{ + /// + /// Prefix for all redis keys. + /// + public string Prefix { get; set; } = "cache_"; + + /// + /// The number of database to use. + /// + public int Database { get; set; } = -1; + + /// + /// The redis configuration, https://stackexchange.github.io/StackExchange.Redis/Configuration + /// + public ConfigurationOptions Configure { get; set; } = new(); +}