Vue合理配置WebSocket并实现群聊

本文涉及的产品
云解析DNS,个人版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: Vue合理配置WebSocket并实现群聊

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


前言


写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


  • 安装成功


640.png


配置插件


  • 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);
  },
}


写在最后


  • 项目地址: chat-system
  • 文中如有错误,点击下方阅读原文,在评论区留言,如果这篇文章帮到了你,欢迎点赞和关注😊
相关文章
|
1月前
|
缓存 Kubernetes Docker
容器服务ACK常见问题之容器服务ACK ingress websocket配置失败如何解决
容器服务ACK(阿里云容器服务 Kubernetes 版)是阿里云提供的一种托管式Kubernetes服务,帮助用户轻松使用Kubernetes进行应用部署、管理和扩展。本汇总收集了容器服务ACK使用中的常见问题及答案,包括集群管理、应用部署、服务访问、网络配置、存储使用、安全保障等方面,旨在帮助用户快速解决使用过程中遇到的难题,提升容器管理和运维效率。
|
1月前
vue3-admin-element-template配置正向代理报错
vue3-admin-element-template配置正向代理报错
45 0
|
1月前
|
移动开发 JavaScript 安全
Vue 应用程序性能优化:代码压缩、加密和混淆配置详解
Vue 应用程序性能优化:代码压缩、加密和混淆配置详解
72 0
|
1月前
vue3配置路由报错Catch all routes (“*“) must now be defined using a param with a custom regexp.
vue3配置路由报错Catch all routes (“*“) must now be defined using a param with a custom regexp.
81 0
|
1月前
|
资源调度 JavaScript 前端开发
Vue的路由管理:VueRouter的配置和使用
【4月更文挑战第24天】VueRouter是Vue.js的官方路由管理器,用于在单页面应用中管理URL路径与组件的映射。通过安装并引入VueRouter,设置路由规则和创建router实例,可以实现不同路径下显示不同组件。主要组件包括:`&lt;router-link&gt;`用于创建导航链接,`&lt;router-view&gt;`负责渲染当前路由对应的组件。此外,VueRouter还支持编程式导航和各种高级特性,如嵌套路由、路由参数和守卫,以应对复杂路由场景。
|
22天前
|
JavaScript 前端开发 开发者
vue3+ts配置跨域报错问题解决:> newpro2@0.1.0 serve > vue-cli-service serve ERROR Invalid options in vue.
【6月更文挑战第3天】在 Vue CLI 项目中遇到 &quot;ERROR Invalid options in vue.config.js: ‘server’ is not allowed&quot; 错误是因为尝试在 `vue.config.js` 中使用不被支持的 `server` 选项。正确配置开发服务器(如代理)应使用 `devServer` 对象,例如设置代理到 `http://xxx.com/`: ```javascript module.exports = { devServer: {
41 1
|
22天前
|
JavaScript
|
19小时前
|
JavaScript
|
1天前
|
JavaScript 前端开发
|
1月前
|
资源调度 JavaScript 前端开发
路由管理:Vue Router的使用和配置技巧
【4月更文挑战第22天】Vue.js的官方路由管理器Vue Router简化了单页面应用的路由管理。通过定义路由组件和映射URL,它实现了页面导航和组件加载。安装Vue Router后,在`src/router/index.js`配置路由,如`{ path: &#39;/&#39;, component: Home }`。使用`&lt;router-view&gt;`渲染组件,`&lt;router-link&gt;`进行导航。动态路由匹配允许同一个组件对应不同URL。嵌套路由和编程式导航进一步增强路由功能。路由守卫可在路由切换时执行逻辑,而路由懒加载能按需加载组件,提升性能。