什么是 SignalR
目前我用業余時間正在做一個博客系統,其中有個功能就是評論通知,就是假如A用戶評論B用戶的時候,如果B用戶首頁處于打開狀態,那么就會提示B用戶有未讀消息。暫時用SignalR來實現這個功能。我也是看了兩天的資料才明白怎么去使用。
關于SignalR的理論知識可以去官網或者百度,我這里只是結合自己的功能來分享下,如果有錯,請原諒指出。
下載js
SignalR是需要微軟提供的js,因為我的項目是前后端分離的,所以我是單獨下載到一個文件夾,然后復制js到我的前端項目里。只需要signalr.js
頁面加載創建連接
//創建連接 var connection = new signalR.HubConnectionBuilder().withUrl('http://127.0.0.1:5000/chatHub').build();//ajax執行成功執行 $.ajax({success: function (response) { connection.start().then(function () { connection.invoke('SetConnectionMaps', response.data.account); } }, });
首先你要了解到SignalR基本運行的原理,官網:https://docs.microsoft.com/zh-cn/aspnet/core/tutorials/signalr?view=aspnetcore-3.1&tabs=visual-studio
你可以直接繼承Hub這個類,我這里用的是強類型Hub,我就是為了讓前端和后端統一下。剛開始Hub我糾結了好久,不知道怎么用,最后我手動做了下,認為它只是為了方便前端和后端統一。
如果你只是簡單的繼承Hub類,你就必須調用SendAsync方法,并且指定前端接收觸發的方法名稱“InvokeMessage”,如果你后端和前端名字對應不上,就會有問題。
public class SingalrService : Hub {private ISingalrSvc _singalrSvc;public SingalrService(ISingalrSvc singalrSvc) { _singalrSvc = singalrSvc; }public async Task SendMessageAsync(Message sendMessage) {await Clients.All.SendAsync("InvokeMessage",sendMessage); }public void SetConnectionMaps(string account) {string connectionid = Context.ConnectionId; _singalrSvc.SetConnectionMaps(connectionid, account); }public override Task OnDisconnectedAsync(Exception exception) { _singalrSvc.Remove(Context.ConnectionId);return base.OnDisconnectedAsync(exception); } }
所以有了強類型Hub,自己定義一個接口,提過方法InvokeMessage供前前端調用。
/// /// 客戶端js調用方法/// public interface ISingalrClient {Task InvokeMessage(Message sendMessage); }public class SingalrService : Hub {private ISingalrSvc _singalrSvc;public SingalrService(ISingalrSvc singalrSvc){ _singalrSvc = singalrSvc; }public void SetConnectionMaps(string account){string connectionid = Context.ConnectionId; _singalrSvc.SetConnectionMaps(connectionid, account); }//連接中斷時執行,微軟這樣描述的://重寫 OnDisconnectedAsync 虛方法,以便在客戶端斷開連接時執行操作。如果客戶端故意斷開連接(例如,通過調用 connection.stop()),exception 參數將 null。//但是,如果客戶端由于錯誤(例如網絡故障)而斷開連接,則 exception 參數將包含描述失敗的異常public override Task OnDisconnectedAsync(Exception exception){ _singalrSvc.Remove(Context.ConnectionId);return base.OnDisconnectedAsync(exception); } }
這個時候一個用戶打開了首頁,然后首頁有個js方法來初始化連接,同一個頁面內的connectionid是一樣的,每次刷新或新打開一個窗口的新頁面的connectionid是不一樣的,并且你刷新頁面或者關掉會認為是連接中斷,會執行OnDisconnectedAsync方法,這個方法時SingalR自帶的,它是個虛方法,你也可以重寫,就像我一樣。我這里的代碼邏輯是將連接id和當前登錄人作為鍵值對存入內存,然后用戶關掉頁面就會執行OnDisconnectedAsync方法,將相關的coonectionid從內存刪掉:??
layui.use(['element', 'layer'], function () {var element = layui.element; element.render('nav'); initLoad();//初始化連接,每個頁面的connection的connectionid是一樣的,但是每次創建的不一樣var connection = new signalR.HubConnectionBuilder().withUrl('http://127.0.0.1:5000/chatHub').build();//綁定后臺觸發的方法,前面已經講過了,具體業務還沒實現, connection.on('InvokeMessage', (reviceMessage) => {var v = reviceMessage; }); $.ajax({url: url + 'user/userInfo',type: 'get',dataType: 'json',beforeSend: function (xhr) { doBeforeSend(xhr); },success: function (response) {if (response.code == '1') { $("#nologin").show(); $("#user").hide(); }else { $("#nologin").hide(); $("#user").show(); $("#photo").attr('src', response.data.headPhoto);//連接開始 connection.start().then(function () {//調用后臺方法,不是api接口,將當前登錄人賬號傳過去 connection.invoke('SetConnectionMaps', response.data.account); }) } },complete: function (xhr) { doComplete(xhr); }, }); });
這個時候連接已經創建完成,并且用戶并沒有關閉首頁,連接一直處于連接狀態。這個時候另一個用戶打開了一篇文章詳情,并且對它評論提交內容后,我讓它觸發了一個連接SingalR的事件,
form.on('submit(review)', function (data) { loading = layer.load(2);var commentModel = {'Content': data.field.desc, } $.ajax({url: url + 'article/review/' + id,contentType: 'application/json; charset=utf-8',type: 'post',datatype: 'json',data: JSON.stringify(commentModel),beforeSend: function (xhr) { doBeforeSend(xhr); },success: function (response) {if (response.code == 0) {//另一個用戶創建了連接var connection = new signalR.HubConnectionBuilder().withUrl('http://127.0.0.1:5000/chatHub').build(); connection.start().then(function () {var apiRoute=url+'Singalr/admin';//admin是我設置死的,實際應該是自己判斷,會調用下面的api,[Route("api/[controller]")],SingalR也是支持api調用的var token=localStorage.getItem('token'); fetch(apiRoute,{method:'get',headers:{'Authorization':'Bearer ' + token } }) event.preventDefault(); }) layer.close(loading); } else { layer.close(loading); layer.msg("評論失敗", {icon: 5 }); } },complete: function (xhr) { doComplete(xhr); }, })
[ApiController]public class SingalrController : ControllerBase{private IHubContext _hubContext;private ISingalrSvc _singalrSvc;public SingalrController(IHubContext hubContext, ISingalrSvc singalrSvc) { _hubContext = hubContext; _singalrSvc = singalrSvc; }/// /// 查詢未處理數量/// /// /// [HttpGet("{account}")]public async Task NewsCount(string account) { Message sendMessage = new Message(); sendMessage.Data = "11";//剛已經講了,用戶加載首頁的時候已經把connectionid和account存入到了內存里面,現在再取用戶相關的connectionID,如果直接調用Clinets.ALL就是給所有客戶端發送消息 IReadOnlyList connectionIds = (IReadOnlyList)_singalrSvc.GetConnectionIds(account);await _hubContext.Clients.Clients(connectionIds).InvokeMessage(sendMessage); }}
這個時候調用了這個api執行了里面的_hubContext.Clients.Clients(connectionIds).InvokeMessage(sendMessage),connectionIds是根據業務邏輯所判斷的觸發的那些客戶端;然后前端會根據方法名響應對應的js代碼,如下
layui.use(['element', 'layer'], function () {var element = layui.element; element.render('nav'); initLoad();var connection = new signalR.HubConnectionBuilder().withUrl('http://127.0.0.1:5000/chatHub').build();
//我認為是一個用來偵聽服務端方法的js connection.on('InvokeMessage', (reviceMessage) => {var v = reviceMessage;
//響應后端方法成功后,就開始自己的業務邏輯 }); $.ajax({ url: url + 'user/userInfo',type: 'get', dataType: 'json', beforeSend: function (xhr) { doBeforeSend(xhr); }, success: function (response) {if (response.code == '1') { $("#nologin").show(); $("#user").hide(); }else { $("#nologin").hide(); $("#user").show(); $("#photo").attr('src', response.data.headPhoto); connection.start().then(function () { connection.invoke('SetConnectionMaps', response.data.account); }) } }, complete: function (xhr) { doComplete(xhr); }, }); });
原文地址:
https://www.cnblogs.com/MrHanBlog/p/11996689.html