本封装方式只是真对目前自己所在公司当下业务需求,后期可能会修改,本文仅仅只作为学习记录。
使用方式
主要使用的方式有4种,建立连接 io()、关闭连接 WS.close()、发送信息使用 WS.send() / io().send() 、监听 WS.on() / io().on()
import { WSMessageTypeMap, WS, io } from '@/utils/webSocket'// ---------------------建立连接 -----------------------------io({...})// ---------------------关闭连接 -----------------------------WS.close() 或者 WS.close(()=>{}) // 支持回调// ---------------------发送 -----------------------------WS.send({type:WSMessageTypeMap['订单状态通知'], // type:必填data?:{}},function (){//回调方法})// ---------------------监听 -----------------------------WS.on(WSMessageTypeMap['订单状态通知'],function (){//回调方法})// 或着WS.on(WSMessageTypeMap['订单状态通知'],[function (){//回调方法}])
封装过程
WebSocketClient
用于创建 webSocket 实例,并建立连接。
HeartBeat
用于心跳检测,主动向后端发送消息,当一定时间内没有收到后端恢复,就会断开重连。
最终结果:
// 业务类型:0-心跳检测;1-订单状态通知;2-接驾路线通知;3-服务中订单查询。。。。后期会组件扩展增多export const WSMessageTypeMap = {HeartBeat: 0,订单状态通知: 1,接驾路线通知: 2,服务中订单查询: 3,司机到达时间: 4,}const replaceUrl = (url, opts) => {url += '/websocket?'for (const key in opts) {// HeartBeatTimeout 心跳时间不处理if (opts.hasOwnProperty(key) && key != 'HeartBeatTimeout') {url = url + `${key}=${opts[key]}&`}}// console.log('🚀 ~ file: webSocket.js ~ line 10 ~ replaceUrl ~ url', url)// TODO: 需要支持wss,ws明文传输在生产环境太危险了url = url.slice(0, -1).split('//').join('/').replace('https:/', 'wss://').replace('http:/', 'ws://')return url}/*** HeartBeat 心跳检测,防止websocket中途断开,断开后重连* @param {object} websocketClient websocketClient实例* @param {number} timeout 心跳时间,默认10s*/class HeartBeat {_timeout = 10000_timeoutObj = null_serverTimeoutObj = null_websocketClient = nullconstructor(websocketClient, timeout) {this._websocketClient = websocketClientthis._timeout = timeout || this._timeoutthis.reset()this.start()}reset() {clearTimeout(this._timeoutObj)clearTimeout(this._serverTimeoutObj)return this}start() {const self = thisthis._timeoutObj && clearTimeout(this._timeoutObj)this._serverTimeoutObj && clearTimeout(this._serverTimeoutObj)this._timeoutObj = setTimeout(function () {// 这里发送一个心跳,后端收到后,返回一个心跳消息,// onmessage拿到返回的心跳就说明连接正常self._websocketClient.send({ type: WSMessageTypeMap['HeartBeat'] })self._serverTimeoutObj = setTimeout(function () {// console.log('🚀 ~ file: webSocket.js ~ line 61 ~ HeartBeat ~ setTimeout', '主动关闭连接')// 如果超过一定时间还没重置,说明后端主动断开了self._websocketClient.close() // 如果onclose会执行reconnect,我们执行close()就行了.如果直接执行 reconnect 会触发onclose导致重连两次}, self._timeout)}, self._timeout)}}/*** @param {string} url 指定创建连接的地址,默认为 process.env.BASE_API* @param {object} opts 指定配置项*/export class WebSocketClient {_websocket_token_path // 路径_opts_readyState_isConnected // 是否连接openList = [] // 连接成功回调队列wsCallbackMap = new Map() // 回调MapclosewebsocketCallback = () => {} // websocket 关闭回调函数_lockReconnect = false // 避免重复连接_ttHeartBeat = null // 心跳检测对象constructor(opts, url) {if (WS) return WSthis._opts = optsthis.createWebSocket(url)}createWebSocket(url) {if (this._websocket) {return this._websocket}this._path =this._path || replaceUrl(url || process.env.BASE_API, this._opts)try {if ('WebSocket' in window) {this._websocket = new WebSocket(this._path)} else if ('MozWebSocket' in window) {// Note: 新版的FirFox已经没有这个构造函数了 99.0 (64 位)// eslint-disable-next-line no-undefthis._websocket = new MozWebSocket(this._path)}this.init()} catch (e) {this.reconnect()// eslint-disable-next-line no-consoleconsole.error('createWebSocket error', e)}}/*** 初始化*/init() {this.HeartBeat =this.HeartBeat || new HeartBeat(this, this._opts.HeartBeatTimeout)// websocket 连接成功this._websocket.onopen = () => {// console.log('🚀 ~ file: webSocket.js ~ line 104 ~ WebSocketClient ~ init ~ onopen',)this._isConnected = true// 执行连接成功的回调const f = this.openList.shift()typeof f === 'function' && f()}this._websocket.onmessage = (event) => {const data = JSON.parse(event.data)if (process.env.NODE_ENV === 'development' &&data.type !== WSMessageTypeMap['HeartBeat']) {// eslint-disable-next-line no-consoleconsole.log('🚀 ~ file: webSocket.js ~ line 111 ~ WebSocketClient ~ init ~ onmessage',data)}// 重启心跳检测this.HeartBeat.reset().start()// 执行所有回调const wsCallbackMap = this.wsCallbackMap.get(data.type)wsCallbackMap && wsCallbackMap.forEach((callback) => callback(data.data))}this._websocket.onclose = () => {// console.log('🚀 ~ file: webSocket.js ~ line 113 ~ WebSocketClient ~ init ~ onclose',)this._isConnected = falsethis.HeartBeat.reset()this.reconnect()}this._websocket.onerror = () => {// console.log('🚀 ~ file: webSocket.js ~ line 118 ~ WebSocketClient ~ init ~ onerror')this._isConnected = falsethis.reconnect()}// 其他异常情况处理// 监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。window.onbeforeunload = function () {this.close()}// 处理手机手机熄屏后会中断document.addEventListener('visibilitychange', () => {let hiddenTimeif (document.visibilityState == 'hidden') {// 记录页面隐藏时间hiddenTime = new Date().getTime()} else {const visibleTime = new Date().getTime()// 页面再次可见的时间-隐藏时间>10S,重连if ((visibleTime - hiddenTime) / 1000 > 10) {// 主动关闭连接this.close()// 1.5S后重连 因为断开需要时间,防止连接早已关闭了setTimeout(() => {this.reconnect()}, 1500)} else {//}}})}/*** 收集回调函数,当收到服务器消息时,会执行对应的回调函数* @param {*} callback 回调函数,可以是一个回调函数队列数组* @param {*} method 回调队列名称,对应前后的交互数据的 type*/on(method, callback) {const tm = this.wsCallbackMap.get(method)if (tm) {Array.isArray(callback) ? tm.push(...callback) : tm.push(callback)} else {const t = Array.isArray(callback) ? [...callback] : [callback]this.wsCallbackMap.set(method, t)}}/*** 删除回调函数* @param {*} method 回调队列名称,对应前后的交互数据的 type*/removeOn(method) {this.wsCallbackMap.delete(method)}/*** 主动向后端发送消息* @param {*} data 发送的数据 格式必须为: {type:1,data:{}},type为必须的字段,因为是根据type来收集回调函数* @param {Functon} callback 发送成功后后端返回数据执行回调函数* data : {* type: 0, // WSMessageTypeMap[key]* data: {} // 需要发送的数据* }*/send(data, callback = () => { }) {const cheapFunction = () => {data.data = { ...data.data }if (data.type !== WSMessageTypeMap['HeartBeat']) {this.removeOn(`send_${data.type}`) // 删除回调函数,必须先删除,否则会导致多次执行this.on(`send_${data.type}`, callback)}this._websocket.send(JSON.stringify(data))if (data.type !== WSMessageTypeMap['HeartBeat']) {// 重启心跳检测 方式发送不是心态检测内容时候,没有收到信息,导致心跳检测失效this.HeartBeat.reset()}}// 如果连接成功,直接发送if (this._isConnected) {cheapFunction()} else {// 添加在openList 延迟执行this.openList.push(() => {setTimeout(() => {cheapFunction()}, 1000)})}}/*** 关闭连接*/close(closewebsocketCallback) {this.closewebsocketCallback =closewebsocketCallback || this.closewebsocketCallbackthis._websocket.close()this._lockReconnect = truethis._isConnected = falsethis._path = undefinedthis._websocket = nullthis.closewebsocketCallback()}/*** 重连* @returns*/reconnect() {// console.log('🚀 ~ file: webSocket.js ~ line 185 ~ WebSocketClient ~ reconnect ~ reconnect')if (this._isConnected) returnthis._lockReconnect = truethis.tt && clearTimeout(this.tt)// 没连接上会一直重连,设置延迟避免请求过多this.tt = setTimeout(() => {// console.log('🚀 ~ file: webSocket.js ~ line 194 ~ WebSocketClient ~ this.tt=setTimeout ~ setTimeout',)this._lockReconnect = falsethis._websocket = nullthis.createWebSocket()}, 4000)}}let WS = null/*** 默认创建websocketClient 方法* @param {*} opts 配置对象*/const io = (opts) => {// 一定要判断是否已经创建过WebSocketClient,否则会导致重复创建if (WS) return WSconst userInfoEtravel = {...JSON.parse(sessionStorage.getItem('userInfoEtravel')),}opts = {token: userInfoEtravel.token,ID: userInfoEtravel.staffId,HeartBeatTimeout: 3000,...opts,}WS = new WebSocketClient(opts)return WS}/**** @param {Object} WS WS 为 WebSocketClient 实例,可以通过创建 io() 获取* @param {Function} io io 为 默认 创建 WebSocketClient 实例的方法,可自定义自己的创建方法**// ---------------------建立连接 -----------------------------io({...})// ---------------------关闭连接 -----------------------------WS.close() 或者 WS.close(()=>{}) // 支持回调// ---------------------发送 -----------------------------WS.send({type:WSMessageTypeMap['订单状态通知'], // type:必填data?:{}},function (){//回调方法})// ---------------------监听 -----------------------------WS.on(WSMessageTypeMap['订单状态通知'],function (){//回调方法})// 或着WS.on(WSMessageTypeMap['订单状态通知'],[function (){//回调方法}])**/export { WS, io }
