useWebSocket.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. import { onBeforeUnmount, reactive, ref } from 'vue';
  2. import { baseUrl, websocketPath } from '@/sheep/config';
  3. import { copyValueToTarget } from '@/sheep/util';
  4. /**
  5. * WebSocket 创建 hook
  6. * @param opt 连接配置
  7. * @return {{options: *}}
  8. */
  9. export function useWebSocket(opt) {
  10. const getAccessToken = () => {
  11. return uni.getStorageSync('token');
  12. };
  13. const options = reactive({
  14. url: (baseUrl + websocketPath).replace('http', 'ws') + '?token=' + getAccessToken(), // ws 地址
  15. isReconnecting: false, // 正在重新连接
  16. reconnectInterval: 3000, // 重连间隔,单位毫秒
  17. heartBeatInterval: 5000, // 心跳间隔,单位毫秒
  18. pingTimeoutDuration: 1000, // 超过这个时间,后端没有返回pong,则判定后端断线了。
  19. heartBeatTimer: null, // 心跳计时器
  20. destroy: false, // 是否销毁
  21. pingTimeout: null, // 心跳检测定时器
  22. reconnectTimeout: null, // 重连定时器ID的属性
  23. onConnected: () => {
  24. }, // 连接成功时触发
  25. onClosed: () => {
  26. }, // 连接关闭时触发
  27. onMessage: (data) => {
  28. }, // 收到消息
  29. });
  30. const SocketTask = ref(null); // SocketTask 由 uni.connectSocket() 接口创建
  31. const initEventListeners = () => {
  32. // 监听 WebSocket 连接打开事件
  33. SocketTask.value.onOpen(() => {
  34. console.log('WebSocket 连接成功');
  35. // 连接成功时触发
  36. options.onConnected();
  37. // 开启心跳检查
  38. startHeartBeat();
  39. });
  40. // 监听 WebSocket 接受到服务器的消息事件
  41. SocketTask.value.onMessage((res) => {
  42. try {
  43. if (res.data === 'pong') {
  44. // 收到心跳重置心跳超时检查
  45. resetPingTimeout();
  46. } else {
  47. options.onMessage(JSON.parse(res.data));
  48. }
  49. } catch (error) {
  50. console.error(error);
  51. }
  52. });
  53. // 监听 WebSocket 连接关闭事件
  54. SocketTask.value.onClose((event) => {
  55. // 情况一:实例销毁
  56. if (options.destroy) {
  57. options.onClosed();
  58. } else { // 情况二:连接失败重连
  59. // 停止心跳检查
  60. stopHeartBeat();
  61. // 重连
  62. reconnect();
  63. }
  64. });
  65. };
  66. // 发送消息
  67. const sendMessage = (message) => {
  68. if (SocketTask.value && !options.destroy) {
  69. SocketTask.value.send({ data: message });
  70. }
  71. };
  72. // 开始心跳检查
  73. const startHeartBeat = () => {
  74. options.heartBeatTimer = setInterval(() => {
  75. sendMessage('ping');
  76. options.pingTimeout = setTimeout(() => {
  77. // 如果在超时时间内没有收到 pong,则认为连接断开
  78. reconnect();
  79. }, options.pingTimeoutDuration);
  80. }, options.heartBeatInterval);
  81. };
  82. // 停止心跳检查
  83. const stopHeartBeat = () => {
  84. clearInterval(options.heartBeatTimer);
  85. resetPingTimeout();
  86. };
  87. // WebSocket 重连
  88. const reconnect = () => {
  89. if (options.destroy || !SocketTask.value) {
  90. // 如果WebSocket已被销毁或尚未完全关闭,不进行重连
  91. return;
  92. }
  93. // 重连中
  94. options.isReconnecting = true;
  95. // 清除现有的重连标志,以避免多次重连
  96. if (options.reconnectTimeout) {
  97. clearTimeout(options.reconnectTimeout);
  98. }
  99. // 设置重连延迟
  100. options.reconnectTimeout = setTimeout(() => {
  101. // 检查组件是否仍在运行和WebSocket是否关闭
  102. if (!options.destroy) {
  103. // 重置重连标志
  104. options.isReconnecting = false;
  105. // 初始化新的WebSocket连接
  106. initSocket();
  107. }
  108. }, options.reconnectInterval);
  109. };
  110. const resetPingTimeout = () => {
  111. if (options.pingTimeout) {
  112. clearTimeout(options.pingTimeout);
  113. options.pingTimeout = null; // 清除超时ID
  114. }
  115. };
  116. const close = () => {
  117. options.destroy = true;
  118. stopHeartBeat();
  119. if (options.reconnectTimeout) {
  120. clearTimeout(options.reconnectTimeout);
  121. }
  122. if (SocketTask.value) {
  123. SocketTask.value.close();
  124. SocketTask.value = null;
  125. }
  126. };
  127. const initSocket = () => {
  128. options.destroy = false;
  129. copyValueToTarget(options, opt);
  130. SocketTask.value = uni.connectSocket({
  131. url: options.url,
  132. complete: () => {
  133. },
  134. success: () => {
  135. },
  136. });
  137. initEventListeners();
  138. };
  139. initSocket();
  140. onBeforeUnmount(() => {
  141. close();
  142. });
  143. return { options };
  144. }