項目開始是由於工做須要一個聊天室功能,可是由於某些緣由最終選用的是基於xmpp協議的Strophe.js寫的。因而就想用node本身寫一套,原本只是想簡單的寫個聊天頁面,可是寫完了又不滿意,因此不斷的重構(彷佛能夠理解產品經理爲何總是改需求了๑乛◡乛๑)。javascript
不少東西,好比mongodb,我也是第一次用,之前只接觸過mysql。因此都是一邊學一邊寫,利用工做之餘的時間,斷斷續續的寫了幾個月,包含了一整套的先後端交互。uI是按照本身的感受來的,沒有設計天分(話說主題切換到如今還只有一套主題,實在是很差設計啊~),輕噴---。項目還有不少須要優化完善的地方,歡迎你們提到issues(文末有q羣,歡迎一塊兒學習交流)。html
閒話少說,本文主要講項目的設計流程,以及部分功能實現思路。對項目感興趣的同窗請移步源碼 Vchat — 從頭到腳,擼一個在線聊天的web應用(vue + node + mongodb)。前端
這是分隔線---------------------------------------深夜碼字,最近真冷vue
前端主要採用了vue全家桶,沒什麼多說的,腳手架構建項目,vuex狀態管理,vue-router控制路由,axios進行先後端交互。後端是基於node搭的服務,用的是express。我爲何不用koa呢,純粹是圖方便,由於koa不熟(捂臉)。聊天最重要的固然是通訊,項目用socket.io來進行先後端通訊。java
數據庫是mongoDB,主要有用戶、好友、羣聊、消息、表情、號碼池等。node
Vchat中用戶註冊時,會隨機指定一個code號碼,而這個code號是從預先生成的一個號碼池(號碼池存在mongodb)中取的。初始指定10000001-10001999的號碼段爲用戶code, 100001-100999的號碼段爲羣聊code。用戶能夠憑藉code號或者帳號登陸。mysql
// 號碼池設計 * 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加密等。ios
// md5 密碼加密 const md5 = pass => { // 避免屢次調用MD5報錯 let md5 = crypto.createHash('md5'); return md5.update(pass).digest("hex"); }; 複製代碼
登陸一樣須要判斷用戶是否已註冊,以及支持帳號和code兩種方式登陸。git
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}); } }) }; 複製代碼
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 }); } } }); 複製代碼
// 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拒絕)、入羣通知、聊天消息(文字、圖片、表情、文件)github
在實現消息發送以前,須要大致的瞭解一些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); // 包括髮送者 }); }); 複製代碼
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); }); 複製代碼
// 前端 獲取指定房間的歷史消息 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}); }); 複製代碼
主頁
聊天窗口,可拖拽或縮放,聊天壁紙及文字顏色設置。
我的設置
應用空間
qq前端交流羣:960807765,歡迎各類技術交流,期待你的加入
歡迎關注公衆號 前端發動機,江三瘋的前端二三事,專一技術,也會時常迷糊。但願在將來的前端路上,與你一同成長。
本文主要講了Vchat的總體設計以及一些主要功能的實現,其實寫項目過程當中坑仍是挺多的,好比mongoose聯表查詢、文件上傳等等,這裏就不在細說,之後有時間再更新。若是Vchat對你有幫助,記得star一下喲^_^。