Vue разумно настраивает WebSocket и реализует групповой чат⛄

Vue.js
Vue разумно настраивает WebSocket и реализует групповой чат⛄

Мотивация обучения исходит из интереса, да узнаешь ты новые знания, мотивация исходит из интереса а не другого 😉

предисловие

При написании проекта JQuery использовать веб-сокет очень просто. Вам не нужно учитывать модульность и доступ между компонентами. Достаточно документо-ориентированного программирования. При использовании его в проекте 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: добавьте связанные функции к мутациям и действиям.
// 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Монитор (в смонтированном жизненном цикле)
// 监听消息接收
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-структура

Определите, отправлено ли текущее сообщение другой стороной, по идентификатору пользователя каждого сообщения и идентификатору пользователя текущего пользователя, хранящемуся в vuex.

<!--消息显示-->
<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

больше использования

Последнее обновление: 1 февраля 2020 г.

Вручную подключите службу 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
  • Если в статье есть ошибки, исправьте их в комментариях, если статья вам поможет, ставьте лайк и подписывайтесь 😊
  • Эта статья была впервые опубликована в Наггетс, если вам нужно перепечатать, пожалуйста, оставьте сообщение в области комментариев 💌