Write the Code. Change the World.

9月 25

在某些场景下,socket 是必须要使用的。vue element admin 中,自己所做的后台中,就想使用这玩意。那么,就 websocket。

websocket 的客户端创建使用是很简单,有些问题却必须要面对。

  1. 怎么做断线重连。(socket.io 自身就实现了,可这里不用它)
  2. 怎么解决在整个项目中的数据来回。
  3. 在什么时候创建 websocket 连接。

断线重连是自己手动去建立一个心跳的方式来实现。socket.io 中叫 ping pong。这里就自定义的搞一搞。数据来回,使用全局事件的方式来处理。无论哪种语言,事件这种方式从来都不会缺失。在什么时候创建连接呢,因为这里针对的是登录后的一些功能,所以最好在登录成功后只调用创建一次。我们可以在 permission.js 中引入并创建。

操作一波

事件使用可以看 https://blog.vini123.com/481

先定义一个 socket.js

import store from '../store'
import eventBus from '../utils/eventBus.js'

/**
 * 这里只管 socket 自己的,不要去关心业务逻辑。
 * 只管接受服务端数据发送给外边和接受外边数据发送给服务端,至于是否连上了,外边无需关心,至于外边是否侦听了这里也无需关心
 */

const client = {
  connected: false,
  jumpTimer: null,
  heartJumping: true,
  jumpInterval: 5000,
  reconnectTimes: 0,
  wss: null,
  msgQueue: [],
  trace(value) {
    const { cmd, msg, data } = value
    const msgData = !data ? '' : (typeof data === 'object') ? JSON.stringify(data) : data
    console.log(cmd, msg, msgData)
  },
  disconnect() {
    if (this.wss) {
      this.wss.close()
      this.wss = null
    }
    this.connected = false
    if (this.jumpTimer) {
      clearInterval(this.jumpTimer)
    }
    this.heartJumping = true
    this.reconnectTimes = 0
    this.msgQueue = []
  },
  connect() {
    const token = store.getters.token
    if (!token) {
      this.trace({ cmd: 'connect', msg: '请先登录' })
      return
    }

    if (this.connected) {
      this.trace({ cmd: 'connect', msg: '已经连接' })
      return
    }

    const url = `ws://localhost:9000?token=${token}`
    this.trace({ cmd: 'connect', msg: `连接 ${url}` })
    this.wss = new WebSocket(url)
    this.wss.onopen = (res) => {
      this.trace({ cmd: 'onopen', msg: `onopen` })
      this.connected = true
      this.reconnectTimes = 0
      this.heartJumping = true

      // 看自己需要
      this.toSocket({ cmd: 'reply' })

      for (const item of this.msgQueue) {
        this.toSocket(item)
      }
      this.jumpHeart()

      eventBus.$off('sendMsg', this.sendMsg.bind(this))
      eventBus.$on('sendMsg', this.sendMsg.bind(this))
    }

    this.wss.onclose = (res) => {
      this.trace({ cmd: 'onclose', msg: `onclose`, data: res })
      this.connected = false
    }

    this.wss.onerror = (error) => {
      this.trace({ cmd: 'error', msg: `error`, data: error })
    }

    this.wss.onmessage = (res) => {
      this.trace({ cmd: 'onmessage', msg: `onmessage`, data: res.data })
      let data = res.data
      if (!data) {
        return
      }
      data = JSON.parse(data)
      if (data.cmd === 'heart') {
        this.heartJumping = true
        return
      }
      eventBus.$emit('message', data)
    }
  },
  reconnect(force = false) {
    this.trace({ cmd: 'reconnect', msg: `reconnect`, data: { force, reconnectTimes: this.reconnectTimes }})
    if (!force && this.reconnectTimes > 10) {
      return
    }
    this.disconnect()
    this.connect()
    this.reconnectTimes++
  },
  jumpHeart() {
    if (this.jumpTimer) {
      clearInterval(this.jumpTimer)
      this.jumpTimer = null
    }

    this.jumpTimer = setInterval(() => {
      if (!this.heartJumping) {
        this.connected = false
        this.reconnect()
        return
      }

      this.heartJumping = false

      this.toSocket({ cmd: 'heart' })
    }, this.jumpInterval)
  },
  toSocket(value) {
    let data = value
    if (typeof value === 'object') {
      data = JSON.stringify(value)
    }
    this.wss.send(data)
  },
  sendMsg(value) {
    if (!this.connected) {
      this.msgQueue.push(value)
      this.reconnect(true)
    } else {
      this.toSocket(value)
    }
  }
}
export default client

可以看到,这里引入了外部的 store(vuex 状态管理)以及 eventBus。 store 项目中本来就有,eventBus 是自己定义的,其实就是一个空的 Vue 实例。

eventBus.js

import Vue from 'vue'

export default new Vue()

permission.js 中引入,并创建 websocket。

      import socket from './api/socket'

      const hasGetUserInfo = store.getters.nickname
      if (hasGetUserInfo) {
        next()
      } else {
        try {
          // get user info
          await store.dispatch('user/getInfo')

          socket.connect()
          next()
        } catch (error) {
          // remove token and go to login page to re-login
          await store.dispatch('user/resetToken')
          Message.error(error || 'Has Error')
          next(`/login?redirect=${to.path}`)
          NProgress.done()
        }
      }
    }

好了,就到这里了。要去上班了。

发表评论

电子邮件地址不会被公开。 必填项已用*标注