├── .gitattributes ├── .gitignore ├── .travis.yml ├── EventBus.NuGet.Sample ├── Controllers │ └── ValuesController.cs ├── EventBus.NuGet.Sample.csproj ├── Program.cs ├── Startup.cs ├── appsettings.Development.json └── appsettings.json ├── EventBus.jpg ├── EventBus.sln ├── LICENSE ├── NuGet.config ├── README.md ├── appveyor.release.yml ├── appveyor.yml ├── sample ├── EventBus.Publish.Sample │ ├── Controllers │ │ └── ValuesController.cs │ ├── EventBus.Publish.Sample.csproj │ ├── Program.cs │ ├── Startup.cs │ ├── appsettings.Development.json │ └── appsettings.json └── EventBus.Sample │ ├── Controllers │ └── ValuesController.cs │ ├── EventBus.Sample.csproj │ ├── EventHandlers │ ├── NewUserEventHandlerAll.cs │ ├── NewUserEventHandlerCommuter.cs │ └── NewUserEventHandlerShuttle.cs │ ├── FailedEventHandlers │ └── NewUserFailedMessageHandler.cs │ ├── Program.cs │ ├── Properties │ └── launchSettings.json │ ├── Startup.cs │ ├── appsettings.Development.json │ └── appsettings.json ├── src ├── EventBus.Alert │ ├── DefaultLastAlertMemento.cs │ ├── DefaultPubAlertHandler.cs │ ├── DefaultSubAlertHandler.cs │ ├── EventBus.Alert.csproj │ ├── EventBusAlertServiceCollectionExtenssions.cs │ ├── ILastAlertMemento.cs │ └── SMSAlertOptions.cs ├── EventBus.Core │ ├── ConsumeFailureContext.cs │ ├── EventAttribute.cs │ ├── EventBus.Core.csproj │ ├── EventBus.Options.Extension.cs │ ├── EventBus.Options.cs │ ├── EventDescriptor.cs │ ├── Extensions │ │ ├── AsynchronousTaskExtensions.cs │ │ ├── ExceptionExtensions.cs │ │ ├── ILogger.Extensions.cs │ │ ├── IMessage.Extensions.cs │ │ ├── IMetaData.Extensions.cs │ │ └── Object.Extenssions.cs │ ├── Extenssion.ApplicationBuilder.cs │ ├── Extenssion.ServiceCollection.cs │ ├── FailContext.cs │ ├── FailedMessageContext.cs │ ├── FailureExtenssion.cs │ ├── IBootstrapper.cs │ ├── IClient.cs │ ├── IConnectionFactoryAccessor.cs │ ├── IConsumer.cs │ ├── IEnviromentNameConcator.cs │ ├── IEventBusTransactionFactory.cs │ ├── IEventPublisher.Extensions.cs │ ├── IEventPublisher.cs │ ├── IEventTransaction.cs │ ├── IFailure.Client.cs │ ├── IFailureContextAccessor.cs │ ├── IFailureHandler.cs │ ├── IIdentityGenerator.Default.cs │ ├── IIdentityGenerator.cs │ ├── IMessageDecoder.cs │ ├── IMessageQueueTransaction.cs │ ├── IMessageSerializer.cs │ ├── IPubFailureHandler.cs │ ├── IPubMessageValidator.cs │ ├── IPublishedEventPersistenter.cs │ ├── IReceivedEventPersistenter.cs │ ├── ISubFailureHandler.cs │ ├── Infrastructure │ │ ├── AffectedRowsCountUnExpectedException.cs │ │ ├── DeadLetterInfo.cs │ │ ├── DefaultEnviromentalNameUpdator.cs │ │ ├── DefaultMessageDecoder.cs │ │ ├── Extenssion.Type.cs │ │ ├── FailureHandleOptions.cs │ │ ├── IBootstrapper.Default.cs │ │ ├── IClient.Failure.cs │ │ ├── IConsumer.Failure.cs │ │ ├── IFailureContextAccessor.Default.cs │ │ ├── IInvoker.cs │ │ ├── IMessage.Default.cs │ │ ├── IMessage.cs │ │ ├── IMessageSerializer.Default.cs │ │ ├── IMetaData.Default.cs │ │ ├── IMetaData.cs │ │ ├── IPubMessageValidator.Default.cs │ │ ├── JSONSimpleType.cs │ │ ├── MessageInfo.cs │ │ ├── MessageType.cs │ │ ├── MySQL.Options.cs │ │ ├── Rabbit.Options.cs │ │ ├── SimpleTypes.cs │ │ └── SubscribeInfo.cs │ ├── Internal │ │ ├── AnonymousObject.cs │ │ ├── AnonymousPropertyInfo.cs │ │ ├── FailureInvoker.cs │ │ ├── Model │ │ │ └── ReceivedMessage.cs │ │ └── SubscribeCallbackHandlerDescriptor.cs │ ├── MessageContext.cs │ ├── PubMessageValidateContext.cs │ ├── PubMessageValidateResult.cs │ ├── State │ │ ├── IStateChangeHandler.cs │ │ ├── MessageState.cs │ │ └── StateChangedArgs.cs │ ├── SubscribeAttribute.cs │ └── Util │ │ └── TopicMatch.cs ├── EventBus.Publish │ ├── EventBus.MySQL.Extension.cs │ ├── EventBus.Publish.csproj │ ├── EventBusExtensions.cs │ ├── EventPublisher.cs │ ├── Infrastructure │ │ └── IConnectionFactoryAccessor.Default.cs │ ├── Internal │ │ ├── LoggerExtensions.cs │ │ └── Model │ │ │ └── Message.cs │ ├── MessageQueueTransaction.cs │ ├── PublishedEventPersistenter.cs │ └── Rabbit.Extension.cs └── EventBus.Subscribe │ ├── EventBus.Subscribe.csproj │ ├── Extenssion.ApplicationBuilder.cs │ ├── Extenssion.ServiceCollection.cs │ ├── IMessageDeSerializer.cs │ ├── ISubscribeClient.cs │ ├── ISubscribeHandler.cs │ ├── Infrastructure │ ├── IMessageDeSerializer.Default.cs │ ├── ISubscribeClient.Default.cs │ └── ISubscribeConsumer.Default.cs │ ├── Internal │ └── IConsumerInvoker.Default.cs │ ├── ReceivedEventPersistenter.cs │ ├── SubscribeAttribute.cs │ └── SubscribeOptions.cs └── test └── EventBus.Core.Test ├── Consts.cs ├── EventBus.Core.Test.csproj ├── PublishTest.Ctor.cs ├── PublishTest.MessagePersistenter.cs ├── PublishTest.MessageQueueTransaction.cs ├── SubscribeTest.Ctor.cs ├── SubscribeTest.ReceivedEventPersistenter.cs ├── TestBase.cs └── TestDbContext.cs /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # .NET Core 46 | project.lock.json 47 | project.fragment.lock.json 48 | artifacts/ 49 | **/Properties/launchSettings.json 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # Visual Studio code coverage results 117 | *.coverage 118 | *.coveragexml 119 | 120 | # NCrunch 121 | _NCrunch_* 122 | .*crunch*.local.xml 123 | nCrunchTemp_* 124 | 125 | # MightyMoose 126 | *.mm.* 127 | AutoTest.Net/ 128 | 129 | # Web workbench (sass) 130 | .sass-cache/ 131 | 132 | # Installshield output folder 133 | [Ee]xpress/ 134 | 135 | # DocProject is a documentation generator add-in 136 | DocProject/buildhelp/ 137 | DocProject/Help/*.HxT 138 | DocProject/Help/*.HxC 139 | DocProject/Help/*.hhc 140 | DocProject/Help/*.hhk 141 | DocProject/Help/*.hhp 142 | DocProject/Help/Html2 143 | DocProject/Help/html 144 | 145 | # Click-Once directory 146 | publish/ 147 | 148 | # Publish Web Output 149 | *.[Pp]ublish.xml 150 | *.azurePubxml 151 | # TODO: Comment the next line if you want to checkin your web deploy settings 152 | # but database connection strings (with potential passwords) will be unencrypted 153 | *.pubxml 154 | *.publishproj 155 | 156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 157 | # checkin your Azure Web App publish settings, but sensitive information contained 158 | # in these scripts will be unencrypted 159 | PublishScripts/ 160 | 161 | # NuGet Packages 162 | *.nupkg 163 | # The packages folder can be ignored because of Package Restore 164 | **/packages/* 165 | # except build/, which is used as an MSBuild target. 166 | !**/packages/build/ 167 | # Uncomment if necessary however generally it will be regenerated when needed 168 | #!**/packages/repositories.config 169 | # NuGet v3's project.json files produces more ignorable files 170 | *.nuget.props 171 | *.nuget.targets 172 | 173 | # Microsoft Azure Build Output 174 | csx/ 175 | *.build.csdef 176 | 177 | # Microsoft Azure Emulator 178 | ecf/ 179 | rcf/ 180 | 181 | # Windows Store app package directories and files 182 | AppPackages/ 183 | BundleArtifacts/ 184 | Package.StoreAssociation.xml 185 | _pkginfo.txt 186 | 187 | # Visual Studio cache files 188 | # files ending in .cache can be ignored 189 | *.[Cc]ache 190 | # but keep track of directories ending in .cache 191 | !*.[Cc]ache/ 192 | 193 | # Others 194 | ClientBin/ 195 | ~$* 196 | *~ 197 | *.dbmdl 198 | *.dbproj.schemaview 199 | *.jfm 200 | *.pfx 201 | *.publishsettings 202 | orleans.codegen.cs 203 | 204 | # Since there are multiple workflows, uncomment next line to ignore bower_components 205 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 206 | #bower_components/ 207 | 208 | # RIA/Silverlight projects 209 | Generated_Code/ 210 | 211 | # Backup & report files from converting an old project file 212 | # to a newer Visual Studio version. Backup files are not needed, 213 | # because we have git ;-) 214 | _UpgradeReport_Files/ 215 | Backup*/ 216 | UpgradeLog*.XML 217 | UpgradeLog*.htm 218 | 219 | # SQL Server files 220 | *.mdf 221 | *.ldf 222 | *.ndf 223 | 224 | # Business Intelligence projects 225 | *.rdl.data 226 | *.bim.layout 227 | *.bim_*.settings 228 | 229 | # Microsoft Fakes 230 | FakesAssemblies/ 231 | 232 | # GhostDoc plugin setting file 233 | *.GhostDoc.xml 234 | 235 | # Node.js Tools for Visual Studio 236 | .ntvs_analysis.dat 237 | node_modules/ 238 | 239 | # Typescript v1 declaration files 240 | typings/ 241 | 242 | # Visual Studio 6 build log 243 | *.plg 244 | 245 | # Visual Studio 6 workspace options file 246 | *.opt 247 | 248 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 249 | *.vbw 250 | 251 | # Visual Studio LightSwitch build output 252 | **/*.HTMLClient/GeneratedArtifacts 253 | **/*.DesktopClient/GeneratedArtifacts 254 | **/*.DesktopClient/ModelManifest.xml 255 | **/*.Server/GeneratedArtifacts 256 | **/*.Server/ModelManifest.xml 257 | _Pvt_Extensions 258 | 259 | # Paket dependency manager 260 | .paket/paket.exe 261 | paket-files/ 262 | 263 | # FAKE - F# Make 264 | .fake/ 265 | 266 | # JetBrains Rider 267 | .idea/ 268 | *.sln.iml 269 | 270 | # CodeRush 271 | .cr/ 272 | 273 | # Python Tools for Visual Studio (PTVS) 274 | __pycache__/ 275 | *.pyc 276 | 277 | # Cake - Uncomment if you are using it 278 | # tools/** 279 | # !tools/packages.config 280 | 281 | # Telerik's JustMock configuration file 282 | *.jmconfig 283 | 284 | # BizTalk build output 285 | *.btp.cs 286 | *.btm.cs 287 | *.odx.cs 288 | *.xsd.cs 289 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: csharp 2 | sudo: false 3 | mono: none 4 | dotnet: 2.0.0 5 | dist: trusty 6 | env: 7 | global: 8 | - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true 9 | - DOTNET_CLI_TELEMETRY_OPTOUT: 1 10 | 11 | os: 12 | - linux 13 | 14 | script: 15 | - if test "$TRAVIS_OS_NAME" == "linux"; then dotnet restore -r ubuntu.14.04-x64; fi 16 | - dotnet build -c Release 17 | - cd $TEST_PATH 18 | - dotnet add ./$TEST_PROJECT package xunit.runner.console --version 2.3.0-beta5-build3769 19 | - dotnet restore ./$TEST_PROJECT -r ubuntu.14.04-x64 20 | - dotnet build ./$TEST_PROJECT -c Release 21 | -------------------------------------------------------------------------------- /EventBus.NuGet.Sample/Controllers/ValuesController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | 7 | namespace EventBus.NuGet.Sample.Controllers 8 | { 9 | [Route("api/[controller]")] 10 | public class ValuesController : Controller 11 | { 12 | // GET api/values 13 | [HttpGet] 14 | public IEnumerable Get() 15 | { 16 | return new string[] { "value1", "value2" }; 17 | } 18 | 19 | // GET api/values/5 20 | [HttpGet("{id}")] 21 | public string Get(int id) 22 | { 23 | return "value"; 24 | } 25 | 26 | // POST api/values 27 | [HttpPost] 28 | public void Post([FromBody]string value) 29 | { 30 | } 31 | 32 | // PUT api/values/5 33 | [HttpPut("{id}")] 34 | public void Put(int id, [FromBody]string value) 35 | { 36 | } 37 | 38 | // DELETE api/values/5 39 | [HttpDelete("{id}")] 40 | public void Delete(int id) 41 | { 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /EventBus.NuGet.Sample/EventBus.NuGet.Sample.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /EventBus.NuGet.Sample/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace EventBus.NuGet.Sample 12 | { 13 | public class Program 14 | { 15 | public static void Main(string[] args) 16 | { 17 | var host = new WebHostBuilder() 18 | .UseKestrel() 19 | .UseContentRoot(Directory.GetCurrentDirectory()) 20 | .UseIISIntegration() 21 | .UseStartup() 22 | .UseApplicationInsights() 23 | .Build(); 24 | 25 | host.Run(); 26 | 27 | //BuildWebHost(args).Run(); 28 | } 29 | 30 | public static IWebHost BuildWebHost(string[] args) => 31 | WebHost.CreateDefaultBuilder(args) 32 | .UseStartup() 33 | .Build(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /EventBus.NuGet.Sample/Startup.cs: -------------------------------------------------------------------------------- 1 | using EventBus.Alert; 2 | using EventBus.Core; 3 | using EventBus.Subscribe; 4 | using Microsoft.AspNetCore.Builder; 5 | using Microsoft.AspNetCore.Hosting; 6 | using Microsoft.EntityFrameworkCore; 7 | using Microsoft.Extensions.Configuration; 8 | using Microsoft.Extensions.DependencyInjection; 9 | 10 | namespace EventBus.NuGet.Sample 11 | { 12 | public class Startup 13 | { 14 | public Startup(IConfiguration configuration) 15 | { 16 | Configuration = configuration; 17 | } 18 | 19 | public IConfiguration Configuration { get; } 20 | private const string ConnectionString = "Server=192.168.126.138;Port=3306;Database=FeiniuCAP; User=root;Password=kge2001;charset=UTF-8"; 21 | // This method gets called by the runtime. Use this method to add services to the container. 22 | public void ConfigureServices(IServiceCollection services) 23 | { 24 | services.AddMvc(); 25 | 26 | services.AddDbContext(options => options.UseMySql(ConnectionString)); 27 | 28 | services.AddEventBus(options => 29 | { 30 | options.UseEntityframework(); 31 | options.UseRabbitMQ(rabbit => 32 | { 33 | rabbit.HostName = "192.168.126.138"; 34 | rabbit.UserName = "andrew"; 35 | rabbit.Password = "kge2001"; 36 | rabbit.Port = 5672; 37 | }); 38 | 39 | options.UseFailureHandle(failure => 40 | { 41 | 42 | }); 43 | }); 44 | 45 | 46 | services.AddSub(options => 47 | { 48 | options.ConsumerClientCount = 5; 49 | options.DefaultGroup = "FeiniuBusPayment1111"; 50 | 51 | //options.RegisterCallback("eventbus.testtopic", "eventbus.testgroup2", typeof(NewUserEventHandler)); 52 | }); 53 | 54 | services.AddEventBusAlert(opts => 55 | { 56 | }); 57 | } 58 | 59 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 60 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 61 | { 62 | if (env.IsDevelopment()) 63 | { 64 | app.UseDeveloperExceptionPage(); 65 | } 66 | app.UseEventBus(); 67 | app.UseMvc(); 68 | 69 | 70 | } 71 | } 72 | 73 | public class SampleDbContext : DbContext 74 | { 75 | public SampleDbContext(DbContextOptions options) : base(options) 76 | { 77 | 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /EventBus.NuGet.Sample/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Debug", 6 | "System": "Information", 7 | "Microsoft": "Information" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /EventBus.NuGet.Sample/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "Debug": { 5 | "LogLevel": { 6 | "Default": "Warning" 7 | } 8 | }, 9 | "Console": { 10 | "LogLevel": { 11 | "Default": "Warning" 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /EventBus.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FeiniuBus/EventBus/6b324dee7ed888d9fb3ea04d34c480f9c7450aba/EventBus.jpg -------------------------------------------------------------------------------- /EventBus.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27004.2005 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sample", "sample", "{5A4E9AF3-C199-4304-A0DE-C3AA652A71A9}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{AD01A83A-D3A7-4CF5-AD89-F9C2C7FC359A}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{0D19674D-66F4-461F-86B3-DABAAE3BB9D1}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EventBus.Core", "src\EventBus.Core\EventBus.Core.csproj", "{203CA579-3C1A-43E6-B866-EA5A57E37EBF}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EventBus.Sample", "sample\EventBus.Sample\EventBus.Sample.csproj", "{4F687607-DE22-41E0-B5CC-C754AE3AA1A9}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EventBus.Publish", "src\EventBus.Publish\EventBus.Publish.csproj", "{F46509D4-67A0-4F36-9F4C-1F4C9FC129AB}" 17 | EndProject 18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EventBus.Subscribe", "src\EventBus.Subscribe\EventBus.Subscribe.csproj", "{30CD147A-5160-48AF-8409-E52595EEB926}" 19 | EndProject 20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EventBus.Publish.Sample", "sample\EventBus.Publish.Sample\EventBus.Publish.Sample.csproj", "{7CEC99E8-7348-440D-909B-1D203B3F7965}" 21 | EndProject 22 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EventBus.Core.Test", "test\EventBus.Core.Test\EventBus.Core.Test.csproj", "{A7D74AA8-89AF-4650-9659-0839CC9D8D50}" 23 | EndProject 24 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EventBus.Alert", "src\EventBus.Alert\EventBus.Alert.csproj", "{DCD5A7C5-062C-4782-B6A4-1AC58C17E36D}" 25 | EndProject 26 | Global 27 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 28 | Debug|Any CPU = Debug|Any CPU 29 | Release|Any CPU = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 32 | {203CA579-3C1A-43E6-B866-EA5A57E37EBF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {203CA579-3C1A-43E6-B866-EA5A57E37EBF}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {203CA579-3C1A-43E6-B866-EA5A57E37EBF}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {203CA579-3C1A-43E6-B866-EA5A57E37EBF}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {4F687607-DE22-41E0-B5CC-C754AE3AA1A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {4F687607-DE22-41E0-B5CC-C754AE3AA1A9}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {4F687607-DE22-41E0-B5CC-C754AE3AA1A9}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {4F687607-DE22-41E0-B5CC-C754AE3AA1A9}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {F46509D4-67A0-4F36-9F4C-1F4C9FC129AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {F46509D4-67A0-4F36-9F4C-1F4C9FC129AB}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {F46509D4-67A0-4F36-9F4C-1F4C9FC129AB}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {F46509D4-67A0-4F36-9F4C-1F4C9FC129AB}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {30CD147A-5160-48AF-8409-E52595EEB926}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {30CD147A-5160-48AF-8409-E52595EEB926}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {30CD147A-5160-48AF-8409-E52595EEB926}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {30CD147A-5160-48AF-8409-E52595EEB926}.Release|Any CPU.Build.0 = Release|Any CPU 48 | {7CEC99E8-7348-440D-909B-1D203B3F7965}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 49 | {7CEC99E8-7348-440D-909B-1D203B3F7965}.Debug|Any CPU.Build.0 = Debug|Any CPU 50 | {7CEC99E8-7348-440D-909B-1D203B3F7965}.Release|Any CPU.ActiveCfg = Release|Any CPU 51 | {7CEC99E8-7348-440D-909B-1D203B3F7965}.Release|Any CPU.Build.0 = Release|Any CPU 52 | {A7D74AA8-89AF-4650-9659-0839CC9D8D50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 53 | {A7D74AA8-89AF-4650-9659-0839CC9D8D50}.Debug|Any CPU.Build.0 = Debug|Any CPU 54 | {A7D74AA8-89AF-4650-9659-0839CC9D8D50}.Release|Any CPU.ActiveCfg = Release|Any CPU 55 | {A7D74AA8-89AF-4650-9659-0839CC9D8D50}.Release|Any CPU.Build.0 = Release|Any CPU 56 | {DCD5A7C5-062C-4782-B6A4-1AC58C17E36D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 57 | {DCD5A7C5-062C-4782-B6A4-1AC58C17E36D}.Debug|Any CPU.Build.0 = Debug|Any CPU 58 | {DCD5A7C5-062C-4782-B6A4-1AC58C17E36D}.Release|Any CPU.ActiveCfg = Release|Any CPU 59 | {DCD5A7C5-062C-4782-B6A4-1AC58C17E36D}.Release|Any CPU.Build.0 = Release|Any CPU 60 | EndGlobalSection 61 | GlobalSection(SolutionProperties) = preSolution 62 | HideSolutionNode = FALSE 63 | EndGlobalSection 64 | GlobalSection(NestedProjects) = preSolution 65 | {203CA579-3C1A-43E6-B866-EA5A57E37EBF} = {AD01A83A-D3A7-4CF5-AD89-F9C2C7FC359A} 66 | {4F687607-DE22-41E0-B5CC-C754AE3AA1A9} = {5A4E9AF3-C199-4304-A0DE-C3AA652A71A9} 67 | {F46509D4-67A0-4F36-9F4C-1F4C9FC129AB} = {AD01A83A-D3A7-4CF5-AD89-F9C2C7FC359A} 68 | {30CD147A-5160-48AF-8409-E52595EEB926} = {AD01A83A-D3A7-4CF5-AD89-F9C2C7FC359A} 69 | {7CEC99E8-7348-440D-909B-1D203B3F7965} = {5A4E9AF3-C199-4304-A0DE-C3AA652A71A9} 70 | {A7D74AA8-89AF-4650-9659-0839CC9D8D50} = {0D19674D-66F4-461F-86B3-DABAAE3BB9D1} 71 | {DCD5A7C5-062C-4782-B6A4-1AC58C17E36D} = {AD01A83A-D3A7-4CF5-AD89-F9C2C7FC359A} 72 | EndGlobalSection 73 | GlobalSection(ExtensibilityGlobals) = postSolution 74 | SolutionGuid = {6715B2A1-ED9A-41D1-8968-2C76A770658C} 75 | EndGlobalSection 76 | EndGlobal 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 四川易达飞牛交通科技有限公司 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NuGet.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EventBus 2 | Branch | Pipeline 3 | ----|---- 4 | master | [![Build status](https://ci.appveyor.com/api/projects/status/42jeqj0h28pdoc3x/branch/master?svg=true)](https://ci.appveyor.com/project/Jamesxql/eventbus/branch/master)[![Build Status](https://travis-ci.org/FeiniuBus/EventBus.svg?branch=master)](https://travis-ci.org/FeiniuBus/EventBus) 5 | release | [![Build status](https://ci.appveyor.com/api/projects/status/42jeqj0h28pdoc3x/branch/release?svg=true)](https://ci.appveyor.com/project/Jamesxql/eventbus/branch/release)[![Build status](https://ci.appveyor.com/api/projects/status/4bfohsc2n3gfd08i?svg=true)](https://ci.appveyor.com/project/standardcore/eventbus) 6 | milestone/1.0.0 | [![Build status](https://ci.appveyor.com/api/projects/status/42jeqj0h28pdoc3x/branch/milestone/1.0.0?svg=true)](https://ci.appveyor.com/project/Jamesxql/eventbus/branch/milestone/1.0.0)[![Build Status](https://travis-ci.org/FeiniuBus/EventBus.svg?branch=milestone%2F1.0.0)](https://travis-ci.org/FeiniuBus/EventBus) 7 | 8 | ## OverView 9 | EventBus is a .Net Standard library to achieve eventually consistent in distributed architectures system like SOA,MicroService. It is lightweight,easy to use and efficiently. 10 | 11 | ![](https://raw.githubusercontent.com/FeiniuBus/EventBus/master/EventBus.jpg) 12 | 13 | 14 | ## Getting Started 15 | ### Step 1 : Configure EventBus Options 16 | *** Add following code after `services.AddDbContext` in `StartUp.cs` *** 17 | ```csharp 18 | services.AddEventBus(options => 19 | { 20 | // using EntityFramework 21 | options.UseEntityframework<**Your DbContext Type**>(); 22 | 23 | // OR using Ado.NET 24 | options.UseMySQL(**Database connection string**) 25 | 26 | // using RabbitMQ 27 | options.UseRabbitMQ(rabbit => 28 | { 29 | rabbit.HostName = "**Your hostname of RabbitMQ**"; 30 | rabbit.UserName = "**Your username if needed**"; 31 | rabbit.Password = "**Your password if needed**"; 32 | }); 33 | }); 34 | ``` 35 | *** Add following codes in `Configure` scope of `StartUp.cs` *** 36 | ```csharp 37 | app.UseEventBus(); 38 | ``` 39 | 40 | ### Step 2 : Publish Event 41 | * Inject `IEventPublisher` in constructor like `.ctor(IEventPublisher eventPublisher)` 42 | * Begin a transaction 43 | * ***using EntityFramework*** 44 | ```csharp 45 | 46 | using(var transaction = dbContext.Database.BeginTransaction) 47 | { 48 |  //TODO:Businesses codes 49 | 50 |  //Publish Event 51 | await _eventPublisher.PrepareAsync(/*RouteKey*/, /*Content Object*/, /*MetaData Object*/); 52 | 53 | //Commit transaction 54 | transaction.Commit(); 55 | 56 | //Confirm Published Event.The event message won't publish untill invoked **IEventPublisher.ConfirmAsync()** 57 | //And you can decide when the event message be confirmed all by your self. 58 | await _eventPublisher.ConfirmAsync(); 59 | 60 | //Or you can just rollback these messages when exception was thrown. 61 | await _eventPublisher.RollbackAsync(); 62 | } 63 | ``` 64 | * ***using Ado.NET*** 65 | 66 | ```csharp 67 |  IDbConnection dbConnection; /*Open your database connection.*/ 68 | IDbTransaction dbTransaction = dbConnection.BeginTransaction(); 69 | 70 | //TODO:Businesses codes 71 | 72 | //Publish Event 73 | await _eventPublisher.PrepareAsync(/*RouteKey*/, /*Content Object*/, /*MetaData Object*/,dbConnection,dbTransaction); 74 | 75 | //Commit transaction 76 | dbTransaction.Commit(); 77 | dbConnection.Close(); 78 | 79 | //Confirm Published Event.The event message won't publish untill invoked **IEventPublisher.ConfirmAsync()** 80 | //And you can decide when the event message should be confirmed all by your self. 81 | await _eventPublisher.ConfirmAsync(); 82 | 83 | //Or you can just rollback these messages when exception was thrown. 84 | await _eventPublisher.RollbackAsync(); 85 | ``` 86 | 87 | ### step 3 : Dead letter callback handler 88 | * Declare a callback handler class implemented `IFailureHandler` 89 | * Register callback handle in `AddEventBus` scope 90 | ```csharp 91 | options.UseFailureHandle(failure => 92 | { 93 | failure.RegisterFailureCallback(/*RouteKey*/, /*Type of your deadletter callback handler*/); 94 | }); 95 | ``` 96 | 97 | 98 | 99 | ### Step 4 : Consumer callback handler 100 | * Declare a callback handler class implemented `ISubscribeCallbackHandler` 101 | * Register callback handle in `StartUp.cs` 102 | ```csharp 103 | services.AddSub(options => 104 | { 105 | options.ConsumerClientCount = 1; 106 | options.DefaultGroup = "/*Default Group Name*/"; 107 | // Use default group 108 | options.RegisterCallback(/*RouteKey*/, /*Type of your callback handler*/); 109 |  // Use specialized group 110 |  options.RegisterCallback(/*RouteKey*/,/*Group Name*/ /*Type of your callback handler*/); 111 | }); 112 | ``` 113 | 114 | 115 | -------------------------------------------------------------------------------- /appveyor.release.yml: -------------------------------------------------------------------------------- 1 | image: Visual Studio 2017 2 | 3 | # branches to build 4 | branches: 5 | # whitelist 6 | only: 7 | - release 8 | 9 | skip_non_tags: true 10 | 11 | environment: 12 | nodejs_version: "6.9.1" 13 | global: 14 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true 15 | DOTNET_CLI_TELEMETRY_OPTOUT: 1 16 | 17 | install: 18 | # Download .NET Core 2.0 SDK and add to PATH 19 | - ps: $urlCurrent = "https://dotnetcli.azureedge.net/dotnet/Sdk/2.0.0/dotnet-sdk-2.0.0-win-x64.zip" 20 | - ps: $env:DOTNET_INSTALL_DIR = "$pwd\.dotnetsdk" 21 | - ps: mkdir $env:DOTNET_INSTALL_DIR -Force | Out-Null 22 | - ps: $tempFileCurrent = [System.IO.Path]::GetTempFileName() 23 | - ps: (New-Object System.Net.WebClient).DownloadFile($urlCurrent, $tempFileCurrent) 24 | - ps: Add-Type -AssemblyName System.IO.Compression.FileSystem; [System.IO.Compression.ZipFile]::ExtractToDirectory($tempFileCurrent, $env:DOTNET_INSTALL_DIR) 25 | - ps: $env:Path = "$env:DOTNET_INSTALL_DIR;$env:Path" 26 | - ps: $nugetUrl = "https://www.nuget.org/nuget.exe" 27 | - ps: mkdir /.nuget 28 | - ps: $nugetFileName = "/.nuget/nuget.exe" 29 | - ps: (New-Object System.Net.WebClient).DownloadFile($nugetUrl, $nugetFileName) 30 | - ps: $publishOutDir = "/build" 31 | - ps: mkdir $publishOutDir 32 | 33 | init: 34 | - git config --global core.autocrlf true 35 | build_script: 36 | - dotnet --version 37 | - dotnet restore 38 | - dotnet build -c Release 39 | - ps: dotnet pack -c Release -o "/build" 40 | - ps: $OutputPath = Resolve-Path /build 41 | - ps: cd $OutputPath 42 | - ps: dir 43 | - ps: /.nuget/nuget.exe push $OutputPath\*.nupkg $env:NUGET_API_KEY -Source $env:NUGET_SOURCE 44 | 45 | clone_depth: 1 46 | test: off 47 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | image: Visual Studio 2017 2 | environment: 3 | nodejs_version: "6.9.1" 4 | global: 5 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true 6 | DOTNET_CLI_TELEMETRY_OPTOUT: 1 7 | 8 | install: 9 | # Download .NET Core 2.0 SDK and add to PATH 10 | - ps: $urlCurrent = "https://dotnetcli.azureedge.net/dotnet/Sdk/2.0.0/dotnet-sdk-2.0.0-win-x64.zip" 11 | - ps: $env:DOTNET_INSTALL_DIR = "$pwd\.dotnetsdk" 12 | - ps: mkdir $env:DOTNET_INSTALL_DIR -Force | Out-Null 13 | - ps: $tempFileCurrent = [System.IO.Path]::GetTempFileName() 14 | - ps: (New-Object System.Net.WebClient).DownloadFile($urlCurrent, $tempFileCurrent) 15 | - ps: Add-Type -AssemblyName System.IO.Compression.FileSystem; [System.IO.Compression.ZipFile]::ExtractToDirectory($tempFileCurrent, $env:DOTNET_INSTALL_DIR) 16 | - ps: $env:Path = "$env:DOTNET_INSTALL_DIR;$env:Path" 17 | 18 | init: 19 | - git config --global core.autocrlf true 20 | build_script: 21 | - dotnet --version 22 | - dotnet restore 23 | - dotnet build -c Release 24 | - dotnet pack -c Release 25 | 26 | clone_depth: 1 27 | test: off -------------------------------------------------------------------------------- /sample/EventBus.Publish.Sample/Controllers/ValuesController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using EventBus.Core; 7 | 8 | namespace EventBus.Publish.Sample.Controllers 9 | { 10 | [Route("api/[controller]")] 11 | public class ValuesController : Controller 12 | { 13 | private readonly IEventPublisher _eventPublisher; 14 | private readonly SampleDbContext _sampleDbContext; 15 | 16 | public ValuesController(IEventPublisher eventPublisher, SampleDbContext sampleDbContext) 17 | { 18 | _eventPublisher = eventPublisher; 19 | _sampleDbContext = sampleDbContext; 20 | } 21 | 22 | // GET api/values 23 | [HttpGet] 24 | public IEnumerable Get() 25 | { 26 | return new string[] { "value1", "value2" }; 27 | } 28 | 29 | // GET api/values/5 30 | [HttpGet("{id}")] 31 | public string Get(int id) 32 | { 33 | return "value"; 34 | } 35 | 36 | // POST api/values 37 | [HttpPost] 38 | public async Task Post([FromBody]string value) 39 | { 40 | using (var transaction = await _sampleDbContext.Database.BeginTransactionAsync()) 41 | { 42 | await _eventPublisher.PrepareAsync("test", value, new { signature = "" }); 43 | transaction.Commit(); 44 | } 45 | await _eventPublisher.ConfirmAsync(); 46 | return Ok(); 47 | } 48 | 49 | // PUT api/values/5 50 | [HttpPut("{id}")] 51 | public void Put(int id, [FromBody]string value) 52 | { 53 | } 54 | 55 | // DELETE api/values/5 56 | [HttpDelete("{id}")] 57 | public void Delete(int id) 58 | { 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /sample/EventBus.Publish.Sample/EventBus.Publish.Sample.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netcoreapp2.0 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /sample/EventBus.Publish.Sample/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Builder; 7 | using Microsoft.AspNetCore.Hosting; 8 | 9 | namespace EventBus.Publish.Sample 10 | { 11 | public class Program 12 | { 13 | public static void Main(string[] args) 14 | { 15 | var host = new WebHostBuilder() 16 | .UseKestrel() 17 | .UseContentRoot(Directory.GetCurrentDirectory()) 18 | .UseIISIntegration() 19 | .UseStartup() 20 | .UseApplicationInsights() 21 | .Build(); 22 | 23 | host.Run(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /sample/EventBus.Publish.Sample/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Builder; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.Extensions.Configuration; 8 | using Microsoft.Extensions.DependencyInjection; 9 | using Microsoft.Extensions.Logging; 10 | using Microsoft.EntityFrameworkCore; 11 | using EventBus.Core; 12 | using EventBus.Subscribe; 13 | 14 | namespace EventBus.Publish.Sample 15 | { 16 | public class Startup 17 | { 18 | private const string ConnectionString = "Server=192.168.206.129;Port=3306;Database=FeiniuCAP; User=root;Password=kge2001;charset=UTF-8"; 19 | public Startup(IHostingEnvironment env) 20 | { 21 | var builder = new ConfigurationBuilder() 22 | .SetBasePath(env.ContentRootPath) 23 | .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) 24 | .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) 25 | .AddEnvironmentVariables(); 26 | Configuration = builder.Build(); 27 | } 28 | 29 | public IConfigurationRoot Configuration { get; } 30 | 31 | // This method gets called by the runtime. Use this method to add services to the container. 32 | public void ConfigureServices(IServiceCollection services) 33 | { 34 | // Add framework services. 35 | services.AddMvc(); 36 | 37 | //services.AddDbContext(options => options.UseMySql(ConnectionString)); 38 | 39 | services.AddEventBus(options => 40 | { 41 | //options.UseEntityframework(); 42 | options.UseMySQL(ConnectionString); 43 | options.UseRabbitMQ(rabbit => 44 | { 45 | rabbit.HostName = "192.168.206.128"; 46 | rabbit.UserName = "andrew"; 47 | rabbit.Password = "kge2001"; 48 | }); 49 | 50 | options.UseFailureHandle(failure => 51 | { 52 | }); 53 | }); 54 | 55 | services.AddSub(options => 56 | { 57 | options.ConsumerClientCount = 1; 58 | options.DefaultGroup = "eventbus.testgroup"; 59 | 60 | }); 61 | } 62 | 63 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 64 | public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) 65 | { 66 | loggerFactory.AddConsole(Configuration.GetSection("Logging")); 67 | loggerFactory.AddDebug() 68 | .AddConsole(); 69 | app.UseEventBus(); 70 | app.UseMvc(); 71 | } 72 | } 73 | 74 | public class SampleDbContext : DbContext 75 | { 76 | public SampleDbContext(DbContextOptions options) : base(options) 77 | { 78 | 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /sample/EventBus.Publish.Sample/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Debug", 6 | "System": "Information", 7 | "Microsoft": "Information" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /sample/EventBus.Publish.Sample/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /sample/EventBus.Sample/Controllers/ValuesController.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Microsoft.AspNetCore.Mvc; 3 | using EventBus.Core; 4 | 5 | namespace EventBus.Sample.Controllers 6 | { 7 | [Route("api/[controller]")] 8 | public class ValuesController : Controller 9 | { 10 | private readonly IEventPublisher _eventPublisher; 11 | private readonly SampleDbContext _sampleDbContext; 12 | 13 | public ValuesController(IEventPublisher eventPublisher, SampleDbContext sampleDbContext) 14 | { 15 | _eventPublisher = eventPublisher; 16 | _sampleDbContext = sampleDbContext; 17 | } 18 | 19 | [HttpPost] 20 | public async Task Post([FromBody]string value) 21 | { 22 | var transaction = await _sampleDbContext.Database.BeginTransactionAsync(); 23 | 24 | //await _eventPublisher.PrepareAsync("charge.ok.shuttle", "Hello-World1", new { signature = "" }); 25 | //await _eventPublisher.PrepareAsync("charge.ok.commute", "Hello-World2", new { signature = "" }); 26 | 27 | for (var i = 0; i < 5; ++i) 28 | { 29 | await _eventPublisher.PrepareAsync("charge.ok.shuttle", "Hello-World1", new { signature = "" }); 30 | } 31 | 32 | transaction.Commit(); 33 | 34 | await _eventPublisher.ConfirmAsync(); 35 | 36 | return Ok(); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /sample/EventBus.Sample/EventBus.Sample.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /sample/EventBus.Sample/EventHandlers/NewUserEventHandlerAll.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using EventBus.Subscribe; 5 | using Microsoft.AspNetCore.Hosting; 6 | using Microsoft.Extensions.Logging; 7 | 8 | namespace EventBus.Sample.EventHandlers 9 | { 10 | public class NewUserEventHandlerAll : ISubscribeHandler 11 | { 12 | private readonly ILogger _logger; 13 | 14 | public NewUserEventHandlerAll(IHostingEnvironment env 15 | , ILogger logger) 16 | { 17 | _logger = logger; 18 | } 19 | 20 | public Task HandleAsync(string message) 21 | { 22 | _logger.LogInformation($"receive message1 from NewUserEventHandlerAll {message} {DateTime.Now}"); 23 | 24 | return Task.FromResult(true); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /sample/EventBus.Sample/EventHandlers/NewUserEventHandlerCommuter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using EventBus.Subscribe; 5 | using Microsoft.AspNetCore.Hosting; 6 | using Microsoft.Extensions.Logging; 7 | 8 | namespace EventBus.Sample.EventHandlers 9 | { 10 | public class NewUserEventHandlerCommuter : ISubscribeHandler 11 | { 12 | private readonly ILogger _logger; 13 | 14 | public NewUserEventHandlerCommuter(IHostingEnvironment env 15 | , ILogger logger) 16 | { 17 | _logger = logger; 18 | } 19 | 20 | public Task HandleAsync(string message) 21 | { 22 | _logger.LogInformation($"receive message1 from NewUserEventHandlerCommuter {message} {DateTime.Now}"); 23 | 24 | return Task.FromResult(true); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /sample/EventBus.Sample/EventHandlers/NewUserEventHandlerShuttle.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using EventBus.Subscribe; 5 | using Microsoft.AspNetCore.Hosting; 6 | using Microsoft.Extensions.Logging; 7 | 8 | namespace EventBus.Sample.EventHandlers 9 | { 10 | public class NewUserEventHandlerShuttle : ISubscribeHandler 11 | { 12 | private readonly ILogger _logger; 13 | 14 | public NewUserEventHandlerShuttle(IHostingEnvironment env 15 | , ILogger logger) 16 | { 17 | _logger = logger; 18 | } 19 | 20 | public Task HandleAsync(string message) 21 | { 22 | _logger.LogInformation($"receive message1 from NewUserEventHandlerShuttle {message} {DateTime.Now}"); 23 | 24 | return Task.FromResult(true); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /sample/EventBus.Sample/FailedEventHandlers/NewUserFailedMessageHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using EventBus.Core; 3 | 4 | namespace EventBus.Sample.FailedEventHandlers 5 | { 6 | public class NewUserFailedMessageHandler : IFailureHandler 7 | { 8 | private readonly ConsumeFailureContext _context; 9 | 10 | public NewUserFailedMessageHandler(IFailureContextAccessor accessor) 11 | { 12 | _context = accessor.FailureContext; 13 | } 14 | 15 | public Task HandleAsync(string message) 16 | { 17 | return Task.FromResult(false); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /sample/EventBus.Sample/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Builder; 7 | using Microsoft.AspNetCore.Hosting; 8 | 9 | namespace EventBus.Sample 10 | { 11 | public class Program 12 | { 13 | public static void Main(string[] args) 14 | { 15 | var host = new WebHostBuilder() 16 | .UseKestrel() 17 | .UseContentRoot(Directory.GetCurrentDirectory()) 18 | .UseIISIntegration() 19 | .UseStartup() 20 | .UseApplicationInsights() 21 | .Build(); 22 | 23 | host.Run(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /sample/EventBus.Sample/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:51080/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "launchUrl": "api/values", 15 | "environmentVariables": { 16 | "ASPNETCORE_ENVIRONMENT": "Development" 17 | } 18 | }, 19 | "EventBus.Sample": { 20 | "commandName": "Project", 21 | "launchBrowser": true, 22 | "launchUrl": "api/values", 23 | "environmentVariables": { 24 | "ASPNETCORE_ENVIRONMENT": "Development" 25 | }, 26 | "applicationUrl": "" 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /sample/EventBus.Sample/Startup.cs: -------------------------------------------------------------------------------- 1 | using EventBus.Sample.EventHandlers; 2 | using EventBus.Subscribe; 3 | using Microsoft.AspNetCore.Builder; 4 | using Microsoft.AspNetCore.Hosting; 5 | using Microsoft.Extensions.Configuration; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Microsoft.Extensions.Logging; 8 | using Microsoft.EntityFrameworkCore; 9 | using EventBus.Sample.FailedEventHandlers; 10 | using EventBus.Core; 11 | using EventBus.Alert; 12 | 13 | namespace EventBus.Sample 14 | { 15 | public class Startup 16 | { 17 | public Startup(IHostingEnvironment env) 18 | { 19 | var builder = new ConfigurationBuilder() 20 | .SetBasePath(env.ContentRootPath) 21 | .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) 22 | .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) 23 | .AddEnvironmentVariables(); 24 | Configuration = builder.Build(); 25 | } 26 | 27 | public IConfigurationRoot Configuration { get; } 28 | 29 | private const string ConnectionString = "Server=localhost;Port=3306;Database=FeiniuCAP; User=root;Password=123456;charset=UTF-8"; 30 | 31 | // This method gets called by the runtime. Use this method to add services to the container. 32 | public void ConfigureServices(IServiceCollection services) 33 | { 34 | // Add framework services. 35 | services.AddMvc(); 36 | 37 | services.AddDbContext(options => options.UseMySql(ConnectionString)); 38 | 39 | services.AddEventBus(options => 40 | { 41 | options.UseEntityframework(); 42 | options.UseRabbitMQ(rabbit => 43 | { 44 | rabbit.HostName = "localhost"; 45 | rabbit.UserName = "guest"; 46 | rabbit.Password = "123456"; 47 | rabbit.Port = 5672; 48 | }); 49 | 50 | options.UseFailureHandle(failure => 51 | { 52 | failure.RegisterFailureCallback("eventbus.testtopic", typeof(NewUserFailedMessageHandler)); 53 | }); 54 | }); 55 | 56 | 57 | services.AddSub(options => 58 | { 59 | options.ConsumerClientCount = 5; 60 | options.DefaultGroup = "FeiniuBusPayment1111"; 61 | 62 | options.RegisterCallback("charge.ok.shuttle", "shuttle", typeof(NewUserEventHandlerShuttle)); 63 | options.RegisterCallback("charge.ok.commute", "commute", typeof(NewUserEventHandlerCommuter)); 64 | options.RegisterCallback("charge.ok.*", "pay", typeof(NewUserEventHandlerAll)); 65 | }); 66 | 67 | services.AddEventBusAlert(opts => 68 | { 69 | }); 70 | } 71 | 72 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 73 | public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) 74 | { 75 | loggerFactory.AddDebug() 76 | .AddConsole(LogLevel.Information); 77 | 78 | app.UseEventBus(); 79 | 80 | app.UseMvc(); 81 | } 82 | } 83 | 84 | public class SampleDbContext : DbContext 85 | { 86 | public SampleDbContext(DbContextOptions options) : base(options) 87 | { 88 | 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /sample/EventBus.Sample/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Debug", 6 | "System": "Information", 7 | "Microsoft": "Information" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /sample/EventBus.Sample/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/EventBus.Alert/DefaultLastAlertMemento.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace EventBus.Alert 4 | { 5 | public class DefaultLastAlertMemento : ILastAlertMemento 6 | { 7 | public DateTime LastPubAlert { get; set; } 8 | 9 | public DateTime LastSubAlert { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/EventBus.Alert/DefaultPubAlertHandler.cs: -------------------------------------------------------------------------------- 1 | using EventBus.Core; 2 | using FeiniuBusSDK.Notification; 3 | using Microsoft.Extensions.Logging; 4 | using Microsoft.Extensions.Options; 5 | using System; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | 9 | namespace EventBus.Alert 10 | { 11 | public class DefaultPubAlertHandler : IPubFailureHandler 12 | { 13 | private readonly SMSAlertOptions _options; 14 | private readonly ILastAlertMemento _memento; 15 | private readonly IFeiniuBusNotification _feiniuBusNotification; 16 | private readonly ILogger _logger; 17 | 18 | public DefaultPubAlertHandler(IOptions optionsAccessor 19 | , ILastAlertMemento memento 20 | , IFeiniuBusNotification feiniuBusNotification 21 | , ILogger logger) 22 | { 23 | _options = optionsAccessor.Value; 24 | _memento = memento; 25 | _feiniuBusNotification = feiniuBusNotification; 26 | _logger = logger; 27 | } 28 | 29 | public async Task HandleAsync(string exchange, string topic, byte[] content) 30 | { 31 | if (!_options.Enable) 32 | { 33 | return; 34 | } 35 | 36 | if (!_options.Contacts.Any()) 37 | { 38 | return; 39 | } 40 | 41 | var now = DateTime.Now; 42 | 43 | if ((now - _memento.LastSubAlert).TotalSeconds < _options.AlertIntervalSecs) 44 | { 45 | return; 46 | } 47 | 48 | _memento.LastSubAlert = now; 49 | 50 | var request = new FeiniuBusSDK.Notification.Model.CreateSmsRequest 51 | { 52 | Numbers = _options.Contacts, 53 | Message = CreateMessage(exchange, topic, content) 54 | }; 55 | 56 | try 57 | { 58 | await _feiniuBusNotification.CreateSmsAsync(request); 59 | } 60 | catch (Exception ex) 61 | { 62 | _logger.LogError(110, ex, $"订阅消息失败通知发送失败"); 63 | } 64 | } 65 | 66 | private static string CreateMessage(string exchange, string topic, byte[] content) 67 | { 68 | return $"你的主题{topic}发送失败,请及时处理。"; 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/EventBus.Alert/DefaultSubAlertHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using EventBus.Core; 3 | using Microsoft.Extensions.Options; 4 | using System; 5 | using FeiniuBusSDK.Notification; 6 | using System.Linq; 7 | using EventBus.Core.Internal.Model; 8 | using Microsoft.Extensions.Logging; 9 | 10 | namespace EventBus.Alert 11 | { 12 | public class DefaultSubAlertHandler : ISubFailureHandler 13 | { 14 | private readonly SMSAlertOptions _options; 15 | private readonly ILastAlertMemento _memento; 16 | private readonly IFeiniuBusNotification _feiniuBusNotification; 17 | private readonly ILogger _logger; 18 | 19 | public DefaultSubAlertHandler(IOptions optionsAccessor 20 | , ILastAlertMemento memento 21 | , IFeiniuBusNotification feiniuBusNotification 22 | , ILogger logger) 23 | { 24 | _options = optionsAccessor.Value; 25 | _memento = memento; 26 | _feiniuBusNotification = feiniuBusNotification; 27 | _logger = logger; 28 | } 29 | 30 | public async Task HandleAsync(FailContext context) 31 | { 32 | if (!_options.Enable) 33 | { 34 | return; 35 | } 36 | 37 | if (!_options.Contacts.Any()) 38 | { 39 | return; 40 | } 41 | 42 | var now = DateTime.Now; 43 | 44 | if ((now - _memento.LastPubAlert).TotalSeconds < _options.AlertIntervalSecs) 45 | { 46 | return; 47 | } 48 | 49 | _memento.LastPubAlert = now; 50 | 51 | var request = new FeiniuBusSDK.Notification.Model.CreateSmsRequest 52 | { 53 | Numbers = _options.Contacts, 54 | Message = CreateMessage(context) 55 | }; 56 | 57 | try 58 | { 59 | await _feiniuBusNotification.CreateSmsAsync(request); 60 | } 61 | catch (Exception ex) 62 | { 63 | _logger.LogError(110, ex, $"订阅消息失败通知发送失败"); 64 | } 65 | } 66 | 67 | private static string CreateMessage(FailContext context) 68 | { 69 | var msg = ""; 70 | 71 | if (context.Raw != null) 72 | { 73 | msg = $"您订阅的消息{context.Raw}处理失败,失败原因{context.ExceptionMessage}"; 74 | } 75 | 76 | if (context.State is ReceivedMessage receivedMessage) 77 | { 78 | 79 | msg = $"您订阅的消息处理失败,Id={receivedMessage.Id},TransactId={receivedMessage.TransactId},Group={receivedMessage.Group},Topic={receivedMessage.RouteKey},失败原因:{context.ExceptionMessage}"; 80 | } 81 | 82 | return msg; 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/EventBus.Alert/EventBus.Alert.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard1.6 5 | 1.0.6 6 | https://github.com/FeiniuBus/EventBus 7 | false 8 | https://s3.cn-north-1.amazonaws.com.cn/nuget-icons/icon175x175.jpeg 9 | https://github.com/FeiniuBus/EventBus.git 10 | https://raw.githubusercontent.com/FeiniuBus/EventBus/master/LICENSE 11 | Feiniubus EventBus libary 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/EventBus.Alert/EventBusAlertServiceCollectionExtenssions.cs: -------------------------------------------------------------------------------- 1 | using EventBus.Core; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using System; 4 | 5 | namespace EventBus.Alert 6 | { 7 | public static class EventBusAlertServiceCollectionExtenssions 8 | { 9 | public static IServiceCollection AddEventBusAlert(this IServiceCollection services, Action setup) 10 | { 11 | services.Configure(setup); 12 | services.AddScoped(); 13 | services.AddScoped(); 14 | services.AddSingleton(); 15 | 16 | return services; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/EventBus.Alert/ILastAlertMemento.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace EventBus.Alert 6 | { 7 | public interface ILastAlertMemento 8 | { 9 | DateTime LastPubAlert { get; set; } 10 | 11 | DateTime LastSubAlert { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/EventBus.Alert/SMSAlertOptions.cs: -------------------------------------------------------------------------------- 1 | namespace EventBus.Alert 2 | { 3 | public class SMSAlertOptions 4 | { 5 | public string[] Contacts { get; set; } 6 | 7 | public long AlertIntervalSecs { get; set; } = 60 * 5; 8 | 9 | public bool Enable { get; set; } = true; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/EventBus.Core/ConsumeFailureContext.cs: -------------------------------------------------------------------------------- 1 | namespace EventBus.Core 2 | { 3 | public class ConsumeFailureContext 4 | { 5 | public string FailureGroup { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/EventBus.Core/EventAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace EventBus.Core 4 | { 5 | [AttributeUsage(AttributeTargets.Class)] 6 | public class EventAttribute: Attribute 7 | { 8 | public string Name { get; set; } = null; 9 | 10 | public string Key { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/EventBus.Core/EventBus.Core.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netstandard1.6 4 | 1.0.6.1 5 | https://github.com/FeiniuBus/EventBus 6 | false 7 | https://s3.cn-north-1.amazonaws.com.cn/nuget-icons/icon175x175.jpeg 8 | https://github.com/FeiniuBus/EventBus.git 9 | https://raw.githubusercontent.com/FeiniuBus/EventBus/master/LICENSE 10 | Feiniubus EventBus libary 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/EventBus.Core/EventBus.Options.Extension.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace EventBus.Core 4 | { 5 | public interface IEventBusOptionsExtension 6 | { 7 | void AddServices(IServiceCollection serviceCollection); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/EventBus.Core/EventBus.Options.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | 5 | namespace EventBus.Core 6 | { 7 | public class EventBusOptions 8 | { 9 | private readonly IList _extensions; 10 | 11 | public EventBusOptions() 12 | { 13 | _extensions = new List(); 14 | } 15 | 16 | public IReadOnlyCollection Extensions => new ReadOnlyCollection(_extensions); 17 | 18 | /// 19 | /// Registers an extension that will be executed when building services. 20 | /// 21 | /// 22 | public void RegisterExtension([NotNull]IEventBusOptionsExtension extension) 23 | { 24 | _extensions.Add(extension); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/EventBus.Core/EventDescriptor.cs: -------------------------------------------------------------------------------- 1 | using EventBus.Core.Infrastructure; 2 | using EventBus.Core.Internal; 3 | 4 | namespace EventBus.Core 5 | { 6 | public class EventDescriptor 7 | { 8 | public EventDescriptor(string routeKey, IMessage message, string exchange = null, object args = null) 9 | { 10 | RouteKey = routeKey; 11 | Message = message; 12 | ContentType = message.Content.GetType().FullName; 13 | Exchange = exchange ?? "default.exchange@feiniubus"; 14 | Arguments = args == null ? null : new AnonymousObject(args); 15 | } 16 | 17 | public string Exchange { get; internal set; } 18 | 19 | public string RouteKey { get; internal set; } 20 | 21 | public IMessage Message { get; internal set; } 22 | 23 | public string ContentType { get; internal set; } 24 | 25 | public AnonymousObject Arguments { get; set; } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/EventBus.Core/Extensions/AsynchronousTaskExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | namespace EventBus.Core.Extensions 5 | { 6 | public static class AsynchronousTaskExtensions 7 | { 8 | internal static TaskFactory GetTaskFactory() => new TaskFactory(CancellationToken.None, 9 | TaskCreationOptions.None, TaskContinuationOptions.None, TaskScheduler.Default); 10 | 11 | public static void Synchronize(this Task task) 12 | { 13 | var factory = GetTaskFactory(); 14 | factory.StartNew(() => task).Unwrap().GetAwaiter().GetResult(); 15 | } 16 | 17 | public static TResult Synchronize(this Task task) 18 | { 19 | var factory = GetTaskFactory(); 20 | return factory.StartNew(() => task).Unwrap().GetAwaiter().GetResult(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/EventBus.Core/Extensions/ExceptionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace EventBus.Core.Extensions 5 | { 6 | public static class ExceptionExtensions 7 | { 8 | public static IEnumerable GetMessages(this Exception e) 9 | { 10 | var messages = new List(); 11 | Exception ex = e; 12 | while(ex != null) 13 | { 14 | messages.Add(ex.Message); 15 | ex = ex.InnerException; 16 | } 17 | return messages; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/EventBus.Core/Extensions/ILogger.Extensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace EventBus.Core.Extensions 7 | { 8 | public static class ILoggerExtensions 9 | { 10 | public static void DecodeError(this ILogger logger, MessageContext context, Exception e) 11 | { 12 | LogJson(logger, $"An exception has been thrown when decode message from context.", new 13 | { 14 | InnerExceptionMessages = GetErrorMessages(e), 15 | MessageContext = context 16 | }); 17 | } 18 | 19 | public static void ReceivedEventPersistenterInsert(this ILogger logger, object message, Exception e) 20 | { 21 | LogJson(logger, $"An exception has been thrown when insert received message[requeue]:.", new 22 | { 23 | InnerExceptionMessages = GetErrorMessages(e), 24 | Message = message 25 | }); 26 | } 27 | 28 | public static void CreateDefaultConsumerInvoker(this ILogger logger, MessageContext context, Exception e) 29 | { 30 | LogJson(logger, $"An exception has been thrown when create DefaultConsumerInvoker.", new 31 | { 32 | InnerExceptionMessages = GetErrorMessages(e), 33 | MessageContext = context 34 | }); 35 | } 36 | 37 | public static void InvokeConsumer(this ILogger logger, MessageContext context,object msg, Exception e) 38 | { 39 | LogJson(logger, $"An exception has been thrown when invoke consumer callback handler.", new 40 | { 41 | InnerExceptionMessages = GetErrorMessages(e), 42 | Message = msg, 43 | MessageContext = context 44 | }); 45 | } 46 | 47 | public static void UpdateReceivedMessage(this ILogger logger, object msg, Exception e) 48 | { 49 | LogJson(logger, $"An exception has been thrown when update received message[ignore].", new 50 | { 51 | InnerExceptionMessages = GetErrorMessages(e), 52 | Message = msg 53 | }); 54 | } 55 | 56 | private static void LogJson(ILogger logger, string message, object data) 57 | { 58 | if (logger == null) return; 59 | logger.LogError($"{message}---->{data.ToJson()}"); 60 | } 61 | 62 | private static IEnumerable GetErrorMessages(Exception e) 63 | { 64 | var ex = e; 65 | var messages = new List(); 66 | while(ex != null) 67 | { 68 | messages.Add(ex.Message); 69 | ex = ex.InnerException; 70 | } 71 | return messages; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/EventBus.Core/Extensions/IMessage.Extensions.cs: -------------------------------------------------------------------------------- 1 | using EventBus.Core.Infrastructure; 2 | using Newtonsoft.Json; 3 | 4 | namespace EventBus.Core.Extensions 5 | { 6 | public static class IMessageExtensions 7 | { 8 | public static object GetTransferMessage(this IMessage message) 9 | { 10 | return message.Content; 11 | } 12 | 13 | public static string GetTransferJson(this IMessage message) 14 | { 15 | return FeiniuBus.Util.FeiniuBusJsonConvert.SerializeObject(message.GetTransferMessage()); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/EventBus.Core/Extensions/IMetaData.Extensions.cs: -------------------------------------------------------------------------------- 1 | using EventBus.Core.Infrastructure; 2 | using Newtonsoft.Json; 3 | 4 | namespace EventBus.Core.Extensions 5 | { 6 | public static class IMetaDataExtensions 7 | { 8 | public static string ToJson(this IMetaData metaData) 9 | { 10 | return FeiniuBus.Util.FeiniuBusJsonConvert.SerializeObject(metaData.GetDictionary()); 11 | } 12 | 13 | public static IMetaData Union(this IMetaData metaData, IMetaData source) 14 | { 15 | metaData.Contact(source); 16 | return metaData; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/EventBus.Core/Extensions/Object.Extenssions.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace EventBus.Core.Extensions 7 | { 8 | public static class ObjectExtenssion 9 | { 10 | public static string ToJson(this object obj) 11 | { 12 | return FeiniuBus.Util.FeiniuBusJsonConvert.SerializeObject(obj); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/EventBus.Core/Extenssion.ApplicationBuilder.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.AspNetCore.Hosting; 4 | 5 | namespace EventBus.Core 6 | { 7 | public static class ApplicationExtenssion 8 | { 9 | public static IApplicationBuilder UseEventBus(this IApplicationBuilder appBuilder) 10 | { 11 | var services = appBuilder.ApplicationServices.CreateScope().ServiceProvider; 12 | 13 | var bootstrapper = services.GetService(); 14 | bootstrapper.Start(); 15 | 16 | var lifeTime = services.GetService(); 17 | lifeTime.ApplicationStopped.Register(() => 18 | { 19 | bootstrapper.Dispose(); 20 | }); 21 | 22 | return appBuilder; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/EventBus.Core/Extenssion.ServiceCollection.cs: -------------------------------------------------------------------------------- 1 | using EventBus.Core; 2 | using EventBus.Core.Infrastructure; 3 | using EventBus.Subscribe.Infrastructure; 4 | using Microsoft.Extensions.DependencyInjection.Extensions; 5 | using System; 6 | 7 | namespace Microsoft.Extensions.DependencyInjection 8 | { 9 | public static class ServiceCollectionExtenssion 10 | { 11 | public static void AddEventBus(this IServiceCollection serviceCollection, Action configure) 12 | { 13 | var options = new EventBusOptions(); 14 | configure(options); 15 | 16 | serviceCollection.AddScoped(); 17 | serviceCollection.AddScoped(); 18 | serviceCollection.AddSingleton(); 19 | serviceCollection.TryAddTransient(); 20 | serviceCollection.TryAddScoped(); 21 | serviceCollection.AddSingleton(); 22 | 23 | foreach (var extension in options.Extensions) 24 | { 25 | extension.AddServices(serviceCollection); 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/EventBus.Core/FailContext.cs: -------------------------------------------------------------------------------- 1 | namespace EventBus.Core 2 | { 3 | public class FailContext 4 | { 5 | public object State { get; set; } 6 | 7 | public string Raw { get; set; } 8 | 9 | public string ExceptionMessage { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/EventBus.Core/FailedMessageContext.cs: -------------------------------------------------------------------------------- 1 | namespace EventBus.Core 2 | { 3 | public class FailedMessageContext 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/EventBus.Core/FailureExtenssion.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using EventBus.Core.Infrastructure; 4 | 5 | namespace EventBus.Core 6 | { 7 | public class FailureExtenssion : IEventBusOptionsExtension 8 | { 9 | private readonly Action _configure; 10 | 11 | public FailureExtenssion(Action configure) 12 | { 13 | _configure = configure; 14 | } 15 | 16 | public void AddServices(IServiceCollection serviceCollection) 17 | { 18 | var options = new FailureHandleOptions(); 19 | _configure(options); 20 | 21 | var provider = serviceCollection.BuildServiceProvider(); 22 | var rabbitOptions = provider.GetRequiredService(); 23 | 24 | options.BuildWithDefaultSelfExchangeName(rabbitOptions.DefaultExchangeName, rabbitOptions.DefaultDeadLetterExchange); 25 | 26 | foreach(var item in options.DeadLetterInfos) 27 | { 28 | serviceCollection.AddScoped(item.CallbackType, item.CallbackType); 29 | } 30 | 31 | serviceCollection.AddSingleton(options); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/EventBus.Core/IBootstrapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace EventBus.Core 4 | { 5 | public interface IBootstrapper: IDisposable 6 | { 7 | void Start(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/EventBus.Core/IClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace EventBus.Core 4 | { 5 | public interface IClient: IDisposable 6 | { 7 | Action OnReceive { get; set; } 8 | 9 | void Subscribe(string[] topics); 10 | void Listening(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/EventBus.Core/IConnectionFactoryAccessor.cs: -------------------------------------------------------------------------------- 1 | using RabbitMQ.Client; 2 | 3 | namespace EventBus.Core 4 | { 5 | public interface IConnectionFactoryAccessor 6 | { 7 | IConnectionFactory ConnectionFactory { get; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/EventBus.Core/IConsumer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace EventBus.Core 4 | { 5 | public interface IConsumer: IDisposable 6 | { 7 | void Start(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/EventBus.Core/IEnviromentNameConcator.cs: -------------------------------------------------------------------------------- 1 | namespace EventBus.Core 2 | { 3 | public interface IEnviromentNameConcator 4 | { 5 | string Concat(string str); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/EventBus.Core/IEventBusTransactionFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Data; 2 | using System.Threading.Tasks; 3 | 4 | namespace EventBus.Core 5 | { 6 | public interface IEventBusTransactionFactory 7 | { 8 | Task BeginTransactionAsync(IDbConnection dbConnection, IsolationLevel isolationLevel = IsolationLevel.ReadCommitted); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/EventBus.Core/IEventPublisher.Extensions.cs: -------------------------------------------------------------------------------- 1 | using EventBus.Core.Infrastructure; 2 | using EventBus.Core.Internal; 3 | using System.Data; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace EventBus.Core 8 | { 9 | public static class IEventPublisherExtensions 10 | { 11 | public static async Task PrepareAsync(this IEventPublisher eventPublisher, string routeKey, object content, object metaData, string exchange = "default.exchange@feiniubus", object args = null) 12 | { 13 | var message = new DefaultMessage(content); 14 | 15 | var anonyObj = new AnonymousObject(metaData); 16 | var anonyMembers = anonyObj.GetProperties().Where(member => member.MemberType == System.Reflection.MemberTypes.Property) 17 | .Select(member=> new { MemberName = member.MemberName, Value = anonyObj.GetValue(member.DeclaringType, member.MemberName) }); 18 | 19 | if (anonyMembers.Any()) 20 | { 21 | var md = new MetaData(); 22 | foreach (var member in anonyMembers) 23 | { 24 | md.Set(member.MemberName, member.Value.ToString()); 25 | } 26 | message.MetaData.Contact(md); 27 | } 28 | 29 | await eventPublisher.PrepareAsync(new EventDescriptor(routeKey, message, exchange, args)); 30 | } 31 | 32 | public static async Task PrepareAsync(this IEventPublisher eventPublisher, string routeKey, object content, object metaData, IDbConnection dbConnection, IDbTransaction dbTransaction, string exchange = "default.exchange@feiniubus", object args = null) 33 | { 34 | var message = new DefaultMessage(content); 35 | 36 | var anonyObj = new AnonymousObject(metaData); 37 | var anonyMembers = anonyObj.GetProperties().Where(member => member.MemberType == System.Reflection.MemberTypes.Property) 38 | .Select(member => new { MemberName = member.MemberName, Value = anonyObj.GetValue(member.DeclaringType, member.MemberName) }); 39 | 40 | if (anonyMembers.Any()) 41 | { 42 | var md = new MetaData(); 43 | foreach (var member in anonyMembers) 44 | { 45 | md.Set(member.MemberName, member.Value.ToString()); 46 | } 47 | message.MetaData.Contact(md); 48 | } 49 | 50 | await eventPublisher.PrepareAsync(new EventDescriptor(routeKey, message, exchange, args), dbConnection,dbTransaction); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/EventBus.Core/IEventPublisher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Threading.Tasks; 4 | 5 | namespace EventBus.Core 6 | { 7 | public interface IEventPublisher : IDisposable 8 | { 9 | IEventPublisher CreateScope(); 10 | Task PrepareAsync(EventDescriptor descriptor); 11 | Task PrepareAsync(EventDescriptor descriptor, IDbConnection dbConnection, IDbTransaction dbTransaction); 12 | Task ConfirmAsync(); 13 | Task RollbackAsync(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/EventBus.Core/IEventTransaction.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data; 4 | using System.Threading.Tasks; 5 | 6 | namespace EventBus.Core 7 | { 8 | public interface IEventTransaction : IDisposable 9 | { 10 | IDbConnection Connection { get; } 11 | IsolationLevel IsolationLevel { get; } 12 | 13 | Task CommitAsync(); 14 | Task RollbackAsync(); 15 | IDbTransaction Transaction { get; } 16 | long TransactID { get; } 17 | Task PublishEventsAsync(IEnumerable eventDescriptors); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/EventBus.Core/IFailure.Client.cs: -------------------------------------------------------------------------------- 1 | namespace EventBus.Core 2 | { 3 | public interface IFailureClient: IClient 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/EventBus.Core/IFailureContextAccessor.cs: -------------------------------------------------------------------------------- 1 | namespace EventBus.Core 2 | { 3 | public interface IFailureContextAccessor 4 | { 5 | ConsumeFailureContext FailureContext { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/EventBus.Core/IFailureHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace EventBus.Core 4 | { 5 | public interface IFailureHandler 6 | { 7 | Task HandleAsync(string message); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/EventBus.Core/IIdentityGenerator.Default.cs: -------------------------------------------------------------------------------- 1 | using FeiniuBus; 2 | 3 | namespace EventBus.Core 4 | { 5 | public class IdentityGenerator : IIdentityGenerator 6 | { 7 | public long NextIdentity() 8 | { 9 | return Puid.NewPuid(); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/EventBus.Core/IIdentityGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace EventBus.Core 2 | { 3 | public interface IIdentityGenerator 4 | { 5 | long NextIdentity(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/EventBus.Core/IMessageDecoder.cs: -------------------------------------------------------------------------------- 1 | using EventBus.Core.Internal.Model; 2 | 3 | namespace EventBus.Core 4 | { 5 | public interface IMessageDecoder 6 | { 7 | ReceivedMessage Decode(MessageContext context); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/EventBus.Core/IMessageQueueTransaction.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace EventBus.Core 5 | { 6 | public interface IMessageQueueTransaction : IDisposable 7 | { 8 | Task CommitAsync(); 9 | Task RollbackAsync(); 10 | Task PublishAsync(string exchange, string routingKey, byte[] body); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/EventBus.Core/IMessageSerializer.cs: -------------------------------------------------------------------------------- 1 | namespace EventBus.Core 2 | { 3 | public interface IMessageSerializer 4 | { 5 | byte[] Serialize(object message); 6 | T Deserialize(byte[] message); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/EventBus.Core/IPubFailureHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | 6 | namespace EventBus.Core 7 | { 8 | public interface IPubFailureHandler 9 | { 10 | Task HandleAsync(string exchange, string topick, byte[] content); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/EventBus.Core/IPubMessageValidator.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace EventBus.Core 4 | { 5 | public interface IPubMessageValidator 6 | { 7 | void Validate(PubMessageValidateContext context); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/EventBus.Core/IPublishedEventPersistenter.cs: -------------------------------------------------------------------------------- 1 | using EventBus.Core.State; 2 | using System.Data; 3 | using System.Threading.Tasks; 4 | 5 | namespace EventBus.Core 6 | { 7 | public interface IPublishedEventPersistenter 8 | { 9 | Task EnsureCreatedAsync(); 10 | 11 | #region Using Ado.Net 12 | /// 13 | /// [For Ado.NET] Insert a message into database asynchrony. 14 | /// 15 | /// 16 | /// 17 | Task InsertAsync(object message, IDbConnection dbConnection, IDbTransaction dbTransaction); 18 | /// 19 | /// [For Ado.NET] Change state of a specialized message asynchrony. 20 | /// 21 | /// 22 | /// new message state 23 | /// 24 | Task ChangeStateAsync(long messageId, long transactId, MessageState messageState, IDbConnection dbConnection, IDbTransaction dbTransaction); 25 | #endregion 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/EventBus.Core/IReceivedEventPersistenter.cs: -------------------------------------------------------------------------------- 1 | using EventBus.Core.State; 2 | using System.Threading.Tasks; 3 | 4 | namespace EventBus.Core 5 | { 6 | public interface IReceivedEventPersistenter 7 | { 8 | Task EnsureCreatedAsync(); 9 | 10 | Task InsertAsync(object message); 11 | Task ChangeStateAsync(long messageId, long transactId, string group, MessageState messageState); 12 | Task ChangeStateAsync(long id, MessageState messageState); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/EventBus.Core/ISubFailureHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace EventBus.Core 4 | { 5 | public interface ISubFailureHandler 6 | { 7 | Task HandleAsync(FailContext context); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/EventBus.Core/Infrastructure/AffectedRowsCountUnExpectedException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace EventBus.Core.Infrastructure 4 | { 5 | public class AffectedRowsCountUnExpectedException : Exception 6 | { 7 | public AffectedRowsCountUnExpectedException(int expected, int actually) : base($"Sql execution should affect {expected} rows but affected {actually} rows actually.") 8 | { 9 | Expected = expected; 10 | Actually = actually; 11 | } 12 | 13 | public int Expected { get; } 14 | public int Actually { get; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/EventBus.Core/Infrastructure/DeadLetterInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace EventBus.Core.Infrastructure 4 | { 5 | public class DeadLetterInfo 6 | { 7 | public string Topic { get; set; } 8 | 9 | public Type HandlerType { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/EventBus.Core/Infrastructure/DefaultEnviromentalNameUpdator.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using System; 3 | 4 | namespace EventBus.Core.Infrastructure 5 | { 6 | public class DefaultEnviromentalNameUpdator : IEnviromentNameConcator 7 | { 8 | private readonly IHostingEnvironment _env; 9 | 10 | public DefaultEnviromentalNameUpdator(IHostingEnvironment env) 11 | { 12 | _env = env; 13 | } 14 | 15 | public string Concat(string str) 16 | { 17 | if (!str.EndsWith(_env.EnvironmentName)) 18 | { 19 | return string.Concat(str, ".", _env.EnvironmentName); 20 | } 21 | return str; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/EventBus.Core/Infrastructure/DefaultMessageDecoder.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using Newtonsoft.Json.Linq; 3 | using EventBus.Core; 4 | using EventBus.Core.Internal.Model; 5 | using FeiniuBus; 6 | using System; 7 | 8 | namespace EventBus.Subscribe.Infrastructure 9 | { 10 | public class DefaultMessageDecoder : IMessageDecoder 11 | { 12 | public ReceivedMessage Decode(MessageContext context) 13 | { 14 | var json = Encoding.UTF8.GetString(context.Content); 15 | var jobject = JObject.Parse(json); 16 | 17 | try 18 | { 19 | var returnValue = new ReceivedMessage 20 | { 21 | Id = BitConverter.ToInt64(Guid.NewGuid().ToByteArray(), 0), 22 | MessageId = jobject["MetaData"]["MessageID"].Value(), 23 | TransactId = jobject["MetaData"]["TransactID"].Value(), 24 | Group = context.Queue, 25 | RouteKey = context.Topic, 26 | MetaData = jobject["MetaData"].ToString(), 27 | Content = jobject["Content"].ToString(), 28 | Retries = 0, 29 | ReceivedTime = DateTime.Now, 30 | ExpiredTime = DateTime.Now.AddDays(1), 31 | State = Core.State.MessageState.Processing 32 | }; 33 | return returnValue; 34 | } 35 | catch(Exception e) 36 | { 37 | throw e; 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/EventBus.Core/Infrastructure/Extenssion.Type.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Text; 6 | 7 | namespace EventBus.Core.Infrastructure 8 | { 9 | public static class TypeExtenssion 10 | { 11 | public static bool IsChildClassOfInterface(this Type type, Type interfaceType) 12 | { 13 | if (!interfaceType.GetTypeInfo().IsInterface) 14 | throw new InvalidOperationException($"{nameof(interfaceType)} must be an interface"); 15 | 16 | return type.GetInterfaces().Any(x => x.GetTypeInfo().IsGenericType && x.GetGenericTypeDefinition() == interfaceType); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/EventBus.Core/Infrastructure/FailureHandleOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Collections.Generic; 4 | using System.Collections.ObjectModel; 5 | 6 | namespace EventBus.Core.Infrastructure 7 | { 8 | public class FailureHandleOptions 9 | { 10 | public IReadOnlyCollection DeadLetterInfos { get; private set; } 11 | 12 | private IList _subscribeInfos; 13 | 14 | public FailureHandleOptions() 15 | { 16 | _subscribeInfos = new List(); 17 | } 18 | 19 | public void RegisterFailureCallback(string topic, Type callbackType) 20 | { 21 | foreach (var info in _subscribeInfos) 22 | { 23 | if (info.Topic == topic) 24 | { 25 | throw new InvalidOperationException($"Duplicate failure callback {topic}"); 26 | } 27 | } 28 | 29 | _subscribeInfos.Add(new SubscribeInfo 30 | { 31 | Topic = topic, 32 | CallbackType = callbackType 33 | }); 34 | } 35 | 36 | public void RegisterFailureCallback(string topic, string selfExchange, Type callbackType) 37 | { 38 | _subscribeInfos.Add(new SubscribeInfo 39 | { 40 | Group = DeadLetterQueueName(selfExchange), 41 | Topic = topic, 42 | CallbackType = callbackType 43 | }); 44 | } 45 | 46 | public void BuildWithDefaultSelfExchangeName(string selfExchangeName, string deadLetterExchange) 47 | { 48 | foreach(var info in _subscribeInfos) 49 | { 50 | if (info.Group == null) info.Group = DeadLetterQueueName(selfExchangeName); 51 | info.Exchange = deadLetterExchange; 52 | 53 | if (_subscribeInfos.Count(x => x.Group == info.Group && x.Topic == info.Topic && x.Exchange == info.Topic) > 1) 54 | { 55 | throw new InvalidOperationException($"Dulicate {info.Exchange} {info.Topic} {info.Group}"); 56 | } 57 | } 58 | 59 | DeadLetterInfos = new ReadOnlyCollection(_subscribeInfos); 60 | } 61 | 62 | private string DeadLetterQueueName(string selfExchange) => "deadletter." + selfExchange; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/EventBus.Core/Infrastructure/IBootstrapper.Default.cs: -------------------------------------------------------------------------------- 1 | using EventBus.Core; 2 | using EventBus.Core.Extensions; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Threading.Tasks; 7 | 8 | namespace EventBus.Subscribe.Infrastructure 9 | { 10 | public class DefaultBootstrapper : IBootstrapper 11 | { 12 | private readonly IServiceProvider _serviceProvider; 13 | 14 | private readonly IList _disposables; 15 | 16 | public DefaultBootstrapper(IServiceProvider serviceProvider) 17 | { 18 | _serviceProvider = serviceProvider; 19 | _disposables = new List(); 20 | } 21 | 22 | public void Start() 23 | { 24 | var ensureCreate = EnsureCreateAsync(); 25 | ensureCreate.Synchronize(); 26 | 27 | StartConsumer(); 28 | } 29 | 30 | private void StartConsumer() 31 | { 32 | var consumers = _serviceProvider.GetServices(); 33 | foreach(var consumer in consumers) 34 | { 35 | _disposables.Add(consumer); 36 | consumer.Start(); 37 | } 38 | } 39 | 40 | private async Task EnsureCreateAsync() 41 | { 42 | var published = _serviceProvider.GetRequiredService(); 43 | var received = _serviceProvider.GetRequiredService(); 44 | await published.EnsureCreatedAsync(); 45 | await received.EnsureCreatedAsync(); 46 | } 47 | 48 | public void Dispose() 49 | { 50 | foreach(var disposable in _disposables) 51 | { 52 | disposable.Dispose(); 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/EventBus.Core/Infrastructure/IClient.Failure.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using RabbitMQ.Client; 3 | using RabbitMQ.Client.Events; 4 | using System; 5 | using System.Collections.Generic; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using EventBus.Core.Extensions; 8 | 9 | namespace EventBus.Core.Infrastructure 10 | { 11 | public class FailureClient : IFailureClient 12 | { 13 | private IConnection Connection; 14 | private IModel Channel; 15 | private readonly IConnectionFactoryAccessor _connectionFactoryAccessor; 16 | private readonly IServiceProvider _serviceProvider; 17 | private readonly ILogger _logger; 18 | private readonly RabbitOptions _rabbitOptions; 19 | private readonly string _group; 20 | private readonly string _exchange; 21 | 22 | public Action OnReceive { get; set; } 23 | 24 | public FailureClient( IServiceProvider serviceProvider 25 | , IConnectionFactoryAccessor connectionFactoryAccessor 26 | , RabbitOptions rabbitOptions 27 | , string group 28 | , string exchange) 29 | { 30 | _serviceProvider = serviceProvider; 31 | _connectionFactoryAccessor = connectionFactoryAccessor; 32 | _rabbitOptions = rabbitOptions; 33 | _group = group; 34 | _exchange = exchange; 35 | 36 | _logger = _serviceProvider.GetService>(); 37 | } 38 | 39 | private void EnsureChannel() 40 | { 41 | if (Channel != null) return; 42 | EnsureConnection(); 43 | Channel = Connection.CreateModel(); 44 | 45 | Channel.ExchangeDeclare(_exchange, "topic", true); 46 | Channel.ExchangeDeclare(_rabbitOptions.DefaultFinalDeadLetterExchange, "topic", true, false, new Dictionary 47 | { 48 | ["x-message-ttl"] = _rabbitOptions.QueueMessageExpires, 49 | }); 50 | 51 | var args = new Dictionary 52 | { 53 | ["x-message-ttl"] = _rabbitOptions.QueueMessageExpires, 54 | ["x-dead-letter-exchange"] = _rabbitOptions.DefaultFinalDeadLetterExchange, 55 | }; 56 | 57 | Channel.QueueDeclare( 58 | _group 59 | , true 60 | , false 61 | , false 62 | , args); 63 | } 64 | 65 | private void EnsureConnection() 66 | { 67 | if (Connection != null) return; 68 | 69 | var factory = _connectionFactoryAccessor.ConnectionFactory; 70 | Connection = factory.CreateConnection(); 71 | } 72 | 73 | public void Subscribe(string[] topics) 74 | { 75 | if (topics == null) throw new ArgumentNullException(nameof(topics)); 76 | 77 | _logger.LogInformation($"failure callback topics {topics.ToJson()}"); 78 | 79 | EnsureChannel(); 80 | 81 | foreach (var topic in topics) 82 | { 83 | Channel.QueueBind(_group, _exchange, topic); 84 | } 85 | } 86 | 87 | public void Listening() 88 | { 89 | EnsureChannel(); 90 | var consumer = new EventingBasicConsumer(Channel); 91 | consumer.Received += OnConsumerReceived; 92 | Channel.BasicConsume(_group, false, consumer); 93 | } 94 | 95 | public void Dispose() 96 | { 97 | Channel?.Dispose(); 98 | Connection?.Dispose(); 99 | Connection?.Close(); 100 | } 101 | 102 | public void OnConsumerReceived(object sender, BasicDeliverEventArgs e) 103 | { 104 | var context = new MessageContext 105 | { 106 | Exchange = _exchange, 107 | Topic = e.RoutingKey, 108 | Queue = _group, 109 | DeliveryTag = e.DeliveryTag, 110 | Channel = Channel, 111 | Content = e.Body, 112 | Args = e 113 | }; 114 | 115 | _logger.LogInformation($"failure context: {context.ToJson()}"); 116 | 117 | OnReceive?.Invoke(context); 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/EventBus.Core/Infrastructure/IConsumer.Failure.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Collections.Generic; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Microsoft.Extensions.Options; 6 | using EventBus.Core.Internal; 7 | using Microsoft.Extensions.Logging; 8 | using EventBus.Core.Extensions; 9 | 10 | namespace EventBus.Core.Infrastructure 11 | { 12 | public class FailureConsumer : IConsumer 13 | { 14 | private readonly IList _disposables; 15 | private readonly IServiceProvider _serviceProvider; 16 | private readonly FailureHandleOptions _subscribeOptions; 17 | private readonly ILogger _logger; 18 | 19 | public FailureConsumer(IServiceProvider serviceProvider 20 | , ILogger logger) 21 | { 22 | _disposables = new List(); 23 | _serviceProvider = serviceProvider; 24 | _subscribeOptions = _serviceProvider.GetService(); 25 | _logger = logger; 26 | } 27 | 28 | public void Start() 29 | { 30 | var clients = GetClients(); 31 | foreach (var client in clients) 32 | { 33 | client.Listening(); 34 | } 35 | } 36 | 37 | private IList GetClients() 38 | { 39 | if (_subscribeOptions == null) return new IClient[] { }; 40 | 41 | var subscribeInfos = _subscribeOptions.DeadLetterInfos; 42 | var groupedInfos = subscribeInfos.GroupBy(x => x.Group) 43 | .ToDictionary(x => x.Key, x => x.GroupBy(y => y.Exchange) 44 | .ToDictionary(y => y.Key, y => y.ToArray())); 45 | 46 | _logger.LogInformation($"deadletter subscribeinfos {subscribeInfos.ToJson()}"); 47 | 48 | var clients = new List(); 49 | var connectfactoryAccessor = _serviceProvider.GetRequiredService(); 50 | var rabbitOption = _serviceProvider.GetRequiredService>().Value; 51 | 52 | foreach (var queueGroupedItems in groupedInfos) 53 | { 54 | foreach (var exchangeGroupedItems in queueGroupedItems.Value) 55 | { 56 | var exchange = string.IsNullOrEmpty(exchangeGroupedItems.Key) ? rabbitOption.DefaultExchangeName : exchangeGroupedItems.Key; 57 | 58 | var client = new FailureClient( _serviceProvider 59 | , connectfactoryAccessor 60 | , rabbitOption 61 | , queueGroupedItems.Key 62 | , exchange); 63 | 64 | _disposables.Add(client); 65 | clients.Add(client); 66 | RegisterClient(client); 67 | 68 | var topics = exchangeGroupedItems.Value.Select(x => x.Topic).ToArray(); 69 | client.Subscribe(topics); 70 | } 71 | } 72 | 73 | return clients; 74 | } 75 | 76 | private void RegisterClient(IClient client) 77 | { 78 | client.OnReceive = (MessageContext context) => 79 | { 80 | bool result = false; 81 | try 82 | { 83 | var invoker = new FailureInvoker(_serviceProvider, context); 84 | result = invoker.InvokeAsync().ConfigureAwait(false).GetAwaiter().GetResult(); 85 | } 86 | catch (Exception ex) 87 | { 88 | _logger.LogError(110, ex, $"invoke deadletter fail: {context.ToJson()}"); 89 | } 90 | finally 91 | { 92 | if (result) 93 | { 94 | context.Ack(); 95 | _logger.LogInformation($"ack deadletter message {context.ToJson()}"); 96 | } 97 | else 98 | { 99 | context.Reject(); 100 | _logger.LogInformation($"reject deadletter message {context.ToJson()}"); 101 | } 102 | } 103 | }; 104 | } 105 | 106 | public void Dispose() 107 | { 108 | foreach (var disposable in _disposables) 109 | { 110 | disposable.Dispose(); 111 | } 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/EventBus.Core/Infrastructure/IFailureContextAccessor.Default.cs: -------------------------------------------------------------------------------- 1 | namespace EventBus.Core.Infrastructure 2 | { 3 | public class DefaultFailureContextAccessor : IFailureContextAccessor 4 | { 5 | public ConsumeFailureContext FailureContext { get ; set ; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/EventBus.Core/Infrastructure/IInvoker.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace EventBus.Core.Infrastructure 8 | { 9 | public interface IInvoker 10 | { 11 | Task InvokeAsync(); 12 | 13 | 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/EventBus.Core/Infrastructure/IMessage.Default.cs: -------------------------------------------------------------------------------- 1 | using EventBus.Core.State; 2 | using System; 3 | 4 | namespace EventBus.Core.Infrastructure 5 | { 6 | public class DefaultMessage : IMessage 7 | { 8 | public DefaultMessage(object content, MessageState state = MessageState.Processing) 9 | { 10 | MetaData = new MetaData(); 11 | Content = content; 12 | State = state; 13 | CreateTime = DateTime.Now; 14 | } 15 | 16 | public IMetaData MetaData { get; } 17 | 18 | public object Content { get; } 19 | 20 | public DateTime CreateTime { get; } 21 | 22 | public MessageState State { get; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/EventBus.Core/Infrastructure/IMessage.cs: -------------------------------------------------------------------------------- 1 | using EventBus.Core.State; 2 | using System; 3 | 4 | namespace EventBus.Core.Infrastructure 5 | { 6 | public interface IMessage 7 | { 8 | IMetaData MetaData { get; } 9 | object Content { get; } 10 | DateTime CreateTime { get; } 11 | MessageState State { get; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/EventBus.Core/Infrastructure/IMessageSerializer.Default.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Text; 4 | 5 | namespace EventBus.Core.Infrastructure 6 | { 7 | public class DefaultMessageSerializer : IMessageSerializer 8 | { 9 | public T Deserialize(byte[] message) 10 | { 11 | var jsonStr = Encoding.UTF8.GetString(message); 12 | return FeiniuBus.Util.FeiniuBusJsonConvert.DeserializeObject(jsonStr); 13 | } 14 | 15 | public byte[] Serialize(object message) 16 | { 17 | string jsonStr = string.Empty; 18 | if (message is string) 19 | { 20 | jsonStr = (string)message; 21 | } 22 | else 23 | { 24 | jsonStr = FeiniuBus.Util.FeiniuBusJsonConvert.SerializeObject(message); 25 | } 26 | return Encoding.UTF8.GetBytes(jsonStr); 27 | } 28 | 29 | private bool IsSimpleType(Type t) 30 | { 31 | return t == typeof(string) 32 | || t == typeof(Int16) 33 | || t == typeof(Int32) 34 | || t == typeof(Int64) 35 | || t == typeof(IntPtr) 36 | || t == typeof(UInt16) 37 | || t == typeof(UInt32) 38 | || t == typeof(UInt64) 39 | || t == typeof(UIntPtr) 40 | || t == typeof(Single) 41 | || t == typeof(Double) 42 | || t == typeof(Decimal) 43 | || t == typeof(Byte) 44 | || t == typeof(DateTime) 45 | || t == typeof(DateTimeOffset) 46 | || t == typeof(TimeSpan); 47 | } 48 | 49 | private SimpleTypes GetSimpleType(Type t) 50 | { 51 | if (t == typeof(Int16)) return SimpleTypes.Int16; 52 | else if (t == typeof(Int32)) return SimpleTypes.Int32; 53 | else if (t == typeof(Int64)) return SimpleTypes.Int64; 54 | else if (t == typeof(IntPtr)) return SimpleTypes.IntPtr; 55 | else if (t == typeof(UInt16)) return SimpleTypes.UInt16; 56 | else if (t == typeof(UInt32)) return SimpleTypes.UInt32; 57 | else if (t == typeof(UInt64)) return SimpleTypes.UInt64; 58 | else if (t == typeof(UIntPtr)) return SimpleTypes.UIntPtr; 59 | else if (t == typeof(Single)) return SimpleTypes.Single; 60 | else if (t == typeof(Double)) return SimpleTypes.Double; 61 | else if (t == typeof(Decimal)) return SimpleTypes.Decimal; 62 | else if (t == typeof(Byte)) return SimpleTypes.Byte; 63 | else if (t == typeof(DateTime)) return SimpleTypes.DateTime; 64 | else if (t == typeof(DateTimeOffset)) return SimpleTypes.DateTimeOffset; 65 | else if (t == typeof(TimeSpan)) return SimpleTypes.TimeSpan; 66 | else return SimpleTypes.String; 67 | } 68 | 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/EventBus.Core/Infrastructure/IMetaData.Default.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace EventBus.Core.Infrastructure 5 | { 6 | public class MetaData : IMetaData 7 | { 8 | private readonly IDictionary _source; 9 | 10 | public MetaData() 11 | { 12 | _source = new Dictionary(); 13 | } 14 | public string this[string name] 15 | { 16 | get 17 | { 18 | return Get(name); 19 | } 20 | set 21 | { 22 | Set(name, value); 23 | } 24 | } 25 | 26 | public void Set(string name, string value) 27 | { 28 | _source[name] = value; 29 | } 30 | 31 | public string Get(string name) 32 | { 33 | if (_source.TryGetValue(name, out string value)) return value; 34 | else throw new IndexOutOfRangeException($"MetaData do net have element named '{name}'."); 35 | } 36 | 37 | public void Remove(string name) 38 | { 39 | _source.Remove(name); 40 | } 41 | 42 | public void Contact(IMetaData metaData) 43 | { 44 | foreach(var change in metaData.GetDictionary()) 45 | { 46 | if (_source.ContainsKey(change.Key)) 47 | { 48 | var newKey = GetNewKey(change.Key); 49 | _source.Add(newKey, change.Value); 50 | } 51 | _source.Add(change); 52 | } 53 | } 54 | 55 | public IEnumerator> GetEnumerator() => _source.GetEnumerator(); 56 | 57 | private string GetNewKey(string key) 58 | { 59 | int index = 1; 60 | var newKey = $"{key}_{index}"; 61 | while (_source.ContainsKey(newKey)) 62 | { 63 | index = index + 1; 64 | newKey = $"{key}_{index}"; 65 | } 66 | return newKey; 67 | } 68 | 69 | public IDictionary GetDictionary() => _source; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/EventBus.Core/Infrastructure/IMetaData.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | 4 | namespace EventBus.Core.Infrastructure 5 | { 6 | public interface IMetaData 7 | { 8 | string this[string name] { get; set; } 9 | void Set(string name, string value); 10 | void Remove(string name); 11 | string Get(string name); 12 | void Contact(IMetaData metaData); 13 | IEnumerator> GetEnumerator(); 14 | IDictionary GetDictionary(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/EventBus.Core/Infrastructure/IPubMessageValidator.Default.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | using System.Reflection; 6 | 7 | namespace EventBus.Core.Infrastructure 8 | { 9 | public class DefaultPubMessageValidator : IPubMessageValidator 10 | { 11 | public void Validate(PubMessageValidateContext context) 12 | { 13 | if (context.MessageType.GetTypeInfo().GetCustomAttribute() == null) 14 | { 15 | context.Result.AddError("events must be marked with EventAttribute"); 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/EventBus.Core/Infrastructure/JSONSimpleType.cs: -------------------------------------------------------------------------------- 1 | namespace EventBus.Core.Infrastructure 2 | { 3 | public sealed class JSONSimpleType 4 | { 5 | public JSONSimpleType(string value, SimpleTypes simpleType) 6 | { 7 | Value = value; 8 | SimpleType = simpleType; 9 | } 10 | public string Value { get; set; } 11 | public SimpleTypes SimpleType { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/EventBus.Core/Infrastructure/MessageInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace EventBus.Core.Infrastructure 4 | { 5 | public class MessageInfo 6 | { 7 | public Type Type { get; set; } 8 | 9 | public string Name { get; set; } 10 | 11 | public string Key { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/EventBus.Core/Infrastructure/MessageType.cs: -------------------------------------------------------------------------------- 1 | namespace EventBus.Core.Infrastructure 2 | { 3 | public enum MessageType : short 4 | { 5 | Published = 0, 6 | Subscribed = 1 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/EventBus.Core/Infrastructure/MySQL.Options.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace EventBus.Core.Infrastructure 4 | { 5 | public class EventBusMySQLOptions 6 | { 7 | public EventBusMySQLOptions() { } 8 | 9 | public EventBusMySQLOptions(Type dbContextType) 10 | { 11 | DbContextType = dbContextType; 12 | } 13 | 14 | public EventBusMySQLOptions(string connectionString) 15 | { 16 | ConnectionString = connectionString; 17 | } 18 | 19 | public Type DbContextType { get; set; } 20 | 21 | public string ConnectionString { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/EventBus.Core/Infrastructure/Rabbit.Options.cs: -------------------------------------------------------------------------------- 1 | using RabbitMQ.Client; 2 | 3 | namespace EventBus.Core.Infrastructure 4 | { 5 | public class RabbitOptions 6 | { 7 | public string UserName { get; set; } = ConnectionFactory.DefaultUser; 8 | 9 | public string Password { get; set; } = ConnectionFactory.DefaultPass; 10 | 11 | public string VirtualHost { get; set; } = ConnectionFactory.DefaultVHost; 12 | 13 | public string HostName { get; set; } 14 | 15 | public int Port { get; set; } = AmqpTcpEndpoint.UseDefaultPort; 16 | 17 | public string DefaultExchangeName { get; set; } = "default.exchange@feiniubus"; 18 | 19 | public int QueueMessageExpires { get; set; } = 864000000; 20 | 21 | public string DefaultDeadLetterExchange { get; set; } = "deadletter.exchange@feiniubus"; 22 | 23 | public string DefaultFinalDeadLetterExchange { get; set; } = "final.deadletter.exchange@feiniubus"; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/EventBus.Core/Infrastructure/SimpleTypes.cs: -------------------------------------------------------------------------------- 1 | namespace EventBus.Core.Infrastructure 2 | { 3 | public enum SimpleTypes 4 | { 5 | String, 6 | Int16, 7 | Int32, 8 | Int64, 9 | IntPtr, 10 | UInt16, 11 | UInt32, 12 | UInt64, 13 | UIntPtr, 14 | Single, 15 | Double, 16 | Decimal, 17 | Byte, 18 | DateTime, 19 | DateTimeOffset, 20 | TimeSpan, 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/EventBus.Core/Infrastructure/SubscribeInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace EventBus.Core.Infrastructure 4 | { 5 | public class SubscribeInfo 6 | { 7 | public string Exchange { get; set; } 8 | 9 | public string Topic { get; set; } 10 | 11 | public string Group { get; set; } 12 | 13 | public Type CallbackType { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/EventBus.Core/Internal/AnonymousObject.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CSharp.RuntimeBinder; 2 | using Newtonsoft.Json; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Runtime.CompilerServices; 8 | 9 | namespace EventBus.Core.Internal 10 | { 11 | public class AnonymousObject 12 | { 13 | private readonly object _instance; 14 | private readonly Type _type; 15 | 16 | public AnonymousObject(object instance) 17 | { 18 | _instance = instance; 19 | _type = instance.GetType(); 20 | } 21 | 22 | public T GetValue(string memberName) 23 | { 24 | var callSiteBinder = Microsoft.CSharp.RuntimeBinder.Binder.Convert(CSharpBinderFlags.None, typeof(T), _type); 25 | var typeDeclarer = CallSite>.Create(callSiteBinder); 26 | 27 | Func typeDeclarerTarget = typeDeclarer.Target; 28 | 29 | var memberFinder = CallSite>.Create(Microsoft.CSharp.RuntimeBinder.Binder.GetMember(CSharpBinderFlags.None, memberName, _type, new CSharpArgumentInfo[] 30 | { 31 | CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) 32 | })); 33 | 34 | return typeDeclarerTarget(typeDeclarer, memberFinder.Target(memberFinder, _instance)); 35 | } 36 | 37 | public object GetValue(Type t, string memberName) 38 | { 39 | var callSiteBinder = Microsoft.CSharp.RuntimeBinder.Binder.Convert(CSharpBinderFlags.None, t, _type); 40 | var typeDeclarer = CallSite>.Create(callSiteBinder); 41 | 42 | Func typeDeclarerTarget = typeDeclarer.Target; 43 | 44 | var memberFinder = CallSite>.Create(Microsoft.CSharp.RuntimeBinder.Binder.GetMember(CSharpBinderFlags.None, memberName, _type, new CSharpArgumentInfo[] 45 | { 46 | CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) 47 | })); 48 | 49 | return typeDeclarerTarget(typeDeclarer, memberFinder.Target(memberFinder, _instance)); 50 | } 51 | 52 | public IEnumerable GetProperties() 53 | { 54 | var memberinfos = _type.GetProperties().Select(member => new AnonymousPropertyInfo 55 | { 56 | MemberName = member.Name, 57 | DeclaringType = member.PropertyType, 58 | MemberType = member.MemberType 59 | }); 60 | return memberinfos; 61 | } 62 | 63 | public string GetJsonDefinition() 64 | { 65 | var defines = GetProperties().Select(x => new KeyValuePair(x.MemberName, x.DeclaringType.Name)); 66 | return FeiniuBus.Util.FeiniuBusJsonConvert.SerializeObject(defines); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/EventBus.Core/Internal/AnonymousPropertyInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | 4 | namespace EventBus.Core.Internal 5 | { 6 | public class AnonymousPropertyInfo 7 | { 8 | public string MemberName { get; internal set; } 9 | public Type DeclaringType { get; internal set; } 10 | public MemberTypes MemberType { get; internal set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/EventBus.Core/Internal/FailureInvoker.cs: -------------------------------------------------------------------------------- 1 | using EventBus.Core.Infrastructure; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Newtonsoft.Json.Linq; 4 | using System; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace EventBus.Core.Internal 11 | { 12 | public class FailureInvoker : IInvoker 13 | { 14 | private readonly IServiceScope _serviceScope; 15 | private readonly IServiceProvider _serviceProvider; 16 | private readonly FailureHandleOptions _failureOptions; 17 | 18 | public MessageContext Context { get; } 19 | 20 | public FailureInvoker(IServiceProvider serviceProvider 21 | , MessageContext context) 22 | { 23 | _serviceScope = serviceProvider.CreateScope(); 24 | _serviceProvider = _serviceScope.ServiceProvider; 25 | _failureOptions = _serviceProvider.GetRequiredService(); 26 | Context = context; 27 | 28 | SetUpFailureContext(); 29 | } 30 | 31 | public Task InvokeAsync() 32 | { 33 | var subscribeInfo = GetSubscribe(); 34 | var handler = GetHandler(subscribeInfo); 35 | var message = GetMessage(subscribeInfo); 36 | return Call(handler, message); 37 | } 38 | 39 | private Task Call(object handler, object message) 40 | { 41 | var method = handler.GetType().GetTypeInfo().GetMethod(nameof(IFailureHandler.HandleAsync)); 42 | var task = method.Invoke(handler, new[] { message }) as Task; 43 | return task; 44 | } 45 | 46 | private object GetHandler(SubscribeInfo subscribeInfo) 47 | { 48 | return _serviceProvider.GetService(subscribeInfo.CallbackType); 49 | } 50 | 51 | private SubscribeInfo GetSubscribe() 52 | { 53 | return _failureOptions.DeadLetterInfos.FirstOrDefault(x => x.Exchange == Context.Exchange 54 | && x.Topic == Context.Topic 55 | && x.Group == Context.Queue); 56 | } 57 | 58 | private string GetMessage(SubscribeInfo subscribeInfo) 59 | { 60 | var receivedMsg = _serviceProvider.GetRequiredService().Decode(Context); 61 | return receivedMsg.Content; 62 | } 63 | 64 | private void SetUpFailureContext() 65 | { 66 | var accessor = _serviceProvider.GetRequiredService(); 67 | var xDeath = Context.Args.BasicProperties.Headers["x-death"]; 68 | var json = FeiniuBus.Util.FeiniuBusJsonConvert.SerializeObject(xDeath); 69 | var jarray = JArray.Parse(json); 70 | var jstr = (jarray[0]["queue"]).ToObject(); 71 | accessor.FailureContext = new ConsumeFailureContext 72 | { 73 | FailureGroup = jstr 74 | }; 75 | } 76 | 77 | public void Dispose() 78 | { 79 | _serviceScope.Dispose(); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/EventBus.Core/Internal/Model/ReceivedMessage.cs: -------------------------------------------------------------------------------- 1 | using EventBus.Core.State; 2 | using System; 3 | 4 | namespace EventBus.Core.Internal.Model 5 | { 6 | public class ReceivedMessage 7 | { 8 | public long Id { get; set; } 9 | public long MessageId { get; set; } 10 | public long TransactId { get; set; } 11 | public string Group { get; set; } 12 | public string RouteKey { get; set; } 13 | public string MetaData { get; set; } 14 | public string Content { get; set; } 15 | public int Retries { get; set; } 16 | public DateTime ReceivedTime { get; set; } 17 | public DateTime ExpiredTime { get; set; } 18 | public MessageState State { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/EventBus.Core/Internal/SubscribeCallbackHandlerDescriptor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace EventBus.Core.Internal 4 | { 5 | internal class SubscribeCallbackHandlerDescriptor 6 | { 7 | public SubscribeCallbackHandlerDescriptor(Type type, string key, string name = null) 8 | { 9 | Type = type; 10 | Name = name; 11 | Key = key; 12 | } 13 | 14 | internal Type Type { get; } 15 | internal string Name { get; } 16 | internal string Key { get; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/EventBus.Core/MessageContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | using RabbitMQ.Client; 4 | using RabbitMQ.Client.Events; 5 | 6 | namespace EventBus.Core 7 | { 8 | public class MessageContext 9 | { 10 | public string Exchange { get; set; } 11 | 12 | public string Topic { get; set; } 13 | 14 | public string Queue { get; set; } 15 | 16 | public ulong DeliveryTag { get; set; } 17 | 18 | [JsonIgnore] 19 | public IModel Channel { get; set; } 20 | 21 | public byte[] Content { get; set; } 22 | 23 | [JsonIgnore] 24 | public BasicDeliverEventArgs Args { get; set; } 25 | 26 | public void Ack() 27 | { 28 | Channel.BasicAck(DeliveryTag, false); 29 | } 30 | 31 | public void Reject(bool requeue = false) 32 | { 33 | Channel.BasicReject(DeliveryTag, requeue); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/EventBus.Core/PubMessageValidateContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace EventBus.Core 4 | { 5 | public class PubMessageValidateContext 6 | { 7 | public PubMessageValidateContext() 8 | { 9 | Result = new PubMessageValidateResult(); 10 | } 11 | public Type MessageType { get; set; } 12 | 13 | public object Message { get; set; } 14 | 15 | public PubMessageValidateResult Result { get; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/EventBus.Core/PubMessageValidateResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace EventBus.Core 7 | { 8 | public class PubMessageValidateResult 9 | { 10 | public IList Errors { get; } 11 | 12 | public PubMessageValidateResult() 13 | { 14 | Errors = new List(); 15 | } 16 | 17 | public void AddError(string error) => Errors.Add(error); 18 | 19 | public bool Failed => Errors.Any(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/EventBus.Core/State/IStateChangeHandler.cs: -------------------------------------------------------------------------------- 1 | using EventBus.Core.Infrastructure; 2 | using System.Threading.Tasks; 3 | 4 | namespace EventBus.Core.State 5 | { 6 | public interface IStateChangeHandler 7 | { 8 | bool CanHandle(MessageType messageType, string content, IMetaData metaData, StateChangedArgs args); 9 | 10 | Task HandleAsync(MessageType messageType, string content, IMetaData metaData, StateChangedArgs args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/EventBus.Core/State/MessageState.cs: -------------------------------------------------------------------------------- 1 | namespace EventBus.Core.State 2 | { 3 | public enum MessageState : short 4 | { 5 | Processing = 1, 6 | Succeeded = 2, 7 | Failed = 3 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/EventBus.Core/State/StateChangedArgs.cs: -------------------------------------------------------------------------------- 1 | namespace EventBus.Core.State 2 | { 3 | public sealed class StateChangedArgs 4 | { 5 | public StateChangedArgs(MessageState oldState, MessageState newState) 6 | { 7 | OldState = oldState; 8 | NewState = newState; 9 | } 10 | 11 | public MessageState OldState { get; } 12 | public MessageState NewState { get; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/EventBus.Core/SubscribeAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace EventBus.Core 4 | { 5 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] 6 | public class SubscribeAttribute : Attribute 7 | { 8 | public string Name { get; set; } = null; 9 | public string Key { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/EventBus.Core/Util/TopicMatch.cs: -------------------------------------------------------------------------------- 1 | namespace EventBus.Core.Util 2 | { 3 | public static class TopicMatch 4 | { 5 | // 判断两个topic名字是否匹配 6 | public static bool IsTopicMatch(string topic, string partern) 7 | { 8 | var segments = topic.Split('.'); 9 | var parternSegments = partern.Split('.'); 10 | 11 | var sLen = segments.Length; 12 | var pLen = parternSegments.Length; 13 | 14 | var dp = new bool[sLen + 1][]; 15 | for (var i = 0; i < sLen + 1; ++i) 16 | { 17 | dp[i] = new bool[pLen + 1]; 18 | } 19 | 20 | dp[0][0] = true; 21 | for (var i = 1; i < sLen + 1; ++i) 22 | { 23 | dp[i][0] = false; 24 | } 25 | 26 | for (var j = 1; j < pLen + 1; j++) 27 | { 28 | if (parternSegments[j - 1] == "#") 29 | { 30 | dp[0][j] = true; 31 | } 32 | else 33 | { 34 | break; 35 | } 36 | } 37 | 38 | for (var i = 1; i < sLen + 1; ++i) 39 | { 40 | for (var j = 1; j < pLen + 1; ++j) 41 | { 42 | if (parternSegments[j - 1] != "#") 43 | { 44 | dp[i][j] = dp[i - 1][j - 1] && 45 | (segments[i - 1] == parternSegments[j - 1] || parternSegments[j - 1] == "*"); 46 | } 47 | else 48 | { 49 | dp[i][j] = dp[i - 1][j] || dp[i][j - 1]; 50 | } 51 | } 52 | } 53 | 54 | return dp[sLen][pLen]; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/EventBus.Publish/EventBus.MySQL.Extension.cs: -------------------------------------------------------------------------------- 1 | using EventBus.Core; 2 | using EventBus.Core.Infrastructure; 3 | using EventBus.Publish.Infrastructure; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using System; 6 | 7 | namespace EventBus.Publish 8 | { 9 | public class EventBusMySQLExtension : IEventBusOptionsExtension 10 | { 11 | private readonly Action _configure; 12 | 13 | public EventBusMySQLExtension(Action configure) 14 | { 15 | _configure = configure; 16 | } 17 | 18 | public void AddServices(IServiceCollection serviceCollection) 19 | { 20 | var options = new EventBusMySQLOptions(); 21 | _configure(serviceCollection.BuildServiceProvider(), options); 22 | 23 | serviceCollection.AddSingleton(options); 24 | serviceCollection.AddScoped(); 25 | serviceCollection.AddScoped(); 26 | serviceCollection.AddScoped(); 27 | serviceCollection.AddScoped(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/EventBus.Publish/EventBus.Publish.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard1.6 5 | 1.0.6 6 | https://github.com/FeiniuBus/EventBus 7 | false 8 | https://s3.cn-north-1.amazonaws.com.cn/nuget-icons/icon175x175.jpeg 9 | https://github.com/FeiniuBus/EventBus.git 10 | https://raw.githubusercontent.com/FeiniuBus/EventBus/master/LICENSE 11 | Feiniubus EventBus libary 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/EventBus.Publish/EventBusExtensions.cs: -------------------------------------------------------------------------------- 1 | using EventBus.Core; 2 | using EventBus.Core.Infrastructure; 3 | using EventBus.Publish; 4 | using Microsoft.EntityFrameworkCore; 5 | using System; 6 | 7 | namespace Microsoft.Extensions.DependencyInjection 8 | { 9 | public static class EventBusExtensions 10 | { 11 | public static EventBusOptions UseEntityframework(this EventBusOptions eventBusOptions) 12 | where TDbContext : DbContext 13 | { 14 | var extension = new EventBusMySQLExtension((provider, options) => 15 | { 16 | options.DbContextType = typeof(TDbContext); 17 | var dbContext = provider.GetRequiredService(); 18 | var conn = dbContext.Database.GetDbConnection(); 19 | var connectionString = conn.ConnectionString; 20 | if(conn.State != System.Data.ConnectionState.Closed) 21 | { 22 | conn.Close(); 23 | } 24 | options.ConnectionString = connectionString; 25 | }); 26 | eventBusOptions.RegisterExtension(extension); 27 | return eventBusOptions; 28 | } 29 | 30 | public static EventBusOptions UseMySQL(this EventBusOptions eventBusOptions, string connectionString) 31 | { 32 | var extension = new EventBusMySQLExtension((provider, options) => options.ConnectionString = connectionString); 33 | eventBusOptions.RegisterExtension(extension); 34 | return eventBusOptions; 35 | } 36 | 37 | public static EventBusOptions UseRabbitMQ(this EventBusOptions eventBusOptions, Action configure) 38 | { 39 | var extension = new RabbitExtension(configure); 40 | eventBusOptions.RegisterExtension(extension); 41 | return eventBusOptions; 42 | } 43 | 44 | public static EventBusOptions UseFailureHandle(this EventBusOptions eventBusOptions, Action configure) 45 | { 46 | var extension = new FailureExtenssion(configure); 47 | eventBusOptions.RegisterExtension(extension); 48 | return eventBusOptions; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/EventBus.Publish/EventPublisher.cs: -------------------------------------------------------------------------------- 1 | using EventBus.Core; 2 | using EventBus.Core.Extensions; 3 | using EventBus.Core.Infrastructure; 4 | using EventBus.Core.State; 5 | using Microsoft.EntityFrameworkCore; 6 | using Microsoft.EntityFrameworkCore.Storage; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using Microsoft.Extensions.Logging; 9 | using Newtonsoft.Json; 10 | using System; 11 | using System.Data; 12 | using System.Text; 13 | using System.Threading.Tasks; 14 | 15 | namespace EventBus.Publish 16 | { 17 | public class EventPublisher : IEventPublisher 18 | { 19 | private readonly IServiceScope _serviceScope; 20 | private readonly IServiceProvider _serviceProvider; 21 | private readonly IIdentityGenerator _identityGenerator; 22 | private readonly IPublishedEventPersistenter _publishedEventPersistenter; 23 | private readonly IMessageQueueTransaction _messageQueueTransaction; 24 | private readonly IMessageSerializer _messageSerializer; 25 | private readonly ILogger _logger; 26 | public EventPublisher(IServiceProvider serviceProvider) 27 | { 28 | _serviceProvider = serviceProvider; 29 | _identityGenerator = serviceProvider.GetRequiredService< IIdentityGenerator>(); 30 | _publishedEventPersistenter = serviceProvider.GetRequiredService< IPublishedEventPersistenter>(); 31 | _messageQueueTransaction = serviceProvider.GetRequiredService< IMessageQueueTransaction>(); 32 | _messageSerializer = serviceProvider.GetRequiredService< IMessageSerializer>(); 33 | _logger = serviceProvider.GetRequiredService().CreateLogger(); 34 | TransactID = _identityGenerator.NextIdentity(); 35 | } 36 | 37 | private EventPublisher(IServiceScope serviceScope) : this(serviceScope.ServiceProvider) 38 | { 39 | _serviceScope = serviceScope; 40 | } 41 | 42 | public long TransactID { get; } 43 | 44 | public async Task ConfirmAsync() 45 | { 46 | await _messageQueueTransaction.CommitAsync(); 47 | } 48 | 49 | public IEventPublisher CreateScope() 50 | { 51 | return new EventPublisher(_serviceProvider.CreateScope()); 52 | } 53 | 54 | public void Dispose() 55 | { 56 | _serviceScope?.Dispose(); 57 | _messageQueueTransaction?.Dispose(); 58 | } 59 | 60 | public async Task PrepareAsync(EventDescriptor descriptor) 61 | { 62 | var options = _serviceProvider.GetRequiredService(); 63 | if (options.DbContextType == null) throw new ArgumentException($"Field `DbContextType` should been configured before using PrepareAsync(EventDescriptor)."); 64 | var dbContext = _serviceProvider.GetRequiredService(options.DbContextType) as DbContext; 65 | var dbContextTransaction = dbContext.Database.CurrentTransaction; 66 | if (dbContextTransaction == null) throw new ArgumentException("Could not access a transaction before it has began."); 67 | var dbConnection = dbContext.Database.GetDbConnection(); 68 | var dbTransaction = dbContextTransaction.GetDbTransaction(); 69 | await PrepareAsync(descriptor, dbConnection, dbTransaction); 70 | } 71 | 72 | public async Task PrepareAsync(EventDescriptor descriptor, IDbConnection dbConnection, IDbTransaction dbTransaction) 73 | { 74 | var message = new Internal.Model.Message 75 | { 76 | Id = _identityGenerator.NextIdentity(), 77 | MessageId = _identityGenerator.NextIdentity(), 78 | TransactId = TransactID, 79 | Content = descriptor.Message.GetTransferJson(), 80 | CreationDate = DateTime.Now, 81 | State = MessageState.Processing, 82 | Type = MessageType.Published, 83 | Exchange = descriptor.Exchange, 84 | RouteKey = descriptor.RouteKey 85 | }; 86 | var metaData = new MetaData(); 87 | metaData.Set("ContentType", descriptor.ContentType); 88 | metaData.Set("TransactID", message.TransactId.ToString()); 89 | metaData.Set("MessageID", message.MessageId.ToString()); 90 | metaData.Contact(descriptor.Message.MetaData); 91 | 92 | message.MetaData = metaData.ToJson(); 93 | 94 | await _publishedEventPersistenter.InsertAsync(message, dbConnection, dbTransaction); 95 | var jsonTransafer = FeiniuBus.Util.FeiniuBusJsonConvert.SerializeObject(new { MetaData = metaData.GetDictionary(), Content = descriptor.Message.Content }); 96 | await _messageQueueTransaction.PublishAsync(descriptor.Exchange, descriptor.RouteKey, Encoding.UTF8.GetBytes(jsonTransafer)); 97 | } 98 | 99 | public async Task RollbackAsync() 100 | { 101 | await _messageQueueTransaction.RollbackAsync(); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/EventBus.Publish/Infrastructure/IConnectionFactoryAccessor.Default.cs: -------------------------------------------------------------------------------- 1 | using EventBus.Core; 2 | using EventBus.Core.Infrastructure; 3 | using RabbitMQ.Client; 4 | 5 | namespace EventBus.Publish.Infrastructure 6 | { 7 | public class DefaultConnectionFactoryAccessor : IConnectionFactoryAccessor 8 | { 9 | RabbitOptions RabbitOptions; 10 | public IConnectionFactory ConnectionFactory { get; } 11 | 12 | public DefaultConnectionFactoryAccessor(RabbitOptions optionsAcssesor) 13 | { 14 | RabbitOptions = optionsAcssesor; 15 | ConnectionFactory = new ConnectionFactory() 16 | { 17 | UserName = RabbitOptions.UserName, 18 | Password = RabbitOptions.Password, 19 | VirtualHost = RabbitOptions.VirtualHost, 20 | HostName = RabbitOptions.HostName, 21 | Port = RabbitOptions.Port, 22 | }; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/EventBus.Publish/Internal/LoggerExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | 3 | namespace EventBus.Publish.Internal 4 | { 5 | public static class LoggerExtensions 6 | { 7 | public static void MessagePersitenterNotUsingTransaction(this ILogger logger, long transactionId) 8 | { 9 | logger.LogWarning($"Not using any transaction during message persistence.Trace Info:[TransactionId]{transactionId}."); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/EventBus.Publish/Internal/Model/Message.cs: -------------------------------------------------------------------------------- 1 | using EventBus.Core.Infrastructure; 2 | using EventBus.Core.State; 3 | using System; 4 | 5 | namespace EventBus.Publish.Internal.Model 6 | { 7 | public class Message 8 | { 9 | public long Id { get; set; } 10 | public long MessageId { get; set; } 11 | public long TransactId { get; set; } 12 | public string MetaData { get; set; } 13 | public string Content { get; set; } 14 | public string Exchange { get; set; } 15 | public string RouteKey { get; set; } 16 | public MessageType Type { get; set; } 17 | public MessageState State { get; set; } 18 | public DateTime CreationDate { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/EventBus.Publish/MessageQueueTransaction.cs: -------------------------------------------------------------------------------- 1 | using EventBus.Core; 2 | using EventBus.Core.Extensions; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Microsoft.Extensions.Logging; 5 | using RabbitMQ.Client; 6 | using System; 7 | using System.Linq; 8 | using System.Threading.Tasks; 9 | 10 | namespace EventBus.Publish 11 | { 12 | public class MessageQueueTransaction : IMessageQueueTransaction 13 | { 14 | private IConnection _connection; 15 | private IModel _channel; 16 | private readonly IConnectionFactoryAccessor _connectionFactoryAccessor; 17 | private readonly IServiceProvider _serviceProvider; 18 | private readonly IEnviromentNameConcator _exchangeNameUpdator; 19 | private readonly ILogger _logger; 20 | 21 | public MessageQueueTransaction(IConnectionFactoryAccessor connectionFactoryAccessor 22 | , ILogger logger 23 | , IServiceProvider serviceProvider) 24 | { 25 | _connectionFactoryAccessor = connectionFactoryAccessor; 26 | _serviceProvider = serviceProvider; 27 | _logger = logger; 28 | 29 | _exchangeNameUpdator = _serviceProvider.GetRequiredService(); 30 | } 31 | 32 | public Task CommitAsync() 33 | { 34 | _channel.TxCommit(); 35 | return Task.CompletedTask; 36 | } 37 | 38 | public void Dispose() 39 | { 40 | try 41 | { 42 | if (_channel != null) 43 | { 44 | _channel.Close(); 45 | _channel.Dispose(); 46 | } 47 | if (_connection != null) 48 | { 49 | _connection.Close(); 50 | _connection.Dispose(); 51 | } 52 | } 53 | catch 54 | { 55 | 56 | } 57 | } 58 | 59 | public async Task PublishAsync(string exchange, string routingKey, byte[] body) 60 | { 61 | OpenConnection(); 62 | exchange = _exchangeNameUpdator.Concat(exchange); 63 | try 64 | { 65 | var property = _channel.CreateBasicProperties(); 66 | _channel.ExchangeDeclare(exchange, "topic", true, false, null); 67 | _channel.BasicPublish(exchange, routingKey, null, body); 68 | _logger.LogInformation(new { Target = "Publish to RabbitMQ", Topic = routingKey, Exchange = exchange, ContentLength = body.Length }.ToJson()); 69 | } 70 | catch(Exception e) 71 | { 72 | _logger.LogError(new { Target = "Publish to RabbitMQ", Errors = e.GetMessages() }.ToJson()); 73 | try 74 | { 75 | await HandleFailureAsync(exchange, routingKey, body); 76 | } 77 | catch 78 | { 79 | 80 | } 81 | } 82 | } 83 | 84 | public Task RollbackAsync() 85 | { 86 | _channel.TxRollback(); 87 | return Task.CompletedTask; 88 | } 89 | 90 | private async Task HandleFailureAsync(string exchange, string topic, byte[] content) 91 | { 92 | OpenConnection(); 93 | var handlers = _serviceProvider.GetServices(); 94 | if (!handlers.Any()) 95 | { 96 | return; 97 | } 98 | 99 | foreach(var handler in handlers) 100 | { 101 | await handler.HandleAsync(exchange, topic, content); 102 | } 103 | } 104 | 105 | private void OpenConnection() 106 | { 107 | if (_connection != null && _connection.IsOpen) return; 108 | 109 | _connection = _connectionFactoryAccessor.ConnectionFactory.CreateConnection(); 110 | _connection.CallbackException += (sender, e) => 111 | { 112 | _logger.LogInformation(new { Source = nameof(MessageQueueTransaction), Target = "RabbitMQ Connection", Event = "CallbackException", Errors = e.Exception.GetMessages(), Detail = e.Detail }.ToJson()); 113 | }; 114 | _connection.ConnectionBlocked += (sender, e) => 115 | { 116 | _logger.LogInformation(new { Source = nameof(MessageQueueTransaction), Target = "RabbitMQ Connection", Event = "ConnectionBlocked", Reason = e.Reason }.ToJson()); 117 | }; 118 | _connection.ConnectionRecoveryError += (sender, e) => 119 | { 120 | _logger.LogInformation(new { Source = nameof(MessageQueueTransaction), Target = "RabbitMQ Connection", Event = "ConnectionRecoveryError", Errors = e.Exception.GetMessages() }.ToJson()); 121 | }; 122 | _connection.ConnectionShutdown += (sender, e) => 123 | { 124 | _logger.LogInformation(new { Source = nameof(MessageQueueTransaction), Target = "RabbitMQ Connection", Event = "ConnectionShutdown", Cause = e.Cause, ClassId = e.ClassId, ShutdownInitiator = e.Initiator, e.MethodId, e.ReplyCode, e.ReplyText }.ToJson()); 125 | }; 126 | _connection.ConnectionUnblocked += (sender, e) => 127 | { 128 | _logger.LogInformation(new { Source = nameof(MessageQueueTransaction), Target = "RabbitMQ Connection", Event = "ConnectionUnblocked" }.ToJson()); 129 | }; 130 | 131 | _connection.RecoverySucceeded += (sender, e) => 132 | { 133 | _logger.LogInformation(new { Source = nameof(MessageQueueTransaction), Target = "RabbitMQ Connection", Event = "RecoverySucceeded" }.ToJson()); 134 | }; 135 | 136 | _channel = _connection.CreateModel(); 137 | 138 | _channel.BasicAcks += (sender, e) => _logger.LogInformation(new { Source = nameof(MessageQueueTransaction), Target = "RabbitMQ Channel", Event = "BasicAcks", e.DeliveryTag, e.Multiple }.ToJson()); 139 | _channel.BasicNacks += (sender, e) => _logger.LogInformation(new { Source = nameof(MessageQueueTransaction), Target = "RabbitMQ Channel", Event = "BasicNacks", e.DeliveryTag, e.Multiple,e.Requeue }.ToJson()); 140 | _channel.BasicRecoverOk += (sender, e) => _logger.LogInformation(new { Source = nameof(MessageQueueTransaction), Target = "RabbitMQ Channel", Event = "BasicRecoverOk"}.ToJson()); 141 | _channel.BasicReturn += (sender, e) => _logger.LogInformation(new { Source = nameof(MessageQueueTransaction), Target = "RabbitMQ Channel", Event = "BasicReturn", ContentLength = e.Body?.Length, e.Exchange, e.ReplyCode,e.ReplyText,e.RoutingKey}.ToJson()); 142 | _channel.CallbackException +=(sender, e) => _logger.LogInformation(new { Source = nameof(MessageQueueTransaction), Target = "RabbitMQ Channel", Event = "CallbackException", e.Detail, Errors = e.Exception.GetMessages() }.ToJson()); 143 | _channel.FlowControl += (sender, e) => _logger.LogInformation(new { Source = nameof(MessageQueueTransaction), Target = "RabbitMQ Channel", Event = "FlowControl", e.Active }.ToJson()); 144 | 145 | _channel.TxSelect(); 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/EventBus.Publish/PublishedEventPersistenter.cs: -------------------------------------------------------------------------------- 1 | using Dapper; 2 | using EventBus.Core; 3 | using EventBus.Core.Infrastructure; 4 | using EventBus.Core.State; 5 | using Microsoft.EntityFrameworkCore; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Microsoft.Extensions.Logging; 8 | using Newtonsoft.Json; 9 | using System; 10 | using System.Data; 11 | using System.Linq; 12 | using System.Text; 13 | using System.Threading.Tasks; 14 | 15 | namespace EventBus.Publish 16 | { 17 | public class PublishedEventPersistenter : IPublishedEventPersistenter 18 | { 19 | private const string TableName = "eventbus.publishedmessages"; 20 | private readonly EventBusMySQLOptions _eventBusMySQLOptions; 21 | private readonly IIdentityGenerator _identityGenerator; 22 | private readonly ILogger _logger; 23 | private readonly IServiceProvider _serviceProvider; 24 | 25 | public PublishedEventPersistenter(IIdentityGenerator identityGenerator, EventBusMySQLOptions eventBusMySQLOptions, IServiceProvider serviceProvider, ILogger logger) 26 | { 27 | _identityGenerator = identityGenerator; 28 | _eventBusMySQLOptions = eventBusMySQLOptions; 29 | _serviceProvider = serviceProvider; 30 | _logger = logger; 31 | } 32 | 33 | public async Task ChangeStateAsync(long messageId, long transactId, MessageState messageState, IDbConnection dbConnection, IDbTransaction dbTransaction) 34 | { 35 | var result = await dbConnection.QueryFirstOrDefaultAsync(BuildChangeStateSql(), 36 | new 37 | { 38 | State = (short)messageState, 39 | MessageId = messageId, 40 | TransactId = transactId 41 | }, dbTransaction); 42 | 43 | if (result == null) throw new AffectedRowsCountUnExpectedException(1, 0); 44 | 45 | var args = new StateChangedArgs(result.State, messageState); 46 | var metaDataObj = result.GetMetaData(); 47 | var stateChangeHandlers = _serviceProvider.GetServices().Where(handler => handler.CanHandle(MessageType.Published, result.Content, metaDataObj, args)); 48 | if (stateChangeHandlers.Any()) 49 | { 50 | foreach(var handler in stateChangeHandlers) 51 | { 52 | try 53 | { 54 | await handler.HandleAsync(MessageType.Published, result.Content, metaDataObj, args); 55 | } 56 | catch(Exception ex) 57 | { 58 | _logger.LogError($"An exception was thrown during {handler.GetType().FullName} executing.{ex.Message}"); 59 | } 60 | } 61 | } 62 | } 63 | 64 | public async Task InsertAsync(object message, IDbConnection dbConnection, IDbTransaction dbTransaction) 65 | { 66 | var affectedRows = await dbConnection.ExecuteAsync(BuildInsertSql(), message, dbTransaction); 67 | 68 | if (affectedRows == 0) throw new AffectedRowsCountUnExpectedException(1, affectedRows); 69 | } 70 | 71 | private string BuildInsertSql() 72 | { 73 | StringBuilder sql = new StringBuilder(); 74 | sql.AppendLine($@"INSERT INTO `{TableName}` ("); 75 | sql.AppendLine(@"`Id`,"); 76 | sql.AppendLine(@"`MessageId`,"); 77 | sql.AppendLine(@"`TransactId`,"); 78 | sql.AppendLine(@"`MetaData`,"); 79 | sql.AppendLine(@"`Content`,"); 80 | sql.AppendLine(@"`Exchange`,"); 81 | sql.AppendLine(@"`RouteKey`,"); 82 | sql.AppendLine(@"`Type`,"); 83 | sql.AppendLine(@"`State`,"); 84 | sql.AppendLine(@"`CreationDate`"); 85 | sql.AppendLine(@")"); 86 | sql.AppendLine(@"VALUES"); 87 | sql.AppendLine(@"("); 88 | sql.AppendLine(@"@Id,"); 89 | sql.AppendLine(@"@MessageId,"); 90 | sql.AppendLine(@"@TransactId,"); 91 | sql.AppendLine(@"@MetaData,"); 92 | sql.AppendLine(@"@Content,"); 93 | sql.AppendLine(@"@Exchange,"); 94 | sql.AppendLine(@"@RouteKey,"); 95 | sql.AppendLine(@"@Type,"); 96 | sql.AppendLine(@"@State,"); 97 | sql.AppendLine(@"@CreationDate"); 98 | sql.AppendLine(@")"); 99 | return sql.ToString(); 100 | } 101 | 102 | private string BuildChangeStateSql() 103 | { 104 | var sql = new StringBuilder(); 105 | sql.AppendLine(@"SELECT"); 106 | sql.AppendLine(@"`State`,"); 107 | sql.AppendLine(@"`MetaData`,"); 108 | sql.AppendLine(@"`Content`"); 109 | sql.AppendLine(@"FROM"); 110 | sql.AppendLine($@"`{TableName}`"); 111 | sql.AppendLine(@"WHERE"); 112 | sql.AppendLine(@" (`MessageId` = @MessageId) AND (`TransactId` = @TransactId);"); 113 | 114 | sql.AppendLine($@"UPDATE `{TableName}`"); 115 | sql.AppendLine(@"SET "); 116 | sql.AppendLine(@"`State` = @State,"); 117 | sql.AppendLine(@"WHERE"); 118 | sql.AppendLine(@" (`MessageId` = @MessageId) AND (`TransactId` = @TransactId);"); 119 | return sql.ToString(); 120 | } 121 | 122 | public async Task EnsureCreatedAsync() 123 | { 124 | var sql = new StringBuilder(); 125 | sql.AppendLine($@"CREATE TABLE IF NOT EXISTS `{TableName}` ("); 126 | sql.AppendLine(@" `Id` bigint(20) NOT NULL, "); 127 | sql.AppendLine(@" `MessageId` bigint(20) NOT NULL,"); 128 | sql.AppendLine(@" `TransactId` bigint(20) NOT NULL, "); 129 | sql.AppendLine(@" `MetaData` longtext NOT NULL,"); 130 | sql.AppendLine(@" `Content` longtext NOT NULL, "); 131 | sql.AppendLine(@" `Exchange` varchar(255) DEFAULT NULL,"); 132 | sql.AppendLine(@" `RouteKey` varchar(255) DEFAULT NULL, "); 133 | sql.AppendLine(@" `Type` tinyint(4) NOT NULL,"); 134 | sql.AppendLine(@" `State` tinyint(4) NOT NULL, "); 135 | sql.AppendLine(@" `CreationDate` datetime NOT NULL,"); 136 | sql.AppendLine(@" PRIMARY KEY(`Id`)"); 137 | sql.AppendLine(@") ENGINE=InnoDB DEFAULT CHARSET=utf8;"); 138 | 139 | try 140 | { 141 | using (var connection = new MySql.Data.MySqlClient.MySqlConnection(_eventBusMySQLOptions.ConnectionString)) 142 | { 143 | await connection.ExecuteAsync(sql.ToString()); 144 | } 145 | } 146 | catch { } 147 | } 148 | } 149 | 150 | internal class ChangeStateMessage 151 | { 152 | public MessageState State { get; set; } 153 | public string MetaData { get; set; } 154 | public string Content { get; set; } 155 | public string ContentType { get; set; } 156 | 157 | public IMetaData GetMetaData() 158 | { 159 | if (string.IsNullOrEmpty(MetaData)) return null; 160 | return FeiniuBus.Util.FeiniuBusJsonConvert.DeserializeObject(MetaData); 161 | } 162 | 163 | public Type GetContentType() 164 | { 165 | return Type.GetType(ContentType); 166 | } 167 | } 168 | 169 | } 170 | -------------------------------------------------------------------------------- /src/EventBus.Publish/Rabbit.Extension.cs: -------------------------------------------------------------------------------- 1 | using EventBus.Core; 2 | using EventBus.Core.Infrastructure; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using System; 5 | 6 | namespace EventBus.Publish 7 | { 8 | public class RabbitExtension : IEventBusOptionsExtension 9 | { 10 | private readonly Action _configure; 11 | 12 | public RabbitExtension(Action configure) 13 | { 14 | _configure = configure; 15 | } 16 | 17 | public void AddServices(IServiceCollection serviceCollection) 18 | { 19 | var options = new RabbitOptions(); 20 | _configure(options); 21 | 22 | var envConcator = serviceCollection.BuildServiceProvider().GetRequiredService(); 23 | options.DefaultDeadLetterExchange = envConcator.Concat(options.DefaultDeadLetterExchange); 24 | options.DefaultFinalDeadLetterExchange = envConcator.Concat(options.DefaultFinalDeadLetterExchange); 25 | options.DefaultExchangeName = envConcator.Concat(options.DefaultExchangeName); 26 | 27 | serviceCollection.AddSingleton(options); 28 | serviceCollection.AddScoped(); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/EventBus.Subscribe/EventBus.Subscribe.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard1.6 5 | 1.0.6 6 | https://github.com/FeiniuBus/EventBus 7 | false 8 | https://s3.cn-north-1.amazonaws.com.cn/nuget-icons/icon175x175.jpeg 9 | https://github.com/FeiniuBus/EventBus.git 10 | https://raw.githubusercontent.com/FeiniuBus/EventBus/master/LICENSE 11 | Feiniubus EventBus libary 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/EventBus.Subscribe/Extenssion.ApplicationBuilder.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | namespace EventBus.Subscribe 6 | { 7 | public static class ApplicationBuilderExtenssion 8 | { 9 | 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/EventBus.Subscribe/Extenssion.ServiceCollection.cs: -------------------------------------------------------------------------------- 1 | using EventBus.Core; 2 | using EventBus.Subscribe.Infrastructure; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Microsoft.Extensions.DependencyInjection.Extensions; 5 | using Microsoft.Extensions.Options; 6 | using System; 7 | 8 | namespace EventBus.Subscribe 9 | { 10 | public static class ServiceCollectionExtenssion 11 | { 12 | public static IServiceCollection AddSub(this IServiceCollection services, Action setup) 13 | { 14 | if (setup == null) 15 | { 16 | throw new ArgumentNullException($"setup"); 17 | } 18 | var options = new SubscribeOptions(); 19 | setup(options); 20 | 21 | services.AddSingleton(options); 22 | 23 | services.AddScoped(); 24 | services.AddTransient(); 25 | services.AddScoped(); 26 | services.TryAddTransient(); 27 | 28 | services.ConfigurationSubscribeCallbacks(); 29 | 30 | return services; 31 | } 32 | 33 | public static IServiceCollection ConfigurationSubscribeCallbacks(this IServiceCollection services) 34 | { 35 | var provider = services.BuildServiceProvider(); 36 | var options = provider.GetRequiredService(); 37 | options.Build(provider); 38 | foreach(var info in options.SubscribeInfos) 39 | { 40 | services.AddScoped(info.CallbackType, info.CallbackType); 41 | } 42 | return services; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/EventBus.Subscribe/IMessageDeSerializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace EventBus.Subscribe 4 | { 5 | public interface IMessageDeSerializer 6 | { 7 | MessageT Deserialize(byte[] bytes); 8 | object Deserialize(byte[] bytes, Type type); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/EventBus.Subscribe/ISubscribeClient.cs: -------------------------------------------------------------------------------- 1 | using EventBus.Core; 2 | using System; 3 | 4 | namespace EventBus.Subscribe 5 | { 6 | public interface ISubscribeClient: IClient 7 | { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/EventBus.Subscribe/ISubscribeHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace EventBus.Subscribe 4 | { 5 | public interface ISubscribeHandler 6 | { 7 | Task HandleAsync(string message); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/EventBus.Subscribe/Infrastructure/IMessageDeSerializer.Default.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace EventBus.Subscribe.Infrastructure 4 | { 5 | public class DefaultMessageDeSerializer : IMessageDeSerializer 6 | { 7 | public MessageT Deserialize(byte[] bytes) 8 | { 9 | throw new NotImplementedException(); 10 | } 11 | 12 | public object Deserialize(byte[] bytes, Type type) 13 | { 14 | throw new NotImplementedException(); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/EventBus.Subscribe/Infrastructure/ISubscribeClient.Default.cs: -------------------------------------------------------------------------------- 1 | using EventBus.Core; 2 | using EventBus.Core.Infrastructure; 3 | using Microsoft.Extensions.Logging; 4 | using RabbitMQ.Client; 5 | using RabbitMQ.Client.Events; 6 | using System; 7 | using System.Collections.Generic; 8 | using Microsoft.Extensions.DependencyInjection; 9 | using EventBus.Core.Extensions; 10 | 11 | namespace EventBus.Subscribe.Infrastructure 12 | { 13 | public class DefaultSubscribeClient : ISubscribeClient 14 | { 15 | private IConnection Connection; 16 | private IModel Channel; 17 | private readonly IConnectionFactoryAccessor _connectionFactoryAccessor; 18 | private readonly IServiceProvider _serviceProvider; 19 | private readonly ILogger _logger; 20 | private readonly RabbitOptions _rabbitOptions; 21 | private readonly string _group; 22 | private readonly string _exchange; 23 | 24 | public Action OnReceive { get; set; } 25 | 26 | public DefaultSubscribeClient( IServiceProvider serviceProvider 27 | , IConnectionFactoryAccessor connectionFactoryAccessor 28 | , RabbitOptions rabbitOptions 29 | , string group 30 | , string exchange) 31 | { 32 | _serviceProvider = serviceProvider; 33 | _connectionFactoryAccessor = connectionFactoryAccessor; 34 | _rabbitOptions = rabbitOptions; 35 | _group = group; 36 | _exchange = exchange; 37 | 38 | _logger = _serviceProvider.GetService>(); 39 | } 40 | 41 | private void EnsureChannel() 42 | { 43 | if (Channel != null) return; 44 | EnsureConnection(); 45 | Channel = Connection.CreateModel(); 46 | 47 | Channel.BasicAcks += (sender, e) => _logger.LogInformation(new { Source = nameof(DefaultSubscribeClient), Target = "RabbitMQ Channel", Event = "BasicAcks", e.DeliveryTag, e.Multiple }.ToJson()); 48 | Channel.BasicNacks += (sender, e) => _logger.LogInformation(new { Source = nameof(DefaultSubscribeClient), Target = "RabbitMQ Channel", Event = "BasicNacks", e.DeliveryTag, e.Multiple, e.Requeue }.ToJson()); 49 | Channel.BasicRecoverOk += (sender, e) => 50 | { 51 | _logger.LogInformation(new 52 | { 53 | Source = nameof(DefaultSubscribeClient), 54 | Target = "RabbitMQ Channel", 55 | Event = "BasicRecoverOk" 56 | }.ToJson()); 57 | }; 58 | Channel.BasicReturn += (sender, e) => _logger.LogInformation(new { Source = nameof(DefaultSubscribeClient), Target = "RabbitMQ Channel", Event = "BasicReturn", ContentLength = e.Body?.Length, e.Exchange, e.ReplyCode, e.ReplyText, e.RoutingKey }.ToJson()); 59 | Channel.CallbackException += (sender, e) => 60 | { 61 | _logger.LogInformation(new 62 | { 63 | Source = nameof(DefaultSubscribeClient), 64 | Target = "RabbitMQ Channel", 65 | Event = "CallbackException", 66 | Errors = e.Exception.GetMessages() 67 | }.ToJson()); 68 | }; 69 | Channel.FlowControl += (sender, e) => _logger.LogInformation(new { Source = nameof(DefaultSubscribeClient), Target = "RabbitMQ Channel", Event = "FlowControl", e.Active }.ToJson()); 70 | Channel.ModelShutdown += (sender, e) => 71 | { 72 | _logger.LogInformation(new 73 | { 74 | Source = nameof(DefaultSubscribeClient), 75 | Target = "RabbitMQ Channel", 76 | Event = "ModelShutdown", 77 | }.ToJson()); 78 | }; 79 | 80 | Channel.ExchangeDeclare(_exchange, "topic", true); 81 | Channel.ExchangeDeclare(_rabbitOptions.DefaultDeadLetterExchange, "topic", true); 82 | 83 | var args = new Dictionary 84 | { 85 | ["x-message-ttl"] = _rabbitOptions.QueueMessageExpires, 86 | ["x-dead-letter-exchange"] = _rabbitOptions.DefaultDeadLetterExchange, 87 | }; 88 | 89 | Channel.QueueDeclare( 90 | _group 91 | , true 92 | , false 93 | , false 94 | , args); 95 | } 96 | 97 | private void EnsureConnection() 98 | { 99 | if (Connection != null) return; 100 | 101 | var factory = _connectionFactoryAccessor.ConnectionFactory; 102 | 103 | Connection = factory.CreateConnection(); 104 | 105 | Connection.CallbackException += (sender, e) => 106 | { 107 | _logger.LogError(new { Source = nameof(DefaultSubscribeClient), Target = "RabbitMQ Connection", Event = "CallbackException", Errors = e.Exception.GetMessages() }.ToJson()); 108 | }; 109 | Connection.ConnectionBlocked += (sender, e) => 110 | { 111 | _logger.LogError(new { Source = nameof(DefaultSubscribeClient), Target = "RabbitMQ Connection", Event = "ConnectionBlocked", Reason = e.Reason }.ToJson()); 112 | }; 113 | Connection.ConnectionRecoveryError += (sender, e) => 114 | { 115 | _logger.LogError(new { Source = nameof(DefaultSubscribeClient), Target = "RabbitMQ Connection", Event = "ConnectionRecoveryError", Errors = e.Exception.GetMessages() }.ToJson()); 116 | }; 117 | Connection.ConnectionShutdown += (sender, e) => 118 | { 119 | _logger.LogError(new { Source = nameof(DefaultSubscribeClient), Target = "RabbitMQ Connection", Event = "ConnectionShutdown", ClassId = e.ClassId, e.MethodId, e.ReplyCode, e.ReplyText }.ToJson()); 120 | }; 121 | Connection.ConnectionUnblocked += (sender, e) => 122 | { 123 | _logger.LogInformation(new { Source = nameof(DefaultSubscribeClient), Target = "RabbitMQ Connection", Event = "ConnectionUnblocked" }.ToJson()); 124 | }; 125 | 126 | Connection.RecoverySucceeded += (sender, e) => 127 | { 128 | _logger.LogInformation(new { Source = nameof(DefaultSubscribeClient), Target = "RabbitMQ Connection", Event = "RecoverySucceeded" }.ToJson()); 129 | }; 130 | } 131 | 132 | public void Subscribe(string[] topics) 133 | { 134 | if (topics == null) throw new ArgumentNullException(nameof(topics)); 135 | 136 | _logger.LogInformation($"subscribe topics {topics.ToJson()}"); 137 | 138 | EnsureChannel(); 139 | 140 | foreach (var topic in topics) 141 | { 142 | Channel.QueueBind(_group, _exchange, topic); 143 | } 144 | } 145 | 146 | public void Listening() 147 | { 148 | EnsureChannel(); 149 | var consumer = new EventingBasicConsumer(Channel); 150 | consumer.Received += OnConsumerReceived; 151 | consumer.ConsumerCancelled += (sender, e) => _logger.LogInformation(new { Source = nameof(DefaultSubscribeClient), Target = "RabbitMQ Consumer", Event = "ConsumerCancelled", e.ConsumerTag }.ToJson()); 152 | consumer.Received += (sender, e) => _logger.LogInformation(new { Source = nameof(DefaultSubscribeClient), Target = "RabbitMQ Consumer", Event = "Received", e.ConsumerTag, e.DeliveryTag, e.Exchange,e.Redelivered,e.RoutingKey, ContentLength = e.Body.Length }.ToJson()); 153 | consumer.Registered += (sender, e) => _logger.LogInformation(new { Source = nameof(DefaultSubscribeClient), Target = "RabbitMQ Consumer", Event = "Registered", e.ConsumerTag }.ToJson()); 154 | consumer.Shutdown +=(sender, e) => _logger.LogInformation(new { Source = nameof(DefaultSubscribeClient), Target = "RabbitMQ Consumer", Event = "Shutdown", e.ClassId, e.MethodId, e.ReplyCode, e.ReplyText }.ToJson()); 155 | consumer.Unregistered += (sender, e) => _logger.LogInformation(new { Source = nameof(DefaultSubscribeClient), Target = "RabbitMQ Consumer", Event = "Unregistered", e.ConsumerTag }.ToJson()); 156 | Channel.BasicConsume(_group, true, consumer); 157 | } 158 | 159 | public void Dispose() 160 | { 161 | Channel?.Dispose(); 162 | Connection?.Dispose(); 163 | Connection?.Close(); 164 | } 165 | 166 | public void OnConsumerReceived(object sender, BasicDeliverEventArgs e) 167 | { 168 | var context = new MessageContext 169 | { 170 | Exchange = _exchange, 171 | Topic = e.RoutingKey, 172 | Queue = _group, 173 | DeliveryTag = e.DeliveryTag, 174 | Channel = Channel, 175 | Content = e.Body 176 | }; 177 | 178 | _logger.LogInformation($"receive messages {context.ToJson()}"); 179 | 180 | OnReceive?.Invoke(context); 181 | } 182 | 183 | private void OpenConnection() 184 | { 185 | if (Connection != null && Connection.IsOpen) return; 186 | 187 | Connection = _connectionFactoryAccessor.ConnectionFactory.CreateConnection(); 188 | Connection.CallbackException += (sender, e) => 189 | { 190 | _logger.LogError(new { Source = nameof(DefaultSubscribeClient), Target = "RabbitMQ Connection", Event = "CallbackException", Errors = e.Exception.GetMessages() }.ToJson()); 191 | }; 192 | Connection.ConnectionBlocked += (sender, e) => 193 | { 194 | _logger.LogError(new { Source = nameof(DefaultSubscribeClient), Target = "RabbitMQ Connection", Event = "ConnectionBlocked", Reason = e.Reason }.ToJson()); 195 | }; 196 | Connection.ConnectionRecoveryError += (sender, e) => 197 | { 198 | _logger.LogError(new { Source = nameof(DefaultSubscribeClient), Target = "RabbitMQ Connection", Event = "ConnectionRecoveryError", Errors = e.Exception.GetMessages() }.ToJson()); 199 | }; 200 | Connection.ConnectionShutdown += (sender, e) => 201 | { 202 | _logger.LogError(new { Source = nameof(DefaultSubscribeClient), Target = "RabbitMQ Connection", Event = "ConnectionShutdown", ClassId = e.ClassId, e.MethodId, e.ReplyCode, e.ReplyText }.ToJson()); 203 | }; 204 | Connection.ConnectionUnblocked += (sender, e) => 205 | { 206 | _logger.LogInformation(new { Source = nameof(DefaultSubscribeClient), Target = "RabbitMQ Connection", Event = "ConnectionUnblocked" }.ToJson()); 207 | }; 208 | Connection.RecoverySucceeded += (sender, e) => 209 | { 210 | _logger.LogInformation(new { Source = nameof(DefaultSubscribeClient), Target = "RabbitMQ Connection", Event = "RecoverySucceeded" }.ToJson()); 211 | }; 212 | } 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /src/EventBus.Subscribe/Infrastructure/ISubscribeConsumer.Default.cs: -------------------------------------------------------------------------------- 1 | using EventBus.Core; 2 | using EventBus.Core.Extensions; 3 | using EventBus.Core.Infrastructure; 4 | using EventBus.Subscribe.Internal; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Microsoft.Extensions.Logging; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using System.Text; 11 | using System.Threading; 12 | using System.Threading.Tasks; 13 | using EventBus.Core.Internal.Model; 14 | 15 | namespace EventBus.Subscribe.Infrastructure 16 | { 17 | public class DefaultSubscribeConsumer : IConsumer 18 | { 19 | private readonly IList _disposables; 20 | private readonly IServiceProvider _serviceProvider; 21 | private readonly SubscribeOptions _subscribeOptions; 22 | private readonly IReceivedEventPersistenter _receivedEventPersistenter; 23 | private readonly IMessageDecoder _messageDecoder; 24 | private readonly ILogger _logger; 25 | 26 | public TaskFactory Factory { get; } 27 | 28 | public DefaultSubscribeConsumer(IServiceProvider serviceProvider 29 | , IReceivedEventPersistenter receivedEventPersistenter 30 | , IMessageDecoder messageDecoder 31 | , SubscribeOptions options 32 | , ILogger logger) 33 | { 34 | _disposables = new List(); 35 | _serviceProvider = serviceProvider; 36 | _subscribeOptions = options; 37 | _receivedEventPersistenter = receivedEventPersistenter; 38 | _messageDecoder = messageDecoder; 39 | _logger = logger; 40 | 41 | Factory = new TaskFactory(TaskCreationOptions.LongRunning, TaskContinuationOptions.LongRunning); 42 | } 43 | 44 | public void Start() 45 | { 46 | var clients = GetClients(); 47 | foreach (var client in clients) 48 | { 49 | client.Listening(); 50 | } 51 | } 52 | 53 | private IList GetClients() 54 | { 55 | var subscribeInfos = _subscribeOptions.SubscribeInfos; 56 | var groupedInfos = subscribeInfos.GroupBy(x => x.Group) 57 | .ToDictionary(x => x.Key, x => x.GroupBy(y => y.Exchange) 58 | .ToDictionary(y => y.Key, y => y.ToArray())); 59 | 60 | _logger.LogInformation($"subscribe infos {subscribeInfos.ToJson()}"); 61 | 62 | var clients = new List(); 63 | var connectfactoryAccessor = _serviceProvider.GetRequiredService(); 64 | var rabbitOption = _serviceProvider.GetRequiredService(); 65 | 66 | foreach (var queueGroupedItems in groupedInfos) 67 | { 68 | foreach (var exchangeGroupedItems in queueGroupedItems.Value) 69 | { 70 | for (var i = 0; i < _subscribeOptions.ConsumerClientCount; ++i) 71 | { 72 | var exchange = string.IsNullOrEmpty(exchangeGroupedItems.Key) ? rabbitOption.DefaultExchangeName : exchangeGroupedItems.Key; 73 | 74 | var client = new DefaultSubscribeClient(_serviceProvider 75 | , connectfactoryAccessor 76 | , rabbitOption 77 | , queueGroupedItems.Key 78 | , exchange); 79 | 80 | _disposables.Add(client); 81 | clients.Add(client); 82 | RegisterClient(client); 83 | 84 | var topics = exchangeGroupedItems.Value.Select(x => x.Topic).ToArray(); 85 | client.Subscribe(topics); 86 | } 87 | } 88 | } 89 | 90 | return clients; 91 | } 92 | 93 | private async Task MessageHandle(object ctx) 94 | { 95 | var context = (MessageContext)ctx; 96 | ReceivedMessage msg = null; 97 | 98 | try 99 | { 100 | msg = _messageDecoder.Decode(context); 101 | } 102 | catch (Exception ex) 103 | { 104 | _logger.DecodeError(context, ex); 105 | 106 | HandleError(_serviceProvider, new FailContext 107 | { 108 | Raw = Encoding.UTF8.GetString(context.Content), 109 | ExceptionMessage = ex.Message 110 | }); 111 | 112 | return; 113 | } 114 | 115 | try 116 | { 117 | await _receivedEventPersistenter.InsertAsync(msg); 118 | } 119 | catch (Exception e) 120 | { 121 | _logger.ReceivedEventPersistenterInsert(msg, e); 122 | 123 | HandleError(_serviceProvider, new FailContext 124 | { 125 | ExceptionMessage = e.Message, 126 | Raw = Encoding.UTF8.GetString(context.Content), 127 | }); 128 | 129 | return; 130 | } 131 | 132 | var result = false; 133 | 134 | try 135 | { 136 | var invoker = new DefaultConsumerInvoker(_serviceProvider, context); 137 | result = await invoker.InvokeAsync(); 138 | 139 | _logger.LogInformation($"invoke result: {result} message: {msg.ToJson()}"); 140 | } 141 | catch (Exception ex) 142 | { 143 | _logger.InvokeConsumer(context, msg, ex); 144 | } 145 | finally 146 | { 147 | if (result) 148 | { 149 | try 150 | { 151 | await _receivedEventPersistenter.ChangeStateAsync(msg.MessageId, msg.TransactId, msg.Group, Core.State.MessageState.Succeeded); 152 | } 153 | catch (Exception ex) 154 | { 155 | _logger.UpdateReceivedMessage(msg, ex); 156 | } 157 | _logger.LogInformation($"message invoke true {msg.ToJson()}"); 158 | } 159 | else 160 | { 161 | _logger.LogInformation($"invoke false {msg.ToJson()}"); 162 | 163 | try 164 | { 165 | await _receivedEventPersistenter.ChangeStateAsync(msg.MessageId, msg.TransactId, msg.Group, Core.State.MessageState.Failed); 166 | } 167 | catch (Exception ex) 168 | { 169 | _logger.LogError(110, ex, $"fail to update received message[ignore]: {msg.ToJson()}"); 170 | } 171 | 172 | HandleError(_serviceProvider, new FailContext 173 | { 174 | ExceptionMessage = "", 175 | State = msg, 176 | }); 177 | } 178 | } 179 | 180 | } 181 | 182 | private void RegisterClient(ISubscribeClient client) 183 | { 184 | client.OnReceive = (MessageContext context) => 185 | { 186 | Factory.StartNew(MessageHandle, context); 187 | }; 188 | } 189 | 190 | public void Dispose() 191 | { 192 | foreach (var disposable in _disposables) 193 | { 194 | disposable.Dispose(); 195 | } 196 | } 197 | 198 | private static void HandleError(IServiceProvider serviceProvider, FailContext context) 199 | { 200 | ThreadPool.QueueUserWorkItem((state) => 201 | { 202 | try 203 | { 204 | var handlers = serviceProvider.GetServices(); 205 | foreach (var handler in handlers) 206 | { 207 | handler.HandleAsync(context); 208 | } 209 | } 210 | catch 211 | { 212 | // ignored 213 | } 214 | }); 215 | } 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /src/EventBus.Subscribe/Internal/IConsumerInvoker.Default.cs: -------------------------------------------------------------------------------- 1 | using EventBus.Subscribe.Infrastructure; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using System; 4 | using System.Threading.Tasks; 5 | using System.Reflection; 6 | using Microsoft.Extensions.Options; 7 | using System.Linq; 8 | using System.Text; 9 | using EventBus.Core.Infrastructure; 10 | using EventBus.Core; 11 | using EventBus.Core.Util; 12 | 13 | namespace EventBus.Subscribe.Internal 14 | { 15 | internal class DefaultConsumerInvoker : IInvoker 16 | { 17 | private readonly IServiceScope _serviceScope; 18 | private readonly IServiceProvider _serviceProvider; 19 | private readonly SubscribeOptions _subscribeOptions; 20 | 21 | public MessageContext Context { get; } 22 | 23 | public DefaultConsumerInvoker(IServiceProvider serviceProvider 24 | , MessageContext context) 25 | { 26 | _serviceScope = serviceProvider.CreateScope(); 27 | _serviceProvider = _serviceScope.ServiceProvider; 28 | _subscribeOptions = _serviceProvider.GetRequiredService(); 29 | Context = context; 30 | } 31 | 32 | public Task InvokeAsync() 33 | { 34 | var subscribeInfo = GetSubscribe(); 35 | var handler = GetHandler(subscribeInfo); 36 | var message = GetMessage(subscribeInfo); 37 | return Call(handler, message); 38 | } 39 | 40 | private Task Call(object handler, object message) 41 | { 42 | var method = handler.GetType().GetTypeInfo().GetMethod(nameof(ISubscribeHandler.HandleAsync)); 43 | var task = method.Invoke(handler, new[] { message }) as Task; 44 | return task; 45 | } 46 | 47 | private object GetHandler(SubscribeInfo subscribeInfo) 48 | { 49 | return _serviceProvider.GetService(subscribeInfo.CallbackType); 50 | } 51 | 52 | private SubscribeInfo GetSubscribe() 53 | { 54 | return _subscribeOptions.SubscribeInfos.FirstOrDefault(x => x.Exchange == Context.Exchange 55 | && TopicMatch.IsTopicMatch(Context.Topic, x.Topic) 56 | && x.Group == Context.Queue); 57 | } 58 | 59 | private string GetMessage(SubscribeInfo subscribeInfo) 60 | { 61 | var receivedMsg = _serviceProvider.GetRequiredService().Decode(Context); 62 | return receivedMsg.Content; 63 | } 64 | 65 | public void Dispose() 66 | { 67 | _serviceScope.Dispose(); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/EventBus.Subscribe/ReceivedEventPersistenter.cs: -------------------------------------------------------------------------------- 1 | using Dapper; 2 | using EventBus.Core; 3 | using EventBus.Core.Infrastructure; 4 | using EventBus.Core.State; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Microsoft.Extensions.Logging; 7 | using Newtonsoft.Json; 8 | using System; 9 | using System.Data; 10 | using System.Linq; 11 | using System.Text; 12 | using System.Threading.Tasks; 13 | 14 | namespace EventBus.Subscribe 15 | { 16 | public class ReceivedEventPersistenter : IReceivedEventPersistenter 17 | { 18 | private const string TableName = "eventbus.receivedmessages"; 19 | 20 | private readonly EventBusMySQLOptions _eventBusMySQLOptions; 21 | private readonly ILogger _logger; 22 | private readonly IServiceProvider _serviceProvider; 23 | 24 | public ReceivedEventPersistenter(EventBusMySQLOptions eventBusMySQLOptions, 25 | ILogger logger, 26 | IServiceProvider serviceProvider) 27 | { 28 | _eventBusMySQLOptions = eventBusMySQLOptions; 29 | _logger = logger; 30 | _serviceProvider = serviceProvider; 31 | } 32 | 33 | public async Task ChangeStateAsync(long messageId, long transactId, string group, MessageState messageState) 34 | { 35 | var sql = BuildChangeStateSQL(false); 36 | var parameters = new { State = (short)messageState, MessageId = messageId, TransactId = transactId, Group = group }; 37 | ChangeStateMessage result; 38 | using (var connection = await OpenDbConnectionAsync()) 39 | { 40 | result = await connection.QueryFirstOrDefaultAsync(sql, parameters); 41 | } 42 | if (result == null) throw new AffectedRowsCountUnExpectedException(1, 0); 43 | 44 | //var args = new StateChangedArgs(result.State, messageState); 45 | //var metaDataObj = result.GetMetaData(); 46 | //var stateChangeHandlers = _serviceProvider.GetServices().Where(handler => handler.CanHandle(MessageType.Subscribed, result.Content, metaDataObj, args)); 47 | //if (stateChangeHandlers.Any()) 48 | //{ 49 | // foreach (var handler in stateChangeHandlers) 50 | // { 51 | // try 52 | // { 53 | // await handler.HandleAsync(MessageType.Subscribed, result.Content, metaDataObj, args); 54 | // } 55 | // catch (Exception ex) 56 | // { 57 | // _logger.LogError($"An exception was thrown during {handler.GetType().FullName} executing.{ex.Message}"); 58 | // } 59 | // } 60 | //} 61 | 62 | } 63 | 64 | public async Task ChangeStateAsync(long id, MessageState messageState) 65 | { 66 | var sql = BuildChangeStateSQL(true); 67 | var parameters = new { State = (short)messageState, Id = id}; 68 | using (var connection = await OpenDbConnectionAsync()) 69 | { 70 | var affectedRows = await connection.ExecuteAsync(sql, parameters); 71 | if (affectedRows != 1) throw new AffectedRowsCountUnExpectedException(1, affectedRows); 72 | } 73 | } 74 | 75 | public async Task EnsureCreatedAsync() 76 | { 77 | var sql = new StringBuilder(); 78 | sql.AppendLine($@"CREATE TABLE IF NOT EXISTS `{TableName}` ("); 79 | sql.AppendLine(@" `Id` bigint(20) NOT NULL, "); 80 | sql.AppendLine(@" `MessageId` bigint(20) DEFAULT NULL,"); 81 | sql.AppendLine(@" `TransactId` bigint(20) DEFAULT NULL, "); 82 | sql.AppendLine(@" `Group` varchar(255) DEFAULT NULL,"); 83 | sql.AppendLine(@" `RouteKey` varchar(255) DEFAULT NULL, "); 84 | sql.AppendLine(@" `MetaData` longtext,"); 85 | sql.AppendLine(@" `Content` longtext, "); 86 | sql.AppendLine(@" `ReceivedTime` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,"); 87 | sql.AppendLine(@" `State` tinyint(4) DEFAULT NULL, "); 88 | sql.AppendLine(@" PRIMARY KEY (`Id`),"); 89 | sql.AppendLine(@" UNIQUE KEY `Index_EventBus_ReceivedMessage_IDs` (`MessageId`,`TransactId`,`Group`) USING BTREE"); 90 | sql.AppendLine(@") ENGINE=InnoDB DEFAULT CHARSET=utf8;"); 91 | try 92 | { 93 | using (var connection = await OpenDbConnectionAsync()) 94 | { 95 | await connection.ExecuteAsync(sql.ToString()); 96 | } 97 | } 98 | catch { } 99 | } 100 | 101 | public async Task InsertAsync(object message) 102 | { 103 | var sql = BuildInsertSQL(); 104 | using (var connection = await OpenDbConnectionAsync()) 105 | { 106 | var affectedRows = await connection.ExecuteAsync(sql, message); 107 | if (affectedRows == 0) throw new AffectedRowsCountUnExpectedException(1, affectedRows); 108 | } 109 | } 110 | 111 | private async Task OpenDbConnectionAsync() 112 | { 113 | var conn = new MySql.Data.MySqlClient.MySqlConnection(_eventBusMySQLOptions.ConnectionString); 114 | await conn.OpenAsync(); 115 | return conn; 116 | } 117 | 118 | public async Task BeginTransaction(IDbConnection connection) 119 | { 120 | return await Task.Run(() => 121 | { 122 | var transaction = connection.BeginTransaction(); 123 | return transaction; 124 | }); 125 | } 126 | 127 | private string BuildInsertSQL() 128 | { 129 | var sql = new StringBuilder(); 130 | sql.AppendLine($@"INSERT INTO `{TableName}` ("); 131 | sql.AppendLine(" `Id`, "); 132 | sql.AppendLine(" `MessageId`,"); 133 | sql.AppendLine(" `TransactId`,"); 134 | sql.AppendLine(" `Group`,"); 135 | sql.AppendLine(" `RouteKey`,"); 136 | sql.AppendLine(" `MetaData`,"); 137 | sql.AppendLine(" `Content`,"); 138 | sql.AppendLine(" `ReceivedTime`,"); 139 | sql.AppendLine(" `State`"); 140 | sql.AppendLine(")"); 141 | sql.AppendLine("VALUES"); 142 | sql.AppendLine(" ("); 143 | sql.AppendLine(" @Id, "); 144 | sql.AppendLine("@MessageId,"); 145 | sql.AppendLine("@TransactId,"); 146 | sql.AppendLine("@Group,"); 147 | sql.AppendLine("@RouteKey,"); 148 | sql.AppendLine("@MetaData,"); 149 | sql.AppendLine("@Content,"); 150 | sql.AppendLine("@ReceivedTime,"); 151 | sql.AppendLine("@State"); 152 | sql.AppendLine(" );"); 153 | return sql.ToString(); 154 | } 155 | 156 | private string BuildChangeStateSQL(bool useLocalId) 157 | { 158 | string whereClause = ""; 159 | if (useLocalId) 160 | whereClause = @"(`Id` = @Id);"; 161 | else 162 | whereClause = @"(`MessageId` = @MessageId) AND (`TransactId` = @TransactId) AND (`Group` = @Group);"; 163 | 164 | 165 | var sql = new StringBuilder(); 166 | 167 | sql.AppendLine(@"SELECT"); 168 | sql.AppendLine(@"`State`,"); 169 | sql.AppendLine(@"`Group`"); 170 | sql.AppendLine(@"`MetaData`,"); 171 | sql.AppendLine(@"`Content`"); 172 | sql.AppendLine(@"FROM"); 173 | sql.AppendLine($@"`{TableName}`"); 174 | sql.AppendLine(@"WHERE"); 175 | sql.AppendLine(whereClause); 176 | 177 | sql.AppendLine($@"UPDATE `{TableName}` SET"); 178 | sql.AppendLine(@" `State` = @State"); 179 | sql.AppendLine(@"WHERE"); 180 | sql.AppendLine(whereClause); 181 | 182 | return sql.ToString(); 183 | } 184 | } 185 | 186 | internal class ChangeStateMessage 187 | { 188 | public MessageState State { get; set; } 189 | public string MetaData { get; set; } 190 | public string Content { get; set; } 191 | public string ContentType { get; set; } 192 | public string Group { get; set; } 193 | 194 | public IMetaData GetMetaData() 195 | { 196 | if (string.IsNullOrEmpty(MetaData)) return null; 197 | return FeiniuBus.Util.FeiniuBusJsonConvert.DeserializeObject(MetaData); 198 | } 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /src/EventBus.Subscribe/SubscribeAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace EventBus.Subscribe 6 | { 7 | [AttributeUsage(AttributeTargets.Class)] 8 | public class SubscribeAttribute: Attribute 9 | { 10 | public string Group { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/EventBus.Subscribe/SubscribeOptions.cs: -------------------------------------------------------------------------------- 1 | using EventBus.Core; 2 | using EventBus.Core.Infrastructure; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using EventBus.Core.Util; 8 | 9 | namespace EventBus.Subscribe 10 | { 11 | public class SubscribeOptions 12 | { 13 | public string DefaultGroup { get; set; } 14 | 15 | public int ConsumerClientCount { get; set; } = 1; 16 | 17 | public IReadOnlyList SubscribeInfos { get; private set; } 18 | 19 | private readonly IList _subscribeInfos; 20 | 21 | public SubscribeOptions() 22 | { 23 | _subscribeInfos = new List(); 24 | } 25 | 26 | public void RegisterCallback(string topic, Type callbackType) 27 | { 28 | if (_subscribeInfos.Any(x => x.Topic == topic && x.Group == null)) 29 | { 30 | throw new InvalidOperationException($"Duplicated subscribe topic={topic}"); 31 | } 32 | 33 | _subscribeInfos.Add(new SubscribeInfo 34 | { 35 | Topic = topic, 36 | CallbackType = callbackType 37 | }); 38 | } 39 | 40 | public void RegisterCallback(string topic, string group, Type callbackType) 41 | { 42 | if (_subscribeInfos.Any(x => x.Topic == topic && x.Group == group)) 43 | { 44 | throw new InvalidOperationException($"Duplicated subscribe topic={topic} group={group}"); 45 | } 46 | 47 | _subscribeInfos.Add(new SubscribeInfo 48 | { 49 | Topic = topic, 50 | Group = group, 51 | CallbackType = callbackType 52 | }); 53 | } 54 | 55 | public void RegisterCallback(string topic, string group, string exchange, Type callbackType) 56 | { 57 | if (_subscribeInfos.Any(x => x.Topic == topic && x.Group == group)) 58 | { 59 | throw new InvalidOperationException($"Duplicated subscribe topic={topic} group={group}"); 60 | } 61 | 62 | _subscribeInfos.Add(new SubscribeInfo 63 | { 64 | Exchange = exchange, 65 | Topic = topic, 66 | Group = group, 67 | CallbackType = callbackType 68 | }); 69 | } 70 | 71 | public void Build(IServiceProvider serviceProvider) 72 | { 73 | var concator = serviceProvider.GetRequiredService(); 74 | var rabbitOptions = serviceProvider.GetRequiredService(); 75 | 76 | var defaultExchange = rabbitOptions.DefaultExchangeName; 77 | 78 | foreach(var info in _subscribeInfos) 79 | { 80 | if (string.IsNullOrEmpty(info.Exchange)) 81 | { 82 | info.Exchange = concator.Concat(info.Exchange ?? defaultExchange); 83 | info.Group = concator.Concat(info.Group ?? DefaultGroup); 84 | } 85 | } 86 | 87 | SubscribeInfos = new List(_subscribeInfos); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /test/EventBus.Core.Test/Consts.cs: -------------------------------------------------------------------------------- 1 | namespace EventBus.Core.Test 2 | { 3 | internal static class Consts 4 | { 5 | internal const string DbConnectionString = "Server=mysql.local;Port=3306;Database=EventBusUnitTest; User=dev;Password=dev;charset=UTF-8"; 6 | internal const string RabbitHost = "192.168.120.133"; 7 | internal const string RabbitUser = "dev"; 8 | internal const string RabbitPassword = "dev"; 9 | 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/EventBus.Core.Test/EventBus.Core.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp2.0 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | true 27 | 28 | -------------------------------------------------------------------------------- /test/EventBus.Core.Test/PublishTest.Ctor.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using NSubstitute; 5 | 6 | namespace EventBus.Core.Test 7 | { 8 | public partial class PublishTest : TestBase 9 | { 10 | protected override void OnServiceProviderBuilding(IServiceCollection services) 11 | { 12 | IHostingEnvironment env = Substitute.For(); 13 | env.EnvironmentName = "staging"; 14 | services.AddSingleton(env); 15 | services.AddDbContext(opt => opt.UseMySql(Consts.DbConnectionString)); 16 | services.AddEventBus((opt => 17 | { 18 | opt.UseEntityframework(); 19 | opt.UseRabbitMQ(cnf => 20 | { 21 | cnf.HostName = Consts.RabbitHost; 22 | cnf.UserName = Consts.RabbitUser; 23 | cnf.Password = Consts.RabbitPassword; 24 | cnf.Port = 5672; 25 | cnf.VirtualHost = "/"; 26 | }); 27 | })); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test/EventBus.Core.Test/PublishTest.MessagePersistenter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using System.Threading.Tasks; 5 | using EventBus.Core.Infrastructure; 6 | using EventBus.Core.State; 7 | using EventBus.Publish.Internal.Model; 8 | using FeiniuBus; 9 | using Xunit; 10 | 11 | namespace EventBus.Core.Test 12 | { 13 | public partial class PublishTest 14 | { 15 | [Fact] 16 | public async Task MessagePersistenerTest() 17 | { 18 | var persistener = Provider.GetRequiredService(); 19 | var dbContext = Provider.GetRequiredService(); 20 | var connection = dbContext.Database.GetDbConnection(); 21 | await connection.OpenAsync(); 22 | var transaction = connection.BeginTransaction(); 23 | await persistener.EnsureCreatedAsync(); 24 | await persistener.InsertAsync(new Message 25 | { 26 | Id= Puid.NewPuid(), 27 | Content = "tesing", 28 | CreationDate = DateTime.Now, 29 | Exchange = "tesing.exchange", 30 | MessageId = Puid.NewPuid(), 31 | MetaData = "{}", 32 | RouteKey = "tesing.key", 33 | State = MessageState.Processing, 34 | TransactId = Puid.NewPuid(), 35 | Type = MessageType.Published 36 | }, connection, transaction); 37 | transaction.Commit(); 38 | connection.Close(); 39 | connection.Dispose(); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/EventBus.Core.Test/PublishTest.MessageQueueTransaction.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using System.Threading.Tasks; 3 | using Xunit; 4 | 5 | namespace EventBus.Core.Test 6 | { 7 | public partial class PublishTest 8 | { 9 | [Fact] 10 | public async Task MessageQueueTransaction() 11 | { 12 | var transaction = Provider.GetRequiredService(); 13 | await transaction.PublishAsync("testing.exchange", "testing.key", new[] {(byte)1, (byte)1, (byte)1 }); 14 | await transaction.CommitAsync(); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/EventBus.Core.Test/SubscribeTest.Ctor.cs: -------------------------------------------------------------------------------- 1 | using EventBus.Subscribe; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using NSubstitute; 6 | 7 | namespace EventBus.Core.Test 8 | { 9 | public partial class SubscribeTest : TestBase 10 | { 11 | protected override void OnServiceProviderBuilding(IServiceCollection services) 12 | { 13 | IHostingEnvironment env = Substitute.For(); 14 | env.EnvironmentName = "staging"; 15 | services.AddSingleton(env); 16 | 17 | services.AddDbContext(opt => opt.UseMySql(Consts.DbConnectionString)); 18 | 19 | services.AddEventBus(opt => 20 | { 21 | opt.UseEntityframework(); 22 | opt.UseRabbitMQ(cnf => 23 | { 24 | cnf.HostName = Consts.RabbitHost; 25 | cnf.UserName = Consts.RabbitUser; 26 | cnf.Password = Consts.RabbitPassword; 27 | cnf.Port = 5672; 28 | cnf.VirtualHost = "/"; 29 | }); 30 | }); 31 | 32 | services.AddSub(options => 33 | { 34 | options.ConsumerClientCount = 5; 35 | options.DefaultGroup = "FeiniuBusPayment1111"; 36 | 37 | //options.RegisterCallback("eventbus.testtopic", "eventbus.testgroup2", typeof(NewUserEventHandler)); 38 | }); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test/EventBus.Core.Test/SubscribeTest.ReceivedEventPersistenter.cs: -------------------------------------------------------------------------------- 1 | using EventBus.Core.State; 2 | using FeiniuBus; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using System; 5 | using System.Threading.Tasks; 6 | using Xunit; 7 | 8 | namespace EventBus.Core.Test 9 | { 10 | public partial class SubscribeTest 11 | { 12 | [Fact] 13 | public async Task ReceivedEventPersistenterTest() 14 | { 15 | var persistenter = Provider.GetRequiredService(); 16 | await persistenter.EnsureCreatedAsync(); 17 | await persistenter.InsertAsync(new 18 | { 19 | Id = Puid.NewPuid(), 20 | MessageId = Puid.NewPuid(), 21 | TransactId = Puid.NewPuid(), 22 | Group = "testGroup", 23 | RouteKey = "test.key", 24 | MetaData = "{}", 25 | Content = @"{""title"":""testing""}", 26 | ReceivedTime = DateTime.Now, 27 | State = MessageState.Succeeded 28 | }); 29 | } 30 | 31 | 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test/EventBus.Core.Test/TestBase.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using System; 3 | 4 | namespace EventBus.Core.Test 5 | { 6 | public abstract class TestBase 7 | { 8 | protected readonly IServiceProvider Provider; 9 | 10 | protected TestBase() 11 | { 12 | var services = new ServiceCollection(); 13 | // ReSharper disable once VirtualMemberCallInConstructor 14 | OnServiceProviderBuilding(services); 15 | Provider = services.BuildServiceProvider(); 16 | } 17 | 18 | protected abstract void OnServiceProviderBuilding(IServiceCollection services); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/EventBus.Core.Test/TestDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | 3 | namespace EventBus.Core.Test 4 | { 5 | public class TestDbContext : DbContext 6 | { 7 | public TestDbContext(DbContextOptions options) : base(options) 8 | { 9 | } 10 | } 11 | } 12 | --------------------------------------------------------------------------------