記得從高中上課時經常偷偷的和同學們使用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
總結
從開始決定要做一個聊天系統,自己也覺得是不是不可能,當時也很沒自信去說自己能去做一個獨立的前台和后台系統,但是但是想的就是害怕什么呢,做不出來能死嗎?如果只是想,而不去干,那你永遠也不會得到什么。於是邊去一遍調研,一遍嘗試些寫,當然白天還是要工作的,一般都是晚上下班后寫一點,遇到不會的每天再學一點。現在系統已經粗糙的成了個大概的雛形。
當朋友使用這個系統在網上聊天,發送過去消息成功的時候,內心是很激動的,或許這不是最好的,這個系統也不會給我帶來什么經濟效益,但從那一刻起,我至少明白了,要敢想敢做!加油。
覺得作者寫的對你有用處的話,就賞個零花錢讓作者買個冰棍吧,感謝支持與鼓勵