socket.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. // src/utils/socket.js
  2. import { ElMessage } from 'element-plus'
  3. import { ROOTPATH2 } from '../comment/httpUrl.js'
  4. let WSS = null // 全局唯一 WebSocket 实例
  5. let heartTimer = null // 心跳定时器
  6. let reconnectTimer = null // 重连定时器
  7. let reconnectCount = 0 // 当前重连次数
  8. const MAX_RECONNECT = 10 // 最大重连次数
  9. const RECONNECT_INTERVAL = 3000 // 重连间隔 3 秒
  10. let manuallyClosed = false
  11. /**
  12. * 连接 WebSocket
  13. * @param {string} userId 当前用户 ID
  14. * @param {object} store Vuex store 实例
  15. * @param {object} router Vue Router 实例
  16. */
  17. export function connectSocket(userId, store, router) {
  18. if (!userId) return
  19. manuallyClosed = false
  20. // 若已有连接则关闭旧连接
  21. if (WSS) {
  22. try { WSS.close() } catch { }
  23. }
  24. // 生成 tabId
  25. let tabId = sessionStorage.getItem('tabId')
  26. if (!tabId) {
  27. tabId = Date.now() + '_' + Math.random().toString(36).slice(2)
  28. sessionStorage.setItem('tabId', tabId)
  29. }
  30. const wsUrl = `${ROOTPATH2}${userId}?tabId=${tabId}`
  31. WSS = new WebSocket(wsUrl)
  32. console.log('WebSocket 连接中:', wsUrl)
  33. /** 连接成功 */
  34. WSS.onopen = () => {
  35. console.log('WebSocket 已连接:', wsUrl)
  36. reconnectCount = 0
  37. // 启动心跳
  38. if (heartTimer) clearInterval(heartTimer)
  39. heartTimer = setInterval(() => {
  40. if (WSS && WSS.readyState === WebSocket.OPEN) {
  41. WSS.send('HeartBeat')
  42. }
  43. }, 5000)
  44. }
  45. /** 接收消息 */
  46. WSS.onmessage = (event) => {
  47. console.log('收到消息:', event.data)
  48. if (event.data === 'HeartBeat' || event.data === 'Pong') return;
  49. let msg
  50. try {
  51. msg = JSON.parse(event.data)
  52. } catch (e) {
  53. console.warn('非 JSON 消息:', event.data)
  54. return
  55. }
  56. if (msg.type === 'kickOut') {
  57. ElMessage({
  58. message: msg.content || '您的账号已在其他设备登录',
  59. type: 'warning',
  60. duration: 1500
  61. })
  62. localStorage.removeItem('token')
  63. localStorage.removeItem('userId')
  64. localStorage.removeItem('userType')
  65. localStorage.removeItem('companyId')
  66. if (store) store.dispatch('SET_DATA')
  67. if (router) router.push({ name: 'login' })
  68. }
  69. }
  70. /** 错误处理 */
  71. WSS.onerror = (err) => {
  72. console.error('WebSocket 错误:', err)
  73. }
  74. /** 连接关闭 */
  75. WSS.onclose = () => {
  76. console.log('WebSocket 已关闭')
  77. if (heartTimer) clearInterval(heartTimer)
  78. WSS = null
  79. // 自动重连
  80. if (!manuallyClosed) {
  81. if (reconnectCount < MAX_RECONNECT) {
  82. reconnectCount++
  83. console.warn(`WebSocket 断开,尝试第 ${reconnectCount} 次重连...`)
  84. reconnectTimer = setTimeout(() => {
  85. connectSocket(userId, store, router)
  86. }, RECONNECT_INTERVAL)
  87. } else {
  88. console.error('达到最大重连次数,停止重连')
  89. }
  90. }
  91. }
  92. }
  93. /** 获取 WebSocket 实例 */
  94. export function getSocket() {
  95. return WSS
  96. }
  97. /** 发送消息 */
  98. export function sendSocketMsg(data) {
  99. if (WSS && WSS.readyState === WebSocket.OPEN) {
  100. WSS.send(data)
  101. } else {
  102. console.warn('WebSocket 未连接,尝试重连后再发送')
  103. }
  104. }
  105. /** 关闭连接 */
  106. export function closeSocket() {
  107. manuallyClosed = true
  108. if (heartTimer) clearInterval(heartTimer)
  109. if (reconnectTimer) clearTimeout(reconnectTimer)
  110. if (WSS) {
  111. try { WSS.close() } catch { }
  112. WSS = null
  113. }
  114. }