RabbitMQ在.net core api中的使用

前言

本文所有讨论都基于.net core api前后端分离项目

服务端推送使用websocket,也可以使用轮询,但是解决高并发的玩法就多了,Nginx+Redis是流行方案,但是也有接入队列的,这样服务端不需要做比较大的改动,差点跑题~

AMQP

这是一个文本消息协议,RabbitMQ是基于这个协议的一种实现,贴个官网RabbitMQ官网,上面详细介绍了怎么在各种开发语言中接入的方法。本文重点谈前后端分离项目中怎么玩

开发环境准备

前端vue,脚手架版本4.2.3
在这里插入图片描述
后端.net core 3.1
在这里插入图片描述
vue接入signalr前端引入@microsoft/signalr这个依赖
在这里插入图片描述
这里我写了一个单独的页进行测试,可能后期不准备接入这玩意儿,几百个用户的访问量我不准备考虑。讲几点

this的指向问题

			//这里存一个别名,防止在connection中获取不到vue的实例
			let _self=this;
			//创建一个Signalr连接
            var connection = new signalR.HubConnectionBuilder().withUrl("/api/msg",{
            //带个token过去后端鉴权和认证
              accessTokenFactory: () => sessionStorage.getItem('token')
			}).build();

后端采用终结点的方式提供服务入口,中间件方式试了一下会报错

     app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
               endpoints.MapHub<MessageHub>("/api/msg")
                .RequireCors(t => t.WithOrigins(new string[] { "http://localhost:5000", "https://localhost:5001" })
                .AllowAnyMethod().AllowAnyHeader().AllowCredentials());
            });

前端Signalr配置

 			connection.serverTimeoutInMilliseconds= 24e4;
            connection.keepaliveintervalinmilliseconds = 12e4

后端引入RabbitMQ依赖(高亮),Signalr框架已经给我们集成了
只引入高亮的这个包

后端Signalr服务注册进服务集合容器

          services.AddSignalR(options =>
            {
                //客户端发保持连接请求到服务端最长间隔,默认30秒,改成4分钟,网页需跟着设置connection.keepaliveintervalinmilliseconds = 12e4;即2分钟
                options.ClientTimeoutInterval = TimeSpan.FromMinutes(4);
                //服务端发保持连接请求到客户端间隔,默认15秒,改成2分钟,网页需跟着设置connection.servertimeoutinmilliseconds = 24e4;即4分钟
                options.KeepAliveInterval = TimeSpan.FromMinutes(2);
            });

服务注册,托管服务只有注册了才会生效

 		services.AddHostedService<MessageHub>();

后端jwt配置

		//TokenManagement
		    public class TokenManagement
		    {
		        [JsonProperty("secret")]
		        public string Secret { get; set; }
		
		        [JsonProperty("issuer")]
		        public string Issuer { get; set; }
		
		        [JsonProperty("audience")]
		        public string Audience { get; set; }
		
		        [JsonProperty("accessExpiration")]
		        public int AccessExpiration { get; set; }
		
		        [JsonProperty("refreshExpiration")]
		        public int RefreshExpiration { get; set; }
		    }
//这里是把配置文件中的section映射到自定义类型并获取实例
 var token = Configuration.GetSection("tokenManagement").Get<TokenManagement>();
            //身份认证代码段,基于jwt进行的身份认证
            services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            }).AddJwtBearer(options =>
            {
               //这里配置是否一定要使用https请求
                options.RequireHttpsMetadata = false;
                options.SaveToken = true;
                options.TokenValidationParameters = new TokenValidationParameters
                {

                    ValidateIssuerSigningKey = true,//是否验证秘钥签名
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(token.Secret)),//签名秘钥
                    ValidIssuer = token.Issuer,//使用者
                    ValidAudience = token.Audience,//颁发者
                    ValidateIssuer = false,
                    ValidateAudience = false,
                    // 是否验证Token有效期,使用当前时间与Token的Claims中的NotBefore和Expires对比
                    //  ValidateLifetime = true,
                    //注意这是缓冲过期时间,总的有效时间等于这个时间加上jwt的过期时间,如果不配置,默认是5分钟
                    // ClockSkew = TimeSpan.FromSeconds(4)
                };
                options.Events = new JwtBearerEvents
                {
                    OnMessageReceived = (context) => {
                        if (!context.HttpContext.Request.Path.HasValue)
                        {
                            return Task.CompletedTask;
                        }
                        //重点在于这里;判断是Signalr的路径
                        var accessToken = context.HttpContext.Request.Query["access_token"];
                        var path = context.HttpContext.Request.Path;
                        if (!(string.IsNullOrWhiteSpace(accessToken)) && path.StartsWithSegments("/api/msg"))
                        {
                            context.Token = accessToken;
                            return Task.CompletedTask;
                        }
                        return Task.CompletedTask;
                    }
                };
            });

贴个前后端完整代码吧还是,按需参考,这些代码在官网都能搞到

前端

<template>
    <div>
        <div class="container">
            <div class="row">&nbsp;</div>
            <div class="row">
                <div class="col-2">User</div>
                <div class="col-4"><input type="text" id="userInput" /></div>
            </div>
            <div class="row">
                <div class="col-2">Message</div>
                <div class="col-4"><input type="text" id="messageInput" /></div>
            </div>
            <div class="row">&nbsp;</div>
            <div class="row">
                <div class="col-6">
                    <input type="button" @click="" id="sendButton" value="Send Message" />
                </div>
                <div class="col-6">
                    <input type="button" @click="" id="sendButtonConfirm" value="Send Message" />
                </div>
            </div>
        </div>
        <div class="row">
            <div class="col-12">
                <hr />
            </div>
        </div>
        <div class="row">
            <div class="col-6">
                <ul id="messagesList"></ul>
            </div>
        </div>
    </div>
</template>

<script>
    import * as signalR from "@microsoft/signalr";
    export default {
        name: "Online",
        data(){
            return {
               onLineUserList:[],
                connectionId:"",
                token:"",
                selfConneId:""
            }
        },
        created() {
            // console.log(this)
        },
        mounted() {
            let _self=this;
            //.withAutomaticReconnect()
            var connection = new signalR.HubConnectionBuilder().withUrl("/api/msg", {
                accessTokenFactory: () => sessionStorage.getItem('token')

            }).build();
            document.getElementById("sendButton").disabled = true;
            connection.serverTimeoutInMilliseconds= 24e4;
            connection.keepaliveintervalinmilliseconds = 12e4
            connection.on("ReceiveMessage", function (res) {
                //var msg = res.data.msg.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
               var encodedMsg = res.userName + " says " +res.msg;
                var li = document.createElement("li");
                li.textContent = encodedMsg;
                document.getElementById("messagesList").appendChild(li);
            });

            // async function start () {
            //     try {
            //         await connection.start()
            //         console.log('connected')
            //     } catch (err) {
            //         console.log(err)
            //         setTimeout(() => start(), 5000)
            //     }
            // }

            connection.onclose(async (e) => {
               _self.$message.error("连接断开");
            })
            connection.on("ConnectResponse",function (res) {
             //   console.log(res);
                // var msg = res.msg.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
            if(res.state===200){
                this.onLineUserList=res.data;
                this.selfConneId=res.connId;
                this.token=res.token;
                _self.$message.success(res.msg);
            }else{
                _self.$message.error("连接服务器失败~");
            }

            })
            connection.start().then(function (e) {
              //  console.log(e)
                document.getElementById("sendButton").disabled = false;
            }).catch(function (err) {
                return console.error(err.toString());
            });

            document.getElementById("sendButton").addEventListener("click", function (event) {
                var user = document.getElementById("userInput").value;
                var message = document.getElementById("messageInput").value;
                connection.invoke("SendAllMessage", {ConnectionId:_self.selfConneId,Token:_self.token,UserName:user,Msg:message}).catch(function (err) {
                    return console.error(err.toString());
                });
                // connection.send("SendMessage",user,message).then(e=>{console.log(e)}).catch(e=>{
                //     this.$message.error('出错了');
                // })
                event.preventDefault();
            });
            document.getElementById("sendButtonConfirm").addEventListener("click", function (event) {
                var user = document.getElementById("userInput").value;
                var message = document.getElementById("messageInput").value;
                connection.invoke("SendOneMessage", {ConnectionId:_self.selfConneId,Token:_self.token,UserName:user,Msg:message}).catch(function (err) {
                    return console.error(err.toString());
                });
                // connection.send("SendMessage",user,message).then(e=>{console.log(e)}).catch(e=>{
                //     this.$message.error('出错了');
                // })
                event.preventDefault();
            });

        }
    }
</script>

<style scoped>

</style>

后端

using IntegratedServices.RabbitMQ;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System.Text;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Threading.Tasks;
using System.Threading;

namespace IntegratedServices.Online
{
    public class MessageHub : Hub, IHostedService
    {
      
        private IConnection _connection;
        /// <summary>
        /// The channel
        /// </summary>
        private IModel _channel;
        private readonly QueueBaseOption _batchSendMessageQueue;
        private readonly RabbitConnectOption _rabbitConnectOptions;
        private readonly IServiceProvider _services;
        private readonly ILogger<MessageHub> _logger;
        public static List<UserModel> OnlineUser = new List<UserModel>() { };

        //ISysUser user
        public MessageHub(IServiceProvider services, IOptions<MessageQueueOption> messageQueueOption, ILogger<MessageHub> logger)
        {
            // _userService = user;
            _logger = logger;
            _services = services;
            _batchSendMessageQueue = messageQueueOption.Value?.BatchSendMessageQueue;
            _rabbitConnectOptions = messageQueueOption.Value?.RabbitConnect;
        }


        /// <summary>
        /// 当连接成功时执行
        /// </summary>
        /// <returns></returns>
        [Authorize]
        public async override Task OnConnectedAsync()
        {
            string connId = Context.ConnectionId;
            // Clients.All
            _logger.LogWarning("SignalR已连接");
            //验证Token
            var token = Context.GetHttpContext().Request.Query["access_token"];
            var user = (new JwtSecurityTokenHandler().ReadJwtToken(token));
            string userName = user.Claims.FirstOrDefault(m => m.Type == "name").Value;
            _logger.LogWarning("SignalR已连接,用户名:" + userName);
            bool isexited = OnlineUser.Any(o => o.UserName == userName);
            if (isexited)
                OnlineUser.RemoveAll(ui => ui.UserName == userName);

            //连接用户 这里可以存在Redis
            //OnlineUser.Add(new UserModel{

            //});
            var model = new UserModel
            {
                ConnectionId = connId,
                Token = token,
                UserName = userName,
                Msg = ""
            };
            //OnlineUser.Add(model);
            OnlineUser.Add(model);



            //  Groups.AddToGroupAsync(connId, "UserList");





            //给当前的连接分组 可以进行同一组接收消息 也可以用Token获取机构权限
            //await Groups.AddToGroupAsync(Context.ConnectionId, "测试组");

            //给当前连接返回消息 .Clients可以发多个连接ID
            await Clients.Client(connId).SendAsync("ConnectResponse",
                     new
                     {
                         state = 200,
                         token = token,
                         connId = connId,
                         data = "",
                         msg = userName + "连接成功"
                     });
            Process(Clients);
            //await StartAsync(CancellationToken.None);
            await base.OnConnectedAsync();
        }

        /// <summary>
        /// 当连接断开时的处理
        /// </summary>
        public override async Task OnDisconnectedAsync(Exception exception)
        {
            var token = Context.GetHttpContext().Request.Query["access_token"];
            var user = (new JwtSecurityTokenHandler().ReadJwtToken(token));
            string userName = user.Claims.FirstOrDefault(m => m.Type == "name").Value;
            string connId = Context.ConnectionId;
            //var model = OnlineUser.FirstOrDefault(OU => OU.Key == userName);
            OnlineUser.RemoveAll(ui => ui.UserName == userName);
          
            await base.OnDisconnectedAsync(exception);
        }

        /// <summary>
        /// 消息不直接发送接受者,先写入队列
        /// </summary>
        /// <returns></returns>
        public async Task SendAllMessage(UserModel userModel)
        {
            var durable = true;//约定使用持久化
           
            _logger.LogInformation(userModel.UserName + "写入消息" + userModel.Msg);
            _channel.ExchangeDeclare(_batchSendMessageQueue.Exchange, _batchSendMessageQueue.ExchangeType, durable);
            _channel.QueueDeclare(_batchSendMessageQueue.Queue, durable, false, false, null);
            _channel.QueueBind(_batchSendMessageQueue.Queue, _batchSendMessageQueue.Exchange, _batchSendMessageQueue.RouteKey, null);
            var sendBytes = Encoding.UTF8.GetBytes(userModel.Msg);
            _channel.BasicPublish(_batchSendMessageQueue.Exchange, _batchSendMessageQueue.RouteKey, null, sendBytes);
            await Task.CompletedTask;
            //Send.SendMessage(userModel.Msg);
            //Receiver.ReceiveMessage();
            //await Clients.All.SendAsync("receiveMessage", new
            //{
            //    userName = userModel.UserName,
            //    state = 200,
            //    msg = userModel.Msg
            //});
            //推送给所有连接ID的第一条数据
            //await Clients.Clients(OnlineUser.Select(q => q.ConnectionId).ToList()).SendAsync("receiveMessage", new {
            //            userName=userModel.UserName,
            //              state= 200,
            //           msg = userModel.Msg
            //        });
        }
        public async Task SendOneMessage(UserModel userModel)
        {
            var durable = true;//约定使用持久化
            var factory = new ConnectionFactory()
            {
                HostName = _rabbitConnectOptions.HostName,
                Port = _rabbitConnectOptions.Port,
                UserName = _rabbitConnectOptions.UserName,
                Password = _rabbitConnectOptions.Password,
            };
            _connection = factory.CreateConnection();
            _channel = _connection.CreateModel();
            _logger.LogInformation(userModel.UserName + "写入消息" + userModel.Msg);
            //_channel.ExchangeDeclare(_batchSendMessageQueue.Exchange, _batchSendMessageQueue.ExchangeType, durable);
            _channel.QueueDeclare("Test", durable, false, false, null);
            //_channel.QueueBind(_batchSendMessageQueue.Queue, _batchSendMessageQueue.Exchange, _batchSendMessageQueue.RouteKey, null);
            var sendBytes = Encoding.UTF8.GetBytes(userModel.Msg);
            //  _channel.BasicPublish(_batchSendMessageQueue.Exchange, _batchSendMessageQueue.RouteKey,null,sendBytes);
            _channel.BasicPublish("","Test", null, sendBytes);
            await Task.CompletedTask;
            //var conneId = OnlineUser.FirstOrDefault(ou => ou.UserName == userModel.UserName).ConnectionId;
            以下方法用户dictionary的反序列化,转json
             var conneId = Newtonsoft.Json.JsonConvert.DeserializeObject<UserModel>(receiver.Value.ToString()).ConnectionId;

            //await Clients.Client(conneId).SendAsync("receiveMessage",
            //                  new
            //                  {
            //                      userName = userModel.UserName,
            //                      state = 200,
            //                      msg = userModel.Msg
            //                  });

        }
        public  void  Process(IHubCallerClients Clients)
        {
  
            _logger.LogInformation("调用ExecuteAsync");
            using (var scope = _services.CreateScope())
            {


                var durable = true;//约定使用持久化
                var noack = false;//消息手动确认,否则消费者在接收到消息后会自动应答
                try
                {
                    if (_rabbitConnectOptions == null) return;
                    var factory = new ConnectionFactory()
                    {
                        HostName = _rabbitConnectOptions.HostName,
                        Port = _rabbitConnectOptions.Port,
                        UserName = _rabbitConnectOptions.UserName,
                        Password = _rabbitConnectOptions.Password,
                    };
                    _connection = factory.CreateConnection();
                    _channel = _connection.CreateModel();
                    //我们在消费端 从新进行一次 队列和交换机的绑定 ,防止 因为消费端在生产端 之前运行的 问题。
                    // _channel.ExchangeDeclare(_batchSendMessageQueue.Exchange, _batchSendMessageQueue.ExchangeType, durable);
                    _channel.QueueDeclare("Test", durable, false, false, null);
                    //_channel.QueueBind(_batchSendMessageQueue.Queue, _batchSendMessageQueue.Exchange, _batchSendMessageQueue.RouteKey, null);
                    #region 通过事件的形式,如果队列中有消息,则执行事件。建议采用这种方式。
                    _logger.LogInformation("开始监听队列:" +"Test" );//_batchSendMessageQueue.Queue
                                                         //  _channel.BasicQos(0, 1, false);//设置一个消费者在同一时间只处理一个消息,这个rabbitmq 就会将消息公平分发
                    var consumer = new EventingBasicConsumer(_channel);
                    //  _channel.BasicQos(0, 1, false);
                   
                    consumer.Received +=  (ch, ea) =>
                    {
                      
                        try
                        {

                            var content = Encoding.UTF8.GetString(ea.Body.ToArray());
                            _logger.LogInformation("获取到消息:" + content);
                            // TODO: 向用户推送消息
                            Clients.All.SendAsync("receiveMessage", new
                            {
                                userName = "测试人员",
                                state = 200,
                                msg = content
                            });
                        }
                        catch (Exception ex)
                        {
                            _logger.LogError(ex, "");
                        }
                        finally
                        {
                            _channel.BasicAck(ea.DeliveryTag, false);
                        }
                    };
                    _channel.BasicConsume("Test", noack, consumer);
                    #endregion
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, "");
                }
            }
        }
        public async Task StartAsync(CancellationToken cancellationToken)
        {
            try
            {
                //if (_rabbitConnectOptions == null) return Task.CompletedTask;
                //var factory = new ConnectionFactory()
                //{
                //    HostName = _rabbitConnectOptions.HostName,
                //    Port = _rabbitConnectOptions.Port,
                //    UserName = _rabbitConnectOptions.UserName,
                //    Password = _rabbitConnectOptions.Password,
                //};
                //_connection = factory.CreateConnection();
                //_channel = _connection.CreateModel();
           
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Rabbit连接出现异常");
            }
            await Task.CompletedTask;
        }

        /// <summary>
        /// Triggered when the application host is performing a graceful shutdown.
        /// </summary>
        /// <param name="cancellationToken">Indicates that the shutdown process should no longer be graceful.</param>
        /// <returns></returns>
        public Task StopAsync(CancellationToken cancellationToken)
        {
            if (_connection != null)
               this._channel.Close();
            this._connection.Close();
            
            return Task.CompletedTask;
        }

    }
        public class UserModel
    {
        public string ConnectionId { get; set; }
        public string Token { get; set; }
        public string UserName { get; set; }
        public string Msg { get; set; }
        public string State { get; set; }

    };

}

效果
前端写入消息
在这里插入图片描述
队列成功收到,我只想让请求排队,队列起到的作用只是作为一个缓冲
在这里插入图片描述
控制台输出,上面warn是log4net输出的内容
在这里插入图片描述
前端收到的反馈,后端通过集线器向所有在线的用户进行发送,这里逻辑好像有点问题,后续慢慢优化
在这里插入图片描述
不在线怎么办?不在线就写入数据库或者redis做持久化,下次用户连接的时候去库里面查询,查到就通过集线器直接推给用户
总结
服务端推送必然是要使用websocket协议,队列的作用是对请求进行排队,起到削减单次处理的并发量的作用,目前就这些,作者也是才入坑,说的不对的地方大家多多指出,谢谢~
推荐一篇关于服务托管的文章

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: .NET Core RabbitMQ是一个基于.NET Core平台的RabbitMQ客户端库,用于在应用程序实现消息队列的功能。RabbitMQ是一个开源的消息代理,它可以在分布式系统传递消息,并提供了高可用性、可扩展性和灵活性。通过使用.NET Core RabbitMQ,开发人员可以轻松地将消息队列集成到他们的应用程序,从而实现异步通信、任务分发和事件驱动等功能。同时,.NET Core RabbitMQ还提供了丰富的API和文档,使开发人员可以快速上手并轻松地使用它。 ### 回答2: .NET Core RabbitMQ是一个基于开源的AMQP协议的消息代理系统,是.NET Core的一种实现。它可以用于构建分布式应用程序,实现不同应用之间的异步通信,从而提高应用程序的可维护性、可扩展性、可靠性和响应性。RabbitMQ的核心概念包括生产者、Exchange、队列、路由键、消费者等。具体来说: 1. 生产者:向RabbitMQ发送消息的程序或系统。 2. Exchange:消息交换器,用于接收生产者发送的消息,并将消息发送到与之绑定的队列。 3. 队列:在RabbitMQ消息存储的地方,消费者从队列获取消息处理。 4. 路由键:用于指定消息要发往的Exchange。 5. 消费者:从队列获取消息并进行处理的程序或系统。 .NET Core RabbitMQ的主要优点包括: 1. 高度可靠性:根据AMQP协议,RabbitMQ提供的消息传递机制具有高度的可靠性,保证消息的传输不会出错。 2. 灵活性:RabbitMQ使用Exchange、路由键和队列的组合,使得消息的传递可以根据不同的需求进行灵活配置。 3. 异步通信:RabbitMQ采用异步通信方式,使得各个应用之间的通信不再是同步的,提高了响应性和可维护性。 4. 可扩展性:RabbitMQ可以通过增加Exchange和队列的数量,实现分布式的消息传递,从而提高了整个系统的可扩展性。 5. 开源:RabbitMQ是一个开源的消息代理系统,所有人都可以获得它的源代码,改进它,并将其用于自己的项目。 总之,.NET Core RabbitMQ提供了一种高效的、可靠的、灵活的消息传递机制,可以被广泛用于构建各种不同类型的分布式应用程序。 ### 回答3: .NET Core是微软推出的开源跨平台框架,可以在Windows、Linux和macOS等多个操作系统上运行;RabbitMQ是一个开源的消息队列协议,它支持多种消息传输模式和多个语言编写的客户端。.NET Core RabbitMQ则是.NET Core框架和RabbitMQ协议的结合体,它将.NET Core应用程序与RabbitMQ对接,实现异步消息传输和多服务之间的解耦。 .NET Core RabbitMQ通过实现AMQP协议(高级消息队列协议)来传递消息,AMQP协议在性能和稳定性上都有很好的表现。通过使用.NET Core RabbitMQ,可以非常容易地实现应用程序之间的异步消息传递,从而提高系统的可伸缩性和弹性。 .NET Core RabbitMQ提供了易于使用API和客户端,这些客户端可以与RabbitMQ消息代理进行通信,从而实现消息的发布和订阅。此外,还提供了一些管理工具,包括Web UI和CLI,可以用于管理消息代理,监视传输指标,配置安全策略等。 总之,.NET Core RabbitMQ是一个强大的消息传输协议,它允许.NET Core应用程序利用消息队列提供异步消息传输和解耦,从而提高应用程序的性能和稳定性。使用.NET Core RabbitMQ,开发人员可以轻松地将.NET Core应用程序与RabbitMQ进行对接,并实现可靠的异步消息传递。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值