基於Express+Socket.io+MongoDB的即時聊天系統的設計與實現



記得從高中上課時經常偷偷的和同學們使用qq進行聊天,那時候經常需要進行下載qq,但是當時又沒有那么多的流量進行下載,這就是一個很尷尬的事情了,當時就多想要有一個可以進行線上聊天的網站呀,不用每次痛苦的進行藍牙傳送軟件了,現在,我從事了IT這個行業,便想要去實現當初的那個夢想吧。畢竟,不去努力的實現夢想,你會從一個夢想家變成一個幻想家。

技術選擇


由於從事的是前端工作,界面什么的對我來說是so easy,后台部分當然是選擇了node.js,經過分析呢,數據庫部分選擇了mongoose,很早之前做個一個簡易的聊天室,采用的是ajax輪詢后台的方式實現的,這種方式對於服務器的壓力很大,而且在用戶量上去的的時候也會出現卡頓的現象,客戶端和服務器通信使用的是webSocket協議。使用WebScoket協議,我們可以實現客戶端和服務器的全雙工通信,實現實時的服務器向客戶端的消息推送,信息返回。由於瀏覽器對於WebScoket的支持性不是特別普遍,所以我們使用的是封裝了WebScoket的socket.io,socket.io可以適配於所有的瀏覽器,因此,我們使用socket.io來實現客戶端和服務端的通信。

選中MongoDB是因為它的簡便性以及易操作性,聊天系統並不是說必須要嚴格遵守例如MySQL的CUID等准則,它允許我們可以延遲個幾百毫秒收到聊天信息,其優點主要有:

(1)mongodb數據庫體積較小,系統運行時較為靈活。
(2)可以提供對於任意類型的數據的查詢。
(3)使用相應的技巧可以降低我們代碼量以及提升查詢速度。
(4)mongodb可以對我們之前的表格進行預加載。

系統設計


我們簡要的選擇了我們要使用的技術,那么我們還需要對要做的產品進行分析設計,分析出來我們要做的產品都有哪些功能,我們要怎么樣去實現這些功能,根據之前對於聊天系統的認知,我們設置系統的功能暫時分為后台管理系統和前台聊天室。其中后台管理系統有當前用戶管理功能、聊天信息管理功能、而前台聊天室擁有添加好友、刪除好友、好友私聊、群組群聊、修改密碼、修改個人信息等功能。大概的功能模塊圖如下所示:

系統的具體實現

經過上部分的分析,我們隊系統有了初步的認知。該系統分為兩個主體,用戶登錄后的主頁即為聊天室主頁如圖1所示。系統管理員登錄后的主頁即為adminChat.ejs如圖2所示。

                                    圖1  聊天室主頁

                                    圖2  后台管理主頁

1 登錄模塊

由於是聊天系統,所以用戶必須先要經過登錄才能進入聊天室或者后台管理系統。因此系統的首頁便是登錄頁,在登錄頁也可以進行注冊。
下面以“登錄”功能進行主要講解。在登錄界面,主要使用ajax去異步判斷是否能登錄成功,如果數據庫中沒有該用戶,會提示用戶前去注冊,采用MVC設計模式。其運行界面如圖3所示。

                                    圖3  登錄界面

登錄界面前台代碼如下:

//Form表單的提交控件代碼
    <form class="form-horizontal" method="post" action="/login">
      <div class="form-group row" style="margin-top: 40px; color: white;" >
         <label for="username" class="control-label col-sm-4" >用戶名</label>
         <div class="col-sm-6">
             <input type="text" class="form-control" id="username" name="username" placeholder="用戶名">
          </div>
      </div>
      <div class="form-group row" style="color: white;">
         <label for="password" class="control-label col-sm-4">密碼</label>
         <div class="col-sm-6">
             <input type="password" class="form-control" id="password" name="password" placeholder="密碼">
          </div>
      </div>
      <div class="form-group row" style="text-align: center;">
         <button type="submit" class="btn btn-primary">登陸</button>
         <a href="/reg" class="btn btn-primary">注冊</a>
      </div>
</form>

其后台主要代碼如下:

//登錄操作
router.post('/login', function(req, res, next) {
  //判斷用戶名是否存在
  User.findOne({username:req.body.username},function (error,user) {
      if (!user) {
        //用戶名不存在
        error = "用戶名不存在,請先去注冊";
      }else if (req.body.password !== user.password) {
        error = "密碼輸入錯誤";
      }
      if (error) {
        req.session.error = error;
        //跳轉路由
        return res.redirect('/login');
      }
      req.session.success = '登錄成功';
      //跳轉到首頁
      req.session.user = user;
      if(user.username === 'weChatAdmin'){
        //如果是管理員登錄,就登錄到管理員界面
        res.render('adminHome',{user:user});
      }else{
        res.render('chatHome',{user:user});
      }
  })
});

2 注冊模塊

可以從登錄頁直接跳轉到注冊頁,注冊時會先判斷當前的用戶名是否已經被注冊,如果沒有被注冊,就將新增的用戶信息插入到用戶列表中。注冊時為了提高用戶體驗性,便沒有將用戶所需字段全部讓新用戶進行填寫,用戶可以到聊天室中進行個人信息的完善。注冊界面如圖4所示。

                                圖4  注冊界面

注冊的后台主要代碼如下:

//注冊操作
router.post('/reg', function(req, res, next) {
  //1.查詢數據庫,判斷當前的用戶名是否存在
User.findOne({username:req.body.username},
function (error,user) {
  //用戶已經存在
  if (user) {
  //console.log('用戶已經存在');
  error = "該用戶名已存在,請重新輸入";
}else if (req.body.password !== req.body.repassword) {
  error = "兩次密碼輸入不一致";
        //console.log("兩次密碼輸入不一致");
  }
  if (error) {
        //將錯誤存入session
        req.session.error = error;
  //回到注冊頁面
  return res.redirect('/reg');
}

3 個人信息

當用戶為普通用戶時,登錄進聊天室后,通過點擊左上角的用戶頭像進行個人信息的查看、修改。

“查看”功能的實現原理是:當點擊頭像時,觸發綁定在頭像上的點擊事件,在事件中,先異步根據用戶ID去數據庫中獲取用戶信息,然后觸發模態框彈出事件,並對模態框進行渲染,使用模態框控件來展示用戶信息。其效果界面如圖5所示。

                                圖5  展示個人信息界面

后台查詢的mongoose代碼如下:

User.find({username:req.body.username},function(error,user){
               if(error){
                         res.send({ status:8000,msg   :"查詢用戶列表失敗"});
               }else{
                        res.send({status:200,user:user});
               }
         });

有了查看功能,便引出了修改功能,在查看界面可以直接點擊編輯按鈕,從而觸發編輯模態框的展示事件,先異步去獲取該用戶的信息,對模態框進行渲染,然后對輸入框進行編輯,編輯完成后,點擊確定按鈕,首先會數據框的數據進行格式判斷,然后將修改后的數據根據用戶ID保存到數據庫用戶表中。其效果界面如圖6所示。

                            圖6  編輯個人信息界面
  使用ajax進行異步修改,js部分代碼如下所示:
        $.ajax({
                    type:'post',
                    dataType:'json',
                    data:queryData,
                    url:'/compileUserInfo',
                    success:function(data){
                        if(data.status == 200){
                            alert(data.msg);
                         $('#compileUserInfoModel').modal('hide');//成功后將模態框關閉
                            //在這里進行input值得初始化
                        }else{
                            alert(data.msg);
                        }
                    },
                    error:function(e){
                        alert(e);
                    },
         })
     后台主要代碼如下所示:
router.post('/compileUserInfo',function(req,res,next){
  var username = req.body.username;
  var oldValue = {username:req.body.username};
  varnewValue= 	{$set:{username:req.body.username  ,mail:req.body.mail,phone:req.body.phone,country:req.body.country,city:  req.body.city}};
  User.update(oldValue,newValue,function(err,result){
    if(err){
      res.send({status:8000,msg:"編輯用戶信息失敗"});
    }else{
      res.send({status:200, msg:"編輯用戶信息成功"});
    }
  })
});

4 密碼修改

在聊天室左邊的個人交互框中,點擊系統設置,便找到了當前用戶的密碼修改。點擊出現修改密碼的模態框。其實現方法是先判斷用戶輸入的原密碼是否正確,然后判斷新密碼和重復輸入的密碼是否一致。通過判斷條件后根據用戶ID將用戶密碼修改為新密碼保存到數據庫中。其運行界面如圖7所示。

                                圖7  修改用戶密碼界面

    后台sql代碼如下:
User.update({username:username},{$set:{password:newPassword}},function(err,result){
      if(err){
        res.send({status:8000,msg:"修改用戶密碼失敗"});
      }else{
        res.send({status:200,msg:"修改用戶密碼成功"});
      }
});

5 好友管理


5.1 添加好友

聊天室的最右邊,是當前系統中所有的用戶列表,如果想要和某個用戶進行私聊,就必須先添加該用戶為好友。點擊該系統用戶,會彈出模態框顯示是查看該用戶信息還是添加該用戶為好友。如圖8所示。如若選擇查看該用戶信息,則會彈出展示該用戶信息的模態框。當選擇添加該用戶為好友時,則向該用戶發送好友請求,如果該用戶同意,則二人便是好友關系,可以進行私聊了。

                             圖8  添加好友界面
   向特定的用戶發送好友請求,是通過socket.io來實現的,具體實現的代碼如下:
    用戶列表中的聊天室用戶綁定點擊事件,在事件中會先去判斷該用戶是否已經是本用戶的好友,代碼如下:
document.getElementById('userListWrapper').addEventListener('click', function(e) {
    var target = e.target;
    if (target.nodeName.toLowerCase() == 'li') {
    //判斷當前要添加的用戶是否已經是自己的好友
        $.post('/judgeFriend',{master:USERNAME,friend:target.innerHTML},
           function(data,status){
                if(data.status == 300){
                    alert(data.msg);
                }else{
                    that.socket.emit('sendFriendReq',target.innerHTML,USERNAME);
                }
            }
        );
           //向特定的好友發送好友請求
    };
}, false);

經過判斷該用戶並不是你的好友,那么這時就可以向該用戶發送好友請求了,需要准確的定位到該用戶,從而保證不會將好友請求消息發給其他用戶,執行下列代碼:

     this.socket.emit('sendFriendReq',target.innerHTML,USERNAME);

該代碼會定位到server.js中的sendFriendReq事件,在server.js中,采用廣播機制,將這條好友請求消息發送給當前連接WS協議的所有用戶,每個用戶根據請求消息中的請求對象去判斷是不是自己的請求消息,如果是,就做出相應的處理,如果不是,就將此消息進行忽略。其實現的相應邏輯代碼如下所示:

   socket.on('sendFriendReq',function(toOne,fromOne){
            //服務器server.js端執行的代碼。相應的去觸發每個用戶的addFriReq事件。
            socket.broadcast.emit('addFriReq',toOne,fromOne);
      });
      //客戶端對於服務器端的addFriReq事件進行處理。
      this.socket.on('addFriReq',function(toOne,fromOne){
            var toOne   = toOne,
                fromOne = fromOne;
            if(toOne==USERNAME){
                //alert(fromOne+'請求添加你為好友');
                var confirmReq = confirm(fromOne+'請求添加你為好友');
                if(confirmReq){
                    //發送消息告訴fromOne同意了好友請求,同時將該好友添加至好友列表中
                    that.socket.emit('agreeReq',toOne,fromOne);
                }else{
                    //發送消息告訴fromOne拒絕了好友請求
                    that.socket.emit('refuseReq',toOne,fromOne);
                }
            }
       });

                                圖9  刪除好友界面
5.2 刪除好友

聊天交友,難免會有人看誰不順眼,恰巧這個人又是該用戶的好友,這時就想要將此好友給刪了,這時在好友列表中對着該好友右鍵,便可選擇將該好友刪除。其界面如上圖9所示。


6.1 私聊好友

當有悄悄話想要和好友進行訴說的時候,這時候就要和該好友進行私聊了,在好友列表鼠標左鍵點擊該好友,便會看到聊天室中間彈出了私聊的窗口。它的實現原理是:點擊該好友時,觸發私聊窗口的彈出事件,並且將模型中保存聊天信息時的sayto字段設置為當前私聊對象的username。其界面如圖10所示。

                                圖10  私聊好友界面

該系統的實現原理是,根據消息列表的sayto字段來進行區分聊天對象,根據sayto字段去保存或查詢出消息列表中的聊天記錄。使用ajax去進行數據的請求和保存,並且雙方會根據socket.io來指向特定的用戶去響應聊天渲染事件。Js代碼如下:

     $.ajax({
            type    :'post',
            dataType:'json',
            url     :'/getChatMsg',
            data    : {sayto:sayto,fromto:user},
            success : function(data){
                if(data.status===200){
                    console.log(data.msg);
                    container.innerHTML='';
                    for(var i=0;i<data.msg.length;i++){
                        var msgToDisplay = document.createElement('p');
                        msgToDisplay.style.color = color || '#000';
                        msgToDisplay.innerHTML = data.msg[i].username + 
                        '<span class="timespan"></span>' + data.msg[i].content;
                        container.appendChild(msgToDisplay);
                        container.scrollTop = container.scrollHeight;
                    }
                }
           },

6.2 群聊好友

當系統用戶想要進行方便且快速的傳播消息時,這時用戶可以使用群聊功能,在左側群組列表里點擊相應的聊天群,便可以進入到相應的群組聊天室,在這里所有的用戶可以暢所欲言。效果如圖11所示。

                                圖11  好友群聊界面

7 后台管理

7.1 后台登錄員登錄

后台管理員登錄的設計模式和用戶登錄模式相同,當前系統設定的有一個超級管理員,超級管理員用戶名為“zidingyi”,密碼為“123456”。
在超級管理員登錄時,后台判斷到是“系統管理員”,然后就跳轉到我們的后台管理界面。后台管理有三大模塊:用戶管理模塊、聊天信息管理模塊、管理員信息修改模塊、管理員地圖位置查看模塊。

7.2 后台用戶管理模塊

“用戶管理”功能可以進行根據用戶名的搜索查找。同時,可以對用戶進行刪除、禁用、編輯等操作。設計原理是使用jqueryForm配合着node.js+mongoose的后台進行數據庫中用用戶列表的檢索。其中運行圖如圖12。

                                圖12  后台用戶管理界面

查看所有用戶信息的后台sql語句為:

User.find({},function(error,user){
    res.render('adminFriendList',{userList:user});
});

點擊禁用,會在后台將該用戶的disabled屬性設置為true,同時,該條記錄的背景顏色會被改成紅色表示禁止使用的賬號。如果再使用該賬號進行登錄,前台會報錯為該賬號已經被禁止使用。其后台效果如圖5-13后台用戶管理界面所示。
點擊修改,則傳遞選中行的用戶ID,主要實現和在聊天室修改個人信息相同。
點擊刪除,則傳遞選中行的用戶ID,將該用戶從用戶列表中進行刪除。

                            圖13  后台禁止用戶界面

7.3 后台聊天信息管理模塊

“聊天信息管理”功能可以進行根據發送者用戶名搜索查找、也可以根據接受者用戶名搜索查找聊天信息。其中運行圖如圖14。

                            圖14  后台聊天信息管理界面

查看所有聊天信息並進行后台分頁的mongoose語句為:

router.post('/msgList',function(req,res,next){
  var sendMsg    = req.body.sendMsg;
  var acceptMsg  = req.body.acceptMsg;
  var pageIndex  = req.body.pageIndex;
  var pageSize   = req.body.pageSize;
  var searchData = {};
  if(sendMsg!=""&& sendMsg!=null){
    searchData.username = sendMsg;
  }
  if(acceptMsg!="" && acceptMsg !=null){
    searchData.sayto   = acceptMsg;
  }
  var a = 0;
  var msgCount = "";
  Message.find({},function(error,msgList){
    msgCount = msgList.length;
    Message.find(searchData)
      .skip(parseInt(pageIndex)*parseInt(pageSize))
      .limit(parseInt(pageSize))
      .exec(function(error,msgList){
      res.send({
          msgList:msgList,
          totalPages:Math.ceil(msgCount/pageSize),
          totalElements:msgCount
      });
    });
  });
});

另外,在圖14中點擊修改按鈕,則傳遞選中行的聊天信息ID,彈出修改模態框,可以點擊進行修改,其效果如圖15所示。
同理,在圖14中點擊刪除按鈕,則傳遞選中行的聊天信息ID,將該條消息從消息列表中進行刪除。

                    圖5-15  修改聊天信息界面

上述為該系統的整體實現的部分,當然系統的完善度以及可擴展性都不算特別高,還有待進一步的開發與拓展。對於系統的擴展,有想法的童鞋可以和我聯系,來呀,一起搞呀!!!

系統安裝運行

  • 首先要安裝的是node.js環境,安裝好之后會自帶有npm
  • 然后使用npm install安裝所需的各個第三方組件,這些組件在package.json中都已有,只需install就行了
  • 然后需要在本地安裝MongoDB,這個安裝介紹起來說麻煩也麻煩,說簡單也簡單,而且網上已經有好多安裝教程,如果大家對於MongoDB還不是特別了解的話,可以去搜索學習一下,這里給大家推薦菜鳥教程的講解
    http://www.runoob.com/mongodb/mongodb-window-install.html
  • 安裝好上述所有環境之后,先在本地運行起來MongoDB,然后執行node server,就可以打開本地locallhost:3000/login進行登錄注冊了。

代碼開源

  • 所寫的代碼也不多,還有待進一步的重構與整理,這里上傳到了github,下邊貼出我github的地址,大家就手動的點個星吧,萬分感謝!!!
    https://github.com/Blackgan3/WeChat
  • 當然,網頁版的聊天室,不部署到線上怎么能行呢,自己買了個服務器來玩,現在項目已經部署到了線上,正在考慮可以加一個機器人。哈哈
    http://www.blackgan.cn

總結


從開始決定要做一個聊天系統,自己也覺得是不是不可能,當時也很沒自信去說自己能去做一個獨立的前台和后台系統,但是但是想的就是害怕什么呢,做不出來能死嗎?如果只是想,而不去干,那你永遠也不會得到什么。於是邊去一遍調研,一遍嘗試些寫,當然白天還是要工作的,一般都是晚上下班后寫一點,遇到不會的每天再學一點。現在系統已經粗糙的成了個大概的雛形。

當朋友使用這個系統在網上聊天,發送過去消息成功的時候,內心是很激動的,或許這不是最好的,這個系統也不會給我帶來什么經濟效益,但從那一刻起,我至少明白了,要敢想敢做!加油。

覺得作者寫的對你有用處的話,就賞個零花錢讓作者買個冰棍吧,感謝支持與鼓勵



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM