百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 博客教程 > 正文

Vue合理配置WebSocket并实现群聊 vue+websocket

connygpt 2024-10-11 11:57 12 浏览



学习的动力源于兴趣,愿你在学习新知识时,动力源于兴趣而并非其它

前言

写JQuery项目时,使用websocket很简单,不用去考虑模块化,组件之间的访问问题,面向文档编程即可,在Vue项目中使用时,远远没有想象中的那么简单,需要考虑很多场景,本篇文章将与各位开发者分享下vue-native-websocket库的使用以及配置,用其实现群聊功能。先看下最终实现的效果



安装依赖

本文中对于vue-native-websocket库的讲解,项目中配置了vuex,对其不了解的开发者请移步官方文档,如果选择继续阅读本篇文章会比较吃力。

  • vue-native-websocket安装
# yarn | npm 安装
yarn add vue-native-websocket | npm install vue-native-websocket --save
复制代码
  • 安装成功


配置插件

  • main.js中进行导入
import VueNativeSock from 'vue-native-websocket'
复制代码
  • 使用VueNativeSock插件,并进行相关配置
// main.js
// base.lkWebSocket为你服务端websocket地址
Vue.use(VueNativeSock,base.lkWebSocket,{
  // 启用Vuex集成,store的值为你的vuex
  store: store,
  // 数据发送/接收使用使用json格式
  format: "json",
  // 开启自动重连
  reconnection: true,
  // 尝试重连的次数
  reconnectionAttempts: 5,
  // 重连间隔时间
  reconnectionDelay: 3000,
  // 将数据进行序列化,由于启用了json格式的数据传输这里需要进行重写
  passToStoreHandler: function (eventName, event) {
    if (!eventName.startsWith('SOCKET_')) { return }
    let method = 'commit';
    let target = eventName.toUpperCase();
    let msg = event;
    if (this.format === 'json' && event.data) {
      msg = JSON.parse(event.data);
      if (msg.mutation) {
        target = [msg.namespace || '', msg.mutation].filter((e) => !!e).join('/');
      } else if (msg.action) {
        method = 'dispatch';
        target = [msg.namespace || '', msg.action].filter((e) => !!e).join('/');
      }
    }
    this.store[method](target, msg);
    this.store.state.socket.message = msg;
  }
});
复制代码
  • vuex的相关配置:mutations和actions添加相关函数
// vuex配置文件
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    token:"",
    userID:"",
    // 用户头像
    profilePicture: "",
    socket: {
      // 连接状态
      isConnected: false,
      // 消息内容
      message: '',
      // 重新连接错误
      reconnectError: false
    }
  },
  mutations: {
    SOCKET_ONOPEN (state, event)  {
      // 连接打开触发的函数
      Vue.prototype.$socket = event.currentTarget;
      state.socket.isConnected = true
    },
    SOCKET_ONCLOSE (state, event)  {
      // 连接关闭触发的函数
      state.socket.isConnected = false;
      console.log(event);
    },
    SOCKET_ONERROR (state, event)  {
      // 连接发生错误触发的函数
      console.error(state, event)
    },
    SOCKET_ONMESSAGE (state, message)  {
      // 收到消息时触发的函数
      state.socket.message = message
    },
    SOCKET_RECONNECT(state, count) {
      // 重新连接触发的函数
      console.info(state, count)
    },
    SOCKET_RECONNECT_ERROR(state) {
      // 重新连接失败触发的函数
      state.socket.reconnectError = true;
    },
  },
  actions: {
    customerAdded (context) {
      // 新连接添加函数
      console.log('action received: customerAdded');
      console.log(context)
    }
  },
  modules: {
  }
})

复制代码

至此vue-native-websocket配置结束,如需了解更多配置方法,请移步npm仓库

使用插件并实现群聊

  • 在消息发送接收组件中添加onmessage监听(mounted生命周期中)
// 监听消息接收
this.$options.sockets.onmessage = (res)=>{
    // res.data为服务端返回的数据
    const data = JSON.parse(res.data);
    // 200为服务端连接建立成功时返回的状态码(此处根据真实后端返回值进行相应的修改)
    if(data.code===200){
        // 连接建立成功
        console.log(data.msg);
    }else{
        // 获取服务端推送的消息
        const msgObj = {
            msg: data.msg,
            avatarSrc: data.avatarSrc,
            userID: data.userID
        };
        // 渲染页面:如果msgArray存在则转json
        if(lodash.isEmpty(localStorage.getItem("msgArray"))){
            this.renderPage([],msgObj,0);
        }else{
            this.renderPage(JSON.parse(localStorage.getItem("msgArray")),msgObj,0);
        }
    }
};
复制代码
  • 实现消息发送
// 消息发送函数
sendMessage: function (event) {
    if (event.keyCode === 13) {
        // 阻止编辑框默认生成div事件
        event.preventDefault();
        let msgText = "";
        // 获取输入框下的所有子元素
        let allNodes = event.target.childNodes;
        for(let item of allNodes){
            // 判断当前元素是否为img元素
            if(item.nodeName==="IMG"){
                msgText += `/${item.alt}/`;
            }
            else{
                // 获取text节点的值
                if(item.nodeValue!==null){
                    msgText += item.nodeValue;
                }
            }
        }
        // 消息发送: 消息内容、状态码、当前登录用户的头像地址、用户id
        this.$socket.sendObj({msg: msgText,code: 0,avatarSrc: this.$store.state.profilePicture,userID: this.$store.state.userID});
        // 清空输入框中的内容
        event.target.innerHTML = "";
    }
}
复制代码
  • 实现页面渲染
// 渲染页面函数
renderPage: function(msgArray,msgObj,status){
    if(status===1){
        // 页面第一次加载,如果本地存储中有数据则渲染至页面
        let msgArray = [];
        if(localStorage.getItem("msgArray")!==null){
            msgArray = JSON.parse(localStorage.getItem("msgArray"));
            for (let i = 0; i<msgArray.length;i++){
                const thisSenderMessageObj = {
                    "msgText": msgArray[i].msg,
                    "msgId": i,
                    "avatarSrc": msgArray[i].avatarSrc,
                    "userID": msgArray[i].userID
                };
                // 解析并渲染
                this.messageParsing(thisSenderMessageObj);
            }
        }
    }else{
        // 判断本地存储中是否有数据
        if(localStorage.getItem("msgArray")===null){
            // 新增记录
            msgArray.push(msgObj);
            localStorage.setItem("msgArray",JSON.stringify(msgArray));
            for (let i = 0; i <msgArray.length; i++){
                const thisSenderMessageObj = {
                    "msgText": msgArray[i].msg,
                    "msgId": i,
                    "avatarSrc": msgArray[i].avatarSrc,
                    "userID": msgArray[i].userID,
                };
                // 解析并渲染
                this.messageParsing(thisSenderMessageObj);
            }
        }else{
            // 更新记录
            msgArray = JSON.parse(localStorage.getItem("msgArray"));
            msgArray.push(msgObj);
            localStorage.setItem("msgArray",JSON.stringify(msgArray));
            const thisSenderMessageObj = {
                "msgText": msgObj.msg,
                "msgId": Date.now(),
                "avatarSrc": msgObj.avatarSrc,
                "userID": msgObj.userID
            };
            // 解析并渲染
            this.messageParsing(thisSenderMessageObj);
        }
    }
}
复制代码
  • 实现消息解析
// 消息解析
messageParsing: function(msgObj){
    // 解析接口返回的数据进行渲染
    let separateReg = /(\/[^/]+\/)/g;
    let msgText = msgObj.msgText;
    let finalMsgText = "";
    // 将符合条件的字符串放到数组里
    const resultArray = msgText.match(separateReg);
    if(resultArray!==null){
        for (let item of resultArray){
            // 删除字符串中的/符号
            item = item.replace(/\//g,"");
            for (let emojiItem of this.emojiList){
                // 判断捕获到的字符串与配置文件中的字符串是否相同
                if(emojiItem.info === item){
                    const imgSrc = require(`../assets/img/emoji/${emojiItem.hover}`);
                    const imgTag = `<img src="${imgSrc}" width="28" height="28" alt="${item}">`;
                    // 替换匹配的字符串为img标签:全局替换
                    msgText = msgText.replace(new RegExp(`/${item}/`,'g'),imgTag);
                }
            }
        }
        finalMsgText = msgText;
    }else{
        finalMsgText = msgText;
    }
    msgObj.msgText = finalMsgText;
    // 渲染页面
    this.senderMessageList.push(msgObj);
    // 修改滚动条位置
    this.$nextTick(function () {
        this.$refs.messagesContainer.scrollTop = this.$refs.messagesContainer.scrollHeight;
    });
}
复制代码
  • DOM结构

通过每条消息的userID和vuex中的存储的当前用户的userID来判断当前消息是否为对方发送

<!--消息显示-->
<div class="messages-panel" ref="messagesContainer">
    <div class="row-panel" v-for="item in senderMessageList" :key="item.msgId">
        <!--发送者消息样式-->
        <div class="sender-panel" v-if="item.userID===userID">
            <!--消息-->
            <div class="msg-body">
                <!--消息尾巴-->
                <div class="tail-panel">
                    <svg class="icon" aria-hidden="true">
                        <use xlink:href="#icon-zbds30duihuakuangyou"></use>
                    </svg>
                </div>
                <!--消息内容-->
                <p v-html="item.msgText"/>
            </div>
            <!--头像-->
            <div class="avatar-panel">
                <img :src="item.avatarSrc" alt="">
            </div>
        </div>
        <!--对方消息样式-->
        <div class="otherSide-panel" v-else>
            <!--头像-->
            <div class="avatar-panel">
                <img :src="item.avatarSrc" alt="">
            </div>
            <!--消息-->
            <div class="msg-body">
                <!--消息尾巴-->
                <div class="tail-panel">
                    <svg class="icon" aria-hidden="true">
                        <use xlink:href="#icon-zbds30duihuakuangzuo"></use>
                    </svg>
                </div>
                <!--消息内容-->
                <p v-html="item.msgText"/>
            </div>
        </div>
    </div>
</div>
复制代码

群聊实现思路解析

  • 消息组件挂载完成后:从本地存储中读取消息记录,如果存在则将消息渲染至页面
  • 监听消息接收:服务端推送消息后触发onmessage事件
  • 获取到服务端推送的消息后:从本地存储中读取消息记录
  • 如果本地存储中存在消息记录:更新本地存储中对消息记录,将当前消息对象放进消息记录中,并渲染页面
  • 如果本地存储中不存在消息记录:在本地存储中创建消息记录字段,将当前消息对象放进消息记录中,并渲染页面
  • 触发消息发送:使用this.$socket.sendObj方法,传当前用户的相关信息,推送至服务端websocket服务
  • 服务端收到消息后:将当前用户发送的消息进行处理,并发送给与服务器取得连接的客户端。
  • 客户端收到消息后:触发onmessage事件

更多用法

最后更新时间: 2020年2月1日

手动连接websockt服务

  • 开启手动连接,实现在需要的页面手动连接websocket
    // main.js 在插件配置里添加connectManually属性
    // 开启手动调用 connect() 连接服务器
    connectManually: true
复制代码
  • 在需要的地方进行手动连接
    // 使用this.$connect(URL)方法
    this.$connect(`${base.lkWebSocket}/${localStorage.getItem("userID")}`);
复制代码
  • 页面销毁时关闭连接
    // beforeDestroy生命周期中调用$disconnect方法
    beforeDestroy() {
      // 页面销毁时,断开连接
      console.log("页面销毁,断开websocket连接");
      this.$disconnect();
    },
复制代码

消息发送注意事项

  • this.$socket.sendObj()函数
  // 开启json传输时使用sendObj进行消息发送
  this.$socket.sendObj({
  });
  
  // 为开启json传输时,使用send()函数进行发送
  this.$socket.send(""); 
复制代码

设置心跳消息

  • 在vuex的配置文件中添加定时器,向服务端推送消息
// src/store/index.js
state{
    socket{
        // 心跳消息发送时间
        heartBeatInterval: 30000,
        // 心跳定时器
        heartBeatTimer: 0
    }
}
mutations:{
    // 连接打开
    SOCKET_ONOPEN (state, event)  {
      Vue.prototype.$socket = event.currentTarget;
      state.socket.isConnected = true;
      // 连接成功时启动定时发送心跳消息,避免被服务器断开连接
      state.socket.heartBeatTimer = setInterval(() => {
        const message = "心跳消息";
        state.socket.isConnected && Vue.prototype.$socket.sendObj({"code":200,"msg":message});
      }, state.socket.heartBeatInterval);
    },
    // 连接关闭
    SOCKET_ONCLOSE (state, event)  {
      state.socket.isConnected = false;
      // 连接关闭时停掉心跳消息
      clearInterval(state.socket.heartBeatTimer);
      state.socket.heartBeatTimer = 0;
      console.log('连接已断开: ' + new Date());
      console.log(event);
    },
}
复制代码

写在最后

  • 项目地址: https://github.com/likaia/chat-system
  • 文中如有错误,欢迎在评论区指正,如果这篇文章帮到了你,欢迎点赞和关注

相关推荐

3分钟让你的项目支持AI问答模块,完全开源!

hello,大家好,我是徐小夕。之前和大家分享了很多可视化,零代码和前端工程化的最佳实践,今天继续分享一下最近开源的Next-Admin的最新更新。最近对这个项目做了一些优化,并集成了大家比较关注...

干货|程序员的副业挂,12个平台分享

1、D2adminD2Admin是一个完全开源免费的企业中后台产品前端集成方案,使用最新的前端技术栈,小于60kb的本地首屏js加载,已经做好大部分项目前期准备工作,并且带有大量示例代码,助...

Github标星超200K,这10个可视化面板你知道几个

在Github上有很多开源免费的后台控制面板可以选择,但是哪些才是最好、最受欢迎的可视化控制面板呢?今天就和大家推荐Github上10个好看又流行的可视化面板:1.AdminLTEAdminLTE是...

开箱即用的炫酷中后台前端开源框架第二篇

#头条创作挑战赛#1、SoybeanAdmin(1)介绍:SoybeanAdmin是一个基于Vue3、Vite3、TypeScript、NaiveUI、Pinia和UnoCSS的清新优...

搭建React+AntDeign的开发环境和框架

搭建React+AntDeign的开发环境和框架随着前端技术的不断发展,React和AntDesign已经成为越来越多Web应用程序的首选开发框架。React是一个用于构建用户界面的JavaScrip...

基于.NET 5实现的开源通用权限管理平台

??大家好,我是为广大程序员兄弟操碎了心的小编,每天推荐一个小工具/源码,装满你的收藏夹,每天分享一个小技巧,让你轻松节省开发效率,实现不加班不熬夜不掉头发,是我的目标!??今天小编推荐一款基于.NE...

StreamPark - 大数据流计算引擎

使用Docker完成StreamPark的部署??1.基于h2和docker-compose进行StreamPark部署wgethttps://raw.githubusercontent.com/a...

教你使用UmiJS框架开发React

1、什么是Umi.js?umi,中文可发音为乌米,是一个可插拔的企业级react应用框架。你可以将它简单地理解为一个专注性能的类next.js前端框架,并通过约定、自动生成和解析代码等方式来辅助...

简单在线流程图工具在用例设计中的运用

敏捷模式下,测试团队的用例逐渐简化以适应快速的发版节奏,大家很早就开始运用思维导图工具比如xmind来编写测试方法、测试点。如今不少已经不少利用开源的思维导图组件(如百度脑图...)来构建测试测试...

【开源分享】神奇的大数据实时平台框架,让Flink&amp;Spark开发更简单

这是一个神奇的框架,让Flink|Spark开发更简单,一站式大数据实时平台!他就是StreamX!什么是StreamX大数据技术如今发展的如火如荼,已经呈现百花齐放欣欣向荣的景象,实时处理流域...

聊聊规则引擎的调研及实现全过程

摘要本期主要以规则引擎业务实现为例,陈述在陌生业务前如何进行业务深入、调研、技术选型、设计及实现全过程分析,如果你对规则引擎不感冒、也可以从中了解一些抽象实现过程。诉求从硬件采集到的数据提供的形式多种...

【开源推荐】Diboot 2.0.5 发布,自动化开发助理

一、前言Diboot2.0.5版本已于近日发布,在此次发布中,我们新增了file-starter组件,完善了iam-starter组件,对core核心进行了相关优化,让devtools也支持对IAM...

微软推出Copilot Actions,使用人工智能自动执行重复性任务

IT之家11月19日消息,微软在今天举办的Ignite大会上宣布了一系列新功能,旨在进一步提升Microsoft365Copilot的智能化水平。其中最引人注目的是Copilot...

Electron 使用Selenium和WebDriver

本节我们来学习如何在Electron下使用Selenium和WebDriver。SeleniumSelenium是ThoughtWorks提供的一个强大的基于浏览器的开源自动化测试工具...

Quick &#39;n Easy Web Builder 11.1.0设计和构建功能齐全的网页的工具

一个实用而有效的应用程序,能够让您轻松构建、创建和设计个人的HTML网站。Quick'nEasyWebBuilder是一款全面且轻巧的软件,为用户提供了一种简单的方式来创建、编辑...