一.项目结构
二 类库Infrastructure代码
添加nuget引用:RabbitMQ.Client,引用项目Rabbit.Entities
1.RabbitOption.cs代码
using System; using System.Collections.Generic; using System.Text; namespace Infrastructure.Config { public class RabbitOption { /// <summary> /// 主机名称 /// </summary> public string HostName { get; set; } public string Address { get; set; } /// <summary> /// 端口号 /// </summary> public int Port { get; set; } public string UserName { get; set; } public string Password { get; set; } public string VirtualHost { get; set; } } }
2.RabbitConnection.cs代码
using RabbitMQ.Client; using System; using System.Collections.Generic; using System.Text; namespace Infrastructure.Config { public class RabbitConnection { private readonly RabbitOption _config; private IConnection _connection = null; public RabbitConnection(RabbitOption rabbitOption) { _config = rabbitOption; } public IConnection GetConnection() { if (_connection == null) { if (string.IsNullOrEmpty(_config.Address)) { ConnectionFactory factory = new ConnectionFactory(); factory.HostName = _config.HostName; factory.Port = _config.Port; factory.UserName = _config.UserName; factory.Password = _config.Password; factory.VirtualHost = _config.VirtualHost; _connection = factory.CreateConnection(); } else { ConnectionFactory factory = new ConnectionFactory(); factory.UserName = _config.UserName; factory.Password = _config.Password; factory.VirtualHost = _config.VirtualHost; var address = _config.Address; List<AmqpTcpEndpoint> endpoints = new List<AmqpTcpEndpoint>(); foreach (var endpoint in address.Split(",")) { endpoints.Add(new AmqpTcpEndpoint(endpoint.Split(":")[0],int.Parse(endpoint.Split(":")[1]))); } _connection = factory.CreateConnection(endpoints); } } return _connection; } } }
3.RabbitConstant.cs代码
using System; using System.Collections.Generic; using System.Text; namespace Infrastructure.Config { public class RabbitConstant { public const string TEST_EXCHANGE = "test.exchange"; public const string TEST_QUEUE = "test.queue"; public const string DELAY_EXCHANGE = "delay.exchange"; public const string DELAY_ROUTING_KEY = "delay.routing.key"; public const string DELAY_QUEUE = "delay.queue"; public const string DEAD_LETTER_EXCHANGE = "dead.letter.exchange"; public const string DEAD_LETTER_QUEUE = "dead.letter.queue"; public const string DEAD_LETTER_ROUTING_KEY = "dead.letter.routing.key"; } }
4.QueueInfo.cs
using System; using System.Collections.Generic; using System.Text; namespace Infrastructure.Consumer { public class QueueInfo { /// <summary> /// 队列名称 /// </summary> public string Queue { get; set; } /// <summary> /// 路由名称 /// </summary> public string RoutingKey { get; set; } /// <summary> /// 交换机类型 /// </summary> public string ExchangeType { get; set; } /// <summary> /// 交换机名称 /// </summary> public string Exchange { get; set; } public IDictionary<string, object> props { get; set; } = null; public Action<RabbitMessageEntity> OnRecevied { get; set; } } }
5.RabbitChannelConfig.cs
using RabbitMQ.Client; using RabbitMQ.Client.Events; using System; using System.Collections.Generic; using System.Text; namespace Infrastructure.Consumer { public class RabbitChannelConfig { public string ExchangeTypeName { get; set; } public string ExchangeName { get; set; } public string QueueName { get; set; } public string RoutingKeyName { get; set; } public IConnection Connection { get; set; } public EventingBasicConsumer Consumer { get; set; } /// <summary> /// 外部订阅消费者通知委托 /// </summary> public Action<RabbitMessageEntity> OnReceivedCallback { get; set; } public RabbitChannelConfig(string exchangeType,string exchange,string queue,string routingKey) { this.ExchangeTypeName = exchangeType; this.ExchangeName = exchange; this.QueueName = queue; this.RoutingKeyName = routingKey; } public void Receive(object sender,BasicDeliverEventArgs args) { RabbitMessageEntity body = new RabbitMessageEntity(); try { string content = Encoding.UTF8.GetString(args.Body.ToArray()); body.Content = content; body.Consumer =(EventingBasicConsumer)sender; body.BasicDeliver = args; } catch (Exception e) { body.ErrorMessage = $"订阅出错{e.Message}"; body.Exception = e; body.Error = true; body.Code = 500; } OnReceivedCallback?.Invoke(body); } } }
6.RabbitChannelManager.cs
using Infrastructure.Config; using RabbitMQ.Client; using RabbitMQ.Client.Events; using System; using System.Collections.Generic; using System.Text; namespace Infrastructure.Consumer { public class RabbitChannelManager { public RabbitConnection Connection { get; set; } public RabbitChannelManager(RabbitConnection connection) { this.Connection = connection; } /// <summary> /// 创建接收消息的通道 /// </summary> /// <param name="exchangeType"></param> /// <param name="exchange"></param> /// <param name="queue"></param> /// <param name="routingKey"></param> /// <param name="arguments"></param> /// <returns></returns> public RabbitChannelConfig CreateReceiveChannel(string exchangeType,string exchange,string queue,string routingKey, IDictionary<string,object>arguments = null) { IModel model = this.CreateModel(exchangeType,exchange,queue,routingKey,arguments); EventingBasicConsumer consumer = this.CreateConsumer(model,queue); RabbitChannelConfig channel = new RabbitChannelConfig(exchangeType,exchange,queue,routingKey); consumer.Received += channel.Receive; return channel; } /// <summary> /// 创建一个通道,包含交换机/队列/路由,并建立绑定关系 /// </summary> /// <param name="exchangeType">交换机类型:Topic,Direct,Fanout</param> /// <param name="exchange">交换机名称</param> /// <param name="queue">队列名称</param> /// <param name="routingKey">路由名称</param> /// <param name="arguments"></param> /// <returns></returns> private IModel CreateModel(string exchangeType, string exchange, string queue, string routingKey, IDictionary<string, object> arguments) { exchangeType = string.IsNullOrEmpty(exchangeType) ? "default" : exchangeType; IModel model = this.Connection.GetConnection().CreateModel(); model.BasicQos(0,1,false); model.QueueDeclare(queue,true,false,false,arguments); model.ExchangeDeclare(exchange, exchangeType); model.QueueBind(queue, exchange, routingKey); return model; } /// <summary> /// 创建消费者 /// </summary> /// <param name="model"></param> /// <param name="queue"></param> /// <returns></returns> private EventingBasicConsumer CreateConsumer(IModel model, string queue) { EventingBasicConsumer consumer = new EventingBasicConsumer(model); model.BasicConsume(queue, false, consumer); return consumer; } } }
7.RabbitMessageEntity.cs
using RabbitMQ.Client.Events; using System; using System.Collections.Generic; using System.Text; namespace Infrastructure.Consumer { public class RabbitMessageEntity { public string Content { get; set; } public EventingBasicConsumer Consumer { get; set; } public BasicDeliverEventArgs BasicDeliver { get; set; } public string ErrorMessage { get; set; } public Exception Exception { get; set; } public bool Error { get; set; } public int Code { get; set; } } }
8.OrderMessage.cs
using RabbitMQ.Entities; namespace Infrastructure.Message { public class OrderMessage { public Account Account { get; set; } public OrderInfo OrderInfo { get; set; } } }
9.IRabbitProducer.cs
using System; using System.Collections.Generic; namespace Infrastructure.Producer { public interface IRabbitProducer { public void Publish(string exchange,string routingKey,IDictionary<string,object> props,string content); } }
10.RabbitProducer.cs
using Infrastructure.Config; using System; using System.Collections.Generic; using System.Text; namespace Infrastructure.Producer { public class RabbitProducer : IRabbitProducer { private readonly RabbitConnection _connection; public RabbitProducer(RabbitConnection connection) { _connection = connection; } public void Publish(string exchange, string routingKey, IDictionary<string, object> props, string content) { var channel = _connection.GetConnection().CreateModel(); var prop = channel.CreateBasicProperties(); if (props.Count > 0) { var delay = props["x-delay"]; prop.Expiration = delay.ToString(); } channel.BasicPublish(exchange, routingKey, false, prop, Encoding.UTF8.GetBytes(content)); } } }
三 类库RabbitMQ.Entities代码
1.Account.cs
using System; namespace RabbitMQ.Entities { public class Account { public string UserName { get; set; } public string Password { get; set; } public string Email { get; set; } public string Phone { get; set; } } }
2.OrderInfo.cs
using System; namespace RabbitMQ.Entities { public class OrderInfo { public int GoodsCount { get; set; } public int GoodsId { get; set; } public string GoodsName { get; set; } public int Status { get; set; } public int UserId { get; set; } } }
四 类库RabbitMQ.Services代码
引用:nuget添加Newtonsoft.Json,添加项目Infrastructure、RabbitMQ.Entities引用
1.IOrderService.cs
using System; namespace RabbitMQ.Services { public interface IOrderService { void SendOrderMessage(); void SendTestMessage(string message); } }
2.OrderService.cs
using Infrastructure.Config; using Infrastructure.Producer; using System; using System.Collections.Generic; using System.Text; using RabbitMQ.Entities; using Newtonsoft.Json; using Infrastructure.Message; namespace RabbitMQ.Services { public class OrderService : IOrderService { private readonly IRabbitProducer _rabbitProducer; public OrderService(IRabbitProducer rabbitProducer) { _rabbitProducer = rabbitProducer; } public void SendOrderMessage() { OrderInfo orderInfo = new OrderInfo(); orderInfo.GoodsCount = 1; orderInfo.GoodsId = 1; orderInfo.GoodsName = "大话设计模式"; orderInfo.Status = 0; orderInfo.UserId = 1; Account account = new Account(); account.UserName = "Hobelee"; account.Password = "password007"; account.Email = "hobelee@163.com"; account.Phone = "13964836342"; OrderMessage orderMessage = new OrderMessage(); orderMessage.Account = account; orderMessage.OrderInfo = orderInfo; string message = JsonConvert.SerializeObject(orderMessage); Console.WriteLine("短信/邮件异步通知"); Console.WriteLine($"send message:{message}"); //支付服务 _rabbitProducer.Publish(RabbitConstant.DELAY_EXCHANGE, RabbitConstant.DELAY_ROUTING_KEY, new Dictionary<string, object>() { { "x-delay",1000*20} },message); } public void SendTestMessage(string message) { Console.WriteLine($"send message:{message}"); _rabbitProducer.Publish(RabbitConstant.TEST_EXCHANGE,"",new Dictionary<string,object>(),message); } } }
3.IPayService.cs
using RabbitMQ.Entities; namespace RabbitMQ.Services { public interface IPayService { void UpdateOrderPayState(OrderInfo orderInfo); } }
4.PayService.cs
using RabbitMQ.Entities; using System; namespace RabbitMQ.Services { public class PayService : IPayService { public void UpdateOrderPayState(OrderInfo orderInfo) { Console.WriteLine($"修改订单状态:{orderInfo.Status}"); } } }
五 ASP.NET Core Web API项目RabbitMQ.WebApi.Order代码
引用:添加项目引用Infrastructure、RabbitMQ.Services
1.appsettins.json
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } }, "RabbitMQ": { "HostName": "127.0.0.1", "Address": "", "Port": 5672, "UserName": "guest", "Password": "guest", "VirtualHost": "/" }, "AllowedHosts": "*" }
2.ServiceExtensions.cs
using Infrastructure.Config; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace RabbitMQ.WebApi.Order.Extensions { public static class ServiceExtensions { public static void ConfigureCors(this IServiceCollection services) { services.AddCors(options=> { options.AddPolicy("AnyPolicy", builder=>builder.AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader()); }); } public static void ConfigureRabbitContext(this IServiceCollection services,IConfiguration config) { var section = config.GetSection("RabbitMQ"); services.AddSingleton( new RabbitConnection(section.Get<RabbitOption>())); } } }
3.Startup.cs
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using RabbitMQ.WebApi.Order.Extensions; using Microsoft.OpenApi.Models; using RabbitMQ.Services; using Infrastructure.Producer; namespace RabbitMQ.WebApi.Order { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddSwaggerGen(c=> { c.SwaggerDoc("v1",new OpenApiInfo { Title="RabbitMQ.WebApi.Order",Version="v1"}); }); services.AddScoped<IOrderService, OrderService>(); services.AddScoped<IRabbitProducer,RabbitProducer>(); services.ConfigureCors(); services.ConfigureRabbitContext(Configuration); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseSwagger(); app.UseSwaggerUI(c=>c.SwaggerEndpoint("/swagger/v1/swagger.json","RabbitMQ.WebApi.Order v1")); } app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); } } }
六 控制台项目RabbitMQ.Pay代码
添加nuget引用:Microsoft.Extensions.Hosting,Newtonsoft.Json
添加项目引用:Infrastructure,RabbitMQ.Services
将项目RabbitMQ.WebApi.Order中的appsettings.json文件复制到该项目
1.ProcessPay.cs
using System; using System.Collections.Generic; using System.Text; using System.Threading; using System.Threading.Tasks; using Infrastructure.Config; using Infrastructure.Consumer; using Infrastructure.Message; using Microsoft.Extensions.Hosting; using Newtonsoft.Json; using RabbitMQ.Client; using RabbitMQ.Entities; using RabbitMQ.Services; namespace RabbitMQ.Pay { public class ProcessPay : IHostedService { private readonly RabbitConnection _connection; private readonly IPayService _payService; public List<RabbitChannelConfig> Channels { get; set; } public List<QueueInfo> Queues { get; } = new List<QueueInfo>(); public ProcessPay(RabbitConnection connection, IPayService payService) { _connection = connection; _payService = payService; Queues.Add(new QueueInfo() { ExchangeType = ExchangeType.Direct, Exchange = RabbitConstant.DELAY_EXCHANGE, Queue = RabbitConstant.DELAY_QUEUE, RoutingKey = RabbitConstant.DELAY_ROUTING_KEY, props = new Dictionary<string, object>() { { "x-dead-letter-exchange",RabbitConstant.DEAD_LETTER_EXCHANGE}, { "x-dead-letter-routing-key",RabbitConstant.DEAD_LETTER_ROUTING_KEY} }, OnRecevied = this.Receive }); } private void Receive(RabbitMessageEntity message) { Console.WriteLine($"Pay Receive Message:{message.Content}"); OrderMessage orderMessage = JsonConvert.DeserializeObject<OrderMessage>(message.Content); //超时未支付 string money = ""; //支付处理 Console.WriteLine("请输入:"); //超时未支付进行处理 Task.Run(()=> { money = Console.ReadLine(); }).Wait(20*1000); if (string.Equals(money, "100")) { //设置状态为支付成功(同时设置消息的状态和数据库订单的状态) orderMessage.OrderInfo.Status = 1; _payService.UpdateOrderPayState(orderMessage.OrderInfo); Console.WriteLine("支付完成"); message.Consumer.Model.BasicAck(deliveryTag:message.BasicDeliver.DeliveryTag,multiple:true); } else { //重试几次依然失败 Console.WriteLine("等待一定时间失效超时未支付的订单"); //消息进入到死信队列 message.Consumer.Model.BasicNack(deliveryTag: message.BasicDeliver.DeliveryTag, multiple: false, requeue: false); } } public Task StartAsync(CancellationToken cancellationToken) { Console.WriteLine("RabbitMQ支付通知处理服务已启动"); RabbitChannelManager channelManager = new RabbitChannelManager(_connection); foreach (var queueInfo in Queues) { RabbitChannelConfig channel = channelManager.CreateReceiveChannel(queueInfo.ExchangeType, queueInfo.Exchange,queueInfo.Queue,queueInfo.RoutingKey,queueInfo.props); channel.OnReceivedCallback = queueInfo.OnRecevied; } return Task.CompletedTask; } public Task StopAsync(CancellationToken cancellationToken) { return Task.CompletedTask; } } }
2.Program.cs
using Infrastructure.Config; using Infrastructure.Producer; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using RabbitMQ.Services; using System; namespace RabbitMQ.Pay { class Program { static void Main(string[] args) { var configRabbit = new ConfigurationBuilder() .AddJsonFile("appsettings.json") .Build() .GetSection("RabbitMQ"); var host = new HostBuilder() .ConfigureServices(services=> services.AddSingleton(new RabbitConnection(configRabbit.Get<RabbitOption>())) .AddSingleton<IHostedService,ProcessPay>() .AddScoped<IRabbitProducer,RabbitProducer>() .AddScoped<IPayService,PayService>()).Build(); host.Run(); } } }
七 控制台RabbitMQ.Pay.Timeout项目代码
添加nuget引用:Microsoft.Extensions.Hosting,Newtonsoft.Json
添加项目引用:Infrastructure,RabbitMQ.Services
将项目RabbitMQ.WebApi.Order中的appsettings.json文件复制到该项目
1.ProcessPayTimeout.cs
using System; using System.Collections.Generic; using System.Text; using System.Threading; using System.Threading.Tasks; using Infrastructure.Config; using Microsoft.Extensions.Hosting; using Newtonsoft.Json; using RabbitMQ.Services; using Infrastructure.Consumer; using RabbitMQ.Client; using RabbitMQ.Entities; using Infrastructure.Message; namespace RabbitMQ.Pay.Timeout { public class ProcessPayTimeout : IHostedService { private readonly RabbitConnection _connection; private readonly IPayService _payService; public List<RabbitChannelConfig> Channels { get; set; } = new List<RabbitChannelConfig>(); public List<QueueInfo> Queues { get; } = new List<QueueInfo>(); public ProcessPayTimeout(RabbitConnection connection,IPayService payService) { _connection = connection; _payService = payService; Queues.Add(new QueueInfo { ExchangeType = ExchangeType.Direct, Exchange = RabbitConstant.DEAD_LETTER_EXCHANGE, Queue = RabbitConstant.DEAD_LETTER_QUEUE, RoutingKey = RabbitConstant.DEAD_LETTER_ROUTING_KEY, OnRecevied = this.Receive }); } private void Receive(RabbitMessageEntity messgae) { Console.WriteLine($"Pay Timeout Receive Message:{messgae.Content}"); OrderMessage orderMessage = JsonConvert.DeserializeObject<OrderMessage>(messgae.Content); //获取到消息后,修改消息的状态为超时未支付 2 orderMessage.OrderInfo.Status = 2; Console.WriteLine("超时未支付"); _payService.UpdateOrderPayState(orderMessage.OrderInfo); messgae.Consumer.Model.BasicAck(messgae.BasicDeliver.DeliveryTag,true); } public Task StartAsync(CancellationToken cancellationToken) { Console.WriteLine("RabbitMQ超时支付处理程序启动"); RabbitChannelManager channelManager = new RabbitChannelManager(_connection); foreach (var queueInfo in Queues) { RabbitChannelConfig channel = channelManager.CreateReceiveChannel(queueInfo.ExchangeType, queueInfo.Exchange,queueInfo.Queue,queueInfo.RoutingKey); channel.OnReceivedCallback = queueInfo.OnRecevied; } return Task.CompletedTask; } public Task StopAsync(CancellationToken cancellationToken) { return Task.CompletedTask; } } }
2.Program.cs
using Infrastructure.Config; using Infrastructure.Producer; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using RabbitMQ.Services; using System; namespace RabbitMQ.Pay.Timeout { class Program { static void Main(string[] args) { var configRabbit = new ConfigurationBuilder() .AddJsonFile("appsettings.json") .Build() .GetSection("RabbitMQ"); var host = new HostBuilder() .ConfigureServices(services => services.AddSingleton(new RabbitConnection(configRabbit.Get<RabbitOption>())) .AddSingleton<IHostedService, ProcessPayTimeout>() .AddScoped<IRabbitProducer, RabbitProducer>() .AddScoped<IPayService, PayService>()).Build(); host.Run(); } } }
八 运行
1.分别使用powershell运行三个项目
PS D:\DotNetProject\RabbitMQ.WebApiDemo\RabbitMQ.Pay> dotnet run
PS D:\DotNetProject\RabbitMQ.WebApiDemo\RabbitMQ.Pay.Timeout> dotnet run
PS D:\DotNetProject\RabbitMQ.WebApiDemo\RabbitMQ.WebApi.Order> dotnet run
2.使用浏览器打开http://localhost:5000/swagger/index.html
3.模拟正常支付
4.模拟超时支付
首先执行3步骤,发送请求
然后powershell控制台不做任何操作
执行结果如下:
5.模拟支付失败
支付金额不够
先发送请求,然后请输入的金额输入一个不是100的值
执行结果: