使用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)
});
}