首页 > 代码库 > 《基于Node.js实现简易聊天室系列之详细设计》
《基于Node.js实现简易聊天室系列之详细设计》
一个完整的项目基本分为三个部分:前端、后台和数据库。依照软件工程的理论知识,应该依次按照以下几个步骤:需求分析、概要设计、详细设计、编码、测试等。由于缺乏相关知识的储备,导致这个Demo系列的文章层次不是很清楚,索性这一章将所有的过程(前后端以及数据库)做一个介绍,下一章写完总结就OK了吧。
(1)前端部分
涉及到的技术:html、css、bootstrap、jquery、jquery UI
登录/注册界面使用的是bootstrap响应式布局,即支持不同尺寸的客户端,以此提高用户的体验。在这之前我以为聊天室比较适合做成SPA(单页应用),想采取backbone,但是结合毕设的主题是基于Node.js,如果采用backbone,路由功能就有两种选择,backbone和Node.js都有着丰富的路由API,由于之前没有用Node.js做过相关项目,所以就放弃了backbone。Demo通过改变css中display的属性来控制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> Setting</li> 49 <li id="changeUser"><i class="glyphicon glyphicon-transfer"></i> Switch</li> 50 <li id="layout"><i class="glyphicon glyphicon-off"></i> Layout</li> 51 </ul> 52 </div> 53 <ul id="setContent" style="display: none"> 54 <li><i class="glyphicon glyphicon-eye-close"></i><em> Update Password</em></li> 55 <li><i class="glyphicon glyphicon-tags"></i><em> Personal Sign</em></li> 56 <li><i class="glyphicon glyphicon-user"></i><em> 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="http://www.mamicode.com/img/firsthead.jpg" /><span>The Legend of Qin</span></li> 80 <li><img src="http://www.mamicode.com/img/secondhead.jpg" ><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> 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="http://www.mamicode.com/socket.io/socket.io.js"></script>138 <script src="http://www.mamicode.com/js/jquery.js"></script>139 <script src="http://www.mamicode.com/js/jquery-ui.min.js"></script>140 <script src="http://www.mamicode.com/js/app.js"></script>141 <script src="http://www.mamicode.com/js/jquery.qqFace.js"></script>
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 =http://www.mamicode.com/== ‘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 =http://www.mamicode.com/== ‘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="http://www.mamicode.com/‘ + CookieObj.h_imgPath + ‘"/>‘ + ‘</li>‘; 89 } else { 90 str = ‘<li class="Lileft"><img class="msgImg" src="http://www.mamicode.com/‘ + 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="http://www.mamicode.com/‘ + 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="http://www.mamicode.com/‘ + 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="http://www.mamicode.com/‘ + 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.name214 }215 })216 .done(function (data, textStatus, jqXHR) {217 if (data.value =http://www.mamicode.com/== ‘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="http://www.mamicode.com/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: selectedImg299 }300 }).done(function (data) {301 if (data.value =http://www.mamicode.com/== ‘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: $newSign329 }330 }).done(function (data) {331 if (data.value =http://www.mamicode.com/== ‘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: $newpass364 }365 }).done(function (data, textStatus, jqXHR) {366 if (data.value =http://www.mamicode.com/== ‘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 =http://www.mamicode.com/== ‘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: 999999999430 }, 0);431 }432 433 /*434 *删除cookie435 */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 *获取cookie448 */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 *界面render461 */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, ‘<‘);481 str = str.replace(/\>/g, ‘>‘);482 str = str.replace(/\n/g, ‘<br/>‘);483 str = str.replace(/\[em_([0-9]*)\]/g, ‘<img src="http://www.mamicode.com/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> <em class="blue">‘ + item.saytime + ‘</em><br/><span>‘ + item.msg + ‘</span></li>‘;502 } else {503 msgStr += ‘<li><span class="green">‘ + item.name + ‘</span> <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 =http://www.mamicode.com/== ‘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://www.mamicode.com/‘http://i2.sinaimg.cn/dy/main/weather/weatherplugin/wthIco/20_20/" + _f562 + "‘ />";563 // var tq = citytq + " " + img + " " + _w.s1 + " " + _w.t1 + "℃~" + _w.t2 + "℃ " + _w.d1 + _w.p1 + "级";564 var tq = img + _w.s1 + ‘ ‘ + citytq + "<br/><span> " + _w.t2 + "℃~" + (_w.t1 || 25) + "℃ " + "</span>";565 $(‘#weather‘).html(tq);566 }567 });568 });569 }570 571 findWeather();572 });
主界面如下图所示即聊天分为三个模块(左中右)即:左为功能模块,用户可以进行房间的选择,以及点击自己的图像修改个人资料等操作;中为聊天模块,显示当前房间聊天内容以及聊天信息输入框;右为信息展示模块,默认显示所有在线用户信息以及当前房间在线成员信息,用户可以切换查看当前房间历史聊天记录。
(2)数据库
涉及到的技术:mongoDB、mongoose
由于javascript是一门弱类型语言,所以操作数据库没有java、php等语言方便。但是我们可以通过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: true22 },23 password:{24 type: String,25 index: true26 },27 user_id: {28 type: mongoose.Schema.Types.ObjectId,29 index: true30 },31 updated: {32 type: Date, default: Date.now33 },34 status: {35 type: Boolean,36 default: false37 },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:Msg53 };
(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 };
服务器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 });
《基于Node.js实现简易聊天室系列之详细设计》