一次悲催的webscket工具类封装——export default 变量导出

88 阅读2分钟

    背景:周末有个紧急需求,需要实时监控车辆位置,使用百度地图规划路线,项目已有公共封装好的websocket工具类。每条线路有多辆公交车,每辆公交车均有终端实时向服务器推送位置。

    项目技术背景:vue 3 + vite 3.2 + element-plus

    于是,吭哧吭哧开始编码,应用代码:

<template>
  <div id="bMap" class="map-box"></div>
</template>

<script setup lang="ts">
import { onMounted, watch } from 'vue'
import { configMapboxCenterLngLat, bMapStyleJson } from '@/views/bigScreen/config/setting.js'
import { socket } from '@/utils/common/websocketCreate'
import { apiBSGetRouteLineStationList, apiBSGetRouteLineBuslist } from '../api/bigScreen'

const props = defineProps({
  // 公交车列表,父组件传入
  busList: {
    type: Array,
    default: () => {
      return []
    },
  },
  routeId: undefined,
})

const bMapObj = window.BMapGL
let bMap = null
let driving = null
const init = () => {
  bMap = new bMapObj.Map('bMap')
  bMap.centerAndZoom(
    new bMapObj.Point(configMapboxCenterLngLat[0], configMapboxCenterLngLat[1]),
    16
  ) // 初始化地图,设置中心点坐标和地图级别
  bMap.enableScrollWheelZoom(true) // 开启鼠标滚轮缩放
  bMap.setHeading(60)
  bMap.setTilt(60)
  bMap.setMapStyleV2({ styleJson: bMapStyleJson })
}

// 标注车辆位置
const addBusPos = (pos) => {
  const icon = new bMapObj.Icon(
    '/images/bigScreen/map-icon/marker-bus.png',
    new bMapObj.Size(31, 50)
  )
  const convertor = new bMapObj.Convertor()
  const pt = new bMapObj.Point(pos.longitude, pos.latitude)
  convertor.translate([pt], 1, 5, (data) => {
    const point = data.points[0]
    const marker = new bMapObj.Marker(point, { icon })
    const opts = { width: 100, height: 50, title: pos.plateNo }
    const infoWin = new bMapObj.InfoWindow('', opts)
    let choseMarker = null
    marker.id = pos.vin
    // 获取所有已经存在的标注,如果有ID相同的标注,则删除之后重新添加
    const markeres = bMap.getOverlays()
    for (let i = 0; i < markeres.length; i++) {
      if (markeres[i].id === pos.vin) {
        choseMarker = markeres[i]
      }
    }
    if (choseMarker) {
      bMap?.removeOverlay(choseMarker)
    }
    bMap?.addOverlay(marker)
    // 无车牌不展示信息框
    if (pos.plateNo) {
      marker.addEventListener('click', function () {
        bMap.openInfoWindow(infoWin, point) //开启信息窗口
      })
    }
  })
}

watch(
  () => props.busList,
  (busList) => {
    bMap.clearOverlays()
    busList.map((item) => {
      let socket = socketCreater()
      addBusPos(item)
      socket.init(`/gb32960/${item.vin}`, item.vin, (message) => {
        try {
          const data = JSON.parse(message.data)
          addBusPos({ ...data.data, plateNo: item.plateNo })
        } catch (e) {
          console.log('心跳检测')
        }
      })
    })
  }
)
</script>

websocket 封装代码

/*
 * @Descripttion: 封装socket方法
 */
const socket = {
  websocket: null,
  connectURL: `${import.meta.env.VITE_SOCEKT_URL}${import.meta.env.VITE_BASE_API}`,
  // 开启标识
  socket_open: false,
  // 心跳timer
  hearbeat_timer: null,
  // 心跳发送频率
  hearbeat_interval: 10000,
  // 是否自动重连
  is_reonnect: true,
  // 重连次数
  reconnect_count: 3,
  // 已发起重连次数
  reconnect_current: 1,
  // 网络错误提示此时
  ronnect_number: 0,
  // 重连timer
  reconnect_timer: null,
  // 重连频率
  reconnect_interval: 5000,
  // 设备id
  clientId: '',
  context: '',
  receiveMessage: null,
  init: (context = '', clientId = '', receiveMessage = null) => {
    if (!('WebSocket' in window)) {
      $message.warning('浏览器不支持WebSocket')
      return null
    }
    // 已经创建过连接不再重复创建
    // if (socket.websocket) {
    //   return socket.websocket
    // }
    socket.clientId = clientId
    socket.context = context
    socket.receiveMessage = receiveMessage
    socket.websocket = new WebSocket(socket.connectURL + context)
    socket.websocket.onmessage = (e) => {
      if (receiveMessage) {
        receiveMessage(e)
      }
    }

    socket.websocket.onclose = () => {
      clearInterval(socket.hearbeat_interval)
      socket.socket_open = false

      // 需要重新连接
      if (socket.is_reonnect) {
        socket.reconnect_timer = setTimeout(() => {
          // 超过重连次数
          if (socket.reconnect_current > socket.reconnect_count) {
            clearTimeout(socket.reconnect_timer)
            socket.is_reonnect = false
            return
          }

          // 记录重连次数
          socket.reconnect_current++
          socket.reconnect()
        }, socket.reconnect_interval)
      }
    }

    // 连接成功
    socket.websocket.onopen = function () {
      console.log('ws建立链接')
      socket.socket_open = true
      socket.is_reonnect = true
      socket.send(socket.clientId)
      // 开启心跳
      socket.heartbeat()
    }

    // 连接发生错误
    socket.websocket.onerror = function () {}
  },

  send: (data, callback = null) => {
    // 开启状态直接发送
    if (socket.websocket.readyState === socket.websocket.OPEN) {
      socket.websocket.send(socket.clientId)
      if (callback) {
        callback()
      }

      // 正在开启状态,则等待1s后重新调用
    } else {
      clearInterval(socket.hearbeat_timer)
      socket.ronnect_number++
    }
  },

  receive: (message) => {
    let params = JSON.parse(message.data).data
    params = JSON.parse(params)
    return params
  },

  heartbeat: () => {
    if (socket.hearbeat_timer) {
      clearInterval(socket.hearbeat_timer)
    }

    socket.hearbeat_timer = setInterval(() => {
      console.log('心跳ping~')
      socket.send(socket.clientId)
    }, socket.hearbeat_interval)
  },

  close: () => {
    clearInterval(socket.hearbeat_interval)
    socket.is_reonnect = false
    if (socket.websocket) {
      socket.websocket.close()
    }
  },

  /**
   * 重新连接
   */
  reconnect: () => {
    if (socket.websocket && !socket.is_reonnect) {
      socket.close()
    }

    socket.init(socket.context, socket.clientId, socket.receiveMessage)
  },
}

export default socket

问题来了,在使用代码中,只有最后一个websocket有效,百思不得其解。

busList.map((item) => {
  let socket = socketCreater()
  addBusPos(item)
  socket.init(`/gb32960/${item.vin}`, item.vin, (message) => { // 此处代码只有最后一个init的websocket能收取消息
    try {
      const data = JSON.parse(message.data)
      addBusPos({ ...data.data, plateNo: item.plateNo })
    } catch (e) {
      console.log('心跳检测')
    }
  })
})

    一直以为,是使用方面的问题,尝试了换成for循环,foreach,等其他循环方式来初始化websocket。然而还是没有什么卵用,照样还是只有最后一个websocket能正常收发消息,直到检查到websocket 公共封装才明白问题所在。直接使用export default socket,导出的变量是全局变量,且封装的是Object,在内存中是引用类型,每次执行,生成的websocket对象都会更新,实际上,每次执行init的方法,都实例化了一个websocket链接,但是由于是引用类型,每次都只是更新了引用,传入的匿名函数去处理onmessage事件,都只是处理了最后一次更新的websocket的实例。按照如下封装,将websocket实例用方法包起来,将websocket实例返回给外部引用对象,就解决了这个问题。

/*
 * @Descripttion: 封装socket方法--
 */
export default function socketCreater() {
  let socket = {
    websocket: null,
    connectURL: `${import.meta.env.VITE_SOCEKT_URL}${import.meta.env.VITE_BASE_API}`,
    // 开启标识
    socket_open: false,
    // 心跳timer
    hearbeat_timer: null,
    // 心跳发送频率
    hearbeat_interval: 10000,
    // 是否自动重连
    is_reonnect: true,
    // 重连次数
    reconnect_count: 3,
    // 已发起重连次数
    reconnect_current: 1,
    // 网络错误提示此时
    ronnect_number: 0,
    // 重连timer
    reconnect_timer: null,
    // 重连频率
    reconnect_interval: 5000,
    // 设备id
    clientId: '',
    context: '',
    receiveMessage: null,
    init: (context = '', clientId = '', receiveMessage = null) => {
      if (!('WebSocket' in window)) {
        $message.warning('浏览器不支持WebSocket')
        return null
      }
      // 已经创建过连接不再重复创建
      // if (socket.websocket) {
      //   return socket.websocket
      // }
      socket.clientId = clientId
      socket.context = context
      socket.receiveMessage = receiveMessage
      socket.websocket = new WebSocket(socket.connectURL + context)
      socket.websocket.onmessage = (e) => {
        if (receiveMessage) {
          receiveMessage(e)
        }
      }

      socket.websocket.onclose = () => {
        clearInterval(socket.hearbeat_interval)
        socket.socket_open = false

        // 需要重新连接
        if (socket.is_reonnect) {
          socket.reconnect_timer = setTimeout(() => {
            // 超过重连次数
            if (socket.reconnect_current > socket.reconnect_count) {
              clearTimeout(socket.reconnect_timer)
              socket.is_reonnect = false
              return
            }

            // 记录重连次数
            socket.reconnect_current++
            socket.reconnect()
          }, socket.reconnect_interval)
        }
      }

      // 连接成功
      socket.websocket.onopen = function () {
        console.log('ws建立链接')
        socket.socket_open = true
        socket.is_reonnect = true
        socket.send(socket.clientId)
        // 开启心跳
        socket.heartbeat()
      }

      // 连接发生错误
      socket.websocket.onerror = function () {}
    },

    send: (data, callback = null) => {
      // 开启状态直接发送
      if (socket.websocket.readyState === socket.websocket.OPEN) {
        socket.websocket.send(socket.clientId)
        if (callback) {
          callback()
        }

        // 正在开启状态,则等待1s后重新调用
      } else {
        clearInterval(socket.hearbeat_timer)
        // if (socket.ronnect_number < 1) {
        //   ElMessage({
        //     type: 'error',
        //     message: i18n.global.t('chat.unopen'),
        //     duration: 0,
        //   })
        // }
        socket.ronnect_number++
      }
    },

    receive: (message) => {
      let params = JSON.parse(message.data).data
      params = JSON.parse(params)
      return params
    },

    heartbeat: () => {
      if (socket.hearbeat_timer) {
        clearInterval(socket.hearbeat_timer)
      }

      socket.hearbeat_timer = setInterval(() => {
        console.log('心跳ping~')
        socket.send(socket.clientId)
      }, socket.hearbeat_interval)
    },

    close: () => {
      clearInterval(socket.hearbeat_interval)
      socket.is_reonnect = false
      if (socket.websocket) {
        socket.websocket.close()
      }
    },

    /**
     * 重新连接
     */
    reconnect: () => {
      if (socket.websocket && !socket.is_reonnect) {
        socket.close()
      }

      socket.init(socket.context, socket.clientId, socket.receiveMessage)
    },
  }
  return socket
}

    使用方法

watch(
  () => props.busList,
  (busList) => {
    bMap.clearOverlays()
    busList.map((item) => {
      let socket = socketCreater()
      addBusPos(item)
      socket.init(`/gb32960/${item.vin}`, item.vin, (message) => {
        try {
          const data = JSON.parse(message.data)
          addBusPos({ ...data.data, plateNo: item.plateNo })
        } catch (e) {
          console.log('心跳检测')
        }
      })
    })
  }
)