From 229894d52a0f4cdca90bebfde323104d1810ef4f Mon Sep 17 00:00:00 2001
From: ZhangXianQiang <1135831638@qq.com>
Date: 星期三, 03 七月 2024 17:18:16 +0800
Subject: [PATCH] feat:添加webscoket

---
 .env.development          |    3 
 src/hooks/useWebScoket.js |  188 +++++++++++++++++++++++++++++++++++++++++++++++
 src/views/exam/index.vue  |   35 +++++++-
 3 files changed, 220 insertions(+), 6 deletions(-)

diff --git a/.env.development b/.env.development
index f6b70c2..4a153b1 100644
--- a/.env.development
+++ b/.env.development
@@ -1 +1,2 @@
-VITE_BASE_API_URL = 
\ No newline at end of file
+VITE_BASE_API_URL = 
+VITE_SCOKET_URL = ws://192.168.3.64:8000/websocket/
\ No newline at end of file
diff --git a/src/hooks/useWebScoket.js b/src/hooks/useWebScoket.js
new file mode 100644
index 0000000..5f3da78
--- /dev/null
+++ b/src/hooks/useWebScoket.js
@@ -0,0 +1,188 @@
+import { ref, onBeforeUnmount, onMounted } from 'vue';
+
+const DEFAULT_OPTIONS = {
+  url: '', // websocket url
+  heartBeatData: '', // 浣犵殑蹇冭烦鏁版嵁
+  heartBeatInterval: 60 * 1000, // 蹇冭烦闂撮殧锛屽崟浣峬s
+  reconnectInterval: 5000, // 鏂嚎閲嶈繛闂撮殧锛屽崟浣峬s
+  maxReconnectAttempts: 10 // 鏈�澶ч噸杩炴鏁�
+};
+
+export const SocketStatus = {
+  Connecting: '姝e湪杩炴帴...', //琛ㄧず姝e湪杩炴帴锛岃繖鏄垵濮嬬姸鎬併��
+  Connected: '杩炴帴宸插缓绔�', //琛ㄧず杩炴帴宸茬粡寤虹珛銆�
+  Disconnecting: '杩炴帴姝e湪鍏抽棴', //琛ㄧず杩炴帴姝e湪鍏抽棴銆�
+  Disconnected: '杩炴帴宸叉柇寮�' //琛ㄧず杩炴帴宸茬粡鍏抽棴
+};
+
+const SocketCloseCode = 1000;
+
+export default function useWebSocket(options = {}) {
+  const state = {
+    options: { ...DEFAULT_OPTIONS, ...options },
+
+    socket: null,
+    reconnectAttempts: 0,
+    reconnectTimeout: null,
+
+    heartBetaSendTimer: null, // 蹇冭烦鍙戦�佸畾鏃跺櫒
+    heartBetaTimeoutTimer: null // 蹇冭烦瓒呮椂瀹氭椂鍣�
+  };
+
+  // 杩炴帴鐘舵��
+  const status = ref(SocketStatus.Disconnected);
+  const message = ref(null);
+  const error = ref(null);
+
+  // 杩炴帴
+  const connect = (connectCallBack) => {
+    disconnect();
+
+    status.value = SocketStatus.Connecting;
+    if (!window.navigator.onLine) {
+      setTimeout(() => {
+        status.value = SocketStatus.Disconnected;
+      }, 500);
+      return;
+    }
+
+    state.socket = new WebSocket(state.options.url);
+
+    state.socket.onopen = openEvent => {
+      console.log('socket杩炴帴:', openEvent);
+      state.reconnectAttempts = 0;
+      status.value = SocketStatus.Connected;
+      error.value = null;
+      startHeartBeat();
+      if(connectCallBack) {
+        connectCallBack();
+      }
+    };
+
+    state.socket.onmessage = msgEvent => {
+      console.log('socket娑堟伅:', msgEvent);
+
+      // 鏀跺埌浠讳綍鏁版嵁锛岄噸鏂板紑濮嬪績璺�
+      startHeartBeat();
+
+      const { data } = msgEvent;
+      const msg = JSON.parse(data);
+
+      //蹇冭烦鏁版嵁, 鍙嚜琛屼慨鏀�
+      if (+msg.msg_id === 0) {
+        return;
+      }
+      message.value = msg;
+    };
+
+    state.socket.onclose = closeEvent => {
+      console.log('socket鍏抽棴:', closeEvent);
+      status.value = SocketStatus.Disconnected;
+      // 闈炴甯稿叧闂�,灏濊瘯閲嶈繛
+      if (closeEvent.code !== SocketCloseCode) {
+        reconnect();
+      }
+    };
+
+    state.socket.onerror = errEvent => {
+      console.log('socket鎶ラ敊:', errEvent);
+      status.value = SocketStatus.Disconnected;
+      error.value = errEvent;
+      // 杩炴帴澶辫触锛屽皾璇曢噸杩�
+      reconnect();
+    };
+  };
+
+  const disconnect = () => {
+    if (state.socket && (state.socket.OPEN || state.socket.CONNECTING)) {
+      console.log('socket鏂紑杩炴帴');
+      status.value = SocketStatus.Disconnecting;
+      state.socket.onmessage = null;
+      state.socket.onerror = null;
+      state.socket.onclose = null;
+      // 鍙戦�佸叧闂抚缁欐湇鍔$
+      state.socket.close(SocketCloseCode, 'normal closure');
+      status.value = SocketStatus.Disconnected;
+      state.socket = null;
+    }
+    stopHeartBeat();
+    stopReconnect();
+  };
+
+  // 鍙戦�佹秷鎭�
+  const sendMessage = (data) => {
+    if (state.socket && status.value === SocketStatus.Connected) {
+      state.socket.send(JSON.stringify(data));
+      console.log('socket鍙戦��:', JSON.stringify(data));
+    }
+  };
+
+  const startHeartBeat = () => {
+    stopHeartBeat();
+    onHeartBeat(() => {
+      if (status.value === SocketStatus.Connected) {
+        state.socket.send(state.options.heartBeatData);
+        console.log('socket蹇冭烦鍙戦��:', state.options.heartBeatData);
+      }
+    });
+  };
+
+  const onHeartBeat = callback => {
+    state.heartBetaSendTimer = setTimeout(() => {
+      callback && callback();
+      state.heartBetaTimeoutTimer = setTimeout(() => {
+        // 蹇冭烦瓒呮椂,鐩存帴鍏抽棴socket,鎶涘嚭鑷畾涔塩ode=4444, onclose閲岃繘琛岄噸杩�
+        state.socket.close(4444, 'heart timeout');
+      }, state.options.heartBeatInterval);
+    }, state.options.heartBeatInterval);
+  };
+
+  const stopHeartBeat = () => {
+    state.heartBetaSendTimer && clearTimeout(state.heartBetaSendTimer);
+    state.heartBetaTimeoutTimer && clearTimeout(state.heartBetaTimeoutTimer);
+  };
+
+  // 閲嶈繛
+  const reconnect = () => {
+    if (status.value === SocketStatus.Connected || status.value === SocketStatus.Connecting) {
+      return;
+    }
+    stopHeartBeat();
+    if (state.reconnectAttempts < state.options.maxReconnectAttempts) {
+      console.log('socket閲嶈繛:', state.reconnectAttempts);
+
+      // 閲嶈繛闂撮殧锛�5绉掕捣姝ワ紝涓嬫閫掑1绉�
+      const interval = Math.max(state.options.reconnectInterval, state.reconnectAttempts * 1000);
+      console.log('闂撮殧鏃堕棿锛�', interval);
+      state.reconnectTimeout = setTimeout(() => {
+        if (status.value !== SocketStatus.Connected && status.value !== SocketStatus.Connecting) {
+          connect();
+        }
+      }, interval);
+      state.reconnectAttempts += 1;
+    } else {
+      status.value = SocketStatus.Disconnected;
+      stopReconnect();
+    }
+  };
+
+  // 鍋滄閲嶈繛
+  const stopReconnect = () => {
+    state.reconnectTimeout && clearTimeout(state.reconnectTimeout);
+  };
+
+  onBeforeUnmount(() => {
+    if (status.value === SocketStatus.Connected || status.value === SocketStatus.Connecting) {
+      disconnect();
+    }
+  });
+
+  return {
+    status,
+    message,
+    error,
+    connect,
+    disconnect,
+    sendMessage
+  };
+}
\ No newline at end of file
diff --git a/src/views/exam/index.vue b/src/views/exam/index.vue
index 0918145..591e735 100644
--- a/src/views/exam/index.vue
+++ b/src/views/exam/index.vue
@@ -130,7 +130,7 @@
 </template>
 
 <script setup>
-import { ref, watchEffect } from 'vue';
+import { ref, watchEffect, watch, onMounted } from 'vue';
 import { storeToRefs } from 'pinia';
 import { Close, Timer } from '@element-plus/icons-vue';
 import AnswerTag from './components/answer-tag/index.vue';
@@ -146,15 +146,19 @@
 import AnswerShort from './components/answer-main/answer-short/index.vue';
 import AnswerCount from './components/answer-main/answer-count/index.vue';
 
-import { useExamStore } from '@/store/index.js';
+import { useExamStore, useUserStore } from '@/store/index.js';
 import { useRouter } from 'vue-router';
 
 import { submitExam } from '@/api/modules/exam.js';
+import useWebScoket from '@/hooks/useWebScoket.js';
 
 
 const router = useRouter();
 
 const examStore = useExamStore();
+const userStore = useUserStore();
+
+const { userInfo } = storeToRefs(userStore);
 const { currentType, currentIndex, examDetail, examType, examInfo } = storeToRefs(examStore);
 
 const typeComponent = {
@@ -172,17 +176,25 @@
 const submitDialog = ref(false);
 const timeDialog = ref(false);
 
+const { status, message, error, connect, disconnect, sendMessage } = useWebScoket({
+  url: 'ws://192.168.3.64:8000/websocket/' + userInfo.value.id,
+  heartBeatData: 'ping'
+});
 
+
+// 涓婁竴棰�
 const prevQuestion = () => {
   currentIndex.value--;
   checkList();
 };
 
+// 涓嬩竴棰�
 const nextQuestion = () => {
   currentIndex.value++;
   checkList();
 };
 
+// 鏌ヨ棰樼洰
 const checkList = () => {
   let tempIndex = 0;
   const typeQuestion = examDetail.value.find((typeItem, index) => {
@@ -218,7 +230,6 @@
 
 };
 
-
 // 閫�鍑鸿�冭瘯
 const closeClick = () => {
   quitDialog.value = true;
@@ -244,12 +255,15 @@
 
 // 鏃堕棿缁撴潫
 const timeOut = () => {
-  resetAllDialog();
-  timeDialog.value = true;
   const temp = {
     ...examInfo.value,
     titleList: examDetail.value
   };
+  timeDialog.value = true;
+  resetAllDialog();
+
+  disconnect();
+  
   submitExam(temp).then(res => {
     returnBack();
   }).catch(() => {
@@ -274,6 +288,17 @@
   });
   examStore.setProgress(progress);
 });
+
+watch(() => message.value, (msg) => {
+  console.log(msg);
+});
+
+
+// -----------------------------------鐢熷懡鍛ㄦ湡
+onMounted(() => {
+  // 杩炴帴webscoket
+  connect();
+});
 </script>
 
 <style lang="scss" scoped>

--
Gitblit v1.8.0