《基于Node.js实现简易聊天室系列之详细设计》

一个完整的项目基本分为三个部分:前端、后台和数据库。依照软件工程的理论知识,应该依次按照以下几个步骤:需求分析、概要设计、详细设计、编码、测试等。由于缺乏相关知识的储备,导致这个Demo系列的文章层次不是很清楚,索性这一章将所有的过程(前后端以及数据库)做一个介绍,下一章写完总结就OK了吧。

(1)前端部分

涉及到的技术:htmlcssbootstrapjqueryjquery UI

登录/注册界面使用的是bootstrap响应式布局,即支持不同尺寸的客户端,以此提高用户的体验。在这之前我以为聊天室比较适合做成SPA(单页应用),想采取backbone,但是结合毕设的主题是基于Node.js,如果采用backbone,路由功能就有两种选择,backboneNode.js都有着丰富的路由API,由于之前没有用Node.js做过相关项目,所以就放弃了backboneDemo通过改变cssdisplay的属性来控制div的显示与隐藏。

页面:

 

  1 <div>
  2     <div class="container">
  3       <div class="row">
  4         <div class="col-sm-5 col-md-5">
  5           <div id="loginBox">
  6             <form id="signinForm" class="form-signin" role="form" onsubmit="return false;">
  7               <h2 class="form-signin-heading">Sign in</h2>
  8               <input id="username" type="text" class="form-control" placeholder="Username" required="" autofocus="">
  9               <input id="userpassword" type="password" class="form-control" placeholder="Password" required="">
 10               <button id="loginBtn" class="btn btn-lg btn-primary btn-block">Sign in</button>
 11             </form>
 12             <p id="SignInErr"></p>
 13           </div>
 14         </div>
 15         <div class="col-sm-2 col-md-2">
 16           <div class="text-center"><br><br>
 17             <h1>Or</h1>
 18           </div>
 19         </div>
 20         <div class="col-sm-5 col-md-5">
 21           <div id="signupBox">
 22             <form id="signupForm" class="form-signin" role="form" onsubmit="return false;">
 23               <h2 class="form-signin-heading">Sign up</h2>
 24               <input id="upName" type="text" maxlength="5" class="form-control" placeholder="Username" required="" />
 25               <input id="upPassword" type="password" maxlength="6" class="form-control" placeholder="Password" required="" />
 26               <button id="signupBtn" class="btn btn-lg btn-primary btn-block">Sign up</button>
 27             </form>
 28             <p id="SignUpErr"></p>
 29           </div>
 30         </div>
 31       </div>
 32     </div>
 33     <div id="main" class="hidden">
 34       <div id="sideBar">
 35         <div id="userInfo">
 36           <img class="headImg" />
 37           <span id="weather"></span>
 38         </div>
 39         <hr style="margin:0;">
 40         <div id="control">
 41           <div>
 42             <span id="gloableName"></span>
 43             <br>
 44             <em></em>
 45           </div>
 46           <hr>
 47           <ul>
 48             <li id="set"><i class="glyphicon glyphicon-cog"></i>&nbsp;&nbsp;Setting</li>
 49             <li id="changeUser"><i class="glyphicon glyphicon-transfer"></i>&nbsp;&nbsp;Switch</li>
 50             <li id="layout"><i class="glyphicon glyphicon-off"></i>&nbsp;&nbsp;Layout</li>
 51           </ul>
 52         </div>
 53         <ul id="setContent" style="display: none">
 54           <li><i class="glyphicon glyphicon-eye-close"></i><em>&nbsp;&nbsp;Update Password</em></li>
 55           <li><i class="glyphicon glyphicon-tags"></i><em>&nbsp;&nbsp;Personal Sign</em></li>
 56           <li><i class="glyphicon glyphicon-user"></i><em>&nbsp;&nbsp;Head Portrait </em></li>
 57         </ul>
 58         <div id="setOne" style="display:none;">
 59           <input type="password" placeholder="Old Password" maxlength="6" id="oldpass" />
 60           <input type="password" placeholder="New Password" maxlength="6" id="newpass" />
 61           <p></p>
 62         </div>
 63         <div id="setTwo" style="display: none;">
 64           <input type="text" placeholder="write something will well" maxlength="16" />
 65           <p></p>
 66         </div>
 67         <div id="setThree" style="display:none;">
 68           <p>*Double click the picture to select</p>
 69           <div id="imgContent">
 70             <ul>
 71             </ul>
 72           </div>
 73         </div>
 74         <div id="chatChange">
 75           <ul id="selectmenu">
 76             <li>Square</li>
 77             <li>Choose Room</li>
 78             <ul id="selectRoom" style="display: none;">
 79               <li><img src="/img/firsthead.jpg" alt="" /><span>The Legend of Qin</span></li>
 80               <li><img src="/img/secondhead.jpg" alt=""><span>Naruto</span></li>
 81             </ul>
 82           </ul>
 83 
 84           <div>
 85           </div>
 86         </div>
 87       </div>
 88       <div id="chatBox">
 89         <div id="headmessages"><strong>Square</strong></div>
 90         <div id="content">
 91           <ul id="messages"></ul>
 92         </div>
 93         <div id="chatbottom">
 94           <div>
 95             <span class="emotion" title="插入表情"><i class="glyphicon glyphicon-picture"></i></span>
 96             <span id="clear" title="清空聊天窗口"><i class="glyphicon glyphicon-refresh"></i></span>
 97             <span id="chatRecord" title="聊天历史消息"><i class="glyphicon glyphicon-time"></i></span>
 98           </div>
 99           <form id="chatMsgForm" onsubmit="return false;">
100             <textarea id="msg" rows="5" cols="35" maxlength="161" placeholder="Enter the content here, you can enter 161 characters at most ~">
101             </textarea>
102             <button id="send" class="btn btn-default"><i class="glyphicon glyphicon-send"></i></button>
103           </form>
104         </div>
105       </div>
106       <div id="model">
107       </div>
108       <div id="rightSide">
109         <span id="membersTitle">Members Information</span>
110         <div id="Allmembers">
111           <div>
112             <i></i>
113             <span>All Members</span>
114             <span id="oncount"></span>/<span id="allcount"></span>
115           </div>
116           <ul id="AllOnline"></ul>
117           <ul id="AllOutline"></ul>
118         </div>
119         <div id="Roommembers">
120           <div>
121             <i></i>
122             <span>Room Members</span>
123             <span id="roomCount"></span>
124           </div>
125           <ul>
126           </ul>
127         </div>
128       </div>
129       <div id="oldMsg" style="display: none;">
130         <span id="oldMsgHead" title="关闭历史消息窗口"><i class="glyphicon glyphicon-arrow-left"></i>&nbsp;&nbsp;MsgHistory</span>
131         <ul></ul>
132         <span id="clearoldMsg"><i title="清空聊天历史消息" class="glyphicon glyphicon-trash"></i></span>
133       </div>
134     </div>
135   </div>
136   </div>
137   <script src="/socket.io/socket.io.js"></script>
138   <script src="/js/jquery.js"></script>
139   <script src="/js/jquery-ui.min.js"></script>
140   <script src="/js/app.js"></script>
141   <script src="/js/jquery.qqFace.js"></script>
View Code

 

js:

  1 $(function () {
  2   var CookieObj = {}, socket = io(), headInfo = "群聊  (";;
  3   window.onbeforeunload = function (e) {
  4     if (document.cookie) return false;
  5   }
  6   render();
  7   /*
  8   *登录
  9   */
 10   var onLogin = function (e) {
 11     var xhr;
 12     if (!$('#username').val() || !$('#userpassword').val()) return;
 13     xhr = $.ajax({
 14       url: '/login',
 15       type: 'POST',
 16       dataType: 'json',
 17       data: {
 18         name: $('#username').val(),
 19         password: $('#userpassword').val()
 20       }
 21     })
 22       .done(function (data, textStatus, jqXHR) {
 23         if (data.value === 'Y') {
 24           render();
 25         } else {
 26           $('#SignInErr').html(data.msg);
 27         }
 28       })
 29       .fail(function (jqXHR, textStatus, errorThrown) {
 30         $('#SignInErr').html('Error occured! Please try again.');
 31       });
 32   };
 33   /*
 34   *注册
 35   */
 36   var onSignup = function (e) {
 37     var xhr;
 38     if (!$('#upName').val() || !$('#upPassword').val()) return;
 39     xhr = $.ajax({
 40       url: '/signup',
 41       type: 'POST',
 42       dataType: 'json',
 43       data: {
 44         name: $('#upName').val(),
 45         password: $('#upPassword').val()
 46       }
 47     })
 48       .done(function (data, textStatus, jqXHR) {
 49         if (data.value === 'Y') {
 50           $('#SignUpErr').html(data.msg || 'Login now with these credentials.');
 51         } else {
 52           $('#SignUpErr').html(data.msg || 'Invalid username');
 53         }
 54       })
 55       .fail(function (jqXHR, textStatus, errorThrown) {
 56         $('#SignUpErr').html('Error occured! Please try again.');
 57       });
 58   };
 59 
 60   var onMsgSubmit = function () {
 61     var str = $("#msg").val();
 62     var sendMsg = replace_em(str);
 63     if (!sendMsg || sendMsg.length > 1261) {
 64       alert("err:内容为空或者内容长度超出限制!")
 65       $('#msg').val('');
 66       return;
 67     }
 68     var roomOf = $("#headmessages strong").html();
 69     socket.emit('chat message', sendMsg, CookieObj.h_imgPath, roomOf);
 70     $('#msg').val('');
 71     return false;
 72   };
 73 
 74   socket.on('sysJoin', function (msg) {
 75     var joinInfo = "";
 76     joinInfo = '<li class="markInfo">' + msg + '</li>';
 77     $(joinInfo).appendTo($("#messages")).animate({ "opacity": 0.5 }, 2000, function () {
 78       $(this).animate({ "opacity": 1 }, 1500, function () {
 79         $(this).animate({ "opacity": 0.3 }, 1000);
 80       });
 81     });
 82     scroll();
 83   });
 84 
 85   socket.on('chat message', function (name, msg, img) {
 86     var str = '';
 87     if (name == CookieObj.name) {
 88       str = '<li class="Liright"><p>' + msg + '</p><img class="msgImg" src="' + CookieObj.h_imgPath + '"/>' + '</li>';
 89     } else {
 90       str = '<li class="Lileft"><img class="msgImg" src="' + img + '"/><p>' + msg + '</p></li>';
 91     }
 92     $('#messages').append(str);
 93     scroll();
 94   });
 95 
 96   /*房间选择*/
 97   //默认是进广场,从其他房间执行如下函数
 98   $("#selectmenu li").eq(0).on("click", function (e) {
 99     e.stopPropagation();
100     $("#selectRoom").hide();
101     $("#headmessages strong").html("Square");
102     socket.emit('join', 'Square', $("#gloableName").html());
103     $("#messages").empty();
104   });
105   $("#selectmenu li").eq(1).on("click", function (e) {
106     e.stopPropagation();
107     $("#selectRoom").show();
108   });
109   //选择秦时明月或火影忍者房间
110   $("#selectRoom li").on("click", function () {
111     var roomName = $(this).children("span").html();
112     var userName = $("#gloableName").html();
113     $("#headmessages strong").html(roomName);
114     socket.emit('join', roomName, userName);
115     $("#messages").empty();
116   });
117 
118 
119 
120   /*
121   *接收所有已注册用户的信息
122   */
123   socket.on('onlineUser', function (online) {
124     var onlineStr = '';
125     for (var i = 0; i < online.length; i++) {
126       var item = online[i];
127       onlineStr += '<li><img src="' + item.h_imgPath + '"/><strong>' + item.name + '</strong><em>[Online]</em></li>';
128     }
129     $("#AllOnline").empty();
130     $("#oncount").html(online.length);
131     $("#AllOnline").append(onlineStr);
132   });
133   socket.on('outlineUser', function (outline) {
134     var outlineStr = '';
135     for (var i = 0; i < outline.length; i++) {
136       var item = outline[i];
137       outlineStr += '<li><img src="' + item.h_imgPath + '"/><strong>' + item.name + '</strong><em>[Outline]</em></li>';
138     }
139     $("#AllOutline").empty();
140     $("#AllOutline").append(outlineStr);
141   });
142   socket.on('allUser', function (doc) {
143     $('#allcount').html(doc.length);
144   });
145   socket.on('disconnect', function (name, msg) {
146     var leftInfo = "";
147     leftInfo = '<li class="markInfo leave">' + msg + '</li>';
148     $(leftInfo).appendTo($("#messages")).animate({ "opacity": 0.3 }, 2000, function () {
149       $(this).animate({ "opacity": 1 }, 1500, function () {
150         $(this).animate({ "opacity": 0.3 }, 1000);
151       });
152       return this;
153     });
154     scroll();
155   });
156 
157   /*当前房间人员信息*/
158   var Lastr, r1, r2, r3;
159   socket.on('SquareRoom', function (roomInfo) {
160     r1 = roomInfo;
161     UpdateRoom();
162   });
163   socket.on('QinRoom', function (roomInfo) {
164     r2 = roomInfo;
165     UpdateRoom();
166   });
167   socket.on('NarutoRoom', function (roomInfo) {
168     r3 = roomInfo;
169     UpdateRoom();
170   });
171   function UpdateRoom() {
172     var $Nowroom = $("#headmessages strong").html(), roomCount, roomStr = '';
173     switch ($Nowroom) {
174       case "Square": Lastr = r1; break;
175       case "The Legend of Qin": Lastr = r2; break;
176       case "Naruto": Lastr = r3; break;
177       default: Lastr = r1;
178     }
179     roomCount = Lastr.length;
180     for (var i = 0; i < roomCount; i++) {
181       var item = Lastr[i];
182       roomStr += '<li><img src="' + item.h_imgPath + '"/><strong>' + item.name + '</strong><em>[Online]</em></li>';
183     }
184     $("#roomCount").html(roomCount);
185     $("#Roommembers ul").empty();
186     $("#Roommembers ul").append(roomStr);
187   }
188   /*
189   *切换/退出账号
190   */
191   $("#changeUser").on('click', function () {
192     var res = confirm("Are you sure you want to quit and switch to another account??");
193     if (res) {
194       UL();
195     } else {
196       $("#control").hide();
197       $("#setContent").hide();
198       $("#stateSelect").hide();
199     }
200   });
201   $("#layout").on('click', UL);
202   function UL() {
203     if (document.cookie) {
204       $('#loginDiv').addClass('hidden');
205       $('#main').removeClass('hidden');
206       var uname = getCookie("userInfo");
207       CookieObj = JSON.parse(uname.substr(2));
208       $.ajax({
209         url: '/layout',
210         type: 'POST',
211         dataType: 'json',
212         data: {
213           name: CookieObj.name
214         }
215       })
216         .done(function (data, textStatus, jqXHR) {
217           if (data.value === 'Y') {
218             clearCookie();
219             window.location.reload();
220           }
221         });
222     };
223   }
224 
225   $('#signinForm #loginBtn').click(onLogin);
226   $('#signupForm #signupBtn').click(onSignup);
227   $('#chatMsgForm #send').click(onMsgSubmit);
228 
229   $("#clear").on("click", function () {
230     $('#messages').empty();
231   });
232 
233   /*
234   *监听滚动条事件
235   */
236   $('#messages').get(0).onscroll = function () {
237     $("#messages .Liright").css("margin-right", 1);
238   }
239 
240   /*
241   *屏蔽回车键
242   */
243   $(document).keydown(function (event) {
244     switch (event.keyCode) {
245       case 13: return false;
246     }
247   });
248   /*
249   *用户信息
250   */
251   $(".headImg").eq(0).on('click', function (e) {
252     e.stopPropagation();
253     if ($("#control").get(0).style.display == "none") {
254       $("#control").show();
255     } else {
256       $("#control").hide();
257       $("#setContent").hide();
258       $("#stateSelect").hide();
259     }
260   });
261 
262   /*更改资料*/
263   $("#set").on("click", function () {
264     $("#setContent").show();
265     $("#stateSelect").hide();
266   });
267   /*构造头像选择内容*/
268   var imgStr = '';
269   for (var i = 1; i <= 18; i++) {
270     imgStr += '<li><img data-in="' + i + '" src="./img/' + i + '.jpg"/></li>';
271     if (i % 6 == 0) {
272       imgStr += "<br/>";
273     }
274   }
275   $("#setThree #imgContent ul").eq(0).append(imgStr);
276   $("#setThree #imgContent li img").on("click", function (e) {
277     e.stopPropagation();
278     var $index = $(this).attr("data-in");
279     $("#setThree #imgContent img").removeClass("imgSelected");
280     $("#setThree #imgContent img").eq(($index - 1)).addClass("imgSelected");
281   });
282   /*人物头像模态框*/
283   $("#setThree").dialog({
284     autoOpen: false,
285     title: "Changing Avatar",
286     modal: true,
287     width: 578,
288     resizable: false,
289     buttons: {
290       "Ok": function () {
291         var selectedImg = $(".imgSelected").attr("data-in");
292         //  alert(selectedImg);
293         $.ajax({
294           url: "/updateImg",
295           type: "POST",
296           data: {
297             name: $('#control div span').eq(0).html(),
298             imgIndex: selectedImg
299           }
300         }).done(function (data) {
301           if (data.value === 'Y') {
302             $("#setThree").dialog("close");
303             $('.headImg').eq(0).attr('src', '/img/' + selectedImg + '.jpg');
304             $('#setContent').hide();
305             $('#control').hide();
306             // alert(data.msg);
307           }
308         });
309         ;
310       }
311     }
312   });
313   /*个性签名模态框*/
314   $("#setTwo").dialog({
315     autoOpen: false,
316     title: "Personalized signature setting",
317     modal: true,
318     resizable: false,
319     buttons: {
320       "OK": function () {
321         var $newSign = $("#setTwo input[type='text']").eq(0).val();
322         if ($newSign != '') {
323           $.ajax({
324             url: '/updateSign',
325             type: 'POST',
326             data: {
327               name: $('#control div span').eq(0).html(),
328               newSign: $newSign
329             }
330           }).done(function (data) {
331             if (data.value === 'Y') {
332               $("#setTwo p").eq(0).html(data.msg);
333               setTimeout(function () {
334                 $('#control div em').eq(0).html($newSign);
335                 $("#setTwo").dialog('close');
336                 $("#setTwo p").eq(0).html('');
337               }, 1000);
338             }
339           });
340         }
341       },
342       "Cancel": function () {
343         $(this).dialog('close');
344       }
345     }
346   });
347   /*密码模态框*/
348   $("#setOne").dialog({
349     autoOpen: false,
350     title: "Changeing User password",
351     modal: true,
352     resizable: false,
353     buttons: {
354       "Ok": function () {
355         var $oldpass = $("#setOne #oldpass").val(), $newpass = $("#setOne #newpass").val();
356         if ($oldpass != '' && $newpass != '') {
357           $.ajax({
358             url: '/changepass',
359             type: 'POST',
360             data: {
361               name: $('#control div span').eq(0).html(),
362               oldpass: $oldpass,
363               newpass: $newpass
364             }
365           }).done(function (data, textStatus, jqXHR) {
366             if (data.value === 'Y') {
367               $("#setOne p").eq(0).html(data.msg);
368               setTimeout(function () {
369                 clearCookie();
370                 $("#setOne p").eq(0).html('');
371                 window.location.reload();
372               }, 1000);
373             } else if (data.value === 'N') {
374               $("#setOne p").eq(0).html(data.msg);
375               $("#setOne #oldpass").val('');
376               $("#setOne #newpass").val('');
377             }
378           });
379         }
380       },
381       "Cancel": function () {
382         $(this).dialog('close');
383       }
384     }
385   });
386   $("#setContent li").eq(0).click(function (e) {
387     e.stopPropagation();
388     $("#setOne").dialog("open");
389   });
390   $("#setContent li").eq(1).click(function (e) {
391     e.stopPropagation();
392     $("#setTwo").dialog("open");
393   });
394   $("#setContent li").eq(2).click(function (e) {
395     e.stopPropagation();
396     $("#setThree").dialog("open");
397   });
398 
399   /*
400   *成员信息面板控制:包括所有成员和具体房间成员的状态
401   */
402   $("#rightSide #Roommembers ul").hide();
403   $("#Allmembers div").css({ 'backgroundColor': "rgb(70,130,180)", "color": "white" });
404   $("#Allmembers div i").addClass("glyphicon glyphicon-triangle-bottom");
405   $("#Roommembers div i").addClass("glyphicon glyphicon-triangle-right");
406   $("#rightSide div >div").click(function (e) {
407     var $title = $(this), $anotherTitle = $(this).parent().siblings("div");
408     if ($title.next('ul').is(":visible")) {
409       $title.siblings('ul').hide();
410       $title.children("i").removeClass("glyphicon glyphicon-triangle-bottom").addClass("glyphicon glyphicon-triangle-right");
411       $title.css({ 'backgroundColor': "", "color": "" });
412     } else {
413       $anotherTitle.children('ul').hide();
414       $anotherTitle.children('div').css({ 'backgroundColor': "", "color": "" });
415       $anotherTitle.children("div").children("i").removeClass("glyphicon glyphicon-triangle-bottom").addClass("glyphicon glyphicon-triangle-right");
416       $title.css({ 'backgroundColor': "rgb(70,130,180)", "color": "white" });
417       $title.siblings('ul').slideToggle(500).show();
418       $title.children("i").removeClass("glyphicon glyphicon-triangle-right").addClass("glyphicon glyphicon-triangle-bottom");
419     }
420   });
421 
422   /*函数集*/
423 
424   /*
425   *保证scroll始终在最底端
426   */
427   function scroll() {
428     $('#messages,#oldMsg ul').animate({
429       scrollTop: 999999999
430     }, 0);
431   }
432 
433   /*
434   *删除cookie
435   */
436   function clearCookie() {
437     var keys = document.cookie.match(/[^=;]+(?=\=)/g);
438     if (keys) {
439       var i = keys.length;
440       while (i--) {
441         document.cookie = keys[i] + '=0;expires=' + new Date(0).toUTCString();
442       }
443     }
444   }
445 
446   /*
447   *获取cookie
448   */
449   function getCookie(sname) {
450     var aCoookie = document.cookie.split(";");
451     for (var i = 0; i < aCoookie.length; i++) {
452       var aCrumb = aCoookie[i].split("=");
453       if (sname == aCrumb[0])
454         return decodeURIComponent(aCrumb[1]);
455     }
456     return null;
457   }
458 
459   /*
460    *界面render
461   */
462   function render() {
463     if (document.cookie) {
464       $('.container').addClass('hidden');
465       $('#main').removeClass('hidden');
466       var uname = getCookie("userInfo");
467       CookieObj = JSON.parse(uname.substr(2));
468       socket.emit('join', $("#headmessages strong").html(), CookieObj.name);
469       $('.headImg').eq(0).attr('src', CookieObj.h_imgPath);
470       $('#control div span').eq(0).html(CookieObj.name);
471       $('#control div em').eq(0).html(CookieObj.personalizedSign);
472     };
473   }
474   $('.emotion').qqFace({
475     id: 'facebox',
476     assign: 'msg',
477     path: 'img/'    //表情存放的路径
478   });
479   function replace_em(str) {
480     str = str.replace(/\</g, '&lt;');
481     str = str.replace(/\>/g, '&gt;');
482     str = str.replace(/\n/g, '<br/>');
483     str = str.replace(/\[em_([0-9]*)\]/g, '<img src="img/$1.gif" border="0" />');
484     return str;
485   }
486   /*查询聊天记录*/
487   $("#chatRecord").on('click', function () {
488     $.ajax({
489       url: "/queryChatMsg",
490       type: "POST",
491       data: {
492         roomName: $("#headmessages strong").html()
493       }
494     }).done(function (data) {
495       var Msg = data.msg;
496       var msgStr = '',
497         $name = $('#control div span').eq(0).html();
498       for (var i = 0; i < Msg.length; i++) {
499         var item = Msg[i];
500         if (item.name == $name) {
501           msgStr += '<li><span class="blue">' + item.name + '</span>&nbsp;&nbsp;<em class="blue">' + item.saytime + '</em><br/><span>' + item.msg + '</span></li>';
502         } else {
503           msgStr += '<li><span class="green">' + item.name + '</span>&nbsp;&nbsp;<em class="green">' + item.saytime + '</em><br/><span>' + item.msg + '</span></li>';
504         }
505       }
506       $("#oldMsg ul").empty();
507       $("#oldMsg ul").css({ "background-img": 'url("/img/loading.gif")' });
508       $("#rightSide").hide();
509       $("#oldMsg").show();
510       setTimeout(function () {
511         $("#oldMsg ul").append(msgStr);
512         scroll();
513       }, 2000);
514     });
515   });
516 
517   /*关闭历史记录窗口*/
518   $("#oldMsgHead i").on('click', function () {
519     $("#oldMsg ul").empty();
520     $("#oldMsg").hide();
521     $("#rightSide").show();
522   });
523   /*清空聊天历史消息*/
524   $("#clearoldMsg i").on('click', function () {
525     var result = confirm("This action will delete the chat record on the database. Do you want to continue?");
526     if (result) {
527       $.ajax({
528         url: '/deleteMsg',
529         type: 'POST',
530         data: {
531           roomName: $("#headmessages strong").html()
532         }
533       }).done(function (data) {
534         if (data.value === 'Y') {
535           alert(data.msg);
536           $("#oldMsg ul").empty();
537           $("#oldMsg").hide();
538           $("#rightSide").show();
539         }
540       });
541     }
542   });
543   //多行文本输入框自动聚焦
544   $("#msg").focus();
545   //获取当前城市以及城市天气
546   function findWeather() {
547     var cityUrl = 'http://int.dpool.sina.com.cn/iplookup/iplookup.php?format=js';
548     $.getScript(cityUrl, function (script, textStatus, jqXHR) {
549       var citytq = remote_ip_info.city;// 获取城市
550       var url = "http://php.weather.sina.com.cn/iframe/index/w_cl.php?code=js&city=" + citytq + "&day=0&dfc=3";
551       $.ajax({
552         url: url,
553         dataType: "script",
554         scriptCharset: "gbk",
555         success: function (data) {
556           var _w = window.SWther.w[citytq][0];
557           var _f = _w.f1 + "_0.png";
558           if (new Date().getHours() > 17) {
559             _f = _w.f2 + "_1.png";
560           }
561           var img = "<img width='25px' height='25px' src='http://i2.sinaimg.cn/dy/main/weather/weatherplugin/wthIco/20_20/" + _f
562             + "' />";
563           // var tq = citytq + " " + img + " " + _w.s1 + " " + _w.t1 + "℃~" + _w.t2 + "℃ " + _w.d1 + _w.p1 + "级";
564           var tq = img + _w.s1 + ' ' + citytq + "<br/><span>&nbsp" + _w.t2 + "℃~" + (_w.t1 || 25) + "℃ " + "</span>";
565           $('#weather').html(tq);
566         }
567       });
568     });
569   }
570 
571   findWeather();
572 });
View Code

主界面如下图所示即聊天分为三个模块(左中右)即:左为功能模块,用户可以进行房间的选择,以及点击自己的图像修改个人资料等操作;中为聊天模块,显示当前房间聊天内容以及聊天信息输入框;右为信息展示模块,默认显示所有在线用户信息以及当前房间在线成员信息,用户可以切换查看当前房间历史聊天记录。

 

(2)数据库

涉及到的技术:mongoDB、mongoose 

由于javascript是一门弱类型语言,所以操作数据库没有javaphp等语言方便。但是我们可以通过mongoose建立模型model映射到数据库中去,将对数据库的操作转换到操作model中去。

 

 1 var mongoose = require("mongoose");
 2 var msgRecord=new mongoose.Schema({
 3     name:{
 4      type:String,
 5      index:true,
 6     },
 7     roomName:{
 8       type:String
 9     },
10     msg:{
11       type:String,
12     },
13     saytime:{
14       type:String,
15     }     
16 });
17 var UserSchema = new mongoose.Schema({
18   name: {
19     type: String,
20     unique: true,
21     index: true
22   },
23   password:{
24     type: String,
25     index: true
26   },
27   user_id: {
28     type: mongoose.Schema.Types.ObjectId,
29     index: true
30   },
31   updated: {
32     type: Date, default: Date.now
33   },
34   status: {
35     type: Boolean,
36     default: false
37   },
38   h_imgPath: {
39     type: String,
40     default:"/img/1.jpg"
41   },
42   personalizedSign:{
43     type:String,
44     default:"Write something will well`"
45   }
46 });
47 
48 var User = mongoose.model('User', UserSchema);
49 var Msg=mongoose.model('Msg',msgRecord);
50 module.exports = {
51   User:User,
52   Msg:Msg
53 };
View Code

(3)后台

       涉及到的技术:Node.js,socket.io,Express

后台作为前端和数据库的桥梁,接收前端传过来的参数,去请求服务器,响应不同的服务请求。同时,通过socket.io进行实时通信,实时通信的前提是在客户端也要引入相关的js文件,通过on()emit()方法、自定义事件达到目的

操作socket.io

  1 var users = {};
  2 var QueryUser = require('./mongoDB/models/model').User;
  3 var Msg = require('./mongoDB/models/model').Msg;
  4 //获取实时时间
  5 function gettime() {
  6   var time = new Date();
  7   var timepartone = time.getFullYear() + '-' + (time.getMonth() + 1) + '-' + time.getDate() + ' ';
  8   var timemid = time.getHours(), s;
  9   if (timemid < 6) {
 10     s = "凌晨 " + timemid;
 11   } else if (timemid < 12) {
 12     s = "上午 " + timemid;
 13   } else if (timemid < 18) {
 14     s = "下午 " + '0' + (timemid - 12);
 15   } else {
 16     s = "晚上 " + (timemid - 12);
 17   }
 18   var timeparttwo = s + ":" + (time.getMinutes() < 10 ? '0' + time.getMinutes() : time.getMinutes());
 19   return timepartone + timeparttwo;
 20 }
 21 /*创建三个房间:Square、The Legend of Qin、Naruto*/
 22 var rooms = { 'Square': [], 'The Legend of Qin': [], 'Naruto': [] };
 23 var user = '';
 24 module.exports = function (app, io) {
 25   io.on('connection', function (socket) {
 26     socket.on('join', function (roomName, userName) {
 27       user = userName;
 28       users[socket.id] = userName;
 29       for (var i in rooms) {
 30         if (roomName != i) {
 31           var index = rooms[i].indexOf(user);
 32           if (index !== -1) {
 33             console.log("删除前" + rooms[i]);
 34             rooms[i].splice(index, 1);
 35             io.to(i).emit('sysLeft', user + "退出了房间" + roomName);
 36             socket.leave(i);
 37             console.log(userName + '离开了房间' + i + ':这个房间里还有' + rooms[i]);
 38           }
 39         }
 40       }
 41       var flag = true;
 42       for (var j = 0; j < rooms[roomName].length; j++) {
 43         if (rooms[roomName][j] == user) {
 44           flag = false;
 45         }
 46       }
 47       if (flag) {
 48         rooms[roomName].push(user);
 49         socket.join(roomName);
 50       }
 51       io.sockets.in(roomName).emit('sysJoin', user + '加入了房间' + roomName);
 52       total();
 53       console.log(user + '加入了' + roomName);
 54     });
 55     socket.on('chat message', function (msg, img, roomOf) {
 56       var name = '';
 57       name = users[socket.id];
 58       var newMsg = new Msg({ name: name, msg: msg, saytime: gettime(),roomName:roomOf });
 59       newMsg.save();
 60       if (rooms[roomOf].indexOf(name) === -1) {
 61         return false;
 62       }
 63       console.log(roomOf + ":" + msg);
 64       io.sockets.in(roomOf).emit('chat message', name, msg, img);
 65     });
 66     socket.on('disconnect', function () {
 67       var msg = '', name = '', time = '';
 68       time = gettime();;
 69       name = users[socket.id];
 70       for (var i in rooms) {
 71           var index = rooms[i].indexOf(name);
 72           if (index !== -1) {
 73             console.log("删除前" + rooms[i]);
 74             rooms[i].splice(index, 1);
 75             io.to(i).emit('sysLeft', name + "退出了房间" + i);
 76             socket.leave(i);
 77             console.log(name + '离开了房间' + i + ':这个房间里还有' + rooms[i]);
 78         }
 79       }
 80       msg = name + '离开群聊  ' + time;
 81       io.emit('disconnect', name, msg);
 82       var timeTotal = total();
 83     });
 84     //获取总用户
 85     function total() {
 86       QueryUser.find({}, function (err, doc) {
 87         io.emit('allUser', doc);
 88       });
 89       QueryUser.find({ status: false }, function (err, doc) {
 90         io.emit('outlineUser', doc);
 91       });
 92       QueryUser.find({ status: true }, function (err, doc) {
 93         io.emit('onlineUser', doc);
 94       });
 95       //查询房间里成员的信息
 96       /*三个房间:Square、The Legend of Qin、Naruto*/
 97       var F_RMInfo = [], S_RMInfo = [], T_RMInfo = [];
 98       for (var k = 0; k < rooms["Square"].length; k++) {
 99         QueryUser.findOne({ name: rooms["Square"][k] }, function (err, doc) {
100           F_RMInfo.push(doc);
101           io.sockets.in("Square").emit('SquareRoom', F_RMInfo);
102           console.log(F_RMInfo);
103         });
104       }
105       for (var i = 0; i < rooms["The Legend of Qin"].length; i++) {
106         QueryUser.findOne({ name: rooms["The Legend of Qin"][i] }, function (err, doc) {
107           S_RMInfo.push(doc);
108           io.sockets.in("The Legend of Qin").emit('QinRoom', S_RMInfo);
109           console.log(S_RMInfo);
110         });
111       }
112       for (var j = 0; j < rooms["Naruto"].length; j++) {
113         QueryUser.findOne({ name: rooms["Naruto"][j] }, function (err, doc) {
114           T_RMInfo.push(doc);
115           io.sockets.in("Naruto").emit('NarutoRoom', T_RMInfo);
116           console.log(T_RMInfo);
117         });
118       }
119     }
120   });
121 
122 };
View Code

服务器js

 1 var express = require('express'),
 2   cookieParser = require('cookie-parser'),
 3   bodyParser = require('body-parser'),
 4   http = require('http'),
 5   path = require('path'),
 6   io = require('socket.io'),
 7   mongoose = require('mongoose'),
 8   app = express(),
 9   db,
10   userRoutes,
11   socketIO;
12 
13 /* 数据库连接 */
14 mongoose.connect('mongodb://localhost:27017/chatroom');
15 db = mongoose.connection;
16 db.on('error', console.error.bind(console, '数据库连接失败!'));
17 db.once('open', function callback() {
18   console.log('数据库连接成功!');
19 });
20 
21 /*Express 配置*/
22 app.use(cookieParser());
23 app.use(bodyParser.json()); 
24 app.use(bodyParser.urlencoded({ extended: true }));
25 app.use(express.static(path.join(__dirname, 'public')));
26 
27 
28 http=http.createServer(app,function(req,res){
29   res.writeHead(200, {'Content-Type': 'text/html;charset=utf-8'});
30 });
31 io = io(http);
32 
33 indexRoutes = require('./routes/index')(app);
34 userRoutes = require('./routes/users')(app);
35 
36 /*绑定io到服务器上*/
37 socketIO = require('./socketIO')(app, io);
38 
39 http.listen(3000, function () {
40   console.log('listening on *:3000');
41 });
View Code

 

posted @ 2017-06-24 21:00  Jinus  阅读(1296)  评论(10编辑  收藏  举报