「從頭到腳」擼一個社交聊天系統(vue + node + mongodb)

「從頭到腳」擼一個社交聊天系統(vue + node + mongodb)

前言

專案開始是因為工作需要一個聊天室功能,但是因為某些原因最終選用的是基於xmpp協議的Strophe。js寫的。於是就想用node自己寫一套,本來只是想簡單的寫個聊天頁面,但是寫完了又不滿意,所以不斷的重構(似乎可以理解產品經理為什麼老是改需求了๑乛◡乛๑)。

很多東西,比如mongodb,我也是第一次用,以前只接觸過mysql。所以都是一邊學一邊寫,利用工作之餘的時間,斷斷續續的寫了幾個月(這次講的是V0。9。0版本,專案還在更新中···),包含了一整套的前後端互動。uI是按照自己的感覺來的,沒有設計天分(話說主題切換到現在還只有一套主題,實在是不好設計啊~),輕噴——-。專案還有很多需要最佳化完善的地方,歡迎大家提到issues(文末有q群,歡迎一起學習交流)。

閒話少說,本文主要講專案的設計流程,以及部分功能實現思路。對專案感興趣的同學請移步原始碼 Vchat — 從頭到腳,擼一個線上聊天的web應用(vue + node + mongodb)。

專案架構

技術棧

「從頭到腳」擼一個社交聊天系統(vue + node + mongodb)

前端主要採用了vue全家桶,沒什麼多說的,腳手架構建專案,vuex狀態管理,vue-router控制路由,axios進行前後端互動。後端是基於node搭的服務,用的是express。我為什麼不用koa呢,純粹是圖方便,因為koa不熟(捂臉)。聊天最重要的當然是通訊,專案用socket。io來進行前後端通訊。

資料庫是mongoDB,主要有使用者、好友、群聊、訊息、表情、號碼池等。

功能概覽

「從頭到腳」擼一個社交聊天系統(vue + node + mongodb)

功能設計

登入註冊

「從頭到腳」擼一個社交聊天系統(vue + node + mongodb)

Vchat中使用者註冊時,會隨機指定一個code號碼,而這個code號是從預先生成的一個號碼池(號碼池存在mongodb)中取的。初始指定10000001-10001999的號碼段為使用者code, 100001-100999的號碼段為群聊code。使用者可以憑藉code號或者賬號登入。

// 號碼池設計 * code 號碼 * status 1 已使用 0 未使用 * type 1 使用者 2 群聊 * random 隨機數索引,用於隨機查詢某一條 // user表主要欄位 * name 賬號 * pass 密碼 * avatar 頭像 * signature 個性簽名 * nickname 暱稱 * email 郵件 * phone 手機 * sex 性別 * bubble 氣泡 * projectTheme 專案主題 * wallpaper 聊天桌布 * signUpTime 註冊時間 * lastLoginTime 最後一次登入時間 * chatColor 聊天文字顏色 * province 省 * city 市 * town 縣 * conversationsList 會話列表 * cover 封面列表

註冊時,需要判斷賬號是否已存在,以及隨機取得的code需要在號碼池中標記為已被使用,使用者密碼用md5加密等。

// md5 密碼加密 const md5 = pass => { // 避免多次呼叫MD5報錯 let md5 = crypto。createHash(‘md5’); return md5。update(pass)。digest(“hex”); };

登入同樣需要判斷使用者是否已註冊,以及支援賬號和code兩種方式登入。

const login = (params, callback) => { // 登入 baseList。users 。find({ // mongodb中可以直接用$or表示或關係 $or: [{“name”: params。name}, {“code”: params。name}] }) 。then(r => { if (r。length) { let pass = md5(params。pass); if (r[0][‘pass’] === pass) { //更新最後一次登入時間 此處直接寫Date。now 會報錯 需要Date。now()!!!; baseList。users。update({name: params。name}, {lastLoginTime: Date。now()})。then(raw => { console。log(raw); }); callback({code: 0, data: {name: r[0]。name, photo: r[0]。photo}}); } else { callback({code: -1}); } } else { callback({code: -1}); } }) };

登入許可權管理

後端設定全域性中介軟體,將沒有登入的api請求統一返回status: 0

app。use(‘/v*’, (req, res, next) => { if (req。session。login) { next(); } else { if (req。originalUrl === ‘/v/user/login’ || req。originalUrl === ‘/v/user/signUp’) { next(); } else { res。json({ status: 0 }); } } });

前端用axios統一設定攔截器

// http response 伺服器響應攔截器,這裡攔截未登入和401錯誤,並重新跳入登頁重新獲取tokeninstance。interceptors。response。use( response => { // 攔截未登入 if (response。data。status === 0) { router。replace(‘/’); } return response; }, error => { if (error。response) { switch (error。response。status) { case 401: // 這裡寫清除token的程式碼 router。replace(‘/’); } } return Promise。reject(error。response。data) });

訊息

vchat中,訊息種類包括好友或者加群申請、回覆申請(同意or拒絕)、入群通知、聊天訊息(文字、圖片、表情、檔案)

「從頭到腳」擼一個社交聊天系統(vue + node + mongodb)

「從頭到腳」擼一個社交聊天系統(vue + node + mongodb)

在實現訊息傳送之前,需要大體的瞭解一些socket。io的api。詳細api文件可以檢視socket。io

// 所有的訊息請求都是建立在已連線的基礎上的 io。on(‘connect’, onConnect); // 傳送給當前客戶端 socket。emit(‘hello’, ‘can you hear me?’, 1, 2, ‘abc’); // 傳送給所有客戶端,除了傳送者 socket。broadcast。emit(‘broadcast’, ‘hello friends!’); // 傳送給同在 ‘game’ 房間的所有客戶端,除了傳送者 socket。to(‘game’)。emit(‘nice game’, “let‘s play a game”); // 傳送給同在 ’game‘ 房間的所有客戶端,包括髮送者 io。in(’game‘)。emit(’big-announcement‘, ’the game will start soon‘);

加入房間

加入會話列表中的房間,會話列表在好友申請成功或者加群成功時會自動新增。但是你也可以手動移除或新增,移除後將不會再收到被移除會話的訊息(類似於遮蔽)。

// 前端 發起加入房間的請求 this。conversationsList。forEach(v => { let val = { name: this。user。name, time: utils。formatTime(new Date()), avatar: this。user。photo, roomid: v。id }; this。$socket。emit(’join‘, val); }); // 後端 接受請求後執行加入操作,記錄每個房間加入的成員,以及回信告知指定房間已上線成員 socket。on(’join‘, (val) => { socket。join(val。roomid, () => { if (OnlineUser[val。name]) { return; } OnlineUser[val。name] = socket。id; io。in(val。roomid)。emit(’joined‘, OnlineUser); // 包括髮送者 }); });

多房間

同時加入多個聊天房間會出現一個問題,socket可以加入多個房間並給指定房間傳送訊息,但是接受訊息的時候並不會區分房間。換句話說,所有房間的訊息,會一起傳送給客戶端。所以我們需要自己區分哪條訊息是哪個房間的並進行分發。這樣就需要一個房間標識來過濾,Vchat用的是房間id。

mes(r) { // 只有本房間的訊息才展示 if (r。roomid === this。currSation。id) { this。chatList。push(Object。assign({}, r, {type: ’other‘})); } }

發訊息

// 前端 send(params, type = ’mess‘) { // 傳送訊息 if (!this。message && !params) { return; } let val = { name: this。user。name, mes: this。message, time: utils。formatTime(new Date()), avatar: this。user。photo, nickname: this。user。nickname, read: [this。user。name], roomid: this。currSation。id, style: ’mess‘, userM: this。user。id }; this。chatList。push(Object。assign({},val,{type: ’mine‘})); // 更新檢視 this。$socket。emit(’mes‘, val); this。message = ’‘; } // 後端 接收訊息後儲存到資料庫,並轉發給房間內其他成員,不包括髮送者。 socket。on(’mes‘, (val) => { // 聊天訊息 apiList。saveMessage(val); socket。to(val。roomid)。emit(’mes‘, val); });

訊息記錄

所有的訊息都會存到mongodb中,當切換房間的時候,會獲取歷史訊息。而處在當前房間時,只會把最新訊息追加到dom中,不會從資料庫獲取。聊天視窗預設只展示最新100條訊息,更多訊息可在聊天記錄中檢視。

// 前端 獲取指定房間的歷史訊息 this。$socket。emit(’getHistoryMessages‘, {roomid: v。id, offset: 1, limit: 100}); // 後端 關聯表、分頁、排序 messages。find({roomid: params。roomid}) 。populate({path: ’userM‘, select: ’signature photo nickname‘}) // 關聯使用者基本資訊 。sort({’time‘: -1}) 。skip((params。offset - 1) * params。limit) 。limit(params。limit) 。then(r => { r。forEach(v => { // 防止使用者修改資料後,資訊未更新 if (v。userM) { v。nickname = v。userM。nickname; v。photo = v。userM。photo; v。signature = v。userM。signature; } }); r。reverse(); callback({code: 0, data: r, count: count}); })。catch(err => { console。log(err); callback({code: -1}); });

專案展示

主頁

「從頭到腳」擼一個社交聊天系統(vue + node + mongodb)

聊天視窗,可拖拽或縮放,聊天桌布及文字顏色設定。

「從頭到腳」擼一個社交聊天系統(vue + node + mongodb)

個人設定

「從頭到腳」擼一個社交聊天系統(vue + node + mongodb)

應用空間

「從頭到腳」擼一個社交聊天系統(vue + node + mongodb)

結束語

本文主要講了Vchat的整體設計以及一些主要功能的實現,其實寫專案過程中坑還是挺多的,比如mongoose聯表查詢、檔案上傳等等,這裡就不在細說,以後有時間再更新。如果Vchat對你有幫助,記得star一下喲^_^。