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

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

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

1
services.AddSignalR();

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

1
2
3
4
5
6
7
8
9
10
11
12
13
/// <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:

1
2
3
4
5
6
7
8
9
10
11
12
13
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代表着路由匹配的地址:

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
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方法里面都要一个字符串,这个字符串很重要,客户端就是根据这个字符串来接收的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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);
   }

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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
 }

  

1
services.AddScoped<ISingalrContent, SingalrContent>();

  

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

1
2
3
4
5
6
7
8
9
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)这个方法:

1
2
3
4
5
6
7
8
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();

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 @   灬丶  阅读(375)  评论(0编辑  收藏  举报
编辑推荐:
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· C++代码改造为UTF-8编码问题的总结
阅读排行:
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
点击右上角即可分享
微信分享提示