使用ABP SignalR重構消息服務
最近協助蟹老闆升級新框架,維護基礎設施服務,目前已經穩了。
早上蟹老闆看到我進入公司,馬上就叫停我,說我爲什麼左腳先進公司,你這樣會讓我很難做耶,這樣把我給你一次機會把現在的消息服務重構了,我就放過你這一次。(當時我都沒有反應過來,蟹老闆就準備和我講需求了,我趕緊着小本子開始記需求)
背景
我們需要記錄所有用戶的在線狀況(登錄的設備存在多個設備同時登錄)
、指定用戶下線
、實時接收消息
技術你可以自由技術發揮,今天中午之前給我一個設計概要。(嗚嗚,天空是蔚藍色、窗外還有千紙鶴)
技術點
- SignalR
SignalR 是一個開放源代碼庫,可用於簡化嚮應用添加實時 Web 功能。 實時 Web 功能使服務器端代碼能夠將內容推送到客戶端。 - Redis
Redis 是一個開源(BSD 許可)的內存數據結構存儲,用作數據庫、緩存和消息代理。 - Jwt
JSON Web Token (JWT) 是一個開放標準 ( RFC 7519 ),它定義了一種緊湊且自包含的方式,用於在各方之間以 JSON 對象的形式安全傳輸信息。此信息可以驗證和信任,因爲它是數字簽名的。JWT 可以使用密鑰(使用HMAC算法)或使用RSA或ECDSA的公鑰/私鑰對進行簽名。
爲什麼使用SignalR
-
ASP.NET Core SignalR 的一些功能
- 自動處理連接管理。
- 同時向所有連接的客戶端發送消息。 例如聊天室。
- 向特定客戶端或客戶端組發送消息。
- 對其進行縮放,以處理不斷增加的流量。
-
SignalR支持如下的方式實現實時通信(SignalR會自動選擇服務器和客戶端能力範圍內的最佳通信方式)
- WebSockets:是一種在單個TCP連接上進行全雙工通信的協議,使得服務器和瀏覽器的通信更加簡單,服務端可以主動發送信息。
- Server-Sent Events:SSE 與 WebSocket 作用相似,都是建立瀏覽器與服務器之間的通信渠道,然後服務器向瀏覽器推送信息。WebSocket是雙向的,而SSE是單向的。
- Long Polling(長輪詢) :和傳統的輪詢原理一樣,只是服務端不會每次都返回響應信息,只有有數據或超時了纔會返回,從而減少了請求次數。
-
SignalR核心
-
Hub 是一種高級管道,允許客戶端和服務器相互調用方法。 SignalR 自動處理跨計算機邊界的調度,並允許客戶端調用服務器上的方法,反之亦然。 可以將強類型參數傳遞給方法,從而支持模型綁定。 SignalR 提供兩種內置中心協議:基於 JSON 的文本協議和基於 SignalR 的二進制協議。 與 JSON 相比,MessagePack 通常會創建更小的消息。 舊版瀏覽器必須支持 XHR 級別 2 才能提供 MessagePack 協議支持。
-
中心通過發送包含客戶端方法的名稱和參數的消息來調用客戶端代碼。 作爲方法參數發送的對象使用配置的協議進行反序列化。 客戶端嘗試將名稱與客戶端代碼中的方法匹配。 當客戶端找到匹配項時,它會調用該方法並將反序列化的參數數據傳遞給它。
-
Hubs(集線器)介紹
- Hub.Context
Hub 類具有一個 Context 屬性,該屬性包含具有連接相關信息的以下屬性
屬性 | 說明 |
---|---|
ConnectionId | 獲取連接的唯一 ID(由 SignalR 分配)。 每個連接有一個連接 ID。 |
UserIdentifier | 獲取用戶標識符。 默認情況下,SignalR 使用與連接關聯的 ClaimsPrincipal 中的 ClaimTypes.NameIdentifier 作爲用戶標識符。 |
User | 獲取與當前用戶關聯的 ClaimsPrincipal。 |
Items | 獲取可用於在此連接範圍內共享數據的鍵/值集合。 數據可以存儲在此集合中,會在不同的中心方法調用間爲連接持久保存。 |
Features | 獲取連接上可用的功能的集合。 目前,在大多數情況下不需要此集合,因此未對其進行詳細記錄。 |
ConnectionAborted | 獲取一個 CancellationToken,它會在連接中止時發出通知。 |
Hub.Context還包含以下方法
方法 | 說明 |
---|---|
GetHttpContext | 返回連接的 HttpContext,如果連接不與 HTTP 請求關聯,則返回 null。 對於 HTTP 連接,可以使用此方法獲取 HTTP 標頭和查詢字符串等信息。 |
Abort | 中止連接。 |
- Hub.Clients
Hub 類具有一個 Clients 屬性,該屬性包含適用於服務器與客戶端之間的通信的以下屬性
屬性 | 說明 |
---|---|
All | 對所有連接的客戶端調用方法 |
Caller | 對調用了中心方法的客戶端調用方法 |
Others | 對所有連接的客戶端調用方法(調用了方法的客戶端除外) |
Hub.Clients還包含以下方法
方法 | 說明 |
---|---|
AllExcept | 對所有連接的客戶端調用方法(指定連接除外) |
Client | 對連接的一個特定客戶端調用方法 |
Clients | 對連接的多個特定客戶端調用方法 |
Group | 對指定組中的所有連接調用方法 |
GroupExcept | 對指定組中的所有連接調用方法(指定連接除外) |
Groups | 對多個連接組調用方法 |
OthersInGroup | 對一個連接組調用方法(不包括調用了中心方法的客戶端) |
User | 對與一個特定用戶關聯的所有連接調用方法 |
Users | 對與多個指定用戶關聯的所有連接調用方法 |
設計思路
使用SignalR與客戶端進行實時通訊、用戶鏈接管理、JWt進行用戶身份認證和鑑權、Redis保存用戶鏈接信息
- 前端創建鏈接之後就會觸發後端
OnConnectedAsync()
方法,這樣我們就可以通過獲取當前的連接IP信息和用戶瀏覽器信息組成一個唯一設備標識。 - 我們創建一個Redis key數據類型爲
Hashes
將用戶Id當成key,然後將不同設備登錄用戶當成value存儲。 - 反之當用戶主動斷開鏈接、或者關閉瀏覽器就會觸發後端
OnDisconnectedAsync()
方法,就代表該設備的用戶下線了。
前端設計
與服務端創建鏈接
前端使用@aspnet/signalr
與服務端進行握手通訊,用戶登錄成功建立一個Socket鏈接
// 創建鏈接
this.init.connection = new signalR.HubConnectionBuilder()
// IM_URL鏈接地址
.withUrl(IM_URL, {
// accessTokenFactory攜帶用戶Token進行身份認證和鑑權
accessTokenFactory: () => this.token
}).build();
監聽關閉事件
方式客戶端發生意外斷線,或者後端斷開我們的鏈接,我們就可以監聽關閉事件,給到用戶一些提示
this.init.connection.onclose(function() {
console.log('connecition closed');
});
接收消息(畫重點)
因爲我自己寫過一個IM的小應用,自己就也寫過前端,所以這裏我會給一些經驗給到前端大佬。
思路是這樣的:前端程序初始化Signalr
接收消息方法的時候帶一個參數(類似委託的參數),這個委託是一個消息類型處理工廠。
App.Vue 文件中的代碼
methods: {
// 接受用戶信息進入消息總線
ReceiveUserMsg(data) {
....處理消息工廠代碼.....
switch (switch_on)
{
case "消息類型" :
break;
}
}},
created() {
try {
// 初始化創建鏈接
this.$signalr.CreatorConnectServer();
// 初始化用戶消息接收
this.$signalr.ReUserReceiveMessage(this.ReceiveUserMsg);
// 初始化鏈接關閉事件
this.$signalr.OnClose();
} catch (e) {
console.log("網絡錯誤");
}}
signalr.js(自己專門封裝的一個js)
// 接受信息
ReUserReceiveMessage(receiveUserMsg) {
this.init.connection.on("ReUserReceiveMessage", (result) => {
// 執行委託
receiveUserMsg(result);
console.log(result)
});
}