我的微服务项目之设计一个消息通知
上一遍文章我讲了下自己的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/
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· C++代码改造为UTF-8编码问题的总结
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库