├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── dotnetcore.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── RabbitMQ.EventBus.AspNetCore.Sample ├── Controllers │ ├── MessageBody.cs │ ├── MessageBodyHandle.cs │ └── ValuesController.cs ├── Program.cs ├── RabbitMQ.EventBus.AspNetCore.Sample.csproj ├── Startup.cs ├── appsettings.Development.json └── appsettings.json ├── RabbitMQ.EventBus.AspNetCore.sln ├── WIKI.md ├── docs ├── _config.yml └── index.md └── src └── RabbitMQ.EventBus.AspNetCore ├── Attributes └── EventBusAttribute.cs ├── Configurations ├── DeadLetterExchangeConfig.cs ├── QueuePrefixType.cs ├── RabbitMQEventBusConnectionConfiguration.cs └── RabbitMQEventBusConnectionConfigurationBuild.cs ├── DefaultRabbitMQEventBusV2.cs ├── Events ├── IEvent.cs ├── V1 │ ├── EventHandlerArgs.cs │ └── IEventHandler.cs └── V2 │ ├── EventHandlerArgs.cs │ └── IEventResponseHandler.cs ├── Extensions ├── DynamicExtensions.cs ├── IModelExtensions.cs ├── ServiceCollectionExtensions.cs └── TypeExtensions.cs ├── Factories ├── DefaultRabbitMQPersistentConnection.cs └── IRabbitMQPersistentConnection.cs ├── GlobalUsings.cs ├── IRabbitMQEventBus.cs ├── Modules ├── EventBusArgs.cs ├── EventHandlerModuleFactory.cs ├── IEventHandlerModuleFactory.cs ├── IModuleHandle.cs └── RabbitMQEventBusModuleOption.cs ├── Properties └── AssemblyInfo.cs ├── RabbitMQ.EventBus.AspNetCore.csproj ├── dotnet-logo.png └── dotnet-logo.svg /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: ojdev 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/dotnetcore.yml: -------------------------------------------------------------------------------- 1 | name: .NET Core App CI to Docker Hub 2 | 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | 8 | jobs: 9 | build: 10 | 11 | runs-on: ubuntu-latest 12 | env: 13 | TZ: Asia/Shanghai 14 | TAG_NUMBER: $GITHUB_RUN_NUMBER 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Setup .NET 18 | uses: actions/setup-dotnet@v1 19 | with: 20 | dotnet-version: 6.0.400 21 | - name: Get tag 22 | id: tag 23 | uses: dawidd6/action-get-tag@v1 24 | with: 25 | # Optionally strip `v` prefix 26 | strip_v: true 27 | - name: Set VERSION variable from tag 28 | run: echo ${{steps.tag.outputs.tag}} 29 | - name: Build with dotnet 30 | run: dotnet build --configuration Release src/RabbitMQ.EventBus.AspNetCore 31 | - name: Pack 32 | run: dotnet pack src/RabbitMQ.EventBus.AspNetCore -c Release --include-symbols --include-source -p:PackageVersion=${{steps.tag.outputs.tag}} -o artifacts/ 33 | - name: Publish Symbols to NuGet 34 | run: dotnet nuget push artifacts/*.symbols.nupkg --api-key ${{secrets.NUGET_API_KEY}} --source https://api.nuget.org/v3/index.json 35 | - name: Add Source Github NuGet 36 | run: dotnet nuget add source --username ojdev --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text --name github "https://nuget.pkg.github.com/OWNER/index.json" 37 | 38 | -------------------------------------------------------------------------------- /.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/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # Visual Studio 2017 auto generated files 33 | Generated\ Files/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # Benchmark Results 49 | BenchmarkDotNet.Artifacts/ 50 | 51 | # .NET Core 52 | project.lock.json 53 | project.fragment.lock.json 54 | artifacts/ 55 | **/Properties/launchSettings.json 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_i.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.iobj 68 | *.pch 69 | *.pdb 70 | *.ipdb 71 | *.pgc 72 | *.pgd 73 | *.rsp 74 | *.sbr 75 | *.tlb 76 | *.tli 77 | *.tlh 78 | *.tmp 79 | *.tmp_proj 80 | *.log 81 | *.vspscc 82 | *.vssscc 83 | .builds 84 | *.pidb 85 | *.svclog 86 | *.scc 87 | 88 | # Chutzpah Test files 89 | _Chutzpah* 90 | 91 | # Visual C++ cache files 92 | ipch/ 93 | *.aps 94 | *.ncb 95 | *.opendb 96 | *.opensdf 97 | *.sdf 98 | *.cachefile 99 | *.VC.db 100 | *.VC.VC.opendb 101 | 102 | # Visual Studio profiler 103 | *.psess 104 | *.vsp 105 | *.vspx 106 | *.sap 107 | 108 | # Visual Studio Trace Files 109 | *.e2e 110 | 111 | # TFS 2012 Local Workspace 112 | $tf/ 113 | 114 | # Guidance Automation Toolkit 115 | *.gpState 116 | 117 | # ReSharper is a .NET coding add-in 118 | _ReSharper*/ 119 | *.[Rr]e[Ss]harper 120 | *.DotSettings.user 121 | 122 | # JustCode is a .NET coding add-in 123 | .JustCode 124 | 125 | # TeamCity is a build add-in 126 | _TeamCity* 127 | 128 | # DotCover is a Code Coverage Tool 129 | *.dotCover 130 | 131 | # AxoCover is a Code Coverage Tool 132 | .axoCover/* 133 | !.axoCover/settings.json 134 | 135 | # Visual Studio code coverage results 136 | *.coverage 137 | *.coveragexml 138 | 139 | # NCrunch 140 | _NCrunch_* 141 | .*crunch*.local.xml 142 | nCrunchTemp_* 143 | 144 | # MightyMoose 145 | *.mm.* 146 | AutoTest.Net/ 147 | 148 | # Web workbench (sass) 149 | .sass-cache/ 150 | 151 | # Installshield output folder 152 | [Ee]xpress/ 153 | 154 | # DocProject is a documentation generator add-in 155 | DocProject/buildhelp/ 156 | DocProject/Help/*.HxT 157 | DocProject/Help/*.HxC 158 | DocProject/Help/*.hhc 159 | DocProject/Help/*.hhk 160 | DocProject/Help/*.hhp 161 | DocProject/Help/Html2 162 | DocProject/Help/html 163 | 164 | # Click-Once directory 165 | publish/ 166 | 167 | # Publish Web Output 168 | *.[Pp]ublish.xml 169 | *.azurePubxml 170 | # Note: Comment the next line if you want to checkin your web deploy settings, 171 | # but database connection strings (with potential passwords) will be unencrypted 172 | *.pubxml 173 | *.publishproj 174 | 175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 176 | # checkin your Azure Web App publish settings, but sensitive information contained 177 | # in these scripts will be unencrypted 178 | PublishScripts/ 179 | 180 | # NuGet Packages 181 | *.nupkg 182 | # The packages folder can be ignored because of Package Restore 183 | **/[Pp]ackages/* 184 | # except build/, which is used as an MSBuild target. 185 | !**/[Pp]ackages/build/ 186 | # Uncomment if necessary however generally it will be regenerated when needed 187 | #!**/[Pp]ackages/repositories.config 188 | # NuGet v3's project.json files produces more ignorable files 189 | *.nuget.props 190 | *.nuget.targets 191 | 192 | # Microsoft Azure Build Output 193 | csx/ 194 | *.build.csdef 195 | 196 | # Microsoft Azure Emulator 197 | ecf/ 198 | rcf/ 199 | 200 | # Windows Store app package directories and files 201 | AppPackages/ 202 | BundleArtifacts/ 203 | Package.StoreAssociation.xml 204 | _pkginfo.txt 205 | *.appx 206 | 207 | # Visual Studio cache files 208 | # files ending in .cache can be ignored 209 | *.[Cc]ache 210 | # but keep track of directories ending in .cache 211 | !*.[Cc]ache/ 212 | 213 | # Others 214 | ClientBin/ 215 | ~$* 216 | *~ 217 | *.dbmdl 218 | *.dbproj.schemaview 219 | *.jfm 220 | *.pfx 221 | *.publishsettings 222 | orleans.codegen.cs 223 | 224 | # Including strong name files can present a security risk 225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 226 | #*.snk 227 | 228 | # Since there are multiple workflows, uncomment next line to ignore bower_components 229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 230 | #bower_components/ 231 | 232 | # RIA/Silverlight projects 233 | Generated_Code/ 234 | 235 | # Backup & report files from converting an old project file 236 | # to a newer Visual Studio version. Backup files are not needed, 237 | # because we have git ;-) 238 | _UpgradeReport_Files/ 239 | Backup*/ 240 | UpgradeLog*.XML 241 | UpgradeLog*.htm 242 | ServiceFabricBackup/ 243 | *.rptproj.bak 244 | 245 | # SQL Server files 246 | *.mdf 247 | *.ldf 248 | *.ndf 249 | 250 | # Business Intelligence projects 251 | *.rdl.data 252 | *.bim.layout 253 | *.bim_*.settings 254 | *.rptproj.rsuser 255 | 256 | # Microsoft Fakes 257 | FakesAssemblies/ 258 | 259 | # GhostDoc plugin setting file 260 | *.GhostDoc.xml 261 | 262 | # Node.js Tools for Visual Studio 263 | .ntvs_analysis.dat 264 | node_modules/ 265 | 266 | # Visual Studio 6 build log 267 | *.plg 268 | 269 | # Visual Studio 6 workspace options file 270 | *.opt 271 | 272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 273 | *.vbw 274 | 275 | # Visual Studio LightSwitch build output 276 | **/*.HTMLClient/GeneratedArtifacts 277 | **/*.DesktopClient/GeneratedArtifacts 278 | **/*.DesktopClient/ModelManifest.xml 279 | **/*.Server/GeneratedArtifacts 280 | **/*.Server/ModelManifest.xml 281 | _Pvt_Extensions 282 | 283 | # Paket dependency manager 284 | .paket/paket.exe 285 | paket-files/ 286 | 287 | # FAKE - F# Make 288 | .fake/ 289 | 290 | # JetBrains Rider 291 | .idea/ 292 | *.sln.iml 293 | 294 | # CodeRush 295 | .cr/ 296 | 297 | # Python Tools for Visual Studio (PTVS) 298 | __pycache__/ 299 | *.pyc 300 | 301 | # Cake - Uncomment if you are using it 302 | # tools/** 303 | # !tools/packages.config 304 | 305 | # Tabs Studio 306 | *.tss 307 | 308 | # Telerik's JustMock configuration file 309 | *.jmconfig 310 | 311 | # BizTalk build output 312 | *.btp.cs 313 | *.btm.cs 314 | *.odx.cs 315 | *.xsd.cs 316 | 317 | # OpenCover UI analysis results 318 | OpenCover/ 319 | 320 | # Azure Stream Analytics local run output 321 | ASALocalRun/ 322 | 323 | # MSBuild Binary and Structured Log 324 | *.binlog 325 | 326 | # NVidia Nsight GPU debugger configuration file 327 | *.nvuser 328 | 329 | # MFractors (Xamarin productivity tool) working folder 330 | .mfractor/ 331 | /.vscode 332 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at luacloud@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 2 | wait…… 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 欧俊 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [License: MIT](https://github.com/ojdev/RabbitMQ.EventBus.AspNetCore/blob/dev/LICENSE) 2 | 3 | # RabbitMQ.EventBus.AspNetCore 4 | 5 | [![CodeFactor](https://www.codefactor.io/repository/github/ojdev/rabbitmq.eventbus.aspnetcore/badge)](https://www.codefactor.io/repository/github/ojdev/rabbitmq.eventbus.aspnetcore) 6 | [![NuGet](https://img.shields.io/nuget/v/RabbitMQ.EventBus.AspNetCore.svg?style=popout)](https://www.nuget.org/packages/RabbitMQ.EventBus.AspNetCore) 7 | [![NuGet](https://img.shields.io/nuget/dt/RabbitMQ.EventBus.AspNetCore.svg?style=popout)](https://www.nuget.org/packages/RabbitMQ.EventBus.AspNetCore) 8 | [![GitHub license](https://img.shields.io/github/license/ojdev/RabbitMQ.EventBus.AspNetCore.svg)](https://github.com/ojdev/RabbitMQ.EventBus.AspNetCore/blob/master/LICENSE) 9 | [![LAST COMMIT](https://img.shields.io/github/last-commit/ojdev/RabbitMQ.EventBus.AspNetCore.svg)]() 10 | [![CODE SIZE](https://img.shields.io/github/languages/code-size/ojdev/RabbitMQ.EventBus.AspNetCore.svg)]() 11 | 12 | 13 | 该包为一个基于官方RabbitMQ.Client的二次封装包,专门针对Asp.Net Core项目进行开发,在微服务中进行消息的传递使用起来比较方便。 14 | 15 | 目前功能: 16 | 17 | - [x] 发布/订阅 18 | - [x] 死信队列 19 | - [x] RPC功能(实验性) 20 | 21 | 22 | # [使用说明](https://github.com/ojdev/RabbitMQ.EventBus.AspNetCore/wiki) 23 | -------------------------------------------------------------------------------- /RabbitMQ.EventBus.AspNetCore.Sample/Controllers/MessageBody.cs: -------------------------------------------------------------------------------- 1 | using RabbitMQ.EventBus.AspNetCore.Attributes; 2 | using RabbitMQ.EventBus.AspNetCore.Events; 3 | using System; 4 | 5 | namespace RabbitMQ.EventBus.AspNetCore.Simple.Controllers 6 | { 7 | [EventBus(Exchange = "RabbitMQ.EventBus.Simple", RoutingKey = "rabbitmq.eventbus.test")] 8 | public class MessageBody : IEvent 9 | { 10 | public string Body { get; set; } 11 | public DateTimeOffset Time { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /RabbitMQ.EventBus.AspNetCore.Sample/Controllers/MessageBodyHandle.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using RabbitMQ.EventBus.AspNetCore.Events; 3 | using System; 4 | using System.Threading.Tasks; 5 | 6 | namespace RabbitMQ.EventBus.AspNetCore.Simple.Controllers 7 | { 8 | public class MessageBodyHandle : IEventResponseHandler, IDisposable 9 | { 10 | private Guid id; 11 | private readonly ILogger _logger; 12 | 13 | public MessageBodyHandle(ILogger logger) 14 | { 15 | id = Guid.NewGuid(); 16 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 17 | } 18 | public void Dispose() 19 | { 20 | _logger.LogInformation("MessageBodyHandle Disposable."); 21 | } 22 | 23 | 24 | public Task HandleAsync(HandlerEventArgs args) 25 | { 26 | return Task.FromResult("收到消息,已确认" + DateTimeOffset.Now); 27 | } 28 | } 29 | 30 | 31 | } 32 | -------------------------------------------------------------------------------- /RabbitMQ.EventBus.AspNetCore.Sample/Controllers/ValuesController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using System; 3 | using System.Threading.Tasks; 4 | 5 | namespace RabbitMQ.EventBus.AspNetCore.Simple.Controllers 6 | { 7 | [Route("api/[controller]")] 8 | [ApiController] 9 | public class ValuesController : ControllerBase 10 | { 11 | private readonly IRabbitMQEventBus _eventBus; 12 | 13 | public ValuesController(IRabbitMQEventBus eventBus) 14 | { 15 | _eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus)); 16 | } 17 | 18 | // GET api/values 19 | [HttpGet] 20 | public async Task> Get() 21 | { 22 | Console.WriteLine($"发送消息{1}"); 23 | var body = new 24 | { 25 | requestId = Guid.NewGuid(), 26 | Body = $"rabbitmq.eventbus.test=>发送消息\t{1}", 27 | Time = DateTimeOffset.Now, 28 | }; 29 | var r = await _eventBus.PublishAsync(body, exchange: "RabbitMQ.EventBus.Simple", routingKey: "rabbitmq.eventbus.test"); 30 | Console.WriteLine($"返回了{r}"); 31 | await Task.Delay(500); 32 | return r; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /RabbitMQ.EventBus.AspNetCore.Sample/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Hosting; 3 | 4 | namespace RabbitMQ.EventBus.AspNetCore.Simple 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | CreateHostBuilder(args).Build().Run(); 11 | } 12 | 13 | public static IHostBuilder CreateHostBuilder(string[] args) => 14 | Host.CreateDefaultBuilder(args) 15 | .ConfigureWebHostDefaults(webBuilder => 16 | { 17 | webBuilder.UseStartup(); 18 | }); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /RabbitMQ.EventBus.AspNetCore.Sample/RabbitMQ.EventBus.AspNetCore.Sample.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | InProcess 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /RabbitMQ.EventBus.AspNetCore.Sample/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.AspNetCore.Http; 4 | using Microsoft.Extensions.Configuration; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Microsoft.Extensions.Diagnostics.HealthChecks; 7 | using Microsoft.Extensions.Hosting; 8 | using RabbitMQ.EventBus.AspNetCore.Events; 9 | using RabbitMQ.EventBus.AspNetCore.Simple.Controllers; 10 | using System; 11 | using System.Reflection; 12 | 13 | namespace RabbitMQ.EventBus.AspNetCore.Simple 14 | { 15 | public class Startup 16 | { 17 | public Startup(IConfiguration configuration) 18 | { 19 | Configuration = configuration; 20 | } 21 | 22 | public IConfiguration Configuration { get; } 23 | 24 | // This method gets called by the runtime. Use this method to add services to the container. 25 | public void ConfigureServices(IServiceCollection services) 26 | { 27 | string assemblyName = typeof(Startup).GetTypeInfo().Assembly.GetName().Name; 28 | 29 | services.AddControllers(); 30 | services.AddHealthChecks(); 31 | 32 | 33 | //services.AddTransient, MessageBodyHandle>(); 34 | services.AddRabbitMQEventBus("localhost", 5672, "guest", "guest", "", eventBusOptionAction: eventBusOption => 35 | { 36 | eventBusOption.ClientProvidedAssembly(assemblyName); 37 | eventBusOption.EnableRetryOnFailure(true, 5000, TimeSpan.FromSeconds(30)); 38 | eventBusOption.RetryOnFailure(TimeSpan.FromSeconds(1)); 39 | eventBusOption.MessageTTL(2000); 40 | eventBusOption.SetBasicQos(10); 41 | eventBusOption.DeadLetterExchangeConfig(config => 42 | { 43 | config.Enabled = false; 44 | config.ExchangeNameSuffix = "-test"; 45 | }); 46 | }); 47 | //or 48 | // 49 | //services.AddRabbitMQEventBus(() => "amqp://guest:guest@localhost:5672/", eventBusOptionAction: eventBusOption => 50 | //{ 51 | // eventBusOption.ClientProvidedAssembly(assemblyName); 52 | // eventBusOption.EnableRetryOnFailure(true, 5000, TimeSpan.FromSeconds(30)); 53 | // eventBusOption.RetryOnFailure(TimeSpan.FromSeconds(1)); 54 | // eventBusOption.MessageTTL(2000); 55 | // eventBusOption.SetBasicQos(10); 56 | // eventBusOption.DeadLetterExchangeConfig(config => 57 | // { 58 | // config.Enabled = false; 59 | // config.ExchangeNameSuffix = "-test"; 60 | // }); 61 | //}); 62 | } 63 | 64 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 65 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IRabbitMQEventBus rabbitMQ) 66 | { 67 | if (env.IsDevelopment()) 68 | { 69 | app.UseDeveloperExceptionPage(); 70 | } 71 | app.UseRabbitmqEventBus(); 72 | app.UseRouting(); 73 | app.UseEndpoints(endpoints => 74 | { 75 | endpoints.MapControllers(); 76 | endpoints.MapHealthChecks("/hc", new Microsoft.AspNetCore.Diagnostics.HealthChecks.HealthCheckOptions 77 | { 78 | ResultStatusCodes = { 79 | [HealthStatus.Healthy] = StatusCodes.Status200OK, 80 | [HealthStatus.Degraded] = StatusCodes.Status200OK, 81 | [HealthStatus.Unhealthy] = StatusCodes.Status503ServiceUnavailable 82 | } 83 | }); 84 | }); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /RabbitMQ.EventBus.AspNetCore.Sample/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Debug", 6 | "Microsoft": "Debug" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /RabbitMQ.EventBus.AspNetCore.Sample/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug" 5 | } 6 | }, 7 | "AllowedHosts": "*" 8 | } 9 | -------------------------------------------------------------------------------- /RabbitMQ.EventBus.AspNetCore.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.4.33205.214 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{AACDCE8A-FB3B-496A-9ADA-527265C9B334}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RabbitMQ.EventBus.AspNetCore", "src\RabbitMQ.EventBus.AspNetCore\RabbitMQ.EventBus.AspNetCore.csproj", "{392C3FF8-BF1E-4A95-98F9-EAD8EE9C9ACE}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "other", "other", "{1619DC26-508D-4DC7-B9B7-5259D0B90030}" 11 | ProjectSection(SolutionItems) = preProject 12 | LICENSE = LICENSE 13 | README.md = README.md 14 | EndProjectSection 15 | EndProject 16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{F1EBF978-A647-4E88-A8E8-6B1F9381ADF2}" 17 | EndProject 18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RabbitMQ.EventBus.AspNetCore.Sample", "RabbitMQ.EventBus.AspNetCore.Sample\RabbitMQ.EventBus.AspNetCore.Sample.csproj", "{A17039B1-80FF-4EB1-8130-9E13B6ED149E}" 19 | EndProject 20 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{9A1A7C5B-E554-44F8-AF5D-B689AC40BEB2}" 21 | ProjectSection(SolutionItems) = preProject 22 | .github\workflows\dotnetcore.yml = .github\workflows\dotnetcore.yml 23 | EndProjectSection 24 | EndProject 25 | Global 26 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 27 | Debug|Any CPU = Debug|Any CPU 28 | Release|Any CPU = Release|Any CPU 29 | EndGlobalSection 30 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 31 | {392C3FF8-BF1E-4A95-98F9-EAD8EE9C9ACE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 32 | {392C3FF8-BF1E-4A95-98F9-EAD8EE9C9ACE}.Debug|Any CPU.Build.0 = Debug|Any CPU 33 | {392C3FF8-BF1E-4A95-98F9-EAD8EE9C9ACE}.Release|Any CPU.ActiveCfg = Release|Any CPU 34 | {392C3FF8-BF1E-4A95-98F9-EAD8EE9C9ACE}.Release|Any CPU.Build.0 = Release|Any CPU 35 | {A17039B1-80FF-4EB1-8130-9E13B6ED149E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 36 | {A17039B1-80FF-4EB1-8130-9E13B6ED149E}.Debug|Any CPU.Build.0 = Debug|Any CPU 37 | {A17039B1-80FF-4EB1-8130-9E13B6ED149E}.Release|Any CPU.ActiveCfg = Release|Any CPU 38 | {A17039B1-80FF-4EB1-8130-9E13B6ED149E}.Release|Any CPU.Build.0 = Release|Any CPU 39 | EndGlobalSection 40 | GlobalSection(SolutionProperties) = preSolution 41 | HideSolutionNode = FALSE 42 | EndGlobalSection 43 | GlobalSection(NestedProjects) = preSolution 44 | {392C3FF8-BF1E-4A95-98F9-EAD8EE9C9ACE} = {AACDCE8A-FB3B-496A-9ADA-527265C9B334} 45 | {A17039B1-80FF-4EB1-8130-9E13B6ED149E} = {AACDCE8A-FB3B-496A-9ADA-527265C9B334} 46 | EndGlobalSection 47 | GlobalSection(ExtensibilityGlobals) = postSolution 48 | SolutionGuid = {FEAA73D1-EA65-4865-9702-F731CD6EBA26} 49 | EndGlobalSection 50 | EndGlobal 51 | -------------------------------------------------------------------------------- /WIKI.md: -------------------------------------------------------------------------------- 1 | # [RabbitMQ.EventBus.AspNetCore](https://github.com/ojdev/RabbitMQ.EventBus.AspNetCore) 2 | 3 | 该包为一个基于官方RabbitMQ.Client的二次封装包,专门针对Asp.Net Core项目进行开发,在微服务中进行消息的传递使用起来比较方便。 4 | 5 | 目前功能: 6 | 7 | - [x] 发布/订阅 8 | - [x] 死信队列 9 | - [x] RPC功能(实验性) 10 | 11 | ### 使用说明(>=6.0.0) 12 | 13 | #### 1. 注册 14 | ~~~ csharp 15 | public void ConfigureServices(IServiceCollection services) 16 | { 17 | string assemblyName = typeof(Startup).GetTypeInfo().Assembly.GetName().Name; 18 | services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); 19 | services.AddRabbitMQEventBus("localhost", 5672, "guest", "guest", "", eventBusOptionAction: eventBusOption => 20 | { 21 | eventBusOption.ClientProvidedAssembly(assemblyName); 22 | eventBusOption.EnableRetryOnFailure(true, 5000, TimeSpan.FromSeconds(30)); 23 | eventBusOption.RetryOnFailure(TimeSpan.FromSeconds(1)); 24 | eventBusOption.MessageTTL(2000); 25 | eventBusOption.SetBasicQos(10); 26 | eventBusOption.DeadLetterExchangeConfig(config => 27 | { 28 | config.Enabled = false; 29 | config.ExchangeNameSuffix = "-test"; 30 | }); 31 | }); 32 | 33 | //or 34 | // 35 | //services.AddRabbitMQEventBus(() => "amqp://guest:guest@localhost:5672/", eventBusOptionAction: eventBusOption => 36 | //{ 37 | // eventBusOption.ClientProvidedAssembly(assemblyName); 38 | // eventBusOption.EnableRetryOnFailure(true, 5000, TimeSpan.FromSeconds(30)); 39 | // eventBusOption.RetryOnFailure(TimeSpan.FromSeconds(1)); 40 | // eventBusOption.MessageTTL(2000); 41 | // eventBusOption.SetBasicQos(10); 42 | // eventBusOption.DeadLetterExchangeConfig(config => 43 | // { 44 | // config.Enabled = false; 45 | // config.ExchangeNameSuffix = "-test"; 46 | // }); 47 | //}); 48 | } 49 | ~~~ 50 | #### 2. 发消息 51 | ##### 2.1 直接发送消息 52 | ~~~ csharp 53 | [Route("api/[controller]")] 54 | [ApiController] 55 | public class EventBusController : ControllerBase 56 | { 57 | private readonly IRabbitMQEventBus _eventBus; 58 | 59 | public EventBusController(IRabbitMQEventBus eventBus) 60 | { 61 | _eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus)); 62 | } 63 | 64 | // GET api/values 65 | [HttpGet] 66 | public IActionResult Send() 67 | { 68 | _eventBus.Publish(new 69 | { 70 | Body = "发送消息", 71 | Time = DateTimeOffset.Now 72 | }, exchange: "RabbitMQ.EventBus.Simple", routingKey: "rabbitmq.eventbus.test"); 73 | return Ok(); 74 | } 75 | } 76 | ~~~ 77 | ##### 2.1 发送消息并等待回复 78 | ~~~ csharp 79 | [Route("api/[controller]")] 80 | [ApiController] 81 | public class EventBusController : ControllerBase 82 | { 83 | private readonly IRabbitMQEventBus _eventBus; 84 | 85 | public EventBusController(IRabbitMQEventBus eventBus) 86 | { 87 | _eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus)); 88 | } 89 | 90 | // GET api/values 91 | [HttpGet] 92 | public async Task> Get() 93 | { 94 | Console.WriteLine($"发送消息{1}"); 95 | var body = new 96 | { 97 | requestId = Guid.NewGuid(), 98 | Body = $"rabbitmq.eventbus.test=>发送消息\t{1}", 99 | Time = DateTimeOffset.Now, 100 | }; 101 | var r = await _eventBus.PublishAsync(body, exchange: "RabbitMQ.EventBus.Simple", routingKey: "rabbitmq.eventbus.test"); 102 | Console.WriteLine($"返回了{r}"); 103 | await Task.Delay(500); 104 | return r; 105 | } 106 | } 107 | ~~~ 108 | #### 3. 订阅消息 109 | ##### 1. 订阅消息(无回复) 110 | ~~~ csharp 111 | [EventBus(Exchange = "RabbitMQ.EventBus.Simple", RoutingKey = "rabbitmq.eventbus.test")] 112 | [EventBus(Exchange = "RabbitMQ.EventBus.Simple", RoutingKey = "rabbitmq.eventbus.test1")] 113 | public class MessageBody : IEvent 114 | { 115 | public string Body { get; set; } 116 | public DateTimeOffset Time { get; set; } 117 | } 118 | public class MessageBodyHandle : IEventHandler, IDisposable 119 | { 120 | private readonly ILogger _logger; 121 | 122 | public MessageBodyHandle(ILogger logger) 123 | { 124 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 125 | } 126 | 127 | public void Dispose() 128 | { 129 | Console.WriteLine("释放"); 130 | } 131 | 132 | public Task Handle(EventHandlerArgs args) 133 | { 134 | _logger.Information(args.Original); 135 | _logger.Information(args.Redelivered); 136 | _logger.Information(args.Exchange); 137 | _logger.Information(args.RoutingKey); 138 | 139 | _logger.Information(args.Event.Body); 140 | return Task.CompletedTask; 141 | } 142 | } 143 | ~~~ 144 | ##### 1. 订阅消息并回复 145 | ~~~ csharp 146 | [EventBus(Exchange = "RabbitMQ.EventBus.Simple", RoutingKey = "rabbitmq.eventbus.test")] 147 | [EventBus(Exchange = "RabbitMQ.EventBus.Simple", RoutingKey = "rabbitmq.eventbus.test1")] 148 | public class MessageBody : IEvent 149 | { 150 | public string Body { get; set; } 151 | public DateTimeOffset Time { get; set; } 152 | } 153 | public class MessageBodyHandle : IEventResponseHandler, IDisposable 154 | { 155 | private Guid id; 156 | private readonly ILogger _logger; 157 | 158 | public MessageBodyHandle(ILogger logger) 159 | { 160 | id = Guid.NewGuid(); 161 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 162 | } 163 | public void Dispose() 164 | { 165 | _logger.LogInformation("MessageBodyHandle Disposable."); 166 | } 167 | 168 | 169 | public Task HandleAsync(HandlerEventArgs args) 170 | { 171 | return Task.FromResult("收到消息,已确认" + DateTimeOffset.Now); 172 | } 173 | } 174 | ~~~ 175 | 176 | ### 使用说明(<=5.1.1) 177 | 178 | #### 1. 注册 179 | ~~~ csharp 180 | public void ConfigureServices(IServiceCollection services) 181 | { 182 | string assemblyName = typeof(Startup).GetTypeInfo().Assembly.GetName().Name; 183 | services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); 184 | services.AddRabbitMQEventBus(()=>"amqp://guest:guest@192.168.0.252:5672/", eventBusOptionAction: eventBusOption => 185 | { 186 | eventBusOption.ClientProvidedAssembly(assemblyName); 187 | eventBusOption.EnableRetryOnFailure(true, 5000, TimeSpan.FromSeconds(30)); 188 | eventBusOption.RetryOnFailure(TimeSpan.FromMilliseconds(100)); 189 | eventBusOption.AddLogging(LogLevel.Warning); 190 | eventBusOption.MessageTTL(2000); 191 | eventBusOption.DeadLetterExchangeConfig(config => 192 | { 193 | config.Enabled = true; 194 | config.ExchangeNameSuffix = "-test"; 195 | }); 196 | }); 197 | services.AddButterfly(butterfly => 198 | { 199 | butterfly.CollectorUrl = "http://192.168.0.252:6401"; 200 | butterfly.Service = "RabbitMQEventBusTest"; 201 | }); 202 | } 203 | ~~~ 204 | #### 2. 订阅消息 205 | ##### 2.1 自动订阅消息 206 | ~~~ csharp 207 | public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceTracer tracer) 208 | { 209 | if (env.IsDevelopment()) 210 | { 211 | app.UseDeveloperExceptionPage(); 212 | } 213 | app.RabbitMQEventBusAutoSubscribe(); 214 | app.UseMvc(); 215 | } 216 | ~~~ 217 | ##### 2.2 手动订阅消息 218 | ~~~ csharp 219 | public void Configure(IApplicationBuilder app, IHostingEnvironment env, IRabbitMQEventBus eventBus) 220 | { 221 | if (env.IsDevelopment()) 222 | { 223 | app.UseDeveloperExceptionPage(); 224 | } 225 | eventBus.Serialize(); 226 | app.UseMvc(); 227 | } 228 | ~~~ 229 | #### 3. 发消息 230 | ~~~ csharp 231 | [Route("api/[controller]")] 232 | [ApiController] 233 | public class EventBusController : ControllerBase 234 | { 235 | private readonly IRabbitMQEventBus _eventBus; 236 | 237 | public EventBusController(IRabbitMQEventBus eventBus) 238 | { 239 | _eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus)); 240 | } 241 | 242 | // GET api/values 243 | [HttpGet] 244 | public IActionResult Send() 245 | { 246 | _eventBus.Publish(new 247 | { 248 | Body = "发送消息", 249 | Time = DateTimeOffset.Now 250 | }, exchange: "RabbitMQ.EventBus.Simple", routingKey: "rabbitmq.eventbus.test"); 251 | return Ok(); 252 | } 253 | } 254 | ~~~ 255 | #### 4. 订阅消息 256 | ~~~ csharp 257 | [EventBus(Exchange = "RabbitMQ.EventBus.Simple", RoutingKey = "rabbitmq.eventbus.test")] 258 | [EventBus(Exchange = "RabbitMQ.EventBus.Simple", RoutingKey = "rabbitmq.eventbus.test1")] 259 | [EventBus(Exchange = "RabbitMQ.EventBus.Simple", RoutingKey = "rabbitmq.eventbus.test2")] 260 | public class MessageBody : IEvent 261 | { 262 | public string Body { get; set; } 263 | public DateTimeOffset Time { get; set; } 264 | } 265 | public class MessageBodyHandle : IEventHandler, IDisposable 266 | { 267 | private readonly ILogger _logger; 268 | 269 | public MessageBodyHandle(ILogger logger) 270 | { 271 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 272 | } 273 | 274 | public void Dispose() 275 | { 276 | Console.WriteLine("释放"); 277 | } 278 | 279 | public Task Handle(EventHandlerArgs args) 280 | { 281 | _logger.Information(args.Original); 282 | _logger.Information(args.Redelivered); 283 | _logger.Information(args.Exchange); 284 | _logger.Information(args.RoutingKey); 285 | 286 | _logger.Information(args.Event.Body); 287 | return Task.CompletedTask; 288 | } 289 | } 290 | ~~~ -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-midnight -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | ## Welcome to GitHub Pages 2 | 3 | You can use the [editor on GitHub](https://github.com/ojdev/RabbitMQ.EventBus.AspNetCore/edit/master/docs/index.md) to maintain and preview the content for your website in Markdown files. 4 | 5 | Whenever you commit to this repository, GitHub Pages will run [Jekyll](https://jekyllrb.com/) to rebuild the pages in your site, from the content in your Markdown files. 6 | 7 | ### Markdown 8 | 9 | Markdown is a lightweight and easy-to-use syntax for styling your writing. It includes conventions for 10 | 11 | ```markdown 12 | Syntax highlighted code block 13 | 14 | # Header 1 15 | ## Header 2 16 | ### Header 3 17 | 18 | - Bulleted 19 | - List 20 | 21 | 1. Numbered 22 | 2. List 23 | 24 | **Bold** and _Italic_ and `Code` text 25 | 26 | [Link](url) and ![Image](src) 27 | ``` 28 | 29 | For more details see [GitHub Flavored Markdown](https://guides.github.com/features/mastering-markdown/). 30 | 31 | ### Jekyll Themes 32 | 33 | Your Pages site will use the layout and styles from the Jekyll theme you have selected in your [repository settings](https://github.com/ojdev/RabbitMQ.EventBus.AspNetCore/settings/pages). The name of this theme is saved in the Jekyll `_config.yml` configuration file. 34 | 35 | ### Support or Contact 36 | 37 | Having trouble with Pages? Check out our [documentation](https://docs.github.com/categories/github-pages-basics/) or [contact support](https://support.github.com/contact) and we’ll help you sort it out. 38 | -------------------------------------------------------------------------------- /src/RabbitMQ.EventBus.AspNetCore/Attributes/EventBusAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace RabbitMQ.EventBus.AspNetCore.Attributes; 2 | /// 3 | /// 4 | /// 5 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] 6 | public class EventBusAttribute : Attribute 7 | { 8 | /// 9 | /// 队列名 10 | /// 11 | public string Queue { get; set; } 12 | /// 13 | /// 交换机名 14 | /// 15 | public string Exchange { get; set; } 16 | /// 17 | /// 路由Key 18 | /// 19 | public string RoutingKey { get; set; } 20 | /// 21 | /// Configures QoS parameters of the Basic content-class. 22 | /// 23 | public ushort? BasicQos { get; set; } 24 | /// 25 | /// 26 | /// 27 | public EventBusAttribute() 28 | { 29 | RoutingKey = ""; 30 | } 31 | } -------------------------------------------------------------------------------- /src/RabbitMQ.EventBus.AspNetCore/Configurations/DeadLetterExchangeConfig.cs: -------------------------------------------------------------------------------- 1 | namespace RabbitMQ.EventBus.AspNetCore.Configurations; 2 | /// 3 | /// 死信交换机 4 | /// 5 | public class DeadLetterExchangeConfig 6 | { 7 | /// 8 | /// 是否开启(默认开启) 9 | /// 10 | public bool Enabled { set; get; } 11 | /// 12 | /// 交换机名前缀(默认为"dead-") 13 | /// 14 | public string ExchangeNamePrefix { set; get; } 15 | /// 16 | /// 交换机名后缀 17 | /// 18 | public string ExchangeNameSuffix { set; get; } 19 | /// 20 | /// 自定义交换机名(留空则使用原有的交换机名) 21 | /// 22 | public string CustomizeExchangeName { set; get; } 23 | /// 24 | /// 25 | /// 26 | /// 是否开启(默认关闭) 27 | /// 交换机名前缀(默认为"dead-") 28 | /// 交换机名后缀 29 | /// 自定义交换机名(留空则使用原有的交换机名) 30 | public DeadLetterExchangeConfig(bool enabled = false, string exchangeNamePrefix = "dead-", string exchangeNameSuffix = null, string customizeExchangeName = null) 31 | { 32 | Enabled = enabled; 33 | ExchangeNameSuffix = exchangeNameSuffix; 34 | ExchangeNamePrefix = exchangeNamePrefix; 35 | CustomizeExchangeName = customizeExchangeName; 36 | } 37 | } -------------------------------------------------------------------------------- /src/RabbitMQ.EventBus.AspNetCore/Configurations/QueuePrefixType.cs: -------------------------------------------------------------------------------- 1 | namespace RabbitMQ.EventBus.AspNetCore.Configurations; 2 | /// 3 | /// 队列名前缀 4 | /// 5 | public enum QueuePrefixType 6 | { 7 | /// 8 | /// 交换机名 9 | /// 10 | ExchangeName, 11 | /// 12 | /// 13 | /// 14 | ClientProvidedName 15 | } -------------------------------------------------------------------------------- /src/RabbitMQ.EventBus.AspNetCore/Configurations/RabbitMQEventBusConnectionConfiguration.cs: -------------------------------------------------------------------------------- 1 | namespace RabbitMQ.EventBus.AspNetCore.Configurations; 2 | /// 3 | /// 4 | /// 5 | public sealed class RabbitMQEventBusConnectionConfiguration 6 | { 7 | private int? messageTTL; 8 | 9 | /// 10 | /// 11 | /// 12 | public string ClientProvidedName { get; set; } 13 | /// 14 | /// 连接出现错误后重试连接的次数(默认:50) 15 | /// 16 | public int FailReConnectRetryCount { get; set; } 17 | /// 18 | /// 是否开启网络自动恢复(默认开启) 19 | /// 20 | public bool AutomaticRecoveryEnabled { get; set; } 21 | /// 22 | /// 网络自动恢复时间间隔(默认5秒) 23 | /// 24 | public TimeSpan NetworkRecoveryInterval { get; set; } 25 | /// 26 | /// 消息消费失败的重试时间间隔(默认1秒) 27 | /// 28 | public TimeSpan ConsumerFailRetryInterval { get; set; } 29 | /// 30 | /// 31 | /// 32 | public LogLevel Level { get; set; } 33 | /// 34 | /// 每次预取的消息条数(默认:1) 35 | /// 36 | public ushort PrefetchCount { get; set; } 37 | /// 38 | /// 队列名前缀(默认ClientProvidedName) 39 | /// 40 | public QueuePrefixType Prefix { get; set; } 41 | /// 42 | /// 死信交换机设置 43 | /// 44 | public DeadLetterExchangeConfig DeadLetterExchange { set; get; } 45 | /// 46 | /// 消息驻留时长(毫秒),超过此市场的则判断为死信 47 | /// 48 | public int? MessageTTL 49 | { 50 | get => messageTTL; 51 | set 52 | { 53 | if (value < 0) 54 | { 55 | throw new ArgumentOutOfRangeException($"{nameof(MessageTTL)}必须大于0"); 56 | } 57 | messageTTL = value; 58 | } 59 | } 60 | /// 61 | /// 62 | /// 63 | public RabbitMQEventBusConnectionConfiguration() 64 | { 65 | Level = LogLevel.Information; 66 | FailReConnectRetryCount = 50; 67 | NetworkRecoveryInterval = TimeSpan.FromSeconds(5); 68 | AutomaticRecoveryEnabled = true; 69 | ConsumerFailRetryInterval = TimeSpan.FromSeconds(1); 70 | DeadLetterExchange = new DeadLetterExchangeConfig(); 71 | PrefetchCount = 1; 72 | } 73 | } -------------------------------------------------------------------------------- /src/RabbitMQ.EventBus.AspNetCore/Configurations/RabbitMQEventBusConnectionConfigurationBuild.cs: -------------------------------------------------------------------------------- 1 | namespace RabbitMQ.EventBus.AspNetCore.Configurations; 2 | /// 3 | /// 4 | /// 5 | public class RabbitMQEventBusConnectionConfigurationBuild 6 | { 7 | private RabbitMQEventBusConnectionConfiguration Configuration { get; } 8 | /// 9 | /// 10 | /// 11 | /// 12 | public RabbitMQEventBusConnectionConfigurationBuild(RabbitMQEventBusConnectionConfiguration configuration) 13 | { 14 | Configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); 15 | } 16 | 17 | /// 18 | /// 设置客户端名称 19 | /// 20 | /// 21 | public void ClientProvidedAssembly(string assembly) 22 | { 23 | Configuration.ClientProvidedName = assembly; 24 | } 25 | /// 26 | /// 网络恢复配置 27 | /// 28 | /// 是否开启网络自动恢复(默认:true) 29 | /// 连接出现错误后重试连接的指数次数(默认:50) 30 | /// 网络自动恢复时间间隔(默认5秒) 31 | public void EnableRetryOnFailure(bool automaticRecovery, int maxRetryCount, TimeSpan maxRetryDelay) 32 | { 33 | Configuration.AutomaticRecoveryEnabled = automaticRecovery; 34 | Configuration.FailReConnectRetryCount = maxRetryCount; 35 | Configuration.NetworkRecoveryInterval = maxRetryDelay; 36 | } 37 | /// 38 | /// 重试错误的消息 39 | /// 40 | /// 消息失败的重试时间间隔(默认1秒) 41 | public void RetryOnFailure(TimeSpan maxRetryDelay) 42 | { 43 | Configuration.ConsumerFailRetryInterval = maxRetryDelay; 44 | } 45 | /// 46 | /// 设置日志输出级别 47 | /// 48 | /// 日志级别 49 | public void LoggingWriteLevel(LogLevel level) 50 | { 51 | Configuration.Level = level; 52 | } 53 | /// 54 | /// 队列名前缀 55 | /// 56 | /// 57 | public void QueuePrefix(QueuePrefixType queuePrefix = QueuePrefixType.ClientProvidedName) 58 | { 59 | Configuration.Prefix = queuePrefix; 60 | } 61 | /// 62 | /// 设置消息的驻留时常(毫秒) 63 | /// 如果开启了死信队列设置则默认为60000毫秒 64 | /// 65 | /// 66 | public void MessageTTL(int millisecond) 67 | { 68 | Configuration.MessageTTL = millisecond; 69 | } 70 | /// 71 | /// 死信队列设置 72 | /// 73 | /// 74 | public void DeadLetterExchangeConfig(Action config) 75 | { 76 | config?.Invoke(Configuration.DeadLetterExchange); 77 | if (Configuration.DeadLetterExchange.Enabled && Configuration.MessageTTL == null) 78 | { 79 | Configuration.MessageTTL = 60000; 80 | } 81 | } 82 | /// 83 | /// 设置预取条数 84 | /// 85 | /// 86 | public void SetBasicQos(ushort prefetchCount) 87 | { 88 | if (prefetchCount < 1) 89 | { 90 | throw new ArgumentOutOfRangeException($"{nameof(prefetchCount)}必须大于0"); 91 | } 92 | Configuration.PrefetchCount = prefetchCount; 93 | } 94 | } -------------------------------------------------------------------------------- /src/RabbitMQ.EventBus.AspNetCore/DefaultRabbitMQEventBusV2.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | 3 | namespace RabbitMQ.EventBus.AspNetCore; 4 | 5 | internal class DefaultRabbitMQEventBusV2 : IRabbitMQEventBus 6 | { 7 | #region singleton 8 | private static DefaultRabbitMQEventBusV2 Instance; 9 | private static readonly object singleton_Lock = new(); 10 | public static DefaultRabbitMQEventBusV2 CreateInstance(IRabbitMQPersistentConnection persistentConnection, IServiceProvider serviceProvider, ILogger logger) 11 | { 12 | lock (singleton_Lock) // 保证任意时刻只有一个线程才能进入判断 13 | { 14 | if (Instance == null) 15 | { 16 | Instance = new(persistentConnection, serviceProvider, logger); 17 | } 18 | } 19 | return Instance; 20 | } 21 | #endregion 22 | private readonly IRabbitMQPersistentConnection _persistentConnection; 23 | private readonly ILogger _logger; 24 | private readonly IServiceProvider _serviceProvider; 25 | private readonly ConcurrentDictionary> callbackMapper = new(); 26 | private readonly Dictionary subscribes; 27 | private readonly string GlaobalExchangeName; 28 | private DefaultRabbitMQEventBusV2(IRabbitMQPersistentConnection persistentConnection, IServiceProvider serviceProvider, ILogger logger) 29 | { 30 | _persistentConnection = persistentConnection ?? throw new ArgumentNullException(nameof(persistentConnection)); 31 | _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); 32 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 33 | subscribes = new Dictionary(); 34 | GlaobalExchangeName = _persistentConnection.Configuration.ClientProvidedName; 35 | _logger.LogInformation("消息队列准备就绪"); 36 | } 37 | #region Publish 38 | public async Task PublishAsync(TMessage message, string exchange, string routingKey, byte deliveryMode = 2, string type = "topic", CancellationToken cancellationToken = default) 39 | { 40 | var channel = _persistentConnection.ExchangeDeclare(exchange, type: type); 41 | channel.ModelShutdown += (object sender, ShutdownEventArgs e) => 42 | { 43 | _logger.LogDebug($"channel shotdown.\t{e.ReplyText}"); 44 | }; 45 | IBasicProperties properties = channel.CreateBasicProperties(); 46 | properties.DeliveryMode = deliveryMode; // persistent 47 | await RetryPolicyPublish(channel, properties, exchange, routingKey, message?.Serialize(), cancellationToken); 48 | } 49 | 50 | public Task PublishAsync(TMessage message, string exchange, string routingKey, byte deliveryMode = 2, string type = "topic", Action errorHandler = null, CancellationToken cancellationToken = default) where TMessage : class 51 | => PublishAsync(message, exchange, routingKey, deliveryMode, type, errorHandler, cancellationToken); 52 | 53 | public async Task PublishAsync(object message, string exchange, string routingKey, byte deliveryMode = 2, string type = ExchangeType.Topic, Action errorHandler = null, CancellationToken cancellationToken = default) 54 | { 55 | var response = await PublishAsync(message.Serialize(), exchange, routingKey, deliveryMode, type, errorHandler, cancellationToken); 56 | if (response.IsNullOrWhiteSpace()) return default; 57 | return response.Deserialize(); 58 | } 59 | 60 | public async Task PublishAsync(string message, string exchange, string routingKey, byte deliveryMode = 2, string type = "topic", Action errorHandler = null, CancellationToken cancellationToken = default) 61 | { 62 | var channel = _persistentConnection.ExchangeDeclare(exchange, type: type); 63 | channel.ModelShutdown += (object sender, ShutdownEventArgs e) => 64 | { 65 | _logger.LogDebug($"channel shotdown.\t{e.ReplyText}"); 66 | }; 67 | var reply = ReplyWaitConfirm(channel, exchange, routingKey); 68 | IBasicProperties properties = channel.CreateBasicProperties(); 69 | properties.DeliveryMode = deliveryMode; // persistent 70 | var correlationId = Guid.NewGuid().ToString(); 71 | properties.CorrelationId = correlationId; 72 | properties.ReplyTo = reply.routingKey; 73 | var tcs = new TaskCompletionSource(); 74 | callbackMapper.TryAdd(correlationId, tcs); 75 | await RetryPolicyPublish(channel, properties, exchange, routingKey, message, cancellationToken); 76 | var result = await tcs.Task; 77 | _logger.LogDebug($"delete message queue {reply.queueName}"); 78 | channel.QueueDelete(reply.queueName); 79 | _logger.LogDebug($"delete message queue {reply.queueName} done."); 80 | return result; 81 | } 82 | #endregion 83 | #region Old Version Publish 84 | public void Publish(TMessage message, string exchange, string routingKey, byte deliveryMode = 2, string type = "topic") 85 | => Publish(message?.Serialize(), exchange, routingKey, deliveryMode, type); 86 | 87 | public void Publish(string message, string exchange, string routingKey, byte deliveryMode = 2, string type = "topic") 88 | { 89 | var _publishChannel = _persistentConnection.ExchangeDeclare(exchange, type: type); 90 | IBasicProperties properties = _publishChannel.CreateBasicProperties(); 91 | properties.DeliveryMode = deliveryMode; // persistent 92 | _publishChannel.BasicPublish(properties, exchange, routingKey, message); 93 | } 94 | #endregion 95 | #region Subscribe 96 | public void Subscribe(Type eventType, string type = "topic") 97 | { 98 | var attributes = eventType.GetCustomAttributes(typeof(EventBusAttribute), true); 99 | var millisecondsDelay = (int?)_persistentConnection?.Configuration?.ConsumerFailRetryInterval.TotalMilliseconds ?? 1000; 100 | foreach (var attribute in attributes) 101 | { 102 | if (attribute is EventBusAttribute attr) 103 | { 104 | string queue = attr.Queue ?? (_persistentConnection.Configuration.Prefix == QueuePrefixType.ExchangeName 105 | ? $"{attr.Exchange}.{eventType.Name}" 106 | : (eventType.FullName ?? $"{GlaobalExchangeName}.{eventType.Name}")); 107 | 108 | var onlyKey = $"{attr.Exchange}_{queue}_{attr.RoutingKey}"; 109 | if (!subscribes.TryGetValue(onlyKey, out IModel channel)) 110 | { 111 | channel = _persistentConnection.QueueDeclareBind(attr.Exchange, queue, attr.RoutingKey); 112 | } 113 | channel.QueueBind(queue, attr.Exchange, attr.RoutingKey, null); 114 | channel.BasicQos(0, attr.BasicQos ?? _persistentConnection.Configuration.PrefetchCount, false); 115 | subscribes[onlyKey] = channel; 116 | EventingBasicConsumer consumer = new(channel); 117 | consumer.Received += async (model, ea) => 118 | { 119 | string body = Encoding.UTF8.GetString(ea.Body.ToArray()); 120 | var props = ea.BasicProperties; 121 | var replyProps = channel.CreateBasicProperties(); 122 | replyProps.CorrelationId = props.CorrelationId; 123 | 124 | bool isAck = false; 125 | try 126 | { 127 | await ProcessEventAsync(body, eventType, ea); 128 | //不确定是否需要改变Multiple是否需要改为true 129 | channel.BasicAck(ea.DeliveryTag, multiple: false); 130 | isAck = true; 131 | } 132 | catch (Exception ex) 133 | { 134 | _logger.LogError(new EventId(ex.HResult), ex, ex.Message); 135 | } 136 | finally 137 | { 138 | _logger.LogInformation($"{DateTimeOffset.Now:yyyy-MM-dd HH:mm:ss}\t{isAck}\t{ea.Exchange}\t{ea.RoutingKey}\t{body}"); 139 | if (!isAck) 140 | { 141 | await Task.Delay(millisecondsDelay); 142 | channel.BasicNack(ea.DeliveryTag, false, true); 143 | } 144 | } 145 | }; 146 | channel.CallbackException += (sender, ex) => 147 | { 148 | _logger.LogError(new EventId(ex.Exception.HResult), ex.Exception, ex.Exception.Message); 149 | }; 150 | channel.BasicConsume(queue: queue, autoAck: false, consumer: consumer); 151 | } 152 | } 153 | } 154 | public void Subscribe(Type eventType, Type responseType, string type = "topic") 155 | { 156 | var attributes = eventType.GetCustomAttributes(typeof(EventBusAttribute), true); 157 | var millisecondsDelay = (int?)_persistentConnection?.Configuration?.ConsumerFailRetryInterval.TotalMilliseconds ?? 1000; 158 | foreach (var attribute in attributes) 159 | { 160 | if (attribute is EventBusAttribute attr) 161 | { 162 | string queue = attr.Queue ?? (_persistentConnection.Configuration.Prefix == QueuePrefixType.ExchangeName 163 | ? $"{attr.Exchange}.{eventType.Name}" 164 | : (eventType.FullName ?? $"{GlaobalExchangeName}.{eventType.Name}")); 165 | 166 | var onlyKey = $"{attr.Exchange}_{queue}_{attr.RoutingKey}"; 167 | _logger.LogWarning($"onlyKey => {onlyKey}"); 168 | if (!subscribes.TryGetValue(onlyKey, out IModel channel)) 169 | { 170 | channel = _persistentConnection.QueueDeclareBind(attr.Exchange, queue, attr.RoutingKey); 171 | } 172 | channel.QueueBind(queue, attr.Exchange, attr.RoutingKey, null); 173 | channel.BasicQos(0, attr.BasicQos ?? _persistentConnection.Configuration.PrefetchCount, false); 174 | subscribes[onlyKey] = channel; 175 | EventingBasicConsumer consumer = new(channel); 176 | consumer.Shutdown += (s, e) => _logger.LogError($"{eventType}\t{responseType}\tConsumer Shutdown.\t{consumer.ShutdownReason}\t{e.ReplyText}"); 177 | consumer.Unregistered += (s, e) => _logger.LogError($"{eventType}\t{responseType}\tConsumer Unregistered."); 178 | consumer.ConsumerCancelled += (s, e) => _logger.LogError($"{eventType}\t{responseType}\tConsumer ConsumerCancelled."); 179 | consumer.Received += async (model, ea) => 180 | { 181 | string body = Encoding.UTF8.GetString(ea.Body.ToArray()); 182 | _logger.LogDebug($"received message: {body}"); 183 | var props = ea.BasicProperties; 184 | var replyProps = channel.CreateBasicProperties(); 185 | replyProps.CorrelationId = props.CorrelationId; 186 | bool isAck = false; 187 | try 188 | { 189 | _logger.LogDebug($"received process event start."); 190 | var response = await ProcessEventAsync(body, eventType, responseType, ea); 191 | _logger.LogDebug($"received process event end."); 192 | isAck = true; 193 | if (!string.IsNullOrEmpty(replyProps.CorrelationId)) 194 | { 195 | _logger.LogDebug($"reply message.{attr.Exchange} {props.ReplyTo} {response}"); 196 | channel.ConfirmSelect(); 197 | channel.BasicPublish(exchange: attr.Exchange, routingKey: props.ReplyTo, mandatory: true, basicProperties: replyProps, body: response.GetBytes()); 198 | var replyIsOk = channel.WaitForConfirms(); 199 | _logger.LogDebug($"reply message confirm {replyIsOk}."); 200 | } 201 | channel.BasicAck(deliveryTag: ea.DeliveryTag, multiple: false); 202 | await Task.Yield(); 203 | } 204 | catch (Exception ex) 205 | { 206 | _logger.LogError(new EventId(ex.HResult), ex, ex.Message); 207 | } 208 | finally 209 | { 210 | _logger.LogInformation($"{DateTimeOffset.Now:yyyy-MM-dd HH:mm:ss}\t{isAck}\t{ea.Exchange}\t{ea.RoutingKey}\t{body}"); 211 | if (!isAck) 212 | { 213 | await Task.Delay(millisecondsDelay); 214 | channel.BasicNack(ea.DeliveryTag, false, true); 215 | } 216 | } 217 | }; 218 | channel.CallbackException += (sender, ex) => 219 | { 220 | _logger.LogError(new EventId(ex.Exception.HResult), ex.Exception, ex.Exception.Message); 221 | }; 222 | var consumerTag = channel.BasicConsume(queue: queue, autoAck: false, consumer: consumer); 223 | _logger.LogInformation($"consumer bind\t{consumerTag}"); 224 | } 225 | } 226 | } 227 | /// 228 | /// 消息发布的回复通知订阅 229 | /// 230 | /// 231 | private (string queueName, string routingKey) ReplyWaitConfirm(IModel channel, string exchange, string routingKey) 232 | { 233 | var timespanStr = $"{DateTimeOffset.Now.ToUnixTimeSeconds()}"; 234 | var replyQueueName = $"{exchange}_{timespanStr}_ReplyQueue"; 235 | routingKey = $"{routingKey}_{timespanStr}_reply"; 236 | var qdOk = channel.QueueDeclare(queue: replyQueueName, 237 | durable: true, 238 | exclusive: false, 239 | autoDelete: false); 240 | channel.QueueBind(replyQueueName, exchange, routingKey, null); 241 | var replyConsumer = new EventingBasicConsumer(channel); 242 | replyConsumer.Received += (model, ea) => 243 | { 244 | if (!callbackMapper.TryRemove(ea.BasicProperties.CorrelationId, out TaskCompletionSource tcs)) 245 | return; 246 | var response = Encoding.UTF8.GetString(ea.Body.ToArray()); 247 | tcs.TrySetResult(response); 248 | }; 249 | var consumeResult = channel.BasicConsume(consumer: replyConsumer, queue: replyQueueName, autoAck: true); 250 | return (qdOk.QueueName, routingKey); 251 | } 252 | 253 | #endregion 254 | 255 | private async Task ProcessEventAsync(string body, Type eventType, Type responseType, BasicDeliverEventArgs args) 256 | { 257 | Type eventHandlerType = typeof(IEventResponseHandler<,>).MakeGenericType(eventType, responseType); 258 | dynamic eventHandler = _serviceProvider.GetRequiredService(eventHandlerType); 259 | if (eventHandler == null) 260 | { 261 | throw new InvalidOperationException(eventHandler.GetType().Name); 262 | } 263 | Type concreteType = eventHandlerType; 264 | var r = (object)await concreteType.GetMethod("HandleAsync").Invoke( 265 | eventHandler, 266 | new object[] { 267 | Activator.CreateInstance(typeof(HandlerEventArgs<>).MakeGenericType(eventType), new object[] { body, args }) 268 | }); 269 | _logger.LogInformation($"ProcessEventAsync :{r?.Serialize()}"); 270 | return r?.Serialize(); 271 | } 272 | /// 273 | /// 274 | /// 275 | /// 276 | /// 277 | /// 278 | /// 279 | private async Task ProcessEventAsync(string body, Type eventType, BasicDeliverEventArgs args) 280 | { 281 | //Type eventHandlerType = typeof(IEventHandler<>).MakeGenericType(eventType); 282 | //dynamic eventHandler = _serviceProvider.GetRequiredService(eventHandlerType); 283 | //if (eventHandler == null) 284 | //{ 285 | // throw new InvalidOperationException(eventHandler.GetType().Name); 286 | //} 287 | ////IEventHandler 288 | 289 | //object logger = _serviceProvider.GetRequiredService(typeof(ILogger<>).MakeGenericType(eventType)); 290 | //Type concreteType = eventHandlerType.MakeGenericType(eventType); 291 | //await (Task)concreteType.GetMethod(nameof(IEventHandler.Handle)).Invoke( 292 | // eventHandler, 293 | // new object[] { 294 | // Activator.CreateInstance(typeof(HandlerEventArgs<>).MakeGenericType(eventType), new object[] { body, args.Redelivered, args.Exchange, args.RoutingKey, logger }) 295 | // }); 296 | 297 | 298 | using var scope = _serviceProvider.CreateScope(); 299 | foreach (Type eventHandleType in typeof(IEventHandler<>).GetMakeGenericType(eventType)) 300 | { 301 | object eventHandler = scope.ServiceProvider.GetRequiredService(eventHandleType); 302 | object logger = scope.ServiceProvider.GetRequiredService(typeof(ILogger<>).MakeGenericType(eventType)); 303 | if (eventHandler == null) 304 | { 305 | throw new InvalidOperationException(eventHandleType.Name); 306 | } 307 | Type concreteType = typeof(IEventHandler<>).MakeGenericType(eventType); 308 | await (Task)concreteType.GetMethod(nameof(IEventHandler.Handle)).Invoke( 309 | eventHandler, 310 | new object[] { 311 | Activator.CreateInstance(typeof(EventHandlerArgs<>).MakeGenericType(eventType), new object[] { body, args.Redelivered, args.Exchange, args.RoutingKey, logger }) 312 | }); 313 | } 314 | } 315 | /// 316 | /// 指数重试机制的发消息机制 317 | /// 318 | /// 319 | /// 320 | /// 321 | /// 322 | /// 323 | /// 324 | /// 325 | private async Task RetryPolicyPublish(IModel channel, IBasicProperties properties, string exchange, string routingKey, string messageBody, CancellationToken cancellationToken) 326 | { 327 | var policy = Policy 328 | .HandleResult(r => !r) 329 | .WaitAndRetryAsync( 330 | 5, 331 | retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), 332 | (exception, timeSpan, context) => 333 | { 334 | _logger.LogError($"Publish failed! retry after {timeSpan.TotalSeconds} seconds..."); 335 | } 336 | ); 337 | await policy.ExecuteAsync(async () => 338 | { 339 | var isOk = channel.BasicPublish(properties, exchange, routingKey, messageBody); 340 | if (isOk) 341 | { 342 | _logger.LogInformation($"Published successfully.\t{DateTimeOffset.Now:yyyy-MM-dd HH:mm:ss}\t{exchange}\t{routingKey}\t{messageBody}"); 343 | if (string.IsNullOrEmpty(properties.CorrelationId)) 344 | cancellationToken.Register(() => callbackMapper.TryRemove(properties.CorrelationId, out var tmp)); 345 | } 346 | else 347 | _logger.LogError($"Publish failed!\t{DateTimeOffset.Now:yyyy-MM-dd HH:mm:ss}\t{exchange}\t{routingKey}\t{messageBody}"); 348 | return await Task.FromResult(isOk); 349 | }); 350 | } 351 | 352 | } 353 | -------------------------------------------------------------------------------- /src/RabbitMQ.EventBus.AspNetCore/Events/IEvent.cs: -------------------------------------------------------------------------------- 1 | namespace RabbitMQ.EventBus.AspNetCore.Events; 2 | /// 3 | /// 消息接口 4 | /// 5 | public interface IEvent 6 | { 7 | } 8 | -------------------------------------------------------------------------------- /src/RabbitMQ.EventBus.AspNetCore/Events/V1/EventHandlerArgs.cs: -------------------------------------------------------------------------------- 1 | namespace RabbitMQ.EventBus.AspNetCore.Events; 2 | /// 3 | /// 4 | /// 5 | public class EventHandlerArgs 6 | { 7 | private readonly ILogger _logger; 8 | /// 9 | /// 原始消息 10 | /// 11 | public string Original { get; } 12 | /// 13 | /// 是否为打回的消息 14 | /// 15 | public bool Redelivered { get; } 16 | /// 17 | /// 交换机 18 | /// 19 | public string Exchange { get; } 20 | /// 21 | /// 路由key 22 | /// 23 | public string RoutingKey { get; } 24 | /// 25 | /// 26 | /// 27 | /// 28 | /// 29 | /// 30 | /// 31 | /// 32 | public EventHandlerArgs(string original, bool redelivered, string exchange, string routingKey, ILogger logger = null) 33 | { 34 | Original = original; 35 | Redelivered = redelivered; 36 | Exchange = exchange; 37 | RoutingKey = routingKey; 38 | _logger = logger; 39 | } 40 | private TEvent _event; 41 | /// 42 | /// 序列化后的对象 43 | /// 44 | public TEvent Event 45 | { 46 | get 47 | { 48 | if (_event == null) 49 | { 50 | _event = Original.Deserialize(); 51 | } 52 | return _event; 53 | } 54 | } 55 | 56 | } -------------------------------------------------------------------------------- /src/RabbitMQ.EventBus.AspNetCore/Events/V1/IEventHandler.cs: -------------------------------------------------------------------------------- 1 | namespace RabbitMQ.EventBus.AspNetCore.Events; 2 | /// 3 | /// EventBus消息处理 4 | /// 5 | /// 6 | public interface IEventHandler where TEvent : IEvent 7 | { 8 | /// 9 | /// 10 | /// 11 | /// 12 | /// 13 | Task Handle(EventHandlerArgs args); 14 | } 15 | -------------------------------------------------------------------------------- /src/RabbitMQ.EventBus.AspNetCore/Events/V2/EventHandlerArgs.cs: -------------------------------------------------------------------------------- 1 | namespace RabbitMQ.EventBus.AspNetCore.Events; 2 | 3 | public class HandlerEventArgs 4 | { 5 | /// 6 | /// Contains all the information about a message delivered from an AMQP broker within
7 | /// the Basic content-class. 8 | ///
9 | public BasicDeliverEventArgs EventArgs { get; } 10 | /// 11 | /// 原始消息 12 | /// 13 | public string Original { get; } 14 | private TEvent _event; 15 | /// 16 | /// 序列化后的对象 17 | /// 18 | public TEvent EventObject 19 | { 20 | get 21 | { 22 | if (_event == null) 23 | { 24 | _event = Original.Deserialize(); 25 | } 26 | return _event; 27 | } 28 | } 29 | 30 | public HandlerEventArgs(string original, BasicDeliverEventArgs eventArgs) 31 | { 32 | Original = original ?? throw new ArgumentNullException(nameof(original)); 33 | EventArgs = eventArgs ?? throw new ArgumentNullException(nameof(eventArgs)); 34 | } 35 | } -------------------------------------------------------------------------------- /src/RabbitMQ.EventBus.AspNetCore/Events/V2/IEventResponseHandler.cs: -------------------------------------------------------------------------------- 1 | namespace RabbitMQ.EventBus.AspNetCore.Events; 2 | 3 | public interface IEventResponseHandler where TEvent : IEvent 4 | { 5 | /// 6 | /// 7 | /// 8 | /// 9 | /// 10 | Task HandleAsync(HandlerEventArgs args); 11 | } -------------------------------------------------------------------------------- /src/RabbitMQ.EventBus.AspNetCore/Extensions/DynamicExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace RabbitMQ.EventBus.AspNetCore.Extensions; 2 | 3 | /// 4 | /// 5 | /// 6 | internal static class DynamicExtensions 7 | { 8 | /// 9 | /// 10 | /// 11 | /// 12 | /// 13 | /// 14 | public static string Serialize(this TMessage message) => JsonSerializer.Serialize(message, new JsonSerializerOptions 15 | { 16 | PropertyNameCaseInsensitive = true, 17 | Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, 18 | NumberHandling = JsonNumberHandling.AllowReadingFromString 19 | }); 20 | /// 21 | /// 22 | /// 23 | /// 24 | /// 25 | public static byte[] GetBytes(this string body) 26 | { 27 | return Encoding.UTF8.GetBytes(body); 28 | } 29 | /// 30 | /// 31 | /// 32 | /// 33 | /// 34 | /// 35 | public static TResponse Deserialize(this string message) 36 | { 37 | if (!string.IsNullOrWhiteSpace(message)) 38 | { 39 | try 40 | { 41 | return JsonSerializer.Deserialize(message, new JsonSerializerOptions 42 | { 43 | PropertyNameCaseInsensitive = true, 44 | Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, 45 | NumberHandling = JsonNumberHandling.AllowReadingFromString 46 | }); 47 | } 48 | catch 49 | { 50 | try 51 | { 52 | return (TResponse)TypeDescriptor.GetConverter(typeof(TResponse)).ConvertFromInvariantString(message); 53 | } 54 | catch (Exception) 55 | { 56 | throw; 57 | } 58 | } 59 | } 60 | return default; 61 | } 62 | } -------------------------------------------------------------------------------- /src/RabbitMQ.EventBus.AspNetCore/Extensions/IModelExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace RabbitMQ.EventBus.AspNetCore.Extensions; 2 | /// 3 | /// 4 | /// 5 | internal static class IModelExtensions 6 | { 7 | /// 8 | /// Do a passive exchange declaration. 9 | /// Or 10 | /// (Spec method) Declare an exchange. 11 | /// This method performs a "passive declare" on an exchange, which verifies whether. It will do nothing if the exchange already exists and result in a channel-levelprotocol exception (channel closure) if not. 12 | /// 13 | /// 14 | /// 15 | /// 16 | /// 17 | /// 18 | /// 19 | public static IModel ExchangeDeclare(this IRabbitMQPersistentConnection persistentConnection, string exchange, string type = ExchangeType.Topic, bool durable = true, bool autoDelete = false, IDictionary arguments = null) 20 | { 21 | IModel channel; 22 | try 23 | { 24 | channel = persistentConnection.CreateModel(); 25 | channel.ExchangeDeclarePassive(exchange); 26 | } 27 | catch 28 | { 29 | channel = persistentConnection.CreateModel(); 30 | channel.ExchangeDeclare(exchange, type, durable, autoDelete, arguments); 31 | } 32 | return channel; 33 | } 34 | public static IModel QueueDeclareBind(this IRabbitMQPersistentConnection persistentConnection, string exchange, string queue, string routingKey) 35 | { 36 | IModel channel; 37 | try 38 | { 39 | channel = persistentConnection.ExchangeDeclare(exchange: exchange); 40 | channel.QueueDeclarePassive(queue); 41 | } 42 | catch 43 | { 44 | channel = persistentConnection.ExchangeDeclare(exchange: exchange); 45 | channel.QueueDeclare(queue: queue, 46 | durable: true, 47 | exclusive: false, 48 | autoDelete: false); 49 | } 50 | channel.QueueBind(queue, exchange, routingKey, null); 51 | return channel; 52 | } 53 | public static bool BasicPublish(this IModel channel, IBasicProperties properties, string exchange, string routingKey, string messageBody) 54 | { 55 | channel.ConfirmSelect(); 56 | channel.BasicPublish( 57 | exchange: exchange, 58 | routingKey: routingKey, 59 | mandatory: true, 60 | basicProperties: properties, 61 | body: messageBody?.GetBytes() 62 | ); 63 | return channel.WaitForConfirms(); 64 | } 65 | } -------------------------------------------------------------------------------- /src/RabbitMQ.EventBus.AspNetCore/Extensions/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using System; 4 | /// 5 | /// 6 | /// 7 | public static class ServiceCollectionExtensions 8 | { 9 | /// 10 | /// 添加RabbitMQEventBus 11 | /// 12 | /// 13 | /// 14 | /// 15 | /// 16 | /// 17 | /// 18 | /// 19 | /// 20 | /// 21 | public static IServiceCollection AddRabbitMQEventBus(this IServiceCollection services, string endpoint, int port, string username, string password, string visualHost, Action eventBusOptionAction, Action moduleOptions = null) 22 | => AddRabbitMQEventBus(services, () => $"amqp://{username}:{password}@{endpoint}:{port}/{visualHost}", eventBusOptionAction, moduleOptions); 23 | 24 | /// 25 | /// 添加RabbitMQEventBus 26 | /// 27 | /// 28 | /// 使用匿名函数取得连接字符串,用来兼容使用Consul获取服务地址的情况 29 | /// 30 | /// 31 | public static IServiceCollection AddRabbitMQEventBus(this IServiceCollection services, Func connectionAction, Action eventBusOptionAction, Action moduleOptions = null) 32 | { 33 | RabbitMQEventBusConnectionConfiguration configuration = new(); 34 | RabbitMQEventBusConnectionConfigurationBuild configurationBuild = new(configuration); 35 | eventBusOptionAction?.Invoke(configurationBuild); 36 | services.TryAddSingleton(options => 37 | { 38 | ILogger logger = options.GetRequiredService>(); 39 | var connection = DefaultRabbitMQPersistentConnection.CreateInstance(configuration, connectionAction, logger); 40 | connection.TryConnect(); 41 | logger.LogInformation("RabbitMQ event bus connected."); 42 | return connection; 43 | }); 44 | 45 | services.TryAddSingleton(options => 46 | { 47 | IRabbitMQPersistentConnection rabbitMQPersistentConnection = options.GetRequiredService(); 48 | ILogger logger = options.GetRequiredService>(); 49 | var eventBus = DefaultRabbitMQEventBusV2.CreateInstance(rabbitMQPersistentConnection, options, logger); 50 | return eventBus; 51 | }); 52 | foreach (var (registerType, handlerType, eventType, responseType) in RabbitmqEventBusHandlers.RegisterEventResponseHandlers()) 53 | { 54 | services.TryAddTransient(registerType, handlerType); 55 | } 56 | foreach (var (handlerType, eventType) in RabbitmqEventBusHandlers.RegisterEventHandlers()) 57 | { 58 | services.TryAddTransient(handlerType); 59 | } 60 | return services; 61 | } 62 | /// 63 | /// 64 | /// 65 | /// 66 | public static void UseRabbitmqEventBus(this IApplicationBuilder app) 67 | { 68 | IRabbitMQEventBus rmqeV2 = app.ApplicationServices.GetRequiredService(); 69 | var _logger = app.ApplicationServices.GetRequiredService>(); 70 | foreach (var (registerType, handlerType, eventType, responseType) in RabbitmqEventBusHandlers.RegisterEventResponseHandlers()) 71 | { 72 | rmqeV2.Subscribe(eventType, responseType); 73 | _logger.LogInformation($"subscribe:\t{eventType}\t=>\t{handlerType}<{eventType.Name},{responseType.Name}>\t return Type : \t{responseType}"); 74 | } 75 | foreach (var (handlerType, eventType) in RabbitmqEventBusHandlers.RegisterEventHandlers()) 76 | { 77 | rmqeV2.Subscribe(eventType); 78 | _logger.LogInformation($"subscribe:\t{eventType}\t=>\t{handlerType}<{eventType.Name}>"); 79 | } 80 | } 81 | } 82 | 83 | -------------------------------------------------------------------------------- /src/RabbitMQ.EventBus.AspNetCore/Extensions/TypeExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection.Extensions; 2 | using RabbitMQ.EventBus.AspNetCore.Events; 3 | using System; 4 | using System.Data; 5 | 6 | namespace Microsoft.Extensions.DependencyInjection; 7 | /// 8 | /// 9 | /// 10 | internal static class TypeExtensions 11 | { 12 | /// 13 | /// 14 | /// 15 | /// 16 | /// 17 | public static IEnumerable GetAssemblies(this Type type) 18 | { 19 | return AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes().Where(t => t.GetInterfaces().Contains(type))); 20 | } 21 | /// 22 | /// 23 | /// 24 | /// 25 | /// 26 | /// 27 | public static IEnumerable GetMakeGenericType(this Type interfalceType, Type makeType) 28 | { 29 | return AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes().Where(t => t.GetInterfaces().Contains(interfalceType.MakeGenericType(makeType)))); 30 | } 31 | 32 | public static bool IsNullOrWhiteSpace(this string source) 33 | { 34 | return string.IsNullOrWhiteSpace(source); 35 | } 36 | } -------------------------------------------------------------------------------- /src/RabbitMQ.EventBus.AspNetCore/Factories/DefaultRabbitMQPersistentConnection.cs: -------------------------------------------------------------------------------- 1 | namespace RabbitMQ.EventBus.AspNetCore.Factories; 2 | 3 | internal sealed class DefaultRabbitMQPersistentConnection : IRabbitMQPersistentConnection 4 | { 5 | public RabbitMQEventBusConnectionConfiguration Configuration { get; } 6 | private readonly IConnectionFactory _connectionFactory; 7 | private readonly ILogger _logger; 8 | private IConnection _connection; 9 | Func _connectionAction; 10 | private bool _disposed; 11 | private readonly object sync_root = new object(); 12 | private IModel _model; 13 | public string Endpoint => _connection?.Endpoint.ToString(); 14 | private static DefaultRabbitMQPersistentConnection Instance; 15 | private static object singleton_Lock = new object(); 16 | public static DefaultRabbitMQPersistentConnection CreateInstance(RabbitMQEventBusConnectionConfiguration configuration, Func connectionAction, ILogger logger) 17 | { 18 | lock (singleton_Lock) // 保证任意时刻只有一个线程才能进入判断 19 | { 20 | if (Instance == null) 21 | { 22 | Instance = new DefaultRabbitMQPersistentConnection(configuration, connectionAction, logger); 23 | } 24 | } 25 | return Instance; 26 | } 27 | private DefaultRabbitMQPersistentConnection(RabbitMQEventBusConnectionConfiguration configuration, Func connectionAction, ILogger logger) 28 | { 29 | Configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); 30 | _connectionAction = connectionAction ?? throw new ArgumentNullException(nameof(connectionAction)); 31 | _connectionFactory = new ConnectionFactory 32 | { 33 | AutomaticRecoveryEnabled = configuration.AutomaticRecoveryEnabled, 34 | NetworkRecoveryInterval = configuration.NetworkRecoveryInterval 35 | }; 36 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 37 | } 38 | 39 | public bool IsConnected => _connection != null && _connection.IsOpen && !_disposed; 40 | 41 | public IModel CreateModel() 42 | { 43 | if (!IsConnected) 44 | { 45 | throw new InvalidOperationException("No RabbitMQ connections are available to perform this action"); 46 | } 47 | if ((_model?.IsOpen ?? false) == false) 48 | { 49 | _model = _connection.CreateModel(); 50 | Console.WriteLine("创建一个Channel"); 51 | } 52 | return _model; 53 | } 54 | 55 | public void Dispose() 56 | { 57 | if (_disposed) 58 | { 59 | return; 60 | } 61 | _disposed = true; 62 | try 63 | { 64 | _connection.Dispose(); 65 | } 66 | catch (IOException ex) 67 | { 68 | _logger.LogCritical(ex.ToString()); 69 | } 70 | } 71 | 72 | public bool TryConnect() 73 | { 74 | _logger.LogInformation("RabbitMQ Client is trying to connect"); 75 | lock (sync_root) 76 | { 77 | RetryPolicy policy = RetryPolicy.Handle() 78 | .Or() 79 | .WaitAndRetry(Configuration.FailReConnectRetryCount, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), (ex, time) => 80 | { 81 | _logger.LogWarning(ex.ToString()); 82 | } 83 | ); 84 | 85 | policy.Execute(() => 86 | { 87 | string connectionString = _connectionAction.Invoke(); 88 | _logger.LogInformation($"[ConnectionString]:\t{connectionString}"); 89 | _connectionFactory.Uri = new Uri(connectionString); 90 | _connection = _connectionFactory.CreateConnection(clientProvidedName: Configuration.ClientProvidedName); 91 | }); 92 | 93 | if (IsConnected) 94 | { 95 | _connection.ConnectionShutdown += OnConnectionShutdown; 96 | _connection.CallbackException += OnCallbackException; 97 | _connection.ConnectionBlocked += OnConnectionBlocked; 98 | _logger.LogInformation($"RabbitMQ persistent connection acquired a connection {_connection.Endpoint.HostName} and is subscribed to failure events"); 99 | return true; 100 | } 101 | else 102 | { 103 | _logger.LogCritical("FATAL ERROR: RabbitMQ connections could not be created and opened"); 104 | return false; 105 | } 106 | } 107 | } 108 | 109 | private void OnConnectionBlocked(object sender, ConnectionBlockedEventArgs e) 110 | { 111 | if (_disposed) 112 | { 113 | return; 114 | } 115 | _logger.LogWarning("A RabbitMQ connection is shutdown. Trying to re-connect..."); 116 | TryConnect(); 117 | } 118 | 119 | private void OnCallbackException(object sender, CallbackExceptionEventArgs e) 120 | { 121 | if (_disposed) 122 | { 123 | return; 124 | } 125 | _logger.LogWarning("A RabbitMQ connection throw exception. Trying to re-connect..."); 126 | TryConnect(); 127 | } 128 | 129 | private void OnConnectionShutdown(object sender, ShutdownEventArgs reason) 130 | { 131 | if (_disposed) 132 | { 133 | return; 134 | } 135 | _logger.LogWarning("A RabbitMQ connection is on shutdown. Trying to re-connect..."); 136 | TryConnect(); 137 | } 138 | } -------------------------------------------------------------------------------- /src/RabbitMQ.EventBus.AspNetCore/Factories/IRabbitMQPersistentConnection.cs: -------------------------------------------------------------------------------- 1 | namespace RabbitMQ.EventBus.AspNetCore.Factories; 2 | /// 3 | /// 4 | /// 5 | public interface IRabbitMQPersistentConnection : IDisposable 6 | { 7 | /// 8 | /// 9 | /// 10 | RabbitMQEventBusConnectionConfiguration Configuration { get; } 11 | /// 12 | /// 连接点 13 | /// 14 | string Endpoint { get; } 15 | /// 16 | /// 连接是否打开 17 | /// 18 | bool IsConnected { get; } 19 | /// 20 | /// 尝试连接 21 | /// 22 | /// 23 | bool TryConnect(); 24 | /// 25 | /// 26 | /// 27 | /// 28 | IModel CreateModel(); 29 | } 30 | 31 | public static class RabbitmqEventBusHandlers 32 | { 33 | 34 | public static IEnumerable<(Type registerType, Type handlerType, Type eventType, Type responseType)> RegisterEventResponseHandlers() 35 | { 36 | foreach (var eventResponseHandler in AppDomain.CurrentDomain.GetAssemblies().SelectMany(t => t.GetTypes()).Where(t => t.GetInterfaces().Any(x => x.IsGenericType && !x.IsGenericTypeDefinition && x.GetGenericTypeDefinition() == typeof(IEventResponseHandler<,>)))) 37 | { 38 | var interfaces = eventResponseHandler.GetInterfaces() 39 | .Where(x => 40 | x.IsGenericType && 41 | !x.IsGenericTypeDefinition && 42 | x.GetGenericTypeDefinition() == typeof(IEventResponseHandler<,>) 43 | ); 44 | foreach (var iface in interfaces) 45 | { 46 | var eventResponseHandleArgs = iface.GetGenericArguments(); 47 | var eventType = eventResponseHandleArgs[0]; 48 | var responseType = eventResponseHandleArgs[1]; 49 | yield return (iface, eventResponseHandler, eventType, responseType); 50 | } 51 | } 52 | } 53 | 54 | 55 | public static IEnumerable<(Type handlerType, Type eventType)> RegisterEventHandlers() 56 | { 57 | foreach (Type mType in typeof(IEvent).GetAssemblies()) 58 | { 59 | foreach (Type hType in typeof(IEventHandler<>).GetMakeGenericType(mType)) 60 | { 61 | yield return (hType, mType); 62 | } 63 | } 64 | 65 | //foreach (var eventHandler in AppDomain.CurrentDomain.GetAssemblies().SelectMany(t => t.GetTypes()) 66 | // .Where(t => t.GetInterfaces().Any(x => x.IsGenericType && !x.IsGenericTypeDefinition && x.GetGenericTypeDefinition() == typeof(IEventHandler<>)))) 67 | //{ 68 | // var interfaces = eventHandler.GetInterfaces() 69 | // .Where(x => 70 | // x.IsGenericType && 71 | // !x.IsGenericTypeDefinition && 72 | // x.GetGenericTypeDefinition() == typeof(IEventHandler<>) 73 | // ); 74 | // foreach (var iface in interfaces) 75 | // { 76 | // var eventType = iface.GetGenericArguments().FirstOrDefault(); 77 | // services.TryAddTransient(eventType); 78 | // services.TryAddTransient(typeof(IEventHandler<>).MakeGenericType(eventType), eventHandler); 79 | // yield return (iface, eventHandler, eventType); 80 | // } 81 | //} 82 | } 83 | } -------------------------------------------------------------------------------- /src/RabbitMQ.EventBus.AspNetCore/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using Microsoft.Extensions.DependencyInjection; 2 | global using Microsoft.Extensions.DependencyInjection.Extensions; 3 | global using Microsoft.Extensions.Logging; 4 | global using Polly; 5 | global using Polly.Retry; 6 | global using RabbitMQ.Client; 7 | global using RabbitMQ.Client.Events; 8 | global using RabbitMQ.Client.Exceptions; 9 | global using RabbitMQ.EventBus.AspNetCore; 10 | global using RabbitMQ.EventBus.AspNetCore.Attributes; 11 | global using RabbitMQ.EventBus.AspNetCore.Configurations; 12 | global using RabbitMQ.EventBus.AspNetCore.Events; 13 | global using RabbitMQ.EventBus.AspNetCore.Extensions; 14 | global using RabbitMQ.EventBus.AspNetCore.Factories; 15 | global using RabbitMQ.EventBus.AspNetCore.Modules; 16 | global using System; 17 | global using System.Collections.Concurrent; 18 | global using System.Collections.Generic; 19 | global using System.ComponentModel; 20 | global using System.IO; 21 | global using System.Linq; 22 | global using System.Net.Sockets; 23 | global using System.Text; 24 | global using System.Text.Encodings.Web; 25 | global using System.Text.Json; 26 | global using System.Text.Json.Serialization; 27 | global using System.Threading; 28 | global using System.Threading.Tasks; 29 | -------------------------------------------------------------------------------- /src/RabbitMQ.EventBus.AspNetCore/IRabbitMQEventBus.cs: -------------------------------------------------------------------------------- 1 | namespace RabbitMQ.EventBus.AspNetCore; 2 | /// 3 | /// RabbitMQEventBus 4 | /// 5 | public interface IRabbitMQEventBus 6 | { 7 | /// 8 | /// 发消息 9 | /// 10 | /// 11 | /// 消息体 12 | /// 交换机 13 | /// 路由 14 | /// 1不持久化,2持久化 15 | /// 消息类型 16 | /// 17 | void Publish(TMessage message, string exchange, string routingKey, byte deliveryMode = 2, string type = ExchangeType.Topic); 18 | /// 19 | /// 发消息 20 | /// 21 | /// 22 | /// 消息体 23 | /// 交换机 24 | /// 路由 25 | /// 1不持久化,2持久化 26 | /// 消息类型 27 | /// 28 | void Publish(string message, string exchange, string routingKey, byte deliveryMode = 2, string type = ExchangeType.Topic); 29 | 30 | /// 31 | /// 发消息 32 | /// 33 | /// 34 | /// 消息体 35 | /// 交换机 36 | /// 路由 37 | /// 1不持久化,2持久化 38 | /// 消息类型 39 | /// 40 | Task PublishAsync(TMessage message, string exchange, string routingKey, byte deliveryMode = 2, string type = ExchangeType.Topic, CancellationToken cancellationToken = default); 41 | /// 42 | /// 发消息 43 | /// 44 | /// 45 | /// 消息体 46 | /// 交换机 47 | /// 路由 48 | /// 1不持久化,2持久化 49 | /// 消息类型 50 | /// 51 | //Task PublishAsync(string message, string exchange, string routingKey, byte deliveryMode = 2, string type = ExchangeType.Topic, CancellationToken cancellationToken = default); 52 | 53 | /// 54 | /// 发消息 55 | /// 56 | /// 57 | /// 58 | /// 消息体 59 | /// 交换机 60 | /// 路由 61 | /// 1不持久化,2持久化 62 | /// 消息类型 63 | /// 64 | /// 65 | Task PublishAsync(TMessage message, string exchange, string routingKey, byte deliveryMode = 2, string type = ExchangeType.Topic, Action errorHandler = null, CancellationToken cancellationToken = default) where TMessage : class; 66 | Task PublishAsync(object message, string exchange, string routingKey, byte deliveryMode = 2, string type = ExchangeType.Topic, Action errorHandler = null, CancellationToken cancellationToken = default); 67 | 68 | /// 69 | /// 发消息 70 | /// 71 | /// 72 | /// 73 | /// 消息体 74 | /// 交换机 75 | /// 路由 76 | /// 1不持久化,2持久化 77 | /// 消息类型 78 | /// 79 | /// 80 | Task PublishAsync(string message, string exchange, string routingKey, byte deliveryMode = 2, string type = ExchangeType.Topic, Action errorHandler = null, CancellationToken cancellationToken = default); 81 | /// 82 | /// 订阅消息 83 | /// 84 | /// 消息体 85 | /// 消息类型 86 | void Subscribe(Type eventType, string type = ExchangeType.Topic); 87 | /// 88 | /// 订阅消息 89 | /// 90 | /// 消息体 91 | /// 返回体 92 | /// 消息类型 93 | void Subscribe(Type eventType, Type responseType, string type = ExchangeType.Topic); 94 | } -------------------------------------------------------------------------------- /src/RabbitMQ.EventBus.AspNetCore/Modules/EventBusArgs.cs: -------------------------------------------------------------------------------- 1 | namespace RabbitMQ.EventBus.AspNetCore.Modules; 2 | /// 3 | /// 4 | /// 5 | public class EventBusArgs 6 | { 7 | /// 8 | /// 9 | /// 10 | public string Endpoint { get; set; } 11 | /// 12 | /// 交换机 13 | /// 14 | public string Exchange { get; set; } 15 | /// 16 | /// 队列 17 | /// 18 | public string Queue { get; set; } 19 | /// 20 | /// 路由 21 | /// 22 | public string RoutingKey { get; set; } 23 | /// 24 | /// 消息模式 25 | /// 26 | public string ExchangeType { get; set; } 27 | /// 28 | /// 客户端 29 | /// 30 | public string ClientProvidedName { get; set; } 31 | /// 32 | /// 消息内容 33 | /// 34 | public string Message { get; set; } 35 | /// 36 | /// 结果 37 | /// 38 | public bool Success { get; set; } 39 | /// 40 | /// 41 | /// 42 | /// 连接点 43 | /// 交换机 44 | /// 队列 45 | /// 路由 46 | /// 消息模式 47 | /// 客户端 48 | /// 消息 49 | /// 结果 50 | public EventBusArgs(string endPoint, string exchange, string queue, string routingKey, string exchangeType, string clientProvidedName, string message, bool success) 51 | { 52 | Endpoint = endPoint; 53 | Exchange = exchange; 54 | Queue = queue; 55 | RoutingKey = routingKey; 56 | ExchangeType = exchangeType; 57 | ClientProvidedName = clientProvidedName; 58 | Message = message; 59 | Success = success; 60 | } 61 | /// 62 | /// 63 | /// 64 | /// 65 | public override string ToString() 66 | { 67 | return $"{Endpoint}\t{ClientProvidedName}\t{Exchange}\t{ExchangeType}\t{Queue}\t{RoutingKey}\t{Message}"; 68 | } 69 | } -------------------------------------------------------------------------------- /src/RabbitMQ.EventBus.AspNetCore/Modules/EventHandlerModuleFactory.cs: -------------------------------------------------------------------------------- 1 | namespace RabbitMQ.EventBus.AspNetCore.Modules; 2 | /// 3 | /// 4 | /// 5 | internal class EventHandlerModuleFactory : IEventHandlerModuleFactory 6 | { 7 | private readonly List modules; 8 | private readonly object sync_root = new object(); 9 | private readonly IServiceProvider _serviceProvider; 10 | 11 | public EventHandlerModuleFactory(IServiceProvider serviceProvider) 12 | { 13 | _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); 14 | modules = new List(); 15 | } 16 | /// 17 | /// 18 | /// 19 | public void PubliushEvent(EventBusArgs e) 20 | { 21 | lock (sync_root) 22 | { 23 | foreach (IModuleHandle model in modules) 24 | { 25 | model.PublishEvent(e); 26 | } 27 | } 28 | } 29 | /// 30 | /// 31 | /// 32 | public void SubscribeEvent(EventBusArgs e) 33 | { 34 | lock (sync_root) 35 | { 36 | foreach (IModuleHandle model in modules) 37 | { 38 | model.SubscribeEvent(e); 39 | } 40 | } 41 | } 42 | 43 | public void TryAddMoudle(IModuleHandle module) 44 | { 45 | lock (sync_root) 46 | { 47 | modules.Add(module); 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /src/RabbitMQ.EventBus.AspNetCore/Modules/IEventHandlerModuleFactory.cs: -------------------------------------------------------------------------------- 1 | namespace RabbitMQ.EventBus.AspNetCore.Modules; 2 | /// 3 | /// 4 | /// 5 | public interface IEventHandlerModuleFactory 6 | { 7 | /// 8 | /// 9 | /// 10 | /// 11 | void TryAddMoudle(IModuleHandle module); 12 | /// 13 | /// 14 | /// 15 | void PubliushEvent(EventBusArgs e); 16 | /// 17 | /// 18 | /// 19 | void SubscribeEvent(EventBusArgs e); 20 | } -------------------------------------------------------------------------------- /src/RabbitMQ.EventBus.AspNetCore/Modules/IModuleHandle.cs: -------------------------------------------------------------------------------- 1 | namespace RabbitMQ.EventBus.AspNetCore.Modules; 2 | /// 3 | /// 模块 4 | /// 5 | public interface IModuleHandle 6 | { 7 | /// 8 | /// 发消息 9 | /// 10 | /// 11 | /// 12 | Task PublishEvent(EventBusArgs e); 13 | /// 14 | /// 收消息 15 | /// 16 | /// 17 | /// 18 | Task SubscribeEvent(EventBusArgs e); 19 | } -------------------------------------------------------------------------------- /src/RabbitMQ.EventBus.AspNetCore/Modules/RabbitMQEventBusModuleOption.cs: -------------------------------------------------------------------------------- 1 | namespace RabbitMQ.EventBus.AspNetCore.Modules; 2 | /// 3 | /// 模块 4 | /// 5 | public sealed class RabbitMQEventBusModuleOption 6 | { 7 | private readonly IEventHandlerModuleFactory handlerFactory; 8 | /// 9 | /// 10 | /// 11 | public IServiceProvider ApplicationServices; 12 | /// 13 | /// 14 | /// 15 | /// 16 | /// 17 | public RabbitMQEventBusModuleOption(IEventHandlerModuleFactory handlerFactory, IServiceProvider applicationServices) 18 | { 19 | this.handlerFactory = handlerFactory ?? throw new ArgumentNullException(nameof(handlerFactory)); 20 | ApplicationServices = applicationServices; 21 | } 22 | /// 23 | /// 添加模块 24 | /// 25 | /// 26 | public void AddModule(IModuleHandle module) 27 | { 28 | handlerFactory.TryAddMoudle(module); 29 | } 30 | } -------------------------------------------------------------------------------- /src/RabbitMQ.EventBus.AspNetCore/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /src/RabbitMQ.EventBus.AspNetCore/RabbitMQ.EventBus.AspNetCore.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6 5 | true 6 | 7 | https://github.com/ojdev/RabbitMQ.EventBus.AspNetCore 8 | git 9 | rqbbitmq,dnc,eventbus 10 | 欧俊 11 | 欧俊 12 | asp.net core 下使用的RabbitMQ 13 | 14 | https://github.com/ojdev/RabbitMQ.EventBus.AspNetCore 15 | 6.0.0 16 | LICENSE 17 | enable 18 | RabbitMQ.EventBus.AspNetCore 19 | README.md 20 | Corp. 2018 欧俊 21 | dotnet-logo.png 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | True 37 | 38 | 39 | 40 | True 41 | \ 42 | 43 | 44 | 45 | 46 | 47 | True 48 | \ 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /src/RabbitMQ.EventBus.AspNetCore/dotnet-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ojdev/RabbitMQ.EventBus.AspNetCore/c0b8fa165242101c3805a60f044f3c701186e5ff/src/RabbitMQ.EventBus.AspNetCore/dotnet-logo.png -------------------------------------------------------------------------------- /src/RabbitMQ.EventBus.AspNetCore/dotnet-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | --------------------------------------------------------------------------------