Vue 项目中使用WebSocket 消息推送

一、功能需求

1.这是我在后台管理项目中使用到的,主要的作用是搞一个消息提醒的功能。
2.主要有右上角的提示和有下角的消息弹框。
3.主要实现的功能是如果用户有未读的消息,那么首次登录就弹框,如果用户关闭了页面,那么再次刷新页面的时候,也不再弹框,意思就是一个账户没有退出之前,也没有实时消息推送的时候,只弹一次框。
4.如果用户点击了未读消息,那么就会将此条消息置位历史(已读)。
页面展示:
在这里插入图片描述

二、页面代码

备注:我的是后台管理系统(用的是vue-element-admin),第一次写websocket,所以我写在了src->layout->AppMain.vue文件下面:

<template>
  <section class="app-main">
    <Message-remind :message-list="messageList" />
    <transition name="fade-transform" mode="out-in">
      <keep-alive :include="cachedViews">
        <router-view :key="key" />
      </keep-alive>
    </transition>
  </section>
</template>

<script>
  import MessageRemind from '@/components/MessageRemind/index.vue'
  import { getToken, getSid } from "@/utils/auth"; // get token from cookie
  export default {
    name: 'AppMain',
    components: {
      MessageRemind
    },
    watch: {
      '$store.state.user': {
        handler: function (newValue, oldValue) {
          // 如果没有token,则表明退出了登录
          if (!newValue.token) {
            this.closeWebSocket();
          }
        },
        immediate: true,
        deep: true
      }
    },
    data() {
      return {
        // socket参数
        socket: null,
        timeout: 60 * 1000, // 45秒一次心跳
        timeoutObj: null, // 心跳心跳倒计时
        serverTimeoutObj: null, // 心跳倒计时
        timeoutnum: null, // 断开 重连倒计时
        lockReconnect: false, // 防止
        websocket: null,

        messageList: {}
      };
    },
    created() {
      const hasToken = getToken();
      const sid = getSid();
      if (hasToken) {
        this.initWebSocket(hasToken, sid)
      }
    },
    computed: {
      cachedViews() {
        return this.$store.state.tagsView.cachedViews
      },
      key() {
        return this.$route.path
      }
    },
    mounted() {
      // console.log(this.$store.state.tagsView.cachedViews)
    },
    methods: {
      initWebSocket(token, sid) {
        // WebSocket与普通的请求所用协议有所不同,ws等同于http,wss等同于https
        this.websocket = new WebSocket(process.env.VUE_APP_WEB_SOCKET_BASE_API + '?uiticket=' + token + '&sid=' + sid);
        this.websocket.onopen = this.websocketonopen;
        this.websocket.onerror = this.websocketonerror;
        this.websocket.onmessage = this.setOnmessageMessage;
        this.websocket.onclose = this.websocketclose;
        // 监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
        // window.onbeforeunload = that.onbeforeunload

      },

      start() {
        //清除延时器
        this.timeoutObj && clearTimeout(this.timeoutObj);
        this.serverTimeoutObj && clearTimeout(this.serverTimeoutObj);
        this.timeoutObj = setTimeout(() => {
          if (this.websocket && this.websocket.readyState == 1) {
            this.websocket.send('{"messageType": 99}');//发送消息,服务端返回信息,即表示连接良好,可以在socket的onmessage事件重置心跳机制函数
          } else {
            this.reconnect();
          }
          //定义一个延时器等待服务器响应,若超时,则关闭连接,重新请求server建立socket连接
          this.serverTimeoutObj = setTimeout(() => {
            this.websocket.close();
          }, this.timeout)
        }, this.timeout)
      },
      reset() { // 重置心跳
        // 清除时间
        clearTimeout(this.timeoutObj);
        clearTimeout(this.serverTimeoutObj);
        // 重启心跳
        this.start();
      },
      // 重新连接
      reconnect() {
        if (this.lockReconnect) return
        this.lockReconnect = true;
        //没连接上会一直重连,设置延迟避免请求过多
        this.timeoutnum && clearTimeout(this.timeoutnum);
        this.timeoutnum = setTimeout(() => {
          this.initWebSocket();
          this.lockReconnect = false;
        }, 5000)
      },
      async setOnmessageMessage(event) {
        this.messageList = JSON.parse(event.data)
        if (this.messageList.data.messageType === 999) {
          this.websocket.send('{"messageType": 99}');
        }
        this.$store.dispatch('user/steMessageMenu', this.messageList)
        this.reset();
        // 自定义全局监听事件
        window.dispatchEvent(new CustomEvent('onmessageWS', {
          detail: {
            data: event.data
          }
        }))
        //发现消息进入    开始处理前端触发逻辑
        // if (event.data === 'success' || event.data === 'heartBath') return
      },
      websocketonopen(e) {
        // console.log('onopen', {e});
        //开启心跳
        this.start();
        console.log("WebSocket连接成功!!!" + new Date() + "----" + this.websocket.readyState);
      },
      websocketonerror(e) {
        // console.log('websocketonerror', {e});
        console.log("WebSocket连接发生错误" + e);
      },
      websocketclose(e) {
        this.websocket.close();
        clearTimeout(this.timeoutObj);
        clearTimeout(this.serverTimeoutObj);
        console.log("WebSocket连接关闭");
      },
      websocketsend(messsage) {
        this.websocket.send(messsage)
      },
      closeWebSocket() { // 关闭websocket
        this.websocket.close()
      },
    },
  }
</script>

<style lang="scss" scoped>
  @import "~@/styles/global-height.scss";

  .app-main {
    /* 50= navbar  50  */
    // min-height: calc(100vh - #{$navbar+'px'});
    width: 100%;
    position: relative;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    flex: 1;
  }

  .fixed-header+.app-main {
    padding-top: #{$navbar+'px'};
  }

  .hasTagsView {
    .app-main {
      // min-height: calc(100vh - #{$appMain+'px'});
    }

    .fixed-header+.app-main {
      padding-top: 90px;
    }
  }

  .copy {
    text-align: center;
    height: 30px;
    line-height: 30px;
    font-size: 13px;
    color: #666;
    background: #fff;
    width: 100%;
    box-shadow: 0 0 10px #dfe4ed;
  }
</style>

<style lang="scss">
  // fix css style bug in open el-dialog
  .el-popup-parent--hidden {
    .fixed-header {
      padding-right: 15px;
    }
  }
</style>