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