From 4260da6a8b15cc3fd8dfb254ba3a078b1d7c596a Mon Sep 17 00:00:00 2001
From: hcong <1050828145@qq.com>
Date: 星期二, 26 十一月 2024 14:28:05 +0800
Subject: [PATCH] 1. socket生命周期监听、断线重连、心跳检测 2. socket消息校验、生成、解析 3. 观察者模式分发消息

---
 src/socket/index.js         |  134 ++++++++++++++++++++++++++
 src/enum/socketMessage.js   |    6 +
 src/api/index.js            |    5 
 src/socket/eventBus.js      |   26 +++++
 src/components.d.ts         |   21 ----
 src/socket/socketMessage.js |   52 ++++++++++
 src/App.vue                 |   23 ++++
 7 files changed, 244 insertions(+), 23 deletions(-)

diff --git a/src/App.vue b/src/App.vue
index 998905c..e992d9f 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -40,13 +40,15 @@
 
 <script>
 import zhCn from 'element-plus/dist/locale/zh-cn.mjs';
-
+import { FYWebSocket } from '@/socket/index.js'
+import eventBus from '@/socket/eventBus.js';
 export default {
   data() {
     return {
       isCollapsed: false,
       navTitles: [],
       locale: zhCn,
+      socket: null,
     };
   },
   methods: {
@@ -56,7 +58,26 @@
     navPage(titles) {
       this.navTitles = titles;
     },
+    // 杩炴帴websocket
+    connectWebSocket() {
+      this.socket = new FYWebSocket()
+      this.socket.init({
+        time: 4 * 1000,
+        timeout: 2 * 1000,
+        reconnect: 3 * 1000
+      }, true)
+    },
+    // 鏂嚎閲嶈繛websocket
+    startReconnectWebSocket() {
+      eventBus.register('reconnect', () => {
+        this.connectWebSocket()
+      })
+    }
   },
+  created() {
+    this.connectWebSocket()
+    this.startReconnectWebSocket()
+  }
 };
 </script>
 
diff --git a/src/api/index.js b/src/api/index.js
index 22e0559..3b5d513 100644
--- a/src/api/index.js
+++ b/src/api/index.js
@@ -17,6 +17,9 @@
   // ip2_file = 'https://fyami.com.cn/';
 }
 
+// socket
+const $socket_base_url = 'ws://192.168.0.150:8080/workstream'
+
 //椋炵窘鐩戠
 const $fysp = axios.create({
   baseURL: ip1,
@@ -106,4 +109,4 @@
   );
 });
 
-export { $fysp, $fytz };
+export { $fysp, $fytz, $socket_base_url };
diff --git a/src/components.d.ts b/src/components.d.ts
index d9c2fb8..48abf97 100644
--- a/src/components.d.ts
+++ b/src/components.d.ts
@@ -13,32 +13,22 @@
     CompGenericWrapper: typeof import('./components/CompGenericWrapper.vue')['default']
     CompQuickSet: typeof import('./components/search-option/CompQuickSet.vue')['default']
     Content: typeof import('./components/core/Content.vue')['default']
-    ElAffix: typeof import('element-plus/es')['ElAffix']
     ElAside: typeof import('element-plus/es')['ElAside']
     ElAvatar: typeof import('element-plus/es')['ElAvatar']
     ElBadge: typeof import('element-plus/es')['ElBadge']
     ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
     ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
     ElButton: typeof import('element-plus/es')['ElButton']
-    ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup']
-    ElCalendar: typeof import('element-plus/es')['ElCalendar']
     ElCard: typeof import('element-plus/es')['ElCard']
-    ElCascader: typeof import('element-plus/es')['ElCascader']
-    ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
     ElCol: typeof import('element-plus/es')['ElCol']
     ElCollapse: typeof import('element-plus/es')['ElCollapse']
     ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
     ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
     ElContainer: typeof import('element-plus/es')['ElContainer']
-    ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
     ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
     ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
     ElDialog: typeof import('element-plus/es')['ElDialog']
-    ElDivider: typeof import('element-plus/es')['ElDivider']
     ElDrawer: typeof import('element-plus/es')['ElDrawer']
-    ElDropdown: typeof import('element-plus/es')['ElDropdown']
-    ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
-    ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
     ElEmpty: typeof import('element-plus/es')['ElEmpty']
     ElForm: typeof import('element-plus/es')['ElForm']
     ElFormItem: typeof import('element-plus/es')['ElFormItem']
@@ -47,29 +37,19 @@
     ElImage: typeof import('element-plus/es')['ElImage']
     ElImageViewer: typeof import('element-plus/es')['ElImageViewer']
     ElInput: typeof import('element-plus/es')['ElInput']
-    ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
-    ElLink: typeof import('element-plus/es')['ElLink']
     ElMain: typeof import('element-plus/es')['ElMain']
     ElMenu: typeof import('element-plus/es')['ElMenu']
     ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
     ElMenuItemGroup: typeof import('element-plus/es')['ElMenuItemGroup']
     ElOption: typeof import('element-plus/es')['ElOption']
-    ElPageHeader: typeof import('element-plus/es')['ElPageHeader']
-    ElPagination: typeof import('element-plus/es')['ElPagination']
-    ElPopconfirm: typeof import('element-plus/es')['ElPopconfirm']
     ElPopover: typeof import('element-plus/es')['ElPopover']
-    ElRadio: typeof import('element-plus/es')['ElRadio']
-    ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
-    ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
     ElRow: typeof import('element-plus/es')['ElRow']
     ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
-    ElSegmented: typeof import('element-plus/es')['ElSegmented']
     ElSelect: typeof import('element-plus/es')['ElSelect']
     ElSpace: typeof import('element-plus/es')['ElSpace']
     ElStep: typeof import('element-plus/es')['ElStep']
     ElSteps: typeof import('element-plus/es')['ElSteps']
     ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
-    ElSwitch: typeof import('element-plus/es')['ElSwitch']
     ElTable: typeof import('element-plus/es')['ElTable']
     ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
     ElTabPane: typeof import('element-plus/es')['ElTabPane']
@@ -77,7 +57,6 @@
     ElTag: typeof import('element-plus/es')['ElTag']
     ElText: typeof import('element-plus/es')['ElText']
     ElTooltip: typeof import('element-plus/es')['ElTooltip']
-    ElTransfer: typeof import('element-plus/es')['ElTransfer']
     ElTree: typeof import('element-plus/es')['ElTree']
     ElUpload: typeof import('element-plus/es')['ElUpload']
     Footer: typeof import('./components/core/Footer.vue')['default']
diff --git a/src/enum/socketMessage.js b/src/enum/socketMessage.js
new file mode 100644
index 0000000..9c3579e
--- /dev/null
+++ b/src/enum/socketMessage.js
@@ -0,0 +1,6 @@
+const messageTypes = [
+  { id: 0, label: '蹇冭烦妫�娴�', value: '0' },
+  { id: 1, label: '鍚庡彴浠诲姟', value: '1' },
+  { id: 2, label: '涓氬姟鏃ュ織', value: '2' }
+];
+export { messageTypes };
diff --git a/src/socket/eventBus.js b/src/socket/eventBus.js
new file mode 100644
index 0000000..ba8e855
--- /dev/null
+++ b/src/socket/eventBus.js
@@ -0,0 +1,26 @@
+// 瑙傚療鑰呮ā寮�
+class EventBus {
+    constructor() {
+      // 娑堟伅涓績锛岃褰曚簡鎵�鏈夌殑浜嬩欢 浠ュ強 浜嬩欢瀵瑰簲鐨勫鐞嗗嚱鏁�
+      this.subs = Object.create(null)
+    }
+   
+    // 娉ㄥ唽鏃堕棿
+    // 鍙傛暟锛�1.浜嬩欢鍚嶇О  2.浜嬩欢澶勭悊鍑芥暟
+    register(eventType, handler) {
+      this.subs[eventType] = this.subs[eventType] || []
+      this.subs[eventType].push(handler)
+    }
+   
+    // 瑙﹀彂浜嬩欢
+    // 鍙傛暟锛� 1.浜嬩欢鍚嶇О 2.鎺ユ敹鐨勫弬鏁�
+    emit(eventType, ...ars) {
+      if(this.subs[eventType]) {
+        this.subs[eventType].forEach(handler => {
+          handler(...ars)
+        })
+      }
+    }
+  }
+   
+  export default new EventBus()
\ No newline at end of file
diff --git a/src/socket/index.js b/src/socket/index.js
new file mode 100644
index 0000000..35d8feb
--- /dev/null
+++ b/src/socket/index.js
@@ -0,0 +1,134 @@
+import eventBus from './eventBus.js';
+import { messageTypes } from '@/enum/socketMessage.js';
+import { encodeMessage, decodeMessage } from './socketMessage.js';
+import { $socket_base_url } from '@/api/index.js';
+class FYWebSocket extends WebSocket {
+  constructor() {
+    super($socket_base_url);
+    return this;
+  }
+  /**
+   * heartBeatConfig 蹇冭烦杩炴帴鍙傛暟
+   *    time: 蹇冭烦鏃堕棿闂撮殧
+   *    timeout: 蹇冭烦瓒呮椂闂撮殧
+   *    reconnect: 鏂嚎閲嶈繛鏃堕棿闂撮殧
+   * isReconnect 鏄惁鏂嚎閲嶈繛
+   */
+  init(heartBeatConfig, isReconnect) {
+    this.onopen = this.openHandler; // 杩炴帴鎴愬姛鍚庣殑鍥炶皟鍑芥暟
+    this.onclose = this.closeHandler; // 杩炴帴鍏抽棴鍚庣殑鍥炶皟 鍑芥暟
+    this.onmessage = this.messageHandler; // 鏀跺埌鏈嶅姟鍣ㄦ暟鎹悗鐨勫洖璋冨嚱鏁�
+    this.onerror = this.errorHandler; // 杩炴帴鍙戠敓閿欒鐨勫洖璋冩柟娉�
+    this.heartBeatConfig = heartBeatConfig; // 蹇冭烦杩炴帴閰嶇疆鍙傛暟
+    this.isReconnect = isReconnect; // 璁板綍鏄惁鏂嚎閲嶈繛
+    this.reconnectTimer = null; // 璁板綍鏂嚎閲嶈繛鐨勬椂闂村櫒
+    this.startHeartBeatTimer = null; // 璁板綍蹇冭烦鏃堕棿鍣�
+    this.webSocketState = false; // 璁板綍socket杩炴帴鐘舵�� true涓哄凡杩炴帴
+  }
+  // 鑾峰彇娑堟伅
+  getMessage({ data }) {
+    return decodeMessage(data);
+  }
+  // 鍙戦�佹秷鎭�
+  sendMessage(type, data) {
+    return this.send(encodeMessage(type, data));
+  }
+  // 杩炴帴鎴愬姛鍚庣殑鍥炶皟鍑芥暟
+  openHandler() {
+    console.log('====onopen 杩炴帴鎴愬姛====');
+    // socket鐘舵�佽缃负杩炴帴锛屽仛涓哄悗闈㈢殑鏂嚎閲嶈繛鐨勬嫤鎴櫒
+    this.webSocketState = true;
+    // 鍒ゆ柇鏄惁鍚姩蹇冭烦鏈哄埗
+    if (this.heartBeatConfig && this.heartBeatConfig.time) {
+      this.startHeartBeat(this.heartBeatConfig.time);
+    }
+  }
+  // 鏀跺埌鏈嶅姟鍣ㄦ暟鎹悗鐨勫洖璋冨嚱鏁�
+  messageHandler(res) {
+    const webSocketMsg = this.getMessage(res);
+    if (!(webSocketMsg && webSocketMsg != null && webSocketMsg != {})) {
+      return;
+    }
+    const type = webSocketMsg.type;
+    const data = webSocketMsg.data
+    
+    switch (type) {
+      case '1': // 鍚庡彴浠诲姟
+        // 鍙戦�佷簨浠�
+        eventBus.emit('1', data);
+        console.log('====onmessage 鍚庡彴浠诲姟====', data);
+        break;
+      case '2': // 涓氬姟鏃ュ織
+        // 鍙戦�佷簨浠�
+        eventBus.emit('2', data);
+        console.log('====onmessage 涓氬姟鏃ュ織====', data);
+        break;
+      case '0': // 蹇冭烦妫�娴�
+        // 灏嗚繛鎺ョ姸鎬佹洿鏂颁负鍦ㄧ嚎
+        this.webSocketState = true;
+        eventBus.emit('0', data);
+        console.log('====onmessage 蹇冭烦妫�娴�====', data);
+        break;
+    }
+  }
+  // 杩炴帴鍏抽棴鍚庣殑鍥炶皟 鍑芥暟
+  closeHandler() {
+    console.log('====onclose websocket鍏抽棴杩炴帴====');
+    // 璁剧疆socket鐘舵�佷负鏂嚎
+    this.webSocketState = false;
+    // 鍦ㄦ柇寮�杩炴帴鏃� 娓呴櫎蹇冭烦鏃堕棿鍣ㄥ拰 鏂紑閲嶈繛鏃堕棿鍣�
+    this.startHeartBeatTimer && clearTimeout(this.startHeartBeatTimer);
+    this.reconnectTimer && clearTimeout(this.reconnectTimer);
+    this.reconnectWebSocket();
+  }
+  errorHandler() {
+    console.log('====onerror websocket杩炴帴鍑洪敊====');
+    // 璁剧疆socket鐘舵�佷负鏂嚎
+    this.webSocketState = false;
+    // 閲嶆柊杩炴帴
+    this.reconnectWebSocket();
+  }
+
+  // 蹇冭烦鍒濆鍖栨柟娉� time锛氬績璺抽棿闅�
+  startHeartBeat(time) {
+    this.startHeartBeatTimer = setTimeout(() => {
+      // 瀹㈡埛绔瘡闅斾竴娈垫椂闂村悜鏈嶅姟绔彂閫佷竴涓績璺虫秷鎭�
+      this.sendMessage(messageTypes[0].value, Date.now());
+      this.waitingServer();
+    }, time);
+  }
+  //鍦ㄥ鎴风鍙戦�佹秷鎭箣鍚庯紝寤舵椂绛夊緟鏈嶅姟鍣ㄥ搷搴�,閫氳繃webSocketState鍒ゆ柇鏄惁杩炵嚎鎴愬姛
+  waitingServer() {
+    this.webSocketState = false;
+    setTimeout(() => {
+      // 杩炵嚎鎴愬姛鐘舵�佷笅 缁х画蹇冭烦妫�娴�
+      if (this.webSocketState) {
+        this.startHeartBeat(this.heartBeatConfig.time);
+        return;
+      }
+      console.log('蹇冭烦鏃犲搷搴�, 宸茬粡鍜屾湇鍔$鏂嚎');
+      // 閲嶆柊杩炴帴鏃讹紝璁板緱瑕佸厛鍏抽棴褰撳墠杩炴帴
+      try {
+        this.close();
+      } catch (error) {
+        console.log('褰撳墠杩炴帴宸茬粡鍏抽棴');
+      }
+      // // 閲嶆柊杩炴帴
+      // this.reconnectWebSocket()
+    }, this.heartBeatConfig.timeout);
+  }
+
+  // 閲嶆柊杩炴帴
+  reconnectWebSocket() {
+    // 鍒ゆ柇鏄惁鏄噸鏂拌繛鎺ョ姸鎬�(鍗宠鍔ㄧ姸鎬佹柇绾�)锛屽鏋滄槸涓诲姩鏂嚎鐨勪笉闇�瑕侀噸鏂拌繛鎺�
+    if (!this.isReconnect) {
+      return;
+    }
+    // 鏍规嵁浼犲叆鐨勬柇绾块噸杩炴椂闂撮棿闅� 寤舵椂杩炴帴
+    this.reconnectTimer = setTimeout(() => {
+      // 瑙﹀彂閲嶆柊杩炴帴浜嬩欢
+      eventBus.emit('reconnect');
+    }, this.heartBeatConfig.reconnect);
+  }
+}
+export { FYWebSocket };
diff --git a/src/socket/socketMessage.js b/src/socket/socketMessage.js
new file mode 100644
index 0000000..c891065
--- /dev/null
+++ b/src/socket/socketMessage.js
@@ -0,0 +1,52 @@
+import { messageTypes } from '@/enum/socketMessage.js' 
+// 寮�濮嬬鍙峰拰缁撴潫绗﹀彿鍒嗗埆涓� '##' 鍜� '%%', 鍒嗛殧绗︿负 &&
+// 寮�濮嬬鍙�
+const startStr = '##';
+// 鍒嗛殧绗﹀彿
+const splitStr = '&&';
+// 缁撴潫绗﹀彿
+const endStr = '%%';
+// 鏍¢獙鏍煎紡
+function verificationMessahe(message) {
+  if (!message || message == '') {
+    return false;
+  }
+  if (!(typeof message == 'string')) {
+    return false;
+  }
+  if (!message.startsWith(startStr)) {
+    return false;
+  }
+  if (!message.endsWith(endStr)) {
+    return false;
+  }
+  return true;
+}
+/**
+ * 瑙f瀽鍑虹被鍨嬪拰鍐呭
+ * @param {*} message socket娑堟伅涓殑data瀛楁
+ * @returns 
+ */
+function decodeMessage(message) {
+  if (!verificationMessahe(message)) {
+    return;
+  }
+  const parts = message.slice(startStr.length, -endStr.length).split(splitStr);
+  const type = parts[0];
+  let data = JSON.parse(parts[1]);
+  return {
+    type: messageTypes.find((item) => item.value == type).value,
+    data: data
+  };
+}
+/**
+ * 鐢熸垚鎸囧畾鏍煎紡鐨勬秷鎭瓧绗︿覆
+ * @param {*} type 娑堟伅绫诲瀷
+ * @param {*} data 娑堟伅鍐呭
+ * @returns 鐢熸垚鐨勬秷鎭瓧绗︿覆
+ */
+function encodeMessage(type, data) {
+  return `${startStr}${type}${splitStr}${JSON.stringify(data)}${endStr}`;
+}
+
+export { verificationMessahe, decodeMessage, encodeMessage, messageTypes };

--
Gitblit v1.9.3