「實戰」搭建完整的IM(即時通訊)應用(1)

hezhongfeng發表於2020-07-15

介紹

即時通訊應用服務,整套包含服務端管理端客戶端

預計3篇分享:這次是第一篇,專案的整體介紹和實體關係的梳理

現已部署上線,客戶端管理端,歡迎體驗

可以註冊客戶端賬號,也可以使用初始預設賬號,現有初始賬號說明:

賬號 密碼 說明
admin 123456 管理端賬號
user 123456 客戶端普通使用者賬號
muteuser 123456 客戶端被禁言使用者賬號
disabled 123456 客戶端被封禁使用者賬號
member1 123456 客戶端普通使用者賬號
member2 123456 客戶端普通使用者賬號
... 123456 客戶端普通使用者賬號
member30 123456 客戶端普通使用者賬號

<img width="300" src="https://i.loli.net/2020/07/15/fO2naUmPluYRsBd.png">

功能簡介

  1. 註冊,登入,個人、群組聊天,個人資訊編輯等基礎功能
  2. 申請新增好友和申請入群
  3. 表情,圖片,視訊,定位資訊支援
  4. 聊天會話列表記錄
  5. 訊息記錄(微信的訊息記錄真實一言難盡)
  6. 支援多點同時登入
  7. 百度 UNIT 機器人自動回覆(todo)
  8. 管理端,進行角色和許可權的管理,群狀態管理(我也當一回馬化騰)

需求簡介

移動網際網路發展至今,以微信為首的即時通訊服務已經融入了我們生活中的各個角落,在公司的一些業務中也扮演著重要的角色,對於即時通訊我們公司原來是使用的環信的服務,但是有很多定製化的需求無法實現,所以後來決定內部開發一個滿足定製化需求的即時通訊微服務。

使用socket.io框架是因為當時後端缺人,加上看了一些例子後覺得使用起來真的很方便,而且全平臺支援,所以這個微服務就在前端團隊進行落地實踐,目前效果還不錯。

社群目前這方面的內容比較少或者太簡陋(只有一個公共的聊天室這種)。另外就是在業務開發過程中被 PM 搞得很難受,所以想脫離一些特有的業務上的東西,實現一個功能簡單五臟俱全的不摻雜公司業務的 IM 應用,包含服務端,管理端和客戶端。客戶端的模仿物件是微信,因為我很熟悉,不用在產品上面思考太多,另外就是試用的人很熟悉,不需要太多的溝通成本。

框架簡介

要開發一套完整的即時通訊服務,需要以下部分:

  1. 服務端:用來實現基礎的服務介面和資料持久化
  2. 客戶端:完成登入、聊天等基礎功能,類似微信
  3. 管理端:管理群組、使用者和角色許可權

server

為企業級框架和應用而生

選用阿里的 egg.js 框架做支撐,看中的原因是他們內部大規模的落地和安全方面做得比較好,沒有選擇 nest 的原因是整合 socket.io 比較麻煩,ORM 選用 sequelize,資料庫是 mysql ,之前一起使用過,上手難度小

admin

開箱即用的中臺前端/設計解決方案

選擇 Ant Design Pro 作為模板開發管理端,選用的原因是我對 Vue 全家桶比較熟悉,想借著這個機會熟悉下整套 React 生態 的開發流程,感受下目前國內兩大開發框架的本質區別和殊途同歸,Ant Design Pro 已經發布了好幾年了,也的確給中小型企業帶來效率的提升,也正好適合我這的需求。

client

?️ Vue.js 開發的標準工具

使用 @vue/cli 搭建 IM 服務的客戶端,一個移動端的 H5 專案,UI 框架使用的有贊 vant,整合了我的開源元件vue-page-stack和黃老師的better-scroll,實現 IM 的基礎功能

實體關係

作為一個前端工程師,大多數的日常工作是不需要思考實體關係的。但是,就我的實際體驗來看,懂得實體關係可以幫助我們更好的理解業務模型。而對產品和業務理解的提升對我們的幫助是非常大的,可以在需求評審的時候發現很多不符合邏輯的地方(怎麼又要吐槽產品經理了),這時候能提出來就會主動避免我們在後續的過程中進行反覆開發,同時可以和產品側的同學形成比較良好的互動(而不是互懟)。下面簡單羅列下比較重要的實體關係:

<img width="600" src="https://i.loli.net/2020/07/14/Zhz85V2ptOylDcj.png">

通過上圖可以看到 user 是整個關係圖中的核心,下面介紹下各個實體之間的關係:

  1. user 和 user_info(使用者資訊) 是一對一的關係
  2. user 和 role(角色)是多對多的關係
  3. role 和 right(許可權)是多對多的關係
  4. user 和 apply(申請)是多對多的關係,申請都是涉及到兩個 user(申請人和被申請人)
  5. user 和 group(群組)是多對多的關係
  6. group 和 conversation(會話) 是一對一的關係
  7. friend 和 conversation(會話) 是一對一的關係
  8. conversation 和 message(訊息)是 1 對多的關係
  9. friend(好友關係) 和 user 沒有直接關係,friend 由兩個 user 確定

下面詳細介紹下會話、角色與許可權:

會話

完成一個即時通訊應用,需要考慮的第一個事情就是會話,就是我們微信裡面的對話視窗。思考會話和訊息、使用者、群組之間的關係花費了不少的精力,最終形成以下的基本關係:

  1. 2 個使用者參與的聊天屬於建立了 Friend 關係(互為好友)
  2. 多個使用者參與的聊天組成了群組關係
  3. Friend 和會話之間的關係是 1 對 1 的關係,可以通過 Friend 找到此 Friend 的會話,也可以通過會話確定 Friend
  4. 群組和會話之間的關係是 1 對 1 的關係,可以通過群組找到此群組的會話,也可以通過會話確定群組
  5. 訊息屬於某個會話,可以根據會話檢視對應的訊息列表
  6. 儲存訊息的時候更新會話的啟用時間,使用者的會話列表根據啟用時間排序,也就是最近的會話再最前面

也就是說,使用者和會話沒有直接的關係,只能通過使用者對應的單聊和群聊去獲取會話,這樣做可以有以下的好處:

  1. 無論是單聊還是群聊,連線上的使用者只要 join 進對應的會話 room 裡面就可以,訊息也是在對應的 room 裡面釋出
  2. 無論是單聊還是群聊,訊息的儲存和查詢都比較簡單,都是隻針對這個會話
  3. 獲取個人的會話列表也變得很簡單,使用者的會話列表通過查詢使用者『所有的 Friend 和群組』->『所有的會話』->『排序會話(根據啟用時間)』,就可以獲取

角色和許可權

為了設計一個靈活、通用、方便的許可權管理系統,本系統採用 RBAC(基於角色的訪問控制)控制,來設計一個通用的『使用者角色許可權』平臺,方便後期擴充套件。

RBAC

RBAC(基於角色的訪問控制)是指使用者通過角色與許可權進行關聯。即一個使用者擁有若干角色,每一個角色擁有若干許可權(當然了,別把衝突的角色和許可權配在一起)。這樣,就構造成“使用者—角色—許可權”的授權模型。在這種模型中,使用者與角色之間、角色與許可權之間,一般是多對多的關係。

本系統預設的角色和許可權

本系統預設有管理員、一般使用者、禁言使用者和封禁使用者這幾種角色,給不同的角色分配不同的許可權,所以需要針對管理和發言等介面路由做一下統一的鑑權(通過中介軟體的方式)處理,具體方式和方法在後端專案中會詳細說明。本系統暫時採用預先定義了角色和許可權的方式,後續想要擴充套件的話可以編輯角色和許可權。

管理員

沒見過微信的管理端,但是可以想象一下,管理員可以配置使用者的角色和許可權,可以編輯群組的狀態:

  1. 登入的許可權
  2. 群組狀態的編輯
  3. 針對使用者的角色和許可權的編輯

普通使用者

註冊登入後,可以正常的新增好友和加入群組,可以修改個人基礎資訊和處理申請

  1. 註冊登入
  2. 編輯個人基礎資訊
  3. 新增好友,申請入群
  4. 處理好友申請和入群申請
  5. 聊天

禁言使用者

  1. 註冊登入
  2. 編輯個人基礎資訊
  3. 新增好友,申請入群
  4. 處理好友申請和入群申請

封禁使用者

無法登入

角色的組合

舉個例子:現在有一個新版的個人中心需要上線測試,首先新建一個角色『測試個人中心』,再給這個角色分配對應的許可權;然後給普通使用者做個分組,選出一些人配置上這個角色,這樣就可以進行測試了。

即時通訊原理

下面說下即時通訊服務的核心通訊原理,和一般的 http 服務一樣,有一個服務端和客戶端進行通訊,只不過詳細的協議和處理方式不一樣。

WebSocket

由於歷史原因,現在主流的 http 協議是無狀態協議(HTTP2 暫時應用不廣泛),一般情況是由客戶端主動發起請求,然後服務端去響應。那麼為了實現服務端向客戶端推送資訊,就需要前端主動向後端去輪詢,這種方式低效且容易出錯,在之前我們的管理端首頁確實是這麼做的(5s 一次)。

為了實現這種服務端主動推送資訊的需求, HTML5 開始提供一種在單個 TCP 連線上進行全雙工通訊的協議,也就是 WebSocket。WebSocket 使得客戶端和伺服器之間的資料交換變得更加簡單,允許服務端主動向客戶端推送資料。WebSocket 協議在 2008 年誕生,2011 年成為國際標準,目前絕大部分瀏覽器都已經支援了。

WebSocket 的用法相當簡單:

var ws = new WebSocket("wss://echo.websocket.org");

ws.onopen = function(evt) {
  console.log("Connection open ...");
  ws.send("Hello WebSockets!");
};

ws.onmessage = function(evt) {
  console.log( "Received Message: " + evt.data);
  ws.close();
};

ws.onclose = function(evt) {
  console.log("Connection closed.");
};

有了 WebSocket 協議讓服務端主動推送資訊有了先進的武器,那麼有沒有什麼方式可以相容新舊瀏覽器呢?其實很多人想到了這點,答案就是socket.io

socket.io

socket.io進一步封裝了WebSocket的介面,而且可以在舊版本瀏覽器中自主切換到使用輪詢的方式進行通訊(我們使用者是不會感知的),形成了一套統一的介面,大大減輕了開發的負擔。主要具有以下優點:

  1. 封裝出了一套非常易用的介面,前後端統一,使用非常簡單
  2. 全平臺支援(原生和 H5,微信小程式中也有對應的實現)
  3. 自適應瀏覽器,在比較老的瀏覽器中主動切換使用輪詢的方式,不需要我們自己搞輪詢

這是 socket.io 主頁

最快,最可靠的即時通訊引擎(FEATURING THE FASTEST AND MOST RELIABLE REAL-TIME ENGINE)

使用起來真的很簡單:

var io = require('socket.io')(80);
var cfg = require('./config.json');
var tw = require('node-tweet-stream')(cfg);
tw.track('socket.io');
tw.track('javascript');
tw.on('tweet', function(tweet){
  io.emit('tweet', tweet);
});

總結

有了以上的基礎,我們就基本完成了開始寫程式碼之前的準備:明確了這個應用的基礎功能和實體之間的關係,也明確了基礎的技術方案選型,劃分了3個專案的各自任務。下面就開始完成 server 端吧

未完待續:下一篇介紹 server 端

相關文章