我的微服务项目之设计一个消息通知

       上一遍文章我讲了下自己的IdentityServer4整合到自己的项目里面,最近有改造了一下自己的原有的消息通知的功能,这里我用的是.net自带的组件Signalr,这是一款很不错的Socket组件,用起来非常简单,我这里讲一下自己的设计思路。,所以也把这个分享出来,如果大佬有觉得不妥的地方,欢迎指出。

       首先是添加项目对Signalr的支持

  services.AddSignalR();

  Signalr提供了一个抽象类Hub,这是一个很重要的抽象类,我们要想使用Signalr,就必须实现它。我结合自己的需求,因为需要指点的客户发送,每个用户登录之后都会连接Singalr产生一个connectionid,准确的说是每个页面都会产生一个connectionid,所以难免会有一个用户打开多个相同的页面,这样就要给每个页面都发送消息通知。所以我的做法是将用户账号和connectionid存入内存中,connectionId作为key:

      /// <summary>
        /// 连接对象集合
        /// </summary>
        public static ConcurrentDictionary<string, string> ConnectionMaps = new ConcurrentDictionary<string, string>();
        /// <summary>
        /// 添加连接对象
        /// </summary>
        /// <param name="connectionId"></param>
        /// <param name="value"></param>
        public static void SetConnectionMaps(string connectionId, string value)
        {
            ConnectionMaps.AddOrUpdate(connectionId, value, (string s, string y) => value);
        }

  添加一个类继承自Hub,Context是Hub一个上下文属性,SetConnectionMaps是自己定义的一个方法,以至于客户端可以调用这个方法,当你刷新或关闭页面时就会断开连接调用OnDisconnectedAsync,刷新时会产生新的connectionid:

  public class SingalrClient : Hub
    {
        public void SetConnectionMaps(string account)
        {
            string connectionid = Context.ConnectionId;
            SingalrConnection.SetConnectionMaps(connectionid, account);
        }
        public override Task OnDisconnectedAsync(Exception exception)
        {
            SingalrConnection.Remove(Context.ConnectionId);
            return base.OnDisconnectedAsync(exception);
        }
    }

  然后再设置路由信息,/SingalrClient代表着路由匹配的地址:

   app.UseEndpoints(endpoints =>
            {
                endpoints.MapHub<SingalrClient>("/SingalrClient");
            });

    以上是完成了服务端的代码,接下是客户端,客户端需要添加引用 signalr.js,下载方法自行百度。引入好之后,根据自己的需求,在指定的页面来设置连接,我这里是登录之后调用SetConnectionMaps来存储账号与connectionid的内容:

            var connection = new signalR.HubConnectionBuilder().withUrl("http://127.0.0.1:5004/SingalrClient").build();
            $.ajax({
                url: api + '/user/loginuser',
                type: 'get',
                success: function (response) {
                        connection.start().then(function () {
                            connection.invoke('SetConnectionMaps', response.data.account).catch(function(errer){
                                console.error(errer.toString())
                            });
                        });
                    } 
                }
            });

  这个时候我们运行程序,就会看到如下的代表初步已经完成:

       接下来我们来看如何向客户端发送消息,我来封装一个发送消息的类和它的接口,并且通过注入IHubContext,当然,你也可以直接通过Hub来直接发送,可以看到,我在每一个SendAsync方法里面都要一个字符串,这个字符串很重要,客户端就是根据这个字符串来接收的方法:

 public  interface ISingalrContent
    {

        /// <summary>
        /// 向所有客户端(用户)发送消息
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        Task SendAllClientsMessage(Message message);
        /// <summary>
        /// 向指定的部分客户端(用户)发送消息
        /// </summary>
        /// <param name="connectionIds"></param>
        /// <param name="message"></param>
        /// <returns></returns>
        Task SendSomeClientsMessage(IReadOnlyList<string> connectionIds, Message message);
        /// <summary>
        /// 向指定的客户端(用户)发送消息
        /// </summary>
        /// <param name="connectionIds"></param>
        /// <param name="message"></param>
        /// <returns></returns>
        Task SendClientMessage(Message message);
    }

  

   public class SingalrContent : ISingalrContent
    {
       
        private IHubContext<SingalrClient> _hubContext;
        public SingalrContent(IHubContext<SingalrClient> hubContext)
        {
            _hubContext = hubContext;
        }
          
        #region 向客户端发送消息
        public async Task SendAllClientsMessage(Message message)
        {
            await _hubContext.Clients.All.SendAsync("AllReviceMesage", message);
        }
        public async Task SendSomeClientsMessage(IReadOnlyList<string> connectionIds, Message message)
        {
            if (connectionIds == null || connectionIds.Count == 0)
                throw new ArgumentNullException("指定的客户端连接为空");
            await _hubContext.Clients.Clients(connectionIds).SendAsync("SendClientMessage", message);
        }

        public async Task SendClientMessage(Message message)
        {
            if (string.IsNullOrEmpty(message.Revicer))
                throw new ArgumentNullException("指定的客户端连接为空");
            IReadOnlyList<string> connectionsByUser = SingalrConnection.GetConnectionIds(message.Revicer);
            await _hubContext.Clients.Clients(connectionsByUser).SendAsync("ReviceMesage", message);
        }
        #endregion
    }

  

   services.AddScoped<ISingalrContent, SingalrContent>();

  

     然后当又消息通过SingalrContent的发送时,客户端通过设置 connection.on的方法来确定接收的数据,就好比我这里,我需要根据登录的账号来接收指定的犯法,所以我传入ReviceMessage来接收,:

            connection.on('ReviceMesage', function (message) {
                var count=$('#notice').html();
                var oldNum = parseInt(count);
                var newNum = parseInt(message.data);
                $('#notice').html(newNum);
                if (oldNum < newNum) {
                    $('#notice').addClass('blink');
                }
            });

  我还有个设计的是就是向所有的页面,不管有没有登录都发生消息,AllReviceMesage变对应的是SendAllClientsMessage(Message message)这个方法:

	var connection = new signalR.HubConnectionBuilder().withUrl("http://111.229.211.248:5004/SingalrClient").build();
    connection.on('AllReviceMesage',function(reviceMessage){
        var data = {
            'list': reviceMessage.data
        };
        bindWhisper(data);
    } );
    connection.start();

  具体的调用就是这样子的:

     public void SendWhisper(List<WhisperDTO> whisperDTOs)
        {          
            Message message = new Message();
            message.Data = whisperDTOs;
            _singalrContent.SendAllClientsMessage(message);
        }

        public void SendTidingsCount(string account, int count)
        {
            Message message = new Message();
            message.Data = count;
            message.Revicer = account;
            _singalrContent.SendClientMessage(message);
        }

  到此,我的设计思路已经讲完了,具体的代码可以参考:https://github.com/Hansdas/Blog_New/tree/master/Socket,我的项目的地址是:http://www.ttblog.site/

     

posted @ 2021-03-08 11:46  灬丶  阅读(349)  评论(0编辑  收藏  举报