聊天室入门实战(node,sockte.io实现)--第三章(在线成员列表及头像显示,单聊)
来源:互联网 发布:java system.gc 编辑:程序博客网 时间:2024/06/15 07:29
项目已经部署,请访问: "chat.mycollagelife.com"
这是整个实战系列的第三章,这三章关联性较大,建议先阅读第一 , 第二两章,本章实现了在线成员的动态显示,和头像的匹配,单聊的设置,以及消息的美化,该系列教程非常详细,建议耐心读完。
实现的结果如图:
登录:
群聊,用户列表
单聊:
该项目已经上传至github https://github.com/neuqzxy/chat ,觉的可以的话,给个星星吧。
技术要求:
本章没有使用到什么其他的技术,如果你符合第二章的要求,就可以完成本章内容
实现在线用户列表
准备工作
1. 先拷贝一份第二章的源码到chat++文件夹中。
2. 逻辑分析:
在线用户列表是通过服务器端的全部成员数组来实现的,
首先,我们在登录之后要初始化用户列表,这需要服务器端返回一个数组,最少包括用户名和头像信息。
之后,处于登录状态,每次新增成员只需要服务器端广播该成员用户名和头像即可。
3. 界面调整:
我们要将界面分为两部分左部为用户列表,右部为聊天界面,并且聊天区域要设置最大的高度,超出显示滚动条。
创建静态文件
首先,用bootstrap的样式,将页面分成两部分,现在的所有内容为一部分,用户列表为另一部分,设置用户列表id为usergroup
<div style="display: none;" id="chatbox"> <div id="usergroup" class="col-md-2"> </div> <div class="col-md-9 col-md-offset-1"> <div class="alert alert-info alert-dismissible fade in" role="alert" id="myalert"> <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button> </div> <div class="alert alert-info alert-dismissible fade in" role="alert" id="myalert1"> <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button> <span></span> </div> <div id="content"></div> <div id="inputgrop"> <div class="col-md-10"> <input type="text" placeholder="saying somgthing" class="form-control chatinput" id="chatinput"> </div> <form action="" style="display: none;" id="resetform"><input type="file" style="display: none" id="imginput"></form> <div class="col-md-2"> <button type="button" class="btn btn-primary" id="imgbutton">发送图片</button> </div> </div> </div></div>
接下来我们使用bootstrap中的标签,显示在线成员字样,然后下面就是成员列表了:
<span class="label label-primary" style="font-size: 2em;">在线成员</span>
成员列表我们使用bootstrap中的列表组来实现
<div id="usergroup" class="col-md-2"> <span class="label label-primary" style="font-size: 2em;">在线成员</span> <div class="list-group"></div> </div>
现在看一下,已经成型了,但是聊天输入框的定位失效了,接下来我们打开开发者工具,发现html,chatbox 和里面两个div的高度全变成失效了,我们设置以下属性:
html,body { background: #ccc; padding: 20px 0 20px 0; height: 100%; }
#chatbox { height: 100%; }然后再聊天区域的div里设置一个class,设置高度100%。
<div class="col-md-9 chatwindow col-md-offset-1">.chatwindow { height: 100%; }现在就可以啦。
初始化页面时显示用户列表
当我们登录进去的时候要渲染出现在在线的所有用户,并且让自己排在第一位。
我们打开app.js在loginSuccess中添加一行,将所有用户数组传过去
//然后触发loginSuccess事件告诉浏览器登陆成功了,广播形式触发 data.userGroup = users; //将所有用户数组传过去 io.emit('loginSuccess',data); //将data原封不动的再发给该浏览器
在main.js中,参照bootstrap的链接样式:
<div class="list-group"> <a href="#" class="list-group-item active"> Cras justo odio </a> <a href="#" class="list-group-item">Dapibus ac facilisis in</a> <a href="#" class="list-group-item">Morbi leo risus</a> <a href="#" class="list-group-item">Porta ac consectetur ac</a> <a href="#" class="list-group-item">Vestibulum at eros</a></div>
我们这么写:/** * 用户列表渲染 * 先添加自己,在从data中找到别人添加进去 */ _$listGroup.append(`<a href="#" class="list-group-item">${_username}</a>`); //添加别人 for(let _user of data.userGroup) { if (_user.username !== _username) { _$listGroup.append(`<a href="#" class="list-group-item">${_user.username}</a>`); } }
注意传入data。 这样就渲染出来了。
用户上下线时列表更新
这部分逻辑要重新写监听事件:
首先我们在loginSuccess中写上线时列表更新,加一行就行:
socket.on('loginSuccess',(data)=>{ /** * 如果服务器返回的用户名和刚刚发送的相同的话,就登录 * 否则说明有地方出问题了,拒绝登录 */ if(data.username === _username) { beginChat(data); }else { $('#myalert1 span').html(`<span>您的好友<strong>${data.username}</strong>上线了!</span>`); setTimeout(function() { $("#myalert1").hide(); }, 1000); $("#myalert1").show(); //用户列表添加该用户 _$listGroup.append(`<a href="#" class="list-group-item">${data.username}</a>`); } });
然后我们写用户离开的监听。
//断开连接后做的事情 socket.on('disconnect',()=>{ //注意,该事件不需要自定义触发器,系统会自动调用 usersNum --; console.log(`当前有${usersNum}个用户连接上服务器了`); //触发用户离开的监听 socket.broadcast.emit("oneLeave",{username: socket.username}); //删除用户 users.forEach(function (user,index) { if(user.username === socket.username) { users.splice(index,1); //找到该用户,删除 } }) })之所以用socket.broadcast.io是因为我们不需要离开的用户知道了。
在main.js中监听,这里我们有两种方式,第一种:用数组记录下用户的列表,删除该用户,使用数组重新渲染。第二种:提前在DOM中标记,找到所在元素,删除。
这里显然第二种方式更加适合。
我们找到刚才添加的那些用户的<a>标签,给他们添加name属性,值为用户名。
然后写:
socket.on('oneLeave',(data)=>{ //找到该用户并删除 _$listGroup.find($(`a[name='${data.username}']`)).remove(); });
用户下线提示,及功能封装
还记得我们的好友上线提醒吗?我们现在写下线提醒:
我们复制一个红色警告框,
<div class="alert alert-danger alert-dismissible fade in" role="alert" id="myalert2"> <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button> <span></span> </div>
然后让他隐藏,在beginChat里写:
$("#myalert2").hide();到了这里我们直接写一个函数,将好友上下线的功能写进去:
/** * * @param flag 为1代表好友上线,-1代表好友下线 * @param data 存储用户信息 */ let comAndLeave = function (flag,data) { //上线显示警告框,用户列表添加一个 if(flag === 1) { $('#myalert1 span').html(`<span>您的好友<strong>${data.username}</strong>上线了!</span>`); setTimeout(function() { $("#myalert1").hide(); }, 1000); $("#myalert1").show(); //用户列表添加该用户 _$listGroup.append(`<a href="#" name="${data.username}" class="list-group-item">${data.username}</a>`); } else { //下线显示警告框,用户列表删除一个 $('#myalert2 span').html(`<span>您的好友<strong>${data.username}</strong>下线了!</span>`); setTimeout(function() { $("#myalert2").hide(); }, 1000); $("#myalert2").show(); //找到该用户并删除 _$listGroup.find($(`a[name='${data.username}']`)).remove(); } };
函数内容很简单,全都是复制之前的代码,现在loginSuccess和onLeave都只需要调用一下该函数就行了
socket.on('oneLeave',(data)=>{ comAndLeave(-1,data); });
socket.on('loginSuccess',(data)=>{ /** * 如果服务器返回的用户名和刚刚发送的相同的话,就登录 * 否则说明有地方出问题了,拒绝登录 */ if(data.username === _username) { beginChat(data); }else { comAndLeave(1,data); } });
设置用户头像
到了这一步还不够,我们想有一个头像,这个头像是随机分配,但是在所有用户界面看都是相同的,所以我们需要随机选一个图片并广播给所有人。
我们进入阿里的图标库,选你喜欢的图片吧,可以添加至项目中以外链的形式加入到源码中。我已经选好了:
最后一个是自己,每个人随机头像自己是看不到的,在自己的界面只能看到最后的那个人头。
具体用法官网很清楚,我使用的是Symbol,因为他支持彩色。
我们打开使用帮助:
设置URL,css:
<script src="http://at.alicdn.com/t/font_o86wdrgtu766r.js"></script> <style> .icon { width: 1em; height: 1em; vertical-align: -0.15em; fill: currentColor; overflow: hidden; } </style>注意,每个人的URL不同。
在showMessage,showImg,beginChat等地方设置本人头像:
/** * Created by zhouxinyu on 2017/8/6. */$(function(){ const url = 'http://127.0.0.1:3000'; let _username = null; let _$inputname = $("#name"); let _$loginButton = $("#loginbutton"); let _$chatinput = $("#chatinput"); let _$inputGroup = $("#inputgrop"); let _$imgButton = $("#imgbutton"); let _$imgInput = $("#imginput"); let _$listGroup = $(".list-group"); let socket = io.connect(url); //设置用户名,当用户登录的时候触发 let setUsername = function () { _username = _$inputname.val().trim(); //得到输入框中用户输入的用户名 //判断用户名是否存在 if(_username) { socket.emit('login',{username: _username}); //如果用户名存在,就代表可以登录了,我们就触发登录事件,就相当于告诉服务器我们要登录了 } }; let beginChat = function (data) { /** * 1.隐藏登录框,取消它绑定的事件 * 2.显示聊天界面 */ $("#loginbox").hide('slow'); _$inputname.off('keyup'); _$loginButton.off('click'); /** * 显示聊天界面,并显示一行文字,表示是谁的聊天界面 * 一个2s的弹框,显示欢迎字样 * 这里我使用了ES6的语法``中可以使用${}在里面写的变量可以直接被浏览器渲染 */ $(`<h2 style="text-align: center">${_username}的聊天室</h2>`).insertBefore($("#content")); $(`<strong>欢迎你</strong><span>${_username}!</span>`).insertAfter($('#myalert button')); $("#myalert1").hide(); $("#myalert2").hide(); $('#myalert').alert(); setTimeout(function () { $('#myalert').alert('close'); },2000); $("#chatbox").show('slow'); /** * 用户列表渲染 * 先添加自己,在从data中找到别人添加进去 */ _$listGroup.append(`<a href="#" name="${_username}" class="list-group-item"><svg class="icon" aria-hidden="true" style="font-size: 2em"><use xlink:href="#icon-yonghu"></use></svg> ${_username}</a>`); //添加别人 for(let _user of data.userGroup) { if (_user.username !== _username) { _$listGroup.append(`<a href="#" name="${_user.username}" class="list-group-item">${_user.username}</a>`); } } }; let sendMessage = function () { /** * 得到输入框的聊天信息,如果不为空,就触发sendMessage * 将信息和用户名发送过去 */ let _message = _$chatinput.val(); if(_message) { socket.emit('sendMessage',{username: _username, message: _message}); } }; let setInputPosition = function () { let height = $(window).height()>$('#content p:last').offset().top+$('#content p:last').height()*2?$(window).height():$('#content p:last').offset().top+$('#content p:last').height()*2; _$inputGroup.css({'top': height}); }; let showMessage = function (data) { //先判断这个消息是不是自己发出的,然后再以不同的样式显示 if(data.username === _username){ $("#content").append(`<p style='background: lightskyblue'><svg class="icon" aria-hidden="true" style="font-size: 2em"><use xlink:href="#icon-yonghu"></use></svg> <span>${data.username} : </span> ${data.message}</p>`); }else { $("#content").append(`<p style='background: lightpink'><span>${data.username} : </span> ${data.message}</p>`); } setInputPosition(); }; let sendImg = function (event) { /** * 先判断浏览器是否支持FileReader */ if (typeof FileReader === 'undefined') { alert('您的浏览器不支持,该更新了'); //使用bootstrap的样式禁用Button _$imgButton.attr('disabled', 'disabled'); } else { let file = event.target.files[0]; //先得到选中的文件 //判断文件是否是图片 if(!/image\/\w+/.test(file.type)){ //如果不是图片 alert ("请选择图片"); return false; } /** * 然后使用FileReader读取文件 */ let reader = new FileReader(); reader.readAsDataURL(file); /** * 读取完自动触发onload函数,我们触发sendImg事件给服务器 */ reader.onload = function (e) { socket.emit('sendImg',{username: _username, dataUrl: this.result}); } } }; let showImg = function (data) { //先判断这个消息是不是自己发出的,然后再以不同的样式显示 if(data.username === _username) { $('#content').append(`<p class="bg-primary" style=' text-align:center;'><svg class="icon" aria-hidden="true" style="font-size: 2em"><use xlink:href="#icon-yonghu"></use></svg> <strong style="font-size: 1.5em;">${_username} </strong>: <img class="img-thumbnail" src="${data.dataUrl}" style="max-height: 100px"/></p>`); } else { $('#content').append(`<p class="bg-info" style=''><strong style="font-size: 1.5em;">${_username} </strong>: <img class="img-thumbnail" src="${data.dataUrl}" style="max-height: 100px"/></p>`); } setInputPosition(); }; /** * * @param flag 为1代表好友上线,-1代表好友下线 * @param data 存储用户信息 */ let comAndLeave = function (flag,data) { //上线显示警告框,用户列表添加一个 if(flag === 1) { $('#myalert1 span').html(`<span>您的好友<strong>${data.username}</strong>上线了!</span>`); setTimeout(function() { $("#myalert1").hide(); }, 1000); $("#myalert1").show(); //用户列表添加该用户 _$listGroup.append(`<a href="#" name="${data.username}" class="list-group-item"><svg class="icon" aria-hidden="true"><use xlink:href="#icon-yonghu" style="font-size: 2em"></use></svg>${data.username}</a>`); } else { //下线显示警告框,用户列表删除一个 $('#myalert2 span').html(`<span>您的好友<strong>${data.username}</strong>下线了!</span>`); setTimeout(function() { $("#myalert2").hide(); }, 1000); $("#myalert2").show(); //找到该用户并删除 _$listGroup.find($(`a[name='${data.username}']`)).remove(); } }; /* 前端事件 */ /*登录事件*/ _$loginButton.on('click',function (event) { //监听按钮的点击事件,如果点击,就说明用户要登录,就执行setUsername函数 setUsername(); }); _$inputname.on('keyup',function (event) { //监听输入框的回车事件,这样用户回车也能登录。 if(event.keyCode === 13) { //如果用户输入的是回车键,就执行setUsername函数 setUsername(); } }); /*聊天事件*/ _$chatinput.on('keyup',function (event) { if(event.keyCode === 13) { sendMessage(); _$chatinput.val(''); } }); //点击图片按钮触发input _$imgButton.on('click',function (event) { _$imgInput.click(); return false; }); _$imgInput.change(function (event) { sendImg(event); //重置一下form元素,否则如果发同一张图片不会触发change事件 $("#resetform")[0].reset(); }); /* socket.io部分逻辑 */ socket.on('loginSuccess',(data)=>{ /** * 如果服务器返回的用户名和刚刚发送的相同的话,就登录 * 否则说明有地方出问题了,拒绝登录 */ if(data.username === _username) { beginChat(data); }else { comAndLeave(1,data); } }); socket.on('receiveMessage',(data)=>{ /** * 监听到事件发生,就显示信息 */ showMessage(data); }); socket.on('usernameErr',(data)=>{ /** * 我们给外部div添加 .has-error * 拷贝label插入 * 控制显示的时间为1.5s */ $(".login .form-inline .form-group").addClass("has-error"); $('<label class="control-label" for="inputError1">用户名重复</label>').insertAfter($('#name')); setTimeout(function() { $('.login .form-inline .form-group').removeClass('has-error'); $("#name + label").remove(); }, 1500) }); socket.on('receiveImg',(data)=>{ /** * 监听到receiveImg发生,就显示图片 */ showImg(data); }); socket.on('oneLeave',(data)=>{ comAndLeave(-1,data); });});
然后我们给所有人设置一个随机头像
//随机设置头像的地址 let touXiang = function (url) { let _url = url || (Math.random()*8 | 0); switch (_url) { case 0 : return "icon-river__easyiconnet1"; case 1 : return "icon-river__easyiconnet"; case 2 : return "icon-photo_camera__easyiconnet"; case 3 : return "icon-planet_earth__easyiconnet"; case 4 : return "icon-palace__easyiconnet"; case 5 : return "icon-mountain__easyiconnet"; case 6 : return "icon-parachute__easyiconnet"; case 7 : return "icon-map__easyiconnet"; case 8 : return "icon-mountains__easyiconnet"; case -1 : return "icon-yonghu" } };这个就是一个随机生成头像url的函数,没什么难度。
//设置用户名,当用户登录的时候触发 let setUsername = function () { _username = _$inputname.val().trim(); //得到输入框中用户输入的用户名 //判断用户名是否存在 if(_username) { socket.emit('login',{username: _username, touXiangUrl: touXiang()}); //如果用户名存在,就代表可以登录了,我们就触发登录事件,就相当于告诉服务器我们要登录了 } };
然后我们将头像URL也传给服务器
//如果用户名存在。将该用户的信息存进数组中 if(socket.username){ users.push({ username: data.username, message: [], dataUrl:[], touXiangUrl:data.touXiangUrl });
我们现在只需要在每一个传username的地方再传一个头像地址就行,然后,在showMwssage,showImg,beginChat地方设置我们的头像即可
/** * Created by zhouxinyu on 2017/8/6. */$(function(){ const url = 'http://127.0.0.1:3000'; let _username = null; let _$inputname = $("#name"); let _$loginButton = $("#loginbutton"); let _$chatinput = $("#chatinput"); let _$inputGroup = $("#inputgrop"); let _$imgButton = $("#imgbutton"); let _$imgInput = $("#imginput"); let _$listGroup = $(".list-group"); let _touXiangUrl = null; let socket = io.connect(url); //随机设置头像的地址 let touXiang = function (url) { let _url = url || (Math.random()*8 | 0); switch (_url) { case 0 : return "icon-river__easyiconnet1"; case 1 : return "icon-river__easyiconnet"; case 2 : return "icon-photo_camera__easyiconnet"; case 3 : return "icon-planet_earth__easyiconnet"; case 4 : return "icon-palace__easyiconnet"; case 5 : return "icon-mountain__easyiconnet"; case 6 : return "icon-parachute__easyiconnet"; case 7 : return "icon-map__easyiconnet"; case 8 : return "icon-mountains__easyiconnet"; case -1 : return "icon-yonghu" } }; //设置用户名,当用户登录的时候触发 let setUsername = function () { _username = _$inputname.val().trim(); //得到输入框中用户输入的用户名 _touXiangUrl = touXiang(); //判断用户名是否存在 if(_username) { socket.emit('login',{username: _username, touXiangUrl: _touXiangUrl}); //如果用户名存在,就代表可以登录了,我们就触发登录事件,就相当于告诉服务器我们要登录了 } }; let beginChat = function (data) { /** * 1.隐藏登录框,取消它绑定的事件 * 2.显示聊天界面 */ $("#loginbox").hide('slow'); _$inputname.off('keyup'); _$loginButton.off('click'); /** * 显示聊天界面,并显示一行文字,表示是谁的聊天界面 * 一个2s的弹框,显示欢迎字样 * 这里我使用了ES6的语法``中可以使用${}在里面写的变量可以直接被浏览器渲染 */ $(`<h2 style="text-align: center">${_username}的聊天室</h2>`).insertBefore($("#content")); $(`<strong>欢迎你</strong><span>${_username}!</span>`).insertAfter($('#myalert button')); $("#myalert1").hide(); $("#myalert2").hide(); $('#myalert').alert(); setTimeout(function () { $('#myalert').alert('close'); },2000); $("#chatbox").show('slow'); /** * 用户列表渲染 * 先添加自己,在从data中找到别人添加进去 */ _$listGroup.append(`<a href="#" name="${_username}" class="list-group-item"><svg class="icon" aria-hidden="true" style="font-size: 2em"><use xlink:href="#icon-yonghu"></use></svg> ${_username}</a>`); //添加别人 for(let _user of data.userGroup) { if (_user.username !== _username) { _$listGroup.append(`<a href="#" name="${_user.username}" class="list-group-item"><svg class="icon" aria-hidden="true" style="font-size: 2em"><use xlink:href="#${_user.touXiangUrl}"></use></svg> ${_user.username}</a>`); } } }; let sendMessage = function () { /** * 得到输入框的聊天信息,如果不为空,就触发sendMessage * 将信息和用户名发送过去 */ let _message = _$chatinput.val(); if(_message) { socket.emit('sendMessage',{username: _username, message: _message, touXiangUrl: _touXiangUrl}); } }; let setInputPosition = function () { let height = $(window).height()>$('#content p:last').offset().top+$('#content p:last').height()*2?$(window).height():$('#content p:last').offset().top+$('#content p:last').height()*2; _$inputGroup.css({'top': height}); }; let showMessage = function (data) { //先判断这个消息是不是自己发出的,然后再以不同的样式显示 if(data.username === _username){ $("#content").append(`<p style='background: lightskyblue'><svg class="icon" aria-hidden="true" style="font-size: 2em"><use xlink:href="#icon-yonghu"></use></svg> <span>${data.username} : </span> ${data.message}</p>`); }else { $("#content").append(`<p style='background: lightpink'><svg class="icon" aria-hidden="true" style="font-size: 2em"><use xlink:href="#${data.touXiangUrl}"></use></svg> <span>${data.username} : </span> ${data.message}</p>`); } setInputPosition(); }; let sendImg = function (event) { /** * 先判断浏览器是否支持FileReader */ if (typeof FileReader === 'undefined') { alert('您的浏览器不支持,该更新了'); //使用bootstrap的样式禁用Button _$imgButton.attr('disabled', 'disabled'); } else { let file = event.target.files[0]; //先得到选中的文件 //判断文件是否是图片 if(!/image\/\w+/.test(file.type)){ //如果不是图片 alert ("请选择图片"); return false; } /** * 然后使用FileReader读取文件 */ let reader = new FileReader(); reader.readAsDataURL(file); /** * 读取完自动触发onload函数,我们触发sendImg事件给服务器 */ reader.onload = function (e) { socket.emit('sendImg',{username: _username, dataUrl: this.result, touXiangUrl: _touXiangUrl}); } } }; let showImg = function (data) { //先判断这个消息是不是自己发出的,然后再以不同的样式显示 if(data.username === _username) { $('#content').append(`<p class="bg-primary" style=' text-align:center;'><svg class="icon" aria-hidden="true" style="font-size: 2em"><use xlink:href="#icon-yonghu"></use></svg> <strong style="font-size: 1.5em;">${_username} </strong>: <img class="img-thumbnail" src="${data.dataUrl}" style="max-height: 100px"/></p>`); } else { $('#content').append(`<p class="bg-info" style=''><svg class="icon" aria-hidden="true" style="font-size: 2em"><use xlink:href="#${data.touXiangUrl}"></use></svg> <strong style="font-size: 1.5em;">${data.username} </strong>: <img class="img-thumbnail" src="${data.dataUrl}" style="max-height: 100px"/></p>`); } setInputPosition(); }; /** * * @param flag 为1代表好友上线,-1代表好友下线 * @param data 存储用户信息 */ let comAndLeave = function (flag,data) { //上线显示警告框,用户列表添加一个 if(flag === 1) { $('#myalert1 span').html(`<span>您的好友<strong>${data.username}</strong>上线了!</span>`); setTimeout(function() { $("#myalert1").hide(); }, 1000); $("#myalert1").show(); //用户列表添加该用户 _$listGroup.append(`<a href="#" name="${data.username}" class="list-group-item"><svg class="icon" aria-hidden="true" style="font-size: 2em"><use xlink:href="#${data.touXiangUrl}"></use></svg>${data.username}</a>`); } else { //下线显示警告框,用户列表删除一个 $('#myalert2 span').html(`<span>您的好友<strong>${data.username}</strong>下线了!</span>`); setTimeout(function() { $("#myalert2").hide(); }, 1000); $("#myalert2").show(); //找到该用户并删除 _$listGroup.find($(`a[name='${data.username}']`)).remove(); } }; /* 前端事件 */ /*登录事件*/ _$loginButton.on('click',function (event) { //监听按钮的点击事件,如果点击,就说明用户要登录,就执行setUsername函数 setUsername(); }); _$inputname.on('keyup',function (event) { //监听输入框的回车事件,这样用户回车也能登录。 if(event.keyCode === 13) { //如果用户输入的是回车键,就执行setUsername函数 setUsername(); } }); /*聊天事件*/ _$chatinput.on('keyup',function (event) { if(event.keyCode === 13) { sendMessage(); _$chatinput.val(''); } }); //点击图片按钮触发input _$imgButton.on('click',function (event) { _$imgInput.click(); return false; }); _$imgInput.change(function (event) { sendImg(event); //重置一下form元素,否则如果发同一张图片不会触发change事件 $("#resetform")[0].reset(); }); /* socket.io部分逻辑 */ socket.on('loginSuccess',(data)=>{ /** * 如果服务器返回的用户名和刚刚发送的相同的话,就登录 * 否则说明有地方出问题了,拒绝登录 */ if(data.username === _username) { beginChat(data); }else { comAndLeave(1,data); } }); socket.on('receiveMessage',(data)=>{ /** * 监听到事件发生,就显示信息 */ showMessage(data); }); socket.on('usernameErr',(data)=>{ /** * 我们给外部div添加 .has-error * 拷贝label插入 * 控制显示的时间为1.5s */ $(".login .form-inline .form-group").addClass("has-error"); $('<label class="control-label" for="inputError1">用户名重复</label>').insertAfter($('#name')); setTimeout(function() { $('.login .form-inline .form-group').removeClass('has-error'); $("#name + label").remove(); }, 1500) }); socket.on('receiveImg',(data)=>{ /** * 监听到receiveImg发生,就显示图片 */ showImg(data); }); socket.on('oneLeave',(data)=>{ comAndLeave(-1,data); });});
实现单聊
首先,我们给本人的a标签设置不可点击属性:在class中写disable
_$listGroup.append(`<a href="#" name="${_username}" class="list-group-item disabled"><svg class="icon" aria-hidden="true" style="font-size: 2em"><use xlink:href="#icon-yonghu"></use></svg> ${_username}</a>`);
然后我们找两个模态框:
一个用于发送单聊消息,一个用于显示。修改一下
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>聊天室</title> <script src="JavaScripts/jquery-3.2.1.js"></script> <link rel="stylesheet" href="stylesheets/bootstrap.css"> <script src="JavaScripts/bootstrap.js"></script> <script src="JavaScripts/main.js"></script> <script src="/socket.io/socket.io.js"></script> <script src="http://at.alicdn.com/t/font_o86wdrgtu766r.js"></script> <style> .icon { width: 1em; height: 1em; vertical-align: -0.15em; fill: currentColor; overflow: hidden; } </style> <style> html,body { background: #ccc; padding: 20px 0 20px 0; height: 100%; } h1 { text-align: center; } .login { text-align: center; } #chatbox { height: 100%; } #inputgrop { display: block; width: 100%; position: absolute; min-height: 40px; bottom: 0; } #content { height: auto; } .alert { z-index: 100; position: absolute; top: 0; width: 100%; } .chatwindow { height: 100%; } </style></head><body><div id="loginbox"> <h1>登录</h1> <hr> <div class="login"> <div class="form-inline"> <div class="form-group"> <label>用户名</label> <input type="text" class="form-control" id="name" placeholder="请输入用户名"> </div> <br><br> <button type="button" class="btn btn-info btn-lg" id="loginbutton">登录</button> </div> </div></div><div style="display: none;" id="chatbox"> <div id="usergroup" class="col-md-2"> <span class="label label-primary" style="font-size: 2em;">在线成员</span> <div class="list-group"></div> </div> <div class="col-md-9 chatwindow col-md-offset-1"> <div class="alert alert-info alert-dismissible fade in" role="alert" id="myalert"> <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button> </div> <div class="alert alert-info alert-dismissible fade in" role="alert" id="myalert1"> <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button> <span></span> </div> <div class="alert alert-danger alert-dismissible fade in" role="alert" id="myalert2"> <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button> <span></span> </div> <div id="content"></div> <div id="inputgrop"> <div class="col-md-10"> <input type="text" placeholder="saying somgthing" class="form-control chatinput" id="chatinput"> </div> <form action="" style="display: none;" id="resetform"><input type="file" style="display: none" id="imginput"></form> <div class="col-md-2"> <button type="button" class="btn btn-primary" id="imgbutton">发送图片</button> </div> </div> </div> <!-- 发送模态框(Modal) --> <div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true"> × </button> <h4 class="modal-title" id="myModalLabel"> 模态框(Modal)标题 </h4> </div> <div class="modal-body"> <input type="text" placeholder="say something" class="form-control chatinput" id="inputtoone"> </div> <div class="modal-footer"> <button type="button" class="btn btn-default" data-dismiss="modal" id="closesendtoo">关闭 </button> <button type="button" class="btn btn-primary" id="sendtoo"> 发送 </button> </div> </div><!-- /.modal-content --> </div><!-- /.modal --> </div> <!-- 收到模态框(Modal) --> <div class="modal fade" id="myModal1" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true"> × </button> <h4 class="modal-title" id="myModalLabel1"> 模态框(Modal)标题 </h4> </div> <div class="modal-body shoudao"> </div> <div class="modal-footer"> <button type="button" class="btn btn-primary" data-dismiss="modal" id="closesendtoo"> 知道了 </button> </div> </div><!-- /.modal-content --> </div><!-- /.modal --> </div> <button id="showmodal" data-toggle="modal" data-target="#myModal1" style="display: none;"></button></div></body></html>我们的button是用来触发模态框的,收到消息后需要触发显示
我们在列表的好友的a标签中添加:
data-toggle="modal" data-target="#myModal"这两个属性即可点击触发modal
然后监听点击事件,并换文字:
//监听成员点击事件 _$listGroup.on('click',function (event) { initModal(event); });
initModal = function (event) { let _name = $(event.target).attr('name'); $("#myModalLabel").text(`发给${_name}`); };
然后就点击发送了,我们发送给服务器,
这里我们设了一个全局变量 _to 里面是收件人的用户名
//监听私聊的按钮,触发私聊事件 $("#sendtoo").on('click',function (event) { /** * 得到用户输入的消息,如果部位空,就发送,清空内容关闭模态框 */ let _text = $("#inputtoone").val(); if (typeof _text !== 'undefined') { socket.emit('sendToOne', {to: _to, text: _text, username: _username}); $("#inputtoone").val(''); $("#closesendtoo").click(); } });
打开app.js,写监听:
我们是通过不同的socket来区别不同客户端的,每一个连接的socket都是不同的,所以,我们需要将socket和用户名匹配,我们使用数组:
//如果用户名存在。将该用户的信息存进数组中 if(socket.username){ users.push({ username: data.username, message: [], dataUrl: [], touXiangUrl: data.touXiangUrl }); //保存socket _sockets[socket.username] = socket; //然后触发loginSuccess事件告诉浏览器登陆成功了,广播形式触发 data.userGroup = users; //将所有用户数组传过去 io.emit('loginSuccess',data); //将data原封不动的再发给该浏览器 }
socket.on('sendToOne',(data)=>{ //判断该用户是否存在,如果存在就触发receiveToOne事件 for (let _user of users) { if (_user.username === data.to) { _sockets[data.to].emit('receiveToOne',data); } } });
下面我们在客户端得到该事件的data
socket.on('receiveToOne',(data)=>{ $("#myModalLabel1").text(`来自${data.username}`); $(".shoudao").text(`${data.text}`); $("#showmodal").click(); });三行代码就搞定了。
下面试一下吧,能过成功私聊了。
最后,我们修改一下聊天消息的样式:
在html中添加:
<style> .sender { clear: both } .sender div:nth-of-type(1) { float: left } .sender div:nth-of-type(2) { background-color: #7fffd4; float: left; margin: 0 20px 10px 15px; padding: 10px 10px 10px 0; border-radius: 7px } .receiver div:first-child img, .sender div:first-child img { width: 50px; height: 50px } .receiver { clear: both } .receiver div:nth-child(1) { float: right } .receiver div:nth-of-type(2) { float: right; background-color: gold; margin: 0 10px 10px 20px; padding: 10px 0 10px 10px; border-radius: 7px } .left_triangle { height: 0; width: 0; border-width: 8px; border-style: solid; border-color: transparent #7fffd4 transparent transparent; position: relative; left: -16px; top: 3px } .right_triangle { height: 0; width: 0; border-width: 8px; border-style: solid; border-color: transparent transparent transparent gold; position: relative; right: -16px; top: 3px } </style>然后修改显示消息和图片的代码:
html:
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>聊天室</title> <script src="JavaScripts/jquery-3.2.1.js"></script> <link rel="stylesheet" href="stylesheets/bootstrap.css"> <script src="JavaScripts/bootstrap.js"></script> <script src="JavaScripts/main.js"></script> <script src="/socket.io/socket.io.js"></script> <script src="http://at.alicdn.com/t/font_o86wdrgtu766r.js"></script> <style> .sender { clear: both } .sender div:nth-of-type(1) { float: left } .sender div:nth-of-type(2) { background-color: #7fffd4; float: left; margin: 0 20px 10px 15px; padding: 10px 10px 10px 0; border-radius: 7px } .receiver div:first-child img, .sender div:first-child img { width: 50px; height: 50px } .receiver { clear: both } .receiver div:nth-child(1) { float: right } .receiver div:nth-of-type(2) { float: right; background-color: gold; margin: 0 10px 10px 20px; padding: 10px 0 10px 10px; border-radius: 7px } .left_triangle { height: 0; width: 0; border-width: 8px; border-style: solid; border-color: transparent #7fffd4 transparent transparent; position: relative; left: -16px; top: 3px } .right_triangle { height: 0; width: 0; border-width: 8px; border-style: solid; border-color: transparent transparent transparent gold; position: relative; right: -16px; top: 3px } </style> <style> .icon { width: 1em; height: 1em; vertical-align: -0.15em; fill: currentColor; overflow: hidden; } </style> <style> html,body { background: #ccc; padding: 20px 0 20px 0; height: 100%; } h1 { text-align: center; } .login { text-align: center; } #chatbox { height: 100%; } #inputgrop { display: block; width: 100%; position: absolute; min-height: 40px; bottom: 0; } #content { height: auto; } .alert { z-index: 100; position: absolute; top: 0; width: 100%; } .chatwindow { height: 100%; } </style></head><body><div id="loginbox"> <h1>登录</h1> <hr> <div class="login"> <div class="form-inline"> <div class="form-group"> <label>用户名</label> <input type="text" class="form-control" id="name" placeholder="请输入用户名"> </div> <br><br> <button type="button" class="btn btn-info btn-lg" id="loginbutton">登录</button> </div> </div></div><div style="display: none;" id="chatbox"> <div id="usergroup" class="col-md-2"> <span class="label label-primary" style="font-size: 2em;">在线成员</span> <div class="list-group"></div> </div> <div class="col-md-9 chatwindow col-md-offset-1"> <div class="alert alert-info alert-dismissible fade in" role="alert" id="myalert"> <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button> </div> <div class="alert alert-info alert-dismissible fade in" role="alert" id="myalert1"> <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button> <span></span> </div> <div class="alert alert-danger alert-dismissible fade in" role="alert" id="myalert2"> <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button> <span></span> </div> <div id="content"></div> <div id="inputgrop"> <div class="col-md-10"> <input type="text" placeholder="saying somgthing" class="form-control chatinput" id="chatinput"> </div> <form action="" style="display: none;" id="resetform"><input type="file" style="display: none" id="imginput"></form> <div class="col-md-2"> <button type="button" class="btn btn-primary" id="imgbutton">发送图片</button> </div> </div> </div> <!-- 发送模态框(Modal) --> <div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true"> × </button> <h4 class="modal-title" id="myModalLabel"> 模态框(Modal)标题 </h4> </div> <div class="modal-body"> <input type="text" placeholder="say something" class="form-control chatinput" id="inputtoone"> </div> <div class="modal-footer"> <button type="button" class="btn btn-default" data-dismiss="modal" id="closesendtoo">关闭 </button> <button type="button" class="btn btn-primary" id="sendtoo"> 发送 </button> </div> </div><!-- /.modal-content --> </div><!-- /.modal --> </div> <!-- 收到模态框(Modal) --> <div class="modal fade" id="myModal1" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true"> × </button> <h4 class="modal-title" id="myModalLabel1"> 模态框(Modal)标题 </h4> </div> <div class="modal-body shoudao"> </div> <div class="modal-footer"> <button type="button" class="btn btn-primary" data-dismiss="modal" id="closesendtoo"> 知道了 </button> </div> </div><!-- /.modal-content --> </div><!-- /.modal --> </div> <button id="showmodal" data-toggle="modal" data-target="#myModal1" style="display: none;"></button></div></body></html>
main.js:
/** * Created by zhouxinyu on 2017/8/6. */$(function(){ const url = 'http://127.0.0.1:3000'; let _username = null; let _$inputname = $("#name"); let _$loginButton = $("#loginbutton"); let _$chatinput = $("#chatinput"); let _$inputGroup = $("#inputgrop"); let _$imgButton = $("#imgbutton"); let _$imgInput = $("#imginput"); let _$listGroup = $(".list-group"); let _touXiangUrl = null; let _to = null; let socket = io.connect(url); //随机设置头像的地址 let touXiang = function (url) { let _url = url || (Math.random()*8 | 0); switch (_url) { case 0 : return "icon-river__easyiconnet1"; case 1 : return "icon-river__easyiconnet"; case 2 : return "icon-photo_camera__easyiconnet"; case 3 : return "icon-planet_earth__easyiconnet"; case 4 : return "icon-palace__easyiconnet"; case 5 : return "icon-mountain__easyiconnet"; case 6 : return "icon-parachute__easyiconnet"; case 7 : return "icon-map__easyiconnet"; case 8 : return "icon-mountains__easyiconnet"; case -1 : return "icon-yonghu" } }; initModal = function (event) { _to = $(event.target).attr('name'); $("#myModalLabel").text(`发给${_to}`); }; //设置用户名,当用户登录的时候触发 let setUsername = function () { _username = _$inputname.val().trim(); //得到输入框中用户输入的用户名 _touXiangUrl = touXiang(); //判断用户名是否存在 if(_username) { socket.emit('login',{username: _username, touXiangUrl: _touXiangUrl}); //如果用户名存在,就代表可以登录了,我们就触发登录事件,就相当于告诉服务器我们要登录了 } }; let beginChat = function (data) { /** * 1.隐藏登录框,取消它绑定的事件 * 2.显示聊天界面 */ $("#loginbox").hide('slow'); _$inputname.off('keyup'); _$loginButton.off('click'); /** * 显示聊天界面,并显示一行文字,表示是谁的聊天界面 * 一个2s的弹框,显示欢迎字样 * 这里我使用了ES6的语法``中可以使用${}在里面写的变量可以直接被浏览器渲染 */ $(`<h2 style="text-align: center">${_username}的聊天室</h2>`).insertBefore($("#content")); $(`<strong>欢迎你</strong><span>${_username}!</span>`).insertAfter($('#myalert button')); $("#myalert1").hide(); $("#myalert2").hide(); $('#myalert').alert(); setTimeout(function () { $('#myalert').alert('close'); },2000); $("#chatbox").show('slow'); /** * 用户列表渲染 * 先添加自己,在从data中找到别人添加进去 */ _$listGroup.append(`<a href="#" name="${_username}" class="list-group-item disabled"><svg class="icon" aria-hidden="true" style="font-size: 2em"><use xlink:href="#icon-yonghu"></use></svg> ${_username}</a>`); //添加别人 for(let _user of data.userGroup) { if (_user.username !== _username) { _$listGroup.append(`<a href="#" name="${_user.username}" class="list-group-item" data-toggle="modal" data-target="#myModal"><svg class="icon" aria-hidden="true" style="font-size: 2em"><use xlink:href="#${_user.touXiangUrl}"></use></svg> ${_user.username}</a>`); } } }; let sendMessage = function () { /** * 得到输入框的聊天信息,如果不为空,就触发sendMessage * 将信息和用户名发送过去 */ let _message = _$chatinput.val(); if(_message) { socket.emit('sendMessage',{username: _username, message: _message, touXiangUrl: _touXiangUrl}); } }; let setInputPosition = function () { let height = $(window).height()>$('#content p:last').offset().top+$('#content p:last').height()*2?$(window).height():$('#content p:last').offset().top+$('#content p:last').height()*2; _$inputGroup.css({'top': height}); }; let showMessage = function (data) { //先判断这个消息是不是自己发出的,然后再以不同的样式显示 if(data.username === _username) { $('#content').append(`<div class="receiver"> <div> <svg class="icon img-circle" aria-hidden="true" style="font-size: 2em;"> <use xlink:href="#icon-yonghu"></use> </svg> <strong style="font-size: 1.5em;"> ${data.username} </strong> </div> <div> <div class="right_triangle"></div> <span> ${data.message}</span> </div> </div>`); } else { $('#content').append(`<div class="sender"> <div> <svg class="icon img-circle" aria-hidden="true" style="font-size: 2em;"> <use xlink:href="#${data.touXiangUrl}"></use> </svg> <strong style="font-size: 1.5em;">${data.username} </strong> </div> <div> <div class="left_triangle"></div> <span> ${data.message}</span> </div> </div>`); } setInputPosition(); }; let sendImg = function (event) { /** * 先判断浏览器是否支持FileReader */ if (typeof FileReader === 'undefined') { alert('您的浏览器不支持,该更新了'); //使用bootstrap的样式禁用Button _$imgButton.attr('disabled', 'disabled'); } else { let file = event.target.files[0]; //先得到选中的文件 //判断文件是否是图片 if(!/image\/\w+/.test(file.type)){ //如果不是图片 alert ("请选择图片"); return false; } /** * 然后使用FileReader读取文件 */ let reader = new FileReader(); reader.readAsDataURL(file); /** * 读取完自动触发onload函数,我们触发sendImg事件给服务器 */ reader.onload = function (e) { socket.emit('sendImg',{username: _username, dataUrl: this.result, touXiangUrl: _touXiangUrl}); } } }; let showImg = function (data) { //先判断这个消息是不是自己发出的,然后再以不同的样式显示 if(data.username === _username) { $('#content').append(`<div class="receiver"> <div> <svg class="icon img-circle" aria-hidden="true" style="font-size: 2em;"> <use xlink:href="#icon-yonghu"></use> </svg> <strong style="font-size: 1.5em;"> ${data.username} </strong> </div> <div> <div class="right_triangle"></div> <span><img class="img-thumbnail" src="${data.dataUrl}" style="max-height: 100px"/></span> </div> </div>`); } else { $('#content').append(`<div class="sender"> <div> <svg class="icon img-circle" aria-hidden="true" style="font-size: 2em;"> <use xlink:href="#${data.touXiangUrl}"></use> </svg> <strong style="font-size: 1.5em;">${data.username} </strong> </div> <div> <div class="left_triangle"></div> <span><img class="img-thumbnail" src="${data.dataUrl}" style="max-height: 100px"/></span> </div> </div>`); } setInputPosition(); }; /** * * @param flag 为1代表好友上线,-1代表好友下线 * @param data 存储用户信息 */ let comAndLeave = function (flag,data) { //上线显示警告框,用户列表添加一个 if(flag === 1) { $('#myalert1 span').html(`<span>您的好友<strong>${data.username}</strong>上线了!</span>`); setTimeout(function() { $("#myalert1").hide(); }, 1000); $("#myalert1").show(); //用户列表添加该用户 _$listGroup.append(`<a href="#" name="${data.username}" class="list-group-item" data-toggle="modal" data-target="#myModal"><svg class="icon" aria-hidden="true" style="font-size: 2em"><use xlink:href="#${data.touXiangUrl}"></use></svg>${data.username}</a>`); } else { //下线显示警告框,用户列表删除一个 $('#myalert2 span').html(`<span>您的好友<strong>${data.username}</strong>下线了!</span>`); setTimeout(function() { $("#myalert2").hide(); }, 1000); $("#myalert2").show(); //找到该用户并删除 _$listGroup.find($(`a[name='${data.username}']`)).remove(); } }; /* 前端事件 */ /*登录事件*/ _$loginButton.on('click',function (event) { //监听按钮的点击事件,如果点击,就说明用户要登录,就执行setUsername函数 setUsername(); }); _$inputname.on('keyup',function (event) { //监听输入框的回车事件,这样用户回车也能登录。 if(event.keyCode === 13) { //如果用户输入的是回车键,就执行setUsername函数 setUsername(); } }); /*聊天事件*/ _$chatinput.on('keyup',function (event) { if(event.keyCode === 13) { sendMessage(); _$chatinput.val(''); } }); //点击图片按钮触发input _$imgButton.on('click',function (event) { _$imgInput.click(); return false; }); _$imgInput.change(function (event) { sendImg(event); //重置一下form元素,否则如果发同一张图片不会触发change事件 $("#resetform")[0].reset(); }); //监听成员点击事件 _$listGroup.on('click',function (event) { initModal(event); }); //监听私聊的按钮,触发私聊事件 $("#sendtoo").on('click',function (event) { /** * 得到用户输入的消息,如果部位空,就发送,清空内容关闭模态框 */ let _text = $("#inputtoone").val(); if (typeof _text !== 'undefined') { socket.emit('sendToOne', {to: _to, text: _text, username: _username}); $("#inputtoone").val(''); $("#closesendtoo").click(); } }); /* socket.io部分逻辑 */ socket.on('loginSuccess',(data)=>{ /** * 如果服务器返回的用户名和刚刚发送的相同的话,就登录 * 否则说明有地方出问题了,拒绝登录 */ if(data.username === _username) { beginChat(data); }else { comAndLeave(1,data); } }); socket.on('receiveMessage',(data)=>{ /** * 监听到事件发生,就显示信息 */ showMessage(data); }); socket.on('usernameErr',(data)=>{ /** * 我们给外部div添加 .has-error * 拷贝label插入 * 控制显示的时间为1.5s */ $(".login .form-inline .form-group").addClass("has-error"); $('<label class="control-label" for="inputError1">用户名重复</label>').insertAfter($('#name')); setTimeout(function() { $('.login .form-inline .form-group').removeClass('has-error'); $("#name + label").remove(); }, 1500) }); socket.on('receiveImg',(data)=>{ /** * 监听到receiveImg发生,就显示图片 */ showImg(data); }); socket.on('oneLeave',(data)=>{ comAndLeave(-1,data); }); socket.on('receiveToOne',(data)=>{ $("#myModalLabel1").text(`来自${data.username}`); $(".shoudao").text(`${data.text}`); $("#showmodal").click(); });});
app.js
/** * Created by zhouxinyu on 2017/8/6. */const express = require('express'); // const是ES6的语法,代表常量,准确来说就是指向不发生改变。如果不习惯就用var代替const app = express(); // express官网就是这么写的就是用来创建一个express程序,赋值给app。如果不理解就当公式记住const server = require('http').Server(app);const path = require('path'); // 这是node的路径处理模块,可以格式化路径const io = require('socket.io')(server); //将socket的监听加到app设置的模块里。const users = []; //用来保存所有的用户信息let usersNum = 0;const _sockets = []; //将socket和用户名匹配server.listen(3000,()=>{ // ()=>是箭头函数,ES6语法,如果不习惯可以使用 function() 来代替 ()=> console.log("server running at 127.0.0.1:3000"); // 代表监听3000端口,然后执行回调函数在控制台输出。});/** * app.get(): express中的一个中间件,用于匹配get请求,所谓中间件就是在该轮http请求中依次执行的一系列函数。 * '/': 它匹配get请求的根路由 '/'也就是 127.0.0.1:3000/就匹配到他了 * (req,res): ES6语法的箭头函数,你暂时可以理解为function(req,res){}。 * req带表浏览器的请求对象,res代表服务器的返回对象 */app.get('/',(req,res)=>{ res.redirect('/chat.html'); // express的重定向函数。如果浏览器请求了根路由'/',浏览器就给他重定向到 '127.0.0.1:3000/chat.html'路由中});/** * __dirname表示当前文件所在的绝对路径,所以我们使用path.join将app.js的绝对路径和public加起来就得到了public的绝对路径。 * 用path.join是为了避免出现 ././public 这种奇怪的路径 * express.static就帮我们托管了public文件夹中的静态资源。 * 只要有 127.0.0.1:3000/XXX 的路径都会去public文件夹下找XXX文件然后发送给浏览器。 */app.use('/',express.static(path.join(__dirname,'./public'))); //一句话就搞定。/*socket*/io.on('connection',(socket)=>{ //监听客户端的连接事件 /** * 所有有关socket事件的逻辑都在这里写 */ usersNum ++; console.log(`当前有${usersNum}个用户连接上服务器了`); socket.on('login',(data)=>{ /** * 先保存在socket中 * 循环数组判断用户名是否重复,如果重复,则触发usernameErr事件 * 将用户名删除,之后的事件要判断用户名是否存在 */ socket.username = data.username; for (let user of users) { if(user.username === data.username){ socket.emit('usernameErr',{err: '用户名重复'}); socket.username = null; break; } } //如果用户名存在。将该用户的信息存进数组中 if(socket.username){ users.push({ username: data.username, message: [], dataUrl: [], touXiangUrl: data.touXiangUrl }); //保存socket _sockets[socket.username] = socket; //然后触发loginSuccess事件告诉浏览器登陆成功了,广播形式触发 data.userGroup = users; //将所有用户数组传过去 io.emit('loginSuccess',data); //将data原封不动的再发给该浏览器 } }); /** * 监听sendMessage,我们得到客户端传过来的data里的message,并存起来。 * 我使用了ES6的for-of循环,和ES5 的for-in类似。 * for-in是得到每一个key,for-of 是得到每一个value */ socket.on('sendMessage',(data)=>{ for(let _user of users) { if(_user.username === data.username) { _user.message.push(data.message); //信息存储之后触发receiveMessage将信息发给所有浏览器 io.emit('receiveMessage',data); break; } } }); /** * 仿照sendMessage监听sendImg事件 */ socket.on("sendImg",(data)=>{ for(let _user of users) { if(_user.username === data.username) { _user.dataUrl.push(data.dataUrl); //存储后将图片广播给所有浏览器 io.emit("receiveImg",data); break; } } }); socket.on('sendToOne',(data)=>{ //判断该用户是否存在,如果存在就触发receiveToOne事件 for (let _user of users) { if (_user.username === data.to) { _sockets[data.to].emit('receiveToOne',data); } } }); //断开连接后做的事情 socket.on('disconnect',()=>{ //注意,该事件不需要自定义触发器,系统会自动调用 usersNum --; console.log(`当前有${usersNum}个用户连接上服务器了`); //触发用户离开的监听 socket.broadcast.emit("oneLeave",{username: socket.username}); //删除用户 users.forEach(function (user,index) { if(user.username === socket.username) { users.splice(index,1); //找到该用户,删除 } }) })});
到这里,第三章的内容就结束了,该系列教程的项目源码已经上传至github https://github.com/neuqzxy/chat 欢迎浏览拷贝,如果觉得不错,给个星星吧
- 聊天室入门实战(node,sockte.io实现)--第三章(在线成员列表及头像显示,单聊)
- 聊天室入门实战(node,socket.io实现)--第一章(实现登录群聊功能)
- 聊天室入门实战(node,socket.io实现)--第二章(美化,图片发送,登录提示,认证)
- 入门WebSocket和socket.io,实现在线实时聊天室
- Asp.Net制作聊天室(显示在线用户列表,禁止发言,允许发言,踢出聊天室,退出聊天室)
- Node.js+socket.io在线聊天室
- socket.io 实现在线聊天室
- node.js实现即时聊天室,使用模块(express+socket.io),附源码
- Node.js+Express+Socket.IO搭建在线聊天室
- Node.js 切近实战(六) 之Excel在线(文件列表)
- IM即时聊天室(二): Socket.io + Node.js
- Socket.io在线聊天室
- Socket.io在线聊天室
- Socket.io在线聊天室
- Socket.io在线聊天室
- 基于RED5&Flex流媒体应用实战开发(附多人聊天室、在线秀场及视频会议三大案例)
- express简单实现登录及在线聊天室
- Node.js + express + socket 实现在线实时多人聊天室
- 全局最小割 poj2914 Minimum Cut
- Linux 下wait函数详解
- UTF-8编码)将字符串写入文件,当字符串中包含中文时,出现写入文件的数据残缺,而当不包含中文时,写入文件的数据正常
- CookieUtils工具类
- declspec的用法小结
- 聊天室入门实战(node,sockte.io实现)--第三章(在线成员列表及头像显示,单聊)
- Ubuntu 主题美化
- vux的生命周期自己总结规律
- 智能指针的交叉引用
- 《GPU高性能编程CUDA实战》 《学习笔记二》
- c语言常用技巧
- [NOIP提高组2000]乘积最大
- Mysql的建表规范与注意事项
- java 打印数组内容