您的位置:首页 > 数据库 > Mongodb

基于Express+Socket.io+MongoDB的即时聊天系统的设计与实现

2017-06-03 17:37 866 查看
记得从高中上课时经常偷偷的和同学们使用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


##总结

从开始决定要做一个聊天系统,自己也觉得是不是不可能,当时也很没自信去说自己能去做一个独立的前台和后台系统,但是但是想的就是害怕什么呢,做不出来能死吗?如果只是想,而不去干,那你永远也不会得到什么。于是边去一遍调研,一遍尝试些写,当然白天还是要工作的,一般都是晚上下班后写一点,遇到不会的每天再学一点。现在系统已经粗糙的成了个大概的雏形。

当朋友使用这个系统在网上聊天,发送过去消息成功的时候,内心是很激动的,或许这不是最好的,这个系统也不会给我带来什么经济效益,但从那一刻起,我至少明白了,要敢想敢做!加油。


觉得作者写的对你有用处的话,就赏个零花钱让作者买个冰棍吧,感谢支持与鼓励





内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: