@@ -69,7 +69,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.AmazonSQS.InMemory", | |||||
EndProject | EndProject | ||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.NATS", "src\DotNetCore.CAP.NATS\DotNetCore.CAP.NATS.csproj", "{8B2FD3EA-E72B-4A82-B182-B87EC0C15D07}" | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.NATS", "src\DotNetCore.CAP.NATS\DotNetCore.CAP.NATS.csproj", "{8B2FD3EA-E72B-4A82-B182-B87EC0C15D07}" | ||||
EndProject | EndProject | ||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample.RabbitMQ.Postgres.DashboardAuth", "samples\Sample.RabbitMQ.Postgres.DashboardAuth\Sample.RabbitMQ.Postgres.DashboardAuth.csproj", "{54F6C206-2A23-4971-AE5A-FC47EB772452}" | |||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.RabbitMQ.Postgres.DashboardAuth", "samples\Sample.RabbitMQ.Postgres.DashboardAuth\Sample.RabbitMQ.Postgres.DashboardAuth.csproj", "{54F6C206-2A23-4971-AE5A-FC47EB772452}" | |||||
EndProject | |||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.Redis", "src\DotNetCore.CAP.Redis\DotNetCore.CAP.Redis.csproj", "{462B6245-46F6-42BF-8CE6-588ACAA53190}" | |||||
EndProject | |||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples.Redis.SqlServer", "samples\Samples.Redis.SqlServer\Samples.Redis.SqlServer.csproj", "{375AF85D-8C81-47C6-BE5B-D0874D4971EA}" | |||||
EndProject | EndProject | ||||
Global | Global | ||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||||
@@ -165,6 +169,14 @@ Global | |||||
{54F6C206-2A23-4971-AE5A-FC47EB772452}.Debug|Any CPU.Build.0 = Debug|Any CPU | {54F6C206-2A23-4971-AE5A-FC47EB772452}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
{54F6C206-2A23-4971-AE5A-FC47EB772452}.Release|Any CPU.ActiveCfg = Release|Any CPU | {54F6C206-2A23-4971-AE5A-FC47EB772452}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
{54F6C206-2A23-4971-AE5A-FC47EB772452}.Release|Any CPU.Build.0 = Release|Any CPU | {54F6C206-2A23-4971-AE5A-FC47EB772452}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
{462B6245-46F6-42BF-8CE6-588ACAA53190}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||||
{462B6245-46F6-42BF-8CE6-588ACAA53190}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||||
{462B6245-46F6-42BF-8CE6-588ACAA53190}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||||
{462B6245-46F6-42BF-8CE6-588ACAA53190}.Release|Any CPU.Build.0 = Release|Any CPU | |||||
{375AF85D-8C81-47C6-BE5B-D0874D4971EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||||
{375AF85D-8C81-47C6-BE5B-D0874D4971EA}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||||
{375AF85D-8C81-47C6-BE5B-D0874D4971EA}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||||
{375AF85D-8C81-47C6-BE5B-D0874D4971EA}.Release|Any CPU.Build.0 = Release|Any CPU | |||||
EndGlobalSection | EndGlobalSection | ||||
GlobalSection(SolutionProperties) = preSolution | GlobalSection(SolutionProperties) = preSolution | ||||
HideSolutionNode = FALSE | HideSolutionNode = FALSE | ||||
@@ -192,6 +204,8 @@ Global | |||||
{B187DD15-092D-4B72-9807-50856607D237} = {3A6B6931-A123-477A-9469-8B468B5385AF} | {B187DD15-092D-4B72-9807-50856607D237} = {3A6B6931-A123-477A-9469-8B468B5385AF} | ||||
{8B2FD3EA-E72B-4A82-B182-B87EC0C15D07} = {9B2AE124-6636-4DE9-83A3-70360DABD0C4} | {8B2FD3EA-E72B-4A82-B182-B87EC0C15D07} = {9B2AE124-6636-4DE9-83A3-70360DABD0C4} | ||||
{54F6C206-2A23-4971-AE5A-FC47EB772452} = {3A6B6931-A123-477A-9469-8B468B5385AF} | {54F6C206-2A23-4971-AE5A-FC47EB772452} = {3A6B6931-A123-477A-9469-8B468B5385AF} | ||||
{462B6245-46F6-42BF-8CE6-588ACAA53190} = {9B2AE124-6636-4DE9-83A3-70360DABD0C4} | |||||
{375AF85D-8C81-47C6-BE5B-D0874D4971EA} = {3A6B6931-A123-477A-9469-8B468B5385AF} | |||||
EndGlobalSection | EndGlobalSection | ||||
GlobalSection(ExtensibilityGlobals) = postSolution | GlobalSection(ExtensibilityGlobals) = postSolution | ||||
SolutionGuid = {2E70565D-94CF-40B4-BFE1-AC18D5F736AB} | SolutionGuid = {2E70565D-94CF-40B4-BFE1-AC18D5F736AB} | ||||
@@ -0,0 +1,32 @@ | |||||
using DotNetCore.CAP; | |||||
using DotNetCore.CAP.Internal; | |||||
using StackExchange.Redis; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
using System.Reflection; | |||||
namespace DotNetCore.CAP.Redis | |||||
{ | |||||
public static class CapRedisOptionsExtensions | |||||
{ | |||||
public static CapOptions UseRedis(this CapOptions options) => | |||||
options.UseRedis(_ => { }); | |||||
public static CapOptions UseRedis(this CapOptions options, string connection) => | |||||
options.UseRedis(opt => opt.Configuration = ConfigurationOptions.Parse(connection)); | |||||
public static CapOptions UseRedis(this CapOptions options, Action<CapRedisOptions> configure) | |||||
{ | |||||
if (configure is null) | |||||
throw new ArgumentNullException(nameof(configure)); | |||||
options.RegisterExtension(new RedisOptionsExtension(configure)); | |||||
return options; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,38 @@ | |||||
using DotNetCore.CAP; | |||||
using DotNetCore.CAP.Internal; | |||||
using Microsoft.Extensions.Options; | |||||
using StackExchange.Redis; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Net; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace DotNetCore.CAP.Redis | |||||
{ | |||||
class CapRedisOptionsPostConfigure : IPostConfigureOptions<CapRedisOptions> | |||||
{ | |||||
private readonly CapOptions capOptions; | |||||
public CapRedisOptionsPostConfigure(IOptions<CapOptions> options) | |||||
{ | |||||
capOptions = options.Value; | |||||
} | |||||
public void PostConfigure(string name, CapRedisOptions options) | |||||
{ | |||||
var groupPrefix = string.IsNullOrWhiteSpace(capOptions.GroupNamePrefix) ? string.Empty : $"{capOptions.GroupNamePrefix}."; | |||||
options.DefaultChannel = $"{groupPrefix}{capOptions.DefaultGroupName}"; | |||||
options.Configuration ??= new ConfigurationOptions(); | |||||
if (!options.Configuration.EndPoints.Any()) | |||||
{ | |||||
options.Configuration.EndPoints.Add(IPAddress.Loopback, 0); | |||||
options.Configuration.SetDefaultPorts(); | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,22 @@ | |||||
using StackExchange.Redis; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.IO; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace DotNetCore.CAP.Redis | |||||
{ | |||||
public class CapRedisOptions | |||||
{ | |||||
/// <summary> | |||||
/// Gets or sets the options of redis connections | |||||
/// </summary> | |||||
public ConfigurationOptions Configuration { get; set; } | |||||
internal string Endpoint { get; set; } | |||||
internal string DefaultChannel { get; set; } | |||||
} | |||||
} |
@@ -0,0 +1,22 @@ | |||||
<Project Sdk="Microsoft.NET.Sdk"> | |||||
<PropertyGroup> | |||||
<TargetFramework>netstandard2.1</TargetFramework> | |||||
<AssemblyName>DotNetCore.CAP.Redis</AssemblyName> | |||||
<PackageTags>$(PackageTags);Redis</PackageTags> | |||||
</PropertyGroup> | |||||
<PropertyGroup> | |||||
<DocumentationFile>bin\$(Configuration)\netstandard2.1\DotNetCore.CAP.Redis.xml</DocumentationFile> | |||||
<NoWarn>1701;1702;1705;CS1591</NoWarn> | |||||
</PropertyGroup> | |||||
<ItemGroup> | |||||
<PackageReference Include="StackExchange.Redis" Version="2.2.4" /> | |||||
</ItemGroup> | |||||
<ItemGroup> | |||||
<ProjectReference Include="..\DotNetCore.CAP\DotNetCore.CAP.csproj" /> | |||||
</ItemGroup> | |||||
</Project> |
@@ -0,0 +1,41 @@ | |||||
using DotNetCore.CAP.Redis; | |||||
using DotNetCore.CAP; | |||||
using DotNetCore.CAP.Transport; | |||||
using Microsoft.Extensions.Configuration; | |||||
using Microsoft.Extensions.DependencyInjection; | |||||
using Microsoft.Extensions.DependencyInjection.Extensions; | |||||
using Microsoft.Extensions.Options; | |||||
using StackExchange.Redis; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace DotNetCore.CAP.Redis | |||||
{ | |||||
class RedisOptionsExtension : ICapOptionsExtension | |||||
{ | |||||
private readonly Action<CapRedisOptions> configure; | |||||
public RedisOptionsExtension(Action<CapRedisOptions> configure) | |||||
{ | |||||
if (configure is null) | |||||
{ | |||||
throw new ArgumentNullException(nameof(configure)); | |||||
} | |||||
this.configure = configure; | |||||
} | |||||
public void AddServices(IServiceCollection services) | |||||
{ | |||||
services.AddSingleton<CapMessageQueueMakerService>(); | |||||
services.AddSingleton<IRedisCacheManager, RedisCacheManager>(); | |||||
services.AddSingleton<IConsumerClientFactory, RedisConsumerClientFactory>(); | |||||
services.AddSingleton<ITransport, RedisTransport>(); | |||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<CapRedisOptions>, CapRedisOptionsPostConfigure>()); | |||||
services.AddOptions<CapRedisOptions>().Configure(configure); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,87 @@ | |||||
using DotNetCore.CAP.Redis; | |||||
using DotNetCore.CAP.Messages; | |||||
using DotNetCore.CAP.Transport; | |||||
using Microsoft.Extensions.Logging; | |||||
using Microsoft.Extensions.Options; | |||||
using System; | |||||
using System.Collections.Concurrent; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Runtime.Serialization; | |||||
using System.Text; | |||||
using System.Text.RegularExpressions; | |||||
using System.Threading; | |||||
using System.Threading.Tasks; | |||||
namespace DotNetCore.CAP.Redis | |||||
{ | |||||
class RedisConsumerClient : IConsumerClient | |||||
{ | |||||
private readonly ILogger<RedisConsumerClient> logger; | |||||
private readonly IRedisCacheManager redis; | |||||
private readonly CapRedisOptions options; | |||||
private readonly string groupId; | |||||
public RedisConsumerClient( | |||||
string groubId, | |||||
IRedisCacheManager redis, | |||||
CapRedisOptions options, | |||||
ILogger<RedisConsumerClient> logger | |||||
) | |||||
{ | |||||
this.groupId = groubId; | |||||
this.redis = redis; | |||||
this.options = options; | |||||
this.logger = logger; | |||||
} | |||||
public event EventHandler<TransportMessage> OnMessageReceived; | |||||
public event EventHandler<LogMessageEventArgs> OnLog; | |||||
public BrokerAddress BrokerAddress => new BrokerAddress("redis", options.Endpoint); | |||||
public void Subscribe(IEnumerable<string> topics) | |||||
{ | |||||
if (topics == null) throw new ArgumentNullException(nameof(topics)); | |||||
redis.SubscribeAsync(groupId, topics, (channel, message) => | |||||
{ | |||||
logger.LogDebug($"Redis message with name {message.GetName()} subscribed."); | |||||
message.GroupId = groupId; | |||||
OnMessageReceived?.Invoke(channel, message); | |||||
return Task.CompletedTask; | |||||
}); | |||||
} | |||||
public void Listening(TimeSpan timeout, CancellationToken cancellationToken) | |||||
{ | |||||
cancellationToken.Register(async () => await redis.UnsubscribeAsync()); | |||||
while (true) | |||||
{ | |||||
cancellationToken.ThrowIfCancellationRequested(); | |||||
cancellationToken.WaitHandle.WaitOne(timeout); | |||||
} | |||||
} | |||||
public void Commit(object sender) | |||||
{ | |||||
// ignore | |||||
} | |||||
public void Reject(object sender) | |||||
{ | |||||
// ignore | |||||
} | |||||
public void Dispose() | |||||
{ | |||||
//ignore | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,33 @@ | |||||
using DotNetCore.CAP.Redis; | |||||
using DotNetCore.CAP; | |||||
using DotNetCore.CAP.Transport; | |||||
using Microsoft.Extensions.Logging; | |||||
using Microsoft.Extensions.Options; | |||||
using StackExchange.Redis; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace DotNetCore.CAP.Redis | |||||
{ | |||||
class RedisConsumerClientFactory : IConsumerClientFactory | |||||
{ | |||||
private readonly CapRedisOptions redisOptions; | |||||
private readonly IRedisCacheManager redis; | |||||
private readonly ILogger<RedisConsumerClient> logger; | |||||
public RedisConsumerClientFactory(IOptions<CapRedisOptions> redisOptions, IRedisCacheManager redis, ILogger<RedisConsumerClient> logger) | |||||
{ | |||||
this.redisOptions = redisOptions.Value; | |||||
this.redis = redis; | |||||
this.logger = logger; | |||||
} | |||||
public IConsumerClient Create(string groupId) | |||||
{ | |||||
return new RedisConsumerClient(groupId, redis, redisOptions, logger); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,108 @@ | |||||
using DotNetCore.CAP.Transport; | |||||
using Microsoft.Extensions.Logging; | |||||
using Microsoft.Extensions.Options; | |||||
using StackExchange.Redis; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading; | |||||
using System.Threading.Tasks; | |||||
namespace DotNetCore.CAP.Redis | |||||
{ | |||||
class RedisCacheManager : IRedisCacheManager | |||||
{ | |||||
private readonly SemaphoreSlim connectionLock = new SemaphoreSlim(1, 1); | |||||
private readonly CapRedisOptions options; | |||||
private readonly ILogger<RedisCacheManager> logger; | |||||
private ConnectionMultiplexer redis; | |||||
private ISubscriber subscriber; | |||||
private bool disposed; | |||||
public RedisCacheManager(IOptions<CapRedisOptions> options, ILogger<RedisCacheManager> logger) | |||||
{ | |||||
this.options = options.Value; | |||||
this.logger = logger; | |||||
_ = ConnectAsync(); | |||||
} | |||||
public async Task SubscribeAsync(string channelName, IEnumerable<string> topics, Func<RedisChannel, RedisMessage, Task> callback) | |||||
{ | |||||
var channel = await GetChannel(channelName); | |||||
channel.OnMessage(channelMessage => | |||||
{ | |||||
var message = RedisMessage.Create(channelMessage.Message); | |||||
if (topics.Any(c => c == message.GetName())) | |||||
{ | |||||
callback?.Invoke(channelMessage.SubscriptionChannel, message); | |||||
} | |||||
}); | |||||
} | |||||
public async Task PublishAsync(string channelName, RedisValue message) | |||||
{ | |||||
await subscriber.PublishAsync(channelName, message); | |||||
} | |||||
public async Task UnsubscribeAsync() | |||||
{ | |||||
await subscriber?.UnsubscribeAllAsync(); | |||||
} | |||||
public void Dispose() | |||||
{ | |||||
if (!disposed) | |||||
{ | |||||
subscriber?.UnsubscribeAll(); | |||||
redis?.Close(); | |||||
disposed = true; | |||||
} | |||||
} | |||||
private async Task<ChannelMessageQueue> GetChannel(string channelName) | |||||
{ | |||||
await ConnectAsync().ConfigureAwait(false); | |||||
return await subscriber.SubscribeAsync(channelName).ConfigureAwait(false); | |||||
} | |||||
private async Task ConnectAsync() | |||||
{ | |||||
if (disposed == true) | |||||
throw new ObjectDisposedException(nameof(IRedisCacheManager)); | |||||
if (redis != null) | |||||
{ | |||||
subscriber ??= redis.GetSubscriber(); | |||||
return; | |||||
} | |||||
else | |||||
{ | |||||
try | |||||
{ | |||||
await connectionLock.WaitAsync().ConfigureAwait(false); | |||||
if (redis != null) | |||||
{ | |||||
return; | |||||
} | |||||
var redisLogger = new RedisCacheLogger(logger); | |||||
redis = await ConnectionMultiplexer.ConnectAsync(options.Configuration, redisLogger) | |||||
.ConfigureAwait(false); | |||||
redis.LogEvents(logger); | |||||
subscriber = redis.GetSubscriber(); | |||||
} | |||||
finally | |||||
{ | |||||
connectionLock.Release(); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,57 @@ | |||||
using Microsoft.Extensions.Logging; | |||||
using StackExchange.Redis; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.IO; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace DotNetCore.CAP.Redis | |||||
{ | |||||
static class RedisConnectionExtensions | |||||
{ | |||||
public static void LogEvents(this IConnectionMultiplexer connection,ILogger logger) | |||||
{ | |||||
if (connection is null) | |||||
{ | |||||
throw new ArgumentNullException(nameof(connection)); | |||||
} | |||||
if (logger is null) | |||||
{ | |||||
throw new ArgumentNullException(nameof(logger)); | |||||
} | |||||
_ = new RedisCacheManagerEvents(connection, logger); | |||||
} | |||||
} | |||||
class RedisCacheManagerEvents | |||||
{ | |||||
private readonly ILogger logger; | |||||
public RedisCacheManagerEvents(IConnectionMultiplexer connection, ILogger logger) | |||||
{ | |||||
this.logger = logger; | |||||
connection.ErrorMessage += Connection_ErrorMessage; | |||||
connection.ConnectionRestored += Connection_ConnectionRestored; | |||||
connection.ConnectionFailed += Connection_ConnectionFailed; | |||||
} | |||||
private void Connection_ConnectionFailed(object sender, ConnectionFailedEventArgs e) | |||||
{ | |||||
logger.LogError(e.Exception, $"Connection failed!, {e.Exception.Message}, for endpoint:{e.EndPoint}, failure type:{e.FailureType}, connection type:{e.ConnectionType}"); | |||||
} | |||||
private void Connection_ConnectionRestored(object sender, ConnectionFailedEventArgs e) | |||||
{ | |||||
logger.LogWarning(e.Exception, $"Connection restored back!, {e.Exception.Message}, for endpoint:{e.EndPoint}, failure type:{e.FailureType}, connection type:{e.ConnectionType}"); | |||||
} | |||||
private void Connection_ErrorMessage(object sender, RedisErrorEventArgs e) | |||||
{ | |||||
logger.LogError($"Server replied with error, {e.Message}, for endpoint:{e.EndPoint}"); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,27 @@ | |||||
using Microsoft.Extensions.Logging; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.IO; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace DotNetCore.CAP.Redis | |||||
{ | |||||
class RedisCacheLogger : TextWriter | |||||
{ | |||||
private readonly ILogger logger; | |||||
public RedisCacheLogger(ILogger logger) | |||||
{ | |||||
this.logger = logger; | |||||
} | |||||
public override void WriteLine(string value) | |||||
{ | |||||
logger.LogInformation(value); | |||||
} | |||||
public override Encoding Encoding => Encoding.UTF8; | |||||
} | |||||
} |
@@ -0,0 +1,20 @@ | |||||
using DotNetCore.CAP.Transport; | |||||
using Microsoft.Extensions.Logging; | |||||
using Microsoft.Extensions.Options; | |||||
using StackExchange.Redis; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading; | |||||
using System.Threading.Tasks; | |||||
namespace DotNetCore.CAP.Redis | |||||
{ | |||||
interface IRedisCacheManager : IDisposable | |||||
{ | |||||
Task SubscribeAsync(string channelName, IEnumerable<string> topics, Func<RedisChannel,RedisMessage, Task> callback); | |||||
Task PublishAsync(string channelName, RedisValue message); | |||||
Task UnsubscribeAsync(); | |||||
} | |||||
} |
@@ -0,0 +1,55 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
using DotNetCore.CAP; | |||||
using DotNetCore.CAP.Internal; | |||||
using DotNetCore.CAP.Messages; | |||||
using DotNetCore.CAP.Transport; | |||||
using Microsoft.Extensions.Logging; | |||||
using Microsoft.Extensions.Options; | |||||
using StackExchange.Redis; | |||||
namespace DotNetCore.CAP.Redis | |||||
{ | |||||
class RedisTransport : ITransport | |||||
{ | |||||
private readonly IRedisCacheManager redis; | |||||
private readonly ILogger<RedisTransport> logger; | |||||
private readonly MethodMatcherCache selector; | |||||
private readonly CapRedisOptions options; | |||||
public RedisTransport(IRedisCacheManager redis, MethodMatcherCache selector, IOptions<CapRedisOptions> options, ILogger<RedisTransport> logger) | |||||
{ | |||||
this.redis = redis; | |||||
this.selector = selector; | |||||
this.options = options.Value; | |||||
this.logger = logger; | |||||
} | |||||
public BrokerAddress BrokerAddress => new BrokerAddress("redis", options.Endpoint); | |||||
public async Task<OperateResult> SendAsync(TransportMessage message) | |||||
{ | |||||
try | |||||
{ | |||||
var redisMessage = new RedisMessage(message.Headers, message.Body).AsRedisValue(); | |||||
var channelName = selector.GetGroupByTopic(message.GetName()) ?? options.DefaultChannel; | |||||
await redis.PublishAsync(channelName, redisMessage); | |||||
logger.LogDebug($"Redis message [{message.GetName()}] has been published."); | |||||
return OperateResult.Success; | |||||
} | |||||
catch (Exception ex) | |||||
{ | |||||
var wrapperEx = new PublisherSentFailedException(ex.Message, ex); | |||||
return OperateResult.Failed(wrapperEx); | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,22 @@ | |||||
using DotNetCore.CAP.Internal; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace DotNetCore.CAP.Redis | |||||
{ | |||||
static class MethodMatcherCacheExtensions | |||||
{ | |||||
public static string GetGroupByTopic(this MethodMatcherCache source, string topicName) | |||||
{ | |||||
var groupsMap = source.GetCandidatesMethodsOfGroupNameGrouped(); | |||||
return (from groupMap in groupsMap | |||||
from topic in groupMap.Value | |||||
where topic.TopicName == topicName | |||||
select topic.Attribute.Group).FirstOrDefault(); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,53 @@ | |||||
using DotNetCore.CAP.Messages; | |||||
using StackExchange.Redis; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Text.Json; | |||||
using System.Threading.Tasks; | |||||
using Messages = DotNetCore.CAP.Messages; | |||||
namespace DotNetCore.CAP.Redis | |||||
{ | |||||
class RedisMessage : TransportMessage | |||||
{ | |||||
public RedisMessage(IDictionary<string, string> headers, byte[] body) : base(headers, body) { } | |||||
public string GroupId | |||||
{ | |||||
get => Headers.TryGetValue(Messages.Headers.Group, out var value) ? value : default; | |||||
set => Headers.TryAdd(Messages.Headers.Group, value); | |||||
} | |||||
internal RedisValue AsRedisValue() | |||||
{ | |||||
return JsonSerializer.Serialize(new RedisMessageValue | |||||
{ | |||||
Headers = Headers, | |||||
Body = Body | |||||
}, new JsonSerializerOptions(JsonSerializerDefaults.Web)); | |||||
} | |||||
public static RedisMessage Create(RedisValue redisValue) | |||||
{ | |||||
if (redisValue.IsNullOrEmpty) | |||||
return Empty(); | |||||
var value = JsonSerializer.Deserialize<RedisMessageValue>(redisValue, new JsonSerializerOptions(JsonSerializerDefaults.Web)); | |||||
return new RedisMessage(value.Headers, value.Body); | |||||
} | |||||
public static RedisMessage Empty() | |||||
{ | |||||
return new RedisMessage(new Dictionary<string, string>(), Array.Empty<byte>()); | |||||
} | |||||
} | |||||
class RedisMessageValue | |||||
{ | |||||
public IDictionary<string, string> Headers { get; set; } | |||||
public byte[] Body { get; set; } | |||||
} | |||||
} |