https://github.com/hua1995116... html
這個項目原本是我學生時代爲了找工做的一個練手項目,可是沒想到受到了不少的關注,star也快要破K了,這也激勵着我不斷去完善他,一方面是得對得起關注學習的人,另外一方面也是想讓本身能過經過慢慢完善一個項目來讓本身提升。node
今天給你們帶來的是基於Websocket+Node+Redis未讀消息功能,可能更加偏向於實戰方向,須要對Websocket和Node有一些瞭解,固然不瞭解也能夠看看效果,效果連接( https://www.qiufengh.com/ )說不定會激起你學習的動力~git
下面我經過本身思考的方式來進行講解,代碼可能講的很少,可是核心邏輯都進行了講解,上面也有github地址,有興趣的能夠進行詳細地查看。本身的idea或多或少會有一些不成熟,可是我仍是厚着臉皮出來拋頭露臉,若是有什麼建議還請你們多多提出,能讓我更加完善這個做品。github
首先對於消息未讀,你們都很熟悉,就是各類聊天的時候,出現的紅點點,且是強迫症者必須清理的一個小點點,如👇所示。我會帶你們實現一個這樣的功能。
web
因爲一對一的方式更加簡單,我如今只考慮多對多的狀況,也就是在一個房間(也能夠稱爲羣組,後面都以房間稱呼)中的未讀消息,那麼設計這樣的一個功能,首相我將它分紅了3種用戶。redis
這種場景就至關於咱們退出微信,可是別人在房間裏發的消息,當咱們再次打開的時候依然可以看到房間增加的未讀消息。mongodb
這種場景就是至關咱們停留在聊天列表頁面,當他人在房間中發送消息,咱們可以實時的看到未讀消息的條數在增加。數據庫
場景示例。ubuntu
這種場景其實就比較普通了,當別人發送新的消息,咱們就能實時看到,此時是不須要標記未讀消息的。windows
場景示例。
主要流程能夠簡化爲三個部分,分別爲用戶,推送功能,消息隊列。
用戶能夠是消息提供者也能夠是消息接受者。如下就是這個過程。
固然在這個過程當中涉及比較複雜的消息的存儲,如何推送,獲取,同步等問題,下面就是對這個過程進行詳細的描述
圖上的流程解釋
A. 存儲在Node緩存中的房間用戶列表(此處信息也能夠存在Redis中)
B. 存儲在Redis中的未讀消息列表
C. 存儲在MongoDB中的未讀消息列表
Redis 是互聯網技術領域使用最爲普遍的存儲中間件,它是「Remote Dictionary Service」的首字母縮寫,是一個高性能的key-value數據庫。具備性能極高,豐富的數據類型,原子,豐富的特性等優點。
redis 具備如下5種數據結構
想要深刻了解這5種存儲結構能夠查看http://www.runoob.com/w3cnote/redis-use-scene.html
windows
http://www.cnblogs.com/jaign/...
mac
brew install redis
ubuntu
apt-get install redis
redhat
yum install redis
centos
https://www.cnblogs.com/zuido...
運行客戶端
redis-cli
windows
https://pan.baidu.com/s/1kU8sY3P
mac
https://pan.baidu.com/s/10vpd...
源碼編譯
http://docs.redisdesktop.com/...
在本項目中咱們用String 來存儲用戶的未讀消息記錄,利用其incr命令來進行自增操做。利用Hash結構 來存儲咱們websocket鏈接時用戶的socket-id。
上面說了計數利用Redis的Stirng數據結構,
在Redis 咱們的計數key-value是這樣的。
username-roomid - number
例子: hua1995116-room1 - 1
咱們的Socket-id則爲Hash結構。
socketId
例子:
socketId
本項目一開始就使用了MongoDB,Node自然搭配的MongoDB的優點,這裏就再也不進行講解,Node操做MongoDB的模塊叫作mongoose,具體的參數方法,能夠查看官方文檔。
https://mongoosejs.com/docs/4.x/index.html
MongoDB下載地址
https://www.mongodb.com/downl...
可視化下載地址
https://github.com/mrvautin/a...
下面咱們經過一開始的3種用戶的場景來具體說明實現的代碼。
客戶端在登陸時會發送一個login事件,如下是後端邏輯。
// 創建鏈接 socket.on('login',async (user) => { console.log('socket login!'); const {name} = user; if (!name) { return; } socket.name = name; const roomInfo = {}; // 初始化socketId await updatehCache('socketId', name, socket.id); for(let i = 0; i < roomList.length; i++) { const roomid = roomList[i]; const key = `${name}-${roomid}`; // 循環全部房間 const res = await findOne({username: key}); const count = await getCacheById(key); if(res) { // 數據庫查數據, 若緩存中沒有數據,更新緩存 if(+count === 0) { updateCache(key, res.roomInfo); } roomInfo[roomid] = res.roomInfo; } else { roomInfo[roomid] = +count; } } // 通知本身有多少條未讀消息 socket.emit('count', roomInfo); });
用戶從離線變成在線狀態,創建socket鏈接時候,會發送一個login事件, 服務端就會去查詢當前用戶的未讀消息狀況,從MongoDB和Redis分別查詢,若Redis中沒有數據,則像數據庫查詢。
客戶端在加入房間說話會發送一個room事件,如下是後端邏輯
// 加入房間 socket.on('room', async (user) => { console.log('socket add room!'); const {name, roomid} = user; if (!name || !roomid) { return; } socket.name = name; socket.roomid = roomid; if (!users[roomid]) { users[roomid] = {}; } // 初始化user users[roomid][name] = Object.assign({}, { socketid: socket.id }, user); // 初始化user const key = `${name}-${roomid}`; await updatehCache('socketId', name, socket.id); // 進入房間默認置空,表示所有已讀 await resetCacheById(key); // 進行會話 socket.join(roomid); const onlineUsers = {}; for(let item in users[roomid]) { onlineUsers[item] = {}; onlineUsers[item].src = users[roomid][item].src; } io.to(roomid).emit('room', onlineUsers); global.logger.info(`${name} 加入了 ${roomid}`); });
服務端接收到客戶端發送的room事件,來重置該用戶房間內的未讀消息,而且該用戶加入房間列表。
客戶端在加入房間說話會發送一個message事件,如下是後端邏輯
socket.on('message', async (msgObj) => { console.log('socket message!'); //向全部客戶端廣播發布的消息 const {username, src, msg, img, roomid, time} = msgObj; if(!msg && !img) { return; } ... // 此處爲向數據庫存入消息 const usersList = await gethAllCache('socketId');// 全部用戶列表 usersList.map(async item => { if(!users[roomid][item]) { // 判斷是否在房間內 const key = `${item}-${roomid}` await inrcCache(key); const socketid = await gethCacheById('socketId', item); const count = await getCacheById(key); const roomInfo = {}; roomInfo[roomid] = count; socket.to(socketid).emit('count', roomInfo); } })
此步驟略微複雜,主要是房間中的用戶發送消息,須要通過判斷,哪部分用戶須要計數,哪部分用戶不須要計數,從圖中能夠看出,不在房間內的用戶都須要計數。
接下來還須要推送,那麼哪些用戶須要實時地推送呢,對的,就是那些在線用戶而且不在房間內的用戶。所以在這裏也須要一個判斷。
這樣就完美了,可以精確地給用戶增長計數,而且精確地推送給須要的用戶。
在線演示: https://www.qiufengh.com/
github地址: https://github.com/hua1995116...
若是有什麼建議或者疑問能夠加入微信羣進行探討。