566
社区成员
随着互联网的发展,远程办公学习需要的日渐增多。因此本方案旨在设计一种简易的在线多人聊天室系统,实现多用户的相互交流。
本项目主要使用Vue+Express(node.js)框架,使用Socket.io实现前后端的通信。
Vue 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。
Express 是一个属于 Node 平台的 Web 应用开发框架,它提供了一系列的强大特性,帮助你创建各种 Web 应用,使用所选择的各种 HTTP 实用工具和中间件,快速方便地创建强大的 API。Express 提供精简的基本 Web 应用程序功能,而不会隐藏您了解和青睐的 Node.js 功能。许多流行的开发框架都基于 Express 构建。
node.js提供了高效的服务端运行环境,但是由于浏览器端对HTML5的支持不一,为了兼容所有浏览器,提供卓越的实时的用户体验,并且为程序员提供客户端与服务端一致的编程体验,于是socket.io诞生。Socket.io将Websocket和轮询 (Polling)机制以及其它的实时通信方式封装成了通用的接口,并且在服务端实现了这些实时机制的相应代码。
用户可以通过用户名(昵称)作为用户标识进入聊天室,用户进入与离开聊天室都会群发系统消息提示所有用户。当用户欲使用的用户名与聊天室内其他用户用户名重复时,将弹出错误提示。
系统将实时统计更新聊天室内的用户人数,并显示在顶部。
用户可以发送消息,该消息将自动转发给聊天室内其他用户。
若用户退出聊天室并在一定时间内重新进入聊天室,即断连后重连,利用Session中的信息我们可以识别出这个用户为旧用户。
首先,我们在输入栏中输入“ZZY569”作为第一个登录用户的用户名。
之后,我们在另一个终端尝试用同一个用户名登入聊天室,这时系统检测到该用户名与当前用户列表中某一用户名重复,于是弹出错误提示,要求用户更换用户名。
在我们登录了第一个用户后,我们在其他两个终端分别用“ZZY”和“569”作为用户名登入该聊天室。如上图所示,这是我们在第一个登录用户的界面看到的信息,可见三人依次登入聊天室,并且聊天室当前总人数为3人。
用户1首先发送消息“hello”:
其他用户接收到该消息,其中显示了发送用户的ID与发送时间:
用户3离开聊天室,我们从用户1的界面看到用户3的断连提示,以及聊天室总人数的变化。
用户3重新进入该聊天室,系统查询到Session中存有该用户的数据,因此便显示重连提示(reconnected),而不像首次进入那样显示“569 has joined the Chatroom”。
在前端,用户输入用户名并点击login按钮后,通过向服务端发送登录事件,若用户名为空,则提示用户输入用户名:
/*登录*/
login:function(){
var vm = this;
if(vm.uname){
vm.socket.emit('login',{username:vm.uname})
}else{
alert('Enter Username')
}
},
在服务端,监听socket中的登录事件,当接收到相应数据后,比较当前用户名是否与用户列表中所有用户名重复,若不重复则识别为一个新用户,isNewPerson=ture,认为登陆成功,否则置为false:
/*监听登录*/
socket.on('login',function(data){
for(var i=0;i<users.length;i++){
if(users[i].username === data.username){
isNewPerson = false;
break;
}else{
isNewPerson = true;
}
}
若登陆成功,则向前端发送登录成功信息,并向所有客户广播该用户登录事件,以及聊天室人数变更的事件:
/*登录成功*/
socket.emit('loginSuccess',{username:data.username});
/*向所有连接的客户端广播事件*/
io.sockets.emit('receiveMessage',resdata);
console.log('登录成功:',resdata)
console.log('当前连接的用户为:',users);
io.sockets.emit('amountChange',users.length);
若登陆失败,则向前端发送登录失败事件:
socket.emit('loginFail','');
前端监听并收到服务端的返回事件后,进行相应的处理并更新数据:
/*登录成功*/
vm.socket.on('loginSuccess',function(data){
vm.name = data.username;
vm.isCheckin = true;
})
/*登录失败*/
vm.socket.on('loginFail',function(){
alert('Please Use A New Name')
})
/*监听人数变化事件*/
vm.socket.on('amountChange',function(data){
vm.amount = data
})
用户退出界面后,服务端检测到socket断开,便向所有用户广播用户离开事件以及人数变更事件:
/*退出登录*/
socket.on('disconnect',function(){
let resdata = {
msgType: 0,
msgDate: new Date(),
message: 'SysInfo:'+username+' has disconnected',
};
io.sockets.emit('receiveMessage',resdata);
/*向所有连接的客户端广播事件*/
io.sockets.emit('leave',username);
users.map(function(val,index){
if(val.username === username){
users.splice(index,1);
}
/*发送人数变更事件信息*/
io.sockets.emit('amountChange',users.length);
})
}
每当用户人数变更后,服务端便会广播人数变更事件,并携带当前人数信息:
io.sockets.emit('amountChange',users.length);
客户端监听到人数变化后,便更新相应数据,从而用户可以在页面上看到人数的实时变化:
/*监听人数变化事件*/
vm.socket.on('amountChange',function(data){
vm.amount = data
})
<h1>Chatroom(TotalNumber:{{amount}})</h1>
用户在前端点击“发送”按钮就会触发发送消息事件,通过socket将改时间以及用户输入的消息发送给服务端。若用户输入的消息为空,则会提示用户输入具体信息:
/*发送消息*/
sendMessage:function(){
var vm = this;
if(vm.inputMsg){
vm.socket.emit('sendMessage',{username:vm.uname,message:vm.inputMsg});
vm.inputMsg = '';
}else{
alert('Please Enter……')
}
},
服务端监听到用户发送消息后,便会在用户的输入文字的基础上加入一些附加信息,比如发送用户名,信息类型与时间信息等,之后便产生一个接受信息的事件,把该信息广播给所有用户:
/*监听到客户发送消息*/
socket.on('sendMessage',function(data){
let resdata = {
username: data.username, /*发送方用户名*/
msgType: 1, /*信息类型:0为系统消息,1为用户消息*/
msgDate: new Date(), /*加入时间信息*/
message: data.message,
}
io.sockets.emit('receiveMessage',resdata);
})
前端收到服务端的接受信息事件后,将其中的信息呈现给用户:
vm.socket.on('receiveMessage',function(data){
vm.msgList.push(data);
window.scrollTo(0, document.getElementById('chat_con').scrollHeight);
})
并且根据信息的类型(0为系统消息,1为用户消息)呈现的格式有所不同。同时,若信息为用户消息,且根据发送用户名的情况,呈现的格式也有所不同。若发送用户名与当前用户名相同,则信息呈现在右侧,表示为自己发送的消息,反之,则呈现在左侧,代表他人发送的消息。
<template v-if="item.msgType==0">
<p >{{item.message}}</p><br/>
</template>
<template v-else>
<div class="chat-item item-right clearfix" v-if="uname == item.username "><span class="img fr"></span><span class="message fr">{{item.message}}</span></div>
<div class="chat-item item-left clearfix rela" v-else><span class="abs uname">{{item.username}} ( {{item.msgDate | formatDate}} )</span><span class="img fl"></span><span class="fl message">{{item.message}}</span></div>
</template>
在用户断连后,相应的session仍会保留其信息一段时间。在用户重连时,我们判断其对应的session中用户数据是否存在,若存在,则判断该用户是一个重连用户,在对应的登录广播信息中我们设置为'has reconnected',表示其是重连的用户:
if (socket.handshake.session.userdata){
username = data.username;
users.push({
username:data.username
})
let resdata = {
msgType: 0,
msgDate: new Date(),
message: 'SysInfo:'+data.username+' has reconnected',
}
/*登录成功*/
socket.emit('loginSuccess',{username:username});
/*向所有连接的客户端广播事件*/
io.sockets.emit('receiveMessage',resdata);
io.sockets.emit('amountChange',users.length);
}
该功能可根据需求进行优化,比如在使用用户名+密码的登录方式中,检测到为重连用户,可以让其不输入密码而直接登录。
作者:NP569