janneyabc 发表于 2017-12-14 22:35:26

基于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>
  <div>
  <label for=&quot;username&quot;>
  <div>
  <input type=&quot;text&quot;>  </div>
  </div>

  <div>
  <label for=&quot;password&quot;>
  <div>
  <input type=&quot;password&quot;>  </div>
  </div>

  <div>
  <button type=&quot;submit&quot;>
  <a href=&quot;/reg&quot;>  </div>
  
</form>
  其后台主要代码如下:
//登录操作  
router.post('/login', function(req, res, next) {
  //判断用户名是否存在
  User.findOne({username:req.body.username},function (error,user) {
  if (!user) {
  //用户名不存在
  error = &quot;用户名不存在,请先去注册&quot;;
  }else if (req.body.password !== user.password) {
  error = &quot;密码输入错误&quot;;
  }
  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 = &quot;该用户名已存在,请重新输入&quot;;
  
}else if (req.body.password !== req.body.repassword) {
  error = &quot;两次密码输入不一致&quot;;
  //console.log(&quot;两次密码输入不一致&quot;);
  }
  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   :&quot;查询用户列表失败&quot;});
  }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:&quot;编辑用户信息失败&quot;});
  }else{
  res.send({status:200, msg:&quot;编辑用户信息成功&quot;});
  }
  })
  
});

4 密码修改
  在聊天室左边的个人交互框中,点击系统设置,便找到了当前用户的密码修改。点击出现修改密码的模态框。其实现方法是先判断用户输入的原密码是否正确,然后判断新密码和重复输入的密码是否一致。通过判断条件后根据用户ID将用户密码修改为新密码保存到数据库中。其运行界面如图7所示。
  

  

                              图7修改用户密码界面  

  后台sql代码如下:
  

User.update({username:username},{$set:{password:newPassword}},function(err,result){  if(err){
  res.send({status:8000,msg:&quot;修改用户密码失败&quot;});
  }else{
  res.send({status:200,msg:&quot;修改用户密码成功&quot;});
  }
  
});

####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.username +

  '<span>  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!=&quot;&quot;&& sendMsg!=null){
  searchData.username = sendMsg;
  }
  if(acceptMsg!=&quot;&quot; && acceptMsg !=null){
  searchData.sayto   = acceptMsg;
  }
  var a = 0;
  var msgCount = &quot;&quot;;
  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


##总结
  从开始决定要做一个聊天系统,自己也觉得是不是不可能,当时也很没自信去说自己能去做一个独立的前台和后台系统,但是但是想的就是害怕什么呢,做不出来能死吗?如果只是想,而不去干,那你永远也不会得到什么。于是边去一遍调研,一遍尝试些写,当然白天还是要工作的,一般都是晚上下班后写一点,遇到不会的每天再学一点。现在系统已经粗糙的成了个大概的雏形。
  当朋友使用这个系统在网上聊天,发送过去消息成功的时候,内心是很激动的,或许这不是最好的,这个系统也不会给我带来什么经济效益,但从那一刻起,我至少明白了,要敢想敢做!加油。

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


  
页: [1]
查看完整版本: 基于Express+Socket.io+MongoDB的即时聊天系统的设计与实现