在某些场景下,socket 是必须要使用的。vue element admin 中,自己所做的后台中,就想使用这玩意。那么,就 websocket。
websocket 的客户端创建使用是很简单,有些问题却必须要面对。
- 怎么做断线重连。(socket.io 自身就实现了,可这里不用它)
- 怎么解决在整个项目中的数据来回。
- 在什么时候创建 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()
}
}
}
好了,就到这里了。要去上班了。