隐藏

SignalR实现在线聊天室功能

发布:2020/10/28 16:52:32作者:管理员 来源:本站 浏览次数:1121

一、在线聊天室

1、新建解决方案 SignalROnlineChatDemo

 

2、新建MVC项目 SignalROnlineChatDemo.Web

(无身份验证)

 

 

3、安装SignalR

PM> install-package Microsoft.AspNet.SignalR

 

4、 创建一个称为 Startup.cs 的新类

复制代码
1 public class Startup 2  { 3 public void Configuration(IAppBuilder app) 4  { 5 // 有关如何配置应用程序的详细信息,请访问 http://go.microsoft.com/fwlink/?LinkID=316888 6  app.MapSignalR(); 7  } 8 }
复制代码

 

5、添加Hubs

复制代码
1 public class ChatHub : Hub 2  { 3 public void Hello() 4  { 5  Clients.All.hello(); 6  } 7 }
复制代码

 

6、Action/View

复制代码
 1 /// <summary>  2 /// 在线聊天室  3 /// </summary>  4 /// <returns></returns>  5 public ActionResult Chat(string groupName)  6  {  7 if (string.IsNullOrWhiteSpace(groupName))  8  {  9 return Content("groupName is nllOrWhiteSpace"); 10  } 11 return View((object)groupName); 12 }
复制代码
复制代码
 1 @model string  2 @{  3  ViewBag.Title = "Chat";  4 }  5  6 <style>  7  .chat-container div {  8  margin: 10px 0;  9 } 10  .dN { 11  display: none; 12 } 13 </style> 14 15 <h2>Chat <span id="chatRoomName"></span></h2> 16 17 <div class="chat-container"> 18 <ul id="discussion"></ul> 19 <div class="sendto-wrap dN"> 20  发送给: 21 <span></span> 22 </div> 23 <div class="sendcontent-wrap"> 24 <input type="text" name="message" /> 25 <input type="button" id="btnSendMessage" value="Send" /> 26 </div> 27 </div> 28 29 @section scripts{ 30 <script src="~/Scripts/jquery.signalR-2.2.0.min.js"></script> 31 <!-- Reference the autogenerated SignalR hub script. --> 32 <script src="~/signalr/hubs"></script> 33 <script type="text/javascript"> 34  $(function () { 35 //聊天室编号 36 var groupName = '@Model'; 37 //成员昵称 38 var nickName = ''; 39 //成员聊天Id 40 var connectionId = ''; 41 42 //Reference the auto-generated proxy for the hub. 43 var chat = $.connection.chatHub; 44 45  $("#chatRoomName").html(groupName); 46 47 //Get the user name and store it to prepend to message 48 while (nickName == '' || $.trim(nickName) == '') { 49  nickName = prompt('Enter your name:', '') 50  $('#displayname').val(nickName); 51  } 52 53  $('#message').focus(); 54 55  }); 56 57 </script> 58 }
复制代码

 

7、功能1:新成员加入,群发欢迎

复制代码
 1 /// <summary>  2 /// newcomer 进入聊天室  3 /// </summary>  4 /// <param name="groupName"></param>  5 public void JoinGroup(string groupName, string userNickName)  6  {  7 //对聊天室成员群发‘新成员加入’  8 var conId = Context.ConnectionId;  9 10  Groups.Add(conId, groupName); 11 12 var psn = new ChatPerson() 13  { 14 ConnectionId = conId, 15 NickName = userNickName, 16 GroupName = groupName, 17  }; 18 19  Clients.Caller.setCallerInfo(psn); 20 //Clients.Group(groupName).welcome(psn); //不能广播给自己,所以分成了两句 21  Clients.Caller.welcome(psn); 22  Clients.Group(groupName, conId).welcome(psn); 23 24 }
复制代码
复制代码
1  //新成员身份信息(connectionId) 2  chat.client.setCallerInfo = function (psn) { 3  connectionId = psn.ConnectionId; 4  groupName = psn.groupName; 5  }; 6  //welcome newcomer 7  chat.client.welcome = function (psn) { 8 $("#discussion").append('<li><a href="javascript:;" data-conId="' + psn.ConnectionId + '">' + psn.NickName + '</a>加入了聊天室</li>'); 9 };
复制代码

结果截图:

 

8、功能2:群发

复制代码
 1 /// <summary>  2 /// newcomer进入聊天室,对聊天室成员群发‘新成员加入’  3 /// </summary>  4 /// <param name="groupName"></param>  5 public void JoinGroup(string groupName, string userNickName)  6  {  7 var conId = Context.ConnectionId;  8 var psn = new ChatPerson()  9  { 10 ConnectionId = conId, 11 NickName = userNickName, 12 GroupName = groupName, 13  }; 14 15 //成员信息计入Redis中 16 //var redisClient = RedisManager.GetClient(); 17 //if (redisClient.Get<ChatPerson>(string.Format(RedisKey.SignalROnlineChatDemoWebModels_ChatPerson, psn.ConnectionId)) != null) 18 //{ 19 // //connected 20 // return; 21 //} 22 //redisClient.Set<ChatPerson>(string.Format(RedisKey.SignalROnlineChatDemoWebModels_ChatPerson, psn.ConnectionId), psn); 23 //redisClient.SaveAsync(); 24 using (var redisClient = RedisManager.GetClient()) 25  { 26 IRedisTypedClient<ChatPerson> psns = redisClient.As<ChatPerson>(); 27 if (psns.GetValue(string.Format(RedisKey.SignalROnlineChatDemoWebModels_ChatPerson, psn.ConnectionId)) != null) 28  { 29 //connected 30 return; 31  } 32 psns.SetValue(string.Format(RedisKey.SignalROnlineChatDemoWebModels_ChatPerson, psn.ConnectionId), psn); 33  } 34 35  Groups.Add(conId, groupName); 36  Clients.Caller.setCallerInfo(psn); 37 //Clients.Group(groupName).welcome(psn); //不能广播给自己,所以分成了两句 38  Clients.Caller.welcome(psn); 39  Clients.Group(groupName, conId).welcome(psn); 40  } 41 42 /// <summary> 43 /// 群发内容 44 /// </summary> 45 public void SendMessage(string message) 46  { 47 if (string.IsNullOrWhiteSpace(message)) 48  { 49 return; 50  } 51 var conId = Context.ConnectionId; 52 ChatPerson psn = null; 53 var redisClient = RedisManager.GetClient(); 54 if ((psn = redisClient.Get<ChatPerson>(string.Format(RedisKey.SignalROnlineChatDemoWebModels_ChatPerson, conId))) == null 55 || string.IsNullOrWhiteSpace(psn.GroupName) || string.IsNullOrWhiteSpace(psn.NickName) 56  ) 57  { 58 //invalid ConnectionId 59 return; 60  } 61  Clients.Group(psn.GroupName).sendMessage(conId, psn.NickName, message); 62 }
复制代码
1 //群发内容 2 chat.client.sendMessage = function (sendFromConnectionId, sendFromNickName, message) { 3 $("#discussion").append('<li><a href="javascript:;" data-conId="' + sendFromConnectionId + '">' + sendFromNickName + '</a>:' + message + '</li>'); 4 };

结果截图:

 

9、功能3:回复

复制代码
 1 /// <summary>  2 /// 回复(@)  3 /// </summary>  4 /// <param name="sendTo"></param>  5 /// <param name="message"></param>  6 public void SendMessageTo(string sendTo, string message)  7  {  8 if (string.IsNullOrWhiteSpace(message) || string.IsNullOrWhiteSpace(sendTo))  9  { 10 return; 11  } 12 var connId = Context.ConnectionId; 13 if (connId == sendTo) 14  { 15 return; 16  } 17 ChatPerson curPerson = null; 18 ChatPerson desPerson = null; 19 var redisClient = RedisManager.GetClient(); 20 if ((curPerson = redisClient.Get<ChatPerson>(string.Format(RedisKey.SignalROnlineChatDemoWebModels_ChatPerson, connId))) == null 21 || string.IsNullOrWhiteSpace(curPerson.GroupName) || string.IsNullOrWhiteSpace(curPerson.NickName) 22  ) 23  { 24 //invalid ConnectionId 25 return; 26  } 27 if ((desPerson = redisClient.Get<ChatPerson>(string.Format(RedisKey.SignalROnlineChatDemoWebModels_ChatPerson, sendTo))) == null 28 || string.IsNullOrWhiteSpace(desPerson.GroupName) || string.IsNullOrWhiteSpace(desPerson.NickName) 29  ) 30  { 31 //invalid ConnectionId 32 return; 33  } 34 if (curPerson.GroupName != desPerson.GroupName) 35  { 36 return; 37  } 38 39  Clients.Group(curPerson.GroupName).sendMessageTo(curPerson.ConnectionId, curPerson.NickName, desPerson.ConnectionId, desPerson.NickName, message); 40 }
复制代码
1 //at回复 2 chat.client.sendMessageTo = function (fromConnId, fromNickName, toConnId, toNickName, message) { 3 $("#discussion").append('<li data-connId="' + fromConnId + '" data-nickName="' + fromNickName + '"><a href="javascript:;">' + fromNickName + '</a>对<a href="javascript:;">' + toNickName + '</a>&nbsp;说:' + message + ' ' 4 + (fromConnId == connectionId ? '' : '&nbsp;&nbsp;&nbsp;<a href="javascript:;" action="at">&#64;他</a>') + '' + (fromConnId == connectionId ? '' : '&nbsp;&nbsp;&nbsp;<a title="屏蔽其发言" href="javascript:;" action="shielding">屏蔽</a>') + '</li>'); 5 };
复制代码
 1 //发送  2 $("#btnSendMessage").click(function () {  3 var desConnId = $('.sendto-wrap a').attr('data-desConnId');  4 if (!desConnId || desConnId.length == 0) {  5 //群发  6 chat.server.sendMessage($('[name=message]').val());  7  }  8 else {  9 //回复 10 chat.server.sendMessageTo(desConnId, $('[name=message]').val()); 11  } 12 $('.sendto-wrap').addClass('dN'); 13 $('.sendto-wrap a').attr('data-desConnId', ''); 14 $('[name=message]').val('').focus(); 15  }); 16 //at 17 $('#discussion').on('click', '[action=at]', function () { 18 var desConnId = $(this).closest('li').attr('data-connId'); 19 var desNickName = $(this).closest('li').attr('data-nickName'); 20 $('.sendto-wrap').removeClass('dN'); 21 $('.sendto-wrap a').attr('data-desConnId', desConnId).html(desNickName); 22 });
复制代码

结果截图:

 

10、功能4:私信

复制代码
 1 /// <summary>  2 /// 私信给  3 /// </summary>  4 /// <param name="sendTo"></param>  5 /// <param name="message"></param>  6 public void PrivateMessageTo(string sendTo, string message)  7  {  8 if (string.IsNullOrWhiteSpace(message) || string.IsNullOrWhiteSpace(sendTo))  9  { 10 return; 11  } 12 var connId = Context.ConnectionId; 13 if (connId == sendTo) 14  { 15 return; 16  } 17 ChatPerson curPerson = null; 18 ChatPerson desPerson = null; 19 var redisClient = RedisManager.GetClient(); 20 if ((curPerson = redisClient.Get<ChatPerson>(string.Format(RedisKey.SignalROnlineChatDemoWebModels_ChatPerson, connId))) == null 21 || string.IsNullOrWhiteSpace(curPerson.GroupName) || string.IsNullOrWhiteSpace(curPerson.NickName) 22  ) 23  { 24 //invalid ConnectionId 25 return; 26  } 27 if ((desPerson = redisClient.Get<ChatPerson>(string.Format(RedisKey.SignalROnlineChatDemoWebModels_ChatPerson, sendTo))) == null 28 || string.IsNullOrWhiteSpace(desPerson.GroupName) || string.IsNullOrWhiteSpace(desPerson.NickName) 29  ) 30  { 31 //invalid ConnectionId 32 return; 33  } 34 if (curPerson.GroupName != desPerson.GroupName) 35  { 36 return; 37  } 38  Clients.Caller.myPrivateMessageTo(desPerson.ConnectionId, desPerson.NickName, message); 39  Clients.Client(sendTo).bePrivateMessageTo(curPerson.ConnectionId, curPerson.NickName, message); 40 }
复制代码
复制代码
1 //私信 2 $('#discussion').on('click', '[action=privateAt]', function () { 3 var desConnId = $(this).closest('li').attr('data-connId'); 4 var desNickName = $(this).closest('li').attr('data-nickName'); 5 $('.sendto-wrap span.sendto-to').html('私信给'); 6 $('.sendto-wrap').removeClass('dN'); 7 $('.sendto-wrap a').attr('data-desConnId', desConnId).attr('data-desAction', 'privateAt').html(desNickName); 8 });
复制代码
复制代码
 1 //privateAt私信  2 //我发的  3 chat.client.myPrivateMessageTo = function (toConnId, toNickName, message) {  4 $("#discussion").append('<li data-connId="' + connectionId + '" data-nickName="' + nickName + '">我对<a href="javascript:;">' + toNickName + '</a>&nbsp;说:' + message + ' '  5 //+ getActionBlockHtml(connectionId)  6  );  7  };  8 //发给我的  9 chat.client.bePrivateMessageTo = function (fromConnId, fromNickName, message) { 10 $("#discussion").append('<li data-connId="' + fromConnId + '" data-nickName="' + fromNickName + '"><a href="javascript:;">' + fromNickName + '</a>对我私信说:' + message + ' ' 11 + getActionBlockHtml(fromConnId) 12  ); 13 };
复制代码

结果截图:

 

11、功能5:屏蔽 

复制代码
 1 /// <summary>  2 ///  3 /// </summary>  4 public class PersonShielding  5  {  6 /// <summary>  7 /// 成员的ConnectionId  8 /// </summary>  9 public string ConnectionId { get; set; } 10 11 /// <summary> 12 /// 被屏蔽 我被哪些人屏蔽(这样设计似乎不合理,但好用) 13 /// </summary> 14 public string[] BeShieldingByConnIdArr { get; set; } 15 }
复制代码

 

复制代码
 1 /// <summary>  2 /// 屏蔽某人的发言  3 /// </summary>  4 /// <param name="desConId"></param>  5 public void Shielding(string desConnId)  6  {  7 if (string.IsNullOrWhiteSpace(desConnId))  8  {  9 return; 10  } 11 var connId = Context.ConnectionId; 12 if (connId == desConnId) 13  { 14 return; 15  } 16 var redisClient = RedisManager.GetClient(); 17 ChatPerson curPerson = null; 18 ChatPerson desPerson = null; 19 if ((curPerson = redisClient.Get<ChatPerson>(string.Format(RedisKey.SignalROnlineChatDemoWebModels_ChatPerson, connId))) == null 20 || string.IsNullOrWhiteSpace(curPerson.GroupName) || string.IsNullOrWhiteSpace(curPerson.NickName) 21  ) 22  { 23 //invalid ConnectionId 24 return; 25  } 26 if ((desPerson = redisClient.Get<ChatPerson>(string.Format(RedisKey.SignalROnlineChatDemoWebModels_ChatPerson, desConnId))) == null 27 || string.IsNullOrWhiteSpace(desPerson.GroupName) || string.IsNullOrWhiteSpace(desPerson.NickName) 28  ) 29  { 30 //invalid ConnectionId 31 return; 32  } 33 var personShielding = redisClient.Get<PersonShielding>(string.Format(RedisKey.SignalROnlineChatDemoWebModels_PersonShielding, desConnId)); 34 if (personShielding == null) 35  { 36 personShielding = new PersonShielding() 37  { 38 ConnectionId = desConnId, 39 BeShieldingByConnIdArr = new string[] { connId } 40  }; 41  } 42 else 43  { 44 if (personShielding.BeShieldingByConnIdArr == null) 45  { 46 personShielding.BeShieldingByConnIdArr = new string[] { connId }; 47  } 48 else if (!personShielding.BeShieldingByConnIdArr.Contains(connId)) 49  { 50 personShielding.BeShieldingByConnIdArr = personShielding.BeShieldingByConnIdArr.Union(new string[] { connId }).ToArray(); 51  } 52  } 53 redisClient.Set<PersonShielding>(string.Format(RedisKey.SignalROnlineChatDemoWebModels_PersonShielding, desConnId), personShielding); 54  redisClient.SaveAsync(); 55 56  Clients.Caller.shieldingSuccess(desConnId, desPerson.NickName); 57 }
复制代码
复制代码
 1 var personShielding = redisClient.Get<PersonShielding>(string.Format(RedisKey.SignalROnlineChatDemoWebModels_PersonShielding, connId));  2 if (personShielding != null && personShielding.BeShieldingByConnIdArr != null && personShielding.BeShieldingByConnIdArr.Length > 0)  3  {  4 //屏蔽我的 不发  5  Clients.Group(curPerson.GroupName, personShielding.BeShieldingByConnIdArr).sendMessage(connId, curPerson.NickName, message);  6  }  7 else  8  {  9  Clients.Group(curPerson.GroupName).sendMessage(connId, curPerson.NickName, message); 10 }
复制代码
1 var personShielding = redisClient.Get<PersonShielding>(string.Format(RedisKey.SignalROnlineChatDemoWebModels_PersonShielding, connId)); 2 if (personShielding == null || personShielding.BeShieldingByConnIdArr == null || !personShielding.BeShieldingByConnIdArr.Contains(sendTo)) 3  { 4 //sendTo没有屏蔽我,那我就发 5  Clients.Group(curPerson.GroupName).sendMessageTo(curPerson.ConnectionId, curPerson.NickName, desPerson.ConnectionId, desPerson.NickName, message); 6 }
复制代码
1 //屏蔽 2 $('#discussion').on('click', '[action=shielding]', function () { 3 if (confirm('确定屏蔽其发言么!')) { 4 var desConnId = $(this).closest('li').attr('data-connId'); 5  chat.server.shielding(desConnId); 6  } 7 });
复制代码
1 //屏蔽成功 callback 2 chat.client.shieldingSuccess = function (desConnId, desNickName) { 3 $("#discussion").append('<li data-connId="' + connectionId + '" data-nickName="' + nickName + '">你成功屏蔽了<a href="javascript:;">' + desNickName + '</a>的发言' 4 + '</li>' 5  ); 6 };

结果截图:

 

二、其他工作

1、退出后重新接入,能确定唯一身份么?

关于这个问题,通常的解决方案是:使用UserId,以UserId作为主线。当时是想到一点就写一点的!

如果能在页面加载$.connection.hub.start()的时候带入上次使用的connectionId就好了,但暂时我还没发现能这么做

(如果谁知道怎么解决,分享给我下,谢谢!!!)

 

2、退出聊天室时从Redis清理用户的相关数据

先来看看Hub的这三个方法:Hub.OnConnected 、Hub.OnDisconnected 、 Hub.OnReconnected

复制代码
 1 /// <summary>  2 /// Called when the connection connects to this hub instance.  3 /// </summary>  4 /// <returns></returns>  5 public override Task OnConnected()  6  {  7 DefaultLoggerProvider.Instance.InfoFormat("ChatHub.OnConnected, ConnectionId:{0}", Context.ConnectionId);  8 return base.OnConnected();  9  } 10 11 /// <summary> 12 /// Called when a connection disconnects from this hub gracefully or due to a timeout. 13 /// </summary> 14 /// <param name="stopCalled"></param> 15 /// <returns></returns> 16 public override Task OnDisconnected(bool stopCalled) 17  { 18 DefaultLoggerProvider.Instance.InfoFormat("ChatHub.OnDisconnected, ConnectionId:{0}", Context.ConnectionId); 19 return base.OnDisconnected(stopCalled); 20  } 21 22 /// <summary> 23 /// Called when the connection reconnects to this hub instance. 24 /// </summary> 25 /// <returns></returns> 26 public override Task OnReconnected() 27  { 28 DefaultLoggerProvider.Instance.InfoFormat("ChatHub.OnReconnected, ConnectionId:{0}", Context.ConnectionId); 29 return base.OnReconnected(); 30 }
复制代码

从查看日志可以得出结论:

Onconnected 是在加载页面/刷新页面重新加载的时候触发($.connection.hub.start())。

OnDisConnected 在离开(关闭选项卡/刷新页面)的时候触发($.connection.hub.stop())。

OnReconnected 仅会在生成网站的时候触发。重启网站不会触发。(暂不知所以然)

所以可以这样

复制代码
 1 public override Task OnDisconnected(bool stopCalled)  2  {  3 var connId = Context.ConnectionId;  4  5 DefaultLoggerProvider.Instance.InfoFormat("ChatHub.OnDisconnected, ConnectionId:{0}, stopCalled:{1}", connId, stopCalled);  6  7 using (var redisClient = RedisManager.GetClient())  8  {  9 redisClient.Remove(string.Format(RedisKey.SignalROnlineChatDemoWebModels_ChatPerson, connId)); 10 redisClient.Remove(string.Format(RedisKey.SignalROnlineChatDemoWebModels_PersonShielding, connId)); 11  redisClient.SaveAsync(); 12  } 13 14 return base.OnDisconnected(stopCalled); 15 }
复制代码

 

附:源码下载