美文网首页Node.js专题
Node.js(Express4.x)搭建聊天室3——完善网页

Node.js(Express4.x)搭建聊天室3——完善网页

作者: Mike的读书季 | 来源:发表于2016-09-20 16:36 被阅读685次

    0.目标与前置条件

    这一节,我将完全实现聊天室的全部页面显示和响应。

    Socket.io聊天室Demo

    这一节内容是在上两节完成的情况下进行的,请先参照第一节完成基本框架的搭建:
    Node.js(Express4.x)搭建聊天室1——基本框架

    并参照第二节添加几个监听:
    Node.js(Express4.x)搭建聊天室2——消息发送与监听


    我把搭建聊天室的步骤分成了几个部分,请按顺序阅读:

    获取代码
    Node.js(Express4.x)搭建聊天室1——基本框架
    Node.js(Express4.x)搭建聊天室2——消息发送与监听
    Node.js(Express4.x)搭建聊天室3——完善网页


    1.服务端

    1.1 chatroom.js

    在之前的几节中,我们已经搭建了chatroom的简易版,但如果进入的用户昵称重复了,我们也不能作出判断和处理;此外,当用户修改昵称时,也有可能出现用户昵称重复的情况。(在上一节,我把它定义为了一个对象)

    所以,在这一节,我增加了一个数组来存储用户昵称。

    var userlist = new Array();
    

    在用户加入聊天时,将昵称存入该数组,如果用户的昵称已存在,则在此昵称后增加一个随机数来保证昵称不同。

      /* *************** 用户emit消息"join"时,响应 *************** */
      socket.on('join', function (username) {
        if (addedUser) return;
              
        // 用户信息存储在socket会话中:在此之前,要检查是否重复
        for(var i=0; i<userlist.length; i++) {
            if(userlist[i] == username) {
                username = username+Math.ceil(Math.random()*10000);
                break;
            }
        }
        
        ...
            
        userlist.push(username)  // 将昵称加入数组
            
        ...
    
      });
    

    在用户修改昵称时,在上一节是直接将socket.name替换为新的昵称的。而现在,首先检查数组中是否存在这个昵称,如果没有,则替换,否则提示用户修改失败。

    /* *************** 更改昵称 *************** */
      socket.on('change_name', function (newname) {
        if (addedUser) {
            var oldname = socket.username;
            
            // ************************** 这里开始本节更新 ************************** 
            for(var i=0; i<userlist.length; i++) {
                if(userlist[i] == newname) {
                    // 通知该用户修改成功
                    socket.emit('name_changed_msg', {
                        res: "failed",
                        error: "已有此用户:"+newname,
                        oldname: oldname,
                        newname: newname,
                        type: "RETURN"
                    });
                    return -1;
                }
            }
            // 通知该用户修改成功
            socket.emit('name_changed_msg', {
                res: "success",
                error: null,
                oldname: oldname,
                newname: newname,
                msg: "["+oldname+"] 改名为 ["+socket.username + "]",
                type: "RETURN",
                numUsers: guest_num
            });
                
            for(var i=0; i<userlist.length; i++) {
                if(userlist[i] == oldname) {
                    userlist[i] = newname;
                    socket.username = newname;
                }
            }
            // ************************** 这里结束本节更新 ************************** 
    
            // 告知所有用户
            socket.broadcast.emit('name_changed', {
                username: newname,
                msg: "["+oldname+"] 改名为 ["+socket.username + "]",
                type: "BROADCAST",
                numUsers: guest_num
            });
        }
      });
    

    此外,为了维护昵称数组,还需要在用户离开时,将离开的用户剔除出昵称数组。为了达到这个目的,我增加了一个函数来实现:

    // 移除数组元素
    var removeArr = function(arr, ele) {
        var new_arr = new Array();
        for(var i=0; i<arr.length; i++) {
            if(ele != arr[i]) {
                new_arr.push(arr[i])
            }
        }
        return new_arr;
    }
    

    用户离开聊天室:

    /* *************** 用户离开 *************** */
      socket.on('disconnect', function () {
    
          ...
    
          // 将离开的用户昵称移出数组  
          userlist = removeArr(userlist, socket.username)
    
          // 告知所有用户
          ...
    
        }
      });
    

    1.2 路由

    在上一节,我们直接就在index页面进行操作了。这一节,我把index界面改为了一个输入用户昵称的界面,然后跳转到一个新界面other。要在routes/index.js中增加一个路由:

    router.get('/other', function(req, res, next) {
      res.render('other', { title: 'Express' });
    });
    

    2. 客户端

    2.1 更改index.jade页面

    doctype html
    html
        head
            title= title
            link(rel='stylesheet', href='/stylesheets/style.css')
        body
            h1 欢迎使用socket.io聊天室
            form(method='get' action='/other')
                input(id='name' name='name' placeholder='输入您的名字')
                input(type='submit' value='进入聊天室')
    

    2.2 新增other.jade页面

    然后在views/index文件夹下创建一个other.jade文件:

    doctype html
    html
        head
            title= title
            link(rel='stylesheet', href='/stylesheets/style.css')
            link(rel='stylesheet', href='/stylesheets/bootstrap.css')
        body
            h1 socket.io聊天室
            p
                span#status
                span ,
                span#roomstatus
            p#notice.notice
            
            a(href='/')
                [退出] 聊天室
            
            hr
            
            div
                h3 聊天记录
            
            div.scrollbar#msg.msgbox
            
            hr
            div
                textarea(id='msgsend' name='msgsend' placeholder='输入消息' rows='4').form-control
            br
            div
                a.btn.btn-primary(onclick="OL_SendMsg()") 发送
            hr
            form.form-inline
                div.form-group
                    input.form-control(id='newnickname' placeholder='新昵称')
                    a.btn.btn-danger(onclick="OL_ModifyNickName()") 修改昵称
            
            hr
            h3 系统消息
            div#history
            
        script(src='/javascripts/jquery.min.js')
        script(src='https://cdn.socket.io/socket.io-1.4.5.js')
    

    这里我们引用了一个Bootstrap的css文件,请自行下载,并放入public/stylesheets文件夹中。

    另外,我们还需要对css文件进行一下替换:

    body {
      padding: 50px;
      font: 14px "Microsoft Yahei", Helvetica, Arial, sans-serif;
    }
    
    a {
      color: #00B7FF;
    }
    
    .msgbox {
      height:300px; 
      overflow-x:auto; 
      overflow-y:auto; 
      border:1px #ccc solid; 
      border-radius:5px; 
      background:#fff; 
      padding:14px 20px;
    }
    .notice {
      color:#EF0000; 
      font-weight:bold;
    }
    .time {
      float:right; 
      color:#999;
    }
    .mymsg {
      color:#2289DB;
      font-weight:bold;
    }
    
    /* 滚动条 */
    .scrollbar::-webkit-scrollbar-track
    {
      background-color: #e1e1e1;
    }
    .scrollbar::-webkit-scrollbar
    {
      width: 10px;
      background-color: #e1e1e1;
    }
    .scrollbar.shortscroll::-webkit-scrollbar
    {
      width: 8px;
      background-color: #e1e1e1;
    }
    .scrollbar::-webkit-scrollbar-thumb
    {
      background-color: #888;   
    }
    

    2.3 other.jade页面的js代码

    在other.jade页面中,加入一些js代码。

    首先,加入基本功能函数,用于此页面的一些基础功能

    script.
            // 基本功能函数
            function ol_pad(num, n)
            { 
                num = ""+num
                var temp = num;
                
                for(var i=0;i<(n-num.length);i++)
                {
                    temp = "0"+temp
                }   
                return temp
            }
            function GetRequest() { 
                var url = location.search; //获取url中"?"符后的字串 
                var theRequest = new Object(); 
                if (url.indexOf("?") != -1) { 
                    var str = url.substr(1); 
                    strs = str.split("&"); 
                    for(var i = 0; i < strs.length; i ++) { 
                        theRequest[strs[i].split("=")[0]]=unescape(strs[i].split("=")[1]); 
                    } 
                } 
                return theRequest; 
            } 
            function GetDateTime() {
                var obj = new Date();
                return (obj.getFullYear()+"/"+ol_pad(obj.getMonth()+1, 2)+"/"+ol_pad(obj.getDate(), 2)+" "+ol_pad(obj.getHours(),2)+":"+ol_pad(obj.getMinutes(),2)+":"+ol_pad(obj.getSeconds(),2));
            }
            
    

    发送聊天信息后,触发的一些响应,包括发送消息、在聊天框中显示、清空输入框等。

    script.
            // 发送聊天信息
            function OL_CleanInput() {
                var obj = document.getElementById('msgsend');
                obj.value = "";
            }
            function OL_ScrollChatWin() {
                var obj = document.getElementById('msg');
                obj.scrollTop = obj.scrollHeight;
            }
            function OL_SentAction() {
                OL_ScrollChatWin();
                OL_CleanInput();
            }
            function OL_CleanNotice() {
                document.getElementById("notice").innerHTML = "";
            }
            function OL_SendMsg() {
                var msg = document.getElementById("msgsend").value;
                if(""==msg) {
                    alert("消息不能为空!")
                    return -1;
                }
                
                send_msg(msg);
                
                document.getElementById("msg").innerHTML += "<p class='mymsg'>"+G_Name+": "+msg+"<span class='time'>"+GetDateTime()+"</span></p>";
                
                OL_SentAction();
            }
            
    

    修改昵称后的响应

    script.
            // 修改昵称
            function OL_ModifyNickName() {
                var newnickname = document.getElementById("newnickname").value;
                if(""==newnickname) {
                    alert("新昵称不能为空!")
                    return -1;
                }
                
                change_name(newnickname);
                
                document.getElementById("newnickname").value = "";
            }
    

    显示系统公告

    script.
            // 通知
            var NoticeTimer = null;
            function OL_ShowNotice(msg, second) {
                NoticeTimer = null;
                document.getElementById("notice").innerHTML = "[消息] "+msg;
                NoticeTimer = setTimeout("OL_CleanNotice()", second*1000)
                
                var history = document.getElementById("history");
                history.innerHTML = "<p>[消息] "+msg+"<span class='time'>"+GetDateTime()+"</span></p>" + history.innerHTML
            }
    

    这部分是根据上一节index.jade的socket.io客户端代码进行修改后的内容:

    script.
            ////////////////////////////////////////////////////////////////////
            //启动
            var socket = io.connect('http://127.0.0.1:3000');
            
            //发送消息
            var Request = new Object(); 
            Request = GetRequest();     
            var G_Name = Request["name"];
            if(null==G_Name) {
                G_Name = "访客"+Math.ceil(Math.random()*10000);
            }
            socket.emit('join', G_Name, function (data) {
                console.log(data);
            });
            
            //监听
            socket.on('login', function (data) {
                console.log(data);
                // 如果有重名的,要更改一个随机名称
                G_Name = data.username;
                document.getElementById("status").innerHTML = "欢迎您!"+G_Name;
                document.getElementById("roomstatus").innerHTML = "当前聊天有"+data.numUsers+"人";
            });
            
            socket.on('user_joined', function (data) {
                console.log(data);
                OL_ShowNotice(data.msg, 3);
                document.getElementById("roomstatus").innerHTML = "当前聊天有"+data.numUsers+"人";
            });
            
            socket.on('user_left', function (data) {
                console.log(data);
                OL_ShowNotice(data.msg, 3);
                document.getElementById("roomstatus").innerHTML = "当前聊天有"+data.numUsers+"人";
            });
            
            //修改昵称
            function change_name(name){
                socket.emit('change_name', name, function (data) {
                    console.log(data);
                });
            }
            // 监听修改昵称后返回的消息
            socket.on('name_changed', function (data) {
                console.log(data);
                document.getElementById("status").innerHTML = "欢迎您!"+G_Name;
                OL_ShowNotice(data.msg, 3);
            });
            // 监听修改昵称后返回给修改者的消息
            socket.on('name_changed_msg', function (data) {
                console.log(data);
                if("success"==data.res) {
                    document.getElementById("status").innerHTML = "欢迎您!"+data.newname;
                    OL_ShowNotice(data.msg, 3);
                }
                else {
                    OL_ShowNotice("修改昵称失败!"+data.error, 3);
                }
            });
            
            //发送消息
            function send_msg(msg){
                socket.emit('send_msg', msg, function (data) {
                    console.log(data);
                });
            }
            // 监听消息
            socket.on('msg_sent', function (data) {
                console.log(data);
                
                document.getElementById("msg").innerHTML += "<p>"+data.username+": "+data.msg+"<span class='time'>"+GetDateTime()+"</span></p>";
                OL_ScrollChatWin();
            });
    

    3.演示

    运行应用(supervisor bin/www 或 node bin/www)

    打开两个浏览器,进入127.0.0.1:3000


    输入不同的用户昵称后,进入聊天室:

    输入不同昵称

    先进入的用户在其他用于进入时,会收到系统公告:

    新用户加入的公告

    如果用户昵称与之前的重名,将会:

    昵称重名的情况

    用户可以更改昵称,如果成功,会收到提示;其他用户也会通过公告的形式收到提醒。

    更改昵称成功

    如果失败,用户会收到提示

    更改昵称失败

    用户聊天时,在输入框中输入消息,点击发送后,在聊天记录面板中会有对应的显示。

    Socket.io聊天室Demo

    当一个用户离开聊天室了,其他用户会收到消息:

    用户离开聊天室

    所有的系统公告会保留在底部:

    系统公告

    结语

    至此,一个相对饱满一些的聊天室就搭建好了。当然,即使“相对饱满”,依然是很简陋的聊天室。接下来如果要丰满这个聊天室、乃至集成到我们的其他应用中,还是有很多工作可以做的,比如:

    • 支持房间管理。用户可以创建房间,可以选择进入某一个房间
    • 用户管理。用户可以注册帐户、登录帐户,这个涉及到数据库
    • 聊天记录。保存聊天记录
    • 图片、文件发送。允许用户发送图片或其他文件
    • ...

    要做好一个聊天室并不容易,但如果我们把它分解成一个个独立的分支,再逐一实现它,就不会那么茫然和不知所措了。

    最后,欢迎fork或star我的项目:

    https://github.com/KKDestiny/chatroom.git


    原创文章,未经许可,请勿转载
    作者:Mike的读书季
    日期:2016.09.29

    相关文章

      网友评论

        本文标题:Node.js(Express4.x)搭建聊天室3——完善网页

        本文链接:https://www.haomeiwen.com/subject/xqsoettx.html