《基于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的显示与隐藏。
页面:
<div>
<div class="container">
<div class="row">
<div class="col-sm-5 col-md-5">
<div id="loginBox">
<form id="signinForm" class="form-signin" role="form" onsubmit="return false;">
<h2 class="form-signin-heading">Sign in</h2>
<input id="username" type="text" class="form-control" placeholder="Username" required="" autofocus="">
<input id="userpassword" type="password" class="form-control" placeholder="Password" required="">
<button id="loginBtn" class="btn btn-lg btn-primary btn-block">Sign in</button>
</form>
<p id="SignInErr"></p>
</div>
</div>
<div class="col-sm-2 col-md-2">
<div class="text-center"><br><br>
<h1>Or</h1>
</div>
</div>
<div class="col-sm-5 col-md-5">
<div id="signupBox">
<form id="signupForm" class="form-signin" role="form" onsubmit="return false;">
<h2 class="form-signin-heading">Sign up</h2>
<input id="upName" type="text" maxlength="5" class="form-control" placeholder="Username" required="" />
<input id="upPassword" type="password" maxlength="6" class="form-control" placeholder="Password" required="" />
<button id="signupBtn" class="btn btn-lg btn-primary btn-block">Sign up</button>
</form>
<p id="SignUpErr"></p>
</div>
</div>
</div>
</div>
<div id="main" class="hidden">
<div id="sideBar">
<div id="userInfo">
<img class="headImg" />
<span id="weather"></span>
</div>
<hr style="margin:0;">
<div id="control">
<div>
<span id="gloableName"></span>
<br>
<em></em>
</div>
<hr>
<ul>
<li id="set"><i class="glyphicon glyphicon-cog"></i> Setting</li>
<li id="changeUser"><i class="glyphicon glyphicon-transfer"></i> Switch</li>
<li id="layout"><i class="glyphicon glyphicon-off"></i> Layout</li>
</ul>
</div>
<ul id="setContent" style="display: none">
<li><i class="glyphicon glyphicon-eye-close"></i><em> Update Password</em></li>
<li><i class="glyphicon glyphicon-tags"></i><em> Personal Sign</em></li>
<li><i class="glyphicon glyphicon-user"></i><em> Head Portrait </em></li>
</ul>
<div id="setOne" style="display:none;">
<input type="password" placeholder="Old Password" maxlength="6" id="oldpass" />
<input type="password" placeholder="New Password" maxlength="6" id="newpass" />
<p></p>
</div>
<div id="setTwo" style="display: none;">
<input type="text" placeholder="write something will well" maxlength="16" />
<p></p>
</div>
<div id="setThree" style="display:none;">
<p>*Double click the picture to select</p>
<div id="imgContent">
<ul>
</ul>
</div>
</div>
<div id="chatChange">
<ul id="selectmenu">
<li>Square</li>
<li>Choose Room</li>
<ul id="selectRoom" style="display: none;">
<li><img src="/img/firsthead.jpg" alt="" /><span>The Legend of Qin</span></li>
<li><img src="/img/secondhead.jpg" alt=""><span>Naruto</span></li>
</ul>
</ul> <div>
</div>
</div>
</div>
<div id="chatBox">
<div id="headmessages"><strong>Square</strong></div>
<div id="content">
<ul id="messages"></ul>
</div>
<div id="chatbottom">
<div>
<span class="emotion" title="插入表情"><i class="glyphicon glyphicon-picture"></i></span>
<span id="clear" title="清空聊天窗口"><i class="glyphicon glyphicon-refresh"></i></span>
<span id="chatRecord" title="聊天历史消息"><i class="glyphicon glyphicon-time"></i></span>
</div>
<form id="chatMsgForm" onsubmit="return false;">
<textarea id="msg" rows="5" cols="35" maxlength="161" placeholder="Enter the content here, you can enter 161 characters at most ~">
</textarea>
<button id="send" class="btn btn-default"><i class="glyphicon glyphicon-send"></i></button>
</form>
</div>
</div>
<div id="model">
</div>
<div id="rightSide">
<span id="membersTitle">Members Information</span>
<div id="Allmembers">
<div>
<i></i>
<span>All Members</span>
<span id="oncount"></span>/<span id="allcount"></span>
</div>
<ul id="AllOnline"></ul>
<ul id="AllOutline"></ul>
</div>
<div id="Roommembers">
<div>
<i></i>
<span>Room Members</span>
<span id="roomCount"></span>
</div>
<ul>
</ul>
</div>
</div>
<div id="oldMsg" style="display: none;">
<span id="oldMsgHead" title="关闭历史消息窗口"><i class="glyphicon glyphicon-arrow-left"></i> MsgHistory</span>
<ul></ul>
<span id="clearoldMsg"><i title="清空聊天历史消息" class="glyphicon glyphicon-trash"></i></span>
</div>
</div>
</div>
</div>
<script src="/socket.io/socket.io.js"></script>
<script src="/js/jquery.js"></script>
<script src="/js/jquery-ui.min.js"></script>
<script src="/js/app.js"></script>
<script src="/js/jquery.qqFace.js"></script>
js:
$(function () {
var CookieObj = {}, socket = io(), headInfo = "群聊 (";;
window.onbeforeunload = function (e) {
if (document.cookie) return false;
}
render();
/*
*登录
*/
var onLogin = function (e) {
var xhr;
if (!$('#username').val() || !$('#userpassword').val()) return;
xhr = $.ajax({
url: '/login',
type: 'POST',
dataType: 'json',
data: {
name: $('#username').val(),
password: $('#userpassword').val()
}
})
.done(function (data, textStatus, jqXHR) {
if (data.value === 'Y') {
render();
} else {
$('#SignInErr').html(data.msg);
}
})
.fail(function (jqXHR, textStatus, errorThrown) {
$('#SignInErr').html('Error occured! Please try again.');
});
};
/*
*注册
*/
var onSignup = function (e) {
var xhr;
if (!$('#upName').val() || !$('#upPassword').val()) return;
xhr = $.ajax({
url: '/signup',
type: 'POST',
dataType: 'json',
data: {
name: $('#upName').val(),
password: $('#upPassword').val()
}
})
.done(function (data, textStatus, jqXHR) {
if (data.value === 'Y') {
$('#SignUpErr').html(data.msg || 'Login now with these credentials.');
} else {
$('#SignUpErr').html(data.msg || 'Invalid username');
}
})
.fail(function (jqXHR, textStatus, errorThrown) {
$('#SignUpErr').html('Error occured! Please try again.');
});
}; var onMsgSubmit = function () {
var str = $("#msg").val();
var sendMsg = replace_em(str);
if (!sendMsg || sendMsg.length > 1261) {
alert("err:内容为空或者内容长度超出限制!")
$('#msg').val('');
return;
}
var roomOf = $("#headmessages strong").html();
socket.emit('chat message', sendMsg, CookieObj.h_imgPath, roomOf);
$('#msg').val('');
return false;
}; socket.on('sysJoin', function (msg) {
var joinInfo = "";
joinInfo = '<li class="markInfo">' + msg + '</li>';
$(joinInfo).appendTo($("#messages")).animate({ "opacity": 0.5 }, 2000, function () {
$(this).animate({ "opacity": 1 }, 1500, function () {
$(this).animate({ "opacity": 0.3 }, 1000);
});
});
scroll();
}); socket.on('chat message', function (name, msg, img) {
var str = '';
if (name == CookieObj.name) {
str = '<li class="Liright"><p>' + msg + '</p><img class="msgImg" src="' + CookieObj.h_imgPath + '"/>' + '</li>';
} else {
str = '<li class="Lileft"><img class="msgImg" src="' + img + '"/><p>' + msg + '</p></li>';
}
$('#messages').append(str);
scroll();
}); /*房间选择*/
//默认是进广场,从其他房间执行如下函数
$("#selectmenu li").eq(0).on("click", function (e) {
e.stopPropagation();
$("#selectRoom").hide();
$("#headmessages strong").html("Square");
socket.emit('join', 'Square', $("#gloableName").html());
$("#messages").empty();
});
$("#selectmenu li").eq(1).on("click", function (e) {
e.stopPropagation();
$("#selectRoom").show();
});
//选择秦时明月或火影忍者房间
$("#selectRoom li").on("click", function () {
var roomName = $(this).children("span").html();
var userName = $("#gloableName").html();
$("#headmessages strong").html(roomName);
socket.emit('join', roomName, userName);
$("#messages").empty();
}); /*
*接收所有已注册用户的信息
*/
socket.on('onlineUser', function (online) {
var onlineStr = '';
for (var i = 0; i < online.length; i++) {
var item = online[i];
onlineStr += '<li><img src="' + item.h_imgPath + '"/><strong>' + item.name + '</strong><em>[Online]</em></li>';
}
$("#AllOnline").empty();
$("#oncount").html(online.length);
$("#AllOnline").append(onlineStr);
});
socket.on('outlineUser', function (outline) {
var outlineStr = '';
for (var i = 0; i < outline.length; i++) {
var item = outline[i];
outlineStr += '<li><img src="' + item.h_imgPath + '"/><strong>' + item.name + '</strong><em>[Outline]</em></li>';
}
$("#AllOutline").empty();
$("#AllOutline").append(outlineStr);
});
socket.on('allUser', function (doc) {
$('#allcount').html(doc.length);
});
socket.on('disconnect', function (name, msg) {
var leftInfo = "";
leftInfo = '<li class="markInfo leave">' + msg + '</li>';
$(leftInfo).appendTo($("#messages")).animate({ "opacity": 0.3 }, 2000, function () {
$(this).animate({ "opacity": 1 }, 1500, function () {
$(this).animate({ "opacity": 0.3 }, 1000);
});
return this;
});
scroll();
}); /*当前房间人员信息*/
var Lastr, r1, r2, r3;
socket.on('SquareRoom', function (roomInfo) {
r1 = roomInfo;
UpdateRoom();
});
socket.on('QinRoom', function (roomInfo) {
r2 = roomInfo;
UpdateRoom();
});
socket.on('NarutoRoom', function (roomInfo) {
r3 = roomInfo;
UpdateRoom();
});
function UpdateRoom() {
var $Nowroom = $("#headmessages strong").html(), roomCount, roomStr = '';
switch ($Nowroom) {
case "Square": Lastr = r1; break;
case "The Legend of Qin": Lastr = r2; break;
case "Naruto": Lastr = r3; break;
default: Lastr = r1;
}
roomCount = Lastr.length;
for (var i = 0; i < roomCount; i++) {
var item = Lastr[i];
roomStr += '<li><img src="' + item.h_imgPath + '"/><strong>' + item.name + '</strong><em>[Online]</em></li>';
}
$("#roomCount").html(roomCount);
$("#Roommembers ul").empty();
$("#Roommembers ul").append(roomStr);
}
/*
*切换/退出账号
*/
$("#changeUser").on('click', function () {
var res = confirm("Are you sure you want to quit and switch to another account??");
if (res) {
UL();
} else {
$("#control").hide();
$("#setContent").hide();
$("#stateSelect").hide();
}
});
$("#layout").on('click', UL);
function UL() {
if (document.cookie) {
$('#loginDiv').addClass('hidden');
$('#main').removeClass('hidden');
var uname = getCookie("userInfo");
CookieObj = JSON.parse(uname.substr(2));
$.ajax({
url: '/layout',
type: 'POST',
dataType: 'json',
data: {
name: CookieObj.name
}
})
.done(function (data, textStatus, jqXHR) {
if (data.value === 'Y') {
clearCookie();
window.location.reload();
}
});
};
} $('#signinForm #loginBtn').click(onLogin);
$('#signupForm #signupBtn').click(onSignup);
$('#chatMsgForm #send').click(onMsgSubmit); $("#clear").on("click", function () {
$('#messages').empty();
}); /*
*监听滚动条事件
*/
$('#messages').get(0).onscroll = function () {
$("#messages .Liright").css("margin-right", 1);
} /*
*屏蔽回车键
*/
$(document).keydown(function (event) {
switch (event.keyCode) {
case 13: return false;
}
});
/*
*用户信息
*/
$(".headImg").eq(0).on('click', function (e) {
e.stopPropagation();
if ($("#control").get(0).style.display == "none") {
$("#control").show();
} else {
$("#control").hide();
$("#setContent").hide();
$("#stateSelect").hide();
}
}); /*更改资料*/
$("#set").on("click", function () {
$("#setContent").show();
$("#stateSelect").hide();
});
/*构造头像选择内容*/
var imgStr = '';
for (var i = 1; i <= 18; i++) {
imgStr += '<li><img data-in="' + i + '" src="./img/' + i + '.jpg"/></li>';
if (i % 6 == 0) {
imgStr += "<br/>";
}
}
$("#setThree #imgContent ul").eq(0).append(imgStr);
$("#setThree #imgContent li img").on("click", function (e) {
e.stopPropagation();
var $index = $(this).attr("data-in");
$("#setThree #imgContent img").removeClass("imgSelected");
$("#setThree #imgContent img").eq(($index - 1)).addClass("imgSelected");
});
/*人物头像模态框*/
$("#setThree").dialog({
autoOpen: false,
title: "Changing Avatar",
modal: true,
width: 578,
resizable: false,
buttons: {
"Ok": function () {
var selectedImg = $(".imgSelected").attr("data-in");
// alert(selectedImg);
$.ajax({
url: "/updateImg",
type: "POST",
data: {
name: $('#control div span').eq(0).html(),
imgIndex: selectedImg
}
}).done(function (data) {
if (data.value === 'Y') {
$("#setThree").dialog("close");
$('.headImg').eq(0).attr('src', '/img/' + selectedImg + '.jpg');
$('#setContent').hide();
$('#control').hide();
// alert(data.msg);
}
});
;
}
}
});
/*个性签名模态框*/
$("#setTwo").dialog({
autoOpen: false,
title: "Personalized signature setting",
modal: true,
resizable: false,
buttons: {
"OK": function () {
var $newSign = $("#setTwo input[type='text']").eq(0).val();
if ($newSign != '') {
$.ajax({
url: '/updateSign',
type: 'POST',
data: {
name: $('#control div span').eq(0).html(),
newSign: $newSign
}
}).done(function (data) {
if (data.value === 'Y') {
$("#setTwo p").eq(0).html(data.msg);
setTimeout(function () {
$('#control div em').eq(0).html($newSign);
$("#setTwo").dialog('close');
$("#setTwo p").eq(0).html('');
}, 1000);
}
});
}
},
"Cancel": function () {
$(this).dialog('close');
}
}
});
/*密码模态框*/
$("#setOne").dialog({
autoOpen: false,
title: "Changeing User password",
modal: true,
resizable: false,
buttons: {
"Ok": function () {
var $oldpass = $("#setOne #oldpass").val(), $newpass = $("#setOne #newpass").val();
if ($oldpass != '' && $newpass != '') {
$.ajax({
url: '/changepass',
type: 'POST',
data: {
name: $('#control div span').eq(0).html(),
oldpass: $oldpass,
newpass: $newpass
}
}).done(function (data, textStatus, jqXHR) {
if (data.value === 'Y') {
$("#setOne p").eq(0).html(data.msg);
setTimeout(function () {
clearCookie();
$("#setOne p").eq(0).html('');
window.location.reload();
}, 1000);
} else if (data.value === 'N') {
$("#setOne p").eq(0).html(data.msg);
$("#setOne #oldpass").val('');
$("#setOne #newpass").val('');
}
});
}
},
"Cancel": function () {
$(this).dialog('close');
}
}
});
$("#setContent li").eq(0).click(function (e) {
e.stopPropagation();
$("#setOne").dialog("open");
});
$("#setContent li").eq(1).click(function (e) {
e.stopPropagation();
$("#setTwo").dialog("open");
});
$("#setContent li").eq(2).click(function (e) {
e.stopPropagation();
$("#setThree").dialog("open");
}); /*
*成员信息面板控制:包括所有成员和具体房间成员的状态
*/
$("#rightSide #Roommembers ul").hide();
$("#Allmembers div").css({ 'backgroundColor': "rgb(70,130,180)", "color": "white" });
$("#Allmembers div i").addClass("glyphicon glyphicon-triangle-bottom");
$("#Roommembers div i").addClass("glyphicon glyphicon-triangle-right");
$("#rightSide div >div").click(function (e) {
var $title = $(this), $anotherTitle = $(this).parent().siblings("div");
if ($title.next('ul').is(":visible")) {
$title.siblings('ul').hide();
$title.children("i").removeClass("glyphicon glyphicon-triangle-bottom").addClass("glyphicon glyphicon-triangle-right");
$title.css({ 'backgroundColor': "", "color": "" });
} else {
$anotherTitle.children('ul').hide();
$anotherTitle.children('div').css({ 'backgroundColor': "", "color": "" });
$anotherTitle.children("div").children("i").removeClass("glyphicon glyphicon-triangle-bottom").addClass("glyphicon glyphicon-triangle-right");
$title.css({ 'backgroundColor': "rgb(70,130,180)", "color": "white" });
$title.siblings('ul').slideToggle(500).show();
$title.children("i").removeClass("glyphicon glyphicon-triangle-right").addClass("glyphicon glyphicon-triangle-bottom");
}
}); /*函数集*/ /*
*保证scroll始终在最底端
*/
function scroll() {
$('#messages,#oldMsg ul').animate({
scrollTop: 999999999
}, 0);
} /*
*删除cookie
*/
function clearCookie() {
var keys = document.cookie.match(/[^=;]+(?=\=)/g);
if (keys) {
var i = keys.length;
while (i--) {
document.cookie = keys[i] + '=0;expires=' + new Date(0).toUTCString();
}
}
} /*
*获取cookie
*/
function getCookie(sname) {
var aCoookie = document.cookie.split(";");
for (var i = 0; i < aCoookie.length; i++) {
var aCrumb = aCoookie[i].split("=");
if (sname == aCrumb[0])
return decodeURIComponent(aCrumb[1]);
}
return null;
} /*
*界面render
*/
function render() {
if (document.cookie) {
$('.container').addClass('hidden');
$('#main').removeClass('hidden');
var uname = getCookie("userInfo");
CookieObj = JSON.parse(uname.substr(2));
socket.emit('join', $("#headmessages strong").html(), CookieObj.name);
$('.headImg').eq(0).attr('src', CookieObj.h_imgPath);
$('#control div span').eq(0).html(CookieObj.name);
$('#control div em').eq(0).html(CookieObj.personalizedSign);
};
}
$('.emotion').qqFace({
id: 'facebox',
assign: 'msg',
path: 'img/' //表情存放的路径
});
function replace_em(str) {
str = str.replace(/\</g, '<');
str = str.replace(/\>/g, '>');
str = str.replace(/\n/g, '<br/>');
str = str.replace(/\[em_([0-9]*)\]/g, '<img src="img/$1.gif" border="0" />');
return str;
}
/*查询聊天记录*/
$("#chatRecord").on('click', function () {
$.ajax({
url: "/queryChatMsg",
type: "POST",
data: {
roomName: $("#headmessages strong").html()
}
}).done(function (data) {
var Msg = data.msg;
var msgStr = '',
$name = $('#control div span').eq(0).html();
for (var i = 0; i < Msg.length; i++) {
var item = Msg[i];
if (item.name == $name) {
msgStr += '<li><span class="blue">' + item.name + '</span> <em class="blue">' + item.saytime + '</em><br/><span>' + item.msg + '</span></li>';
} else {
msgStr += '<li><span class="green">' + item.name + '</span> <em class="green">' + item.saytime + '</em><br/><span>' + item.msg + '</span></li>';
}
}
$("#oldMsg ul").empty();
$("#oldMsg ul").css({ "background-img": 'url("/img/loading.gif")' });
$("#rightSide").hide();
$("#oldMsg").show();
setTimeout(function () {
$("#oldMsg ul").append(msgStr);
scroll();
}, 2000);
});
}); /*关闭历史记录窗口*/
$("#oldMsgHead i").on('click', function () {
$("#oldMsg ul").empty();
$("#oldMsg").hide();
$("#rightSide").show();
});
/*清空聊天历史消息*/
$("#clearoldMsg i").on('click', function () {
var result = confirm("This action will delete the chat record on the database. Do you want to continue?");
if (result) {
$.ajax({
url: '/deleteMsg',
type: 'POST',
data: {
roomName: $("#headmessages strong").html()
}
}).done(function (data) {
if (data.value === 'Y') {
alert(data.msg);
$("#oldMsg ul").empty();
$("#oldMsg").hide();
$("#rightSide").show();
}
});
}
});
//多行文本输入框自动聚焦
$("#msg").focus();
//获取当前城市以及城市天气
function findWeather() {
var cityUrl = 'http://int.dpool.sina.com.cn/iplookup/iplookup.php?format=js';
$.getScript(cityUrl, function (script, textStatus, jqXHR) {
var citytq = remote_ip_info.city;// 获取城市
var url = "http://php.weather.sina.com.cn/iframe/index/w_cl.php?code=js&city=" + citytq + "&day=0&dfc=3";
$.ajax({
url: url,
dataType: "script",
scriptCharset: "gbk",
success: function (data) {
var _w = window.SWther.w[citytq][0];
var _f = _w.f1 + "_0.png";
if (new Date().getHours() > 17) {
_f = _w.f2 + "_1.png";
}
var img = "<img width='25px' height='25px' src='http://i2.sinaimg.cn/dy/main/weather/weatherplugin/wthIco/20_20/" + _f
+ "' />";
// var tq = citytq + " " + img + " " + _w.s1 + " " + _w.t1 + "℃~" + _w.t2 + "℃ " + _w.d1 + _w.p1 + "级";
var tq = img + _w.s1 + ' ' + citytq + "<br/><span> " + _w.t2 + "℃~" + (_w.t1 || 25) + "℃ " + "</span>";
$('#weather').html(tq);
}
});
});
} findWeather();
});
主界面如下图所示即聊天分为三个模块(左中右)即:左为功能模块,用户可以进行房间的选择,以及点击自己的图像修改个人资料等操作;中为聊天模块,显示当前房间聊天内容以及聊天信息输入框;右为信息展示模块,默认显示所有在线用户信息以及当前房间在线成员信息,用户可以切换查看当前房间历史聊天记录。
(2)数据库
涉及到的技术:mongoDB、mongoose
由于javascript是一门弱类型语言,所以操作数据库没有java、php等语言方便。但是我们可以通过mongoose建立模型model映射到数据库中去,将对数据库的操作转换到操作model中去。
var mongoose = require("mongoose");
var msgRecord=new mongoose.Schema({
name:{
type:String,
index:true,
},
roomName:{
type:String
},
msg:{
type:String,
},
saytime:{
type:String,
}
});
var UserSchema = new mongoose.Schema({
name: {
type: String,
unique: true,
index: true
},
password:{
type: String,
index: true
},
user_id: {
type: mongoose.Schema.Types.ObjectId,
index: true
},
updated: {
type: Date, default: Date.now
},
status: {
type: Boolean,
default: false
},
h_imgPath: {
type: String,
default:"/img/1.jpg"
},
personalizedSign:{
type:String,
default:"Write something will well`"
}
}); var User = mongoose.model('User', UserSchema);
var Msg=mongoose.model('Msg',msgRecord);
module.exports = {
User:User,
Msg:Msg
};
(3)后台
涉及到的技术:Node.js,socket.io,Express
后台作为前端和数据库的桥梁,接收前端传过来的参数,去请求服务器,响应不同的服务请求。同时,通过socket.io进行实时通信,实时通信的前提是在客户端也要引入相关的js文件,通过on()和emit()方法、自定义事件达到目的
操作socket.io
var users = {};
var QueryUser = require('./mongoDB/models/model').User;
var Msg = require('./mongoDB/models/model').Msg;
//获取实时时间
function gettime() {
var time = new Date();
var timepartone = time.getFullYear() + '-' + (time.getMonth() + 1) + '-' + time.getDate() + ' ';
var timemid = time.getHours(), s;
if (timemid < 6) {
s = "凌晨 " + timemid;
} else if (timemid < 12) {
s = "上午 " + timemid;
} else if (timemid < 18) {
s = "下午 " + '0' + (timemid - 12);
} else {
s = "晚上 " + (timemid - 12);
}
var timeparttwo = s + ":" + (time.getMinutes() < 10 ? '0' + time.getMinutes() : time.getMinutes());
return timepartone + timeparttwo;
}
/*创建三个房间:Square、The Legend of Qin、Naruto*/
var rooms = { 'Square': [], 'The Legend of Qin': [], 'Naruto': [] };
var user = '';
module.exports = function (app, io) {
io.on('connection', function (socket) {
socket.on('join', function (roomName, userName) {
user = userName;
users[socket.id] = userName;
for (var i in rooms) {
if (roomName != i) {
var index = rooms[i].indexOf(user);
if (index !== -1) {
console.log("删除前" + rooms[i]);
rooms[i].splice(index, 1);
io.to(i).emit('sysLeft', user + "退出了房间" + roomName);
socket.leave(i);
console.log(userName + '离开了房间' + i + ':这个房间里还有' + rooms[i]);
}
}
}
var flag = true;
for (var j = 0; j < rooms[roomName].length; j++) {
if (rooms[roomName][j] == user) {
flag = false;
}
}
if (flag) {
rooms[roomName].push(user);
socket.join(roomName);
}
io.sockets.in(roomName).emit('sysJoin', user + '加入了房间' + roomName);
total();
console.log(user + '加入了' + roomName);
});
socket.on('chat message', function (msg, img, roomOf) {
var name = '';
name = users[socket.id];
var newMsg = new Msg({ name: name, msg: msg, saytime: gettime(),roomName:roomOf });
newMsg.save();
if (rooms[roomOf].indexOf(name) === -1) {
return false;
}
console.log(roomOf + ":" + msg);
io.sockets.in(roomOf).emit('chat message', name, msg, img);
});
socket.on('disconnect', function () {
var msg = '', name = '', time = '';
time = gettime();;
name = users[socket.id];
for (var i in rooms) {
var index = rooms[i].indexOf(name);
if (index !== -1) {
console.log("删除前" + rooms[i]);
rooms[i].splice(index, 1);
io.to(i).emit('sysLeft', name + "退出了房间" + i);
socket.leave(i);
console.log(name + '离开了房间' + i + ':这个房间里还有' + rooms[i]);
}
}
msg = name + '离开群聊 ' + time;
io.emit('disconnect', name, msg);
var timeTotal = total();
});
//获取总用户
function total() {
QueryUser.find({}, function (err, doc) {
io.emit('allUser', doc);
});
QueryUser.find({ status: false }, function (err, doc) {
io.emit('outlineUser', doc);
});
QueryUser.find({ status: true }, function (err, doc) {
io.emit('onlineUser', doc);
});
//查询房间里成员的信息
/*三个房间:Square、The Legend of Qin、Naruto*/
var F_RMInfo = [], S_RMInfo = [], T_RMInfo = [];
for (var k = 0; k < rooms["Square"].length; k++) {
QueryUser.findOne({ name: rooms["Square"][k] }, function (err, doc) {
F_RMInfo.push(doc);
io.sockets.in("Square").emit('SquareRoom', F_RMInfo);
console.log(F_RMInfo);
});
}
for (var i = 0; i < rooms["The Legend of Qin"].length; i++) {
QueryUser.findOne({ name: rooms["The Legend of Qin"][i] }, function (err, doc) {
S_RMInfo.push(doc);
io.sockets.in("The Legend of Qin").emit('QinRoom', S_RMInfo);
console.log(S_RMInfo);
});
}
for (var j = 0; j < rooms["Naruto"].length; j++) {
QueryUser.findOne({ name: rooms["Naruto"][j] }, function (err, doc) {
T_RMInfo.push(doc);
io.sockets.in("Naruto").emit('NarutoRoom', T_RMInfo);
console.log(T_RMInfo);
});
}
}
}); };
服务器js
var express = require('express'),
cookieParser = require('cookie-parser'),
bodyParser = require('body-parser'),
http = require('http'),
path = require('path'),
io = require('socket.io'),
mongoose = require('mongoose'),
app = express(),
db,
userRoutes,
socketIO; /* 数据库连接 */
mongoose.connect('mongodb://localhost:27017/chatroom');
db = mongoose.connection;
db.on('error', console.error.bind(console, '数据库连接失败!'));
db.once('open', function callback() {
console.log('数据库连接成功!');
}); /*Express 配置*/
app.use(cookieParser());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static(path.join(__dirname, 'public'))); http=http.createServer(app,function(req,res){
res.writeHead(200, {'Content-Type': 'text/html;charset=utf-8'});
});
io = io(http); indexRoutes = require('./routes/index')(app);
userRoutes = require('./routes/users')(app); /*绑定io到服务器上*/
socketIO = require('./socketIO')(app, io); http.listen(3000, function () {
console.log('listening on *:3000');
});
《基于Node.js实现简易聊天室系列之详细设计》的更多相关文章
- 简单物联网:外网访问内网路由器下树莓派Flask服务器
最近做一个小东西,大概过程就是想在教室,宿舍控制实验室的一些设备. 已经在树莓上搭了一个轻量的flask服务器,在实验室的路由器下,任何设备都是可以访问的:但是有一些限制条件,比如我想在宿舍控制我种花 ...
- 利用ssh反向代理以及autossh实现从外网连接内网服务器
前言 最近遇到这样一个问题,我在实验室架设了一台服务器,给师弟或者小伙伴练习Linux用,然后平时在实验室这边直接连接是没有问题的,都是内网嘛.但是回到宿舍问题出来了,使用校园网的童鞋还是能连接上,使 ...
- 外网访问内网Docker容器
外网访问内网Docker容器 本地安装了Docker容器,只能在局域网内访问,怎样从外网也能访问本地Docker容器? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Docker容器 ...
- 外网访问内网SpringBoot
外网访问内网SpringBoot 本地安装了SpringBoot,只能在局域网内访问,怎样从外网也能访问本地SpringBoot? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装Java 1 ...
- 外网访问内网Elasticsearch WEB
外网访问内网Elasticsearch WEB 本地安装了Elasticsearch,只能在局域网内访问其WEB,怎样从外网也能访问本地Elasticsearch? 本文将介绍具体的实现步骤. 1. ...
- 怎样从外网访问内网Rails
外网访问内网Rails 本地安装了Rails,只能在局域网内访问,怎样从外网也能访问本地Rails? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Rails 默认安装的Rails端口 ...
- 怎样从外网访问内网Memcached数据库
外网访问内网Memcached数据库 本地安装了Memcached数据库,只能在局域网内访问,怎样从外网也能访问本地Memcached数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装 ...
- 怎样从外网访问内网CouchDB数据库
外网访问内网CouchDB数据库 本地安装了CouchDB数据库,只能在局域网内访问,怎样从外网也能访问本地CouchDB数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Cou ...
- 怎样从外网访问内网DB2数据库
外网访问内网DB2数据库 本地安装了DB2数据库,只能在局域网内访问,怎样从外网也能访问本地DB2数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动DB2数据库 默认安装的DB2 ...
- 怎样从外网访问内网OpenLDAP数据库
外网访问内网OpenLDAP数据库 本地安装了OpenLDAP数据库,只能在局域网内访问,怎样从外网也能访问本地OpenLDAP数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动 ...
随机推荐
- 详解Java动态代理机制
之前介绍的反射和注解都是Java中的动态特性,还有即将介绍的动态代理也是Java中的一个动态特性.这些动态特性使得我们的程序很灵活.动态代理是面向AOP编程的基础.通过动态代理,我们可以在运行时动态创 ...
- RocketMQ 介绍与基本使用
介绍 RocketMQ是阿里巴巴自研的第三代分布式消息中间件,是阿里系下开源的一款分布式.队列模型的消息中间件,原名Metaq,3.0 版本名称改为RocketMQ,是阿里参照kafka设计思想使用J ...
- Python学习之路-Day2-Python基础3
Python学习之路第三天 学习内容: 1.文件操作 2.字符转编码操作 3.函数介绍 4.递归 5.函数式编程 1.文件操作 打印到屏幕 最简单的输出方法是用print语句,你可以给它传递零个或多个 ...
- 在QT中创建文件
最近在做QT东西时遇到在指定路径下创建文件,发现qt中没有直接用的. 主要通过自定义一个createFile()函数来实现,其中需要用到<QFile> <QDir> <Q ...
- Memcached for windows x64 x32 安装
Memcached for windows 一.安装Memcached 1.下载 Memcached32位:http://s3.amazonaws.com/downloads.northscale.c ...
- Java 多线程详解(三)------线程的同步
Java 多线程详解(一)------概念的引入:http://www.cnblogs.com/ysocean/p/6882988.html Java 多线程详解(二)------如何创建进程和线程: ...
- 对类对象使用new时地址分配的情况
我们知道,string类内部的构造函数是采用new来分配地址的.当创建对象时,会调用string的构造函数,从而实质上也使用了new.那么问题来了,如果我用new再创建一个string类型的指针呢?下 ...
- EntityFramework6.X 之LocalDB&ConnectionString
LocalDB 面向开发人员的SQL Server Express的执行模式,它的安装将复制启动SQL Server数据库引擎所需的最少文件集且使用特定连接字符串来启动连接,它是可以创建和打开SQL ...
- iOS 开发之 protocol Buffer 数据交换
前言: 从 14 年公司做项目时开始接触 Google 的 protocol Buffer,用了一段时间,后来到新公司就没有机会再使用了,趁着还没完全忘记,记录下. 简介:protocolbuffer ...
- 从计算机语言的发展到我的第一行代码(HelloWorld)
程序:为了让计算机执行某些操作或解决某个问题而编写的一系列有序指令的集合 算法:解决问题的具体方法和步骤 流程图是算法的一种图形化表示方式. 流程图直观.清晰,更有利于人们设计与理解算法. 它使用一组 ...