前言
項目開始是因為工作需要一個聊天室功能,但是因為某些原因最終選用的是基于xmpp協議的Strophe.js寫的。于是就想用node自己寫一套,本來只是想簡單的寫個聊天頁面,但是寫完了又不滿意,所以不斷的重構(似乎可以理解產品經理為什么老是改需求了?乛?乛?)。很多東西,比如mongodb,我也是第一次用,以前只接觸過mysql。所以都是一邊學一邊寫,利用工作之余的時間,斷斷續續的寫了幾個月(這次講的是V0.9.0版本,項目還在更新中···),包含了一整套的前后端交互。uI是按照自己的感覺來的,沒有設計天分(話說主題切換到現在還只有一套主題,實在是不好設計啊~),輕噴---。項目還有很多需要優化完善的地方,歡迎大家提到issues(文末有q群,歡迎一起學習交流)。
閑話少說,本文主要講項目的設計流程,以及部分功能實現思路。對項目感興趣的同學請移步源碼 Vchat — 從頭到腳,擼一個在線聊天的web應用(vue + node + mongodb)。
*這是分隔線---------------------------------------深夜碼字,最近真冷
相關地址在線預覽
github
碼云
簡書
知乎
項目架構技術棧
前端主要采用了vue全家桶,沒什么多說的,腳手架構建項目,vuex狀態管理,vue-router控制路由,axios進行前后端交互。后端是基于node搭的服務,用的是express。我為什么不用koa呢,純粹是圖方便,因為koa不熟(捂臉)。聊天最重要的當然是通信,項目用socket.io來進行前后端通信。數據庫是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錯誤,并重新跳入登頁重新獲取token instance.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拒絕)、入群通知、聊天消息(文字、圖片、表情、文件)
在實現消息發送之前,需要大體的了解一些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}); });項目展示
主頁
聊天窗口,可拖拽或縮放,聊天壁紙及文字顏色設置。
個人設置
應用空間相關閱讀
Mongoose基礎入門
socket.io文檔
Vchat主題切換實現方案來自于 d2-admin
交流群群內有豐富學習資料^_^
寫在后面本文主要講了Vchat的整體設計以及一些主要功能的實現,其實寫項目過程中坑還是挺多的,比如mongoose聯表查詢、文件上傳等等,這里就不在細說,以后有時間再更新。如果Vchat對你有幫助,記得star一下喲^_^。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/100896.html
摘要:在實際開發項目中,有時我們會用到自定義按鈕因為一個項目中,眾多的頁面,為了統一風格,我們會重復用到很多相同或相似的按鈕,這時候,自定義按鈕組件就派上了大用場,我們把定義好的按鈕組件導出,在全局引用,就可以在其他組件隨意使用啦,這樣可以大幅度 在實際開發項目中,有時我們會用到自定義按鈕;因為一個項目中,眾多的頁面,為了統一風格,我們會重復用到很多相同或相似的按鈕,這時候,自定義按鈕組件就...
摘要:代碼整潔之道整潔的代碼不僅僅是讓人看起來舒服,更重要的是遵循一些規范能夠讓你的代碼更容易維護,同時降低幾率。另外這不是強制的代碼規范,就像原文中說的,。里式替換原則父類和子類應該可以被交換使用而不會出錯。注釋好的代碼是自解釋的。 JavaScript代碼整潔之道 整潔的代碼不僅僅是讓人看起來舒服,更重要的是遵循一些規范能夠讓你的代碼更容易維護,同時降低bug幾率。 原文clean-c...
對比內容UCloudStackZStackVMwareQingCloud騰訊TStack華為云Stack優勢總結?基于公有云自主可控?公有云架構私有化部署?輕量化/輕運維/易用性好?政府行業可復制案例輕量化 IaaS 虛擬化平臺?輕量化、產品成熟度高?業內好評度高?功能豐富、交付部署快?中小企業案例多全套虛擬產品及云平臺產品?完整生態鏈、技術成熟?比較全面且健全的渠道?產品成熟度被市場認可,市場占...
摘要:能跨平臺地設置及使用環境變量讓這一切變得簡單,不同平臺使用唯一指令,無需擔心跨平臺問題安裝方式改寫使用了環境變量的常見如在腳本多是里這么配置運行,這樣便設置成功,無需擔心跨平臺問題關于跨平臺兼容,有幾點注意 cross-env能跨平臺地設置及使用環境變量, cross-env讓這一切變得簡單,不同平臺使用唯一指令,無需擔心跨平臺問題 1、npm安裝方式 npm i --save-de...
摘要:引入的模塊引入的使用將打包打包的拆分將一部分抽離出來物理地址拼接優化打包速度壓縮代碼,這里使用的是,同樣在的里面添加 const path = require(path); //引入node的path模塊const webpack = require(webpack); //引入的webpack,使用lodashconst HtmlWebpackPlugin = require(ht...
閱讀 2649·2021-09-13 10:26
閱讀 1913·2021-09-03 10:28
閱讀 1983·2019-08-30 15:44
閱讀 800·2019-08-29 14:07
閱讀 392·2019-08-29 13:12
閱讀 2148·2019-08-26 11:44
閱讀 2342·2019-08-26 11:36
閱讀 2010·2019-08-26 10:19