From 0301a7b21a538b73b5ca2ee65b8395f61eac5356 Mon Sep 17 00:00:00 2001 From: xiangxiren Date: Fri, 3 Jul 2020 18:02:04 +0800 Subject: [PATCH 01/85] Fix mysql transaction rollback bug(#598) --- src/DotNetCore.CAP.MySql/IDbContextTransaction.CAP.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DotNetCore.CAP.MySql/IDbContextTransaction.CAP.cs b/src/DotNetCore.CAP.MySql/IDbContextTransaction.CAP.cs index 85a36b2..8fc7fd0 100644 --- a/src/DotNetCore.CAP.MySql/IDbContextTransaction.CAP.cs +++ b/src/DotNetCore.CAP.MySql/IDbContextTransaction.CAP.cs @@ -45,7 +45,7 @@ namespace Microsoft.EntityFrameworkCore.Storage public Task RollbackAsync(CancellationToken cancellationToken = default) { - return _transaction.CommitAsync(cancellationToken); + return _transaction.RollbackAsync(cancellationToken); } public ValueTask DisposeAsync() @@ -54,4 +54,4 @@ namespace Microsoft.EntityFrameworkCore.Storage return new ValueTask(); } } -} \ No newline at end of file +} From 4066e8603d63aaacea7514ebca2a9652dee345f6 Mon Sep 17 00:00:00 2001 From: Marko Zorec Date: Fri, 3 Jul 2020 16:40:49 +0200 Subject: [PATCH 02/85] Minor gramatical fixes (#599) * Minor gramatical fixes * Update README.md --- README.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 78c23d9..765a755 100644 --- a/README.md +++ b/README.md @@ -10,11 +10,11 @@ [![Member project of .NET Core Community](https://img.shields.io/badge/member%20project%20of-NCC-9e20c9.svg)](https://github.com/dotnetcore) [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/dotnetcore/CAP/master/LICENSE.txt) -CAP is a library based on .Net standard, which is a solution to deal with distributed transactions, also has the function of EventBus, it is lightweight, easy to use, and efficiently. +CAP is a library based on .Net standard, which is a solution to deal with distributed transactions, has the function of EventBus, it is lightweight, easy to use, and efficient. -In the process of building an SOA or MicroService system, we usually need to use the event to integrate each services. In the process, the simple use of message queue does not guarantee the reliability. CAP is adopted the local message table program integrated with the current database to solve the exception may occur in the process of the distributed system calling each other. It can ensure that the event messages are not lost in any case. +In the process of building an SOA or MicroService system, we usually need to use the event to integrate each service. In the process, the simple use of message queue does not guarantee the reliability. CAP adopts local message table program integrated with the current database to solve exceptions that may occur in the process of the distributed system calling each other. It can ensure that the event messages are not lost in any case. -You can also use the CAP as an EventBus. The CAP provides a simpler way to implement event publishing and subscriptions. You do not need to inherit or implement any interface during the process of subscription and sending. +You can also use CAP as an EventBus. CAP provides a simpler way to implement event publishing and subscriptions. You do not need to inherit or implement any interface during subscription and sending process. ## Architecture overview @@ -26,13 +26,13 @@ You can also use the CAP as an EventBus. The CAP provides a simpler way to imple ### NuGet -You can run the following command to install the CAP in your project. +You can run the following command to install CAP in your project. ``` PM> Install-Package DotNetCore.CAP ``` -CAP supports RabbitMQ,Kafka and AzureService as message queue, select the packages you need to install: +CAP supports RabbitMQ, Kafka and AzureService as message queue, following packages are available to install: ``` PM> Install-Package DotNetCore.CAP.Kafka @@ -53,7 +53,7 @@ PM> Install-Package DotNetCore.CAP.MongoDB //need MongoDB 4.0+ cluster ### Configuration -First,You need to config CAP in your Startup.cs: +First, you need to config CAP in your Startup.cs: ```cs public void ConfigureServices(IServiceCollection services) @@ -151,7 +151,7 @@ public class PublishController : Controller **In Business Logic Service** -If your subscribe method is not in the Controller,then your subscribe class need to Inheritance `ICapSubscribe`: +If your subscription method is not in the Controller,then your subscribe class needs to implement `ICapSubscribe` interface: ```c# @@ -173,7 +173,7 @@ namespace BusinessCode.Service ``` -Then inject your `ISubscriberService` class in Startup.cs +Then register your class that implements `ISubscriberService` in Startup.cs ```c# public void ConfigureServices(IServiceCollection services) @@ -192,10 +192,10 @@ public void ConfigureServices(IServiceCollection services) The concept of a subscription group is similar to that of a consumer group in Kafka. it is the same as the broadcast mode in the message queue, which is used to process the same message between multiple different microservice instances. -When CAP startup, it will use the current assembly name as the default group name, if multiple same group subscribers subscribe the same topic name, there is only one subscriber can receive the message. +When CAP startups, it will use the current assembly name as the default group name, if multiple same group subscribers subscribe to the same topic name, there is only one subscriber that can receive the message. Conversely, if subscribers are in different groups, they will all receive messages. -In the same application, you can specify the `Group` property to keep they are in different subscribe groups: +In the same application, you can specify `Group` property to keep subscriptions in different subscribe groups: ```C# @@ -224,7 +224,7 @@ services.AddCap(x => ### Dashboard -CAP v2.1+ provides the dashboard pages, you can easily view the sent and received messages. In addition, you can also view the message status in real time on the dashboard. Use the following command to install the Dashboard in your project. +CAP v2.1+ provides dashboard pages, you can easily view message that were sent and received. In addition, you can also view the message status in real time in the dashboard. Use the following command to install the Dashboard in your project. ``` PM> Install-Package DotNetCore.CAP.Dashboard @@ -253,7 +253,7 @@ services.AddCap(x => }); ``` -The default dashboard address is :[http://localhost:xxx/cap](http://localhost:xxx/cap), you can also configure the `/cap` suffix with `x.UseDashboard(opt =>{ opt.MatchPath="/mycap"; })`. +The default dashboard address is :[http://localhost:xxx/cap](http://localhost:xxx/cap), you can configure relative path `/cap` with `x.UseDashboard(opt =>{ opt.MatchPath="/mycap"; })`. ![dashboard](http://images2017.cnblogs.com/blog/250417/201710/250417-20171004220827302-189215107.png) From 338ba4c201c2f2017003104a5f8d57f7762e8c8f Mon Sep 17 00:00:00 2001 From: xiangxiren Date: Sun, 5 Jul 2020 18:23:29 +0800 Subject: [PATCH 03/85] fix sqlserver dashboard query bug (#600) * fix bug * fix bug Co-authored-by: wandone\xlw <123456> --- .../DashboardRoutes.cs | 2 +- .../IDataStorage.MySql.cs | 26 +++++++---------- .../IDataStorage.PostgreSql.cs | 27 +++++++----------- .../IDataStorage.SqlServer.cs | 28 +++++++------------ .../IMonitoringApi.SqlServer.cs | 2 +- 5 files changed, 32 insertions(+), 53 deletions(-) diff --git a/src/DotNetCore.CAP.Dashboard/DashboardRoutes.cs b/src/DotNetCore.CAP.Dashboard/DashboardRoutes.cs index bfb7aa1..35105cf 100644 --- a/src/DotNetCore.CAP.Dashboard/DashboardRoutes.cs +++ b/src/DotNetCore.CAP.Dashboard/DashboardRoutes.cs @@ -128,7 +128,7 @@ namespace DotNetCore.CAP.Dashboard Routes.AddRazorPage("/nodes", x => { - var id = x.Request.Cookies["cap.node"]; + var id = x.Request.Cookies.ContainsKey("cap.node") ? x.Request.Cookies["cap.node"] : string.Empty; return new NodePage(id); }); diff --git a/src/DotNetCore.CAP.MySql/IDataStorage.MySql.cs b/src/DotNetCore.CAP.MySql/IDataStorage.MySql.cs index 4db2b94..b0aced0 100644 --- a/src/DotNetCore.CAP.MySql/IDataStorage.MySql.cs +++ b/src/DotNetCore.CAP.MySql/IDataStorage.MySql.cs @@ -141,22 +141,11 @@ namespace DotNetCore.CAP.MySql new MySqlParameter("@timeout", timeout), new MySqlParameter("@batchCount", batchCount)); } - public async Task> GetPublishedMessagesOfNeedRetry() - { - var fourMinAgo = DateTime.Now.AddMinutes(-4).ToString("O"); - var sql = $"SELECT * FROM `{_pubName}` WHERE `Retries`<{_capOptions.Value.FailedRetryCount} AND `Version`='{_capOptions.Value.Version}' AND `Added`<'{fourMinAgo}' AND (`StatusName` = '{StatusName.Failed}' OR `StatusName` = '{StatusName.Scheduled}') LIMIT 200;"; - - return await GetMessagesOfNeedRetryAsync(sql); - } - - public async Task> GetReceivedMessagesOfNeedRetry() - { - var fourMinAgo = DateTime.Now.AddMinutes(-4).ToString("O"); - var sql = - $"SELECT * FROM `{_recName}` WHERE `Retries`<{_capOptions.Value.FailedRetryCount} AND `Version`='{_capOptions.Value.Version}' AND `Added`<'{fourMinAgo}' AND (`StatusName` = '{StatusName.Failed}' OR `StatusName` = '{StatusName.Scheduled}') LIMIT 200;"; + public async Task> GetPublishedMessagesOfNeedRetry() => + await GetMessagesOfNeedRetryAsync(_pubName); - return await GetMessagesOfNeedRetryAsync(sql); - } + public async Task> GetReceivedMessagesOfNeedRetry() => + await GetMessagesOfNeedRetryAsync(_recName); public IMonitoringApi GetMonitoringApi() { @@ -189,8 +178,13 @@ namespace DotNetCore.CAP.MySql connection.ExecuteNonQuery(sql, sqlParams: sqlParams); } - private async Task> GetMessagesOfNeedRetryAsync(string sql) + private async Task> GetMessagesOfNeedRetryAsync(string tableName) { + var fourMinAgo = DateTime.Now.AddMinutes(-4).ToString("O"); + var sql = + $"SELECT `Id`,`Content`,`Retries`,`Added` FROM `{tableName}` WHERE `Retries`<{_capOptions.Value.FailedRetryCount} " + + $"AND `Version`='{_capOptions.Value.Version}' AND `Added`<'{fourMinAgo}' AND (`StatusName` = '{StatusName.Failed}' OR `StatusName` = '{StatusName.Scheduled}') LIMIT 200;"; + await using var connection = new MySqlConnection(_options.Value.ConnectionString); var result = connection.ExecuteReader(sql, reader => { diff --git a/src/DotNetCore.CAP.PostgreSql/IDataStorage.PostgreSql.cs b/src/DotNetCore.CAP.PostgreSql/IDataStorage.PostgreSql.cs index 1cf0ce7..aa55d34 100644 --- a/src/DotNetCore.CAP.PostgreSql/IDataStorage.PostgreSql.cs +++ b/src/DotNetCore.CAP.PostgreSql/IDataStorage.PostgreSql.cs @@ -143,23 +143,11 @@ namespace DotNetCore.CAP.PostgreSql return await Task.FromResult(count); } - public async Task> GetPublishedMessagesOfNeedRetry() - { - var fourMinAgo = DateTime.Now.AddMinutes(-4).ToString("O"); - var sql = - $"SELECT * FROM {_pubName} WHERE \"Retries\"<{_capOptions.Value.FailedRetryCount} AND \"Version\"='{_capOptions.Value.Version}' AND \"Added\"<'{fourMinAgo}' AND (\"StatusName\"='{StatusName.Failed}' OR \"StatusName\"='{StatusName.Scheduled}') LIMIT 200;"; + public async Task> GetPublishedMessagesOfNeedRetry() => + await GetMessagesOfNeedRetryAsync(_pubName); - return await GetMessagesOfNeedRetryAsync(sql); - } - - public async Task> GetReceivedMessagesOfNeedRetry() - { - var fourMinAgo = DateTime.Now.AddMinutes(-4).ToString("O"); - var sql = - $"SELECT * FROM {_recName} WHERE \"Retries\"<{_capOptions.Value.FailedRetryCount} AND \"Version\"='{_capOptions.Value.Version}' AND \"Added\"<'{fourMinAgo}' AND (\"StatusName\"='{StatusName.Failed}' OR \"StatusName\"='{StatusName.Scheduled}') LIMIT 200;"; - - return await GetMessagesOfNeedRetryAsync(sql); - } + public async Task> GetReceivedMessagesOfNeedRetry() => + await GetMessagesOfNeedRetryAsync(_recName); public IMonitoringApi GetMonitoringApi() { @@ -195,8 +183,13 @@ namespace DotNetCore.CAP.PostgreSql connection.ExecuteNonQuery(sql, sqlParams: sqlParams); } - private async Task> GetMessagesOfNeedRetryAsync(string sql) + private async Task> GetMessagesOfNeedRetryAsync(string tableName) { + var fourMinAgo = DateTime.Now.AddMinutes(-4).ToString("O"); + var sql = + $"SELECT \"Id\",\"Content\",\"Retries\",\"Added\" FROM {tableName} WHERE \"Retries\"<{_capOptions.Value.FailedRetryCount} " + + $"AND \"Version\"='{_capOptions.Value.Version}' AND \"Added\"<'{fourMinAgo}' AND (\"StatusName\"='{StatusName.Failed}' OR \"StatusName\"='{StatusName.Scheduled}') LIMIT 200;"; + await using var connection = new NpgsqlConnection(_options.Value.ConnectionString); var result = connection.ExecuteReader(sql, reader => { diff --git a/src/DotNetCore.CAP.SqlServer/IDataStorage.SqlServer.cs b/src/DotNetCore.CAP.SqlServer/IDataStorage.SqlServer.cs index 20db036..dc83dec 100644 --- a/src/DotNetCore.CAP.SqlServer/IDataStorage.SqlServer.cs +++ b/src/DotNetCore.CAP.SqlServer/IDataStorage.SqlServer.cs @@ -142,24 +142,11 @@ namespace DotNetCore.CAP.SqlServer return await Task.FromResult(count); } - public async Task> GetPublishedMessagesOfNeedRetry() - { - var fourMinAgo = DateTime.Now.AddMinutes(-4).ToString("O"); - var sql = $"SELECT TOP (200) Id, Content, Retries, Added FROM {_pubName} WITH (readpast) WHERE Retries<{_capOptions.Value.FailedRetryCount} " + - $"AND Version='{_capOptions.Value.Version}' AND Added<'{fourMinAgo}' AND (StatusName = '{StatusName.Failed}' OR StatusName = '{StatusName.Scheduled}')"; - - return await GetMessagesOfNeedRetryAsync(sql); - } - - public async Task> GetReceivedMessagesOfNeedRetry() - { - var fourMinAgo = DateTime.Now.AddMinutes(-4).ToString("O"); - var sql = - $"SELECT TOP (200) Id, Content, Retries, Added FROM {_recName} WITH (readpast) WHERE Retries<{_capOptions.Value.FailedRetryCount} " + - $"AND Version='{_capOptions.Value.Version}' AND Added<'{fourMinAgo}' AND (StatusName = '{StatusName.Failed}' OR StatusName = '{StatusName.Scheduled}')"; + public async Task> GetPublishedMessagesOfNeedRetry() => + await GetMessagesOfNeedRetryAsync(_pubName); - return await GetMessagesOfNeedRetryAsync(sql); - } + public async Task> GetReceivedMessagesOfNeedRetry() => + await GetMessagesOfNeedRetryAsync(_recName); public IMonitoringApi GetMonitoringApi() { @@ -195,8 +182,13 @@ namespace DotNetCore.CAP.SqlServer connection.ExecuteNonQuery(sql, sqlParams: sqlParams); } - private async Task> GetMessagesOfNeedRetryAsync(string sql) + private async Task> GetMessagesOfNeedRetryAsync(string tableName) { + var fourMinAgo = DateTime.Now.AddMinutes(-4).ToString("O"); + var sql = + $"SELECT TOP (200) Id, Content, Retries, Added FROM {tableName} WITH (readpast) WHERE Retries<{_capOptions.Value.FailedRetryCount} " + + $"AND Version='{_capOptions.Value.Version}' AND Added<'{fourMinAgo}' AND (StatusName = '{StatusName.Failed}' OR StatusName = '{StatusName.Scheduled}')"; + List result; using (var connection = new SqlConnection(_options.Value.ConnectionString)) { diff --git a/src/DotNetCore.CAP.SqlServer/IMonitoringApi.SqlServer.cs b/src/DotNetCore.CAP.SqlServer/IMonitoringApi.SqlServer.cs index 3e908d5..0bbfc58 100644 --- a/src/DotNetCore.CAP.SqlServer/IMonitoringApi.SqlServer.cs +++ b/src/DotNetCore.CAP.SqlServer/IMonitoringApi.SqlServer.cs @@ -193,7 +193,7 @@ with aggr as ( where StatusName = @statusName group by replace(convert(varchar, Added, 111), '/','-') + '-' + CONVERT(varchar, DATEPART(hh, Added)) ) -select [Key], [Count] from aggr with (nolock) where [Key] in @keys;"; +select [Key], [Count] from aggr with (nolock) where [Key] >= @minKey and [Key] <= @maxKey;"; //SQL Server 2012+ var sqlQuery = $@" From 55079a3127286814b4af300e86a34ca254b94718 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Wed, 8 Jul 2020 10:41:44 +0800 Subject: [PATCH 04/85] Add AWS SQS Support (#603) --- CAP.sln | 7 + CAP.sln.DotSettings | 1 + .../AmazonSQSConsumerClient.cs | 166 ++++++++++++++++++ .../AmazonSQSConsumerClientFactory.cs | 31 ++++ .../CAP.AmazonSQSOptions.cs | 17 ++ .../CAP.AmazonSQSOptionsExtension.cs | 30 ++++ .../CAP.Options.Extensions.cs | 30 ++++ .../DotNetCore.CAP.AmazonSQS.csproj | 23 +++ .../ITransport.AmazonSQS.cs | 125 +++++++++++++ .../SQSReceivedMessage.cs | 18 ++ .../TopicNormalizer.cs | 21 +++ 11 files changed, 469 insertions(+) create mode 100644 src/DotNetCore.CAP.AmazonSQS/AmazonSQSConsumerClient.cs create mode 100644 src/DotNetCore.CAP.AmazonSQS/AmazonSQSConsumerClientFactory.cs create mode 100644 src/DotNetCore.CAP.AmazonSQS/CAP.AmazonSQSOptions.cs create mode 100644 src/DotNetCore.CAP.AmazonSQS/CAP.AmazonSQSOptionsExtension.cs create mode 100644 src/DotNetCore.CAP.AmazonSQS/CAP.Options.Extensions.cs create mode 100644 src/DotNetCore.CAP.AmazonSQS/DotNetCore.CAP.AmazonSQS.csproj create mode 100644 src/DotNetCore.CAP.AmazonSQS/ITransport.AmazonSQS.cs create mode 100644 src/DotNetCore.CAP.AmazonSQS/SQSReceivedMessage.cs create mode 100644 src/DotNetCore.CAP.AmazonSQS/TopicNormalizer.cs diff --git a/CAP.sln b/CAP.sln index b5be9a6..c494fab 100644 --- a/CAP.sln +++ b/CAP.sln @@ -65,6 +65,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.Test", "test EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.ConsoleApp", "samples\Sample.ConsoleApp\Sample.ConsoleApp.csproj", "{2B0F467E-ABBD-4A51-BF38-D4F609DB6266}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.AmazonSQS", "src\DotNetCore.CAP.AmazonSQS\DotNetCore.CAP.AmazonSQS.csproj", "{43475E00-51B7-443D-BC2D-FC21F9D8A0B4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -147,6 +149,10 @@ Global {2B0F467E-ABBD-4A51-BF38-D4F609DB6266}.Debug|Any CPU.Build.0 = Debug|Any CPU {2B0F467E-ABBD-4A51-BF38-D4F609DB6266}.Release|Any CPU.ActiveCfg = Release|Any CPU {2B0F467E-ABBD-4A51-BF38-D4F609DB6266}.Release|Any CPU.Build.0 = Release|Any CPU + {43475E00-51B7-443D-BC2D-FC21F9D8A0B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {43475E00-51B7-443D-BC2D-FC21F9D8A0B4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {43475E00-51B7-443D-BC2D-FC21F9D8A0B4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {43475E00-51B7-443D-BC2D-FC21F9D8A0B4}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -171,6 +177,7 @@ Global {93176BAE-914B-4BED-9DE3-01FFB4F27FC5} = {9B2AE124-6636-4DE9-83A3-70360DABD0C4} {75CC45E6-BF06-40F4-977D-10DCC05B2EFA} = {C09CDAB0-6DD4-46E9-B7F3-3EF2A4741EA0} {2B0F467E-ABBD-4A51-BF38-D4F609DB6266} = {3A6B6931-A123-477A-9469-8B468B5385AF} + {43475E00-51B7-443D-BC2D-FC21F9D8A0B4} = {9B2AE124-6636-4DE9-83A3-70360DABD0C4} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {2E70565D-94CF-40B4-BFE1-AC18D5F736AB} diff --git a/CAP.sln.DotSettings b/CAP.sln.DotSettings index 3eef960..486eace 100644 --- a/CAP.sln.DotSettings +++ b/CAP.sln.DotSettings @@ -1,4 +1,5 @@  DB + SNS True True \ No newline at end of file diff --git a/src/DotNetCore.CAP.AmazonSQS/AmazonSQSConsumerClient.cs b/src/DotNetCore.CAP.AmazonSQS/AmazonSQSConsumerClient.cs new file mode 100644 index 0000000..2bd6ee4 --- /dev/null +++ b/src/DotNetCore.CAP.AmazonSQS/AmazonSQSConsumerClient.cs @@ -0,0 +1,166 @@ +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using Amazon.SimpleNotificationService; +using Amazon.SimpleNotificationService.Model; +using Amazon.SQS; +using Amazon.SQS.Model; +using DotNetCore.CAP.Messages; +using DotNetCore.CAP.Transport; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; +using Headers = DotNetCore.CAP.Messages.Headers; + +namespace DotNetCore.CAP.AmazonSQS +{ + internal sealed class AmazonSQSConsumerClient : IConsumerClient + { + private static readonly SemaphoreSlim ConnectionLock = new SemaphoreSlim(initialCount: 1, maxCount: 1); + + private readonly string _groupId; + private readonly AmazonSQSOptions _amazonSQSOptions; + + private IAmazonSimpleNotificationService _snsClient; + private IAmazonSQS _sqsClient; + private string _queueUrl = string.Empty; + + public AmazonSQSConsumerClient(string groupId, IOptions options) + { + _groupId = groupId; + _amazonSQSOptions = options.Value; + } + + public event EventHandler OnMessageReceived; + + public event EventHandler OnLog; + + public BrokerAddress BrokerAddress => new BrokerAddress("AmazonSQS", _queueUrl); + + public void Subscribe(IEnumerable topics) + { + if (topics == null) + { + throw new ArgumentNullException(nameof(topics)); + } + + Connect(initSNS: true, initSQS: false); + + var topicArns = new List(); + foreach (var topic in topics) + { + var createTopicRequest = new CreateTopicRequest(topic.NormalizeForAws()); + + var createTopicResponse = _snsClient.CreateTopicAsync(createTopicRequest).GetAwaiter().GetResult(); + + topicArns.Add(createTopicResponse.TopicArn); + } + + Connect(initSNS: false, initSQS: true); + + _snsClient.SubscribeQueueToTopicsAsync(topicArns, _sqsClient, _queueUrl) + .GetAwaiter().GetResult(); + } + + public void Listening(TimeSpan timeout, CancellationToken cancellationToken) + { + Connect(); + + var request = new ReceiveMessageRequest(_queueUrl) + { + WaitTimeSeconds = 5, + MaxNumberOfMessages = 1 + }; + + while (true) + { + var response = _sqsClient.ReceiveMessageAsync(request, cancellationToken).GetAwaiter().GetResult(); + + if (response.Messages.Count == 1) + { + var messageObj = JsonConvert.DeserializeObject(response.Messages[0].Body); + + var header = messageObj.MessageAttributes.ToDictionary(x => x.Key, x => x.Value.Value); + var body = messageObj.Message; + + var message = new TransportMessage(header, body != null ? Encoding.UTF8.GetBytes(body) : null); + + message.Headers.Add(Headers.Group, _groupId); + + OnMessageReceived?.Invoke(response.Messages[0].ReceiptHandle, message); + } + else + { + cancellationToken.ThrowIfCancellationRequested(); + cancellationToken.WaitHandle.WaitOne(timeout); + } + } + } + + public void Commit(object sender) + { + _sqsClient.DeleteMessageAsync(_queueUrl, (string)sender); + } + + public void Reject(object sender) + { + _sqsClient.ChangeMessageVisibilityAsync(_queueUrl, (string)sender, 3000); + } + + public void Dispose() + { + _sqsClient?.Dispose(); + _snsClient?.Dispose(); + } + + public void Connect(bool initSNS = true, bool initSQS = true) + { + if (_snsClient != null && _sqsClient != null) + { + return; + } + + if (_snsClient == null && initSNS) + { + ConnectionLock.Wait(); + + try + { + _snsClient = _amazonSQSOptions.Credentials != null + ? new AmazonSimpleNotificationServiceClient(_amazonSQSOptions.Credentials, _amazonSQSOptions.Region) + : new AmazonSimpleNotificationServiceClient(_amazonSQSOptions.Region); + } + finally + { + ConnectionLock.Release(); + } + } + + if (_sqsClient == null && initSQS) + { + ConnectionLock.Wait(); + + try + { + + _sqsClient = _amazonSQSOptions.Credentials != null + ? new AmazonSQSClient(_amazonSQSOptions.Credentials, _amazonSQSOptions.Region) + : new AmazonSQSClient(_amazonSQSOptions.Region); + + // If provide the name of an existing queue along with the exact names and values + // of all the queue's attributes, CreateQueue returns the queue URL for + // the existing queue. + _queueUrl = _sqsClient.CreateQueueAsync(_groupId.NormalizeForAws()).GetAwaiter().GetResult().QueueUrl; + } + finally + { + ConnectionLock.Release(); + } + } + } + } +} \ No newline at end of file diff --git a/src/DotNetCore.CAP.AmazonSQS/AmazonSQSConsumerClientFactory.cs b/src/DotNetCore.CAP.AmazonSQS/AmazonSQSConsumerClientFactory.cs new file mode 100644 index 0000000..7665991 --- /dev/null +++ b/src/DotNetCore.CAP.AmazonSQS/AmazonSQSConsumerClientFactory.cs @@ -0,0 +1,31 @@ +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using DotNetCore.CAP.Transport; +using Microsoft.Extensions.Options; + +namespace DotNetCore.CAP.AmazonSQS +{ + internal sealed class AmazonSQSConsumerClientFactory : IConsumerClientFactory + { + private readonly IOptions _amazonSQSOptions; + + public AmazonSQSConsumerClientFactory(IOptions amazonSQSOptions) + { + _amazonSQSOptions = amazonSQSOptions; + } + + public IConsumerClient Create(string groupId) + { + try + { + var client = new AmazonSQSConsumerClient(groupId, _amazonSQSOptions); + return client; + } + catch (System.Exception e) + { + throw new BrokerConnectionException(e); + } + } + } +} \ No newline at end of file diff --git a/src/DotNetCore.CAP.AmazonSQS/CAP.AmazonSQSOptions.cs b/src/DotNetCore.CAP.AmazonSQS/CAP.AmazonSQSOptions.cs new file mode 100644 index 0000000..00c751c --- /dev/null +++ b/src/DotNetCore.CAP.AmazonSQS/CAP.AmazonSQSOptions.cs @@ -0,0 +1,17 @@ +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Amazon; +using Amazon.Runtime; + +// ReSharper disable once CheckNamespace +namespace DotNetCore.CAP +{ + // ReSharper disable once InconsistentNaming + public class AmazonSQSOptions + { + public RegionEndpoint Region { get; set; } + + public AWSCredentials Credentials { get; set; } + } +} \ No newline at end of file diff --git a/src/DotNetCore.CAP.AmazonSQS/CAP.AmazonSQSOptionsExtension.cs b/src/DotNetCore.CAP.AmazonSQS/CAP.AmazonSQSOptionsExtension.cs new file mode 100644 index 0000000..8f246fc --- /dev/null +++ b/src/DotNetCore.CAP.AmazonSQS/CAP.AmazonSQSOptionsExtension.cs @@ -0,0 +1,30 @@ +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using DotNetCore.CAP.AmazonSQS; +using DotNetCore.CAP.Transport; +using Microsoft.Extensions.DependencyInjection; + +// ReSharper disable once CheckNamespace +namespace DotNetCore.CAP +{ + internal sealed class AmazonSQSOptionsExtension : ICapOptionsExtension + { + private readonly Action _configure; + + public AmazonSQSOptionsExtension(Action configure) + { + _configure = configure; + } + + public void AddServices(IServiceCollection services) + { + services.AddSingleton(); + + services.Configure(_configure); + services.AddSingleton(); + services.AddSingleton(); + } + } +} \ No newline at end of file diff --git a/src/DotNetCore.CAP.AmazonSQS/CAP.Options.Extensions.cs b/src/DotNetCore.CAP.AmazonSQS/CAP.Options.Extensions.cs new file mode 100644 index 0000000..23ede03 --- /dev/null +++ b/src/DotNetCore.CAP.AmazonSQS/CAP.Options.Extensions.cs @@ -0,0 +1,30 @@ +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Amazon; +using DotNetCore.CAP; + +// ReSharper disable once CheckNamespace +namespace Microsoft.Extensions.DependencyInjection +{ + public static class CapOptionsExtensions + { + public static CapOptions UseAmazonSQS(this CapOptions options, RegionEndpoint region) + { + return options.UseAmazonSQS(opt => { opt.Region = region; }); + } + + public static CapOptions UseAmazonSQS(this CapOptions options, Action configure) + { + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } + + options.RegisterExtension(new AmazonSQSOptionsExtension(configure)); + + return options; + } + } +} \ No newline at end of file diff --git a/src/DotNetCore.CAP.AmazonSQS/DotNetCore.CAP.AmazonSQS.csproj b/src/DotNetCore.CAP.AmazonSQS/DotNetCore.CAP.AmazonSQS.csproj new file mode 100644 index 0000000..5a7116b --- /dev/null +++ b/src/DotNetCore.CAP.AmazonSQS/DotNetCore.CAP.AmazonSQS.csproj @@ -0,0 +1,23 @@ + + + + netstandard2.0 + DotNetCore.CAP.AmazonSQS + $(PackageTags);AmazonSQS;SQS + + + + bin\$(Configuration)\netstandard2.0\DotNetCore.CAP.AmazonSQS.xml + 1701;1702;1705;CS1591 + + + + + + + + + + + + \ No newline at end of file diff --git a/src/DotNetCore.CAP.AmazonSQS/ITransport.AmazonSQS.cs b/src/DotNetCore.CAP.AmazonSQS/ITransport.AmazonSQS.cs new file mode 100644 index 0000000..2ab3bf6 --- /dev/null +++ b/src/DotNetCore.CAP.AmazonSQS/ITransport.AmazonSQS.cs @@ -0,0 +1,125 @@ +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Amazon.SimpleNotificationService; +using Amazon.SimpleNotificationService.Model; +using DotNetCore.CAP.Internal; +using DotNetCore.CAP.Messages; +using DotNetCore.CAP.Transport; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace DotNetCore.CAP.AmazonSQS +{ + internal sealed class AmazonSQSTransport : ITransport + { + private readonly ILogger _logger; + private readonly IOptions _sqsOptions; + private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); + private IAmazonSimpleNotificationService _snsClient; + private IDictionary _topicArnMaps; + + public AmazonSQSTransport(ILogger logger, IOptions sqsOptions) + { + _logger = logger; + _sqsOptions = sqsOptions; + } + + public BrokerAddress BrokerAddress => new BrokerAddress("RabbitMQ", string.Empty); + + public async Task SendAsync(TransportMessage message) + { + try + { + await TryAddTopicArns(); + + if (_topicArnMaps.TryGetValue(message.GetName().NormalizeForAws(), out var arn)) + { + string bodyJson = null; + if (message.Body != null) + { + bodyJson = Encoding.UTF8.GetString(message.Body); + } + + var attributes = message.Headers.Where(x => x.Value != null).ToDictionary(x => x.Key, + x => new MessageAttributeValue + { + StringValue = x.Value, + DataType = "String" + }); + + var request = new PublishRequest(arn, bodyJson) + { + MessageAttributes = attributes + }; + + await _snsClient.PublishAsync(request); + + _logger.LogDebug($"SNS topic message [{message.GetName().NormalizeForAws()}] has been published."); + } + else + { + _logger.LogWarning($"Can't be found SNS topics for [{message.GetName().NormalizeForAws()}]"); + } + return OperateResult.Success; + } + catch (Exception ex) + { + var wrapperEx = new PublisherSentFailedException(ex.Message, ex); + var errors = new OperateError + { + Code = ex.HResult.ToString(), + Description = ex.Message + }; + + return OperateResult.Failed(wrapperEx, errors); + } + } + + public async Task TryAddTopicArns() + { + if (_topicArnMaps != null) + { + return true; + } + + await _semaphore.WaitAsync(); + + try + { + _snsClient = _sqsOptions.Value.Credentials != null + ? new AmazonSimpleNotificationServiceClient(_sqsOptions.Value.Credentials, _sqsOptions.Value.Region) + : new AmazonSimpleNotificationServiceClient(_sqsOptions.Value.Region); + + if (_topicArnMaps == null) + { + _topicArnMaps = new Dictionary(); + var topics = await _snsClient.ListTopicsAsync(); + topics.Topics.ForEach(x => + { + var name = x.TopicArn.Split(':').Last(); + _topicArnMaps.Add(name, x.TopicArn); + }); + + return true; + } + } + catch (Exception e) + { + _logger.LogError(e, "Init topics from aws sns error!"); + } + finally + { + _semaphore.Release(); + } + + return false; + } + } +} \ No newline at end of file diff --git a/src/DotNetCore.CAP.AmazonSQS/SQSReceivedMessage.cs b/src/DotNetCore.CAP.AmazonSQS/SQSReceivedMessage.cs new file mode 100644 index 0000000..893b418 --- /dev/null +++ b/src/DotNetCore.CAP.AmazonSQS/SQSReceivedMessage.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; + +namespace DotNetCore.CAP.AmazonSQS +{ + class SQSReceivedMessage + { + public string Message { get; set; } + + public Dictionary MessageAttributes { get; set; } + } + + class SQSReceivedMessageAttributes + { + public string Type { get; set; } + + public string Value { get; set; } + } +} diff --git a/src/DotNetCore.CAP.AmazonSQS/TopicNormalizer.cs b/src/DotNetCore.CAP.AmazonSQS/TopicNormalizer.cs new file mode 100644 index 0000000..f6d0965 --- /dev/null +++ b/src/DotNetCore.CAP.AmazonSQS/TopicNormalizer.cs @@ -0,0 +1,21 @@ +using System; + +namespace DotNetCore.CAP.AmazonSQS +{ + public static class TopicNormalizer + { + public static string NormalizeForAws(this string origin) + { + if (origin.Length > 256) + { + throw new ArgumentOutOfRangeException(nameof(origin) + " character string length must between 1~256!"); + } + return origin.Replace(".", "-").Replace(":", "_"); + } + + public static string DeNormalizeForAws(this string origin) + { + return origin.Replace("-", ".").Replace("_", ":"); + } + } +} From 6f41916241ef59f86d45ffebed58557674b64878 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Wed, 8 Jul 2020 16:16:18 +0800 Subject: [PATCH 05/85] Remove unused code --- src/DotNetCore.CAP.AmazonSQS/TopicNormalizer.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/DotNetCore.CAP.AmazonSQS/TopicNormalizer.cs b/src/DotNetCore.CAP.AmazonSQS/TopicNormalizer.cs index f6d0965..f08c481 100644 --- a/src/DotNetCore.CAP.AmazonSQS/TopicNormalizer.cs +++ b/src/DotNetCore.CAP.AmazonSQS/TopicNormalizer.cs @@ -11,11 +11,6 @@ namespace DotNetCore.CAP.AmazonSQS throw new ArgumentOutOfRangeException(nameof(origin) + " character string length must between 1~256!"); } return origin.Replace(".", "-").Replace(":", "_"); - } - - public static string DeNormalizeForAws(this string origin) - { - return origin.Replace("-", ".").Replace("_", ":"); - } + } } } From 67ebf8dcc14699aeba4009e45d3f435e794b7c75 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Wed, 8 Jul 2020 16:22:56 +0800 Subject: [PATCH 06/85] Add aws sqs transport docs. (#597) --- docs/content/img/aws-sns-demo.png | Bin 0 -> 31569 bytes docs/content/img/aws-sqs-demo.png | Bin 0 -> 62756 bytes .../user-guide/en/transports/aws-sqs.md | 90 +++++++++++++++++ .../user-guide/en/transports/general.md | 1 + .../user-guide/zh/transports/aws-sqs.md | 91 ++++++++++++++++++ .../user-guide/zh/transports/general.md | 1 + 6 files changed, 183 insertions(+) create mode 100644 docs/content/img/aws-sns-demo.png create mode 100644 docs/content/img/aws-sqs-demo.png create mode 100644 docs/content/user-guide/en/transports/aws-sqs.md create mode 100644 docs/content/user-guide/zh/transports/aws-sqs.md diff --git a/docs/content/img/aws-sns-demo.png b/docs/content/img/aws-sns-demo.png new file mode 100644 index 0000000000000000000000000000000000000000..f708c2b340d7ab04022ba92ee6c77b28edef34e2 GIT binary patch literal 31569 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 literal 0 HcmV?d00001 diff --git a/docs/content/img/aws-sqs-demo.png b/docs/content/img/aws-sqs-demo.png new file mode 100644 index 0000000000000000000000000000000000000000..1e6c791be0a2d053d2576a8a9fa10da94b78c225 GIT binary patch literal 62756 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 + { + x.UseAmazonSQS(opt=> + { + //AmazonSQSOptions + }); + // x.UseXXX ... + }); +} + +``` + +#### AmazonSQS Options + +CAP 直接对外提供的 AmazonSQSOptions 配置参数如下: + +NAME | DESCRIPTION | TYPE | DEFAULT +:---|:---|---|:--- +Region | AWS 所处的区域 | Amazon.RegionEndpoint | +Credentials | AWS AK SK信息 | Amazon.Runtime.AWSCredentials | + +如果你的项目运行在 AWS EC2 中,则不需要设置 Credentials,直接对 EC2 应用 IAM 策略即可。 + +Credentials 需要具有新增和订阅 SNS Topic,SQS Queue 等权限。 \ No newline at end of file diff --git a/docs/content/user-guide/en/transports/general.md b/docs/content/user-guide/en/transports/general.md index 1272bf4..4e764ba 100644 --- a/docs/content/user-guide/en/transports/general.md +++ b/docs/content/user-guide/en/transports/general.md @@ -9,6 +9,7 @@ CAP supports several transport methods: * [RabbitMQ](rabbitmq.md) * [Kafka](kafka.md) * [Azure Service Bus](azure-service-bus.md) +* [Amazon SQS](aws-sqs.md) * [In-Memory Queue](in-memory-queue.md) ## How to select a transport diff --git a/docs/content/user-guide/zh/transports/aws-sqs.md b/docs/content/user-guide/zh/transports/aws-sqs.md new file mode 100644 index 0000000..f60a0f1 --- /dev/null +++ b/docs/content/user-guide/zh/transports/aws-sqs.md @@ -0,0 +1,91 @@ +# Amazon SQS + +AWS SQS 是一种完全托管的消息队列服务,可让您分离和扩展微服务、分布式系统和无服务器应用程序。 + +AWS SNS 是一种高度可用、持久、安全、完全托管的发布/订阅消息收发服务,可以轻松分离微服务、分布式系统和无服务器应用程序。 + +## CAP 如何使用 AWS SNS & SQS + +### SNS + +由于 CAP 是基于 Topic 模式工作的,所以需要使用到 AWS SNS,SNS 简化了消息的发布订阅架构。 + +在 CAP 启动时会将所有的订阅名称注册为 SNS 的 Topic,你将会在管理控制台中看到所有已经注册的 Topic 列表。 + +由于 SNS 不支持使用 `.` `:` 等符号作为 Topic 的名称,所以我们进行了替换,我们将 `.` 替换为了 `-`,将 `:` 替换为了 `_` + +!!! note "注意事项" + Amazon SNS 当前允许发布的消息最大大小为 256KB + +举例,你的当前项目中有以下两个订阅者方法 + +```C# +[CapSubscribe("sample.sns.foo")] +public void TestFoo(DateTime value) +{ +} + +[CapSubscribe("sample.sns.bar")] +public void TestBar(DateTime value) +{ +} +``` + +在 CAP 启动后,在 AWS SNS 中你将看到 + +![img](/img/aws-sns-demo.png) + +### SQS + +针对每个消费者组,CAP 将创建一个与之对应的 SQS 队列,队列的名称为配置项中 DefaultGroup 的名称,类型为 Standard Queue 。 + +该 SQS 队列将订阅 SNS 中的 Topic ,如下图: + +![img](/img/aws-sns-demo.png) + +!!! warning "注意事项" + 由于 AWS SNS 的限制,当你减少订阅方法时,我们不会主动删除 AWS SNS 或者 SQS 上的相关 Topic 或 Queue,你需要手动删除他们。 + + +## 配置 + +要使用 AWS SQS 作为消息传输器,你需要从 NuGet 安装以下扩展包: + +```shell + +Install-Package DotNetCore.CAP.AmazonSQS + +``` + +然后,你可以在 `Startup.cs` 的 `ConfigureServices` 方法中添加基于 RabbitMQ 的配置项。 + +```csharp + +public void ConfigureServices(IServiceCollection services) +{ + // ... + + services.AddCap(x => + { + x.UseAmazonSQS(opt=> + { + //AmazonSQSOptions + }); + // x.UseXXX ... + }); +} + +``` + +#### AmazonSQS Options + +CAP 直接对外提供的 AmazonSQSOptions 配置参数如下: + +NAME | DESCRIPTION | TYPE | DEFAULT +:---|:---|---|:--- +Region | AWS 所处的区域 | Amazon.RegionEndpoint | +Credentials | AWS AK SK信息 | Amazon.Runtime.AWSCredentials | + +如果你的项目运行在 AWS EC2 中,则不需要设置 Credentials,直接对 EC2 应用 IAM 策略即可。 + +Credentials 需要具有新增和订阅 SNS Topic,SQS Queue 等权限。 \ No newline at end of file diff --git a/docs/content/user-guide/zh/transports/general.md b/docs/content/user-guide/zh/transports/general.md index 5df35e2..6fc75d1 100644 --- a/docs/content/user-guide/zh/transports/general.md +++ b/docs/content/user-guide/zh/transports/general.md @@ -9,6 +9,7 @@ CAP 支持以下几种运输方式: * [RabbitMQ](rabbitmq.md) * [Kafka](kafka.md) * [Azure Service Bus](azure-service-bus.md) +* [Amazon SQS](aws-sqs.md) * [In-Memory Queue](in-memory-queue.md) ## 怎么选择运输器 From 8b51b1d058360f2124138dab1d021d665065a9c4 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Wed, 8 Jul 2020 16:24:13 +0800 Subject: [PATCH 07/85] Upgrade mkdocs-material to 5.x --- docs/mkdocs.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 2d988c6..3cde8fc 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -23,8 +23,8 @@ theme: include_sidebar: true logo: 'img/logo.svg' favicon: 'img/favicon.ico' - feature: - tabs: true + features: + - tabs i18n: prev: 'Previous' next: 'Next' @@ -32,11 +32,11 @@ theme: #Customization extra: social: - - type: 'github' + - icon: 'fontawesome/brands/github' link: 'https://github.com/dotnetcore/CAP' - - type: 'twitter' + - icon: 'fontawesome/brands/twitter' link: 'https://twitter.com/ncc_community' - - type: 'weibo' + - icon: 'fontawesome/brands/weibo' link: 'https://weibo.com/dotnetcore' # Extensions @@ -90,6 +90,7 @@ nav: - RabbitMQ: user-guide/en/transports/rabbitmq.md - Apache Kafka®: user-guide/en/transports/kafka.md - Azure Service Bus: user-guide/en/transports/azure-service-bus.md + - Amazon SQS: user-guide/en/transports/aws-sqs.md - In-Memory Queue: user-guide/en/transports/in-memory-queue.md - Persistent: - General: user-guide/en/persistent/general.md @@ -122,6 +123,7 @@ nav: - RabbitMQ: user-guide/zh/transports/rabbitmq.md - Apache Kafka®: user-guide/zh/transports/kafka.md - Azure Service Bus: user-guide/zh/transports/azure-service-bus.md + - Amazon SQS: user-guide/zh/transports/aws-sqs.md - In-Memory Queue: user-guide/zh/transports/in-memory-queue.md - 持久化: - 简介: user-guide/zh/persistent/general.md From 8215dd51c52d0a17cb699ea46ffcf6d04fa5ad4b Mon Sep 17 00:00:00 2001 From: Savorboard Date: Wed, 8 Jul 2020 16:24:45 +0800 Subject: [PATCH 08/85] update docs --- docs/content/index.md | 2 +- docs/content/user-guide/en/persistent/sqlserver.md | 3 +++ .../content/user-guide/en/transports/azure-service-bus.md | 4 +--- docs/content/user-guide/en/transports/in-memory-queue.md | 3 ++- docs/content/user-guide/en/transports/kafka.md | 2 +- docs/content/user-guide/en/transports/rabbitmq.md | 8 ++++++++ docs/content/user-guide/zh/persistent/sqlserver.md | 3 +++ docs/content/user-guide/zh/transports/rabbitmq.md | 8 ++++++++ 8 files changed, 27 insertions(+), 6 deletions(-) diff --git a/docs/content/index.md b/docs/content/index.md index 2b18b49..ed388f2 100644 --- a/docs/content/index.md +++ b/docs/content/index.md @@ -2,7 +2,7 @@ Title: CAP - A distributed transaction solution in micro-service base on eventua # CAP - + [![Travis branch](https://img.shields.io/travis/dotnetcore/CAP/master.svg?label=travis-ci)](https://travis-ci.org/dotnetcore/CAP) [![AppVeyor](https://ci.appveyor.com/api/projects/status/v8gfh6pe2u2laqoa/branch/master?svg=true)](https://ci.appveyor.com/project/yuleyule66/cap/branch/master) [![NuGet](https://img.shields.io/nuget/v/DotNetCore.CAP.svg)](https://www.nuget.org/packages/DotNetCore.CAP/) diff --git a/docs/content/user-guide/en/persistent/sqlserver.md b/docs/content/user-guide/en/persistent/sqlserver.md index 124f8f1..6610856 100644 --- a/docs/content/user-guide/en/persistent/sqlserver.md +++ b/docs/content/user-guide/en/persistent/sqlserver.md @@ -2,6 +2,9 @@ SQL Server is a relational database management system developed by Microsoft. CAP has supported SQL Server as persistent. +!!! warning "Warning" + We currently use `Microsoft.Data.SqlClient` as the database driver, which is the future of SQL Server drivers, and we have abandoned `System.Data.SqlClient`, we suggest you switch into. + ## Configuration To use SQL Server storage, you need to install the following extensions from NuGet: diff --git a/docs/content/user-guide/en/transports/azure-service-bus.md b/docs/content/user-guide/en/transports/azure-service-bus.md index f027de0..e046341 100644 --- a/docs/content/user-guide/en/transports/azure-service-bus.md +++ b/docs/content/user-guide/en/transports/azure-service-bus.md @@ -14,9 +14,7 @@ To use Azure Service Bus as a message transport, you need to install the followi ```powershell PM> Install-Package DotNetCore.CAP.AzureServiceBus ``` - -Then you can add memory-based configuration items to the `ConfigureServices` method of `Startup.cs`. - +Next, add configuration items to the `ConfigureServices` method of `Startup.cs`: ```csharp diff --git a/docs/content/user-guide/en/transports/in-memory-queue.md b/docs/content/user-guide/en/transports/in-memory-queue.md index cc99972..60cae3a 100644 --- a/docs/content/user-guide/en/transports/in-memory-queue.md +++ b/docs/content/user-guide/en/transports/in-memory-queue.md @@ -10,7 +10,8 @@ To use In Memory Queue as a message transporter, you need to install the followi PM> Install-Package Savorboard.CAP.InMemoryMessageQueue ``` -Then you can add memory-based configuration items to the `ConfigureServices` method of `Startup.cs`. + +Next, add configuration items to the `ConfigureServices` method of `Startup.cs`: ```csharp diff --git a/docs/content/user-guide/en/transports/kafka.md b/docs/content/user-guide/en/transports/kafka.md index 5f1bc09..0af9d15 100644 --- a/docs/content/user-guide/en/transports/kafka.md +++ b/docs/content/user-guide/en/transports/kafka.md @@ -13,7 +13,7 @@ PM> Install-Package DotNetCore.CAP.Kafka ``` -Then you can add memory-based configuration items to the `ConfigureServices` method of `Startup.cs`. +Then you can add configuration items to the `ConfigureServices` method of `Startup.cs`. ```csharp diff --git a/docs/content/user-guide/en/transports/rabbitmq.md b/docs/content/user-guide/en/transports/rabbitmq.md index 5d79991..383da27 100644 --- a/docs/content/user-guide/en/transports/rabbitmq.md +++ b/docs/content/user-guide/en/transports/rabbitmq.md @@ -64,4 +64,12 @@ services.AddCap(x => }); }); +``` + +#### How to connect cluster + +using comma split connection string, like this: + +``` +x=> x.UseRabbitMQ("localhost:5672,localhost:5673,localhost:5674") ``` \ No newline at end of file diff --git a/docs/content/user-guide/zh/persistent/sqlserver.md b/docs/content/user-guide/zh/persistent/sqlserver.md index bd0315f..1cd0ac6 100644 --- a/docs/content/user-guide/zh/persistent/sqlserver.md +++ b/docs/content/user-guide/zh/persistent/sqlserver.md @@ -2,6 +2,9 @@ SQL Server 是由微软开发的一个关系型数据库,你可以使用 SQL Server 来作为 CAP 消息的持久化。 +!!! warning "注意" + 我们目前使用 `Microsoft.Data.SqlClient` 作为数据库驱动程序,它是SQL Server 驱动的未来,并且已经放弃了 `System.Data.SqlClient`,我们建议你切换过去。 + ## 配置 要使用 SQL Server 存储,你需要从 NuGet 安装以下扩展包: diff --git a/docs/content/user-guide/zh/transports/rabbitmq.md b/docs/content/user-guide/zh/transports/rabbitmq.md index 09b7c7f..ed9d2d9 100644 --- a/docs/content/user-guide/zh/transports/rabbitmq.md +++ b/docs/content/user-guide/zh/transports/rabbitmq.md @@ -66,3 +66,11 @@ services.AddCap(x => }); ``` + +#### 如何连接 RabbitMQ 集群? + +使用逗号分隔连接字符串即可,如下: + +``` +x=> x.UseRabbitMQ("localhost:5672,localhost:5673,localhost:5674") +``` From 58a4faca01f686f889e01511ea6b0b9acc099a03 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Wed, 8 Jul 2020 17:57:19 +0800 Subject: [PATCH 09/85] Add new version introduction article to docs. --- docs/content/user-guide/en/getting-started/introduction.md | 4 ++++ docs/content/user-guide/zh/getting-started/introduction.md | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/docs/content/user-guide/en/getting-started/introduction.md b/docs/content/user-guide/en/getting-started/introduction.md index 0f51d86..14fd85b 100644 --- a/docs/content/user-guide/en/getting-started/introduction.md +++ b/docs/content/user-guide/en/getting-started/introduction.md @@ -25,6 +25,10 @@ The CAP is modular in design and highly scalable. You have many options to choos [Article: Introduction and how to use](http://www.cnblogs.com/savorboard/p/cap.html) +[Article: New features in version 3.0](https://www.cnblogs.com/savorboard/p/cap-3-0.html) + +[Article: New features in version 2.6](https://www.cnblogs.com/savorboard/p/cap-2-6.html) + [Article: New features in version 2.5](https://www.cnblogs.com/savorboard/p/cap-2-5.html) [Article: New features in version 2.4](http://www.cnblogs.com/savorboard/p/cap-2-4.html) diff --git a/docs/content/user-guide/zh/getting-started/introduction.md b/docs/content/user-guide/zh/getting-started/introduction.md index 517092b..68c49ba 100644 --- a/docs/content/user-guide/zh/getting-started/introduction.md +++ b/docs/content/user-guide/zh/getting-started/introduction.md @@ -25,6 +25,10 @@ CAP 采用模块化设计,具有高度的可扩展性。你有许多选项可 [Article: CAP 介绍及使用](http://www.cnblogs.com/savorboard/p/cap.html) +[Article: CAP 3.0 版本中的新特性](https://www.cnblogs.com/savorboard/p/cap-3-0.html) + +[Article: CAP 2.6 版本中的新特性](https://www.cnblogs.com/savorboard/p/cap-2-6.html) + [Article: CAP 2.5 版本中的新特性](https://www.cnblogs.com/savorboard/p/cap-2-5.html) [Article: CAP 2.4 版本中的新特性](http://www.cnblogs.com/savorboard/p/cap-2-4.html) From 0ee6262b69791c1c76aa54346e228d54bc4fc00f Mon Sep 17 00:00:00 2001 From: Savorboard Date: Wed, 8 Jul 2020 18:05:00 +0800 Subject: [PATCH 10/85] Add release notes to docs. --- docs/content/about/license.md | 2 +- docs/content/about/release-notes.md | 89 +++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 1 deletion(-) diff --git a/docs/content/about/license.md b/docs/content/about/license.md index b26942f..2a9e2e8 100644 --- a/docs/content/about/license.md +++ b/docs/content/about/license.md @@ -2,7 +2,7 @@ **MIT License** -Copyright (c) 2016 - 2019 Savorboard +Copyright (c) 2016 - 2020 Savorboard Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/docs/content/about/release-notes.md b/docs/content/about/release-notes.md index d5ef6e8..35f03d8 100644 --- a/docs/content/about/release-notes.md +++ b/docs/content/about/release-notes.md @@ -1,5 +1,94 @@ # Release Notes +## Version 3.0.4 (2020-05-27) + +**Bug Fixed:** + +* Fix kafka consumer group does not works bug. (#541) +* Fix cast object to primitive types failed bug. (#547) +* Fix subscriber primitive types convert exception. (#568) +* Add conosole app sample. +* Upgrade Confluent.Kafka to 1.4.3 + + +## Version 3.0.3 (2020-04-01) + +**Bug Fixed:** + +* Change ISubscribeInvoker interface access modifier to public. (#537) +* Fix rabbitmq connection may be reused when close forced. (#533) +* Fix dahsboard message reexecute button throws exception bug. (#525) + +## Version 3.0.2 (2020-02-05) + +**Bug Fixed:** + +- Fixed diagnostics event data object error. (#504 ) +- Fixed RabbitMQ transport check not working. (#503 ) +- Fixed Azure Service Bus subscriber error. (#502 ) + +## Version 3.0.1 (2020-01-19) + +**Bug Fixed:** + +* Fixed Dashboard requeue and reconsume failed bug. (#482 ) +* Fixed Azure service bus null reference exception. (#483 ) +* Fixed type cast exception from storage. (#473 ) +* Fixed SqlServer connection undisponse bug. (#477 ) + +## Version 3.0.0 (2019-12-30) + +**Breaking Changes:** + +In this version, we have made major improvements to the code structure, which have introduced some destructive changes. + +* Publisher and Consumer are not compatible with older versions +This version is not compatible with older versions of the message protocol because we have improved the format in which messages are published and stored. + +* Interface changes +We have done a lot of refactoring of the code, and some of the interfaces may be incompatible with older versions + +* Detach the dashboard project + +**Features:** + +* Supports .NET Core 3.1. +* Upgrade dependent packages. +* New serialization interface `ISerializer` to support serialization of message body sent to MQ. +* Add new api for `ICapPublisher` to publish message with headers. +* Diagnostics event structure and names improved. #378 +* Support consumer method to read the message headers. #472 +* Support rename message storage tables. #435 +* Support for Kafka to write such as Offset and Partition to the header. #374 +* Improved the processor retry interval time. #444 + +**Bug Fixed:** + +* Fixed SqlServer dashboard sql query bug. #470 +* Fixed Kafka health check bug. #436 +* Fixed dashboard bugs. #412 #404 +* Fixed transaction bug for sql server when using EF. #402 + + +## Version 2.6.0 (2019-08-29) + +**Features:** + +* Improvement Diagnostic support. Thanks [@gfx687](https://github.com/gfx687) +* Improvement documention. https://cap.dotnetcore.xyz +* Improvement `ConsumerInvoker` implementation. Thanks [@hetaoos](https://github.com/hetaoos) +* Support multiple consumer threads. (#295) +* Change DashboardMiddleware to async. (#390) Thanks [@liuzhenyulive](https://github.com/liuzhenyulive) + +**Bug Fixed:** + +* SQL Server Options Bug. +* Fix transaction scope disposed bug. (#365) +* Fix thread safe issue of ICapPublisher bug. (#371) +* Improved Ctrl+C action raised exception issue. +* Fixed asynchronous exception catching bug of sending. +* Fix MatchPoundUsingRegex "." not escaped bug (#373) + ## Version 2.5.1 (2019-06-21) **Features:** From c5a8f06cf0f25fd6132753ca36f5492ff09fd4e4 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Thu, 9 Jul 2020 09:49:10 +0800 Subject: [PATCH 11/85] Update appveyor.yml --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 2e8fe7e..e928f22 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -19,5 +19,5 @@ deploy: appveyor_repo_tag: true api_key: secure: PZXRBOGLyhYLP7ulHfrh6MnkqB8CstuitgbLcJr3cZkLJLLzPH0ahvuTtmhWxtR2 - skip_symbols: true + skip_symbols: false artifact: /artifacts\/.+\.nupkg/ From 16ae853feedd083b5869d4de7f63c01f178a76a7 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Fri, 10 Jul 2020 09:58:12 +0800 Subject: [PATCH 12/85] Sets the symbol file to new format --- appveyor.yml | 4 ++-- src/Directory.Build.props | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index e928f22..018ca7a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -12,7 +12,7 @@ build_script: - ps: flubu test: off artifacts: -- path: artifacts/*.nupkg +- path: artifacts/** deploy: provider: NuGet on: @@ -20,4 +20,4 @@ deploy: api_key: secure: PZXRBOGLyhYLP7ulHfrh6MnkqB8CstuitgbLcJr3cZkLJLLzPH0ahvuTtmhWxtR2 skip_symbols: false - artifact: /artifacts\/.+\.nupkg/ + artifact: /artifacts\/.+\.s?nupkg/ diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 2fe53ba..9b58ae9 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -18,7 +18,8 @@ - true + snupkg + true true $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb From 13f4258760e7be7992c4ecf4ad5d148d223f8fce Mon Sep 17 00:00:00 2001 From: Marko Zorec Date: Sat, 11 Jul 2020 13:26:16 +0200 Subject: [PATCH 13/85] Updates english documentation (#606) --- README.md | 2 +- docs/content/index.md | 6 ++-- .../user-guide/en/cap/configuration.md | 32 +++++++++---------- docs/content/user-guide/en/cap/idempotence.md | 14 ++++---- docs/content/user-guide/en/cap/messaging.md | 16 +++++----- .../user-guide/en/cap/serialization.md | 6 ++-- .../content/user-guide/en/cap/transactions.md | 14 ++++---- .../en/getting-started/introduction.md | 8 ++--- .../user-guide/en/monitoring/dashboard.md | 8 ++--- .../user-guide/en/monitoring/diagnostics.md | 2 +- .../user-guide/en/persistent/general.md | 10 +++--- .../en/persistent/in-memory-storage.md | 6 ++-- .../user-guide/en/persistent/mongodb.md | 4 +-- .../content/user-guide/en/persistent/mysql.md | 4 +-- .../user-guide/en/persistent/postgresql.md | 4 +-- .../user-guide/en/persistent/sqlserver.md | 4 +-- docs/content/user-guide/en/samples/faq.md | 2 +- docs/content/user-guide/en/samples/github.md | 2 +- .../user-guide/en/transports/aws-sqs.md | 8 ++--- .../en/transports/azure-service-bus.md | 6 ++-- .../en/transports/in-memory-queue.md | 4 +-- .../content/user-guide/en/transports/kafka.md | 10 +++--- .../user-guide/en/transports/rabbitmq.md | 6 ++-- 23 files changed, 89 insertions(+), 89 deletions(-) diff --git a/README.md b/README.md index 765a755..6659068 100644 --- a/README.md +++ b/README.md @@ -151,7 +151,7 @@ public class PublishController : Controller **In Business Logic Service** -If your subscription method is not in the Controller,then your subscribe class needs to implement `ICapSubscribe` interface: +If your subscription method is not in the Controller, then your subscribe class needs to implement `ICapSubscribe` interface: ```c# diff --git a/docs/content/index.md b/docs/content/index.md index ed388f2..2e96fd4 100644 --- a/docs/content/index.md +++ b/docs/content/index.md @@ -10,13 +10,13 @@ Title: CAP - A distributed transaction solution in micro-service base on eventua [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/dotnetcore/CAP/master/LICENSE.txt) [![Member project of .NET Core Community](https://img.shields.io/badge/member%20project%20of-NCC-9e20c9.svg)](https://github.com/dotnetcore) -CAP is a library based on .net standard, which is a solution to deal with distributed transactions, also has the function of EventBus, it is lightweight, easy to use, and efficiently. +CAP is a library based on .net standard, which is a solution to deal with distributed transactions, also has the function of EventBus, it is lightweight, easy to use, and efficient. ## Introduction -In the process of building an SOA or MicroService system, we usually need to use the event to integrate each services. In the process, the simple use of message queue does not guarantee the reliability. CAP is adopted the local message table program integrated with the current database to solve the exception may occur in the process of the distributed system calling each other. It can ensure that the event messages are not lost in any case. +In the process of building an SOA or MicroService system, we usually need to use the event to integrate each service. In the process, the simple use of message queue does not guarantee the reliability. CAP adopts local message table program integrated with the current database to solve exceptions that may occur in the process of the distributed system calling each other. It can ensure that the event messages are not lost in any case. -You can also use the CAP as an EventBus. The CAP provides a simpler way to implement event publishing and subscriptions. You do not need to inherit or implement any interface during the process of subscription and sending. +You can also use CAP as an EventBus. CAP provides a simpler way to implement event publishing and subscriptions. You do not need to inherit or implement any interface during subscription and sending process. !!! Tip "CAP implements the Outbox Pattern described in the [eShop ebook](https://docs.microsoft.com/en-us/dotnet/standard/microservices-architecture/multi-container-microservice-net-applications/subscribe-events#designing-atomicity-and-resiliency-when-publishing-to-the-event-bus)" diff --git a/docs/content/user-guide/en/cap/configuration.md b/docs/content/user-guide/en/cap/configuration.md index c34b61f..e224e17 100644 --- a/docs/content/user-guide/en/cap/configuration.md +++ b/docs/content/user-guide/en/cap/configuration.md @@ -1,6 +1,6 @@ # Configuration -By default, you can specify the configuration when you register the CAP service into the IoC container for ASP.NET Core project. +By default, you can specify configuration when you register CAP services into the IoC container for ASP.NET Core project. ```c# services.AddCap(config=> @@ -9,13 +9,13 @@ services.AddCap(config=> }); ``` -The `services` is `IServiceCollection` interface,which is under the `Microsoft.Extensions.DependencyInjection`. +`services` is `IServiceCollection` interface, which can be found in the `Microsoft.Extensions.DependencyInjection` package. -If you don't want to use Microsoft's IoC container, you can view ASP.NET Core documentation [here](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.2#default-service-container-replacement) to learn how to replace the default container implementation. +If you don't want to use Microsoft's IoC container, you can take a look at ASP.NET Core documentation [here](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.2#default-service-container-replacement) to learn how to replace the default container implementation. -## What is the minimum configuration? +## what is minimum configuration required for CAP -The simplest answer is that at least you have to configure a transport and a storage. If you want to get started quickly you can use the following configuration: +you have to configure at least a transport and a storage. If you want to get started quickly you can use the following configuration: ```C# services.AddCap(capOptions => @@ -25,47 +25,47 @@ services.AddCap(capOptions => }); ``` -For specific transport and storage configuration, you can view the configuration items provided by the specific components in the [Transports](../transports/general.md) section and the [Persistent](../persistent/general.md) section. +For specific transport and storage configuration, you can take a look at the configuration options provided by the specific components in the [Transports](../transports/general.md) section and the [Persistent](../persistent/general.md) section. ## Custom configuration -The `CapOptions` is used to store configuration information. By default they have the default values, and sometimes you may need to customize them. +The `CapOptions` is used to store configuration information. By default they have default values, sometimes you may need to customize them. #### DefaultGroup > Default: cap.queue.{assembly name} -The default consumer group name, corresponding to different names in different Transports, you can customize this value to customize the names in Transports for easy viewing. +The default consumer group name, corresponds to different names in different Transports, you can customize this value to customize the names in Transports for easy viewing. !!! info "Mapping" Map to [Queue Names](https://www.rabbitmq.com/queues.html#names) in RabbitMQ. Map to [Consumer Group Id](http://kafka.apache.org/documentation/#group.id) in Apache Kafka. Map to Subscription Name in Azure Service Bus. -#### Version +#### Versioning > Default: v1 -This is a new configuration item introduced in the CAP v2.4 version. It is used to specify a version of a message to isolate messages of different versions of the service. It is often used in A/B testing or multi-service version scenarios. The following is its application scenario: +This is a new configuration option introduced in the CAP v2.4 version. It is used to specify a version of a message to isolate messages of different versions of the service. It is often used in A/B testing or multi-service version scenarios. Following are application scenarios that needs versioning: !!! info "Business Iterative and compatible" - Due to the rapid iteration of services, the data structure of the message is not fixed during each service integration process. Sometimes we add or modify certain data structures to accommodate the newly introduced requirements. If you're a brand new system, there's no problem, but if your system is deployed to a production environment and serves customers, this will cause new features to be incompatible with the old data structure when they go online, and then these changes can cause serious problems. To work around this issue, you can only clean up message queues and persistent messages before starting the application, which is obviously fatal for production environments. + Due to the rapid iteration of services, the data structure of the message is not fixed during each service integration process. Sometimes we add or modify certain data structures to accommodate the newly introduced requirements. If you have a brand new system, there's no problem, but if your system is already deployed to a production environment and serves customers, this will cause new features to be incompatible with the old data structure when they go online, and then these changes can cause serious problems. To work around this issue, you can only clean up message queues and persistent messages before starting the application, which is obviously fatal for production environments. !!! info "Multiple versions of the server" - Sometimes, the server's server needs to provide multiple sets of interfaces to support different versions of the app. The data structures of the same interface and server interaction of these different versions of the app may be different, so usually the server does not provide the same. Routing addresses to adapt to different versions of App calls. + Sometimes, the server's server needs to provide multiple sets of interfaces to support different versions of the app. Data structures of the same interface and server interaction of these different versions of the app may be different, so usually server does not provide the same routing addresses to adapt to different versions of App calls. !!! info "Using the same persistent table/collection in different instance" If you want multiple different instance services to use the same database, in versions prior to 2.4, we could isolate database tables for different instances by specifying different table names. That is to say, when configuring the CAP, it is implemented by configuring different table name prefixes. -> Check out the blog to learn more about Version feature: https://www.cnblogs.com/savorboard/p/cap-2-4.html +> Check out the blog to learn more about the Versioning feature: https://www.cnblogs.com/savorboard/p/cap-2-4.html #### FailedRetryInterval > Default: 60 sec -In the process of message message sent to transport failed, the CAP will be retry to sent. This configuration item is used to configure the interval between each retry. +During the message sending process if message transport fails, CAP will try to send the message again. This configuration option is used to configure the interval between each retry. -In the process of message consumption failed, the CAP will retry to execute. This configuration item is used to configure the interval between each retry. +During the message sending process if consumption method fails, CAP will try to execute the method again. This configuration option is used to configure the interval between each retry. !!! WARNING "Retry & Interval" By default, retry will start after **4 minutes** of failure to send or consume, in order to avoid possible problems caused by setting message state delays. @@ -94,7 +94,7 @@ T1 : Message Type T2 : Message Name T3 : Message Content -Failure threshold callback. This action is called when the retry reaches the value set by `FailedRetryCount`, and you can receive the notification by specifying this parameter to make a manual intervention. For example, send an email or notify. +Failure threshold callback. This action is called when the retry reaches the value set by `FailedRetryCount`, you can receive notification by specifying this parameter to make a manual intervention. For example, send an email or notify. #### SucceedMessageExpiredAfter diff --git a/docs/content/user-guide/en/cap/idempotence.md b/docs/content/user-guide/en/cap/idempotence.md index 5356ad8..30c4c34 100644 --- a/docs/content/user-guide/en/cap/idempotence.md +++ b/docs/content/user-guide/en/cap/idempotence.md @@ -8,7 +8,7 @@ Imdempotence (which you may read a formal definition of on [Wikipedia](https://e Before we talk about idempotency, let's talk about the delivery of messages on the consumer side. -Since CAP is not a used MS DTC or other type of 2PC distributed transaction mechanism, there is a problem that at least the message is strictly delivered once. Specifically, in a message-based system, there are three possibilities: +Since CAP doesn't uses MS DTC or other type of 2PC distributed transaction mechanism, there is a problem that the message is strictly delivered at least once. Specifically, in a message-based system, there are three possibilities: * Exactly Once(*) * At Most Once @@ -35,9 +35,9 @@ This type of delivery guarantee can arise from your messaging system and your co 2. Put message back into the queue ``` -In the sunshine scenario, this is all well and good – your messages will be received, and work transactions will be committed, and you will be happy. +In the best case scenario, this is all well and good – your messages will be received, and work transactions will be committed, and you will be happy. -However, the sun does not always shine, and stuff tends to fail – especially if you do enough stuff. Consider e.g. what would happen if anything fails after having performed step (1), and then – when you try to execute step (4)/(2) (i.e. put the message back into the queue) – the network was temporarily unavailable, or the message broker restarted, or the host machine decided to reboot because it had installed an update. +However, the sun does not always shine, and stuff tends to fail – especially if you do alot of stuff. Consider e.g. what would happen if anything fails after having performed step (1), and then – when you try to execute step (4)/(2) (i.e. put the message back into the queue) – the network was temporarily unavailable, or the message broker restarted, or the host machine decided to reboot because it had installed an update. This can be OK if it's what you want, but most things in CAP revolve around the concept of DURABLE messages, i.e. messages whose contents is just as important as the data in your database. @@ -72,7 +72,7 @@ The fact that the "work transaction" is just a conceptual thing is what makes it ## Idempotence at CAP -In the CAP, the delivery guarantees we use is **At Least Once**. +In CAP, **At Least Once** delivery guarantee is used. Since we have a temporary storage medium (database table), we may be able to do At Most Once, but in order to strictly guarantee that the message will not be lost, we do not provide related functions or configurations. @@ -83,9 +83,9 @@ Since we have a temporary storage medium (database table), we may be able to do There are a lot of reasons why the Consumer method fails. I don't know if the specific scene is blindly retrying or not retrying is an incorrect choice. For example, if the consumer is debiting service, if the execution of the debit is successful, but fails to write the debit log, the CAP will judge that the consumer failed to execute and try again. If the client does not guarantee idempotency, the framework will retry it, which will inevitably lead to serious consequences for multiple debits. -2. The implementation of the Consumer method succeeded, but received the same message. +2. The execution of the Consumer method succeeded, but received the same message. - The scenario is also possible here. If the Consumer has been successfully executed at the beginning, but for some reason, such as the Broker recovery, and received the same message, the CAP will consider this a new after receiving the Broker message. The message will be executed again by the Consumer. Because it is a new message, the CAP cannot be idempotent at this time. + This scenario is also possible. If the Consumer has been successfully executed at the beginning, but for some reason, such as the Broker recovery, same message has been received, CAP will consider this as a new message after receiving the Broker message. Message will be executed again by the Consumer. Because it is a new message, CAP cannot be idempotent at this time. 3. The current data storage mode can not be idempotent. @@ -95,7 +95,7 @@ Since we have a temporary storage medium (database table), we may be able to do Many event-driven frameworks require users to ensure idempotent operations, such as ENode, RocketMQ, etc... -From an implementation point of view, CAP can do some less stringent idempotence, but strict idempotent cannot. +From an implementation point of view, CAP can do some less stringent idempotence, but strict idempotent can not be guaranteed. ### Naturally idempotent message processing diff --git a/docs/content/user-guide/en/cap/messaging.md b/docs/content/user-guide/en/cap/messaging.md index 4c809e2..af82261 100644 --- a/docs/content/user-guide/en/cap/messaging.md +++ b/docs/content/user-guide/en/cap/messaging.md @@ -4,25 +4,25 @@ The data sent by using the `ICapPublisher` interface is called `Message`. ## Scheduling -After the CAP receives the message, it sends the message to Transport, which is transported by transport. +After CAP receives a message, it sends the message to Transport, which is transported by transport. -When you send using the `ICapPublisher` interface, the CAP will dispatch the message to the corresponding Transport. Currently, bulk messaging is not supported. +When you send message using the `ICapPublisher` interface, CAP will dispatch message to the corresponding Transport. Currently, bulk messaging is not supported. -For more information on transports, see the [Transports](../transports/general.md) section. +For more information on transports, see [Transports](../transports/general.md) section. ## Persistent -The CAP will storage after receiving the message. For more information on storage, see the [Persistent](../persistent/general.md) section. +CAP will store the message after receiving it. For more information on storage, see the [Persistent](../persistent/general.md) section. ## Retry -Retrying plays an important role in the overall CAP architecture design, and CAPs retry for messages that fail to send or fail to execute. There are several retry strategies used throughout the CAP design process. +Retrying plays an important role in the overall CAP architecture design, CAP retry messages that fail to send or fail to execute. There are several retry strategies used throughout the CAP design process. ### Send retry -During the message sending process, when the broker crashes or the connection fails or an abnormality occurs, the CAP will retry the sending. Retry 3 times for the first time, retry every minute after 4 minutes, and +1 retries. When the total number of times reaches 50, the CAP will stop retrying. +During the message sending process, when the broker crashes or the connection fails or an abnormality occurs, the CAP will retry the sending. Retry 3 times for the first time, retry every minute after 4 minutes, and +1 retries. When the total number of times reaches 50,CAP will stop retrying. -You can adjust the total number of default retries by setting `FailedRetryCount` in CapOptions. +You can adjust the total number of retries by setting `FailedRetryCount` in CapOptions. It will stop when the maximum number of times is reached. You can see the reason for the failure in Dashboard and choose whether to manually retry. @@ -36,6 +36,6 @@ There is an `ExpiresAt` field in the database message table indicating the expir Consuming failure will change the message status to `Failed` and `ExpiresAt` will be set to **15 days** later. -By default, the data of the message table is deleted **every hour** to avoid performance degradation caused by too much data. The cleanup strategy is `ExpiresAt` is not empty and is less than the current time. +By default, the data of the message in the table is deleted **every hour** to avoid performance degradation caused by too much data. The cleanup strategy `ExpiresAt` is performed when field is not empty and is less than the current time. That is to say, the message with the status Failed (normally they have been retried 50 times), if you do not have manual intervention for 15 days, it will **also be** cleaned up. \ No newline at end of file diff --git a/docs/content/user-guide/en/cap/serialization.md b/docs/content/user-guide/en/cap/serialization.md index 7cf5000..78ed793 100644 --- a/docs/content/user-guide/en/cap/serialization.md +++ b/docs/content/user-guide/en/cap/serialization.md @@ -1,6 +1,6 @@ # Serialization -We provide the `ISerializer` interface to support serialization of messages. By default, we use json to serialize messages and store them in the database. +We provide the `ISerializer` interface to support serialization of messages. By default, json is used to serialize messages and store them in the database. ## Custom Serialization @@ -34,7 +34,7 @@ services.AddCap In heterogeneous systems, sometimes you need to communicate with other systems, but other systems use message objects that may be different from CAP's [**Wrapper Object**](../persistent/general.md#_7). This time maybe you need to customize the message wapper. -The CAP provides the `IMessagePacker` interface for customizing the [**Wrapper Object**](../persistent/general.md#_7). The custom MessagePacker usually packs and unpacks the `CapMessage` In this process you can add your own business objects. +CAP provides the `IMessagePacker` interface for customizing the [**Wrapper Object**](../persistent/general.md#_7). Custom MessagePacker usually packs and unpacks the `CapMessage` In this process you can add your own business objects. Usage : @@ -76,7 +76,7 @@ class MyMessagePacker : IMessagePacker } ``` -Next, configure the custom `MyMessagePacker` to the service. +Next, add the custom `MyMessagePacker` to the service. ```csharp diff --git a/docs/content/user-guide/en/cap/transactions.md b/docs/content/user-guide/en/cap/transactions.md index dcbbc6b..24a2979 100644 --- a/docs/content/user-guide/en/cap/transactions.md +++ b/docs/content/user-guide/en/cap/transactions.md @@ -27,9 +27,9 @@ For example, suppose we need to solve the following task: * register a user profile * do some automated background check that the user can actually access the system -The second task is to ensure, for example, that this user wasn’t banned from our servers for some reason. +Second task is to ensure, for example, that this user wasn’t banned from our servers for some reason. -But it could take time, and we’d like to extract it to a separate microservice. It wouldn’t be reasonable to keep the user waiting for so long just to know that she was registered successfully. +But it could take time, and we’d like to extract it to a separate microservice. It wouldn’t be reasonable to keep the user waiting for so long just to know that he was registered successfully. **One way to solve it would be with a message-driven approach including compensation**. Let’s consider the following architecture: @@ -37,13 +37,13 @@ But it could take time, and we’d like to extract it to a separate microservice * the validation microservice tasked with doing a background check * the messaging platform that supports persistent queues -The messaging platform could ensure that the messages sent by the microservices are persisted. Then they would be delivered at a later time if the receiver weren’t currently available +The messaging platform could ensure that the messages sent by the microservices are persisted. Then they would be delivered at a later time if the receiver wasn't currently available -#### Happy Scenario +#### Best case scenario -In this architecture, a happy scenario would be: +In this architecture, best case scenario would be: -* the user microservice registers a user, saving information about her in its local database +* the user microservice registers a user, saving information about him in its local database * the user microservice marks this user with a flag. It could signify that this user hasn’t yet been validated and doesn’t have access to full system functionality * a confirmation of registration is sent to the user with a warning that not all functionality of the system is accessible right away * the user microservice sends a message to the validation microservice to do the background check of a user @@ -51,7 +51,7 @@ In this architecture, a happy scenario would be: * if the results are positive, the user microservice unblocks the user * if the results are negative, the user microservice deletes the user account -After we’ve gone through all these steps, the system should be in a consistent state. However, for some period of time, the user entity appeared to be in an incomplete state. +After we’ve gone through all these steps, the system should be in a consistent state. However, for some period of time, user entity appeared to be in an incomplete state. The last step, when the user microservice removes the invalid account, is a compensation phase. diff --git a/docs/content/user-guide/en/getting-started/introduction.md b/docs/content/user-guide/en/getting-started/introduction.md index 14fd85b..1446144 100644 --- a/docs/content/user-guide/en/getting-started/introduction.md +++ b/docs/content/user-guide/en/getting-started/introduction.md @@ -2,16 +2,16 @@ CAP is an EventBus and a solution for solving distributed transaction problems in microservices or SOA systems. It helps create a microservices system that is scalable, reliable, and easy to change. -In Microsoft's [eShopOnContainer](https://github.com/dotnet-architecture/eShopOnContainers) microservices sample project, it is recommended to use CAP as the EventBus available in the production environment. +In Microsoft's [eShopOnContainer](https://github.com/dotnet-architecture/eShopOnContainers) microservices sample project, it is recommended to use CAP as the EventBus in the production environment. !!! question "What is EventBus?" - An Eventbus is a mechanism that allows different components to communicate with each other without knowing about each other. A component can send an Event to the Eventbus without knowing who will pick it up or how many others will pick it up. Components can also listen to Events on an Eventbus, without knowing who sent the Events. That way, components can communicate without depending on each other. Also, it is very easy to substitute a component. As long as the new component understands the Events that are being sent and received, the other components will never know. + An Eventbus is a mechanism that allows different components to communicate with each other without knowing about each other. A component can send an Event to the Eventbus without knowing who will pick it up or how many others will pick it up. Components can also listen to Events on an Eventbus, without knowing who sent the Events. That way, components can communicate without depending on each other. Also, it is very easy to substitute a component. As long as the new component understands events that are being sent and received, other components will never know about the substitution. -Compared to other Service Bus or Event Bus, CAP has its own characteristics. It does not require users to implement or inherit any interface when sending messages or processing messages. It has very high flexibility. We have always believed that the appointment is greater than the configuration, so the CAP is very simple to use, very friendly to the novice, and lightweight. +Compared to other Services Bus or Event Bus, CAP has its own characteristics. It does not require users to implement or inherit any interface when sending messages or processing messages. It has very high flexibility. We have always believed that the appointment is greater than the configuration, so the CAP is very simple to use, very friendly to the novice, and lightweight. -The CAP is modular in design and highly scalable. You have many options to choose from, including message queues, storage, serialization, etc. Many elements of the system can be replaced with custom implementations. +CAP is modular in design and highly scalable. You have many options to choose from, including message queues, storage, serialization, etc. Many elements of the system can be replaced with custom implementations. ## Related videos diff --git a/docs/content/user-guide/en/monitoring/dashboard.md b/docs/content/user-guide/en/monitoring/dashboard.md index cf6c9f0..fa0fc61 100644 --- a/docs/content/user-guide/en/monitoring/dashboard.md +++ b/docs/content/user-guide/en/monitoring/dashboard.md @@ -1,6 +1,6 @@ # Dashboard -The CAP provides a Dashboard for viewing messages, and the features provided by Dashboard make it easy to view and manage messages. +CAP provides a Dashboard for viewing messages, and features provided by Dashboard make it easy to view and manage messages. ## Enable Dashboard @@ -24,17 +24,17 @@ By default, you can open the Dashboard by visiting the url `http://localhost:xxx > Default :'/cap' -You can change the path of the Dashboard by modifying this configuration item. +You can change the path of the Dashboard by modifying this configuration option. * StatsPollingInterval > Default: 2000ms -This configuration item is used to configure the Dashboard front end to get the polling time of the status interface (/stats). +This configuration option is used to configure the Dashboard front end to get the polling time of the status interface (/stats). * Authorization -This configuration item is used to configure the authorization filter when accessing the Dashboard. The default filter allows LAN access. When your application wants to provide external network access, you can customize the authentication rules by setting this configuration. See the next section for details. +This configuration option is used to configure the authorization filter when accessing the Dashboard. The default filter allows LAN access. When your application wants to provide external network access, you can customize authentication rules by setting this configuration. See the next section for details. ### Custom authentication diff --git a/docs/content/user-guide/en/monitoring/diagnostics.md b/docs/content/user-guide/en/monitoring/diagnostics.md index eb7c57d..b6bbd56 100644 --- a/docs/content/user-guide/en/monitoring/diagnostics.md +++ b/docs/content/user-guide/en/monitoring/diagnostics.md @@ -1,6 +1,6 @@ # Diagnostics -Diagnostics provides a set of features that make it easy for us to document the critical operations that occur during the application's operation, their execution time, etc., allowing administrators to find the root cause of problems, especially in production environments. +Diagnostics provides a set of features that make it easy for us to document critical operations that occurs during the application's operation, their execution time, etc., allowing administrators to find the root cause of problems, especially in production environments. ## Diagnostics events diff --git a/docs/content/user-guide/en/persistent/general.md b/docs/content/user-guide/en/persistent/general.md index 675a8a1..57c9cc7 100644 --- a/docs/content/user-guide/en/persistent/general.md +++ b/docs/content/user-guide/en/persistent/general.md @@ -1,18 +1,18 @@ # General -CAP need to use storage media with persistence capabilities to store event messages, such as through databases or other NoSql facilities. CAP uses this approach to deal with the loss of messages in all environments or network anomalies. The reliability of messages is the cornerstone of distributed transactions, so messages cannot be lost under any circumstances. +CAP needs to use storage media with persistence capabilities to store event messages, such as through databases or other NoSql facilities. CAP uses this approach to deal with loss of messages in all environments or network anomalies. Reliability of messages is the cornerstone of distributed transactions, so messages cannot be lost under any circumstances. ## Persistent ### Before sent -Before the message enters the message queue, the CAP uses the local database table to persist the message, which ensures that the message is not lost when the message queue is abnormal or a network error occurs. +Before message enters the message queue, CAP uses the local database table to persist the message, which ensures that the message is not lost when the message queue is abnormal or a network error occurs. To ensure the reliability of this mechanism, CAP uses the same database transactions as the business code to ensure that business operations and CAP messages are consistent in the persistence process. That is to say, in the process of message persistence, the database will be rolled back when any one of the exceptions occurs. ### After sent -After the message enters the message queue, the CAP will start the persistence function of the message queue. We need to explain how the CAP message is persisted in RabbitMQ and Kafka. +After the message enters the message queue, CAP will start the persistence function of the message queue. We need to explain how CAP message is persisted in RabbitMQ and Kafka. For message persistence in RabbitMQ, CAP uses a consumer queue with message persistence, but there may be exceptions here. @@ -23,7 +23,7 @@ Since Kafka is born with message persistence using files, Kafka will ensure that ## Storage -After the CAP started, two tables are generated into the persistent, by default the name is `Cap.Published` and `Cap.Received`. +After CAP is started, two tables are generated into the persistent, by default the name is `Cap.Published` and `Cap.Received`. ### Storage Data Structure @@ -56,7 +56,7 @@ StatusName | Status Name | string ### Wapper Object -When the CAP sends a message, it will store the original message object in a second package in the `Content` field. +When CAP sends a message, it will store original message object in a second package in the `Content` field. The following is the **Wapper Object** data structure of Content field. diff --git a/docs/content/user-guide/en/persistent/in-memory-storage.md b/docs/content/user-guide/en/persistent/in-memory-storage.md index e5b2982..b44259a 100644 --- a/docs/content/user-guide/en/persistent/in-memory-storage.md +++ b/docs/content/user-guide/en/persistent/in-memory-storage.md @@ -4,7 +4,7 @@ Persistent storage of memory messages is often used in development and test envi ## Configuration -To use in-memory storage, you need to install the following extensions from NuGet: +To use in-memory storage, you need to install following package from NuGet: ```powershell PM> Install-Package DotNetCore.CAP.InMemoryStorage @@ -27,8 +27,8 @@ public void ConfigureServices(IServiceCollection services) ``` -The successful message in memory, the CAP will be cleaned **every 5 minutes**. + CAP will clean **every 5 minutes** Successful messages in memory. ## Publish with transaction -In-Memory Storage **Not supported** Transaction mode to send messages. +In-Memory Storage **does not support** Transaction mode to send messages. diff --git a/docs/content/user-guide/en/persistent/mongodb.md b/docs/content/user-guide/en/persistent/mongodb.md index 8f3ca46..9386644 100644 --- a/docs/content/user-guide/en/persistent/mongodb.md +++ b/docs/content/user-guide/en/persistent/mongodb.md @@ -2,7 +2,7 @@ MongoDB is a cross-platform document-oriented database program. Classified as a NoSQL database program, MongoDB uses JSON-like documents with schema. -CAP has supported MongoDB as persistent since version 2.3 . +CAP supports MongoDB since version 2.3 . MongoDB supports ACID transactions since version 4.0, so CAP only supports MongoDB above 4.0, and MongoDB needs to be deployed as a cluster, because MongoDB's ACID transaction requires a cluster to be used. @@ -10,7 +10,7 @@ For a quick development of the MongoDB 4.0+ cluster for the development environm ## Configuration -To use MongoDB storage, you need to install the following extensions from NuGet: +To use MongoDB storage, you need to install the following package from NuGet: ```powershell PM> Install-Package DotNetCore.CAP.MongoDB diff --git a/docs/content/user-guide/en/persistent/mysql.md b/docs/content/user-guide/en/persistent/mysql.md index 794fd3e..2d1b4e5 100644 --- a/docs/content/user-guide/en/persistent/mysql.md +++ b/docs/content/user-guide/en/persistent/mysql.md @@ -1,10 +1,10 @@ # MySQL -MySQL is an open-source relational database management system. CAP has supported MySQL as persistent. +MySQL is an open-source relational database management system. CAP supports MySQL database. ## Configuration -To use MySQL storage, you need to install the following extensions from NuGet: +To use MySQL storage, you need to install the following package from NuGet: ```powershell PM> Install-Package DotNetCore.CAP.MySql diff --git a/docs/content/user-guide/en/persistent/postgresql.md b/docs/content/user-guide/en/persistent/postgresql.md index eeca893..7835b7f 100644 --- a/docs/content/user-guide/en/persistent/postgresql.md +++ b/docs/content/user-guide/en/persistent/postgresql.md @@ -1,10 +1,10 @@ # Postgre SQL -PostgreSQL is an open-source relational database management system. CAP has supported PostgreSQL as persistent. +PostgreSQL is an open-source relational database management system. CAP supports PostgreSQL database. ## Configuration -To use PostgreSQL storage, you need to install the following extensions from NuGet: +To use PostgreSQL storage, you need to install the following package from NuGet: ```powershell PM> Install-Package DotNetCore.CAP.PostgreSql diff --git a/docs/content/user-guide/en/persistent/sqlserver.md b/docs/content/user-guide/en/persistent/sqlserver.md index 6610856..0119266 100644 --- a/docs/content/user-guide/en/persistent/sqlserver.md +++ b/docs/content/user-guide/en/persistent/sqlserver.md @@ -1,13 +1,13 @@ # SQL Server -SQL Server is a relational database management system developed by Microsoft. CAP has supported SQL Server as persistent. +SQL Server is a relational database management system developed by Microsoft. CAP supports SQL Server database. !!! warning "Warning" We currently use `Microsoft.Data.SqlClient` as the database driver, which is the future of SQL Server drivers, and we have abandoned `System.Data.SqlClient`, we suggest you switch into. ## Configuration -To use SQL Server storage, you need to install the following extensions from NuGet: +To use SQL Server storage, you need to install the following package from NuGet: ```powershell PM> Install-Package DotNetCore.CAP.SqlServer diff --git a/docs/content/user-guide/en/samples/faq.md b/docs/content/user-guide/en/samples/faq.md index 1715ca2..4fef6e6 100644 --- a/docs/content/user-guide/en/samples/faq.md +++ b/docs/content/user-guide/en/samples/faq.md @@ -6,7 +6,7 @@ !!! faq "Does it require certain different databases, one each for productor and resumer in CAP?" - Not requird differences necessary, a given advice is that using a special database for each program. + Not required differences necessary, a given advice is that using a special database for each program. Otherwise, look at Q&A below. diff --git a/docs/content/user-guide/en/samples/github.md b/docs/content/user-guide/en/samples/github.md index 3f5f6ff..d287202 100644 --- a/docs/content/user-guide/en/samples/github.md +++ b/docs/content/user-guide/en/samples/github.md @@ -1,5 +1,5 @@ # Github Samples -You can find the sample code at Github repository : +You can find the sample code at the Github repository: https://github.com/dotnetcore/CAP/tree/master/samples \ No newline at end of file diff --git a/docs/content/user-guide/en/transports/aws-sqs.md b/docs/content/user-guide/en/transports/aws-sqs.md index b13a73e..173881b 100644 --- a/docs/content/user-guide/en/transports/aws-sqs.md +++ b/docs/content/user-guide/en/transports/aws-sqs.md @@ -10,12 +10,12 @@ AWS SNS is a highly available, durable, secure, fully managed pub/sub messaging Because CAP works based on the topic pattern, it needs to use AWS SNS, which simplifies the publish and subscribe architecture of messages. -When the CAP startup, all subscription names will be registered as SNS topics, and you will see a list of all registered topics in the management console. +When CAP startups, all subscription names will be registered as SNS topics, and you will see a list of all registered topics in the management console. -SNS does not support the use of symbols such as `.` `:` as the name of the topic, so we replaced it. We replaced `.` with `-` and `:` with `_` +SNS does not support use of symbols such as `.` `:` as the name of the topic, so we replaced it. We replaced `.` with `-` and `:` with `_` !!! note "Precautions" - Amazon SNS currently allows the maximum size of published messages to be 256KB + Amazon SNS currently allows maximum size of published messages to be 256KB For example, you have the following two subscriber methods in your current project @@ -30,7 +30,7 @@ public void TestBar(DateTime value) { } ``` -After the CAP startup, you will see in SNS management console: +After CAP startups, you will see in SNS management console: ![img](/img/aws-sns-demo.png) diff --git a/docs/content/user-guide/en/transports/azure-service-bus.md b/docs/content/user-guide/en/transports/azure-service-bus.md index e046341..173e24d 100644 --- a/docs/content/user-guide/en/transports/azure-service-bus.md +++ b/docs/content/user-guide/en/transports/azure-service-bus.md @@ -2,14 +2,14 @@ Microsoft Azure Service Bus is a fully managed enterprise integration message broker. Service Bus is most commonly used to decouple applications and services from each other, and is a reliable and secure platform for asynchronous data and state transfer. -CAP supports Azure Service Bus as a message transporter. +Azure services can be used in CAP as a message transporter. ## Configuration !!! warning "Requirement" For the Service Bus pricing layer, CAP requires "standard" or "advanced" to support Topic functionality. -To use Azure Service Bus as a message transport, you need to install the following extensions from NuGet: +To use Azure Service Bus as a message transport, you need to install the following package from NuGet: ```powershell PM> Install-Package DotNetCore.CAP.AzureServiceBus @@ -36,7 +36,7 @@ public void ConfigureServices(IServiceCollection services) #### AzureServiceBus Options -The AzureServiceBus configuration options provided directly by the CAP are as follows: +The AzureServiceBus configuration options provided directly by the CAP: NAME | DESCRIPTION | TYPE | DEFAULT :---|:---|---|:--- diff --git a/docs/content/user-guide/en/transports/in-memory-queue.md b/docs/content/user-guide/en/transports/in-memory-queue.md index 60cae3a..c6b3d6f 100644 --- a/docs/content/user-guide/en/transports/in-memory-queue.md +++ b/docs/content/user-guide/en/transports/in-memory-queue.md @@ -4,14 +4,14 @@ In Memory Queue is a memory-based message queue provided by [Community](https:// ## Configuration -To use In Memory Queue as a message transporter, you need to install the following extensions from NuGet: +To use In Memory Queue as a message transporter, you need to install the following package from NuGet: ```powershell PM> Install-Package Savorboard.CAP.InMemoryMessageQueue ``` -Next, add configuration items to the `ConfigureServices` method of `Startup.cs`: +Next, add configuration options to the `ConfigureServices` method of `Startup.cs`: ```csharp diff --git a/docs/content/user-guide/en/transports/kafka.md b/docs/content/user-guide/en/transports/kafka.md index 0af9d15..8ea08bb 100644 --- a/docs/content/user-guide/en/transports/kafka.md +++ b/docs/content/user-guide/en/transports/kafka.md @@ -2,11 +2,11 @@ [Apache Kafka®](https://kafka.apache.org/) is an open-source stream-processing software platform developed by LinkedIn and donated to the Apache Software Foundation, written in Scala and Java. -CAP has supported Kafka® as message transporter. +Kafka® can be used in CAP as a message transporter. ## Configuration -To use Kafka transporter, you need to install the following extensions from NuGet: +To use Kafka transporter, you need to install the following package from NuGet: ```powershell PM> Install-Package DotNetCore.CAP.Kafka @@ -34,7 +34,7 @@ public void ConfigureServices(IServiceCollection services) #### Kafka Options -The Kafka configuration parameters provided directly by the CAP are as follows: +The Kafka configuration parameters provided directly by the CAP: NAME | DESCRIPTION | TYPE | DEFAULT :---|:---|---|:--- @@ -43,7 +43,7 @@ ConnectionPoolSize | connection pool size | int | 10 #### Kafka MainConfig Options -If you need **more** native Kakfa related configuration items, you can set it with the `MainConfig` configuration option: +If you need **more** native Kakfa related configuration options, you can set them in the `MainConfig` configuration option: ```csharp services.AddCap(capOptions => @@ -56,6 +56,6 @@ services.AddCap(capOptions => }); ``` -`MainConfig` is a configuration dictionary, you can find a list of supported configuration items through the following link. +`MainConfig` is a configuration dictionary, you can find a list of supported configuration options through the following link. [https://github.com/edenhill/librdkafka/blob/master/CONFIGURATION.md](https://github.com/edenhill/librdkafka/blob/master/CONFIGURATION.md) diff --git a/docs/content/user-guide/en/transports/rabbitmq.md b/docs/content/user-guide/en/transports/rabbitmq.md index 383da27..658a751 100644 --- a/docs/content/user-guide/en/transports/rabbitmq.md +++ b/docs/content/user-guide/en/transports/rabbitmq.md @@ -2,11 +2,11 @@ RabbitMQ is an open-source message-broker software that originally implemented the Advanced Message Queuing Protocol and has since been extended with a plug-in architecture to support Streaming Text Oriented Messaging Protocol, Message Queuing Telemetry Transport, and other protocols. -CAP has supported RabbitMQ as message transporter. +RabbitMQ can be used in CAP as a message transporter. ## Configuration -To use RabbitMQ transporter, you need to install the following extensions from NuGet: +To use RabbitMQ transporter, you need to install the following package from NuGet: ```powershell PM> Install-Package DotNetCore.CAP.RabbitMQ @@ -35,7 +35,7 @@ public void ConfigureServices(IServiceCollection services) #### RabbitMQ Options -The RabbitMQ configuration parameters provided directly by the CAP are as follows: +The RabbitMQ configuration parameters provided directly by CAP: NAME | DESCRIPTION | TYPE | DEFAULT :---|:---|---|:--- From 285904bd036f309609fd03bc8afe2c87f23f57bb Mon Sep 17 00:00:00 2001 From: Bradley Grainger Date: Sun, 12 Jul 2020 02:06:28 -0700 Subject: [PATCH 14/85] Update MySqlConnector to 1.0. (#587) --- samples/Sample.RabbitMQ.MySql/Controllers/ValuesController.cs | 2 +- src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj | 2 +- src/DotNetCore.CAP.MySql/IDataStorage.MySql.cs | 2 +- src/DotNetCore.CAP.MySql/IMonitoringApi.MySql.cs | 2 +- src/DotNetCore.CAP.MySql/IStorageInitializer.MySql.cs | 2 +- test/DotNetCore.CAP.MySql.Test/ConnectionUtil.cs | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/samples/Sample.RabbitMQ.MySql/Controllers/ValuesController.cs b/samples/Sample.RabbitMQ.MySql/Controllers/ValuesController.cs index a20fad2..52c6270 100644 --- a/samples/Sample.RabbitMQ.MySql/Controllers/ValuesController.cs +++ b/samples/Sample.RabbitMQ.MySql/Controllers/ValuesController.cs @@ -4,7 +4,7 @@ using System.Threading.Tasks; using Dapper; using DotNetCore.CAP; using Microsoft.AspNetCore.Mvc; -using MySql.Data.MySqlClient; +using MySqlConnector; namespace Sample.RabbitMQ.MySql.Controllers { diff --git a/src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj b/src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj index f5ea6c2..423a542 100644 --- a/src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj +++ b/src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj @@ -14,7 +14,7 @@ - + diff --git a/src/DotNetCore.CAP.MySql/IDataStorage.MySql.cs b/src/DotNetCore.CAP.MySql/IDataStorage.MySql.cs index b0aced0..9199afb 100644 --- a/src/DotNetCore.CAP.MySql/IDataStorage.MySql.cs +++ b/src/DotNetCore.CAP.MySql/IDataStorage.MySql.cs @@ -13,7 +13,7 @@ using DotNetCore.CAP.Persistence; using DotNetCore.CAP.Serialization; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.Extensions.Options; -using MySql.Data.MySqlClient; +using MySqlConnector; namespace DotNetCore.CAP.MySql { diff --git a/src/DotNetCore.CAP.MySql/IMonitoringApi.MySql.cs b/src/DotNetCore.CAP.MySql/IMonitoringApi.MySql.cs index 31d6189..f4f68d0 100644 --- a/src/DotNetCore.CAP.MySql/IMonitoringApi.MySql.cs +++ b/src/DotNetCore.CAP.MySql/IMonitoringApi.MySql.cs @@ -10,7 +10,7 @@ using DotNetCore.CAP.Messages; using DotNetCore.CAP.Monitoring; using DotNetCore.CAP.Persistence; using Microsoft.Extensions.Options; -using MySql.Data.MySqlClient; +using MySqlConnector; namespace DotNetCore.CAP.MySql { diff --git a/src/DotNetCore.CAP.MySql/IStorageInitializer.MySql.cs b/src/DotNetCore.CAP.MySql/IStorageInitializer.MySql.cs index 83d086c..f32163d 100644 --- a/src/DotNetCore.CAP.MySql/IStorageInitializer.MySql.cs +++ b/src/DotNetCore.CAP.MySql/IStorageInitializer.MySql.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; using DotNetCore.CAP.Persistence; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using MySql.Data.MySqlClient; +using MySqlConnector; namespace DotNetCore.CAP.MySql { diff --git a/test/DotNetCore.CAP.MySql.Test/ConnectionUtil.cs b/test/DotNetCore.CAP.MySql.Test/ConnectionUtil.cs index d37e76e..bd329d1 100644 --- a/test/DotNetCore.CAP.MySql.Test/ConnectionUtil.cs +++ b/test/DotNetCore.CAP.MySql.Test/ConnectionUtil.cs @@ -1,5 +1,5 @@ using System; -using MySql.Data.MySqlClient; +using MySqlConnector; namespace DotNetCore.CAP.MySql.Test { From 9c2096ef066691d48e007f4ad706c11ae7686f1f Mon Sep 17 00:00:00 2001 From: Marko Zorec Date: Tue, 14 Jul 2020 04:43:08 +0200 Subject: [PATCH 15/85] Update english documentation - 2 (#608) * Updates english documentation * updates english documentation --- README.md | 16 ++++++++-------- docs/content/index.md | 2 +- docs/content/user-guide/en/cap/configuration.md | 10 +++++----- docs/content/user-guide/en/cap/messaging.md | 12 ++++++------ docs/content/user-guide/en/cap/serialization.md | 2 +- docs/content/user-guide/en/cap/transactions.md | 2 +- docs/content/user-guide/en/persistent/general.md | 6 +++--- .../en/persistent/in-memory-storage.md | 2 +- .../user-guide/en/persistent/sqlserver.md | 2 +- docs/mkdocs.yml | 2 +- 10 files changed, 28 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 6659068..66586f3 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ CAP is a library based on .Net standard, which is a solution to deal with distributed transactions, has the function of EventBus, it is lightweight, easy to use, and efficient. -In the process of building an SOA or MicroService system, we usually need to use the event to integrate each service. In the process, the simple use of message queue does not guarantee the reliability. CAP adopts local message table program integrated with the current database to solve exceptions that may occur in the process of the distributed system calling each other. It can ensure that the event messages are not lost in any case. +In the process of building an SOA or MicroService system, we usually need to use the event to integrate each service. In the process, simple use of message queue does not guarantee reliability. CAP adopts local message table program integrated with the current database to solve exceptions that may occur in the process of the distributed system calling each other. It can ensure that the event messages are not lost in any case. You can also use CAP as an EventBus. CAP provides a simpler way to implement event publishing and subscriptions. You do not need to inherit or implement any interface during subscription and sending process. @@ -26,7 +26,7 @@ You can also use CAP as an EventBus. CAP provides a simpler way to implement eve ### NuGet -You can run the following command to install CAP in your project. +CAP can be installed in your project with the following command. ``` PM> Install-Package DotNetCore.CAP @@ -53,7 +53,7 @@ PM> Install-Package DotNetCore.CAP.MongoDB //need MongoDB 4.0+ cluster ### Configuration -First, you need to config CAP in your Startup.cs: +First, you need to configure CAP in your Startup.cs: ```cs public void ConfigureServices(IServiceCollection services) @@ -87,7 +87,7 @@ public void ConfigureServices(IServiceCollection services) ### Publish -Inject `ICapPublisher` in your Controller, then use the `ICapPublisher` to send message +Inject `ICapPublisher` in your Controller, then use the `ICapPublisher` to send messages ```c# public class PublishController : Controller @@ -135,7 +135,7 @@ public class PublishController : Controller **In Controller Action** -Add the Attribute `[CapSubscribe()]` on Action to subscribe message: +Add the Attribute `[CapSubscribe()]` on Action to subscribe to messages: ```c# public class PublishController : Controller @@ -212,7 +212,7 @@ public void ShowTime2(DateTime datetime) ``` `ShowTime1` and `ShowTime2` will be called at the same time. -BTW, You can specify the default group name in the configuration : +BTW, You can specify the default group name in the configuration: ```C# services.AddCap(x => @@ -224,13 +224,13 @@ services.AddCap(x => ### Dashboard -CAP v2.1+ provides dashboard pages, you can easily view message that were sent and received. In addition, you can also view the message status in real time in the dashboard. Use the following command to install the Dashboard in your project. +CAP v2.1+ provides dashboard pages, you can easily view messages that were sent and received. In addition, you can also view the message status in real time in the dashboard. Use the following command to install the Dashboard in your project. ``` PM> Install-Package DotNetCore.CAP.Dashboard ``` -In the distributed environment, the dashboard built-in integrated [Consul](http://consul.io) as a node discovery, while the realization of the gateway agent function, you can also easily view the node or other node data, It's like you are visiting local resources. +In the distributed environment, the dashboard built-in integrates [Consul](http://consul.io) as a node discovery, while the realization of the gateway agent function, you can also easily view the node or other node data, It's like you are visiting local resources. ```c# services.AddCap(x => diff --git a/docs/content/index.md b/docs/content/index.md index 2e96fd4..a64c9d8 100644 --- a/docs/content/index.md +++ b/docs/content/index.md @@ -14,7 +14,7 @@ CAP is a library based on .net standard, which is a solution to deal with distri ## Introduction -In the process of building an SOA or MicroService system, we usually need to use the event to integrate each service. In the process, the simple use of message queue does not guarantee the reliability. CAP adopts local message table program integrated with the current database to solve exceptions that may occur in the process of the distributed system calling each other. It can ensure that the event messages are not lost in any case. +In the process of building an SOA or MicroService system, we usually need to use the event to integrate each service. In the process, simple use of message queue does not guarantee reliability. CAP adopts local message table program integrated with the current database to solve exceptions that may occur in the process of the distributed system calling each other. It can ensure that the event messages are not lost in any case. You can also use CAP as an EventBus. CAP provides a simpler way to implement event publishing and subscriptions. You do not need to inherit or implement any interface during subscription and sending process. diff --git a/docs/content/user-guide/en/cap/configuration.md b/docs/content/user-guide/en/cap/configuration.md index e224e17..90a8cf1 100644 --- a/docs/content/user-guide/en/cap/configuration.md +++ b/docs/content/user-guide/en/cap/configuration.md @@ -49,13 +49,13 @@ The default consumer group name, corresponds to different names in different Tra This is a new configuration option introduced in the CAP v2.4 version. It is used to specify a version of a message to isolate messages of different versions of the service. It is often used in A/B testing or multi-service version scenarios. Following are application scenarios that needs versioning: !!! info "Business Iterative and compatible" - Due to the rapid iteration of services, the data structure of the message is not fixed during each service integration process. Sometimes we add or modify certain data structures to accommodate the newly introduced requirements. If you have a brand new system, there's no problem, but if your system is already deployed to a production environment and serves customers, this will cause new features to be incompatible with the old data structure when they go online, and then these changes can cause serious problems. To work around this issue, you can only clean up message queues and persistent messages before starting the application, which is obviously fatal for production environments. + Due to the rapid iteration of services, the data structure of the message is not fixed during each service integration process. Sometimes we add or modify certain data structures to accommodate the newly introduced requirements. If you have a brand new system, there's no problem, but if your system is already deployed to a production environment and serves customers, this will cause new features to be incompatible with the old data structure when they go online, and then these changes can cause serious problems. To work around this issue, you can only clean up message queues and persistent messages before starting the application, which is obviously not acceptable for production environments. !!! info "Multiple versions of the server" Sometimes, the server's server needs to provide multiple sets of interfaces to support different versions of the app. Data structures of the same interface and server interaction of these different versions of the app may be different, so usually server does not provide the same routing addresses to adapt to different versions of App calls. !!! info "Using the same persistent table/collection in different instance" - If you want multiple different instance services to use the same database, in versions prior to 2.4, we could isolate database tables for different instances by specifying different table names. That is to say, when configuring the CAP, it is implemented by configuring different table name prefixes. + If you want multiple different instance services to use the same database, in versions prior to 2.4, we could isolate database tables for different instances by specifying different table names. After version 2.4 this can be achived through CAP configuration, by configuring different table name prefixes. > Check out the blog to learn more about the Versioning feature: https://www.cnblogs.com/savorboard/p/cap-2-4.html @@ -68,7 +68,7 @@ During the message sending process if message transport fails, CAP will try to s During the message sending process if consumption method fails, CAP will try to execute the method again. This configuration option is used to configure the interval between each retry. !!! WARNING "Retry & Interval" - By default, retry will start after **4 minutes** of failure to send or consume, in order to avoid possible problems caused by setting message state delays. + By default if failure occurs on send or consume, retry will start after **4 minutes** in order to avoid possible problems caused by setting message state delays. Failures in the process of sending and consuming messages will be retried 3 times immediately, and will be retried polling after 3 times, at which point the FailedRetryInterval configuration will take effect. #### ConsumerThreadCount @@ -94,10 +94,10 @@ T1 : Message Type T2 : Message Name T3 : Message Content -Failure threshold callback. This action is called when the retry reaches the value set by `FailedRetryCount`, you can receive notification by specifying this parameter to make a manual intervention. For example, send an email or notify. +Failure threshold callback. This action is called when the retry reaches the value set by `FailedRetryCount`, you can receive notification by specifying this parameter to make a manual intervention. For example, send an email or notification. #### SucceedMessageExpiredAfter > Default: 24*3600 sec (1 days) -The expiration time (in seconds) of the success message. When the message is sent or consumed successfully, it will be removed from persistent when the time reaches `SucceedMessageExpiredAfter` seconds. You can set the expiration time by specifying this value. \ No newline at end of file +The expiration time (in seconds) of the success message. When the message is sent or consumed successfully, it will be removed from database storage when the time reaches `SucceedMessageExpiredAfter` seconds. You can set the expiration time by specifying this value. \ No newline at end of file diff --git a/docs/content/user-guide/en/cap/messaging.md b/docs/content/user-guide/en/cap/messaging.md index af82261..48f5c24 100644 --- a/docs/content/user-guide/en/cap/messaging.md +++ b/docs/content/user-guide/en/cap/messaging.md @@ -4,15 +4,15 @@ The data sent by using the `ICapPublisher` interface is called `Message`. ## Scheduling -After CAP receives a message, it sends the message to Transport, which is transported by transport. +After CAP receives a message, it sends the message to Transport(RabitMq, Kafka...), which is transported by transport. When you send message using the `ICapPublisher` interface, CAP will dispatch message to the corresponding Transport. Currently, bulk messaging is not supported. For more information on transports, see [Transports](../transports/general.md) section. -## Persistent +## Storage -CAP will store the message after receiving it. For more information on storage, see the [Persistent](../persistent/general.md) section. +CAP will store the message after receiving it. For more information on storage, see the [Storage](../persistent/general.md) section. ## Retry @@ -20,7 +20,7 @@ Retrying plays an important role in the overall CAP architecture design, CAP ret ### Send retry -During the message sending process, when the broker crashes or the connection fails or an abnormality occurs, the CAP will retry the sending. Retry 3 times for the first time, retry every minute after 4 minutes, and +1 retries. When the total number of times reaches 50,CAP will stop retrying. +During the message sending process, when the broker crashes or the connection fails or an abnormality occurs, CAP will retry the sending. Retry 3 times for the first time, retry every minute after 4 minutes, and +1 retry. When the total number of retries reaches 50,CAP will stop retrying. You can adjust the total number of retries by setting `FailedRetryCount` in CapOptions. @@ -32,10 +32,10 @@ The consumer method is executed when the Consumer receives the message and will ## Data Cleanup -There is an `ExpiresAt` field in the database message table indicating the expiration time of the message. When the message is sent successfully, the status will be changed to `Successed`, and `ExpiresAt` will be set to **1 hour** later. +There is an `ExpiresAt` field in the database message table indicating the expiration time of the message. When the message is sent successfully, status will be changed to `Successed`, and `ExpiresAt` will be set to **1 hour** later. Consuming failure will change the message status to `Failed` and `ExpiresAt` will be set to **15 days** later. By default, the data of the message in the table is deleted **every hour** to avoid performance degradation caused by too much data. The cleanup strategy `ExpiresAt` is performed when field is not empty and is less than the current time. -That is to say, the message with the status Failed (normally they have been retried 50 times), if you do not have manual intervention for 15 days, it will **also be** cleaned up. \ No newline at end of file +That is to say, the message with the status Failed (by default they have been retried 50 times), if you do not have manual intervention for 15 days, it will **also be** cleaned up. \ No newline at end of file diff --git a/docs/content/user-guide/en/cap/serialization.md b/docs/content/user-guide/en/cap/serialization.md index 78ed793..d4c256a 100644 --- a/docs/content/user-guide/en/cap/serialization.md +++ b/docs/content/user-guide/en/cap/serialization.md @@ -19,7 +19,7 @@ public class YourSerializer: ISerializer } ``` -Then register your implementation in the container: +Then register your implemented serializer in the container: ``` diff --git a/docs/content/user-guide/en/cap/transactions.md b/docs/content/user-guide/en/cap/transactions.md index 24a2979..ef4a164 100644 --- a/docs/content/user-guide/en/cap/transactions.md +++ b/docs/content/user-guide/en/cap/transactions.md @@ -4,7 +4,7 @@ CAP does not directly provide out-of-the-box MS DTC or 2PC-based distributed transactions, instead we provide a solution that can be used to solve problems encountered in distributed transactions. -In a distributed environment, using 2PC or DTC-based distributed transactions can be very expensive due to the overhead involved in communication, as is performance. In addition, since distributed transactions based on 2PC or DTC are also subject to the **CAP theorem**, it will have to give up availability (A in CAP) when network partitioning occurs. +In a distributed environment, using 2PC or DTC-based distributed transactions can be very expensive due to the overhead involved in communication which affects performance. In addition, since distributed transactions based on 2PC or DTC are also subject to the **CAP theorem**, it will have to give up availability (A in CAP) when network partitioning occurs. > A distributed transaction is a very complex process with a lot of moving parts that can fail. Also, if these parts run on different machines or even in different data centers, the process of committing a transaction could become very long and unreliable. diff --git a/docs/content/user-guide/en/persistent/general.md b/docs/content/user-guide/en/persistent/general.md index 57c9cc7..e178f69 100644 --- a/docs/content/user-guide/en/persistent/general.md +++ b/docs/content/user-guide/en/persistent/general.md @@ -1,8 +1,8 @@ # General -CAP needs to use storage media with persistence capabilities to store event messages, such as through databases or other NoSql facilities. CAP uses this approach to deal with loss of messages in all environments or network anomalies. Reliability of messages is the cornerstone of distributed transactions, so messages cannot be lost under any circumstances. +CAP needs to use storage media with persistence capabilities to store event messages in databases or other NoSql facilities. CAP uses this approach to deal with loss of messages in all environments or network anomalies. Reliability of messages is the cornerstone of distributed transactions, so messages cannot be lost under any circumstances. -## Persistent +## Persistence ### Before sent @@ -23,7 +23,7 @@ Since Kafka is born with message persistence using files, Kafka will ensure that ## Storage -After CAP is started, two tables are generated into the persistent, by default the name is `Cap.Published` and `Cap.Received`. +After CAP is started, two tables are generated in used storage, by default the name is `Cap.Published` and `Cap.Received`. ### Storage Data Structure diff --git a/docs/content/user-guide/en/persistent/in-memory-storage.md b/docs/content/user-guide/en/persistent/in-memory-storage.md index b44259a..b2d432f 100644 --- a/docs/content/user-guide/en/persistent/in-memory-storage.md +++ b/docs/content/user-guide/en/persistent/in-memory-storage.md @@ -1,6 +1,6 @@ # In-Memory Storage -Persistent storage of memory messages is often used in development and test environments, and if you use memory-based storage you lose the reliability of local transaction messages. +In-memory storage is often used in development and test environments, and if you use memory-based storage you lose the reliability of local transaction messages. ## Configuration diff --git a/docs/content/user-guide/en/persistent/sqlserver.md b/docs/content/user-guide/en/persistent/sqlserver.md index 0119266..c66a82a 100644 --- a/docs/content/user-guide/en/persistent/sqlserver.md +++ b/docs/content/user-guide/en/persistent/sqlserver.md @@ -3,7 +3,7 @@ SQL Server is a relational database management system developed by Microsoft. CAP supports SQL Server database. !!! warning "Warning" - We currently use `Microsoft.Data.SqlClient` as the database driver, which is the future of SQL Server drivers, and we have abandoned `System.Data.SqlClient`, we suggest you switch into. + We currently use `Microsoft.Data.SqlClient` as the database driver, which is the future of SQL Server drivers, and we have abandoned `System.Data.SqlClient`, we suggest that you switch to. ## Configuration diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 3cde8fc..f714092 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -92,7 +92,7 @@ nav: - Azure Service Bus: user-guide/en/transports/azure-service-bus.md - Amazon SQS: user-guide/en/transports/aws-sqs.md - In-Memory Queue: user-guide/en/transports/in-memory-queue.md - - Persistent: + - Storage: - General: user-guide/en/persistent/general.md - SQL Server: user-guide/en/persistent/sqlserver.md - MySQL: user-guide/en/persistent/mysql.md From 27e45736e322562d282a90447f5ed644d1f9c4e1 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Wed, 15 Jul 2020 22:13:36 +0800 Subject: [PATCH 16/85] Fixes wording and docs url --- .../user-guide/en/cap/configuration.md | 2 +- docs/content/user-guide/en/cap/messaging.md | 4 +- .../user-guide/en/cap/serialization.md | 4 +- .../user-guide/en/monitoring/consul.md | 42 +++++++++++++- .../user-guide/en/monitoring/metrics.md | 3 - .../en/{persistent => storage}/general.md | 0 .../in-memory-storage.md | 0 .../en/{persistent => storage}/mongodb.md | 0 .../en/{persistent => storage}/mysql.md | 0 .../en/{persistent => storage}/postgresql.md | 0 .../en/{persistent => storage}/sqlserver.md | 0 .../en/{transports => transport}/aws-sqs.md | 0 .../azure-service-bus.md | 0 .../en/{transports => transport}/general.md | 0 .../in-memory-queue.md | 0 .../en/{transports => transport}/kafka.md | 0 .../en/{transports => transport}/rabbitmq.md | 0 docs/content/user-guide/zh/cap/messaging.md | 4 +- .../user-guide/zh/cap/serialization.md | 4 +- .../user-guide/zh/monitoring/consul.md | 42 +++++++++++++- .../user-guide/zh/monitoring/metrics.md | 3 - .../zh/{persistent => storage}/general.md | 0 .../in-memory-storage.md | 0 .../zh/{persistent => storage}/mongodb.md | 0 .../zh/{persistent => storage}/mysql.md | 0 .../zh/{persistent => storage}/postgresql.md | 0 .../zh/{persistent => storage}/sqlserver.md | 0 .../zh/{transports => transport}/aws-sqs.md | 0 .../azure-service-bus.md | 0 .../zh/{transports => transport}/general.md | 0 .../in-memory-queue.md | 0 .../zh/{transports => transport}/kafka.md | 0 .../zh/{transports => transport}/rabbitmq.md | 0 docs/mkdocs.yml | 56 +++++++++---------- 34 files changed, 119 insertions(+), 45 deletions(-) delete mode 100644 docs/content/user-guide/en/monitoring/metrics.md rename docs/content/user-guide/en/{persistent => storage}/general.md (100%) rename docs/content/user-guide/en/{persistent => storage}/in-memory-storage.md (100%) rename docs/content/user-guide/en/{persistent => storage}/mongodb.md (100%) rename docs/content/user-guide/en/{persistent => storage}/mysql.md (100%) rename docs/content/user-guide/en/{persistent => storage}/postgresql.md (100%) rename docs/content/user-guide/en/{persistent => storage}/sqlserver.md (100%) rename docs/content/user-guide/en/{transports => transport}/aws-sqs.md (100%) rename docs/content/user-guide/en/{transports => transport}/azure-service-bus.md (100%) rename docs/content/user-guide/en/{transports => transport}/general.md (100%) rename docs/content/user-guide/en/{transports => transport}/in-memory-queue.md (100%) rename docs/content/user-guide/en/{transports => transport}/kafka.md (100%) rename docs/content/user-guide/en/{transports => transport}/rabbitmq.md (100%) delete mode 100644 docs/content/user-guide/zh/monitoring/metrics.md rename docs/content/user-guide/zh/{persistent => storage}/general.md (100%) rename docs/content/user-guide/zh/{persistent => storage}/in-memory-storage.md (100%) rename docs/content/user-guide/zh/{persistent => storage}/mongodb.md (100%) rename docs/content/user-guide/zh/{persistent => storage}/mysql.md (100%) rename docs/content/user-guide/zh/{persistent => storage}/postgresql.md (100%) rename docs/content/user-guide/zh/{persistent => storage}/sqlserver.md (100%) rename docs/content/user-guide/zh/{transports => transport}/aws-sqs.md (100%) rename docs/content/user-guide/zh/{transports => transport}/azure-service-bus.md (100%) rename docs/content/user-guide/zh/{transports => transport}/general.md (100%) rename docs/content/user-guide/zh/{transports => transport}/in-memory-queue.md (100%) rename docs/content/user-guide/zh/{transports => transport}/kafka.md (100%) rename docs/content/user-guide/zh/{transports => transport}/rabbitmq.md (100%) diff --git a/docs/content/user-guide/en/cap/configuration.md b/docs/content/user-guide/en/cap/configuration.md index 90a8cf1..65000d2 100644 --- a/docs/content/user-guide/en/cap/configuration.md +++ b/docs/content/user-guide/en/cap/configuration.md @@ -25,7 +25,7 @@ services.AddCap(capOptions => }); ``` -For specific transport and storage configuration, you can take a look at the configuration options provided by the specific components in the [Transports](../transports/general.md) section and the [Persistent](../persistent/general.md) section. +For specific transport and storage configuration, you can take a look at the configuration options provided by the specific components in the [Transports](../transport/general.md) section and the [Persistent](../storage/general.md) section. ## Custom configuration diff --git a/docs/content/user-guide/en/cap/messaging.md b/docs/content/user-guide/en/cap/messaging.md index 48f5c24..770d898 100644 --- a/docs/content/user-guide/en/cap/messaging.md +++ b/docs/content/user-guide/en/cap/messaging.md @@ -8,11 +8,11 @@ After CAP receives a message, it sends the message to Transport(RabitMq, Kafka.. When you send message using the `ICapPublisher` interface, CAP will dispatch message to the corresponding Transport. Currently, bulk messaging is not supported. -For more information on transports, see [Transports](../transports/general.md) section. +For more information on transports, see [Transports](../transport/general.md) section. ## Storage -CAP will store the message after receiving it. For more information on storage, see the [Storage](../persistent/general.md) section. +CAP will store the message after receiving it. For more information on storage, see the [Storage](../storage/general.md) section. ## Retry diff --git a/docs/content/user-guide/en/cap/serialization.md b/docs/content/user-guide/en/cap/serialization.md index d4c256a..c15e932 100644 --- a/docs/content/user-guide/en/cap/serialization.md +++ b/docs/content/user-guide/en/cap/serialization.md @@ -32,9 +32,9 @@ services.AddCap ## Message Adapter (removed in v3.0) -In heterogeneous systems, sometimes you need to communicate with other systems, but other systems use message objects that may be different from CAP's [**Wrapper Object**](../persistent/general.md#_7). This time maybe you need to customize the message wapper. +In heterogeneous systems, sometimes you need to communicate with other systems, but other systems use message objects that may be different from CAP's [**Wrapper Object**](../storage/general.md#_7). This time maybe you need to customize the message wapper. -CAP provides the `IMessagePacker` interface for customizing the [**Wrapper Object**](../persistent/general.md#_7). Custom MessagePacker usually packs and unpacks the `CapMessage` In this process you can add your own business objects. +CAP provides the `IMessagePacker` interface for customizing the [**Wrapper Object**](../storage/general.md#_7). Custom MessagePacker usually packs and unpacks the `CapMessage` In this process you can add your own business objects. Usage : diff --git a/docs/content/user-guide/en/monitoring/consul.md b/docs/content/user-guide/en/monitoring/consul.md index 7c50a97..9c11320 100644 --- a/docs/content/user-guide/en/monitoring/consul.md +++ b/docs/content/user-guide/en/monitoring/consul.md @@ -1,4 +1,44 @@ # Consul -Consul is a distributed service mesh to connect, secure, and configure services across any runtime platform and public or private cloud. +[Consul](https://www.consul.io/) is a distributed service mesh to connect, secure, and configure services across any runtime platform and public or private cloud. +## Consul Configuration for dashboard + +CAP's Dashboard uses Consul as a service discovery to get the data of other nodes, and you can switch to the Servers page to see other nodes. + +![](https://camo.githubusercontent.com/54c00c6ae65ce1d7b9109ed8cbcdca703a050c47/687474703a2f2f696d61676573323031372e636e626c6f67732e636f6d2f626c6f672f3235303431372f3230313731302f3235303431372d32303137313030343232313030313838302d313136323931383336322e706e67) + +Click the `Switch` button to switch to the target node, CAP will use a proxy to get the data of the node you switched to. + +The following is a configuration example, you need to configure them on each node. + +```C# +services.AddCap(x => +{ + x.UseMySql(Configuration.GetValue("ConnectionString")); + x.UseRabbitMQ("localhost"); + x.UseDashboard(); + x.UseDiscovery(_ => + { + _.DiscoveryServerHostName = "localhost"; + _.DiscoveryServerPort = 8500; + _.CurrentNodeHostName = Configuration.GetValue("ASPNETCORE_HOSTNAME"); + _.CurrentNodePort = Configuration.GetValue("ASPNETCORE_PORT"); + _.NodeId = Configuration.GetValue("NodeId"); + _.NodeName = Configuration.GetValue("NodeName"); + }); +}); +``` + +Consul 1.6.2: + +``` +consul agent -dev +``` + +Windows 10, ASP.NET Core 3.1: + +```sh +set ASPNETCORE_HOSTNAME=localhost&& set ASPNETCORE_PORT=5001&& dotnet run --urls=http://localhost:5001 NodeId=1 NodeName=CAP-1 ConnectionString="Server=localhost;Database=aaa;UserId=xxx;Password=xxx;" +set ASPNETCORE_HOSTNAME=localhost&& set ASPNETCORE_PORT=5002&& dotnet run --urls=http://localhost:5002 NodeId=2 NodeName=CAP-2 ConnectionString="Server=localhost;Database=bbb;UserId=xxx;Password=xxx;" +``` \ No newline at end of file diff --git a/docs/content/user-guide/en/monitoring/metrics.md b/docs/content/user-guide/en/monitoring/metrics.md deleted file mode 100644 index 0f82e79..0000000 --- a/docs/content/user-guide/en/monitoring/metrics.md +++ /dev/null @@ -1,3 +0,0 @@ -# Metrics - -TODO: \ No newline at end of file diff --git a/docs/content/user-guide/en/persistent/general.md b/docs/content/user-guide/en/storage/general.md similarity index 100% rename from docs/content/user-guide/en/persistent/general.md rename to docs/content/user-guide/en/storage/general.md diff --git a/docs/content/user-guide/en/persistent/in-memory-storage.md b/docs/content/user-guide/en/storage/in-memory-storage.md similarity index 100% rename from docs/content/user-guide/en/persistent/in-memory-storage.md rename to docs/content/user-guide/en/storage/in-memory-storage.md diff --git a/docs/content/user-guide/en/persistent/mongodb.md b/docs/content/user-guide/en/storage/mongodb.md similarity index 100% rename from docs/content/user-guide/en/persistent/mongodb.md rename to docs/content/user-guide/en/storage/mongodb.md diff --git a/docs/content/user-guide/en/persistent/mysql.md b/docs/content/user-guide/en/storage/mysql.md similarity index 100% rename from docs/content/user-guide/en/persistent/mysql.md rename to docs/content/user-guide/en/storage/mysql.md diff --git a/docs/content/user-guide/en/persistent/postgresql.md b/docs/content/user-guide/en/storage/postgresql.md similarity index 100% rename from docs/content/user-guide/en/persistent/postgresql.md rename to docs/content/user-guide/en/storage/postgresql.md diff --git a/docs/content/user-guide/en/persistent/sqlserver.md b/docs/content/user-guide/en/storage/sqlserver.md similarity index 100% rename from docs/content/user-guide/en/persistent/sqlserver.md rename to docs/content/user-guide/en/storage/sqlserver.md diff --git a/docs/content/user-guide/en/transports/aws-sqs.md b/docs/content/user-guide/en/transport/aws-sqs.md similarity index 100% rename from docs/content/user-guide/en/transports/aws-sqs.md rename to docs/content/user-guide/en/transport/aws-sqs.md diff --git a/docs/content/user-guide/en/transports/azure-service-bus.md b/docs/content/user-guide/en/transport/azure-service-bus.md similarity index 100% rename from docs/content/user-guide/en/transports/azure-service-bus.md rename to docs/content/user-guide/en/transport/azure-service-bus.md diff --git a/docs/content/user-guide/en/transports/general.md b/docs/content/user-guide/en/transport/general.md similarity index 100% rename from docs/content/user-guide/en/transports/general.md rename to docs/content/user-guide/en/transport/general.md diff --git a/docs/content/user-guide/en/transports/in-memory-queue.md b/docs/content/user-guide/en/transport/in-memory-queue.md similarity index 100% rename from docs/content/user-guide/en/transports/in-memory-queue.md rename to docs/content/user-guide/en/transport/in-memory-queue.md diff --git a/docs/content/user-guide/en/transports/kafka.md b/docs/content/user-guide/en/transport/kafka.md similarity index 100% rename from docs/content/user-guide/en/transports/kafka.md rename to docs/content/user-guide/en/transport/kafka.md diff --git a/docs/content/user-guide/en/transports/rabbitmq.md b/docs/content/user-guide/en/transport/rabbitmq.md similarity index 100% rename from docs/content/user-guide/en/transports/rabbitmq.md rename to docs/content/user-guide/en/transport/rabbitmq.md diff --git a/docs/content/user-guide/zh/cap/messaging.md b/docs/content/user-guide/zh/cap/messaging.md index ec0987a..bd853a6 100644 --- a/docs/content/user-guide/zh/cap/messaging.md +++ b/docs/content/user-guide/zh/cap/messaging.md @@ -52,11 +52,11 @@ CAP 接收到消息之后会将消息发送到 Transport, 由 Transport 进行 当你使用 `ICapPublisher` 接口发送时,CAP将会将消息调度到相应的 Transport中去,目前还不支持批量发送消息。 -有关 Transports 的更多信息,可以查看 [Transports](../transports/general.md) 章节。 +有关 Transports 的更多信息,可以查看 [Transports](../transport/general.md) 章节。 ## 消息存储 -CAP 接收到消息之后会将消息进行 Persistent(持久化), 有关 Persistent 的更多信息,可以查看 [Persistent](../persistent/general.md) 章节。 +CAP 接收到消息之后会将消息进行 Persistent(持久化), 有关 Persistent 的更多信息,可以查看 [Persistent](../storage/general.md) 章节。 ## 消息重试 diff --git a/docs/content/user-guide/zh/cap/serialization.md b/docs/content/user-guide/zh/cap/serialization.md index cc78348..84c3624 100644 --- a/docs/content/user-guide/zh/cap/serialization.md +++ b/docs/content/user-guide/zh/cap/serialization.md @@ -34,9 +34,9 @@ services.AddCap ## 消息适配器 (v3.0移除 ) -在异构系统中,有时候需要和其他系统进行通讯,但是其他系统使用的消息对象可能和 CAP 的[**包装器对象**](../persistent/general.md#_7)不一样,这个时候就需要对消息进行自定义适配。 +在异构系统中,有时候需要和其他系统进行通讯,但是其他系统使用的消息对象可能和 CAP 的[**包装器对象**](../storage/general.md#_7)不一样,这个时候就需要对消息进行自定义适配。 -CAP 提供了 `IMessagePacker` 接口用于对 [**包装器对象**](../persistent/general.md#_7) 进行自定义,自定义的 MessagePacker 通常是将 `CapMessage` 进行打包和解包操作,在这个过程中可以添加自己的业务对象。 +CAP 提供了 `IMessagePacker` 接口用于对 [**包装器对象**](../storage/general.md#_7) 进行自定义,自定义的 MessagePacker 通常是将 `CapMessage` 进行打包和解包操作,在这个过程中可以添加自己的业务对象。 使用方法: diff --git a/docs/content/user-guide/zh/monitoring/consul.md b/docs/content/user-guide/zh/monitoring/consul.md index 7c50a97..707632a 100644 --- a/docs/content/user-guide/zh/monitoring/consul.md +++ b/docs/content/user-guide/zh/monitoring/consul.md @@ -1,4 +1,44 @@ # Consul -Consul is a distributed service mesh to connect, secure, and configure services across any runtime platform and public or private cloud. +[Consul](https://www.consul.io/) 是一个分布式服务网格,用于跨任何运行时平台和公共或私有云连接,保护和配置服务。 +## Dashboard 中的 Consul 配置 + +CAP的 Dashboard 使用 Consul 作为服务发现来显示其他节点的数据,然后你就在任意节点的 Dashboard 中切换到 Servers 页面看到其他的节点。 + +![](https://camo.githubusercontent.com/54c00c6ae65ce1d7b9109ed8cbcdca703a050c47/687474703a2f2f696d61676573323031372e636e626c6f67732e636f6d2f626c6f672f3235303431372f3230313731302f3235303431372d32303137313030343232313030313838302d313136323931383336322e706e67) + +通过点击 Switch 按钮来切换到其他的节点看到其他节点的数据,而不必访问很多地址来分别查看。 + +以下是一个配置示例, 你需要在每个节点分别配置: + +```C# +services.AddCap(x => +{ + x.UseMySql(Configuration.GetValue("ConnectionString")); + x.UseRabbitMQ("localhost"); + x.UseDashboard(); + x.UseDiscovery(_ => + { + _.DiscoveryServerHostName = "localhost"; + _.DiscoveryServerPort = 8500; + _.CurrentNodeHostName = Configuration.GetValue("ASPNETCORE_HOSTNAME"); + _.CurrentNodePort = Configuration.GetValue("ASPNETCORE_PORT"); + _.NodeId = Configuration.GetValue("NodeId"); + _.NodeName = Configuration.GetValue("NodeName"); + }); +}); +``` + +Consul 1.6.2: + +``` +consul agent -dev +``` + +Windows 10, ASP.NET Core 3.1: + +```sh +set ASPNETCORE_HOSTNAME=localhost&& set ASPNETCORE_PORT=5001&& dotnet run --urls=http://localhost:5001 NodeId=1 NodeName=CAP-1 ConnectionString="Server=localhost;Database=aaa;UserId=xxx;Password=xxx;" +set ASPNETCORE_HOSTNAME=localhost&& set ASPNETCORE_PORT=5002&& dotnet run --urls=http://localhost:5002 NodeId=2 NodeName=CAP-2 ConnectionString="Server=localhost;Database=bbb;UserId=xxx;Password=xxx;" +``` \ No newline at end of file diff --git a/docs/content/user-guide/zh/monitoring/metrics.md b/docs/content/user-guide/zh/monitoring/metrics.md deleted file mode 100644 index 0f82e79..0000000 --- a/docs/content/user-guide/zh/monitoring/metrics.md +++ /dev/null @@ -1,3 +0,0 @@ -# Metrics - -TODO: \ No newline at end of file diff --git a/docs/content/user-guide/zh/persistent/general.md b/docs/content/user-guide/zh/storage/general.md similarity index 100% rename from docs/content/user-guide/zh/persistent/general.md rename to docs/content/user-guide/zh/storage/general.md diff --git a/docs/content/user-guide/zh/persistent/in-memory-storage.md b/docs/content/user-guide/zh/storage/in-memory-storage.md similarity index 100% rename from docs/content/user-guide/zh/persistent/in-memory-storage.md rename to docs/content/user-guide/zh/storage/in-memory-storage.md diff --git a/docs/content/user-guide/zh/persistent/mongodb.md b/docs/content/user-guide/zh/storage/mongodb.md similarity index 100% rename from docs/content/user-guide/zh/persistent/mongodb.md rename to docs/content/user-guide/zh/storage/mongodb.md diff --git a/docs/content/user-guide/zh/persistent/mysql.md b/docs/content/user-guide/zh/storage/mysql.md similarity index 100% rename from docs/content/user-guide/zh/persistent/mysql.md rename to docs/content/user-guide/zh/storage/mysql.md diff --git a/docs/content/user-guide/zh/persistent/postgresql.md b/docs/content/user-guide/zh/storage/postgresql.md similarity index 100% rename from docs/content/user-guide/zh/persistent/postgresql.md rename to docs/content/user-guide/zh/storage/postgresql.md diff --git a/docs/content/user-guide/zh/persistent/sqlserver.md b/docs/content/user-guide/zh/storage/sqlserver.md similarity index 100% rename from docs/content/user-guide/zh/persistent/sqlserver.md rename to docs/content/user-guide/zh/storage/sqlserver.md diff --git a/docs/content/user-guide/zh/transports/aws-sqs.md b/docs/content/user-guide/zh/transport/aws-sqs.md similarity index 100% rename from docs/content/user-guide/zh/transports/aws-sqs.md rename to docs/content/user-guide/zh/transport/aws-sqs.md diff --git a/docs/content/user-guide/zh/transports/azure-service-bus.md b/docs/content/user-guide/zh/transport/azure-service-bus.md similarity index 100% rename from docs/content/user-guide/zh/transports/azure-service-bus.md rename to docs/content/user-guide/zh/transport/azure-service-bus.md diff --git a/docs/content/user-guide/zh/transports/general.md b/docs/content/user-guide/zh/transport/general.md similarity index 100% rename from docs/content/user-guide/zh/transports/general.md rename to docs/content/user-guide/zh/transport/general.md diff --git a/docs/content/user-guide/zh/transports/in-memory-queue.md b/docs/content/user-guide/zh/transport/in-memory-queue.md similarity index 100% rename from docs/content/user-guide/zh/transports/in-memory-queue.md rename to docs/content/user-guide/zh/transport/in-memory-queue.md diff --git a/docs/content/user-guide/zh/transports/kafka.md b/docs/content/user-guide/zh/transport/kafka.md similarity index 100% rename from docs/content/user-guide/zh/transports/kafka.md rename to docs/content/user-guide/zh/transport/kafka.md diff --git a/docs/content/user-guide/zh/transports/rabbitmq.md b/docs/content/user-guide/zh/transport/rabbitmq.md similarity index 100% rename from docs/content/user-guide/zh/transports/rabbitmq.md rename to docs/content/user-guide/zh/transport/rabbitmq.md diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index f714092..0d2daab 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -85,21 +85,22 @@ nav: - Serialization: user-guide/en/cap/serialization.md - Transactions: user-guide/en/cap/transactions.md - Idempotence: user-guide/en/cap/idempotence.md - - Transports: - - General: user-guide/en/transports/general.md - - RabbitMQ: user-guide/en/transports/rabbitmq.md - - Apache Kafka®: user-guide/en/transports/kafka.md - - Azure Service Bus: user-guide/en/transports/azure-service-bus.md - - Amazon SQS: user-guide/en/transports/aws-sqs.md - - In-Memory Queue: user-guide/en/transports/in-memory-queue.md + - Transport: + - General: user-guide/en/transport/general.md + - RabbitMQ: user-guide/en/transport/rabbitmq.md + - Apache Kafka®: user-guide/en/transport/kafka.md + - Azure Service Bus: user-guide/en/transport/azure-service-bus.md + - Amazon SQS: user-guide/en/transport/aws-sqs.md + - In-Memory Queue: user-guide/en/transport/in-memory-queue.md - Storage: - - General: user-guide/en/persistent/general.md - - SQL Server: user-guide/en/persistent/sqlserver.md - - MySQL: user-guide/en/persistent/mysql.md - - PostgreSql: user-guide/en/persistent/postgresql.md - - MongoDB: user-guide/en/persistent/mongodb.md - - In-Memory: user-guide/en/persistent/in-memory-storage.md + - General: user-guide/en/storage/general.md + - SQL Server: user-guide/en/storage/sqlserver.md + - MySQL: user-guide/en/storage/mysql.md + - PostgreSql: user-guide/en/storage/postgresql.md + - MongoDB: user-guide/en/storage/mongodb.md + - In-Memory: user-guide/en/storage/in-memory-storage.md - Monitoring: + - Consul: user-guide/en/monitoring/consul.md - Dashboard: user-guide/en/monitoring/dashboard.md - Diagnostics: user-guide/en/monitoring/diagnostics.md - Samples: @@ -119,25 +120,24 @@ nav: - 运输: user-guide/zh/cap/transactions.md - 幂等性: user-guide/zh/cap/idempotence.md - 传输: - - 简介: user-guide/zh/transports/general.md - - RabbitMQ: user-guide/zh/transports/rabbitmq.md - - Apache Kafka®: user-guide/zh/transports/kafka.md - - Azure Service Bus: user-guide/zh/transports/azure-service-bus.md - - Amazon SQS: user-guide/zh/transports/aws-sqs.md - - In-Memory Queue: user-guide/zh/transports/in-memory-queue.md - - 持久化: - - 简介: user-guide/zh/persistent/general.md - - SQL Server: user-guide/zh/persistent/sqlserver.md - - MySQL: user-guide/zh/persistent/mysql.md - - PostgreSql: user-guide/zh/persistent/postgresql.md - - MongoDB: user-guide/zh/persistent/mongodb.md - - In-Memory: user-guide/zh/persistent/in-memory-storage.md + - 简介: user-guide/zh/transport/general.md + - RabbitMQ: user-guide/zh/transport/rabbitmq.md + - Apache Kafka®: user-guide/zh/transport/kafka.md + - Azure Service Bus: user-guide/zh/transport/azure-service-bus.md + - Amazon SQS: user-guide/zh/transport/aws-sqs.md + - In-Memory Queue: user-guide/zh/transport/in-memory-queue.md + - 存储: + - 简介: user-guide/zh/storage/general.md + - SQL Server: user-guide/zh/storage/sqlserver.md + - MySQL: user-guide/zh/storage/mysql.md + - PostgreSql: user-guide/zh/storage/postgresql.md + - MongoDB: user-guide/zh/storage/mongodb.md + - In-Memory: user-guide/zh/storage/in-memory-storage.md - 监控: - Consul: user-guide/zh/monitoring/consul.md - Dashboard: user-guide/zh/monitoring/dashboard.md - - Diagnostics: user-guide/zh/monitoring/diagnostics.md + - 性能追踪: user-guide/zh/monitoring/diagnostics.md - 健康检查: user-guide/zh/monitoring/health-checks.md - - Metrics: user-guide/zh/monitoring/metrics.md - 示例: - Github: user-guide/zh/samples/github.md - eShopOnContainers: user-guide/zh/samples/eshoponcontainers.md From a13e1c7640e19d795b2c2685e6635fc8ff79bfb0 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Wed, 15 Jul 2020 22:13:47 +0800 Subject: [PATCH 17/85] Update docs --- .../user-guide/en/monitoring/diagnostics.md | 24 ++++++++++++++++++- .../user-guide/zh/monitoring/diagnostics.md | 6 ++--- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/docs/content/user-guide/en/monitoring/diagnostics.md b/docs/content/user-guide/en/monitoring/diagnostics.md index b6bbd56..58f5d13 100644 --- a/docs/content/user-guide/en/monitoring/diagnostics.md +++ b/docs/content/user-guide/en/monitoring/diagnostics.md @@ -20,4 +20,26 @@ Diagnostics provides external event information as follows: * After the subscriber method is executed * Subscriber method execution exception -Related objects, you can find at the `DotNetCore.CAP.Diagnostics` namespace. \ No newline at end of file +Related objects, you can find at the `DotNetCore.CAP.Diagnostics` namespace. + + +## Tracing CAP events in [Apache Skywalking](https://github.com/apache/skywalking) + +Skywalking's C# client provides support for CAP Diagnostics. You can use [SkyAPM-dotnet](https://github.com/SkyAPM/SkyAPM-dotnet) to tracking. + +Try to read the [README](https://github.com/SkyAPM/SkyAPM-dotnet/blob/master/README.md) to integrate it in your project. + + Example tracking image : + +![](https://user-images.githubusercontent.com/8205994/71006463-51025980-2120-11ea-82dc-bffa5530d515.png) + + +![](https://user-images.githubusercontent.com/8205994/71006589-7b541700-2120-11ea-910b-7e0f2dfddce8.png) + +## Others APM support + +There is currently no support for APMs other than Skywalking, and if you would like to support CAP diagnostic events in other APM, you can refer to the code here to implement it: + +At present, apart from Skywalking, we have not provided support for other APMs. If you need it, you can refer the code [here](https://github.com/SkyAPM/SkyAPM-dotnet/tree/master/src/SkyApm.Diagnostics.CAP) to implementation, and we also welcome the Pull Request. + +https://github.com/SkyAPM/SkyAPM-dotnet/tree/master/src/SkyApm.Diagnostics.CAP \ No newline at end of file diff --git a/docs/content/user-guide/zh/monitoring/diagnostics.md b/docs/content/user-guide/zh/monitoring/diagnostics.md index 40af9fb..825c1b9 100644 --- a/docs/content/user-guide/zh/monitoring/diagnostics.md +++ b/docs/content/user-guide/zh/monitoring/diagnostics.md @@ -1,9 +1,9 @@ -# Diagnostics +# 性能追踪 Diagnostics 提供一组功能使我们能够很方便的可以记录在应用程序运行期间发生的关键性操作以及他们的执行时间等,使管理员可以查找特别是生产环境中出现问题所在的根本原因。 -## CAP 中的 Diagnostics +## CAP 中的性能追踪 在 CAP 中,对 `DiagnosticSource` 提供了支持,监听器名称为 `CapDiagnosticListener`。 @@ -37,6 +37,6 @@ Skywalking 的 C# 客户端提供了对 CAP Diagnostics 的支持,你可以利 ## 其他 APM 的支持 -目前还没有实现对除了Skywalking的其他APM的支持,如果你想在其他 APM 中实现对 CAP 诊断事件的支持,你可以参考这里的代码来实现它: +目前还没有实现对除了 Skywalking 的其他APM的支持,如果你想在其他 APM 中实现对 CAP 诊断事件的支持,你可以参考这里的代码来实现它: https://github.com/SkyAPM/SkyAPM-dotnet/tree/master/src/SkyApm.Diagnostics.CAP \ No newline at end of file From daf4efbf50b708c91f054297f9fd396d08f3b4c4 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Mon, 20 Jul 2020 23:21:13 +0800 Subject: [PATCH 18/85] Fix amazon sqs reject message bug --- .../AmazonSQSConsumerClient.cs | 50 ++++++++++++++++++- .../TopicNormalizer.cs | 2 +- .../Internal/IConsumerRegister.Default.cs | 6 +++ src/DotNetCore.CAP/Transport/MqLogType.cs | 6 ++- 4 files changed, 60 insertions(+), 4 deletions(-) diff --git a/src/DotNetCore.CAP.AmazonSQS/AmazonSQSConsumerClient.cs b/src/DotNetCore.CAP.AmazonSQS/AmazonSQSConsumerClient.cs index 2bd6ee4..75b933a 100644 --- a/src/DotNetCore.CAP.AmazonSQS/AmazonSQSConsumerClient.cs +++ b/src/DotNetCore.CAP.AmazonSQS/AmazonSQSConsumerClient.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; +using System.Threading.Tasks; using Amazon.SimpleNotificationService; using Amazon.SimpleNotificationService.Model; using Amazon.SQS; @@ -103,12 +104,27 @@ namespace DotNetCore.CAP.AmazonSQS public void Commit(object sender) { - _sqsClient.DeleteMessageAsync(_queueUrl, (string)sender); + try + { + _sqsClient.DeleteMessageAsync(_queueUrl, (string)sender); + } + catch (InvalidIdFormatException ex) + { + InvalidIdFormatLog(ex.Message); + } } public void Reject(object sender) { - _sqsClient.ChangeMessageVisibilityAsync(_queueUrl, (string)sender, 3000); + try + { + // Visible again in 3 seconds + _sqsClient.ChangeMessageVisibilityAsync(_queueUrl, (string)sender, 3); + } + catch (MessageNotInflightException ex) + { + MessageNotInflightLog(ex.Message); + } } public void Dispose() @@ -162,5 +178,35 @@ namespace DotNetCore.CAP.AmazonSQS } } } + + #region private methods + + private Task InvalidIdFormatLog(string exceptionMessage) + { + var logArgs = new LogMessageEventArgs + { + LogType = MqLogType.InvalidIdFormat, + Reason = exceptionMessage + }; + + OnLog?.Invoke(null, logArgs); + + return Task.CompletedTask; + } + + private Task MessageNotInflightLog(string exceptionMessage) + { + var logArgs = new LogMessageEventArgs + { + LogType = MqLogType.MessageNotInflight, + Reason = exceptionMessage + }; + + OnLog?.Invoke(null, logArgs); + + return Task.CompletedTask; + } + + #endregion } } \ No newline at end of file diff --git a/src/DotNetCore.CAP.AmazonSQS/TopicNormalizer.cs b/src/DotNetCore.CAP.AmazonSQS/TopicNormalizer.cs index f08c481..622fc2e 100644 --- a/src/DotNetCore.CAP.AmazonSQS/TopicNormalizer.cs +++ b/src/DotNetCore.CAP.AmazonSQS/TopicNormalizer.cs @@ -2,7 +2,7 @@ namespace DotNetCore.CAP.AmazonSQS { - public static class TopicNormalizer + internal static class TopicNormalizer { public static string NormalizeForAws(this string origin) { diff --git a/src/DotNetCore.CAP/Internal/IConsumerRegister.Default.cs b/src/DotNetCore.CAP/Internal/IConsumerRegister.Default.cs index 7e32a68..65cb999 100644 --- a/src/DotNetCore.CAP/Internal/IConsumerRegister.Default.cs +++ b/src/DotNetCore.CAP/Internal/IConsumerRegister.Default.cs @@ -271,6 +271,12 @@ namespace DotNetCore.CAP.Internal case MqLogType.ExceptionReceived: _logger.LogError("AzureServiceBus subscriber received an error. --> " + logmsg.Reason); break; + case MqLogType.InvalidIdFormat: + _logger.LogError("AmazonSQS subscriber delete inflight message failed, invalid id. --> " + logmsg.Reason); + break; + case MqLogType.MessageNotInflight: + _logger.LogError("AmazonSQS subscriber change message's visibility failed, message isn't in flight. --> " + logmsg.Reason); + break; default: throw new ArgumentOutOfRangeException(); } diff --git a/src/DotNetCore.CAP/Transport/MqLogType.cs b/src/DotNetCore.CAP/Transport/MqLogType.cs index 3412b3a..659e004 100644 --- a/src/DotNetCore.CAP/Transport/MqLogType.cs +++ b/src/DotNetCore.CAP/Transport/MqLogType.cs @@ -18,7 +18,11 @@ namespace DotNetCore.CAP.Transport ServerConnError, //AzureServiceBus - ExceptionReceived + ExceptionReceived, + + //Amazon SQS + InvalidIdFormat, + MessageNotInflight } public class LogMessageEventArgs : EventArgs From ae471b4cae14ccad55f9177c37c5c5284aae1781 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Mon, 20 Jul 2020 23:21:46 +0800 Subject: [PATCH 19/85] Add AmazonSQS samples --- CAP.sln | 14 +++++++------- .../Controllers/ValuesController.cs | 8 ++++---- .../Program.cs | 2 +- .../Sample.AmazonSQS.InMemory.csproj} | 2 +- .../Startup.cs | 7 ++++--- .../appsettings.json | 2 +- 6 files changed, 18 insertions(+), 17 deletions(-) rename samples/{Sample.Kafka.InMemory => Sample.AmazonSQS.InMemory}/Controllers/ValuesController.cs (72%) rename samples/{Sample.Kafka.InMemory => Sample.AmazonSQS.InMemory}/Program.cs (93%) rename samples/{Sample.Kafka.InMemory/Sample.Kafka.InMemory.csproj => Sample.AmazonSQS.InMemory/Sample.AmazonSQS.InMemory.csproj} (82%) rename samples/{Sample.Kafka.InMemory => Sample.AmazonSQS.InMemory}/Startup.cs (79%) rename samples/{Sample.Kafka.InMemory => Sample.AmazonSQS.InMemory}/appsettings.json (75%) diff --git a/CAP.sln b/CAP.sln index c494fab..6e4e4ec 100644 --- a/CAP.sln +++ b/CAP.sln @@ -51,8 +51,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.AzureService EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.Dashboard", "src\DotNetCore.CAP.Dashboard\DotNetCore.CAP.Dashboard.csproj", "{56FB261C-67AF-4715-9A46-4FA4FAB91B2C}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.Kafka.InMemory", "samples\Sample.Kafka.InMemory\Sample.Kafka.InMemory.csproj", "{1B0371D6-36A4-4C78-A727-8ED732FDBA1D}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.RabbitMQ.SqlServer", "samples\Sample.RabbitMQ.SqlServer\Sample.RabbitMQ.SqlServer.csproj", "{F6C5C676-AF05-46D5-A45D-442137B31898}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.Kafka.PostgreSql", "samples\Sample.Kafka.PostgreSql\Sample.Kafka.PostgreSql.csproj", "{F1EF1D26-8A6B-403E-85B0-250DF44A4A7C}" @@ -67,6 +65,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.ConsoleApp", "sample EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.CAP.AmazonSQS", "src\DotNetCore.CAP.AmazonSQS\DotNetCore.CAP.AmazonSQS.csproj", "{43475E00-51B7-443D-BC2D-FC21F9D8A0B4}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.AmazonSQS.InMemory", "samples\Sample.AmazonSQS.InMemory\Sample.AmazonSQS.InMemory.csproj", "{B187DD15-092D-4B72-9807-50856607D237}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -121,10 +121,6 @@ Global {56FB261C-67AF-4715-9A46-4FA4FAB91B2C}.Debug|Any CPU.Build.0 = Debug|Any CPU {56FB261C-67AF-4715-9A46-4FA4FAB91B2C}.Release|Any CPU.ActiveCfg = Release|Any CPU {56FB261C-67AF-4715-9A46-4FA4FAB91B2C}.Release|Any CPU.Build.0 = Release|Any CPU - {1B0371D6-36A4-4C78-A727-8ED732FDBA1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1B0371D6-36A4-4C78-A727-8ED732FDBA1D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1B0371D6-36A4-4C78-A727-8ED732FDBA1D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1B0371D6-36A4-4C78-A727-8ED732FDBA1D}.Release|Any CPU.Build.0 = Release|Any CPU {F6C5C676-AF05-46D5-A45D-442137B31898}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F6C5C676-AF05-46D5-A45D-442137B31898}.Debug|Any CPU.Build.0 = Debug|Any CPU {F6C5C676-AF05-46D5-A45D-442137B31898}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -153,6 +149,10 @@ Global {43475E00-51B7-443D-BC2D-FC21F9D8A0B4}.Debug|Any CPU.Build.0 = Debug|Any CPU {43475E00-51B7-443D-BC2D-FC21F9D8A0B4}.Release|Any CPU.ActiveCfg = Release|Any CPU {43475E00-51B7-443D-BC2D-FC21F9D8A0B4}.Release|Any CPU.Build.0 = Release|Any CPU + {B187DD15-092D-4B72-9807-50856607D237}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B187DD15-092D-4B72-9807-50856607D237}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B187DD15-092D-4B72-9807-50856607D237}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B187DD15-092D-4B72-9807-50856607D237}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -170,7 +170,6 @@ Global {4473DE19-E8D2-4B57-80A8-C8AAA2BFA20F} = {3A6B6931-A123-477A-9469-8B468B5385AF} {63B2A464-FBEA-42FB-8EFA-98AFA39FC920} = {9B2AE124-6636-4DE9-83A3-70360DABD0C4} {56FB261C-67AF-4715-9A46-4FA4FAB91B2C} = {9B2AE124-6636-4DE9-83A3-70360DABD0C4} - {1B0371D6-36A4-4C78-A727-8ED732FDBA1D} = {3A6B6931-A123-477A-9469-8B468B5385AF} {F6C5C676-AF05-46D5-A45D-442137B31898} = {3A6B6931-A123-477A-9469-8B468B5385AF} {F1EF1D26-8A6B-403E-85B0-250DF44A4A7C} = {3A6B6931-A123-477A-9469-8B468B5385AF} {F8EF381A-FE83-40B3-A63D-09D83851B0FB} = {10C0818D-9160-4B80-BB86-DDE925B64D43} @@ -178,6 +177,7 @@ Global {75CC45E6-BF06-40F4-977D-10DCC05B2EFA} = {C09CDAB0-6DD4-46E9-B7F3-3EF2A4741EA0} {2B0F467E-ABBD-4A51-BF38-D4F609DB6266} = {3A6B6931-A123-477A-9469-8B468B5385AF} {43475E00-51B7-443D-BC2D-FC21F9D8A0B4} = {9B2AE124-6636-4DE9-83A3-70360DABD0C4} + {B187DD15-092D-4B72-9807-50856607D237} = {3A6B6931-A123-477A-9469-8B468B5385AF} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {2E70565D-94CF-40B4-BFE1-AC18D5F736AB} diff --git a/samples/Sample.Kafka.InMemory/Controllers/ValuesController.cs b/samples/Sample.AmazonSQS.InMemory/Controllers/ValuesController.cs similarity index 72% rename from samples/Sample.Kafka.InMemory/Controllers/ValuesController.cs rename to samples/Sample.AmazonSQS.InMemory/Controllers/ValuesController.cs index b382b41..e557d7e 100644 --- a/samples/Sample.Kafka.InMemory/Controllers/ValuesController.cs +++ b/samples/Sample.AmazonSQS.InMemory/Controllers/ValuesController.cs @@ -3,7 +3,7 @@ using System.Threading.Tasks; using DotNetCore.CAP; using Microsoft.AspNetCore.Mvc; -namespace Sample.Kafka.InMemory.Controllers +namespace Sample.AmazonSQS.InMemory.Controllers { [Route("api/[controller]")] public class ValuesController : Controller, ICapSubscribe @@ -18,13 +18,13 @@ namespace Sample.Kafka.InMemory.Controllers [Route("~/without/transaction")] public async Task WithoutTransaction() { - await _capBus.PublishAsync("sample.azure.mysql2", DateTime.Now); + await _capBus.PublishAsync("sample.aws.in-memory", DateTime.Now); return Ok(); } - [CapSubscribe("sample.azure.mysql2")] - public void Test2T2(DateTime value) + [CapSubscribe("sample.aws.in-memory")] + public void SubscribeInMemoryTopic(DateTime value) { Console.WriteLine("Subscriber output message: " + value); } diff --git a/samples/Sample.Kafka.InMemory/Program.cs b/samples/Sample.AmazonSQS.InMemory/Program.cs similarity index 93% rename from samples/Sample.Kafka.InMemory/Program.cs rename to samples/Sample.AmazonSQS.InMemory/Program.cs index b0af402..5767a5e 100644 --- a/samples/Sample.Kafka.InMemory/Program.cs +++ b/samples/Sample.AmazonSQS.InMemory/Program.cs @@ -1,7 +1,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Hosting; -namespace Sample.Kafka.InMemory +namespace Sample.AmazonSQS.InMemory { public class Program { diff --git a/samples/Sample.Kafka.InMemory/Sample.Kafka.InMemory.csproj b/samples/Sample.AmazonSQS.InMemory/Sample.AmazonSQS.InMemory.csproj similarity index 82% rename from samples/Sample.Kafka.InMemory/Sample.Kafka.InMemory.csproj rename to samples/Sample.AmazonSQS.InMemory/Sample.AmazonSQS.InMemory.csproj index 0cde14f..c8ed0b3 100644 --- a/samples/Sample.Kafka.InMemory/Sample.Kafka.InMemory.csproj +++ b/samples/Sample.AmazonSQS.InMemory/Sample.AmazonSQS.InMemory.csproj @@ -5,9 +5,9 @@ + - diff --git a/samples/Sample.Kafka.InMemory/Startup.cs b/samples/Sample.AmazonSQS.InMemory/Startup.cs similarity index 79% rename from samples/Sample.Kafka.InMemory/Startup.cs rename to samples/Sample.AmazonSQS.InMemory/Startup.cs index ae3ee2a..f01411c 100644 --- a/samples/Sample.Kafka.InMemory/Startup.cs +++ b/samples/Sample.AmazonSQS.InMemory/Startup.cs @@ -1,7 +1,8 @@ -using Microsoft.AspNetCore.Builder; +using Amazon; +using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; -namespace Sample.Kafka.InMemory +namespace Sample.AmazonSQS.InMemory { public class Startup { @@ -10,7 +11,7 @@ namespace Sample.Kafka.InMemory services.AddCap(x => { x.UseInMemoryStorage(); - x.UseKafka("localhost:9092"); + x.UseAmazonSQS(RegionEndpoint.CNNorthWest1); x.UseDashboard(); }); diff --git a/samples/Sample.Kafka.InMemory/appsettings.json b/samples/Sample.AmazonSQS.InMemory/appsettings.json similarity index 75% rename from samples/Sample.Kafka.InMemory/appsettings.json rename to samples/Sample.AmazonSQS.InMemory/appsettings.json index 20aa907..50fe9a3 100644 --- a/samples/Sample.Kafka.InMemory/appsettings.json +++ b/samples/Sample.AmazonSQS.InMemory/appsettings.json @@ -2,7 +2,7 @@ "Logging": { "IncludeScopes": false, "LogLevel": { - "Default": "Debug" + "Default": "Error" } } } From 3f2814f186371427a4ed47a8763e3b721520a76c Mon Sep 17 00:00:00 2001 From: Shane Rogers Date: Thu, 23 Jul 2020 16:38:57 +1200 Subject: [PATCH 20/85] Updated mongo db query. (#611) Updated query as it referenced the wrong collection as a result after 3 retries the old messages are never processed (<4min) --- src/DotNetCore.CAP.MongoDB/IDataStorage.MongoDB.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/DotNetCore.CAP.MongoDB/IDataStorage.MongoDB.cs b/src/DotNetCore.CAP.MongoDB/IDataStorage.MongoDB.cs index e3e9df1..9275b31 100644 --- a/src/DotNetCore.CAP.MongoDB/IDataStorage.MongoDB.cs +++ b/src/DotNetCore.CAP.MongoDB/IDataStorage.MongoDB.cs @@ -27,8 +27,7 @@ namespace DotNetCore.CAP.MongoDB public MongoDBDataStorage( IOptions capOptions, IOptions options, - IMongoClient client, - ILogger logger) + IMongoClient client) { _capOptions = capOptions; _options = options; @@ -194,7 +193,7 @@ namespace DotNetCore.CAP.MongoDB public async Task> GetReceivedMessagesOfNeedRetry() { var fourMinAgo = DateTime.Now.AddMinutes(-4); - var collection = _database.GetCollection(_options.Value.PublishedCollection); + var collection = _database.GetCollection(_options.Value.ReceivedCollection); var queryResult = await collection .Find(x => x.Retries < _capOptions.Value.FailedRetryCount && x.Added < fourMinAgo @@ -217,4 +216,4 @@ namespace DotNetCore.CAP.MongoDB return new MongoDBMonitoringApi(_client, _options); } } -} \ No newline at end of file +} From 847899342e94b3e9904cd42c5c026c468c2de6eb Mon Sep 17 00:00:00 2001 From: Savorboard Date: Wed, 29 Jul 2020 16:36:59 +0800 Subject: [PATCH 21/85] Add file license header --- ...ConnectionExtensions.cs => IDbConnection.Extensions.cs} | 7 +++++-- ...ConnectionExtensions.cs => IDbConnection.Extensions.cs} | 7 +++++-- ...ConnectionExtensions.cs => IDbConnection.Extensions.cs} | 7 +++++-- 3 files changed, 15 insertions(+), 6 deletions(-) rename src/DotNetCore.CAP.MySql/{IDbConnectionExtensions.cs => IDbConnection.Extensions.cs} (92%) rename src/DotNetCore.CAP.PostgreSql/{IDbConnectionExtensions.cs => IDbConnection.Extensions.cs} (92%) rename src/DotNetCore.CAP.SqlServer/{IDbConnectionExtensions.cs => IDbConnection.Extensions.cs} (92%) diff --git a/src/DotNetCore.CAP.MySql/IDbConnectionExtensions.cs b/src/DotNetCore.CAP.MySql/IDbConnection.Extensions.cs similarity index 92% rename from src/DotNetCore.CAP.MySql/IDbConnectionExtensions.cs rename to src/DotNetCore.CAP.MySql/IDbConnection.Extensions.cs index e75527f..f203025 100644 --- a/src/DotNetCore.CAP.MySql/IDbConnectionExtensions.cs +++ b/src/DotNetCore.CAP.MySql/IDbConnection.Extensions.cs @@ -1,10 +1,13 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.ComponentModel; using System.Data; namespace DotNetCore.CAP.MySql { - internal static class IDbConnectionExtensions + internal static class DbConnectionExtensions { public static int ExecuteNonQuery(this IDbConnection connection, string sql, IDbTransaction transaction = null, params object[] sqlParams) diff --git a/src/DotNetCore.CAP.PostgreSql/IDbConnectionExtensions.cs b/src/DotNetCore.CAP.PostgreSql/IDbConnection.Extensions.cs similarity index 92% rename from src/DotNetCore.CAP.PostgreSql/IDbConnectionExtensions.cs rename to src/DotNetCore.CAP.PostgreSql/IDbConnection.Extensions.cs index b94f7f4..0a5ac1f 100644 --- a/src/DotNetCore.CAP.PostgreSql/IDbConnectionExtensions.cs +++ b/src/DotNetCore.CAP.PostgreSql/IDbConnection.Extensions.cs @@ -1,10 +1,13 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.ComponentModel; using System.Data; namespace DotNetCore.CAP.PostgreSql { - internal static class IDbConnectionExtensions + internal static class DbConnectionExtensions { public static int ExecuteNonQuery(this IDbConnection connection, string sql, IDbTransaction transaction = null, params object[] sqlParams) diff --git a/src/DotNetCore.CAP.SqlServer/IDbConnectionExtensions.cs b/src/DotNetCore.CAP.SqlServer/IDbConnection.Extensions.cs similarity index 92% rename from src/DotNetCore.CAP.SqlServer/IDbConnectionExtensions.cs rename to src/DotNetCore.CAP.SqlServer/IDbConnection.Extensions.cs index 0853ff3..ba7dbce 100644 --- a/src/DotNetCore.CAP.SqlServer/IDbConnectionExtensions.cs +++ b/src/DotNetCore.CAP.SqlServer/IDbConnection.Extensions.cs @@ -1,10 +1,13 @@ -using System; +// Copyright (c) .NET Core Community. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.ComponentModel; using System.Data; namespace DotNetCore.CAP.SqlServer { - internal static class IDbConnectionExtensions + internal static class DbConnectionExtensions { public static int ExecuteNonQuery(this IDbConnection connection, string sql, IDbTransaction transaction = null, params object[] sqlParams) From 0d603a7bdeb0eb605027430615479155a3cbbf95 Mon Sep 17 00:00:00 2001 From: Pascal Slegtenhorst Date: Thu, 30 Jul 2020 03:18:18 +0200 Subject: [PATCH 22/85] partial topic attributes (#617) * Added support for defining topic attribute partials * Updated readme * Small improvements to partial topic implementation. Co-authored-by: Pascal Slegtenhorst --- README.md | 15 +++++++ .../Pages/SubscriberPage.cshtml | 2 +- .../Pages/SubscriberPage.generated.cs | 2 +- src/DotNetCore.CAP/CAP.Attribute.cs | 14 +++---- .../Internal/ConsumerExecutorDescriptor.cs | 26 ++++++++++++ .../Internal/IConsumerRegister.Default.cs | 2 +- .../IConsumerServiceSelector.Default.cs | 40 ++++++++++--------- src/DotNetCore.CAP/Internal/TopicAttribute.cs | 10 ++++- .../ConsumerServiceSelectorTest.cs | 28 ++++++++++--- 9 files changed, 104 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 66586f3..540b68d 100644 --- a/README.md +++ b/README.md @@ -187,6 +187,21 @@ public void ConfigureServices(IServiceCollection services) }); } ``` +#### Use partials for topic subscriptions + +To group topic subscriptions on class level you're able to define a subscription on a method as a partial. Subscriptions on the message queue will then be a combination of the topic defined on the class and the topic defined on the method. In the following example the `Create(..)` function will be invoked when receiving a message on `customers.create` + +```c# +[CapSubscribe("customers")] +public class CustomersSubscriberService : ICapSubscribe +{ + [CapSubscribe("create", isPartial: true)] + public void Create(Customer customer) + { + } +} +``` + #### Subscribe Group diff --git a/src/DotNetCore.CAP.Dashboard/Pages/SubscriberPage.cshtml b/src/DotNetCore.CAP.Dashboard/Pages/SubscriberPage.cshtml index 0b6ba0d..3bd479e 100644 --- a/src/DotNetCore.CAP.Dashboard/Pages/SubscriberPage.cshtml +++ b/src/DotNetCore.CAP.Dashboard/Pages/SubscriberPage.cshtml @@ -45,7 +45,7 @@ { @subscriber.Key } - @column.Attribute.Name + @column.TopicName @column.ImplTypeInfo.Name
diff --git a/src/DotNetCore.CAP.Dashboard/Pages/SubscriberPage.generated.cs b/src/DotNetCore.CAP.Dashboard/Pages/SubscriberPage.generated.cs index b243943..3f05369 100644 --- a/src/DotNetCore.CAP.Dashboard/Pages/SubscriberPage.generated.cs +++ b/src/DotNetCore.CAP.Dashboard/Pages/SubscriberPage.generated.cs @@ -200,7 +200,7 @@ WriteLiteral(" "); #line 48 "..\..\Pages\SubscriberPage.cshtml" - Write(column.Attribute.Name); + Write(column.TopicName); #line default diff --git a/src/DotNetCore.CAP/CAP.Attribute.cs b/src/DotNetCore.CAP/CAP.Attribute.cs index 40092a5..d77df33 100644 --- a/src/DotNetCore.CAP/CAP.Attribute.cs +++ b/src/DotNetCore.CAP/CAP.Attribute.cs @@ -10,13 +10,13 @@ using DotNetCore.CAP.Internal; namespace DotNetCore.CAP { public class CapSubscribeAttribute : TopicAttribute - { - public CapSubscribeAttribute(string name) - : base(name) - { - - } - + { + public CapSubscribeAttribute(string name, bool isPartial = false) + : base(name, isPartial) + { + + } + public override string ToString() { return Name; diff --git a/src/DotNetCore.CAP/Internal/ConsumerExecutorDescriptor.cs b/src/DotNetCore.CAP/Internal/ConsumerExecutorDescriptor.cs index b7a0f8f..7a5cc0a 100644 --- a/src/DotNetCore.CAP/Internal/ConsumerExecutorDescriptor.cs +++ b/src/DotNetCore.CAP/Internal/ConsumerExecutorDescriptor.cs @@ -20,7 +20,33 @@ namespace DotNetCore.CAP.Internal public TopicAttribute Attribute { get; set; } + public TopicAttribute ClassAttribute { get; set; } + public IList Parameters { get; set; } + + private string _topicName; + /// + /// Topic name based on both and . + /// + public string TopicName + { + get + { + if (_topicName == null) + { + if (ClassAttribute != null && Attribute.IsPartial) + { + // Allows class level attribute name to end with a '.' and allows methods level attribute to start with a '.'. + _topicName = $"{ClassAttribute.Name.TrimEnd('.')}.{Attribute.Name.TrimStart('.')}"; + } + else + { + _topicName = Attribute.Name; + } + } + return _topicName; + } + } } public class ParameterDescriptor diff --git a/src/DotNetCore.CAP/Internal/IConsumerRegister.Default.cs b/src/DotNetCore.CAP/Internal/IConsumerRegister.Default.cs index 65cb999..c41c96d 100644 --- a/src/DotNetCore.CAP/Internal/IConsumerRegister.Default.cs +++ b/src/DotNetCore.CAP/Internal/IConsumerRegister.Default.cs @@ -79,7 +79,7 @@ namespace DotNetCore.CAP.Internal RegisterMessageProcessor(client); - client.Subscribe(matchGroup.Value.Select(x => x.Attribute.Name)); + client.Subscribe(matchGroup.Value.Select(x => x.TopicName)); client.Listening(_pollingDelay, _cts.Token); } diff --git a/src/DotNetCore.CAP/Internal/IConsumerServiceSelector.Default.cs b/src/DotNetCore.CAP/Internal/IConsumerServiceSelector.Default.cs index f77ed53..f8e278c 100644 --- a/src/DotNetCore.CAP/Internal/IConsumerServiceSelector.Default.cs +++ b/src/DotNetCore.CAP/Internal/IConsumerServiceSelector.Default.cs @@ -116,17 +116,24 @@ namespace DotNetCore.CAP.Internal protected IEnumerable GetTopicAttributesDescription(TypeInfo typeInfo, TypeInfo serviceTypeInfo = null) { + var topicClassAttribute = typeInfo.GetCustomAttribute(true); + foreach (var method in typeInfo.DeclaredMethods) { - var topicAttr = method.GetCustomAttributes(true); - var topicAttributes = topicAttr as IList ?? topicAttr.ToList(); + var topicMethodAttributes = method.GetCustomAttributes(true); + + // Ignore partial attributes when no topic attribute is defined on class. + if (topicClassAttribute is null) + { + topicMethodAttributes = topicMethodAttributes.Where(x => x.IsPartial); + } - if (!topicAttributes.Any()) + if (!topicMethodAttributes.Any()) { continue; } - foreach (var attr in topicAttributes) + foreach (var attr in topicMethodAttributes) { SetSubscribeAttribute(attr); @@ -138,21 +145,14 @@ namespace DotNetCore.CAP.Internal IsFromCap = parameter.GetCustomAttributes(typeof(FromCapAttribute)).Any() }).ToList(); - yield return InitDescriptor(attr, method, typeInfo, serviceTypeInfo, parameters); + yield return InitDescriptor(attr, method, typeInfo, serviceTypeInfo, parameters, topicClassAttribute); } } } protected virtual void SetSubscribeAttribute(TopicAttribute attribute) { - if (attribute.Group == null) - { - attribute.Group = _capOptions.DefaultGroup + "." + _capOptions.Version; - } - else - { - attribute.Group = attribute.Group + "." + _capOptions.Version; - } + attribute.Group = (attribute.Group ?? _capOptions.DefaultGroup) + "." + _capOptions.Version; } private static ConsumerExecutorDescriptor InitDescriptor( @@ -160,11 +160,13 @@ namespace DotNetCore.CAP.Internal MethodInfo methodInfo, TypeInfo implType, TypeInfo serviceTypeInfo, - IList parameters) + IList parameters, + TopicAttribute classAttr = null) { var descriptor = new ConsumerExecutorDescriptor { Attribute = attr, + ClassAttribute = classAttr, MethodInfo = methodInfo, ImplTypeInfo = implType, ServiceTypeInfo = serviceTypeInfo, @@ -176,7 +178,7 @@ namespace DotNetCore.CAP.Internal private ConsumerExecutorDescriptor MatchUsingName(string key, IReadOnlyList executeDescriptor) { - return executeDescriptor.FirstOrDefault(x => x.Attribute.Name.Equals(key, StringComparison.InvariantCultureIgnoreCase)); + return executeDescriptor.FirstOrDefault(x => x.TopicName.Equals(key, StringComparison.InvariantCultureIgnoreCase)); } private ConsumerExecutorDescriptor MatchAsteriskUsingRegex(string key, IReadOnlyList executeDescriptor) @@ -184,10 +186,10 @@ namespace DotNetCore.CAP.Internal var group = executeDescriptor.First().Attribute.Group; if (!_asteriskList.TryGetValue(group, out var tmpList)) { - tmpList = executeDescriptor.Where(x => x.Attribute.Name.IndexOf('*') >= 0) + tmpList = executeDescriptor.Where(x => x.TopicName.IndexOf('*') >= 0) .Select(x => new RegexExecuteDescriptor { - Name = ("^" + x.Attribute.Name + "$").Replace("*", "[0-9_a-zA-Z]+").Replace(".", "\\."), + Name = ("^" + x.TopicName + "$").Replace("*", "[0-9_a-zA-Z]+").Replace(".", "\\."), Descriptor = x }).ToList(); _asteriskList.TryAdd(group, tmpList); @@ -210,10 +212,10 @@ namespace DotNetCore.CAP.Internal if (!_poundList.TryGetValue(group, out var tmpList)) { tmpList = executeDescriptor - .Where(x => x.Attribute.Name.IndexOf('#') >= 0) + .Where(x => x.TopicName.IndexOf('#') >= 0) .Select(x => new RegexExecuteDescriptor { - Name = ("^" + x.Attribute.Name.Replace(".", "\\.") + "$").Replace("#", "[0-9_a-zA-Z\\.]+"), + Name = ("^" + x.TopicName.Replace(".", "\\.") + "$").Replace("#", "[0-9_a-zA-Z\\.]+"), Descriptor = x }).ToList(); _poundList.TryAdd(group, tmpList); diff --git a/src/DotNetCore.CAP/Internal/TopicAttribute.cs b/src/DotNetCore.CAP/Internal/TopicAttribute.cs index 559cd5f..9c0fca9 100644 --- a/src/DotNetCore.CAP/Internal/TopicAttribute.cs +++ b/src/DotNetCore.CAP/Internal/TopicAttribute.cs @@ -12,9 +12,10 @@ namespace DotNetCore.CAP.Internal [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)] public abstract class TopicAttribute : Attribute { - protected TopicAttribute(string name) + protected TopicAttribute(string name, bool isPartial = false) { Name = name; + IsPartial = isPartial; } /// @@ -22,6 +23,13 @@ namespace DotNetCore.CAP.Internal /// public string Name { get; } + /// + /// Defines wether this attribute defines a topic subscription partial. + /// The defined topic will be combined with a topic subscription defined on class level, + /// which results for example in subscription on "class.method". + /// + public bool IsPartial { get; } + /// /// Default group name is CapOptions setting.(Assembly name) /// kafka --> groups.id diff --git a/test/DotNetCore.CAP.Test/ConsumerServiceSelectorTest.cs b/test/DotNetCore.CAP.Test/ConsumerServiceSelectorTest.cs index 59f231d..bb9dcdb 100644 --- a/test/DotNetCore.CAP.Test/ConsumerServiceSelectorTest.cs +++ b/test/DotNetCore.CAP.Test/ConsumerServiceSelectorTest.cs @@ -29,15 +29,18 @@ namespace DotNetCore.CAP.Test var selector = _provider.GetRequiredService(); var candidates = selector.SelectCandidates(); - Assert.Equal(6, candidates.Count); + Assert.Equal(8, candidates.Count); } - [Fact] - public void CanFindSpecifiedTopic() + [Theory] + [InlineData("Candidates.Foo")] + [InlineData("Candidates.Foo3")] + [InlineData("Candidates.Foo4")] + public void CanFindSpecifiedTopic(string topic) { var selector = _provider.GetRequiredService(); var candidates = selector.SelectCandidates(); - var bestCandidates = selector.SelectBestCandidate("Candidates.Foo", candidates); + var bestCandidates = selector.SelectBestCandidate(topic, candidates); Assert.NotNull(bestCandidates); Assert.NotNull(bestCandidates.MethodInfo); @@ -116,7 +119,7 @@ namespace DotNetCore.CAP.Test public class CandidatesTopic : TopicAttribute { - public CandidatesTopic(string topicName) : base(topicName) + public CandidatesTopic(string topicName, bool isPartial = false) : base(topicName, isPartial) { } } @@ -129,6 +132,7 @@ namespace DotNetCore.CAP.Test { } + [CandidatesTopic("Candidates")] public class CandidatesFooTest : IFooTest, ICapSubscribe { [CandidatesTopic("Candidates.Foo")] @@ -144,6 +148,20 @@ namespace DotNetCore.CAP.Test Console.WriteLine("GetFoo2() method has bee excuted."); } + [CandidatesTopic("Foo3", isPartial: true)] + public Task GetFoo3() + { + Console.WriteLine("GetFoo3() method has bee excuted."); + return Task.CompletedTask; + } + + [CandidatesTopic(".Foo4", isPartial: true)] + public Task GetFoo4() + { + Console.WriteLine("GetFoo4() method has bee excuted."); + return Task.CompletedTask; + } + [CandidatesTopic("*.*.Asterisk")] [CandidatesTopic("*.Asterisk")] public void GetFooAsterisk() From 7e5210ed6a32ce81155c4eb88752ea21e39e6fa5 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Fri, 31 Jul 2020 20:19:10 +0800 Subject: [PATCH 23/85] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 540b68d..f1bfd74 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@

-# CAP                       [中文](https://github.com/dotnetcore/CAP/blob/master/README.zh-cn.md) +# CAP                     [中文](https://github.com/dotnetcore/CAP/blob/master/README.zh-cn.md) [![Travis branch](https://img.shields.io/travis/dotnetcore/CAP/master.svg?label=travis-ci)](https://travis-ci.org/dotnetcore/CAP) [![AppVeyor](https://ci.appveyor.com/api/projects/status/v8gfh6pe2u2laqoa/branch/master?svg=true)](https://ci.appveyor.com/project/yang-xiaodong/cap/branch/master) [![NuGet](https://img.shields.io/nuget/v/DotNetCore.CAP.svg)](https://www.nuget.org/packages/DotNetCore.CAP/) From 92eed9e7d5d33b314a111e45593ebfec7e486b2c Mon Sep 17 00:00:00 2001 From: Johan Date: Tue, 4 Aug 2020 18:33:26 +0800 Subject: [PATCH 24/85] Update README.zh-cn.md (#620) --- README.zh-cn.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.zh-cn.md b/README.zh-cn.md index f0e771f..b5efb90 100644 --- a/README.zh-cn.md +++ b/README.zh-cn.md @@ -174,7 +174,7 @@ namespace xxx.Service { public interface ISubscriberService { - public void CheckReceivedMessage(DateTime datetime); + void CheckReceivedMessage(DateTime datetime); } public class SubscriberService: ISubscriberService, ICapSubscribe From f773714574e8c0800362832c862599f13e2ed14b Mon Sep 17 00:00:00 2001 From: Johan Date: Wed, 5 Aug 2020 18:08:16 +0800 Subject: [PATCH 25/85] Update README.md (#621) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f1bfd74..7949dd2 100644 --- a/README.md +++ b/README.md @@ -159,7 +159,7 @@ namespace BusinessCode.Service { public interface ISubscriberService { - public void CheckReceivedMessage(DateTime datetime); + void CheckReceivedMessage(DateTime datetime); } public class SubscriberService: ISubscriberService, ICapSubscribe From 110bdbe73bf8a0b805e7cf71b536ca54720f39d5 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Thu, 6 Aug 2020 11:48:07 +0800 Subject: [PATCH 26/85] Fix docs. #623 --- docs/content/user-guide/en/cap/messaging.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/user-guide/en/cap/messaging.md b/docs/content/user-guide/en/cap/messaging.md index 770d898..169549b 100644 --- a/docs/content/user-guide/en/cap/messaging.md +++ b/docs/content/user-guide/en/cap/messaging.md @@ -32,7 +32,7 @@ The consumer method is executed when the Consumer receives the message and will ## Data Cleanup -There is an `ExpiresAt` field in the database message table indicating the expiration time of the message. When the message is sent successfully, status will be changed to `Successed`, and `ExpiresAt` will be set to **1 hour** later. +There is an `ExpiresAt` field in the database message table indicating the expiration time of the message. When the message is sent successfully, status will be changed to `Successed`, and `ExpiresAt` will be set to **1 day** later. Consuming failure will change the message status to `Failed` and `ExpiresAt` will be set to **15 days** later. From d029b6c30db679e1b83b839fca866f164094af3b Mon Sep 17 00:00:00 2001 From: mHalo Date: Thu, 6 Aug 2020 12:53:04 +0800 Subject: [PATCH 27/85] fixed #622 #624 (#625) --- src/DotNetCore.CAP.PostgreSql/IMonitoringApi.PostgreSql.cs | 2 +- src/DotNetCore.CAP/Internal/IConsumerServiceSelector.Default.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DotNetCore.CAP.PostgreSql/IMonitoringApi.PostgreSql.cs b/src/DotNetCore.CAP.PostgreSql/IMonitoringApi.PostgreSql.cs index 9cae360..ed5ee54 100644 --- a/src/DotNetCore.CAP.PostgreSql/IMonitoringApi.PostgreSql.cs +++ b/src/DotNetCore.CAP.PostgreSql/IMonitoringApi.PostgreSql.cs @@ -108,8 +108,8 @@ namespace DotNetCore.CAP.PostgreSql { Id = reader.GetInt64(index++), Version = reader.GetString(index++), - Group = queryDto.MessageType == MessageType.Subscribe ? reader.GetString(index++) : default, Name = reader.GetString(index++), + Group = queryDto.MessageType == MessageType.Subscribe ? reader.GetString(index++) : default, Content = reader.GetString(index++), Retries = reader.GetInt32(index++), Added = reader.GetDateTime(index++), diff --git a/src/DotNetCore.CAP/Internal/IConsumerServiceSelector.Default.cs b/src/DotNetCore.CAP/Internal/IConsumerServiceSelector.Default.cs index f8e278c..eedf675 100644 --- a/src/DotNetCore.CAP/Internal/IConsumerServiceSelector.Default.cs +++ b/src/DotNetCore.CAP/Internal/IConsumerServiceSelector.Default.cs @@ -125,7 +125,7 @@ namespace DotNetCore.CAP.Internal // Ignore partial attributes when no topic attribute is defined on class. if (topicClassAttribute is null) { - topicMethodAttributes = topicMethodAttributes.Where(x => x.IsPartial); + topicMethodAttributes = topicMethodAttributes.Where(x => !x.IsPartial); } if (!topicMethodAttributes.Any()) From 079191b14bcd3a83881e192486de2a73ccd5f2af Mon Sep 17 00:00:00 2001 From: cBear Date: Fri, 7 Aug 2020 17:45:35 +0800 Subject: [PATCH 28/85] fix #624 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 接收列表中的分组和名称展示到了相反的位置 * 接收列表中的分组和名称展示到了相反的位置 --- src/DotNetCore.CAP.MySql/IMonitoringApi.MySql.cs | 4 ++-- src/DotNetCore.CAP.SqlServer/IMonitoringApi.SqlServer.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/DotNetCore.CAP.MySql/IMonitoringApi.MySql.cs b/src/DotNetCore.CAP.MySql/IMonitoringApi.MySql.cs index f4f68d0..106141f 100644 --- a/src/DotNetCore.CAP.MySql/IMonitoringApi.MySql.cs +++ b/src/DotNetCore.CAP.MySql/IMonitoringApi.MySql.cs @@ -124,8 +124,8 @@ SELECT { Id = reader.GetInt64(index++), Version = reader.GetString(index++), - Group = queryDto.MessageType == MessageType.Subscribe ? reader.GetString(index++) : default, Name = reader.GetString(index++), + Group = queryDto.MessageType == MessageType.Subscribe ? reader.GetString(index++) : default, Content = reader.GetString(index++), Retries = reader.GetInt32(index++), Added = reader.GetDateTime(index++), @@ -269,4 +269,4 @@ WHERE `Key` >= @minKey return mediumMessage; } } -} \ No newline at end of file +} diff --git a/src/DotNetCore.CAP.SqlServer/IMonitoringApi.SqlServer.cs b/src/DotNetCore.CAP.SqlServer/IMonitoringApi.SqlServer.cs index 0bbfc58..54dcacb 100644 --- a/src/DotNetCore.CAP.SqlServer/IMonitoringApi.SqlServer.cs +++ b/src/DotNetCore.CAP.SqlServer/IMonitoringApi.SqlServer.cs @@ -119,8 +119,8 @@ SELECT { Id = reader.GetInt64(index++), Version = reader.GetString(index++), - Group = queryDto.MessageType == MessageType.Subscribe ? reader.GetString(index++) : default, Name = reader.GetString(index++), + Group = queryDto.MessageType == MessageType.Subscribe ? reader.GetString(index++) : default, Content = reader.GetString(index++), Retries = reader.GetInt32(index++), Added = reader.GetDateTime(index++), @@ -271,4 +271,4 @@ select [Key], [Count] from aggr with (nolock) where [Key] >= @minKey and [Key] < return await Task.FromResult(mediumMessage); } } -} \ No newline at end of file +} From 1c3f70940f8a20d5e6b9c7ce7aa7f92bfc14ad73 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Wed, 12 Aug 2020 09:33:34 +0800 Subject: [PATCH 29/85] Upgrade dependencies NuGet package to latest --- samples/Sample.ConsoleApp/Sample.ConsoleApp.csproj | 2 +- .../Sample.RabbitMQ.SqlServer.csproj | 4 ++-- .../DotNetCore.CAP.AmazonSQS.csproj | 4 ++-- .../DotNetCore.CAP.AzureServiceBus.csproj | 2 +- .../DotNetCore.CAP.Dashboard.csproj | 4 ++-- src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj | 2 +- .../DotNetCore.CAP.MongoDB.csproj | 4 ++-- src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj | 4 ++-- .../DotNetCore.CAP.PostgreSql.csproj | 6 +++--- .../DotNetCore.CAP.SqlServer.csproj | 6 +++--- src/DotNetCore.CAP/DotNetCore.CAP.csproj | 4 ++-- .../DotNetCore.CAP.MySql.Test.csproj | 6 +++--- test/DotNetCore.CAP.Test/DotNetCore.CAP.Test.csproj | 12 ++++++------ 13 files changed, 30 insertions(+), 30 deletions(-) diff --git a/samples/Sample.ConsoleApp/Sample.ConsoleApp.csproj b/samples/Sample.ConsoleApp/Sample.ConsoleApp.csproj index b2c9c03..fa5e69e 100644 --- a/samples/Sample.ConsoleApp/Sample.ConsoleApp.csproj +++ b/samples/Sample.ConsoleApp/Sample.ConsoleApp.csproj @@ -6,7 +6,7 @@ - + diff --git a/samples/Sample.RabbitMQ.SqlServer/Sample.RabbitMQ.SqlServer.csproj b/samples/Sample.RabbitMQ.SqlServer/Sample.RabbitMQ.SqlServer.csproj index 69cae9b..e22c30f 100644 --- a/samples/Sample.RabbitMQ.SqlServer/Sample.RabbitMQ.SqlServer.csproj +++ b/samples/Sample.RabbitMQ.SqlServer/Sample.RabbitMQ.SqlServer.csproj @@ -6,11 +6,11 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/src/DotNetCore.CAP.AmazonSQS/DotNetCore.CAP.AmazonSQS.csproj b/src/DotNetCore.CAP.AmazonSQS/DotNetCore.CAP.AmazonSQS.csproj index 5a7116b..4b5f8bd 100644 --- a/src/DotNetCore.CAP.AmazonSQS/DotNetCore.CAP.AmazonSQS.csproj +++ b/src/DotNetCore.CAP.AmazonSQS/DotNetCore.CAP.AmazonSQS.csproj @@ -12,8 +12,8 @@ - - + + diff --git a/src/DotNetCore.CAP.AzureServiceBus/DotNetCore.CAP.AzureServiceBus.csproj b/src/DotNetCore.CAP.AzureServiceBus/DotNetCore.CAP.AzureServiceBus.csproj index f73f0fb..97d28ce 100644 --- a/src/DotNetCore.CAP.AzureServiceBus/DotNetCore.CAP.AzureServiceBus.csproj +++ b/src/DotNetCore.CAP.AzureServiceBus/DotNetCore.CAP.AzureServiceBus.csproj @@ -13,7 +13,7 @@ - + diff --git a/src/DotNetCore.CAP.Dashboard/DotNetCore.CAP.Dashboard.csproj b/src/DotNetCore.CAP.Dashboard/DotNetCore.CAP.Dashboard.csproj index 119c046..29ef3fb 100644 --- a/src/DotNetCore.CAP.Dashboard/DotNetCore.CAP.Dashboard.csproj +++ b/src/DotNetCore.CAP.Dashboard/DotNetCore.CAP.Dashboard.csproj @@ -40,8 +40,8 @@ - - + + diff --git a/src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj b/src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj index a63996c..a8b8951 100644 --- a/src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj +++ b/src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj @@ -13,7 +13,7 @@ - + diff --git a/src/DotNetCore.CAP.MongoDB/DotNetCore.CAP.MongoDB.csproj b/src/DotNetCore.CAP.MongoDB/DotNetCore.CAP.MongoDB.csproj index 8cf9628..fef7eae 100644 --- a/src/DotNetCore.CAP.MongoDB/DotNetCore.CAP.MongoDB.csproj +++ b/src/DotNetCore.CAP.MongoDB/DotNetCore.CAP.MongoDB.csproj @@ -16,8 +16,8 @@ - - + + diff --git a/src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj b/src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj index 423a542..023bfc1 100644 --- a/src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj +++ b/src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj @@ -12,8 +12,8 @@ - - + + diff --git a/src/DotNetCore.CAP.PostgreSql/DotNetCore.CAP.PostgreSql.csproj b/src/DotNetCore.CAP.PostgreSql/DotNetCore.CAP.PostgreSql.csproj index ab16a92..9c89d80 100644 --- a/src/DotNetCore.CAP.PostgreSql/DotNetCore.CAP.PostgreSql.csproj +++ b/src/DotNetCore.CAP.PostgreSql/DotNetCore.CAP.PostgreSql.csproj @@ -12,9 +12,9 @@ - - - + + + diff --git a/src/DotNetCore.CAP.SqlServer/DotNetCore.CAP.SqlServer.csproj b/src/DotNetCore.CAP.SqlServer/DotNetCore.CAP.SqlServer.csproj index fff239e..3307a7d 100644 --- a/src/DotNetCore.CAP.SqlServer/DotNetCore.CAP.SqlServer.csproj +++ b/src/DotNetCore.CAP.SqlServer/DotNetCore.CAP.SqlServer.csproj @@ -13,9 +13,9 @@ - - - + + + diff --git a/src/DotNetCore.CAP/DotNetCore.CAP.csproj b/src/DotNetCore.CAP/DotNetCore.CAP.csproj index 390900b..fe2c46c 100644 --- a/src/DotNetCore.CAP/DotNetCore.CAP.csproj +++ b/src/DotNetCore.CAP/DotNetCore.CAP.csproj @@ -10,8 +10,8 @@ - - + + diff --git a/test/DotNetCore.CAP.MySql.Test/DotNetCore.CAP.MySql.Test.csproj b/test/DotNetCore.CAP.MySql.Test/DotNetCore.CAP.MySql.Test.csproj index 19410e6..dd0a52c 100644 --- a/test/DotNetCore.CAP.MySql.Test/DotNetCore.CAP.MySql.Test.csproj +++ b/test/DotNetCore.CAP.MySql.Test/DotNetCore.CAP.MySql.Test.csproj @@ -12,9 +12,9 @@ - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -22,7 +22,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + \ No newline at end of file diff --git a/test/DotNetCore.CAP.Test/DotNetCore.CAP.Test.csproj b/test/DotNetCore.CAP.Test/DotNetCore.CAP.Test.csproj index f8fbb72..ae50a1f 100644 --- a/test/DotNetCore.CAP.Test/DotNetCore.CAP.Test.csproj +++ b/test/DotNetCore.CAP.Test/DotNetCore.CAP.Test.csproj @@ -10,12 +10,12 @@ - - - - + + + + - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -23,7 +23,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + From 80c76ed52f3675be986a4e4d4bb95311aa3ea4d2 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Fri, 14 Aug 2020 15:12:35 +0800 Subject: [PATCH 30/85] Fix browser language detection. #631 --- src/DotNetCore.CAP.Dashboard/CAP.DashboardMiddleware.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/DotNetCore.CAP.Dashboard/CAP.DashboardMiddleware.cs b/src/DotNetCore.CAP.Dashboard/CAP.DashboardMiddleware.cs index c8d5e92..d584b2f 100644 --- a/src/DotNetCore.CAP.Dashboard/CAP.DashboardMiddleware.cs +++ b/src/DotNetCore.CAP.Dashboard/CAP.DashboardMiddleware.cs @@ -2,12 +2,13 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using System; -using System.Linq; +using System.Globalization; using System.Net; using System.Threading.Tasks; using DotNetCore.CAP.Dashboard; using DotNetCore.CAP.Dashboard.GatewayProxy; using DotNetCore.CAP.Dashboard.NodeDiscovery; +using DotNetCore.CAP.Dashboard.Resources; using DotNetCore.CAP.Persistence; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -36,7 +37,6 @@ namespace DotNetCore.CAP { app.UseMiddleware(); } - app.UseMiddleware(); } @@ -77,7 +77,7 @@ namespace DotNetCore.CAP app.UseCapDashboard(); next(app); - }; + }; } } @@ -106,6 +106,9 @@ namespace DotNetCore.CAP return; } + var userLanguages = context.Request.Headers["Accept-Language"].ToString(); + Strings.Culture = userLanguages.Contains("zh-") ? new CultureInfo("zh-CN") : new CultureInfo("en-US"); + // Update the path var path = context.Request.Path; var pathBase = context.Request.PathBase; From d17b4771ebc7054f361a3d0a3d8288b9b0ba7204 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Tue, 18 Aug 2020 14:24:22 +0800 Subject: [PATCH 31/85] Update to 3.1.1 --- build/version.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/version.props b/build/version.props index 933f022..7018086 100644 --- a/build/version.props +++ b/build/version.props @@ -2,7 +2,7 @@ 3 1 - 0 + 1 $(VersionMajor).$(VersionMinor).$(VersionPatch) From ee6de6aa756acaebd03887d174bcb139ba234bd5 Mon Sep 17 00:00:00 2001 From: cBear Date: Tue, 18 Aug 2020 15:24:41 +0800 Subject: [PATCH 32/85] Fix sql server table name customize bug. #632 #632 --- .../IStorageInitializer.SqlServer.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/DotNetCore.CAP.SqlServer/IStorageInitializer.SqlServer.cs b/src/DotNetCore.CAP.SqlServer/IStorageInitializer.SqlServer.cs index f617af7..2b6636c 100644 --- a/src/DotNetCore.CAP.SqlServer/IStorageInitializer.SqlServer.cs +++ b/src/DotNetCore.CAP.SqlServer/IStorageInitializer.SqlServer.cs @@ -25,12 +25,12 @@ namespace DotNetCore.CAP.SqlServer public virtual string GetPublishedTableName() { - return $"[{_options.Value.Schema}].[Published]"; + return $"{_options.Value.Schema}.Published"; } public virtual string GetReceivedTableName() { - return $"[{_options.Value.Schema}].[Received]"; + return $"{_options.Value.Schema}.Received"; } public async Task InitializeAsync(CancellationToken cancellationToken) @@ -57,7 +57,7 @@ END; IF OBJECT_ID(N'{GetReceivedTableName()}',N'U') IS NULL BEGIN -CREATE TABLE [{schema}].[Received]( +CREATE TABLE {GetReceivedTableName()}( [Id] [bigint] NOT NULL, [Version] [nvarchar](20) NOT NULL, [Name] [nvarchar](200) NOT NULL, @@ -67,7 +67,7 @@ CREATE TABLE [{schema}].[Received]( [Added] [datetime2](7) NOT NULL, [ExpiresAt] [datetime2](7) NULL, [StatusName] [nvarchar](50) NOT NULL, - CONSTRAINT [PK_{schema}.Received] PRIMARY KEY CLUSTERED + CONSTRAINT [PK_{GetReceivedTableName()}] PRIMARY KEY CLUSTERED ( [Id] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] @@ -76,7 +76,7 @@ END; IF OBJECT_ID(N'{GetPublishedTableName()}',N'U') IS NULL BEGIN -CREATE TABLE [{schema}].[Published]( +CREATE TABLE {GetPublishedTableName()}( [Id] [bigint] NOT NULL, [Version] [nvarchar](20) NOT NULL, [Name] [nvarchar](200) NOT NULL, @@ -85,7 +85,7 @@ CREATE TABLE [{schema}].[Published]( [Added] [datetime2](7) NOT NULL, [ExpiresAt] [datetime2](7) NULL, [StatusName] [nvarchar](50) NOT NULL, - CONSTRAINT [PK_{schema}.Published] PRIMARY KEY CLUSTERED + CONSTRAINT [PK_{GetPublishedTableName()}] PRIMARY KEY CLUSTERED ( [Id] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] @@ -94,4 +94,4 @@ END;"; return batchSql; } } -} \ No newline at end of file +} From d63fedccdaade3ec1cebc92a5a542764aa112c86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B0=B7=E8=8D=89=60?= Date: Thu, 27 Aug 2020 15:20:50 +0800 Subject: [PATCH 33/85] fix DotNetCore.CAP.PostgreSql.CapEFDbTransaction RollbackAsync Method. (#640) --- src/DotNetCore.CAP.PostgreSql/IDbContextTransaction.CAP.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DotNetCore.CAP.PostgreSql/IDbContextTransaction.CAP.cs b/src/DotNetCore.CAP.PostgreSql/IDbContextTransaction.CAP.cs index ac5f87d..8bd7619 100644 --- a/src/DotNetCore.CAP.PostgreSql/IDbContextTransaction.CAP.cs +++ b/src/DotNetCore.CAP.PostgreSql/IDbContextTransaction.CAP.cs @@ -16,7 +16,7 @@ namespace Microsoft.EntityFrameworkCore.Storage public CapEFDbTransaction(ICapTransaction transaction) { _transaction = transaction; - var dbContextTransaction = (IDbContextTransaction) _transaction.DbTransaction; + var dbContextTransaction = (IDbContextTransaction)_transaction.DbTransaction; TransactionId = dbContextTransaction.TransactionId; } @@ -42,7 +42,7 @@ namespace Microsoft.EntityFrameworkCore.Storage public Task RollbackAsync(CancellationToken cancellationToken = default) { - return _transaction.CommitAsync(cancellationToken); + return _transaction.RollbackAsync(cancellationToken); } public Guid TransactionId { get; } From e2caef32322c0f4b5e684ba61012dde15c7d5b8f Mon Sep 17 00:00:00 2001 From: Savorboard Date: Mon, 31 Aug 2020 21:11:46 +0800 Subject: [PATCH 34/85] Add re-enable the auto create topics configuration item, it's false by default in rdkafka 1.5.0. #635 --- src/DotNetCore.CAP.Kafka/CAP.KafkaOptions.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/DotNetCore.CAP.Kafka/CAP.KafkaOptions.cs b/src/DotNetCore.CAP.Kafka/CAP.KafkaOptions.cs index 9247576..6cb12f1 100644 --- a/src/DotNetCore.CAP.Kafka/CAP.KafkaOptions.cs +++ b/src/DotNetCore.CAP.Kafka/CAP.KafkaOptions.cs @@ -60,6 +60,7 @@ namespace DotNetCore.CAP MainConfig["bootstrap.servers"] = Servers; MainConfig["queue.buffering.max.ms"] = "10"; + MainConfig["allow.auto.create.topics"] = "true"; MainConfig["enable.auto.commit"] = "false"; MainConfig["log.connection.close"] = "false"; MainConfig["request.timeout.ms"] = "3000"; From 67cc04136b12396a3210c6d677d002e2f51a5dd2 Mon Sep 17 00:00:00 2001 From: patheems Date: Wed, 2 Sep 2020 09:23:28 +0200 Subject: [PATCH 35/85] GetRuntimeMethods instead of DeclaredMethods to get inherited members. (#647) * GetRuntimeMethods gets also the inherited members that are not declared in the inherited class. * Update of failed test. Co-authored-by: Patrick Heemskerk --- .../IConsumerServiceSelector.Default.cs | 2 +- .../ConsumerServiceSelectorTest.cs | 50 ++++++++++++++++++- 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/src/DotNetCore.CAP/Internal/IConsumerServiceSelector.Default.cs b/src/DotNetCore.CAP/Internal/IConsumerServiceSelector.Default.cs index eedf675..26e0802 100644 --- a/src/DotNetCore.CAP/Internal/IConsumerServiceSelector.Default.cs +++ b/src/DotNetCore.CAP/Internal/IConsumerServiceSelector.Default.cs @@ -118,7 +118,7 @@ namespace DotNetCore.CAP.Internal { var topicClassAttribute = typeInfo.GetCustomAttribute(true); - foreach (var method in typeInfo.DeclaredMethods) + foreach (var method in typeInfo.GetRuntimeMethods()) { var topicMethodAttributes = method.GetCustomAttributes(true); diff --git a/test/DotNetCore.CAP.Test/ConsumerServiceSelectorTest.cs b/test/DotNetCore.CAP.Test/ConsumerServiceSelectorTest.cs index bb9dcdb..156f6cc 100644 --- a/test/DotNetCore.CAP.Test/ConsumerServiceSelectorTest.cs +++ b/test/DotNetCore.CAP.Test/ConsumerServiceSelectorTest.cs @@ -17,8 +17,9 @@ namespace DotNetCore.CAP.Test services.AddOptions(); services.PostConfigure(x=>{}); services.AddSingleton(); - services.AddScoped(); + services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddLogging(); _provider = services.BuildServiceProvider(); } @@ -29,7 +30,7 @@ namespace DotNetCore.CAP.Test var selector = _provider.GetRequiredService(); var candidates = selector.SelectCandidates(); - Assert.Equal(8, candidates.Count); + Assert.Equal(10, candidates.Count); } [Theory] @@ -46,6 +47,20 @@ namespace DotNetCore.CAP.Test Assert.NotNull(bestCandidates.MethodInfo); Assert.Equal(typeof(Task), bestCandidates.MethodInfo.ReturnType); } + + [Theory] + [InlineData("Candidates.Abstract")] + [InlineData("Candidates.Abstract2")] + public void CanFindInheritedMethodsTopic(string topic) + { + var selector = _provider.GetRequiredService(); + var candidates = selector.SelectCandidates(); + var bestCandidates = selector.SelectBestCandidate(topic, candidates); + + Assert.NotNull(bestCandidates); + Assert.NotNull(bestCandidates.MethodInfo); + Assert.Equal(typeof(Task), bestCandidates.MethodInfo.ReturnType); + } [Theory] @@ -132,6 +147,10 @@ namespace DotNetCore.CAP.Test { } + public interface IAbstractTest + { + } + [CandidatesTopic("Candidates")] public class CandidatesFooTest : IFooTest, ICapSubscribe { @@ -198,4 +217,31 @@ namespace DotNetCore.CAP.Test Console.WriteLine("GetBar3() method has bee excuted."); } } + + /// + /// Test to verify if an inherited class also gets the subscribed methods. + /// Abstract class doesn't have a subscribe topic, inherited class with a topic + /// should also get the partial subscribed methods. + /// + public abstract class CandidatesAbstractBaseTest : ICapSubscribe, IAbstractTest + { + [CandidatesTopic("Candidates.Abstract")] + public virtual Task GetAbstract() + { + Console.WriteLine("GetAbstract() method has been excuted."); + return Task.CompletedTask; + } + + [CandidatesTopic("Abstract2", isPartial: true)] + public virtual Task GetAbstract2() + { + Console.WriteLine("GetAbstract2() method has been excuted."); + return Task.CompletedTask; + } + } + + [CandidatesTopic("Candidates")] + public class CandidatesAbstractTest : CandidatesAbstractBaseTest + { + } } \ No newline at end of file From dfc26e3f7fa576f198a2181406171a0b4cbcfaf0 Mon Sep 17 00:00:00 2001 From: patheems Date: Sat, 5 Sep 2020 14:41:22 +0200 Subject: [PATCH 36/85] Added comparer to remove duplicate CosumerExecutors. (#654) Co-authored-by: Patrick Heemskerk --- .../Internal/ConsumerExecutorDescriptor.cs | 43 +++++++++++++++++-- .../IConsumerServiceSelector.Default.cs | 2 + 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/src/DotNetCore.CAP/Internal/ConsumerExecutorDescriptor.cs b/src/DotNetCore.CAP/Internal/ConsumerExecutorDescriptor.cs index 7a5cc0a..a9da4ad 100644 --- a/src/DotNetCore.CAP/Internal/ConsumerExecutorDescriptor.cs +++ b/src/DotNetCore.CAP/Internal/ConsumerExecutorDescriptor.cs @@ -28,8 +28,8 @@ namespace DotNetCore.CAP.Internal /// /// Topic name based on both and . /// - public string TopicName - { + public string TopicName + { get { if (_topicName == null) @@ -44,11 +44,48 @@ namespace DotNetCore.CAP.Internal _topicName = Attribute.Name; } } - return _topicName; + return _topicName; } } } + public class ConsumerExecutorDescriptorComparer : IEqualityComparer + { + public bool Equals(ConsumerExecutorDescriptor x, ConsumerExecutorDescriptor y) + { + //Check whether the compared objects reference the same data. + if (ReferenceEquals(x, y)) + { + return true; + } + + //Check whether any of the compared objects is null. + if (x is null || y is null) + { + return false; + } + + //Check whether the ConsumerExecutorDescriptor' properties are equal. + return x.TopicName.Equals(y.TopicName, StringComparison.OrdinalIgnoreCase) && + x.Attribute.Group.Equals(y.Attribute.Group, StringComparison.OrdinalIgnoreCase); + } + + public int GetHashCode(ConsumerExecutorDescriptor obj) + { + //Check whether the object is null + if (obj is null) return 0; + + //Get hash code for the Attribute Group field if it is not null. + int hashAttributeGroup = obj.Attribute?.Group == null ? 0 : obj.Attribute.Group.GetHashCode(); + + //Get hash code for the TopicName field. + int hashTopicName = obj.TopicName.GetHashCode(); + + //Calculate the hash code. + return hashAttributeGroup ^ hashTopicName; + } + } + public class ParameterDescriptor { public string Name { get; set; } diff --git a/src/DotNetCore.CAP/Internal/IConsumerServiceSelector.Default.cs b/src/DotNetCore.CAP/Internal/IConsumerServiceSelector.Default.cs index 26e0802..0498941 100644 --- a/src/DotNetCore.CAP/Internal/IConsumerServiceSelector.Default.cs +++ b/src/DotNetCore.CAP/Internal/IConsumerServiceSelector.Default.cs @@ -47,6 +47,8 @@ namespace DotNetCore.CAP.Internal executorDescriptorList.AddRange(FindConsumersFromControllerTypes()); + executorDescriptorList = executorDescriptorList.Distinct(new ConsumerExecutorDescriptorComparer()).ToList(); + return executorDescriptorList; } From 1f2a9c09a6586c8fe727af3f51ed5581ed96dd9a Mon Sep 17 00:00:00 2001 From: virtual <1185513330@qq.com> Date: Tue, 8 Sep 2020 13:39:19 +0800 Subject: [PATCH 37/85] Update docs (#661) Signed-off-by: virtual <1185513330@qq.com> --- docs/content/user-guide/zh/storage/general.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/content/user-guide/zh/storage/general.md b/docs/content/user-guide/zh/storage/general.md index 05fea55..c4f1571 100644 --- a/docs/content/user-guide/zh/storage/general.md +++ b/docs/content/user-guide/zh/storage/general.md @@ -69,3 +69,5 @@ CallbackName | 回调的订阅者名称 | string 感谢社区对CAP的支持,以下是社区支持的持久化的实现 * SQLite ([@colinin](https://github.com/colinin)) : https://github.com/colinin/DotNetCore.CAP.Sqlite + +* SQLite & Oracle ([@cocosip](https://github.com/cocosip)) : https://github.com/cocosip/CAP-Extensions \ No newline at end of file From d7b904c263621b30b88d7e3eddde021fba35bfff Mon Sep 17 00:00:00 2001 From: MysticBoy Date: Tue, 8 Sep 2020 14:20:13 +0800 Subject: [PATCH 38/85] Add litedb and ZeroMQ to general.md (#660) * add litedb and ZeroMQ * Update general.md * Update general.md Co-authored-by: Savorboard --- docs/content/user-guide/zh/storage/general.md | 4 +++- docs/content/user-guide/zh/transport/general.md | 9 ++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/docs/content/user-guide/zh/storage/general.md b/docs/content/user-guide/zh/storage/general.md index c4f1571..9d04e64 100644 --- a/docs/content/user-guide/zh/storage/general.md +++ b/docs/content/user-guide/zh/storage/general.md @@ -70,4 +70,6 @@ CallbackName | 回调的订阅者名称 | string * SQLite ([@colinin](https://github.com/colinin)) : https://github.com/colinin/DotNetCore.CAP.Sqlite -* SQLite & Oracle ([@cocosip](https://github.com/cocosip)) : https://github.com/cocosip/CAP-Extensions \ No newline at end of file +* LiteDB ([@maikebing](https://github.com/maikebing)) https://github.com/maikebing/CAP.Extensions/tree/master/src/DotNetCore.CAP.LiteDB + +* SQLite & Oracle ([@cocosip](https://github.com/cocosip)) : https://github.com/cocosip/CAP-Extensions diff --git a/docs/content/user-guide/zh/transport/general.md b/docs/content/user-guide/zh/transport/general.md index 6fc75d1..62dda8c 100644 --- a/docs/content/user-guide/zh/transport/general.md +++ b/docs/content/user-guide/zh/transport/general.md @@ -26,4 +26,11 @@ CAP 支持以下几种运输方式: > http://geekswithblogs.net/michaelstephenson/archive/2012/08/12/150399.aspx >`Kafka` vs `RabbitMQ` : -> https://stackoverflow.com/questions/42151544/is-there-any-reason-to-use-rabbitmq-over-kafka \ No newline at end of file +> https://stackoverflow.com/questions/42151544/is-there-any-reason-to-use-rabbitmq-over-kafka + +## 社区支持的运输器 + +感谢社区对CAP的支持,以下是社区支持的运输器实现 + +* ZeroMQ ([@maikebing](https://github.com/maikebing)) https://github.com/maikebing/CAP.Extensions/tree/master/src/DotNetCore.CAP.ZeroMQ + From 7ab41e7bd4647d50f9bc0038a648acab5c820e56 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Fri, 11 Sep 2020 22:23:41 +0800 Subject: [PATCH 39/85] Update docs --- docs/content/user-guide/en/storage/general.md | 13 ++++++++++++- docs/content/user-guide/en/transport/general.md | 6 ++++++ docs/content/user-guide/zh/storage/general.md | 6 +++--- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/docs/content/user-guide/en/storage/general.md b/docs/content/user-guide/en/storage/general.md index e178f69..f95b474 100644 --- a/docs/content/user-guide/en/storage/general.md +++ b/docs/content/user-guide/en/storage/general.md @@ -67,4 +67,15 @@ Timestamp | Message created time | string Content | Message content | string CallbackName | Consumer callback topic name | string -The `Id` field is generate using the mongo [objectid algorithm](https://www.mongodb.com/blog/post/generating-globally-unique-identifiers-for-use-with-mongodb). \ No newline at end of file +The `Id` field is generate using the mongo [objectid algorithm](https://www.mongodb.com/blog/post/generating-globally-unique-identifiers-for-use-with-mongodb). + + +## Community-supported extensions + +Thanks to the community for supporting CAP, the following is the implementation of community-supported storage + +* SQLite ([@colinin](https://github.com/colinin)) :https://github.com/colinin/DotNetCore.CAP.Sqlite + +* LiteDB ([@maikebing](https://github.com/maikebing)) :https://github.com/maikebing/CAP.Extensions + +* SQLite & Oracle ([@cocosip](https://github.com/cocosip)) :https://github.com/cocosip/CAP-Extensions diff --git a/docs/content/user-guide/en/transport/general.md b/docs/content/user-guide/en/transport/general.md index 4e764ba..7c627dd 100644 --- a/docs/content/user-guide/en/transport/general.md +++ b/docs/content/user-guide/en/transport/general.md @@ -28,3 +28,9 @@ CAP supports several transport methods: >`Kafka` vs `RabbitMQ` : > https://stackoverflow.com/questions/42151544/is-there-any-reason-to-use-rabbitmq-over-kafka +## Community-supported extensions + +Thanks to the community for supporting CAP, the following is the implementation of community-supported transport + +* ZeroMQ ([@maikebing](https://github.com/maikebing)): https://github.com/maikebing/CAP.Extensions + diff --git a/docs/content/user-guide/zh/storage/general.md b/docs/content/user-guide/zh/storage/general.md index 9d04e64..d05aaf1 100644 --- a/docs/content/user-guide/zh/storage/general.md +++ b/docs/content/user-guide/zh/storage/general.md @@ -68,8 +68,8 @@ CallbackName | 回调的订阅者名称 | string 感谢社区对CAP的支持,以下是社区支持的持久化的实现 -* SQLite ([@colinin](https://github.com/colinin)) : https://github.com/colinin/DotNetCore.CAP.Sqlite +* SQLite ([@colinin](https://github.com/colinin)) :https://github.com/colinin/DotNetCore.CAP.Sqlite -* LiteDB ([@maikebing](https://github.com/maikebing)) https://github.com/maikebing/CAP.Extensions/tree/master/src/DotNetCore.CAP.LiteDB +* LiteDB ([@maikebing](https://github.com/maikebing)) :https://github.com/maikebing/CAP.Extensions -* SQLite & Oracle ([@cocosip](https://github.com/cocosip)) : https://github.com/cocosip/CAP-Extensions +* SQLite & Oracle ([@cocosip](https://github.com/cocosip)) :https://github.com/cocosip/CAP-Extensions From ba31886db7df58a6be03ca28a9347ea4b8a41fcf Mon Sep 17 00:00:00 2001 From: patheems Date: Sun, 13 Sep 2020 09:09:08 +0200 Subject: [PATCH 40/85] Solve the issue of being restricted to using Newtonsoft (#664) * Solve the issue of being restricted to using Newtonsoft for serialization/deserialization. * Removed whitespace * Failed build fixed by injecting ISerializer. * Removed unintended reference. Co-authored-by: Patrick Heemskerk --- .../CAP.DashboardOptionsExtensions.cs | 3 +- .../DashboardRoutes.cs | 14 ++--- .../IDataStorage.InMemory.cs | 12 ++-- .../IDataStorage.MongoDB.cs | 14 +++-- .../IDataStorage.MySql.cs | 11 ++-- .../IDataStorage.PostgreSql.cs | 11 ++-- .../IDataStorage.SqlServer.cs | 11 ++-- .../Internal/IConsumerRegister.Default.cs | 2 +- .../Internal/ISubscribeInvoker.Default.cs | 10 +-- src/DotNetCore.CAP/Messages/Message.cs | 10 ++- .../Serialization/ISerializer.JsonUtf8.cs | 29 ++++++++- .../Serialization/ISerializer.cs | 32 +++++++++- .../MySqlStorageConnectionTest.cs | 4 +- test/DotNetCore.CAP.MySql.Test/TestHost.cs | 4 +- test/DotNetCore.CAP.Test/MessageTest.cs | 62 +++++++++++++++++++ .../SubscribeInvokerTest.cs | 2 + 16 files changed, 188 insertions(+), 43 deletions(-) create mode 100644 test/DotNetCore.CAP.Test/MessageTest.cs diff --git a/src/DotNetCore.CAP.Dashboard/CAP.DashboardOptionsExtensions.cs b/src/DotNetCore.CAP.Dashboard/CAP.DashboardOptionsExtensions.cs index 5a27d66..00db11d 100644 --- a/src/DotNetCore.CAP.Dashboard/CAP.DashboardOptionsExtensions.cs +++ b/src/DotNetCore.CAP.Dashboard/CAP.DashboardOptionsExtensions.cs @@ -6,6 +6,7 @@ using DotNetCore.CAP; using DotNetCore.CAP.Dashboard; using DotNetCore.CAP.Dashboard.GatewayProxy; using DotNetCore.CAP.Dashboard.GatewayProxy.Requester; +using DotNetCore.CAP.Serialization; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; @@ -26,7 +27,7 @@ namespace DotNetCore.CAP _options?.Invoke(dashboardOptions); services.AddTransient(); services.AddSingleton(dashboardOptions); - services.AddSingleton(DashboardRoutes.Routes); + services.AddSingleton(x => DashboardRoutes.GetDashboardRoutes(x.GetRequiredService())); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/DotNetCore.CAP.Dashboard/DashboardRoutes.cs b/src/DotNetCore.CAP.Dashboard/DashboardRoutes.cs index 35105cf..4672f58 100644 --- a/src/DotNetCore.CAP.Dashboard/DashboardRoutes.cs +++ b/src/DotNetCore.CAP.Dashboard/DashboardRoutes.cs @@ -10,7 +10,7 @@ using Microsoft.Extensions.DependencyInjection; namespace DotNetCore.CAP.Dashboard { - public static class DashboardRoutes + public class DashboardRoutes { private static readonly string[] Javascripts = { @@ -33,9 +33,9 @@ namespace DotNetCore.CAP.Dashboard "cap.css" }; - static DashboardRoutes() + public static RouteCollection GetDashboardRoutes(ISerializer serializer) { - Routes = new RouteCollection(); + RouteCollection Routes = new RouteCollection(); Routes.AddRazorPage("/", x => new HomePage()); Routes.Add("/stats", new JsonStats()); Routes.Add("/health", new OkStats()); @@ -104,7 +104,7 @@ namespace DotNetCore.CAP.Dashboard { var msg = client.Storage.GetMonitoringApi().GetPublishedMessageAsync(messageId) .GetAwaiter().GetResult(); - msg.Origin = StringSerializer.DeSerialize(msg.Content); + msg.Origin = serializer.Deserialize(msg.Content); client.RequestServices.GetService().EnqueueToPublish(msg); }); Routes.AddPublishBatchCommand( @@ -113,7 +113,7 @@ namespace DotNetCore.CAP.Dashboard { var msg = client.Storage.GetMonitoringApi().GetReceivedMessageAsync(messageId) .GetAwaiter().GetResult(); - msg.Origin = StringSerializer.DeSerialize(msg.Content); + msg.Origin = serializer.Deserialize(msg.Content); client.RequestServices.GetService().DispatchAsync(msg); }); @@ -135,9 +135,9 @@ namespace DotNetCore.CAP.Dashboard Routes.AddRazorPage("/nodes/node/(?.+)", x => new NodePage(x.UriMatch.Groups["Id"].Value)); #endregion Razor pages and commands - } - public static RouteCollection Routes { get; } + return Routes; + } internal static string GetContentFolderNamespace(string contentFolder) { diff --git a/src/DotNetCore.CAP.InMemoryStorage/IDataStorage.InMemory.cs b/src/DotNetCore.CAP.InMemoryStorage/IDataStorage.InMemory.cs index ee01cf3..5730fec 100644 --- a/src/DotNetCore.CAP.InMemoryStorage/IDataStorage.InMemory.cs +++ b/src/DotNetCore.CAP.InMemoryStorage/IDataStorage.InMemory.cs @@ -19,10 +19,12 @@ namespace DotNetCore.CAP.InMemoryStorage internal class InMemoryStorage : IDataStorage { private readonly IOptions _capOptions; + private readonly ISerializer _serializer; - public InMemoryStorage(IOptions capOptions) + public InMemoryStorage(IOptions capOptions, ISerializer serializer) { _capOptions = capOptions; + _serializer = serializer; } public static ConcurrentDictionary PublishedMessages { get; } = new ConcurrentDictionary(); @@ -49,7 +51,7 @@ namespace DotNetCore.CAP.InMemoryStorage { DbId = content.GetId(), Origin = content, - Content = StringSerializer.Serialize(content), + Content = _serializer.Serialize(content), Added = DateTime.Now, ExpiresAt = null, Retries = 0 @@ -104,7 +106,7 @@ namespace DotNetCore.CAP.InMemoryStorage Origin = mdMessage.Origin, Group = group, Name = name, - Content = StringSerializer.Serialize(mdMessage.Origin), + Content = _serializer.Serialize(mdMessage.Origin), Retries = mdMessage.Retries, Added = mdMessage.Added, ExpiresAt = mdMessage.ExpiresAt, @@ -152,7 +154,7 @@ namespace DotNetCore.CAP.InMemoryStorage foreach (var message in ret) { - message.Origin = StringSerializer.DeSerialize(message.Content); + message.Origin = _serializer.Deserialize(message.Content); } return Task.FromResult(ret); @@ -169,7 +171,7 @@ namespace DotNetCore.CAP.InMemoryStorage foreach (var message in ret) { - message.Origin = StringSerializer.DeSerialize(message.Content); + message.Origin = _serializer.Deserialize(message.Content); } return Task.FromResult(ret); diff --git a/src/DotNetCore.CAP.MongoDB/IDataStorage.MongoDB.cs b/src/DotNetCore.CAP.MongoDB/IDataStorage.MongoDB.cs index 9275b31..86f1e39 100644 --- a/src/DotNetCore.CAP.MongoDB/IDataStorage.MongoDB.cs +++ b/src/DotNetCore.CAP.MongoDB/IDataStorage.MongoDB.cs @@ -11,7 +11,6 @@ using DotNetCore.CAP.Messages; using DotNetCore.CAP.Monitoring; using DotNetCore.CAP.Persistence; using DotNetCore.CAP.Serialization; -using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using MongoDB.Driver; @@ -23,16 +22,19 @@ namespace DotNetCore.CAP.MongoDB private readonly IMongoClient _client; private readonly IMongoDatabase _database; private readonly IOptions _options; + private readonly ISerializer _serializer; public MongoDBDataStorage( IOptions capOptions, IOptions options, - IMongoClient client) + IMongoClient client, + ISerializer serializer) { _capOptions = capOptions; _options = options; _client = client; _database = _client.GetDatabase(_options.Value.DatabaseName); + _serializer = serializer; } public async Task ChangePublishStateAsync(MediumMessage message, StatusName state) @@ -67,7 +69,7 @@ namespace DotNetCore.CAP.MongoDB { DbId = content.GetId(), Origin = content, - Content = StringSerializer.Serialize(content), + Content = _serializer.Serialize(content), Added = DateTime.Now, ExpiresAt = null, Retries = 0 @@ -130,7 +132,7 @@ namespace DotNetCore.CAP.MongoDB ExpiresAt = null, Retries = 0 }; - var content = StringSerializer.Serialize(mdMessage.Origin); + var content = _serializer.Serialize(mdMessage.Origin); var collection = _database.GetCollection(_options.Value.ReceivedCollection); @@ -184,7 +186,7 @@ namespace DotNetCore.CAP.MongoDB return queryResult.Select(x => new MediumMessage { DbId = x.Id.ToString(), - Origin = StringSerializer.DeSerialize(x.Content), + Origin = _serializer.Deserialize(x.Content), Retries = x.Retries, Added = x.Added }).ToList(); @@ -205,7 +207,7 @@ namespace DotNetCore.CAP.MongoDB return queryResult.Select(x => new MediumMessage { DbId = x.Id.ToString(), - Origin = StringSerializer.DeSerialize(x.Content), + Origin = _serializer.Deserialize(x.Content), Retries = x.Retries, Added = x.Added }).ToList(); diff --git a/src/DotNetCore.CAP.MySql/IDataStorage.MySql.cs b/src/DotNetCore.CAP.MySql/IDataStorage.MySql.cs index 9199afb..3f3892c 100644 --- a/src/DotNetCore.CAP.MySql/IDataStorage.MySql.cs +++ b/src/DotNetCore.CAP.MySql/IDataStorage.MySql.cs @@ -22,17 +22,20 @@ namespace DotNetCore.CAP.MySql private readonly IOptions _options; private readonly IOptions _capOptions; private readonly IStorageInitializer _initializer; + private readonly ISerializer _serializer; private readonly string _pubName; private readonly string _recName; public MySqlDataStorage( IOptions options, IOptions capOptions, - IStorageInitializer initializer) + IStorageInitializer initializer, + ISerializer serializer) { _options = options; _capOptions = capOptions; _initializer = initializer; + _serializer = serializer; _pubName = initializer.GetPublishedTableName(); _recName = initializer.GetReceivedTableName(); } @@ -52,7 +55,7 @@ namespace DotNetCore.CAP.MySql { DbId = content.GetId(), Origin = content, - Content = StringSerializer.Serialize(content), + Content = _serializer.Serialize(content), Added = DateTime.Now, ExpiresAt = null, Retries = 0 @@ -122,7 +125,7 @@ namespace DotNetCore.CAP.MySql new MySqlParameter("@Id", mdMessage.DbId), new MySqlParameter("@Name", name), new MySqlParameter("@Group", group), - new MySqlParameter("@Content", StringSerializer.Serialize(mdMessage.Origin)), + new MySqlParameter("@Content", _serializer.Serialize(mdMessage.Origin)), new MySqlParameter("@Retries", mdMessage.Retries), new MySqlParameter("@Added", mdMessage.Added), new MySqlParameter("@ExpiresAt", mdMessage.ExpiresAt.HasValue ? (object) mdMessage.ExpiresAt.Value : DBNull.Value), @@ -194,7 +197,7 @@ namespace DotNetCore.CAP.MySql messages.Add(new MediumMessage { DbId = reader.GetInt64(0).ToString(), - Origin = StringSerializer.DeSerialize(reader.GetString(1)), + Origin = _serializer.Deserialize(reader.GetString(1)), Retries = reader.GetInt32(2), Added = reader.GetDateTime(3) }); diff --git a/src/DotNetCore.CAP.PostgreSql/IDataStorage.PostgreSql.cs b/src/DotNetCore.CAP.PostgreSql/IDataStorage.PostgreSql.cs index aa55d34..7d7f602 100644 --- a/src/DotNetCore.CAP.PostgreSql/IDataStorage.PostgreSql.cs +++ b/src/DotNetCore.CAP.PostgreSql/IDataStorage.PostgreSql.cs @@ -22,17 +22,20 @@ namespace DotNetCore.CAP.PostgreSql private readonly IOptions _capOptions; private readonly IStorageInitializer _initializer; private readonly IOptions _options; + private readonly ISerializer _serializer; private readonly string _pubName; private readonly string _recName; public PostgreSqlDataStorage( IOptions options, IOptions capOptions, - IStorageInitializer initializer) + IStorageInitializer initializer, + ISerializer serializer) { _capOptions = capOptions; _initializer = initializer; _options = options; + _serializer = serializer; _pubName = initializer.GetPublishedTableName(); _recName = initializer.GetReceivedTableName(); } @@ -53,7 +56,7 @@ namespace DotNetCore.CAP.PostgreSql { DbId = content.GetId(), Origin = content, - Content = StringSerializer.Serialize(content), + Content = _serializer.Serialize(content), Added = DateTime.Now, ExpiresAt = null, Retries = 0 @@ -121,7 +124,7 @@ namespace DotNetCore.CAP.PostgreSql new NpgsqlParameter("@Id", long.Parse(mdMessage.DbId)), new NpgsqlParameter("@Name", name), new NpgsqlParameter("@Group", group), - new NpgsqlParameter("@Content", StringSerializer.Serialize(mdMessage.Origin)), + new NpgsqlParameter("@Content", _serializer.Serialize(mdMessage.Origin)), new NpgsqlParameter("@Retries", mdMessage.Retries), new NpgsqlParameter("@Added", mdMessage.Added), new NpgsqlParameter("@ExpiresAt", mdMessage.ExpiresAt.HasValue ? (object) mdMessage.ExpiresAt.Value : DBNull.Value), @@ -199,7 +202,7 @@ namespace DotNetCore.CAP.PostgreSql messages.Add(new MediumMessage { DbId = reader.GetInt64(0).ToString(), - Origin = StringSerializer.DeSerialize(reader.GetString(1)), + Origin = _serializer.Deserialize(reader.GetString(1)), Retries = reader.GetInt32(2), Added = reader.GetDateTime(3) }); diff --git a/src/DotNetCore.CAP.SqlServer/IDataStorage.SqlServer.cs b/src/DotNetCore.CAP.SqlServer/IDataStorage.SqlServer.cs index dc83dec..0db52d1 100644 --- a/src/DotNetCore.CAP.SqlServer/IDataStorage.SqlServer.cs +++ b/src/DotNetCore.CAP.SqlServer/IDataStorage.SqlServer.cs @@ -22,17 +22,20 @@ namespace DotNetCore.CAP.SqlServer private readonly IOptions _capOptions; private readonly IOptions _options; private readonly IStorageInitializer _initializer; + private readonly ISerializer _serializer; private readonly string _pubName; private readonly string _recName; public SqlServerDataStorage( IOptions capOptions, IOptions options, - IStorageInitializer initializer) + IStorageInitializer initializer, + ISerializer serializer) { _options = options; _initializer = initializer; _capOptions = capOptions; + _serializer = serializer; _pubName = initializer.GetPublishedTableName(); _recName = initializer.GetReceivedTableName(); } @@ -52,7 +55,7 @@ namespace DotNetCore.CAP.SqlServer { DbId = content.GetId(), Origin = content, - Content = StringSerializer.Serialize(content), + Content = _serializer.Serialize(content), Added = DateTime.Now, ExpiresAt = null, Retries = 0 @@ -120,7 +123,7 @@ namespace DotNetCore.CAP.SqlServer new SqlParameter("@Id", mdMessage.DbId), new SqlParameter("@Name", name), new SqlParameter("@Group", group), - new SqlParameter("@Content", StringSerializer.Serialize(mdMessage.Origin)), + new SqlParameter("@Content", _serializer.Serialize(mdMessage.Origin)), new SqlParameter("@Retries", mdMessage.Retries), new SqlParameter("@Added", mdMessage.Added), new SqlParameter("@ExpiresAt", mdMessage.ExpiresAt.HasValue ? (object) mdMessage.ExpiresAt.Value : DBNull.Value), @@ -200,7 +203,7 @@ namespace DotNetCore.CAP.SqlServer messages.Add(new MediumMessage { DbId = reader.GetInt64(0).ToString(), - Origin = StringSerializer.DeSerialize(reader.GetString(1)), + Origin = _serializer.Deserialize(reader.GetString(1)), Retries = reader.GetInt32(2), Added = reader.GetDateTime(3) }); diff --git a/src/DotNetCore.CAP/Internal/IConsumerRegister.Default.cs b/src/DotNetCore.CAP/Internal/IConsumerRegister.Default.cs index c41c96d..9b1098b 100644 --- a/src/DotNetCore.CAP/Internal/IConsumerRegister.Default.cs +++ b/src/DotNetCore.CAP/Internal/IConsumerRegister.Default.cs @@ -195,7 +195,7 @@ namespace DotNetCore.CAP.Internal if (message.HasException()) { - var content = StringSerializer.Serialize(message); + var content = _serializer.Serialize(message); _storage.StoreReceivedExceptionMessage(name, group, content); diff --git a/src/DotNetCore.CAP/Internal/ISubscribeInvoker.Default.cs b/src/DotNetCore.CAP/Internal/ISubscribeInvoker.Default.cs index 2db57d4..dd6da06 100644 --- a/src/DotNetCore.CAP/Internal/ISubscribeInvoker.Default.cs +++ b/src/DotNetCore.CAP/Internal/ISubscribeInvoker.Default.cs @@ -8,10 +8,10 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using DotNetCore.CAP.Messages; +using DotNetCore.CAP.Serialization; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Internal; using Microsoft.Extensions.Logging; -using Newtonsoft.Json.Linq; namespace DotNetCore.CAP.Internal { @@ -19,11 +19,13 @@ namespace DotNetCore.CAP.Internal { private readonly ILogger _logger; private readonly IServiceProvider _serviceProvider; + private readonly ISerializer _serializer; private readonly ConcurrentDictionary _executors; - public SubscribeInvoker(ILoggerFactory loggerFactory, IServiceProvider serviceProvider) + public SubscribeInvoker(ILoggerFactory loggerFactory, IServiceProvider serviceProvider, ISerializer serializer) { _serviceProvider = serviceProvider; + _serializer = serializer; _logger = loggerFactory.CreateLogger(); _executors = new ConcurrentDictionary(); } @@ -57,9 +59,9 @@ namespace DotNetCore.CAP.Internal { if (message.Value != null) { - if (message.Value is JToken jToken) //reading from storage + if (_serializer.IsJsonType(message.Value)) // use ISerializer when reading from storage, skip other objects if not Json { - executeParameters[i] = jToken.ToObject(parameterDescriptors[i].ParameterType); + executeParameters[i] = _serializer.Deserialize(message.Value, parameterDescriptors[i].ParameterType); } else { diff --git a/src/DotNetCore.CAP/Messages/Message.cs b/src/DotNetCore.CAP/Messages/Message.cs index 6ab4935..eb52659 100644 --- a/src/DotNetCore.CAP/Messages/Message.cs +++ b/src/DotNetCore.CAP/Messages/Message.cs @@ -9,16 +9,22 @@ namespace DotNetCore.CAP.Messages { public class Message { + /// + /// System.Text.Json requires that class explicitly has a parameterless constructor + /// and public properties have a setter. + /// + public Message() {} + public Message(IDictionary headers, [CanBeNull] object value) { Headers = headers ?? throw new ArgumentNullException(nameof(headers)); Value = value; } - public IDictionary Headers { get; } + public IDictionary Headers { get; set; } [CanBeNull] - public object Value { get; } + public object Value { get; set; } } public static class MessageExtensions diff --git a/src/DotNetCore.CAP/Serialization/ISerializer.JsonUtf8.cs b/src/DotNetCore.CAP/Serialization/ISerializer.JsonUtf8.cs index a2e534a..70ab10e 100644 --- a/src/DotNetCore.CAP/Serialization/ISerializer.JsonUtf8.cs +++ b/src/DotNetCore.CAP/Serialization/ISerializer.JsonUtf8.cs @@ -6,6 +6,7 @@ using System.Text; using System.Threading.Tasks; using DotNetCore.CAP.Messages; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; namespace DotNetCore.CAP.Serialization { @@ -37,5 +38,29 @@ namespace DotNetCore.CAP.Serialization var json = Encoding.UTF8.GetString(transportMessage.Body); return Task.FromResult(new Message(transportMessage.Headers, JsonConvert.DeserializeObject(json, valueType))); } - } -} + + public string Serialize(Message message) + { + return JsonConvert.SerializeObject(message); + } + + public Message Deserialize(string json) + { + return JsonConvert.DeserializeObject(json); + } + + public object Deserialize(object value, Type valueType) + { + if (value is JToken jToken) + { + return jToken.ToObject(valueType); + } + throw new NotSupportedException("Type is not of type JToken"); + } + + public bool IsJsonType(object jsonObject) + { + return jsonObject is JToken; + } + } +} \ No newline at end of file diff --git a/src/DotNetCore.CAP/Serialization/ISerializer.cs b/src/DotNetCore.CAP/Serialization/ISerializer.cs index 0c831f7..ee6135a 100644 --- a/src/DotNetCore.CAP/Serialization/ISerializer.cs +++ b/src/DotNetCore.CAP/Serialization/ISerializer.cs @@ -16,8 +16,38 @@ namespace DotNetCore.CAP.Serialization Task SerializeAsync(Message message); /// - /// Deserializes the given back into a + /// Deserialize the given back into a /// Task DeserializeAsync(TransportMessage transportMessage, [CanBeNull] Type valueType); + + /// + /// Serializes the given into a string + /// + string Serialize(Message message); + + /// + /// Deserialize the given string into a + /// + Message Deserialize(string json); + + /// + /// Deserialize the given object with the given Type into an object + /// + object Deserialize(object value, Type valueType); + + /// + /// Check if the given object is of Json type, e.g. JToken or JsonElement + /// depending on the type of serializer implemented + /// + /// + /// + /// // Example implementation for System.Text.Json + /// public bool IsJsonType(object jsonObject) + /// { + /// return jsonObject is JsonElement; + /// } + /// + /// + bool IsJsonType(object jsonObject); } } \ No newline at end of file diff --git a/test/DotNetCore.CAP.MySql.Test/MySqlStorageConnectionTest.cs b/test/DotNetCore.CAP.MySql.Test/MySqlStorageConnectionTest.cs index 12fb9e2..3a2e0b4 100644 --- a/test/DotNetCore.CAP.MySql.Test/MySqlStorageConnectionTest.cs +++ b/test/DotNetCore.CAP.MySql.Test/MySqlStorageConnectionTest.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using DotNetCore.CAP.Internal; using DotNetCore.CAP.Messages; using DotNetCore.CAP.Persistence; +using DotNetCore.CAP.Serialization; using Microsoft.Extensions.Options; using Xunit; @@ -15,10 +16,11 @@ namespace DotNetCore.CAP.MySql.Test public MySqlStorageConnectionTest() { + var serializer = GetService(); var options = GetService>(); var capOptions = GetService>(); var initializer = GetService(); - _storage = new MySqlDataStorage(options, capOptions, initializer); + _storage = new MySqlDataStorage(options, capOptions, initializer, serializer); } [Fact] diff --git a/test/DotNetCore.CAP.MySql.Test/TestHost.cs b/test/DotNetCore.CAP.MySql.Test/TestHost.cs index acb9fc0..35480bc 100644 --- a/test/DotNetCore.CAP.MySql.Test/TestHost.cs +++ b/test/DotNetCore.CAP.MySql.Test/TestHost.cs @@ -1,5 +1,6 @@ using System; using DotNetCore.CAP.Persistence; +using DotNetCore.CAP.Serialization; using Microsoft.Extensions.DependencyInjection; namespace DotNetCore.CAP.MySql.Test @@ -34,8 +35,9 @@ namespace DotNetCore.CAP.MySql.Test { x.ConnectionString = ConnectionString; }); - services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); _services = services; } diff --git a/test/DotNetCore.CAP.Test/MessageTest.cs b/test/DotNetCore.CAP.Test/MessageTest.cs new file mode 100644 index 0000000..133c24a --- /dev/null +++ b/test/DotNetCore.CAP.Test/MessageTest.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using DotNetCore.CAP.Messages; +using DotNetCore.CAP.Serialization; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace DotNetCore.CAP.Test +{ + public class MessageTest + { + private readonly IServiceProvider _provider; + + public MessageTest() + { + var services = new ServiceCollection(); + ServiceCollectionExtensions.ServiceCollection = services; + + services.AddSingleton(); + _provider = services.BuildServiceProvider(); + } + + [Fact] + public void Serialize_then_Deserialize_Message_With_Utf8JsonSerializer() + { + // Given + var givenMessage = new Message( + headers: new Dictionary() { + { "cap-msg-name", "authentication.users.update"}, + { "cap-msg-type", "User" }, + { "cap-corr-seq", "0"}, + { "cap-msg-group","service.v1"} + }, + value: new MessageValue("test@test.com", "User")); + + // When + var serializer = _provider.GetRequiredService(); + var json = serializer.Serialize(givenMessage); + var deserializedMessage = serializer.Deserialize(json); + + // Then + Assert.True(serializer.IsJsonType(deserializedMessage.Value)); + + var result = serializer.Deserialize(deserializedMessage.Value, typeof(MessageValue)) as MessageValue; + Assert.NotNull(result); + Assert.Equal(result.Email, ((MessageValue)givenMessage.Value).Email); + Assert.Equal(result.Name, ((MessageValue)givenMessage.Value).Name); + } + } + + public class MessageValue + { + public MessageValue(string email, string name) + { + Email = email; + Name = name; + } + + public string Email { get; } + public string Name { get; } + } +} \ No newline at end of file diff --git a/test/DotNetCore.CAP.Test/SubscribeInvokerTest.cs b/test/DotNetCore.CAP.Test/SubscribeInvokerTest.cs index 9815bf0..5db7959 100644 --- a/test/DotNetCore.CAP.Test/SubscribeInvokerTest.cs +++ b/test/DotNetCore.CAP.Test/SubscribeInvokerTest.cs @@ -4,6 +4,7 @@ using System.Reflection; using System.Threading.Tasks; using DotNetCore.CAP.Internal; using DotNetCore.CAP.Messages; +using DotNetCore.CAP.Serialization; using Microsoft.Extensions.DependencyInjection; using Xunit; @@ -17,6 +18,7 @@ namespace DotNetCore.CAP.Test { var serviceCollection = new ServiceCollection(); serviceCollection.AddLogging(); + serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); _serviceProvider = serviceCollection.BuildServiceProvider(); } From 1de8d6cc19c3ff4fc91bc5d17bbf27de8836d157 Mon Sep 17 00:00:00 2001 From: Dima Zhemkov Date: Mon, 14 Sep 2020 04:11:02 +0300 Subject: [PATCH 41/85] Solve the issue of being duplicated executors from different assemblies (#667) --- src/DotNetCore.CAP/Internal/ISubscribeInvoker.Default.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/DotNetCore.CAP/Internal/ISubscribeInvoker.Default.cs b/src/DotNetCore.CAP/Internal/ISubscribeInvoker.Default.cs index dd6da06..b1ef187 100644 --- a/src/DotNetCore.CAP/Internal/ISubscribeInvoker.Default.cs +++ b/src/DotNetCore.CAP/Internal/ISubscribeInvoker.Default.cs @@ -20,14 +20,14 @@ namespace DotNetCore.CAP.Internal private readonly ILogger _logger; private readonly IServiceProvider _serviceProvider; private readonly ISerializer _serializer; - private readonly ConcurrentDictionary _executors; + private readonly ConcurrentDictionary _executors; public SubscribeInvoker(ILoggerFactory loggerFactory, IServiceProvider serviceProvider, ISerializer serializer) { _serviceProvider = serviceProvider; _serializer = serializer; _logger = loggerFactory.CreateLogger(); - _executors = new ConcurrentDictionary(); + _executors = new ConcurrentDictionary(); } public async Task InvokeAsync(ConsumerContext context, CancellationToken cancellationToken = default) @@ -38,7 +38,8 @@ namespace DotNetCore.CAP.Internal _logger.LogDebug("Executing subscriber method : {0}", methodInfo.Name); - var executor = _executors.GetOrAdd(methodInfo.MetadataToken, x => ObjectMethodExecutor.Create(methodInfo, context.ConsumerDescriptor.ImplTypeInfo)); + var key = $"{methodInfo.Module.Name}_{methodInfo.MetadataToken}"; + var executor = _executors.GetOrAdd(key, x => ObjectMethodExecutor.Create(methodInfo, context.ConsumerDescriptor.ImplTypeInfo)); using var scope = _serviceProvider.CreateScope(); From 04a272a780d20dbc5b3fdb51446996c9c1730454 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Wed, 16 Sep 2020 17:37:23 +0800 Subject: [PATCH 42/85] Add determines whether the subscriber parameter is an instance of the current type. #669 --- src/DotNetCore.CAP/Internal/ISubscribeInvoker.Default.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/DotNetCore.CAP/Internal/ISubscribeInvoker.Default.cs b/src/DotNetCore.CAP/Internal/ISubscribeInvoker.Default.cs index b1ef187..bbaeb6b 100644 --- a/src/DotNetCore.CAP/Internal/ISubscribeInvoker.Default.cs +++ b/src/DotNetCore.CAP/Internal/ISubscribeInvoker.Default.cs @@ -73,7 +73,14 @@ namespace DotNetCore.CAP.Internal } else { - executeParameters[i] = Convert.ChangeType(message.Value, parameterDescriptors[i].ParameterType); + if (parameterDescriptors[i].ParameterType.IsInstanceOfType(message.Value)) + { + executeParameters[i] = message.Value; + } + else + { + executeParameters[i] = Convert.ChangeType(message.Value, parameterDescriptors[i].ParameterType); + } } } } From c87677e9aa18f07a4af01a33f0f52198cc8a88fb Mon Sep 17 00:00:00 2001 From: xiangxiren Date: Thu, 17 Sep 2020 18:11:03 +0800 Subject: [PATCH 43/85] Update docs (#670) --- docs/content/user-guide/zh/storage/general.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/content/user-guide/zh/storage/general.md b/docs/content/user-guide/zh/storage/general.md index d05aaf1..512e0b6 100644 --- a/docs/content/user-guide/zh/storage/general.md +++ b/docs/content/user-guide/zh/storage/general.md @@ -73,3 +73,5 @@ CallbackName | 回调的订阅者名称 | string * LiteDB ([@maikebing](https://github.com/maikebing)) :https://github.com/maikebing/CAP.Extensions * SQLite & Oracle ([@cocosip](https://github.com/cocosip)) :https://github.com/cocosip/CAP-Extensions + +* SmartSql ([@xiangxiren](https://github.com/xiangxiren)) :https://github.com/xiangxiren/SmartSql.CAP From a9616d687354fb9249c0e0dfad5982ad90797c85 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Wed, 23 Sep 2020 17:51:37 +0800 Subject: [PATCH 44/85] Add compensating transaction to docs --- docs/content/user-guide/en/cap/messaging.md | 84 +++++++++++++++++++++ docs/content/user-guide/zh/cap/messaging.md | 44 +++++++++++ docs/mkdocs.yml | 2 - 3 files changed, 128 insertions(+), 2 deletions(-) diff --git a/docs/content/user-guide/en/cap/messaging.md b/docs/content/user-guide/en/cap/messaging.md index 169549b..f6ca2b1 100644 --- a/docs/content/user-guide/en/cap/messaging.md +++ b/docs/content/user-guide/en/cap/messaging.md @@ -2,6 +2,90 @@ The data sent by using the `ICapPublisher` interface is called `Message`. +## Compensating transaction + +Wiki : +[Compensating transaction](https://en.wikipedia.org/wiki/Compensating_transaction) + +In some cases, consumers need to return the execution value to tell the publisher, so that the publisher can implement some compensation actions, usually we called message compensation. + +Usually you can notify the upstream by republishing a new message in the consumer code. CAP provides a simple way to do this. You can specify `callbackName` parameter when publishing message, usually this only applies to point-to-point consumption. The following is an example. + +For example, in an e-commerce application, the initial status of the order is pending, and the status is marked as succeeded when the product quantity is successfully deducted, otherwise it is failed. + +```C# +// ============= Publisher ================= + +_capBus.Publish("place.order.qty.deducted", new { OrderId = 1234, ProductId = 23255, Qty = 1 }, "place.order.mark.status"); + +// publisher using `callbackName` to subscribe consumer result + +[CapSubscribe("place.order.mark.status")] +public void MarkOrderStatus(JToken param) +{ + var orderId = param.Value("OrderId"); + var isSuccess = param.Value("IsSuccess"); + + if(isSuccess) + //mark order status to succeeded + else + //mark order status to failed +} + +// ============= Consumer =================== + +[CapSubscribe("place.order.qty.deducted")] +public object DeductProductQty(JToken param) +{ + var orderId = param.Value("OrderId"); + var productId = param.Value("ProductId"); + var qty = param.Value("Qty"); + + //business logic + + return new { OrderId = orderId, IsSuccess = true }; +} +``` + +## 异构系统集成 + +在 3.0+ 版本中,我们对消息结构进行了重构,我们利用了消息队列中消息协议中的 Header 来传输一些额外信息,以便于在 Body 中我们可以做到不需要修改或包装使用者的原始消息数据格式和内容进行发送。 + +这样的做法是合理的,它有助于在异构系统中进行更好的集成,相对于以前的版本使用者不需要知道CAP内部使用的消息结构就可以完成集成工作。 + +现在我们将消息划分为 Header 和 Body 来进行传输。 + +Body 中的数据为用户发送的原始消息内容,也就是调用 Publish 方法发送的内容,我们不进行任何包装仅仅是序列化后传递到消息队列。 + +在 Header 中,我们需要传递一些额外信息以便于CAP在收到消息时能够提取到关键特征进行操作。 + +以下是在异构系统中,需要在发消息的时候向消息的Header 中写入的内容: + + 键 | 类型 | 说明 +-- | --| -- +cap-msg-id | string | 消息Id, 由雪花算法生成,也可以是 guid +cap-msg-name | string | 消息名称,即 Topic 名字 +cap-msg-type | string | 消息的类型, 即 typeof(T).FullName (非必须) +cap-senttime | stringg | 发送的时间 (非必须) + +以 Java 系统发送 RabbitMQ 为例: + +```java + +Map headers = new HashMap(); +headers.put("cap-msg-id", UUID.randomUUID().toString()); +headers.put("cap-msg-name", routingKey); + +channel.basicPublish(exchangeName, routingKey, + new AMQP.BasicProperties.Builder() + .headers(headers) + .build(), + messageBodyBytes); +// messageBodyBytes = "发送的json".getBytes(Charset.forName("UTF-8")) +// 注意 messageBody 默认为 json 的 byte[],如果采用其他系列化,需要在CAP侧自定义反序列化器 + +``` + ## Scheduling After CAP receives a message, it sends the message to Transport(RabitMq, Kafka...), which is transported by transport. diff --git a/docs/content/user-guide/zh/cap/messaging.md b/docs/content/user-guide/zh/cap/messaging.md index bd853a6..678520c 100644 --- a/docs/content/user-guide/zh/cap/messaging.md +++ b/docs/content/user-guide/zh/cap/messaging.md @@ -6,6 +6,50 @@ 你可以阅读 [quick-start](../getting-started/quick-start.md#_3) 来学习如何发送和处理消息。 +## 补偿事务 + +[Compensating transaction](https://en.wikipedia.org/wiki/Compensating_transaction) + +某些情况下,消费者需要返回值以告诉发布者执行结果,以便于发布者实施一些动作,通常情况下这属于补偿范围。 + +你可以在消费者执行的代码中通过重新发布一个新消息来通知上游,CAP 提供了一种简单的方式来做到这一点。 你可以在发送的时候指定 `callbackName` 来得到消费者的执行结果,通常这仅适用于点对点的消费。以下是一个示例。 + +例如,在一个电商程序中,订单初始状态为 pending,当商品数量成功扣除时将状态标记为 succeeded ,否则为 failed。 + +```C# +// ============= Publisher ================= + +_capBus.Publish("place.order.qty.deducted", new { OrderId = 1234, ProductId = 23255, Qty = 1 }, "place.order.mark.status"); + +// publisher using `callbackName` to subscribe consumer result + +[CapSubscribe("place.order.mark.status")] +public void MarkOrderStatus(JToken param) +{ + var orderId = param.Value("OrderId"); + var isSuccess = param.Value("IsSuccess"); + + if(isSuccess) + //mark order status to succeeded + else + //mark order status to failed +} + +// ============= Consumer =================== + +[CapSubscribe("place.order.qty.deducted")] +public object DeductProductQty(JToken param) +{ + var orderId = param.Value("OrderId"); + var productId = param.Value("ProductId"); + var qty = param.Value("Qty"); + + //business logic + + return new { OrderId = orderId, IsSuccess = true }; +} +``` + ## 异构系统集成 在 3.0+ 版本中,我们对消息结构进行了重构,我们利用了消息队列中消息协议中的 Header 来传输一些额外信息,以便于在 Body 中我们可以做到不需要修改或包装使用者的原始消息数据格式和内容进行发送。 diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 0d2daab..3eaf87d 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -81,7 +81,6 @@ nav: - CAP: - Configuration: user-guide/en/cap/configuration.md - Messaging: user-guide/en/cap/messaging.md - - Sagas: user-guide/en/cap/sagas.md - Serialization: user-guide/en/cap/serialization.md - Transactions: user-guide/en/cap/transactions.md - Idempotence: user-guide/en/cap/idempotence.md @@ -115,7 +114,6 @@ nav: - CAP: - 配置: user-guide/zh/cap/configuration.md - 消息: user-guide/zh/cap/messaging.md - - Sagas: user-guide/zh/cap/sagas.md - 序列化: user-guide/zh/cap/serialization.md - 运输: user-guide/zh/cap/transactions.md - 幂等性: user-guide/zh/cap/idempotence.md From 14148591d08e723d67c805fa83a8767d1f03da62 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Wed, 23 Sep 2020 17:57:28 +0800 Subject: [PATCH 45/85] update docs --- docs/content/user-guide/zh/cap/messaging.md | 30 ++++++++++----------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/content/user-guide/zh/cap/messaging.md b/docs/content/user-guide/zh/cap/messaging.md index 678520c..f111849 100644 --- a/docs/content/user-guide/zh/cap/messaging.md +++ b/docs/content/user-guide/zh/cap/messaging.md @@ -50,28 +50,28 @@ public object DeductProductQty(JToken param) } ``` -## 异构系统集成 +## Heterogeneous system integration -在 3.0+ 版本中,我们对消息结构进行了重构,我们利用了消息队列中消息协议中的 Header 来传输一些额外信息,以便于在 Body 中我们可以做到不需要修改或包装使用者的原始消息数据格式和内容进行发送。 +In version 3.0+, we reconstructed the message structure. We used the Header in the message protocol in the message queue to transmit some additional information, so that we can do it in the Body without modifying or packaging the user’s original The message data format and content are sent. -这样的做法是合理的,它有助于在异构系统中进行更好的集成,相对于以前的版本使用者不需要知道CAP内部使用的消息结构就可以完成集成工作。 +This approach is reasonable. It helps to better integrate in heterogeneous systems. Compared with previous versions, users do not need to know the message structure used inside CAP to complete the integration work. -现在我们将消息划分为 Header 和 Body 来进行传输。 +Now we divide the message into Header and Body for transmission. -Body 中的数据为用户发送的原始消息内容,也就是调用 Publish 方法发送的内容,我们不进行任何包装仅仅是序列化后传递到消息队列。 +The data in the body is the content of the original message sent by the user, that is, the content sent by calling the Publish method. We do not perform any packaging, but send it to the message queue after serialization. -在 Header 中,我们需要传递一些额外信息以便于CAP在收到消息时能够提取到关键特征进行操作。 +In the Header, we need to pass some additional information so that the CAP can extract the key features for operation when the message is received. -以下是在异构系统中,需要在发消息的时候向消息的Header 中写入的内容: +The following is the content that needs to be written into the header of the message when sending a message in a heterogeneous system: - 键 | 类型 | 说明 + Key | DataType | Description -- | --| -- -cap-msg-id | string | 消息Id, 由雪花算法生成,也可以是 guid -cap-msg-name | string | 消息名称,即 Topic 名字 -cap-msg-type | string | 消息的类型, 即 typeof(T).FullName (非必须) -cap-senttime | stringg | 发送的时间 (非必须) +cap-msg-id | string | Message Id, Generated by snowflake algorithm, can also be guid +cap-msg-name | string | The name of the message +cap-msg-type | string | The type of message, `typeof(T).FullName`(not required) +cap-senttime | string | sending time (not required) -以 Java 系统发送 RabbitMQ 为例: +Take the Java system sending RabbitMQ as an example: ```java @@ -84,8 +84,8 @@ channel.basicPublish(exchangeName, routingKey, .headers(headers) .build(), messageBodyBytes); -// messageBodyBytes = "发送的json".getBytes(Charset.forName("UTF-8")) -// 注意 messageBody 默认为 json 的 byte[],如果采用其他系列化,需要在CAP侧自定义反序列化器 +// messageBodyBytes = "json".getBytes(Charset.forName("UTF-8")) +// Note that messageBody defaults to byte[] of json. If other serialization is used, the deserializer needs to be customized on the CAP side ``` From fadccc53ca06f301cbf06468ad9b4be98568dfd2 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Wed, 23 Sep 2020 18:00:31 +0800 Subject: [PATCH 46/85] Update docs. --- docs/content/user-guide/en/cap/messaging.md | 35 +++++++++++---------- docs/content/user-guide/zh/cap/messaging.md | 31 +++++++++--------- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/docs/content/user-guide/en/cap/messaging.md b/docs/content/user-guide/en/cap/messaging.md index f6ca2b1..f40b6fb 100644 --- a/docs/content/user-guide/en/cap/messaging.md +++ b/docs/content/user-guide/en/cap/messaging.md @@ -16,7 +16,9 @@ For example, in an e-commerce application, the initial status of the order is pe ```C# // ============= Publisher ================= -_capBus.Publish("place.order.qty.deducted", new { OrderId = 1234, ProductId = 23255, Qty = 1 }, "place.order.mark.status"); +_capBus.Publish("place.order.qty.deducted", + contentObj: new { OrderId = 1234, ProductId = 23255, Qty = 1 }, + callbackName: "place.order.mark.status"); // publisher using `callbackName` to subscribe consumer result @@ -47,28 +49,28 @@ public object DeductProductQty(JToken param) } ``` -## 异构系统集成 +## Heterogeneous system integration -在 3.0+ 版本中,我们对消息结构进行了重构,我们利用了消息队列中消息协议中的 Header 来传输一些额外信息,以便于在 Body 中我们可以做到不需要修改或包装使用者的原始消息数据格式和内容进行发送。 +In version 3.0+, we reconstructed the message structure. We used the Header in the message protocol in the message queue to transmit some additional information, so that we can do it in the Body without modifying or packaging the user’s original The message data format and content are sent. -这样的做法是合理的,它有助于在异构系统中进行更好的集成,相对于以前的版本使用者不需要知道CAP内部使用的消息结构就可以完成集成工作。 +This approach is reasonable. It helps to better integrate in heterogeneous systems. Compared with previous versions, users do not need to know the message structure used inside CAP to complete the integration work. -现在我们将消息划分为 Header 和 Body 来进行传输。 +Now we divide the message into Header and Body for transmission. -Body 中的数据为用户发送的原始消息内容,也就是调用 Publish 方法发送的内容,我们不进行任何包装仅仅是序列化后传递到消息队列。 +The data in the body is the content of the original message sent by the user, that is, the content sent by calling the Publish method. We do not perform any packaging, but send it to the message queue after serialization. -在 Header 中,我们需要传递一些额外信息以便于CAP在收到消息时能够提取到关键特征进行操作。 +In the Header, we need to pass some additional information so that the CAP can extract the key features for operation when the message is received. -以下是在异构系统中,需要在发消息的时候向消息的Header 中写入的内容: +The following is the content that needs to be written into the header of the message when sending a message in a heterogeneous system: - 键 | 类型 | 说明 + Key | DataType | Description -- | --| -- -cap-msg-id | string | 消息Id, 由雪花算法生成,也可以是 guid -cap-msg-name | string | 消息名称,即 Topic 名字 -cap-msg-type | string | 消息的类型, 即 typeof(T).FullName (非必须) -cap-senttime | stringg | 发送的时间 (非必须) +cap-msg-id | string | Message Id, Generated by snowflake algorithm, can also be guid +cap-msg-name | string | The name of the message +cap-msg-type | string | The type of message, `typeof(T).FullName`(not required) +cap-senttime | string | sending time (not required) -以 Java 系统发送 RabbitMQ 为例: +Take the Java system sending RabbitMQ as an example: ```java @@ -81,11 +83,10 @@ channel.basicPublish(exchangeName, routingKey, .headers(headers) .build(), messageBodyBytes); -// messageBodyBytes = "发送的json".getBytes(Charset.forName("UTF-8")) -// 注意 messageBody 默认为 json 的 byte[],如果采用其他系列化,需要在CAP侧自定义反序列化器 +// messageBodyBytes = "json".getBytes(Charset.forName("UTF-8")) +// Note that messageBody defaults to byte[] of json. If other serialization is used, the deserializer needs to be customized on the CAP side ``` - ## Scheduling After CAP receives a message, it sends the message to Transport(RabitMq, Kafka...), which is transported by transport. diff --git a/docs/content/user-guide/zh/cap/messaging.md b/docs/content/user-guide/zh/cap/messaging.md index f111849..1a4608d 100644 --- a/docs/content/user-guide/zh/cap/messaging.md +++ b/docs/content/user-guide/zh/cap/messaging.md @@ -50,28 +50,28 @@ public object DeductProductQty(JToken param) } ``` -## Heterogeneous system integration +## 异构系统集成 -In version 3.0+, we reconstructed the message structure. We used the Header in the message protocol in the message queue to transmit some additional information, so that we can do it in the Body without modifying or packaging the user’s original The message data format and content are sent. +在 3.0+ 版本中,我们对消息结构进行了重构,我们利用了消息队列中消息协议中的 Header 来传输一些额外信息,以便于在 Body 中我们可以做到不需要修改或包装使用者的原始消息数据格式和内容进行发送。 -This approach is reasonable. It helps to better integrate in heterogeneous systems. Compared with previous versions, users do not need to know the message structure used inside CAP to complete the integration work. +这样的做法是合理的,它有助于在异构系统中进行更好的集成,相对于以前的版本使用者不需要知道CAP内部使用的消息结构就可以完成集成工作。 -Now we divide the message into Header and Body for transmission. +现在我们将消息划分为 Header 和 Body 来进行传输。 -The data in the body is the content of the original message sent by the user, that is, the content sent by calling the Publish method. We do not perform any packaging, but send it to the message queue after serialization. +Body 中的数据为用户发送的原始消息内容,也就是调用 Publish 方法发送的内容,我们不进行任何包装仅仅是序列化后传递到消息队列。 -In the Header, we need to pass some additional information so that the CAP can extract the key features for operation when the message is received. +在 Header 中,我们需要传递一些额外信息以便于CAP在收到消息时能够提取到关键特征进行操作。 -The following is the content that needs to be written into the header of the message when sending a message in a heterogeneous system: +以下是在异构系统中,需要在发消息的时候向消息的Header 中写入的内容: - Key | DataType | Description + 键 | 类型 | 说明 -- | --| -- -cap-msg-id | string | Message Id, Generated by snowflake algorithm, can also be guid -cap-msg-name | string | The name of the message -cap-msg-type | string | The type of message, `typeof(T).FullName`(not required) -cap-senttime | string | sending time (not required) +cap-msg-id | string | 消息Id, 由雪花算法生成,也可以是 guid +cap-msg-name | string | 消息名称,即 Topic 名字 +cap-msg-type | string | 消息的类型, 即 typeof(T).FullName (非必须) +cap-senttime | stringg | 发送的时间 (非必须) -Take the Java system sending RabbitMQ as an example: +以 Java 系统发送 RabbitMQ 为例: ```java @@ -84,12 +84,11 @@ channel.basicPublish(exchangeName, routingKey, .headers(headers) .build(), messageBodyBytes); -// messageBodyBytes = "json".getBytes(Charset.forName("UTF-8")) -// Note that messageBody defaults to byte[] of json. If other serialization is used, the deserializer needs to be customized on the CAP side +// messageBodyBytes = "发送的json".getBytes(Charset.forName("UTF-8")) +// 注意 messageBody 默认为 json 的 byte[],如果采用其他系列化,需要在CAP侧自定义反序列化器 ``` - ## 消息调度 CAP 接收到消息之后会将消息发送到 Transport, 由 Transport 进行运输。 From 01053b1a845d47acac80ab76f964ae9104ec0d8c Mon Sep 17 00:00:00 2001 From: Henery309 <59211495+Henery309@users.noreply.github.com> Date: Thu, 24 Sep 2020 02:18:09 +1200 Subject: [PATCH 47/85] Set correlation Id for azure service bus message (#673) * Update TransportMessage.cs Added GetCorrelationId to TransportMessage * Update ITransport.AzureServiceBus.cs Set CorrelationId for Microsoft.Azure.ServiceBus.Message --- .../ITransport.AzureServiceBus.cs | 5 +++-- src/DotNetCore.CAP/Messages/TransportMessage.cs | 5 +++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/DotNetCore.CAP.AzureServiceBus/ITransport.AzureServiceBus.cs b/src/DotNetCore.CAP.AzureServiceBus/ITransport.AzureServiceBus.cs index 763e381..fbac724 100644 --- a/src/DotNetCore.CAP.AzureServiceBus/ITransport.AzureServiceBus.cs +++ b/src/DotNetCore.CAP.AzureServiceBus/ITransport.AzureServiceBus.cs @@ -42,7 +42,8 @@ namespace DotNetCore.CAP.AzureServiceBus { MessageId = transportMessage.GetId(), Body = transportMessage.Body, - Label = transportMessage.GetName() + Label = transportMessage.GetName(), + CorrelationId = transportMessage.GetCorrelationId() }; foreach (var header in transportMessage.Headers) @@ -86,4 +87,4 @@ namespace DotNetCore.CAP.AzureServiceBus } } } -} \ No newline at end of file +} diff --git a/src/DotNetCore.CAP/Messages/TransportMessage.cs b/src/DotNetCore.CAP/Messages/TransportMessage.cs index c4f7fa7..80c1fe7 100644 --- a/src/DotNetCore.CAP/Messages/TransportMessage.cs +++ b/src/DotNetCore.CAP/Messages/TransportMessage.cs @@ -43,5 +43,10 @@ namespace DotNetCore.CAP.Messages { return Headers.TryGetValue(Messages.Headers.Group, out var value) ? value : null; } + + public string GetCorrelationId() + { + return Headers.TryGetValue(Messages.Headers.CorrelationId, out var value) ? value : null; + } } } From 623966fb91c54c4e835f95892f99ada180eaac05 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Wed, 23 Sep 2020 22:20:39 +0800 Subject: [PATCH 48/85] Add support for custom message id. #668 --- .../Internal/ICapPublisher.Default.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/DotNetCore.CAP/Internal/ICapPublisher.Default.cs b/src/DotNetCore.CAP/Internal/ICapPublisher.Default.cs index 01141cd..c238bf8 100644 --- a/src/DotNetCore.CAP/Internal/ICapPublisher.Default.cs +++ b/src/DotNetCore.CAP/Internal/ICapPublisher.Default.cs @@ -63,21 +63,22 @@ namespace DotNetCore.CAP.Internal throw new ArgumentNullException(nameof(name)); } - if (headers == null) + headers ??= new Dictionary(); + + if (!headers.ContainsKey(Headers.MessageId)) { - headers = new Dictionary(); + var messageId = SnowflakeId.Default().NextId().ToString(); + headers.Add(Headers.MessageId, messageId); } - - var messageId = SnowflakeId.Default().NextId().ToString(); - headers.Add(Headers.MessageId, messageId); - headers.Add(Headers.MessageName, name); - headers.Add(Headers.Type, typeof(T).Name); - headers.Add(Headers.SentTime, DateTimeOffset.Now.ToString()); + if (!headers.ContainsKey(Headers.CorrelationId)) { - headers.Add(Headers.CorrelationId, messageId); + headers.Add(Headers.CorrelationId, headers[Headers.MessageId]); headers.Add(Headers.CorrelationSequence, 0.ToString()); } + headers.Add(Headers.MessageName, name); + headers.Add(Headers.Type, typeof(T).Name); + headers.Add(Headers.SentTime, DateTimeOffset.Now.ToString()); var message = new Message(headers, value); From 65f94bf9ef264ca7da0db41dd40990604fd3ebc9 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Thu, 24 Sep 2020 12:37:40 +0800 Subject: [PATCH 49/85] Update general.md --- docs/content/user-guide/zh/transport/general.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/user-guide/zh/transport/general.md b/docs/content/user-guide/zh/transport/general.md index 62dda8c..5739a4c 100644 --- a/docs/content/user-guide/zh/transport/general.md +++ b/docs/content/user-guide/zh/transport/general.md @@ -15,7 +15,7 @@ CAP 支持以下几种运输方式: ## 怎么选择运输器 🏳‍🌈 | RabbitMQ | Kafka | Azure Service Bus | In-Memory -:-- | :--: | :--: | :--: | :-- : +:-- | :--: | :--: | :--: | :--: **定位** | 可靠消息传输 | 实时数据处理 | 云 | 内存型,测试 **分布式** | ✔ | ✔ | ✔ |❌ **持久化** | ✔ | ✔ | ✔ | ❌ From edae04d6081b96c497dab3d2eab9abefb07c4a42 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Fri, 25 Sep 2020 16:55:42 +0800 Subject: [PATCH 50/85] Copyright update --- .../Internal/ObjectMethodExecutor/AwaitableInfo.cs | 4 ++-- .../Internal/ObjectMethodExecutor/CoercedAwaitableInfo.cs | 4 ++-- .../Internal/ObjectMethodExecutor/ObjectMethodExecutor.cs | 4 ++-- .../ObjectMethodExecutor/ObjectMethodExecutorAwaitable.cs | 4 ++-- .../ObjectMethodExecutor/ObjectMethodExecutorFSharpSupport.cs | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/AwaitableInfo.cs b/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/AwaitableInfo.cs index 5c494e9..7046303 100644 --- a/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/AwaitableInfo.cs +++ b/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/AwaitableInfo.cs @@ -1,5 +1,5 @@ -// Copyright (c) .NET Core Community. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Linq; diff --git a/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/CoercedAwaitableInfo.cs b/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/CoercedAwaitableInfo.cs index 168e2e9..3f923e0 100644 --- a/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/CoercedAwaitableInfo.cs +++ b/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/CoercedAwaitableInfo.cs @@ -1,5 +1,5 @@ -// Copyright (c) .NET Core Community. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Linq.Expressions; diff --git a/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/ObjectMethodExecutor.cs b/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/ObjectMethodExecutor.cs index cf25fc9..26708a2 100644 --- a/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/ObjectMethodExecutor.cs +++ b/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/ObjectMethodExecutor.cs @@ -1,5 +1,5 @@ -// Copyright (c) .NET Core Community. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; diff --git a/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/ObjectMethodExecutorAwaitable.cs b/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/ObjectMethodExecutorAwaitable.cs index e5becb1..ed2fee8 100644 --- a/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/ObjectMethodExecutorAwaitable.cs +++ b/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/ObjectMethodExecutorAwaitable.cs @@ -1,5 +1,5 @@ -// Copyright (c) .NET Core Community. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Runtime.CompilerServices; diff --git a/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/ObjectMethodExecutorFSharpSupport.cs b/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/ObjectMethodExecutorFSharpSupport.cs index 09a8fda..9ec3c41 100644 --- a/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/ObjectMethodExecutorFSharpSupport.cs +++ b/src/DotNetCore.CAP/Internal/ObjectMethodExecutor/ObjectMethodExecutorFSharpSupport.cs @@ -1,5 +1,5 @@ -// Copyright (c) .NET Core Community. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Linq; From 7c33b2503d5b20f9b1644cfe47de181d63f69fa7 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Fri, 25 Sep 2020 16:56:02 +0800 Subject: [PATCH 51/85] Delete unused file --- .../Serialization/StringSerializer.cs | 21 ------------------- 1 file changed, 21 deletions(-) delete mode 100644 src/DotNetCore.CAP/Serialization/StringSerializer.cs diff --git a/src/DotNetCore.CAP/Serialization/StringSerializer.cs b/src/DotNetCore.CAP/Serialization/StringSerializer.cs deleted file mode 100644 index 3c96647..0000000 --- a/src/DotNetCore.CAP/Serialization/StringSerializer.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) .NET Core Community. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using DotNetCore.CAP.Messages; -using Newtonsoft.Json; - -namespace DotNetCore.CAP.Serialization -{ - public class StringSerializer - { - public static string Serialize(Message message) - { - return JsonConvert.SerializeObject(message); - } - - public static Message DeSerialize(string json) - { - return JsonConvert.DeserializeObject(json); - } - } -} \ No newline at end of file From c97defa9c7ddf9b3c98d035a5674d22ec29a1e94 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Fri, 25 Sep 2020 16:57:09 +0800 Subject: [PATCH 52/85] Cleaning code --- .../Serialization/ISerializer.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/DotNetCore.CAP/Serialization/ISerializer.cs b/src/DotNetCore.CAP/Serialization/ISerializer.cs index ee6135a..da89e90 100644 --- a/src/DotNetCore.CAP/Serialization/ISerializer.cs +++ b/src/DotNetCore.CAP/Serialization/ISerializer.cs @@ -11,25 +11,25 @@ namespace DotNetCore.CAP.Serialization public interface ISerializer { /// - /// Serializes the given into a + /// Serializes the given into a string /// - Task SerializeAsync(Message message); + string Serialize(Message message); /// - /// Deserialize the given back into a - /// - Task DeserializeAsync(TransportMessage transportMessage, [CanBeNull] Type valueType); - - /// - /// Serializes the given into a string + /// Serializes the given into a /// - string Serialize(Message message); + Task SerializeAsync(Message message); /// /// Deserialize the given string into a /// Message Deserialize(string json); + /// + /// Deserialize the given back into a + /// + Task DeserializeAsync(TransportMessage transportMessage, [CanBeNull] Type valueType); + /// /// Deserialize the given object with the given Type into an object /// @@ -42,7 +42,7 @@ namespace DotNetCore.CAP.Serialization /// /// /// // Example implementation for System.Text.Json - /// public bool IsJsonType(object jsonObject) + /// public bool IsJsonType(object jsonObject) /// { /// return jsonObject is JsonElement; /// } From 50f0def6990c2f9b7b623701db24f0de7150edff Mon Sep 17 00:00:00 2001 From: Savorboard Date: Fri, 25 Sep 2020 16:58:06 +0800 Subject: [PATCH 53/85] Update version.props --- build/version.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/version.props b/build/version.props index 7018086..eda4f2c 100644 --- a/build/version.props +++ b/build/version.props @@ -2,7 +2,7 @@ 3 1 - 1 + 2 $(VersionMajor).$(VersionMinor).$(VersionPatch) From 1004b54ace4c3f391025a91dd3a27e0051aeb636 Mon Sep 17 00:00:00 2001 From: xiangxiren Date: Tue, 29 Sep 2020 11:15:13 +0800 Subject: [PATCH 54/85] Record the exception in the headers (#679) * Record the exception in the headers * Record only Exception.Message Co-authored-by: wandone\xlw <123456> --- .../IDataStorage.InMemory.cs | 2 ++ .../IDataStorage.MongoDB.cs | 2 ++ src/DotNetCore.CAP.MySql/IDataStorage.MySql.cs | 3 ++- .../IDataStorage.PostgreSql.cs | 3 ++- .../IDataStorage.SqlServer.cs | 3 ++- .../Internal/IConsumerRegister.Default.cs | 3 ++- .../Internal/IMessageSender.Default.cs | 3 ++- .../Internal/ISubscribeDispatcher.Default.cs | 9 +-------- src/DotNetCore.CAP/Messages/Message.cs | 17 ++++++++++++++--- 9 files changed, 29 insertions(+), 16 deletions(-) diff --git a/src/DotNetCore.CAP.InMemoryStorage/IDataStorage.InMemory.cs b/src/DotNetCore.CAP.InMemoryStorage/IDataStorage.InMemory.cs index 5730fec..e7569d8 100644 --- a/src/DotNetCore.CAP.InMemoryStorage/IDataStorage.InMemory.cs +++ b/src/DotNetCore.CAP.InMemoryStorage/IDataStorage.InMemory.cs @@ -35,6 +35,7 @@ namespace DotNetCore.CAP.InMemoryStorage { PublishedMessages[message.DbId].StatusName = state; PublishedMessages[message.DbId].ExpiresAt = message.ExpiresAt; + PublishedMessages[message.DbId].Content = _serializer.Serialize(message.Origin); return Task.CompletedTask; } @@ -42,6 +43,7 @@ namespace DotNetCore.CAP.InMemoryStorage { ReceivedMessages[message.DbId].StatusName = state; ReceivedMessages[message.DbId].ExpiresAt = message.ExpiresAt; + ReceivedMessages[message.DbId].Content = _serializer.Serialize(message.Origin); return Task.CompletedTask; } diff --git a/src/DotNetCore.CAP.MongoDB/IDataStorage.MongoDB.cs b/src/DotNetCore.CAP.MongoDB/IDataStorage.MongoDB.cs index 86f1e39..f6132ed 100644 --- a/src/DotNetCore.CAP.MongoDB/IDataStorage.MongoDB.cs +++ b/src/DotNetCore.CAP.MongoDB/IDataStorage.MongoDB.cs @@ -42,6 +42,7 @@ namespace DotNetCore.CAP.MongoDB var collection = _database.GetCollection(_options.Value.PublishedCollection); var updateDef = Builders.Update + .Set(x => x.Content, _serializer.Serialize(message.Origin)) .Set(x => x.Retries, message.Retries) .Set(x => x.ExpiresAt, message.ExpiresAt) .Set(x => x.StatusName, state.ToString("G")); @@ -54,6 +55,7 @@ namespace DotNetCore.CAP.MongoDB var collection = _database.GetCollection(_options.Value.ReceivedCollection); var updateDef = Builders.Update + .Set(x => x.Content, _serializer.Serialize(message.Origin)) .Set(x => x.Retries, message.Retries) .Set(x => x.ExpiresAt, message.ExpiresAt) .Set(x => x.StatusName, state.ToString("G")); diff --git a/src/DotNetCore.CAP.MySql/IDataStorage.MySql.cs b/src/DotNetCore.CAP.MySql/IDataStorage.MySql.cs index 3f3892c..72a0074 100644 --- a/src/DotNetCore.CAP.MySql/IDataStorage.MySql.cs +++ b/src/DotNetCore.CAP.MySql/IDataStorage.MySql.cs @@ -158,11 +158,12 @@ namespace DotNetCore.CAP.MySql private async Task ChangeMessageStateAsync(string tableName, MediumMessage message, StatusName state) { var sql = - $"UPDATE `{tableName}` SET `Retries` = @Retries,`ExpiresAt` = @ExpiresAt,`StatusName`=@StatusName WHERE `Id`=@Id;"; + $"UPDATE `{tableName}` SET `Content`=@Content,`Retries`=@Retries,`ExpiresAt`=@ExpiresAt,`StatusName`=@StatusName WHERE `Id`=@Id;"; object[] sqlParams = { new MySqlParameter("@Id", message.DbId), + new MySqlParameter("@Content", _serializer.Serialize(message.Origin)), new MySqlParameter("@Retries", message.Retries), new MySqlParameter("@ExpiresAt", message.ExpiresAt), new MySqlParameter("@StatusName", state.ToString("G")) diff --git a/src/DotNetCore.CAP.PostgreSql/IDataStorage.PostgreSql.cs b/src/DotNetCore.CAP.PostgreSql/IDataStorage.PostgreSql.cs index 7d7f602..162f0c1 100644 --- a/src/DotNetCore.CAP.PostgreSql/IDataStorage.PostgreSql.cs +++ b/src/DotNetCore.CAP.PostgreSql/IDataStorage.PostgreSql.cs @@ -160,11 +160,12 @@ namespace DotNetCore.CAP.PostgreSql private async Task ChangeMessageStateAsync(string tableName, MediumMessage message, StatusName state) { var sql = - $"UPDATE {tableName} SET \"Retries\"=@Retries,\"ExpiresAt\"=@ExpiresAt,\"StatusName\"=@StatusName WHERE \"Id\"=@Id"; + $"UPDATE {tableName} SET \"Content\"=@Content,\"Retries\"=@Retries,\"ExpiresAt\"=@ExpiresAt,\"StatusName\"=@StatusName WHERE \"Id\"=@Id"; object[] sqlParams = { new NpgsqlParameter("@Id", long.Parse(message.DbId)), + new NpgsqlParameter("@Content", _serializer.Serialize(message.Origin)), new NpgsqlParameter("@Retries", message.Retries), new NpgsqlParameter("@ExpiresAt", message.ExpiresAt), new NpgsqlParameter("@StatusName", state.ToString("G")) diff --git a/src/DotNetCore.CAP.SqlServer/IDataStorage.SqlServer.cs b/src/DotNetCore.CAP.SqlServer/IDataStorage.SqlServer.cs index 0db52d1..d927d0d 100644 --- a/src/DotNetCore.CAP.SqlServer/IDataStorage.SqlServer.cs +++ b/src/DotNetCore.CAP.SqlServer/IDataStorage.SqlServer.cs @@ -159,11 +159,12 @@ namespace DotNetCore.CAP.SqlServer private async Task ChangeMessageStateAsync(string tableName, MediumMessage message, StatusName state) { var sql = - $"UPDATE {tableName} SET Retries=@Retries,ExpiresAt=@ExpiresAt,StatusName=@StatusName WHERE Id=@Id"; + $"UPDATE {tableName} SET Content=@Content, Retries=@Retries,ExpiresAt=@ExpiresAt,StatusName=@StatusName WHERE Id=@Id"; object[] sqlParams = { new SqlParameter("@Id", message.DbId), + new SqlParameter("@Content", _serializer.Serialize(message.Origin)), new SqlParameter("@Retries", message.Retries), new SqlParameter("@ExpiresAt", message.ExpiresAt), new SqlParameter("@StatusName", state.ToString("G")) diff --git a/src/DotNetCore.CAP/Internal/IConsumerRegister.Default.cs b/src/DotNetCore.CAP/Internal/IConsumerRegister.Default.cs index 9b1098b..c309be7 100644 --- a/src/DotNetCore.CAP/Internal/IConsumerRegister.Default.cs +++ b/src/DotNetCore.CAP/Internal/IConsumerRegister.Default.cs @@ -177,6 +177,7 @@ namespace DotNetCore.CAP.Internal var type = executor.Parameters.FirstOrDefault(x => x.IsFromCap == false)?.ParameterType; message = await _serializer.DeserializeAsync(transportMessage, type); + message.RemoveException(); } catch (Exception e) { @@ -276,7 +277,7 @@ namespace DotNetCore.CAP.Internal break; case MqLogType.MessageNotInflight: _logger.LogError("AmazonSQS subscriber change message's visibility failed, message isn't in flight. --> " + logmsg.Reason); - break; + break; default: throw new ArgumentOutOfRangeException(); } diff --git a/src/DotNetCore.CAP/Internal/IMessageSender.Default.cs b/src/DotNetCore.CAP/Internal/IMessageSender.Default.cs index 39c619c..76792d6 100644 --- a/src/DotNetCore.CAP/Internal/IMessageSender.Default.cs +++ b/src/DotNetCore.CAP/Internal/IMessageSender.Default.cs @@ -96,6 +96,7 @@ namespace DotNetCore.CAP.Internal { var needRetry = UpdateMessageForRetry(message); + message.Origin.AddOrUpdateException(ex); message.ExpiresAt = message.Added.AddDays(15); await _dataStorage.ChangePublishStateAsync(message, StatusName.Failed); @@ -118,7 +119,7 @@ namespace DotNetCore.CAP.Internal ServiceProvider = _serviceProvider, MessageType = MessageType.Publish, Message = message.Origin - }); + }); _logger.SenderAfterThreshold(message.DbId, _options.Value.FailedRetryCount); } diff --git a/src/DotNetCore.CAP/Internal/ISubscribeDispatcher.Default.cs b/src/DotNetCore.CAP/Internal/ISubscribeDispatcher.Default.cs index 1d1b9b3..8c57915 100644 --- a/src/DotNetCore.CAP/Internal/ISubscribeDispatcher.Default.cs +++ b/src/DotNetCore.CAP/Internal/ISubscribeDispatcher.Default.cs @@ -122,11 +122,9 @@ namespace DotNetCore.CAP.Internal message.Retries = _options.FailedRetryCount; // not retry if SubscriberNotFoundException } - //TODO: Add exception to content - // AddErrorReasonToContent(message, ex); - var needRetry = UpdateMessageForRetry(message); + message.Origin.AddOrUpdateException(ex); message.ExpiresAt = message.Added.AddDays(15); await _dataStorage.ChangeReceiveStateAsync(message, StatusName.Failed); @@ -167,11 +165,6 @@ namespace DotNetCore.CAP.Internal return true; } - //private static void AddErrorReasonToContent(CapReceivedMessage message, Exception exception) - //{ - // message.Content = Helper.AddExceptionProperty(message.Content, exception); - //} - private async Task InvokeConsumerMethodAsync(MediumMessage message, ConsumerExecutorDescriptor descriptor, CancellationToken cancellationToken) { var consumerContext = new ConsumerContext(descriptor, message.Origin); diff --git a/src/DotNetCore.CAP/Messages/Message.cs b/src/DotNetCore.CAP/Messages/Message.cs index eb52659..675fdad 100644 --- a/src/DotNetCore.CAP/Messages/Message.cs +++ b/src/DotNetCore.CAP/Messages/Message.cs @@ -13,7 +13,7 @@ namespace DotNetCore.CAP.Messages /// System.Text.Json requires that class explicitly has a parameterless constructor /// and public properties have a setter. ///
- public Message() {} + public Message() { } public Message(IDictionary headers, [CanBeNull] object value) { @@ -67,6 +67,17 @@ namespace DotNetCore.CAP.Messages { return message.Headers.ContainsKey(Headers.Exception); } - } -} \ No newline at end of file + public static void AddOrUpdateException(this Message message, Exception ex) + { + var msg = $"{ex.GetType().Name}-->{ex.Message}"; + + message.Headers[Headers.Exception] = msg; + } + + public static void RemoveException(this Message message) + { + message.Headers.Remove(Headers.Exception); + } + } +} From 185ad25a5023dee920cec17d5feb45bd696f5fd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=A8=E6=9C=A8?= <67222160+difuteam@users.noreply.github.com> Date: Mon, 5 Oct 2020 13:10:49 +0800 Subject: [PATCH 55/85] Update general.md (#681) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加RedisMQ扩展 --- docs/content/user-guide/zh/storage/general.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/content/user-guide/zh/storage/general.md b/docs/content/user-guide/zh/storage/general.md index 512e0b6..3bbcf89 100644 --- a/docs/content/user-guide/zh/storage/general.md +++ b/docs/content/user-guide/zh/storage/general.md @@ -75,3 +75,5 @@ CallbackName | 回调的订阅者名称 | string * SQLite & Oracle ([@cocosip](https://github.com/cocosip)) :https://github.com/cocosip/CAP-Extensions * SmartSql ([@xiangxiren](https://github.com/xiangxiren)) :https://github.com/xiangxiren/SmartSql.CAP + +* RedisMQ ([@木木](https://github.com/difuteam)) :https://github.com/difudotnet/CAP.RedisMQ.Extensions From ea1639f3622baaf6d985af111c4f208fd3839658 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=A8=E6=9C=A8?= <67222160+difuteam@users.noreply.github.com> Date: Mon, 5 Oct 2020 13:11:16 +0800 Subject: [PATCH 56/85] Update general.md (#682) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加 RedisMQ 扩展 --- docs/content/user-guide/zh/transport/general.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/content/user-guide/zh/transport/general.md b/docs/content/user-guide/zh/transport/general.md index 5739a4c..ee47cfd 100644 --- a/docs/content/user-guide/zh/transport/general.md +++ b/docs/content/user-guide/zh/transport/general.md @@ -34,3 +34,4 @@ CAP 支持以下几种运输方式: * ZeroMQ ([@maikebing](https://github.com/maikebing)) https://github.com/maikebing/CAP.Extensions/tree/master/src/DotNetCore.CAP.ZeroMQ +* RedisMQ ([@木木](https://github.com/difudotnet)) https://github.com/difudotnet/CAP.RedisMQ.Extensions From f3c0aeec326220907201a7cf721dbae5dea48f14 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Wed, 7 Oct 2020 18:56:22 +0800 Subject: [PATCH 57/85] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7949dd2..8778e79 100644 --- a/README.md +++ b/README.md @@ -32,12 +32,13 @@ CAP can be installed in your project with the following command. PM> Install-Package DotNetCore.CAP ``` -CAP supports RabbitMQ, Kafka and AzureService as message queue, following packages are available to install: +CAP supports RabbitMQ, Kafka, AzureService, AmazonSQS as message queue, following packages are available to install: ``` PM> Install-Package DotNetCore.CAP.Kafka PM> Install-Package DotNetCore.CAP.RabbitMQ PM> Install-Package DotNetCore.CAP.AzureServiceBus +PM> Install-Package DotNetCore.CAP.AmazonSQS ``` CAP supports SqlServer, MySql, PostgreSql,MongoDB as event log storage. @@ -80,6 +81,7 @@ public void ConfigureServices(IServiceCollection services) x.UseRabbitMQ("ConnectionString"); x.UseKafka("ConnectionString"); x.UseAzureServiceBus("ConnectionString"); + x.UseAmazonSQS(); }); } From 8ddeda2f4450f255643e4690befe9bdaa0471915 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Sun, 11 Oct 2020 21:30:06 +0800 Subject: [PATCH 58/85] Fix docs --- docs/mkdocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 3eaf87d..2744da1 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -115,7 +115,7 @@ nav: - 配置: user-guide/zh/cap/configuration.md - 消息: user-guide/zh/cap/messaging.md - 序列化: user-guide/zh/cap/serialization.md - - 运输: user-guide/zh/cap/transactions.md + - 事务: user-guide/zh/cap/transactions.md - 幂等性: user-guide/zh/cap/idempotence.md - 传输: - 简介: user-guide/zh/transport/general.md From a5c7b3faa5389f0e8ba893fa0091768f38493cd2 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Tue, 13 Oct 2020 21:30:18 +0800 Subject: [PATCH 59/85] Fix docs --- docs/content/user-guide/en/transport/general.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/content/user-guide/en/transport/general.md b/docs/content/user-guide/en/transport/general.md index 7c627dd..601eb6c 100644 --- a/docs/content/user-guide/en/transport/general.md +++ b/docs/content/user-guide/en/transport/general.md @@ -32,5 +32,11 @@ CAP supports several transport methods: Thanks to the community for supporting CAP, the following is the implementation of community-supported transport +* ActiveMQ (@[Lukas Zhang](https://github.com/lukazh/Lukaz.CAP.ActiveMQ)): https://github.com/lukazh + +* RedisMQ ([@木木](https://github.com/difudotnet)) https://github.com/difudotnet/CAP.RedisMQ.Extensions + * ZeroMQ ([@maikebing](https://github.com/maikebing)): https://github.com/maikebing/CAP.Extensions + + From 2170e5c5949a50ab923347d98ceb9d8b30f2e288 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Wed, 14 Oct 2020 20:58:57 +0800 Subject: [PATCH 60/85] Upgrade Confluent.Kafka nuget package --- src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj b/src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj index a8b8951..660bf9f 100644 --- a/src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj +++ b/src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj @@ -13,11 +13,11 @@ - + - \ No newline at end of file + From afaf20a3f2319ea3888179a992833c298d6dac8b Mon Sep 17 00:00:00 2001 From: Savorboard Date: Wed, 14 Oct 2020 21:30:39 +0800 Subject: [PATCH 61/85] Fix docs --- docs/content/user-guide/zh/transport/general.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/content/user-guide/zh/transport/general.md b/docs/content/user-guide/zh/transport/general.md index ee47cfd..268e4d8 100644 --- a/docs/content/user-guide/zh/transport/general.md +++ b/docs/content/user-guide/zh/transport/general.md @@ -32,6 +32,8 @@ CAP 支持以下几种运输方式: 感谢社区对CAP的支持,以下是社区支持的运输器实现 -* ZeroMQ ([@maikebing](https://github.com/maikebing)) https://github.com/maikebing/CAP.Extensions/tree/master/src/DotNetCore.CAP.ZeroMQ +* ActiveMQ (@[Lukas Zhang](https://github.com/lukazh/Lukaz.CAP.ActiveMQ)): https://github.com/lukazh * RedisMQ ([@木木](https://github.com/difudotnet)) https://github.com/difudotnet/CAP.RedisMQ.Extensions + +* ZeroMQ ([@maikebing](https://github.com/maikebing)) https://github.com/maikebing/CAP.Extensions/tree/master/src/DotNetCore.CAP.ZeroMQ \ No newline at end of file From ec22b181af34e9ad6dd159fbb9cfb902452abc43 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Tue, 20 Oct 2020 23:39:23 +0800 Subject: [PATCH 62/85] Update the sample to remove test string --- samples/Sample.RabbitMQ.MongoDB/Startup.cs | 2 +- samples/Sample.RabbitMQ.MySql/AppDbContext.cs | 2 +- samples/Sample.RabbitMQ.SqlServer/AppDbContext.cs | 2 +- samples/Sample.RabbitMQ.SqlServer/Startup.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/samples/Sample.RabbitMQ.MongoDB/Startup.cs b/samples/Sample.RabbitMQ.MongoDB/Startup.cs index bd80f11..b235a4c 100644 --- a/samples/Sample.RabbitMQ.MongoDB/Startup.cs +++ b/samples/Sample.RabbitMQ.MongoDB/Startup.cs @@ -20,7 +20,7 @@ namespace Sample.RabbitMQ.MongoDB services.AddCap(x => { x.UseMongoDB(Configuration.GetConnectionString("MongoDB")); - x.UseRabbitMQ("192.168.2.120"); + x.UseRabbitMQ(""); x.UseDashboard(); }); services.AddControllers(); diff --git a/samples/Sample.RabbitMQ.MySql/AppDbContext.cs b/samples/Sample.RabbitMQ.MySql/AppDbContext.cs index 0481e10..526424d 100644 --- a/samples/Sample.RabbitMQ.MySql/AppDbContext.cs +++ b/samples/Sample.RabbitMQ.MySql/AppDbContext.cs @@ -26,7 +26,7 @@ namespace Sample.RabbitMQ.MySql } public class AppDbContext : DbContext { - public const string ConnectionString = "Server=localhost;Database=testcap;UserId=root;Password=123123;"; + public const string ConnectionString = ""; public DbSet Persons { get; set; } diff --git a/samples/Sample.RabbitMQ.SqlServer/AppDbContext.cs b/samples/Sample.RabbitMQ.SqlServer/AppDbContext.cs index fdad2a5..e24e04d 100644 --- a/samples/Sample.RabbitMQ.SqlServer/AppDbContext.cs +++ b/samples/Sample.RabbitMQ.SqlServer/AppDbContext.cs @@ -28,7 +28,7 @@ namespace Sample.RabbitMQ.SqlServer public class AppDbContext : DbContext { - public const string ConnectionString = "Server=192.168.2.120;Database=captest;User Id=sa;Password=P@ssw0rd;"; + public const string ConnectionString = ""; public DbSet Persons { get; set; } diff --git a/samples/Sample.RabbitMQ.SqlServer/Startup.cs b/samples/Sample.RabbitMQ.SqlServer/Startup.cs index a309637..69f76f8 100644 --- a/samples/Sample.RabbitMQ.SqlServer/Startup.cs +++ b/samples/Sample.RabbitMQ.SqlServer/Startup.cs @@ -15,7 +15,7 @@ namespace Sample.RabbitMQ.SqlServer services.AddCap(x => { x.UseEntityFramework(); - x.UseRabbitMQ("192.168.2.120"); + x.UseRabbitMQ(""); x.UseDashboard(); x.FailedRetryCount = 5; x.FailedThresholdCallback = failed => From cbde44041456771f8f412575f4cf111f165f7889 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Mon, 19 Oct 2020 20:46:39 +0800 Subject: [PATCH 63/85] Upgrade dependent nuget packages to latest --- src/DotNetCore.CAP.AmazonSQS/DotNetCore.CAP.AmazonSQS.csproj | 4 ++-- src/DotNetCore.CAP.MongoDB/DotNetCore.CAP.MongoDB.csproj | 4 ++-- src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj | 2 +- .../DotNetCore.CAP.PostgreSql.csproj | 2 +- src/DotNetCore.CAP.RabbitMQ/DotNetCore.CAP.RabbitMQ.csproj | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/DotNetCore.CAP.AmazonSQS/DotNetCore.CAP.AmazonSQS.csproj b/src/DotNetCore.CAP.AmazonSQS/DotNetCore.CAP.AmazonSQS.csproj index 4b5f8bd..45db3a3 100644 --- a/src/DotNetCore.CAP.AmazonSQS/DotNetCore.CAP.AmazonSQS.csproj +++ b/src/DotNetCore.CAP.AmazonSQS/DotNetCore.CAP.AmazonSQS.csproj @@ -12,8 +12,8 @@ - - + + diff --git a/src/DotNetCore.CAP.MongoDB/DotNetCore.CAP.MongoDB.csproj b/src/DotNetCore.CAP.MongoDB/DotNetCore.CAP.MongoDB.csproj index fef7eae..6140851 100644 --- a/src/DotNetCore.CAP.MongoDB/DotNetCore.CAP.MongoDB.csproj +++ b/src/DotNetCore.CAP.MongoDB/DotNetCore.CAP.MongoDB.csproj @@ -16,8 +16,8 @@ - - + + diff --git a/src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj b/src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj index 023bfc1..b5373de 100644 --- a/src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj +++ b/src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj @@ -14,7 +14,7 @@ - + diff --git a/src/DotNetCore.CAP.PostgreSql/DotNetCore.CAP.PostgreSql.csproj b/src/DotNetCore.CAP.PostgreSql/DotNetCore.CAP.PostgreSql.csproj index 9c89d80..912a9f3 100644 --- a/src/DotNetCore.CAP.PostgreSql/DotNetCore.CAP.PostgreSql.csproj +++ b/src/DotNetCore.CAP.PostgreSql/DotNetCore.CAP.PostgreSql.csproj @@ -14,7 +14,7 @@ - + diff --git a/src/DotNetCore.CAP.RabbitMQ/DotNetCore.CAP.RabbitMQ.csproj b/src/DotNetCore.CAP.RabbitMQ/DotNetCore.CAP.RabbitMQ.csproj index 927ad5d..1fc96d1 100644 --- a/src/DotNetCore.CAP.RabbitMQ/DotNetCore.CAP.RabbitMQ.csproj +++ b/src/DotNetCore.CAP.RabbitMQ/DotNetCore.CAP.RabbitMQ.csproj @@ -12,7 +12,7 @@ - + From bd1cbd685334070e2ac7252d8325cf05c98c8589 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Fri, 23 Oct 2020 22:56:42 +0800 Subject: [PATCH 64/85] Fix InmemoryQueue expired messages are not removed bug. #691 (#693) --- .../IDataStorage.InMemory.cs | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/DotNetCore.CAP.InMemoryStorage/IDataStorage.InMemory.cs b/src/DotNetCore.CAP.InMemoryStorage/IDataStorage.InMemory.cs index e7569d8..40a554d 100644 --- a/src/DotNetCore.CAP.InMemoryStorage/IDataStorage.InMemory.cs +++ b/src/DotNetCore.CAP.InMemoryStorage/IDataStorage.InMemory.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -27,9 +26,9 @@ namespace DotNetCore.CAP.InMemoryStorage _serializer = serializer; } - public static ConcurrentDictionary PublishedMessages { get; } = new ConcurrentDictionary(); + public static Dictionary PublishedMessages { get; } = new Dictionary(); - public static ConcurrentDictionary ReceivedMessages { get; } = new ConcurrentDictionary(); + public static Dictionary ReceivedMessages { get; } = new Dictionary(); public Task ChangePublishStateAsync(MediumMessage message, StatusName state) { @@ -122,10 +121,14 @@ namespace DotNetCore.CAP.InMemoryStorage var removed = 0; if (table == nameof(PublishedMessages)) { - var ids = PublishedMessages.Values.Where(x => x.ExpiresAt < timeout).Select(x => x.DbId).ToList(); + var ids = PublishedMessages.Values + .Where(x => x.ExpiresAt < timeout) + .Select(x => x.DbId) + .Take(batchCount); + foreach (var id in ids) { - if (PublishedMessages.TryRemove(id, out _)) + if (PublishedMessages.Remove(id)) { removed++; } @@ -133,15 +136,20 @@ namespace DotNetCore.CAP.InMemoryStorage } else { - var ids = ReceivedMessages.Values.Where(x => x.ExpiresAt < timeout).Select(x => x.DbId).ToList(); + var ids = ReceivedMessages.Values + .Where(x => x.ExpiresAt < timeout) + .Select(x => x.DbId) + .Take(batchCount); + foreach (var id in ids) { - if (PublishedMessages.TryRemove(id, out _)) + if (ReceivedMessages.Remove(id)) { removed++; } } - } + } + return Task.FromResult(removed); } From 755f21039012b5e51bbdaaff9abf50d4ca8e30b9 Mon Sep 17 00:00:00 2001 From: patheems Date: Mon, 26 Oct 2020 14:39:07 +0100 Subject: [PATCH 65/85] Executor key not unique (#696) Co-authored-by: Patrick Heemskerk --- src/DotNetCore.CAP/Internal/ISubscribeInvoker.Default.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/DotNetCore.CAP/Internal/ISubscribeInvoker.Default.cs b/src/DotNetCore.CAP/Internal/ISubscribeInvoker.Default.cs index bbaeb6b..76458ef 100644 --- a/src/DotNetCore.CAP/Internal/ISubscribeInvoker.Default.cs +++ b/src/DotNetCore.CAP/Internal/ISubscribeInvoker.Default.cs @@ -35,10 +35,11 @@ namespace DotNetCore.CAP.Internal cancellationToken.ThrowIfCancellationRequested(); var methodInfo = context.ConsumerDescriptor.MethodInfo; + var serviceTypeInfo = context.ConsumerDescriptor.ServiceTypeInfo.Name; _logger.LogDebug("Executing subscriber method : {0}", methodInfo.Name); - var key = $"{methodInfo.Module.Name}_{methodInfo.MetadataToken}"; + var key = $"{methodInfo.Module.Name}_{serviceTypeInfo}_{methodInfo.MetadataToken}"; var executor = _executors.GetOrAdd(key, x => ObjectMethodExecutor.Create(methodInfo, context.ConsumerDescriptor.ImplTypeInfo)); using var scope = _serviceProvider.CreateScope(); From 07076e52e088b21f1db6943fb367e7024a16d654 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Wed, 28 Oct 2020 22:01:15 +0800 Subject: [PATCH 66/85] Add release notes to docs --- docs/content/about/release-notes.md | 33 +++++++++++++++++++++++++++++ docs/mkdocs.yml | 2 +- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/docs/content/about/release-notes.md b/docs/content/about/release-notes.md index 35f03d8..df7b02e 100644 --- a/docs/content/about/release-notes.md +++ b/docs/content/about/release-notes.md @@ -1,5 +1,38 @@ # Release Notes +## Version 3.1.1 (2020-09-23) + +**Features:** + +* Add consumer parameter with interface suppport. (#669) +* Add custom correlation id and message id support. (#668) +* Enhanced custom serialization support. (#641) + +**Bug Fixed:** + +* Solve the issue of being duplicated executors from different assemblies. (#666) +* Added comparer to remove duplicate ConsumerExecutors. (#653) +* Add re-enable the auto create topics configuration item for Kafka, it's false by default. now is true. (#635) +* Fixed postgresql transaction rollback invoke bug. (#640) +* Fixed SQLServer table name customize bug. (#632) + +## Version 3.1.0 (2020-08-15) + +**Features:** + +* Add Amazon SQS support. (#597) +* Remove Dapper and replace with ADO.NET in storage project. (#583) +* Add debug symbols package to nuget. +* Upgrade dependent nuget package version to latest. +* English docs grammar correction. Thanks @mzorec + +**Bug Fixed:** + +* Fix mysql transaction rollback bug. (#598) +* Fix dashboard query bug. (#600) +* Fix mongo db query bug. (#611) +* Fix dashboard browser language detection bug. (#631) + ## Version 3.0.4 (2020-05-27) **Bug Fixed:** diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 2744da1..13e4a34 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -10,7 +10,7 @@ edit_uri: 'edit/master/docs/content' docs_dir: 'content' # Copyright -copyright: Copyright © 2017
NCC, Maintained by the CAP Team. +copyright: Copyright © 2020 NCC, Maintained by the CAP Team. #theme: material From 334db374790d023287a0737b4443e570528f5f58 Mon Sep 17 00:00:00 2001 From: patheems Date: Wed, 28 Oct 2020 15:02:46 +0100 Subject: [PATCH 67/85] Executor key change lead to possible null reference exception (#698) * Executor key not unique * Changed ServiceTypeInfo to name of ReflectedType due to null reference exceptions. Co-authored-by: Patrick Heemskerk --- src/DotNetCore.CAP/Internal/ISubscribeInvoker.Default.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DotNetCore.CAP/Internal/ISubscribeInvoker.Default.cs b/src/DotNetCore.CAP/Internal/ISubscribeInvoker.Default.cs index 76458ef..5cd12a9 100644 --- a/src/DotNetCore.CAP/Internal/ISubscribeInvoker.Default.cs +++ b/src/DotNetCore.CAP/Internal/ISubscribeInvoker.Default.cs @@ -35,11 +35,11 @@ namespace DotNetCore.CAP.Internal cancellationToken.ThrowIfCancellationRequested(); var methodInfo = context.ConsumerDescriptor.MethodInfo; - var serviceTypeInfo = context.ConsumerDescriptor.ServiceTypeInfo.Name; + var reflectedType = methodInfo.ReflectedType.Name; _logger.LogDebug("Executing subscriber method : {0}", methodInfo.Name); - var key = $"{methodInfo.Module.Name}_{serviceTypeInfo}_{methodInfo.MetadataToken}"; + var key = $"{methodInfo.Module.Name}_{reflectedType}_{methodInfo.MetadataToken}"; var executor = _executors.GetOrAdd(key, x => ObjectMethodExecutor.Create(methodInfo, context.ConsumerDescriptor.ImplTypeInfo)); using var scope = _serviceProvider.CreateScope(); From 07c3137d4035b371e26cafa7ee80dda7466e5bee Mon Sep 17 00:00:00 2001 From: Savorboard Date: Thu, 29 Oct 2020 10:07:02 +0800 Subject: [PATCH 68/85] Fix typo --- src/DotNetCore.CAP/Processor/IProcessingServer.Cap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DotNetCore.CAP/Processor/IProcessingServer.Cap.cs b/src/DotNetCore.CAP/Processor/IProcessingServer.Cap.cs index d864c7d..18fd663 100644 --- a/src/DotNetCore.CAP/Processor/IProcessingServer.Cap.cs +++ b/src/DotNetCore.CAP/Processor/IProcessingServer.Cap.cs @@ -77,7 +77,7 @@ namespace DotNetCore.CAP.Processor } catch (Exception ex) { - _logger.LogWarning(ex, "An exception was occured when disposing."); + _logger.LogWarning(ex, "An exception was occurred when disposing."); } finally { From a1fc03320016b293baa7db52c277931027280af6 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Thu, 29 Oct 2020 13:52:58 +0800 Subject: [PATCH 69/85] Fix sample code error. #701 --- .../Sample.RabbitMQ.SqlServer/AppDbContext.cs | 14 +---------- .../Controllers/ValuesController.cs | 23 ++++++++++++------- 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/samples/Sample.RabbitMQ.SqlServer/AppDbContext.cs b/samples/Sample.RabbitMQ.SqlServer/AppDbContext.cs index e24e04d..e8835c6 100644 --- a/samples/Sample.RabbitMQ.SqlServer/AppDbContext.cs +++ b/samples/Sample.RabbitMQ.SqlServer/AppDbContext.cs @@ -12,19 +12,7 @@ namespace Sample.RabbitMQ.SqlServer { return $"Name:{Name}, Id:{Id}"; } - } - - public class Person2 - { - public int Id { get; set; } - - public string Name { get; set; } - - public override string ToString() - { - return $"Name:{Name}, Id:{Id}"; - } - } + } public class AppDbContext : DbContext { diff --git a/samples/Sample.RabbitMQ.SqlServer/Controllers/ValuesController.cs b/samples/Sample.RabbitMQ.SqlServer/Controllers/ValuesController.cs index 24d1848..c7e32dc 100644 --- a/samples/Sample.RabbitMQ.SqlServer/Controllers/ValuesController.cs +++ b/samples/Sample.RabbitMQ.SqlServer/Controllers/ValuesController.cs @@ -1,5 +1,4 @@ using System; -using System.Data; using System.Threading.Tasks; using Dapper; using DotNetCore.CAP; @@ -21,7 +20,7 @@ namespace Sample.RabbitMQ.SqlServer.Controllers [Route("~/without/transaction")] public async Task WithoutTransaction() { - await _capBus.PublishAsync("sample.rabbitmq.mysql", new Person() + await _capBus.PublishAsync("sample.rabbitmq.sqlserver", new Person() { Id = 123, Name = "Bar" @@ -40,7 +39,11 @@ namespace Sample.RabbitMQ.SqlServer.Controllers //your business code connection.Execute("insert into test(name) values('test')", transaction: transaction); - _capBus.Publish("sample.rabbitmq.mysql", DateTime.Now); + _capBus.Publish("sample.rabbitmq.sqlserver", new Person() + { + Id = 123, + Name = "Bar" + }); } } @@ -54,21 +57,25 @@ namespace Sample.RabbitMQ.SqlServer.Controllers { dbContext.Persons.Add(new Person() { Name = "ef.transaction" }); - _capBus.Publish("sample.rabbitmq.mysql", DateTime.Now); + _capBus.Publish("sample.rabbitmq.sqlserver", new Person() + { + Id = 123, + Name = "Bar" + }); } return Ok(); } [NonAction] - [CapSubscribe("sample.rabbitmq.mysql")] - public void Subscriber(DateTime p) + [CapSubscribe("sample.rabbitmq.sqlserver")] + public void Subscriber(Person p) { Console.WriteLine($@"{DateTime.Now} Subscriber invoked, Info: {p}"); } [NonAction] - [CapSubscribe("sample.rabbitmq.mysql", Group = "group.test2")] - public void Subscriber2(DateTime p, [FromCap]CapHeader header) + [CapSubscribe("sample.rabbitmq.sqlserver", Group = "group.test2")] + public void Subscriber2(Person p, [FromCap]CapHeader header) { Console.WriteLine($@"{DateTime.Now} Subscriber invoked, Info: {p}"); } From 5cf83af13e7ba5f5bcafe6f12d358e16cb390575 Mon Sep 17 00:00:00 2001 From: virtual <1185513330@qq.com> Date: Thu, 12 Nov 2020 03:05:58 -0600 Subject: [PATCH 70/85] fix postgresql delete expires data logic error (#714) Signed-off-by: virtual <1185513330@qq.com> --- src/DotNetCore.CAP.PostgreSql/IDataStorage.PostgreSql.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DotNetCore.CAP.PostgreSql/IDataStorage.PostgreSql.cs b/src/DotNetCore.CAP.PostgreSql/IDataStorage.PostgreSql.cs index 162f0c1..da053f2 100644 --- a/src/DotNetCore.CAP.PostgreSql/IDataStorage.PostgreSql.cs +++ b/src/DotNetCore.CAP.PostgreSql/IDataStorage.PostgreSql.cs @@ -140,7 +140,7 @@ namespace DotNetCore.CAP.PostgreSql { await using var connection = new NpgsqlConnection(_options.Value.ConnectionString); var count = connection.ExecuteNonQuery( - $"DELETE FROM {table} WHERE \"ExpiresAt\" < @timeout AND \"Id\" IN (SELECT \"Id\" FROM {table} LIMIT @batchCount);", null, + $"DELETE FROM {table} WHERE \"Id\" IN (SELECT \"Id\" FROM {table} WHERE \"ExpiresAt\" < @timeout LIMIT @batchCount);", null, new NpgsqlParameter("@timeout", timeout), new NpgsqlParameter("@batchCount", batchCount)); return await Task.FromResult(count); From fcd95fe104799a5f837ccf658c56c8c1f6d0e170 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Mon, 16 Nov 2020 09:32:20 +0800 Subject: [PATCH 71/85] update docs --- docs/content/user-guide/zh/storage/general.md | 2 -- docs/content/user-guide/zh/transport/general.md | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/content/user-guide/zh/storage/general.md b/docs/content/user-guide/zh/storage/general.md index 3bbcf89..512e0b6 100644 --- a/docs/content/user-guide/zh/storage/general.md +++ b/docs/content/user-guide/zh/storage/general.md @@ -75,5 +75,3 @@ CallbackName | 回调的订阅者名称 | string * SQLite & Oracle ([@cocosip](https://github.com/cocosip)) :https://github.com/cocosip/CAP-Extensions * SmartSql ([@xiangxiren](https://github.com/xiangxiren)) :https://github.com/xiangxiren/SmartSql.CAP - -* RedisMQ ([@木木](https://github.com/difuteam)) :https://github.com/difudotnet/CAP.RedisMQ.Extensions diff --git a/docs/content/user-guide/zh/transport/general.md b/docs/content/user-guide/zh/transport/general.md index 268e4d8..de41965 100644 --- a/docs/content/user-guide/zh/transport/general.md +++ b/docs/content/user-guide/zh/transport/general.md @@ -34,6 +34,6 @@ CAP 支持以下几种运输方式: * ActiveMQ (@[Lukas Zhang](https://github.com/lukazh/Lukaz.CAP.ActiveMQ)): https://github.com/lukazh -* RedisMQ ([@木木](https://github.com/difudotnet)) https://github.com/difudotnet/CAP.RedisMQ.Extensions +* RedisMQ ([@木木](https://github.com/difudotnet)): https://github.com/difudotnet/CAP.RedisMQ.Extensions -* ZeroMQ ([@maikebing](https://github.com/maikebing)) https://github.com/maikebing/CAP.Extensions/tree/master/src/DotNetCore.CAP.ZeroMQ \ No newline at end of file +* ZeroMQ ([@maikebing](https://github.com/maikebing)): https://github.com/maikebing/CAP.Extensions/tree/master/src/DotNetCore.CAP.ZeroMQ \ No newline at end of file From ca031d3fc493e2aa89803e9d7e5fac845591a3ea Mon Sep 17 00:00:00 2001 From: niceguy3-31 <842007947@qq.com> Date: Mon, 16 Nov 2020 14:45:23 +0800 Subject: [PATCH 72/85] Update sqlserver.md (#720) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 应该是UseSqlServer而不是UsePostgreSql --- docs/content/user-guide/zh/storage/sqlserver.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/user-guide/zh/storage/sqlserver.md b/docs/content/user-guide/zh/storage/sqlserver.md index 1cd0ac6..be1f44f 100644 --- a/docs/content/user-guide/zh/storage/sqlserver.md +++ b/docs/content/user-guide/zh/storage/sqlserver.md @@ -25,7 +25,7 @@ public void ConfigureServices(IServiceCollection services) services.AddCap(x => { - x.UsePostgreSql(opt=>{ + x.UseSqlServer(opt=>{ //SqlServerOptions }); // x.UseXXX ... From 078effc3e819fc0b87c381ab7e4034f10f5c609f Mon Sep 17 00:00:00 2001 From: Savorboard Date: Mon, 16 Nov 2020 18:26:27 +0800 Subject: [PATCH 73/85] Refactoring --- src/DotNetCore.CAP.Kafka/CAP.KafkaOptions.cs | 32 ++----------------- .../IConnectionPool.Default.cs | 14 ++++++-- src/DotNetCore.CAP.Kafka/ITransport.Kafka.cs | 2 +- .../KafkaConsumerClient.cs | 12 +++++-- .../KafkaConsumerClientFactory.cs | 4 +-- 5 files changed, 24 insertions(+), 40 deletions(-) diff --git a/src/DotNetCore.CAP.Kafka/CAP.KafkaOptions.cs b/src/DotNetCore.CAP.Kafka/CAP.KafkaOptions.cs index 6cb12f1..0faa14f 100644 --- a/src/DotNetCore.CAP.Kafka/CAP.KafkaOptions.cs +++ b/src/DotNetCore.CAP.Kafka/CAP.KafkaOptions.cs @@ -2,9 +2,7 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using System; -using System.Collections.Concurrent; using System.Collections.Generic; -using System.Linq; using Confluent.Kafka; // ReSharper disable once CheckNamespace @@ -21,14 +19,11 @@ namespace DotNetCore.CAP /// Topic configuration parameters are specified via the "default.topic.config" sub-dictionary config parameter. /// /// - public readonly ConcurrentDictionary MainConfig; - - private IEnumerable> _kafkaConfig; - + public readonly Dictionary MainConfig; public KafkaOptions() { - MainConfig = new ConcurrentDictionary(); + MainConfig = new Dictionary(); } /// @@ -48,28 +43,5 @@ namespace DotNetCore.CAP /// If you need to get offset and partition and so on.., you can use this function to write additional header into /// public Func, List>> CustomHeaders { get; set; } - - internal IEnumerable> AsKafkaConfig() - { - if (_kafkaConfig == null) - { - if (string.IsNullOrWhiteSpace(Servers)) - { - throw new ArgumentNullException(nameof(Servers)); - } - - MainConfig["bootstrap.servers"] = Servers; - MainConfig["queue.buffering.max.ms"] = "10"; - MainConfig["allow.auto.create.topics"] = "true"; - MainConfig["enable.auto.commit"] = "false"; - MainConfig["log.connection.close"] = "false"; - MainConfig["request.timeout.ms"] = "3000"; - MainConfig["message.timeout.ms"] = "5000"; - - _kafkaConfig = MainConfig.AsEnumerable(); - } - - return _kafkaConfig; - } } } \ No newline at end of file diff --git a/src/DotNetCore.CAP.Kafka/IConnectionPool.Default.cs b/src/DotNetCore.CAP.Kafka/IConnectionPool.Default.cs index 714954d..9f1c023 100644 --- a/src/DotNetCore.CAP.Kafka/IConnectionPool.Default.cs +++ b/src/DotNetCore.CAP.Kafka/IConnectionPool.Default.cs @@ -3,11 +3,11 @@ using System; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Threading; using Confluent.Kafka; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Newtonsoft.Json; namespace DotNetCore.CAP.Kafka { @@ -24,7 +24,7 @@ namespace DotNetCore.CAP.Kafka _producerPool = new ConcurrentQueue>(); _maxSize = _options.ConnectionPoolSize; - logger.LogDebug("CAP Kafka configuration: {0}", JsonConvert.SerializeObject(_options.AsKafkaConfig(), Formatting.Indented)); + logger.LogDebug("CAP Kafka servers: {0}", _options.Servers); } public string ServersAddress => _options.Servers; @@ -38,7 +38,15 @@ namespace DotNetCore.CAP.Kafka return producer; } - producer = new ProducerBuilder(_options.AsKafkaConfig()).Build(); + var config = new ProducerConfig(new Dictionary(_options.MainConfig)) + { + BootstrapServers = _options.Servers, + QueueBufferingMaxMessages = 10, + MessageTimeoutMs = 5000, + RequestTimeoutMs = 3000 + }; + + producer = new ProducerBuilder(config).Build(); return producer; } diff --git a/src/DotNetCore.CAP.Kafka/ITransport.Kafka.cs b/src/DotNetCore.CAP.Kafka/ITransport.Kafka.cs index 60bcea7..28e59f9 100644 --- a/src/DotNetCore.CAP.Kafka/ITransport.Kafka.cs +++ b/src/DotNetCore.CAP.Kafka/ITransport.Kafka.cs @@ -39,7 +39,7 @@ namespace DotNetCore.CAP.Kafka ? new Header(header.Key, Encoding.UTF8.GetBytes(header.Value)) : new Header(header.Key, null)); } - + var result = await producer.ProduceAsync(message.GetName(), new Message { Headers = headers, diff --git a/src/DotNetCore.CAP.Kafka/KafkaConsumerClient.cs b/src/DotNetCore.CAP.Kafka/KafkaConsumerClient.cs index e7c4d6b..3299dee 100644 --- a/src/DotNetCore.CAP.Kafka/KafkaConsumerClient.cs +++ b/src/DotNetCore.CAP.Kafka/KafkaConsumerClient.cs @@ -106,9 +106,15 @@ namespace DotNetCore.CAP.Kafka { if (_consumerClient == null) { - _kafkaOptions.MainConfig["group.id"] = _groupId; - _kafkaOptions.MainConfig["auto.offset.reset"] = "earliest"; - var config = _kafkaOptions.AsKafkaConfig(); + var config = new ConsumerConfig(new Dictionary(_kafkaOptions.MainConfig)) + { + BootstrapServers = _kafkaOptions.Servers, + GroupId = _groupId, + AutoOffsetReset = AutoOffsetReset.Earliest, + AllowAutoCreateTopics = true, + EnableAutoCommit = false, + LogConnectionClose = false + }; _consumerClient = new ConsumerBuilder(config) .SetErrorHandler(ConsumerClient_OnConsumeError) diff --git a/src/DotNetCore.CAP.Kafka/KafkaConsumerClientFactory.cs b/src/DotNetCore.CAP.Kafka/KafkaConsumerClientFactory.cs index 1f68cd0..59374bd 100644 --- a/src/DotNetCore.CAP.Kafka/KafkaConsumerClientFactory.cs +++ b/src/DotNetCore.CAP.Kafka/KafkaConsumerClientFactory.cs @@ -19,9 +19,7 @@ namespace DotNetCore.CAP.Kafka { try { - var client = new KafkaConsumerClient(groupId, _kafkaOptions); - client.Connect(); - return client; + return new KafkaConsumerClient(groupId, _kafkaOptions); } catch (System.Exception e) { From 781de19f5ec56034829113c0820a1d7cbb4a5ce9 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Mon, 16 Nov 2020 18:26:36 +0800 Subject: [PATCH 74/85] Upgrade confluent kafka to 1.5.2 --- src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj b/src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj index 660bf9f..1f2f8ff 100644 --- a/src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj +++ b/src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj @@ -13,7 +13,7 @@ - + From 8946ca33af4ed7ec61c4313130a00f787d163da4 Mon Sep 17 00:00:00 2001 From: zhangq <943620963@qq.com> Date: Tue, 17 Nov 2020 09:30:56 +0800 Subject: [PATCH 75/85] support consul service check for https (#722) --- .../NodeDiscovery/CAP.DiscoveryOptions.cs | 6 +++++ .../INodeDiscoveryProvider.Consul.cs | 23 +++++++++++-------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/DotNetCore.CAP.Dashboard/NodeDiscovery/CAP.DiscoveryOptions.cs b/src/DotNetCore.CAP.Dashboard/NodeDiscovery/CAP.DiscoveryOptions.cs index 6b720eb..8d2e3b6 100644 --- a/src/DotNetCore.CAP.Dashboard/NodeDiscovery/CAP.DiscoveryOptions.cs +++ b/src/DotNetCore.CAP.Dashboard/NodeDiscovery/CAP.DiscoveryOptions.cs @@ -13,6 +13,8 @@ namespace DotNetCore.CAP.Dashboard.NodeDiscovery public const string DefaultMatchPath = "/cap"; + public const string DefaultScheme = "http"; + public DiscoveryOptions() { DiscoveryServerHostName = DefaultDiscoveryServerHost; @@ -22,6 +24,8 @@ namespace DotNetCore.CAP.Dashboard.NodeDiscovery CurrentNodePort = DefaultCurrentNodePort; MatchPath = DefaultMatchPath; + + Scheme = DefaultScheme; } public string DiscoveryServerHostName { get; set; } @@ -34,5 +38,7 @@ namespace DotNetCore.CAP.Dashboard.NodeDiscovery public string NodeName { get; set; } public string MatchPath { get; set; } + + public string Scheme { get; set; } } } \ No newline at end of file diff --git a/src/DotNetCore.CAP.Dashboard/NodeDiscovery/INodeDiscoveryProvider.Consul.cs b/src/DotNetCore.CAP.Dashboard/NodeDiscovery/INodeDiscoveryProvider.Consul.cs index 6947b2d..df8bd2b 100644 --- a/src/DotNetCore.CAP.Dashboard/NodeDiscovery/INodeDiscoveryProvider.Consul.cs +++ b/src/DotNetCore.CAP.Dashboard/NodeDiscovery/INodeDiscoveryProvider.Consul.cs @@ -69,21 +69,26 @@ namespace DotNetCore.CAP.Dashboard.NodeDiscovery { try { + var healthCheck = new AgentServiceCheck + { + DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(30), + Interval = TimeSpan.FromSeconds(10), + Status = HealthStatus.Passing + }; + + if (_options.Scheme.Equals("http", StringComparison.OrdinalIgnoreCase)) + healthCheck.HTTP = $"http://{_options.CurrentNodeHostName}:{_options.CurrentNodePort}{_options.MatchPath}/health"; + else if (_options.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase)) + healthCheck.TCP = $"{_options.CurrentNodeHostName}:{_options.CurrentNodePort}"; + return _consul.Agent.ServiceRegister(new AgentServiceRegistration { ID = _options.NodeId, Name = _options.NodeName, Address = _options.CurrentNodeHostName, Port = _options.CurrentNodePort, - Tags = new[] {"CAP", "Client", "Dashboard"}, - Check = new AgentServiceCheck - { - DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(30), - Interval = TimeSpan.FromSeconds(10), - Status = HealthStatus.Passing, - HTTP = - $"http://{_options.CurrentNodeHostName}:{_options.CurrentNodePort}{_options.MatchPath}/health" - } + Tags = new[] { "CAP", "Client", "Dashboard" }, + Check = healthCheck }); } catch (Exception ex) From 3528432277329a18931abd88e9dbf8c6177c85ff Mon Sep 17 00:00:00 2001 From: Savorboard Date: Mon, 30 Nov 2020 18:16:43 +0800 Subject: [PATCH 76/85] =?UTF-8?q?Support=20custom=20multiple=20producer=20?= =?UTF-8?q?threads=20for=20sending=E3=80=82=20#731?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/ValuesController.cs | 3 +++ src/DotNetCore.CAP/Processor/IDispatcher.Default.cs | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/samples/Sample.RabbitMQ.SqlServer/Controllers/ValuesController.cs b/samples/Sample.RabbitMQ.SqlServer/Controllers/ValuesController.cs index c7e32dc..bb75269 100644 --- a/samples/Sample.RabbitMQ.SqlServer/Controllers/ValuesController.cs +++ b/samples/Sample.RabbitMQ.SqlServer/Controllers/ValuesController.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using Dapper; using DotNetCore.CAP; +using DotNetCore.CAP.Messages; using Microsoft.AspNetCore.Mvc; using Microsoft.Data.SqlClient; @@ -77,6 +78,8 @@ namespace Sample.RabbitMQ.SqlServer.Controllers [CapSubscribe("sample.rabbitmq.sqlserver", Group = "group.test2")] public void Subscriber2(Person p, [FromCap]CapHeader header) { + var id = header[Headers.MessageId]; + Console.WriteLine($@"{DateTime.Now} Subscriber invoked, Info: {p}"); } } diff --git a/src/DotNetCore.CAP/Processor/IDispatcher.Default.cs b/src/DotNetCore.CAP/Processor/IDispatcher.Default.cs index 2139ee1..d2f21cc 100644 --- a/src/DotNetCore.CAP/Processor/IDispatcher.Default.cs +++ b/src/DotNetCore.CAP/Processor/IDispatcher.Default.cs @@ -34,10 +34,11 @@ namespace DotNetCore.CAP.Processor _sender = sender; _executor = executor; - _publishedChannel = Channel.CreateUnbounded(new UnboundedChannelOptions() { SingleReader = true, SingleWriter = true }); + _publishedChannel = Channel.CreateUnbounded(new UnboundedChannelOptions() { SingleReader = false, SingleWriter = true }); _receivedChannel = Channel.CreateUnbounded<(MediumMessage, ConsumerExecutorDescriptor)>(); - Task.Factory.StartNew(Sending, _cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default); + Task.WhenAll(Enumerable.Range(0, options.Value.ProducerThreadCount) + .Select(_ => Task.Factory.StartNew(Sending, _cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default)).ToArray()); Task.WhenAll(Enumerable.Range(0, options.Value.ConsumerThreadCount) .Select(_ => Task.Factory.StartNew(Processing, _cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default)).ToArray()); From d74d15ef1a49c43b704095445b1290ce116e220e Mon Sep 17 00:00:00 2001 From: Savorboard Date: Mon, 30 Nov 2020 18:22:34 +0800 Subject: [PATCH 77/85] =?UTF-8?q?Support=20custom=20multiple=20producer=20?= =?UTF-8?q?threads=20for=20sending=E3=80=82=20#731?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/DotNetCore.CAP/CAP.Options.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/DotNetCore.CAP/CAP.Options.cs b/src/DotNetCore.CAP/CAP.Options.cs index ebdbf91..2990a36 100644 --- a/src/DotNetCore.CAP/CAP.Options.cs +++ b/src/DotNetCore.CAP/CAP.Options.cs @@ -21,6 +21,7 @@ namespace DotNetCore.CAP FailedRetryInterval = 60; FailedRetryCount = 50; ConsumerThreadCount = 1; + ProducerThreadCount = 1; Extensions = new List(); Version = "v1"; DefaultGroup = "cap.queue." + Assembly.GetEntryAssembly()?.GetName().Name.ToLower(); @@ -67,6 +68,12 @@ namespace DotNetCore.CAP /// public int ConsumerThreadCount { get; set; } + /// + /// The number of producer thread connections. + /// Default is 1 + /// + public int ProducerThreadCount { get; set; } + /// /// Registers an extension that will be executed when building services. /// From 8cc49dba173128f65ee5a5403c5a4fa487d8a9d8 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Thu, 3 Dec 2020 13:59:47 +0800 Subject: [PATCH 78/85] Remove test connection string from sample --- samples/Sample.AmazonSQS.InMemory/appsettings.json | 2 +- samples/Sample.ConsoleApp/Program.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/Sample.AmazonSQS.InMemory/appsettings.json b/samples/Sample.AmazonSQS.InMemory/appsettings.json index 50fe9a3..ef6dc62 100644 --- a/samples/Sample.AmazonSQS.InMemory/appsettings.json +++ b/samples/Sample.AmazonSQS.InMemory/appsettings.json @@ -2,7 +2,7 @@ "Logging": { "IncludeScopes": false, "LogLevel": { - "Default": "Error" + "Default": "Information" } } } diff --git a/samples/Sample.ConsoleApp/Program.cs b/samples/Sample.ConsoleApp/Program.cs index f0315e1..6f9011a 100644 --- a/samples/Sample.ConsoleApp/Program.cs +++ b/samples/Sample.ConsoleApp/Program.cs @@ -16,7 +16,7 @@ namespace Sample.ConsoleApp { //console app does not support dashboard - x.UseMySql("Server=192.168.3.57;Port=3307;Database=captest;Uid=root;Pwd=123123;"); + x.UseMySql(""); x.UseRabbitMQ(z => { z.HostName = "192.168.3.57"; From cb0deca5792811cf2b74ab3fdb2992022826692f Mon Sep 17 00:00:00 2001 From: Vlad Leonov Date: Thu, 10 Dec 2020 03:05:18 +0200 Subject: [PATCH 79/85] Update sqlserver.md (#738) Changed the misprint of Postgre to SqlServer --- docs/content/user-guide/en/storage/sqlserver.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/user-guide/en/storage/sqlserver.md b/docs/content/user-guide/en/storage/sqlserver.md index c66a82a..f860d90 100644 --- a/docs/content/user-guide/en/storage/sqlserver.md +++ b/docs/content/user-guide/en/storage/sqlserver.md @@ -24,7 +24,7 @@ public void ConfigureServices(IServiceCollection services) services.AddCap(x => { - x.UsePostgreSql(opt=>{ + x.UseSqlServer(opt=>{ //SqlServerOptions }); // x.UseXXX ... From ef4190ceeca1e9d023dd86e58a2b102f5b87c545 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Tue, 15 Dec 2020 13:49:12 +0800 Subject: [PATCH 80/85] update version to 5.0.0 --- build/version.props | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build/version.props b/build/version.props index eda4f2c..7ac3182 100644 --- a/build/version.props +++ b/build/version.props @@ -1,8 +1,8 @@ - 3 - 1 - 2 + 5 + 0 + 0 $(VersionMajor).$(VersionMinor).$(VersionPatch) From e11e44ba7c01b46c88218637f83805e82ef8edf8 Mon Sep 17 00:00:00 2001 From: Reza3307 Date: Mon, 14 Dec 2020 21:50:02 -0800 Subject: [PATCH 81/85] Update nuget packages and support .Net 5 (#727) Co-authored-by: Solale90 --- build/BuildScript.csproj | 2 +- .../Sample.AmazonSQS.InMemory.csproj | 2 +- samples/Sample.ConsoleApp/Sample.ConsoleApp.csproj | 2 +- .../Sample.Kafka.PostgreSql/Sample.Kafka.PostgreSql.csproj | 2 +- .../Sample.RabbitMQ.MongoDB/Sample.RabbitMQ.MongoDB.csproj | 2 +- samples/Sample.RabbitMQ.MySql/AppDbContext.cs | 2 +- .../Sample.RabbitMQ.MySql/Migrations/20180821021736_init.cs | 6 +++--- samples/Sample.RabbitMQ.MySql/Sample.RabbitMQ.MySql.csproj | 4 ++-- .../Sample.RabbitMQ.SqlServer.csproj | 2 +- .../DotNetCore.CAP.AmazonSQS.csproj | 4 ++-- .../DotNetCore.CAP.AzureServiceBus.csproj | 4 ++-- .../DotNetCore.CAP.Dashboard.csproj | 2 +- .../DotNetCore.CAP.InMemoryStorage.csproj | 2 +- src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj | 4 ++-- src/DotNetCore.CAP.MongoDB/DotNetCore.CAP.MongoDB.csproj | 4 ++-- src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj | 4 ++-- .../DotNetCore.CAP.PostgreSql.csproj | 4 ++-- src/DotNetCore.CAP.RabbitMQ/DotNetCore.CAP.RabbitMQ.csproj | 4 ++-- .../DotNetCore.CAP.SqlServer.csproj | 4 ++-- src/DotNetCore.CAP/DotNetCore.CAP.csproj | 4 ++-- .../DotNetCore.CAP.MySql.Test.csproj | 2 +- test/DotNetCore.CAP.Test/DotNetCore.CAP.Test.csproj | 2 +- 22 files changed, 34 insertions(+), 34 deletions(-) diff --git a/build/BuildScript.csproj b/build/BuildScript.csproj index bb5c75c..b6f64b9 100644 --- a/build/BuildScript.csproj +++ b/build/BuildScript.csproj @@ -1,7 +1,7 @@  - netstandard2.0 + netstandard2.1 diff --git a/samples/Sample.AmazonSQS.InMemory/Sample.AmazonSQS.InMemory.csproj b/samples/Sample.AmazonSQS.InMemory/Sample.AmazonSQS.InMemory.csproj index c8ed0b3..7acd5c7 100644 --- a/samples/Sample.AmazonSQS.InMemory/Sample.AmazonSQS.InMemory.csproj +++ b/samples/Sample.AmazonSQS.InMemory/Sample.AmazonSQS.InMemory.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1 + net5.0 diff --git a/samples/Sample.ConsoleApp/Sample.ConsoleApp.csproj b/samples/Sample.ConsoleApp/Sample.ConsoleApp.csproj index fa5e69e..442d727 100644 --- a/samples/Sample.ConsoleApp/Sample.ConsoleApp.csproj +++ b/samples/Sample.ConsoleApp/Sample.ConsoleApp.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1 + net5.0 Exe diff --git a/samples/Sample.Kafka.PostgreSql/Sample.Kafka.PostgreSql.csproj b/samples/Sample.Kafka.PostgreSql/Sample.Kafka.PostgreSql.csproj index 90d24a4..3348e3b 100644 --- a/samples/Sample.Kafka.PostgreSql/Sample.Kafka.PostgreSql.csproj +++ b/samples/Sample.Kafka.PostgreSql/Sample.Kafka.PostgreSql.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1 + net5.0 NU1701 NU1701 diff --git a/samples/Sample.RabbitMQ.MongoDB/Sample.RabbitMQ.MongoDB.csproj b/samples/Sample.RabbitMQ.MongoDB/Sample.RabbitMQ.MongoDB.csproj index 831dc9c..3b31902 100644 --- a/samples/Sample.RabbitMQ.MongoDB/Sample.RabbitMQ.MongoDB.csproj +++ b/samples/Sample.RabbitMQ.MongoDB/Sample.RabbitMQ.MongoDB.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1 + net5.0 diff --git a/samples/Sample.RabbitMQ.MySql/AppDbContext.cs b/samples/Sample.RabbitMQ.MySql/AppDbContext.cs index 526424d..cb24887 100644 --- a/samples/Sample.RabbitMQ.MySql/AppDbContext.cs +++ b/samples/Sample.RabbitMQ.MySql/AppDbContext.cs @@ -32,7 +32,7 @@ namespace Sample.RabbitMQ.MySql protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { - optionsBuilder.UseMySql(ConnectionString); + optionsBuilder.UseMySQL(ConnectionString); } } } diff --git a/samples/Sample.RabbitMQ.MySql/Migrations/20180821021736_init.cs b/samples/Sample.RabbitMQ.MySql/Migrations/20180821021736_init.cs index 762ddbf..646bf08 100644 --- a/samples/Sample.RabbitMQ.MySql/Migrations/20180821021736_init.cs +++ b/samples/Sample.RabbitMQ.MySql/Migrations/20180821021736_init.cs @@ -1,5 +1,5 @@ -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; +using MySql.Data.EntityFrameworkCore.Metadata; namespace Sample.RabbitMQ.MySql.Migrations { @@ -12,7 +12,7 @@ namespace Sample.RabbitMQ.MySql.Migrations columns: table => new { Id = table.Column(nullable: false) - .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + .Annotation("MySql:ValueGenerationStrategy", MySQLValueGenerationStrategy.IdentityColumn), Name = table.Column(nullable: true) }, constraints: table => diff --git a/samples/Sample.RabbitMQ.MySql/Sample.RabbitMQ.MySql.csproj b/samples/Sample.RabbitMQ.MySql/Sample.RabbitMQ.MySql.csproj index 099f9d6..0dafb6f 100644 --- a/samples/Sample.RabbitMQ.MySql/Sample.RabbitMQ.MySql.csproj +++ b/samples/Sample.RabbitMQ.MySql/Sample.RabbitMQ.MySql.csproj @@ -1,12 +1,12 @@  - netcoreapp3.1 + net5.0 - + diff --git a/samples/Sample.RabbitMQ.SqlServer/Sample.RabbitMQ.SqlServer.csproj b/samples/Sample.RabbitMQ.SqlServer/Sample.RabbitMQ.SqlServer.csproj index e22c30f..ad0d9ec 100644 --- a/samples/Sample.RabbitMQ.SqlServer/Sample.RabbitMQ.SqlServer.csproj +++ b/samples/Sample.RabbitMQ.SqlServer/Sample.RabbitMQ.SqlServer.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1 + net5.0 diff --git a/src/DotNetCore.CAP.AmazonSQS/DotNetCore.CAP.AmazonSQS.csproj b/src/DotNetCore.CAP.AmazonSQS/DotNetCore.CAP.AmazonSQS.csproj index 45db3a3..e594310 100644 --- a/src/DotNetCore.CAP.AmazonSQS/DotNetCore.CAP.AmazonSQS.csproj +++ b/src/DotNetCore.CAP.AmazonSQS/DotNetCore.CAP.AmazonSQS.csproj @@ -1,13 +1,13 @@  - netstandard2.0 + netstandard2.1 DotNetCore.CAP.AmazonSQS $(PackageTags);AmazonSQS;SQS - bin\$(Configuration)\netstandard2.0\DotNetCore.CAP.AmazonSQS.xml + bin\$(Configuration)\netstandard2.1\DotNetCore.CAP.AmazonSQS.xml 1701;1702;1705;CS1591 diff --git a/src/DotNetCore.CAP.AzureServiceBus/DotNetCore.CAP.AzureServiceBus.csproj b/src/DotNetCore.CAP.AzureServiceBus/DotNetCore.CAP.AzureServiceBus.csproj index 97d28ce..66bebbf 100644 --- a/src/DotNetCore.CAP.AzureServiceBus/DotNetCore.CAP.AzureServiceBus.csproj +++ b/src/DotNetCore.CAP.AzureServiceBus/DotNetCore.CAP.AzureServiceBus.csproj @@ -1,7 +1,7 @@  - netstandard2.0 + netstandard2.1 DotNetCore.CAP.AzureServiceBus $(PackageTags);AzureServiceBus @@ -9,7 +9,7 @@ NU1605;NU1701 NU1701;CS1591 - bin\$(Configuration)\netstandard2.0\DotNetCore.CAP.AzureServiceBus.xml + bin\$(Configuration)\netstandard2.1\DotNetCore.CAP.AzureServiceBus.xml diff --git a/src/DotNetCore.CAP.Dashboard/DotNetCore.CAP.Dashboard.csproj b/src/DotNetCore.CAP.Dashboard/DotNetCore.CAP.Dashboard.csproj index 29ef3fb..0f0b538 100644 --- a/src/DotNetCore.CAP.Dashboard/DotNetCore.CAP.Dashboard.csproj +++ b/src/DotNetCore.CAP.Dashboard/DotNetCore.CAP.Dashboard.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1 + net5.0 1591 diff --git a/src/DotNetCore.CAP.InMemoryStorage/DotNetCore.CAP.InMemoryStorage.csproj b/src/DotNetCore.CAP.InMemoryStorage/DotNetCore.CAP.InMemoryStorage.csproj index 34925ce..127176a 100644 --- a/src/DotNetCore.CAP.InMemoryStorage/DotNetCore.CAP.InMemoryStorage.csproj +++ b/src/DotNetCore.CAP.InMemoryStorage/DotNetCore.CAP.InMemoryStorage.csproj @@ -1,7 +1,7 @@  - netstandard2.0 + netstandard2.1 DotNetCore.CAP.InMemoryStorage $(PackageTags);InMemory diff --git a/src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj b/src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj index 1f2f8ff..1f82ccf 100644 --- a/src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj +++ b/src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj @@ -1,7 +1,7 @@  - netstandard2.0 + netstandard2.1 DotNetCore.CAP.Kafka $(PackageTags);Kafka @@ -9,7 +9,7 @@ NU1605;NU1701 NU1701;CS1591 - bin\$(Configuration)\netstandard2.0\DotNetCore.CAP.Kafka.xml + bin\$(Configuration)\netstandard2.1\DotNetCore.CAP.Kafka.xml diff --git a/src/DotNetCore.CAP.MongoDB/DotNetCore.CAP.MongoDB.csproj b/src/DotNetCore.CAP.MongoDB/DotNetCore.CAP.MongoDB.csproj index 6140851..1a2e3f3 100644 --- a/src/DotNetCore.CAP.MongoDB/DotNetCore.CAP.MongoDB.csproj +++ b/src/DotNetCore.CAP.MongoDB/DotNetCore.CAP.MongoDB.csproj @@ -1,13 +1,13 @@  - netstandard2.0 + netstandard2.1 DotNetCore.CAP.MongoDB $(PackageTags);MongoDB - bin\$(Configuration)\netstandard2.0\DotNetCore.CAP.MongoDB.xml + bin\$(Configuration)\netstandard2.1\DotNetCore.CAP.MongoDB.xml 1701;1702;1705;CS1591 diff --git a/src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj b/src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj index b5373de..07621d0 100644 --- a/src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj +++ b/src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj @@ -1,13 +1,13 @@  - netstandard2.0;netstandard2.1 + netstandard2.1 DotNetCore.CAP.MySql $(PackageTags);MySQL - bin\$(Configuration)\netstandard2.0\DotNetCore.CAP.MySql.xml + bin\$(Configuration)\netstandard2.1\DotNetCore.CAP.MySql.xml 1701;1702;1705;CS1591 diff --git a/src/DotNetCore.CAP.PostgreSql/DotNetCore.CAP.PostgreSql.csproj b/src/DotNetCore.CAP.PostgreSql/DotNetCore.CAP.PostgreSql.csproj index 912a9f3..608a8ce 100644 --- a/src/DotNetCore.CAP.PostgreSql/DotNetCore.CAP.PostgreSql.csproj +++ b/src/DotNetCore.CAP.PostgreSql/DotNetCore.CAP.PostgreSql.csproj @@ -1,13 +1,13 @@  - netstandard2.0 + netstandard2.1 DotNetCore.CAP.PostgreSql $(PackageTags);PostgreSQL - bin\$(Configuration)\netstandard2.0\DotNetCore.CAP.PostgreSql.xml + bin\$(Configuration)\netstandard2.1\DotNetCore.CAP.PostgreSql.xml 1701;1702;1705;CS1591 diff --git a/src/DotNetCore.CAP.RabbitMQ/DotNetCore.CAP.RabbitMQ.csproj b/src/DotNetCore.CAP.RabbitMQ/DotNetCore.CAP.RabbitMQ.csproj index 1fc96d1..4705aa1 100644 --- a/src/DotNetCore.CAP.RabbitMQ/DotNetCore.CAP.RabbitMQ.csproj +++ b/src/DotNetCore.CAP.RabbitMQ/DotNetCore.CAP.RabbitMQ.csproj @@ -1,13 +1,13 @@  - netstandard2.0 + netstandard2.1 DotNetCore.CAP.RabbitMQ $(PackageTags);RabbitMQ - bin\$(Configuration)\netstandard2.0\DotNetCore.CAP.RabbitMQ.xml + bin\$(Configuration)\netstandard2.1\DotNetCore.CAP.RabbitMQ.xml 1701;1702;1705;CS1591 diff --git a/src/DotNetCore.CAP.SqlServer/DotNetCore.CAP.SqlServer.csproj b/src/DotNetCore.CAP.SqlServer/DotNetCore.CAP.SqlServer.csproj index 3307a7d..73a280f 100644 --- a/src/DotNetCore.CAP.SqlServer/DotNetCore.CAP.SqlServer.csproj +++ b/src/DotNetCore.CAP.SqlServer/DotNetCore.CAP.SqlServer.csproj @@ -1,14 +1,14 @@  - netstandard2.0 + netstandard2.1 DotNetCore.CAP.SqlServer $(PackageTags);SQL Server - bin\$(Configuration)\netstandard2.0\DotNetCore.CAP.SqlServer.xml + bin\$(Configuration)\netstandard2.1\DotNetCore.CAP.SqlServer.xml 1701;1702;1705;CS1591 diff --git a/src/DotNetCore.CAP/DotNetCore.CAP.csproj b/src/DotNetCore.CAP/DotNetCore.CAP.csproj index fe2c46c..a7bebc2 100644 --- a/src/DotNetCore.CAP/DotNetCore.CAP.csproj +++ b/src/DotNetCore.CAP/DotNetCore.CAP.csproj @@ -1,11 +1,11 @@  - netstandard2.0 + netstandard2.1 - bin\$(Configuration)\netstandard2.0\DotNetCore.CAP.xml + bin\$(Configuration)\netstandard2.1\DotNetCore.CAP.xml 1701;1702;1705;CS1591 diff --git a/test/DotNetCore.CAP.MySql.Test/DotNetCore.CAP.MySql.Test.csproj b/test/DotNetCore.CAP.MySql.Test/DotNetCore.CAP.MySql.Test.csproj index dd0a52c..5f147cc 100644 --- a/test/DotNetCore.CAP.MySql.Test/DotNetCore.CAP.MySql.Test.csproj +++ b/test/DotNetCore.CAP.MySql.Test/DotNetCore.CAP.MySql.Test.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1 + net5.0 false diff --git a/test/DotNetCore.CAP.Test/DotNetCore.CAP.Test.csproj b/test/DotNetCore.CAP.Test/DotNetCore.CAP.Test.csproj index ae50a1f..e196a9d 100644 --- a/test/DotNetCore.CAP.Test/DotNetCore.CAP.Test.csproj +++ b/test/DotNetCore.CAP.Test/DotNetCore.CAP.Test.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1 + net5.0 false From ea7e6e7764b0735880d70068819d80f381e29a05 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Tue, 15 Dec 2020 14:14:41 +0800 Subject: [PATCH 82/85] Update nuget packages to .NET 5 --- samples/Sample.ConsoleApp/Sample.ConsoleApp.csproj | 2 +- .../Sample.Kafka.PostgreSql.csproj | 2 +- samples/Sample.RabbitMQ.MySql/AppDbContext.cs | 2 +- .../Migrations/20180821021736_init.cs | 6 +++--- .../Sample.RabbitMQ.MySql.csproj | 4 ++-- .../Sample.RabbitMQ.SqlServer.csproj | 6 +++--- .../DotNetCore.CAP.AmazonSQS.csproj | 8 ++++++-- .../DotNetCore.CAP.AzureServiceBus.csproj | 6 +++++- .../DotNetCore.CAP.Dashboard.csproj | 8 ++++++-- .../DotNetCore.CAP.InMemoryStorage.csproj | 4 ++++ .../DotNetCore.CAP.Kafka.csproj | 6 +++++- .../DotNetCore.CAP.MongoDB.csproj | 8 ++++++-- .../DotNetCore.CAP.MySql.csproj | 12 ++++++++---- .../DotNetCore.CAP.PostgreSql.csproj | 10 +++++++--- .../DotNetCore.CAP.RabbitMQ.csproj | 4 ++++ .../DotNetCore.CAP.SqlServer.csproj | 10 +++++++--- src/DotNetCore.CAP/DotNetCore.CAP.csproj | 13 +++++++++---- .../DotNetCore.CAP.MySql.Test.csproj | 6 +++--- test/DotNetCore.CAP.Test/DotNetCore.CAP.Test.csproj | 10 +++++----- 19 files changed, 86 insertions(+), 41 deletions(-) diff --git a/samples/Sample.ConsoleApp/Sample.ConsoleApp.csproj b/samples/Sample.ConsoleApp/Sample.ConsoleApp.csproj index 442d727..cd8736f 100644 --- a/samples/Sample.ConsoleApp/Sample.ConsoleApp.csproj +++ b/samples/Sample.ConsoleApp/Sample.ConsoleApp.csproj @@ -6,7 +6,7 @@ - + diff --git a/samples/Sample.Kafka.PostgreSql/Sample.Kafka.PostgreSql.csproj b/samples/Sample.Kafka.PostgreSql/Sample.Kafka.PostgreSql.csproj index 3348e3b..89f3ff3 100644 --- a/samples/Sample.Kafka.PostgreSql/Sample.Kafka.PostgreSql.csproj +++ b/samples/Sample.Kafka.PostgreSql/Sample.Kafka.PostgreSql.csproj @@ -7,7 +7,7 @@ - + diff --git a/samples/Sample.RabbitMQ.MySql/AppDbContext.cs b/samples/Sample.RabbitMQ.MySql/AppDbContext.cs index cb24887..9d01707 100644 --- a/samples/Sample.RabbitMQ.MySql/AppDbContext.cs +++ b/samples/Sample.RabbitMQ.MySql/AppDbContext.cs @@ -32,7 +32,7 @@ namespace Sample.RabbitMQ.MySql protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { - optionsBuilder.UseMySQL(ConnectionString); + optionsBuilder.UseMySql(ConnectionString, ServerVersion.FromString("mysql")); } } } diff --git a/samples/Sample.RabbitMQ.MySql/Migrations/20180821021736_init.cs b/samples/Sample.RabbitMQ.MySql/Migrations/20180821021736_init.cs index 646bf08..762ddbf 100644 --- a/samples/Sample.RabbitMQ.MySql/Migrations/20180821021736_init.cs +++ b/samples/Sample.RabbitMQ.MySql/Migrations/20180821021736_init.cs @@ -1,5 +1,5 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using MySql.Data.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; namespace Sample.RabbitMQ.MySql.Migrations { @@ -12,7 +12,7 @@ namespace Sample.RabbitMQ.MySql.Migrations columns: table => new { Id = table.Column(nullable: false) - .Annotation("MySql:ValueGenerationStrategy", MySQLValueGenerationStrategy.IdentityColumn), + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), Name = table.Column(nullable: true) }, constraints: table => diff --git a/samples/Sample.RabbitMQ.MySql/Sample.RabbitMQ.MySql.csproj b/samples/Sample.RabbitMQ.MySql/Sample.RabbitMQ.MySql.csproj index 0dafb6f..7326bf6 100644 --- a/samples/Sample.RabbitMQ.MySql/Sample.RabbitMQ.MySql.csproj +++ b/samples/Sample.RabbitMQ.MySql/Sample.RabbitMQ.MySql.csproj @@ -5,8 +5,8 @@ - - + + diff --git a/samples/Sample.RabbitMQ.SqlServer/Sample.RabbitMQ.SqlServer.csproj b/samples/Sample.RabbitMQ.SqlServer/Sample.RabbitMQ.SqlServer.csproj index ad0d9ec..00f5adf 100644 --- a/samples/Sample.RabbitMQ.SqlServer/Sample.RabbitMQ.SqlServer.csproj +++ b/samples/Sample.RabbitMQ.SqlServer/Sample.RabbitMQ.SqlServer.csproj @@ -5,12 +5,12 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/src/DotNetCore.CAP.AmazonSQS/DotNetCore.CAP.AmazonSQS.csproj b/src/DotNetCore.CAP.AmazonSQS/DotNetCore.CAP.AmazonSQS.csproj index e594310..f48f8f5 100644 --- a/src/DotNetCore.CAP.AmazonSQS/DotNetCore.CAP.AmazonSQS.csproj +++ b/src/DotNetCore.CAP.AmazonSQS/DotNetCore.CAP.AmazonSQS.csproj @@ -12,12 +12,16 @@ - - + + + + + + \ No newline at end of file diff --git a/src/DotNetCore.CAP.AzureServiceBus/DotNetCore.CAP.AzureServiceBus.csproj b/src/DotNetCore.CAP.AzureServiceBus/DotNetCore.CAP.AzureServiceBus.csproj index 66bebbf..8760ec3 100644 --- a/src/DotNetCore.CAP.AzureServiceBus/DotNetCore.CAP.AzureServiceBus.csproj +++ b/src/DotNetCore.CAP.AzureServiceBus/DotNetCore.CAP.AzureServiceBus.csproj @@ -13,11 +13,15 @@ - + + + + + \ No newline at end of file diff --git a/src/DotNetCore.CAP.Dashboard/DotNetCore.CAP.Dashboard.csproj b/src/DotNetCore.CAP.Dashboard/DotNetCore.CAP.Dashboard.csproj index 0f0b538..826c303 100644 --- a/src/DotNetCore.CAP.Dashboard/DotNetCore.CAP.Dashboard.csproj +++ b/src/DotNetCore.CAP.Dashboard/DotNetCore.CAP.Dashboard.csproj @@ -40,8 +40,8 @@ - - + + @@ -234,4 +234,8 @@ + + + + diff --git a/src/DotNetCore.CAP.InMemoryStorage/DotNetCore.CAP.InMemoryStorage.csproj b/src/DotNetCore.CAP.InMemoryStorage/DotNetCore.CAP.InMemoryStorage.csproj index 127176a..9a46737 100644 --- a/src/DotNetCore.CAP.InMemoryStorage/DotNetCore.CAP.InMemoryStorage.csproj +++ b/src/DotNetCore.CAP.InMemoryStorage/DotNetCore.CAP.InMemoryStorage.csproj @@ -10,4 +10,8 @@ + + + + diff --git a/src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj b/src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj index 1f82ccf..38b8ad5 100644 --- a/src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj +++ b/src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj @@ -13,11 +13,15 @@ - + + + + + diff --git a/src/DotNetCore.CAP.MongoDB/DotNetCore.CAP.MongoDB.csproj b/src/DotNetCore.CAP.MongoDB/DotNetCore.CAP.MongoDB.csproj index 1a2e3f3..f6338c9 100644 --- a/src/DotNetCore.CAP.MongoDB/DotNetCore.CAP.MongoDB.csproj +++ b/src/DotNetCore.CAP.MongoDB/DotNetCore.CAP.MongoDB.csproj @@ -16,8 +16,12 @@ - - + + + + + + diff --git a/src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj b/src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj index 07621d0..3283f4a 100644 --- a/src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj +++ b/src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj @@ -1,7 +1,7 @@  - netstandard2.1 + netstandard2.1 DotNetCore.CAP.MySql $(PackageTags);MySQL @@ -12,13 +12,17 @@ - - - + + + + + + + diff --git a/src/DotNetCore.CAP.PostgreSql/DotNetCore.CAP.PostgreSql.csproj b/src/DotNetCore.CAP.PostgreSql/DotNetCore.CAP.PostgreSql.csproj index 608a8ce..df6fc67 100644 --- a/src/DotNetCore.CAP.PostgreSql/DotNetCore.CAP.PostgreSql.csproj +++ b/src/DotNetCore.CAP.PostgreSql/DotNetCore.CAP.PostgreSql.csproj @@ -12,13 +12,17 @@ - - - + + + + + + + diff --git a/src/DotNetCore.CAP.RabbitMQ/DotNetCore.CAP.RabbitMQ.csproj b/src/DotNetCore.CAP.RabbitMQ/DotNetCore.CAP.RabbitMQ.csproj index 4705aa1..8aadcde 100644 --- a/src/DotNetCore.CAP.RabbitMQ/DotNetCore.CAP.RabbitMQ.csproj +++ b/src/DotNetCore.CAP.RabbitMQ/DotNetCore.CAP.RabbitMQ.csproj @@ -19,4 +19,8 @@ + + + + \ No newline at end of file diff --git a/src/DotNetCore.CAP.SqlServer/DotNetCore.CAP.SqlServer.csproj b/src/DotNetCore.CAP.SqlServer/DotNetCore.CAP.SqlServer.csproj index 73a280f..40c8ce3 100644 --- a/src/DotNetCore.CAP.SqlServer/DotNetCore.CAP.SqlServer.csproj +++ b/src/DotNetCore.CAP.SqlServer/DotNetCore.CAP.SqlServer.csproj @@ -13,13 +13,17 @@ - - - + + + + + + + \ No newline at end of file diff --git a/src/DotNetCore.CAP/DotNetCore.CAP.csproj b/src/DotNetCore.CAP/DotNetCore.CAP.csproj index a7bebc2..e913bb6 100644 --- a/src/DotNetCore.CAP/DotNetCore.CAP.csproj +++ b/src/DotNetCore.CAP/DotNetCore.CAP.csproj @@ -10,11 +10,16 @@ - - + + + - - + + + + + + \ No newline at end of file diff --git a/test/DotNetCore.CAP.MySql.Test/DotNetCore.CAP.MySql.Test.csproj b/test/DotNetCore.CAP.MySql.Test/DotNetCore.CAP.MySql.Test.csproj index 5f147cc..317a70b 100644 --- a/test/DotNetCore.CAP.MySql.Test/DotNetCore.CAP.MySql.Test.csproj +++ b/test/DotNetCore.CAP.MySql.Test/DotNetCore.CAP.MySql.Test.csproj @@ -11,8 +11,8 @@ - - + + all @@ -22,7 +22,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + \ No newline at end of file diff --git a/test/DotNetCore.CAP.Test/DotNetCore.CAP.Test.csproj b/test/DotNetCore.CAP.Test/DotNetCore.CAP.Test.csproj index e196a9d..d01270a 100644 --- a/test/DotNetCore.CAP.Test/DotNetCore.CAP.Test.csproj +++ b/test/DotNetCore.CAP.Test/DotNetCore.CAP.Test.csproj @@ -10,10 +10,10 @@ - - - - + + + + all @@ -23,7 +23,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + From 6230a233b623add8b860b869d39923cf3ca71f2b Mon Sep 17 00:00:00 2001 From: Savorboard Date: Tue, 15 Dec 2020 14:15:47 +0800 Subject: [PATCH 83/85] update travis to 5.0.100 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 1a12425..5f3cffb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ language: csharp sudo: required dist: xenial solution: CAP.sln -dotnet: 3.1.100 +dotnet: 5.0.100 mono: none env: - Cap_MySql_ConnectionString="Server=127.0.0.1;Database=cap_test;Uid=root;Pwd=;Allow User Variables=True;SslMode=none" From 734fd226776d56bf19ee0fc1e8bfa64a9d4bcee4 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Tue, 15 Dec 2020 14:18:41 +0800 Subject: [PATCH 84/85] Update JetBrains.Annotations to 2020.3.0 --- src/Directory.Build.props | 2 +- .../DotNetCore.CAP.AmazonSQS.csproj | 6 +----- .../DotNetCore.CAP.AzureServiceBus.csproj | 6 +----- .../DotNetCore.CAP.Dashboard.csproj | 6 +----- .../DotNetCore.CAP.InMemoryStorage.csproj | 6 +----- src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj | 6 +----- src/DotNetCore.CAP.MongoDB/DotNetCore.CAP.MongoDB.csproj | 6 +----- src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj | 6 +----- .../DotNetCore.CAP.PostgreSql.csproj | 6 +----- src/DotNetCore.CAP.RabbitMQ/DotNetCore.CAP.RabbitMQ.csproj | 4 ---- .../DotNetCore.CAP.SqlServer.csproj | 6 +----- src/DotNetCore.CAP/DotNetCore.CAP.csproj | 4 ---- 12 files changed, 10 insertions(+), 54 deletions(-) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 9b58ae9..a30ba1b 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -26,7 +26,7 @@ - + \ No newline at end of file diff --git a/src/DotNetCore.CAP.AmazonSQS/DotNetCore.CAP.AmazonSQS.csproj b/src/DotNetCore.CAP.AmazonSQS/DotNetCore.CAP.AmazonSQS.csproj index f48f8f5..4748e12 100644 --- a/src/DotNetCore.CAP.AmazonSQS/DotNetCore.CAP.AmazonSQS.csproj +++ b/src/DotNetCore.CAP.AmazonSQS/DotNetCore.CAP.AmazonSQS.csproj @@ -19,9 +19,5 @@ - - - - - + \ No newline at end of file diff --git a/src/DotNetCore.CAP.AzureServiceBus/DotNetCore.CAP.AzureServiceBus.csproj b/src/DotNetCore.CAP.AzureServiceBus/DotNetCore.CAP.AzureServiceBus.csproj index 8760ec3..5ff314a 100644 --- a/src/DotNetCore.CAP.AzureServiceBus/DotNetCore.CAP.AzureServiceBus.csproj +++ b/src/DotNetCore.CAP.AzureServiceBus/DotNetCore.CAP.AzureServiceBus.csproj @@ -18,10 +18,6 @@ - - - - - + \ No newline at end of file diff --git a/src/DotNetCore.CAP.Dashboard/DotNetCore.CAP.Dashboard.csproj b/src/DotNetCore.CAP.Dashboard/DotNetCore.CAP.Dashboard.csproj index 826c303..1db04ea 100644 --- a/src/DotNetCore.CAP.Dashboard/DotNetCore.CAP.Dashboard.csproj +++ b/src/DotNetCore.CAP.Dashboard/DotNetCore.CAP.Dashboard.csproj @@ -232,10 +232,6 @@ RazorGenerator _SidebarMenu.generated.cs - - - - - + diff --git a/src/DotNetCore.CAP.InMemoryStorage/DotNetCore.CAP.InMemoryStorage.csproj b/src/DotNetCore.CAP.InMemoryStorage/DotNetCore.CAP.InMemoryStorage.csproj index 9a46737..45b69b8 100644 --- a/src/DotNetCore.CAP.InMemoryStorage/DotNetCore.CAP.InMemoryStorage.csproj +++ b/src/DotNetCore.CAP.InMemoryStorage/DotNetCore.CAP.InMemoryStorage.csproj @@ -9,9 +9,5 @@ - - - - - + diff --git a/src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj b/src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj index 38b8ad5..64a200a 100644 --- a/src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj +++ b/src/DotNetCore.CAP.Kafka/DotNetCore.CAP.Kafka.csproj @@ -18,10 +18,6 @@ - - - - - + diff --git a/src/DotNetCore.CAP.MongoDB/DotNetCore.CAP.MongoDB.csproj b/src/DotNetCore.CAP.MongoDB/DotNetCore.CAP.MongoDB.csproj index f6338c9..db10bdd 100644 --- a/src/DotNetCore.CAP.MongoDB/DotNetCore.CAP.MongoDB.csproj +++ b/src/DotNetCore.CAP.MongoDB/DotNetCore.CAP.MongoDB.csproj @@ -18,10 +18,6 @@ - - - - - + diff --git a/src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj b/src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj index 3283f4a..fb10f5f 100644 --- a/src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj +++ b/src/DotNetCore.CAP.MySql/DotNetCore.CAP.MySql.csproj @@ -20,9 +20,5 @@ - - - - - + diff --git a/src/DotNetCore.CAP.PostgreSql/DotNetCore.CAP.PostgreSql.csproj b/src/DotNetCore.CAP.PostgreSql/DotNetCore.CAP.PostgreSql.csproj index df6fc67..d0a5e87 100644 --- a/src/DotNetCore.CAP.PostgreSql/DotNetCore.CAP.PostgreSql.csproj +++ b/src/DotNetCore.CAP.PostgreSql/DotNetCore.CAP.PostgreSql.csproj @@ -20,9 +20,5 @@ - - - - - + diff --git a/src/DotNetCore.CAP.RabbitMQ/DotNetCore.CAP.RabbitMQ.csproj b/src/DotNetCore.CAP.RabbitMQ/DotNetCore.CAP.RabbitMQ.csproj index 8aadcde..4705aa1 100644 --- a/src/DotNetCore.CAP.RabbitMQ/DotNetCore.CAP.RabbitMQ.csproj +++ b/src/DotNetCore.CAP.RabbitMQ/DotNetCore.CAP.RabbitMQ.csproj @@ -19,8 +19,4 @@ - - - - \ No newline at end of file diff --git a/src/DotNetCore.CAP.SqlServer/DotNetCore.CAP.SqlServer.csproj b/src/DotNetCore.CAP.SqlServer/DotNetCore.CAP.SqlServer.csproj index 40c8ce3..a0bb71f 100644 --- a/src/DotNetCore.CAP.SqlServer/DotNetCore.CAP.SqlServer.csproj +++ b/src/DotNetCore.CAP.SqlServer/DotNetCore.CAP.SqlServer.csproj @@ -20,10 +20,6 @@ - - - - - + \ No newline at end of file diff --git a/src/DotNetCore.CAP/DotNetCore.CAP.csproj b/src/DotNetCore.CAP/DotNetCore.CAP.csproj index e913bb6..70c7234 100644 --- a/src/DotNetCore.CAP/DotNetCore.CAP.csproj +++ b/src/DotNetCore.CAP/DotNetCore.CAP.csproj @@ -17,9 +17,5 @@ - - - - \ No newline at end of file From e5607f09f184b23e6c33330c568c9082fd49c8d9 Mon Sep 17 00:00:00 2001 From: Savorboard Date: Tue, 15 Dec 2020 17:45:24 +0800 Subject: [PATCH 85/85] Replace Newtonsoft.Json to System.Text.Json. #740 --- .../AmazonSQSConsumerClient.cs | 4 +-- .../DotNetCore.CAP.Dashboard.csproj | 2 -- .../JsonDispatcher.cs | 21 +++--------- src/DotNetCore.CAP.Dashboard/JsonStats.cs | 19 ++--------- .../Pages/HomePage.cshtml | 10 +++--- .../Pages/HomePage.generated.cs | 10 +++--- src/DotNetCore.CAP/DotNetCore.CAP.csproj | 2 +- .../Serialization/ISerializer.JsonUtf8.cs | 32 +++++++++++-------- 8 files changed, 39 insertions(+), 61 deletions(-) diff --git a/src/DotNetCore.CAP.AmazonSQS/AmazonSQSConsumerClient.cs b/src/DotNetCore.CAP.AmazonSQS/AmazonSQSConsumerClient.cs index 75b933a..82595d9 100644 --- a/src/DotNetCore.CAP.AmazonSQS/AmazonSQSConsumerClient.cs +++ b/src/DotNetCore.CAP.AmazonSQS/AmazonSQSConsumerClient.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Text; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Amazon.SimpleNotificationService; @@ -14,7 +15,6 @@ using Amazon.SQS.Model; using DotNetCore.CAP.Messages; using DotNetCore.CAP.Transport; using Microsoft.Extensions.Options; -using Newtonsoft.Json; using Headers = DotNetCore.CAP.Messages.Headers; namespace DotNetCore.CAP.AmazonSQS @@ -83,7 +83,7 @@ namespace DotNetCore.CAP.AmazonSQS if (response.Messages.Count == 1) { - var messageObj = JsonConvert.DeserializeObject(response.Messages[0].Body); + var messageObj = JsonSerializer.Deserialize(response.Messages[0].Body); var header = messageObj.MessageAttributes.ToDictionary(x => x.Key, x => x.Value.Value); var body = messageObj.Message; diff --git a/src/DotNetCore.CAP.Dashboard/DotNetCore.CAP.Dashboard.csproj b/src/DotNetCore.CAP.Dashboard/DotNetCore.CAP.Dashboard.csproj index 1db04ea..d66e95d 100644 --- a/src/DotNetCore.CAP.Dashboard/DotNetCore.CAP.Dashboard.csproj +++ b/src/DotNetCore.CAP.Dashboard/DotNetCore.CAP.Dashboard.csproj @@ -40,8 +40,6 @@ - - diff --git a/src/DotNetCore.CAP.Dashboard/JsonDispatcher.cs b/src/DotNetCore.CAP.Dashboard/JsonDispatcher.cs index aad6d42..6f45a33 100644 --- a/src/DotNetCore.CAP.Dashboard/JsonDispatcher.cs +++ b/src/DotNetCore.CAP.Dashboard/JsonDispatcher.cs @@ -2,10 +2,8 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using System; -using System.Threading.Tasks; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using Newtonsoft.Json.Serialization; +using System.Text.Json; +using System.Threading.Tasks; namespace DotNetCore.CAP.Dashboard { @@ -30,19 +28,8 @@ namespace DotNetCore.CAP.Dashboard if (_command != null) { var result = _command(context); - - var settings = new JsonSerializerSettings - { - ContractResolver = new CamelCasePropertyNamesContractResolver(), - Converters = new JsonConverter[] - { - new StringEnumConverter - { - NamingStrategy = new CamelCaseNamingStrategy() - } - } - }; - serialized = JsonConvert.SerializeObject(result, settings); + + serialized = JsonSerializer.Serialize(result); } if (_jsonCommand != null) diff --git a/src/DotNetCore.CAP.Dashboard/JsonStats.cs b/src/DotNetCore.CAP.Dashboard/JsonStats.cs index 35dbf0a..de42c33 100644 --- a/src/DotNetCore.CAP.Dashboard/JsonStats.cs +++ b/src/DotNetCore.CAP.Dashboard/JsonStats.cs @@ -3,10 +3,8 @@ using System.Collections.Generic; using System.Linq; +using System.Text.Json; using System.Threading.Tasks; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using Newtonsoft.Json.Serialization; namespace DotNetCore.CAP.Dashboard { @@ -26,19 +24,8 @@ namespace DotNetCore.CAP.Dashboard var value = metric.Func(page); result.Add(metric.Name, value); } - - var settings = new JsonSerializerSettings - { - ContractResolver = new CamelCasePropertyNamesContractResolver(), - Converters = new JsonConverter[] - { - new StringEnumConverter - { - NamingStrategy = new CamelCaseNamingStrategy() - } - } - }; - var serialized = JsonConvert.SerializeObject(result, settings); + + var serialized = JsonSerializer.Serialize(result); context.Response.ContentType = "application/json"; await context.Response.WriteAsync(serialized); diff --git a/src/DotNetCore.CAP.Dashboard/Pages/HomePage.cshtml b/src/DotNetCore.CAP.Dashboard/Pages/HomePage.cshtml index 91b840d..8c40ab3 100644 --- a/src/DotNetCore.CAP.Dashboard/Pages/HomePage.cshtml +++ b/src/DotNetCore.CAP.Dashboard/Pages/HomePage.cshtml @@ -1,8 +1,8 @@ @* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True *@ +@using System.Text.Json @using DotNetCore.CAP.Dashboard.Pages @using DotNetCore.CAP.Dashboard.Resources @using DotNetCore.CAP.Messages -@using Newtonsoft.Json @inherits DotNetCore.CAP.Dashboard.RazorPage @{ Layout = new LayoutPage(Strings.HomePage_Title); @@ -52,12 +52,12 @@
diff --git a/src/DotNetCore.CAP.Dashboard/Pages/HomePage.generated.cs b/src/DotNetCore.CAP.Dashboard/Pages/HomePage.generated.cs index 060844e..6ee090e 100644 --- a/src/DotNetCore.CAP.Dashboard/Pages/HomePage.generated.cs +++ b/src/DotNetCore.CAP.Dashboard/Pages/HomePage.generated.cs @@ -35,7 +35,7 @@ namespace DotNetCore.CAP.Dashboard.Pages #line hidden #line 5 "..\..\Pages\HomePage.cshtml" - using Newtonsoft.Json; + using System.Text.Json; #line default #line hidden @@ -252,7 +252,7 @@ WriteLiteral("\r\n \r\n\r\n
- + \ No newline at end of file diff --git a/src/DotNetCore.CAP/Serialization/ISerializer.JsonUtf8.cs b/src/DotNetCore.CAP/Serialization/ISerializer.JsonUtf8.cs index 70ab10e..ec02bfc 100644 --- a/src/DotNetCore.CAP/Serialization/ISerializer.JsonUtf8.cs +++ b/src/DotNetCore.CAP/Serialization/ISerializer.JsonUtf8.cs @@ -2,11 +2,10 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using System; -using System.Text; +using System.Buffers; +using System.Text.Json; using System.Threading.Tasks; using DotNetCore.CAP.Messages; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; namespace DotNetCore.CAP.Serialization { @@ -24,8 +23,8 @@ namespace DotNetCore.CAP.Serialization return Task.FromResult(new TransportMessage(message.Headers, null)); } - var json = JsonConvert.SerializeObject(message.Value); - return Task.FromResult(new TransportMessage(message.Headers, Encoding.UTF8.GetBytes(json))); + var jsonBytes = JsonSerializer.SerializeToUtf8Bytes(message.Value); + return Task.FromResult(new TransportMessage(message.Headers, jsonBytes)); } public Task DeserializeAsync(TransportMessage transportMessage, Type valueType) @@ -35,32 +34,39 @@ namespace DotNetCore.CAP.Serialization return Task.FromResult(new Message(transportMessage.Headers, null)); } - var json = Encoding.UTF8.GetString(transportMessage.Body); - return Task.FromResult(new Message(transportMessage.Headers, JsonConvert.DeserializeObject(json, valueType))); + var obj = JsonSerializer.Deserialize(transportMessage.Body, valueType); + + return Task.FromResult(new Message(transportMessage.Headers, obj)); } public string Serialize(Message message) { - return JsonConvert.SerializeObject(message); + return JsonSerializer.Serialize(message); } public Message Deserialize(string json) { - return JsonConvert.DeserializeObject(json); + return JsonSerializer.Deserialize(json); } public object Deserialize(object value, Type valueType) { - if (value is JToken jToken) + if (value is JsonElement jToken) { - return jToken.ToObject(valueType); + var bufferWriter = new ArrayBufferWriter(); + using (var writer = new Utf8JsonWriter(bufferWriter)) + { + jToken.WriteTo(writer); + } + return JsonSerializer.Deserialize(bufferWriter.WrittenSpan, valueType); } throw new NotSupportedException("Type is not of type JToken"); } public bool IsJsonType(object jsonObject) { - return jsonObject is JToken; + return jsonObject is JsonElement; } - } + + } } \ No newline at end of file