Bootstrap

【基于vue+express+websocket实现简单的聊天室】

一、技术简介

Vue.js

这是一个流行的 JavaScript 框架,用于构建用户界面和单页应用程序。它的响应式数据绑定和组件化特性使得构建前端界面更加便捷。

Express框架

Express 是一个基于 Node.js 的 Web 应用程序框架,用于构建后端服务器。它提供了路由、中间件和简化了 HTTP 请求处理的工具,使得构建服务器端应用更加简单和灵活。

WebSocket

WebSocket 是一种在单个 TCP 连接上提供全双工通信的协议。它允许客户端和服务器之间实现持久的、低延迟的连接,适用于实时性要求高的应用,比如聊天应用、实时协作工具等。

二、代码仓库

链接: 仓库地址

三、前端设计及实现

1、登录界面

使用elmentUI提供的表单组件,这里只需要输入用户名即可。

<template>
    <div class="body">
        <el-form :rules="rules" ref="loginForm" :model="loginForm" class="loginContainer">
            <h3 class="loginTitle">
            欢迎登录
            </h3>
            <el-form-item prop="username">
                <el-input type="text" v-model="loginForm.username" placeholder="亲,请输入用户名" >
                </el-input>
            </el-form-item>
            
            <el-button type="primary" style="width:100%" @click="submitLogin">登录</el-button>
        </el-form>
    </div>
</template>

添加表单验证的规则,首先不能为空其次长度最多1-10个字符,并将用户名存储在sessionStorage,登录成功会跳转到聊天室主页,登录成功或失败都会有对应的消息提示。

export default {
  name: "Login",
    data(){
      return{
          captchaUrl: "",
          loginForm:{
              username:"",
          },
          checked: true,
          rules:{
              username:[{required:true,message:"请输入用户名",trigger:"blur"},{ min: 1, max: 10, message: '长度在 1 到 10 个字符', trigger: 'blur' }
              ],
          }

      }
  },
    methods:{
      submitLogin(){
          var _this = this;
          this.$refs.loginForm.validate((valid) => {
            if (valid) {
                sessionStorage.setItem('username', this.loginForm.username);
                _this.$message({
                    message: '登录成功',
                    type: 'success'
                })
                _this.$router.push('/ChatHome');
            } 
            else {
                _this.$message.error('登录出错请重新输入');
                return false;
            }
        });
      }
    }
};

2、聊天室界面

展示了主要的聊天界面,包括用户昵称、头像、时间戳等,其中包括了一些自定义的组件。

<template>
  <div class="chat-window">
    <div class="top">
      <div class="head-pic">
        <HeadPortrait :imgUrl="frinedInfo.headImg"></HeadPortrait>
      </div>
      <div class="info-detail">
        <div class="name">{{ frinedInfo.name }}</div>
        <div class="detail">{{ frinedInfo.detail }}</div>
      </div>
      
    </div>
    <div class="botoom">
      <div class="chat-content" ref="chatContent">
        <div class="chat-wrapper" v-for="item in chatList" :key="item.id">
          <div class="chat-friend" v-if="item.uid !== uid">
            <div class="chat-text" >
              {{ item.msg }}
            </div>
            <div class="info-time">
              <img :src="item.headImg" alt="" />
              <span>{{ item.name }}</span>
              <span>{{ item.time }}</span>
            </div>
          </div>
          <div class="chat-me" v-else>
            <div class="chat-text" >
              {{ item.msg }}
            </div>
            <div class="info-time">
              <span>{{ item.name }}</span>
              <span>{{ item.time }}</span>
              <img :src="item.headImg" alt="" />
            </div>
          </div>
        </div>
      </div>
      <div class="chatInputs">
        <input class="inputs" v-model="inputMsg" @keyup.enter="sendText" />
        <div class="send boxinput" @click="sendText">
          <img src="@/assets/img/emoji/rocket.png" alt="" />
        </div>
      </div>
    </div>
  </div>
</template>

mounted是vue中的一个钩子函数,在初始化页面完成后对尝试对WebSocket进行连接。在连接成功后存储一下服务器生成的userId以便后续区分不同的用户。

mounted() {
    this.init();
  },
methods: {
    init: function () {
        if(typeof(WebSocket) === "undefined"){
            alert("您的浏览器不支持socket")
        }else{
            // 实例化socket
            this.socket = new WebSocket(this.path)
            // 监听socket连接
            this.socket.onopen = this.open
            // 监听socket错误信息
            this.socket.onerror = this.error
            // 监听socket消息
            this.socket.onmessage = this.getMessage
        }
    },
    open: function () {
        console.log("socket连接成功")
    },
    error: function () {
        console.log("连接错误")
    },
    getMessage: function (msg) {
        console.log(JSON.parse(msg.data));
        let params = JSON.parse(msg.data);
        //如果为第一次收到消息,存取用户id
        if(params.type === 0) {
          sessionStorage.setItem('userId', params.userId);
          this.uid = params.userId;
        }
        else {
          this.chatList.push(params);
          this.scrollBottom();
        }
    },
    // 发送消息给被连接的服务端
    send: function (params) {
        this.socket.send(params)
    },
    close: function () {
        console.log("socket已经关闭")
    },

在路由方面添加一个全局导航守卫,用户需要先登录才能聊天。

// 添加全局导航守卫
  router.beforeEach((to, from, next) => {
    let username = sessionStorage.getItem('username')
    let _this = this;
    if(to.path === '/Login') {
        next();
    }else if(username === null) {
        alert('请先登录');
        next('/Login');
    }
    else {
        next();
    }
  });

四、后端设计及实现

后端使用了express-ws处理WebSocket请求,需要注意的是发送JSON数据需要先通过JSON.stringify()方法转为字符串,前端处理时再通过JSON.parse()方法转回JSON。这里还定义了一个函数,用于生成用户的Id,为了方便前端区分用户发送的消息和接受的消息。

const express = require('express');
const expressWs = require('express-ws');

const app = express();
expressWs(app);

// 存储连接的客户端
const clients = new Set();

// 处理 WebSocket 请求
app.ws('/chat', (ws, req) => {
    // 将新连接的客户端添加到集合中
    clients.add(ws);
    const clientId = generateClientId();
    // 发送欢迎消息给连接的客户端
    const params = {
        type: 0,
        wel: '欢迎来到聊天室',
        userId: clientId,
    }
    ws.send(JSON.stringify(params));
    // console.log(clientId);
    // 监听消息事件
    ws.on('message', (message) => {
    // 广播消息给所有客户端
    clients.forEach((client) => {
      if (ws !== client && client.readyState === client.OPEN) {
        client.send(message);
      }
    });
  });
    // 监听连接关闭事件
    ws.on('close', () => {
        // 从集合中移除断开连接的客户端
        clients.delete(ws);
    });
});

function generateClientId() {
    return Math.random().toString(36).substr(2, 9);
  }
// 启动服务器
app.listen(3000, () => {
  console.log('Server is running');
});

五、界面展示

登录界面

登录界面展示

聊天室界面

张三聊天界面
李四聊天界面
王五聊天界面

六、实验总结

在本次项目的完成中,切实地感受到了前后端分离的开发模式,学到了许多之前没接触过的知识,例如:WebSocket的双向通信和应用、前后端交互时产生的跨域问题和如何配置代理、以及Session的会话控制等。同时也让我对vue、node.js这些技术有了更好的理解。十分感谢孟宁老师的指导,孟宁老师深入浅出的教学也我让对网络程序设计产生了浓厚的兴趣。

;