zhanghaijun %!s(int64=2) %!d(string=hai) anos
pai
achega
2119ea8224

+ 3 - 3
train-common/train-common-websocket/src/main/java/top/haijunit/websocket/handler/WebSocketHandler.java

@@ -27,7 +27,7 @@ public class WebSocketHandler extends AbstractWebSocketHandler {
     /** 处理发送来的文本消息 */
     @Override
     protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
-        log.info("[websocket] 连接:" + session.getId() + ",已收到消息:" + message.getPayload());
+        log.info("[websocket] 连接成功:" + session.getId() + ",已收到消息:" + message.getPayload());
         String sessionId = Convert.toStr(session.getAttributes().get("loginUser"), "");
         WebSocketUtils.sendMessage(sessionId, message.getPayload());
     }
@@ -41,13 +41,13 @@ public class WebSocketHandler extends AbstractWebSocketHandler {
     /** 连接出错时 */
     @Override
     public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
-        log.error("[websocket] sessionId: {} , exception:{}", session.getId(), exception.getMessage());
+        log.error("[websocket] 连接异常 sessionId: {} , exception:{}", session.getId(), exception.getMessage());
     }
 
     /** 连接关闭后 */
     @Override
     public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
-        log.info("[websocket] sessionId: {}", session.getId());
+        log.info("[websocket] 连接关闭: {}", session.getId());
     }
 
     /** 是否支持分片消息 */

+ 13 - 0
train-web/src/App.vue

@@ -4,6 +4,18 @@
   </router-view>
 </template>
 
+<script setup lang="ts">
+import { onMounted, onUnmounted } from "vue";
+import { useStoreSocket } from "@/store/socket";
+
+const socketClinet = useStoreSocket();
+onMounted(() => {
+  socketClinet.connect("ws://127.0.0.1:8086/websocket");
+});
+onUnmounted(() => {
+  socketClinet.close();
+});
+</script>
 <style>
 .fade-enter-active,
 .fade-leave-active {
@@ -14,3 +26,4 @@
   opacity: 0;
 }
 </style>
+@/store/socket

+ 0 - 20
train-web/src/store/setting.ts

@@ -1,20 +0,0 @@
-import { defineStore } from "pinia";
-
-export const SettingStore = defineStore("setting", {
-  state: () => {
-    return {
-      train: {
-        name: "zhangsan",
-        age: 23,
-      },
-      token: "S1",
-    };
-  },
-  getters: {},
-  actions: {},
-  persist: {
-    key: "storekey",
-    storage: window.localStorage,
-    paths: ["train.name"],
-  },
-});

+ 1 - 1
train-web/src/store/devices.ts

@@ -1,6 +1,6 @@
 import { defineStore } from "pinia";
 
-export const SettingStore = defineStore("devices", {
+export const StoreDevices = defineStore("devices", {
   state: () => {
     return {};
   },

+ 1 - 1
train-web/src/store/situation.ts

@@ -1,6 +1,6 @@
 import { defineStore } from "pinia";
 
-export const SettingStore = defineStore("situation", {
+export const StoreSituation = defineStore("situation", {
   state: () => {
     return {};
   },

+ 1 - 1
train-web/src/store/train.ts

@@ -7,7 +7,7 @@ export interface TrainInfo {
   speed: number,
 }
 
-export const SettingStore = defineStore("train", {
+export const StoreTrain = defineStore("train", {
   state: (): Array<TrainInfo> => {
     return [];
   },

+ 33 - 0
train-web/src/store/socket.ts

@@ -0,0 +1,33 @@
+import { defineStore } from "pinia";
+import SocketClient from "@/types/WebSocketClient";
+
+interface WebSocketState {
+  client: SocketClient<string> | null;
+  messages: string[];
+}
+
+export const useStoreSocket = defineStore("websocket", {
+  state: (): WebSocketState => ({
+    client: null,
+    messages: [],
+  }),
+  getters: {},
+  actions: {
+    connect(url: string, protocol?: string) {
+      this.client = new SocketClient<string>((data: string) => {
+        console.log("收到消息:", data);
+        this.messages.push(data);
+      }, url, protocol);
+    },
+    close() {
+      this.client?.close();
+      this.client = null;
+    },
+    sendMessage(message: string): boolean {
+      return this.client != null && this.client.send(message);
+    },
+  },
+  persist: {
+    paths: ['messages']
+  },
+});

+ 65 - 0
train-web/src/types/WebSocketClient.ts

@@ -0,0 +1,65 @@
+class SocketClient<T> {
+  private socket: WebSocket | null = null;
+  private retryCount: number = 0;
+  private readonly maxRetries: number = -1;
+
+  constructor(private listener: (data: T) => void, private url: string, private protocol?: string) {
+    this.connect();
+  }
+  private isConnected(): boolean {
+    return this.socket !== null && this.socket.readyState === WebSocket.OPEN;
+  }
+  private connect() {
+    this.socket = new WebSocket(this.url, this.protocol);
+
+    this.socket.addEventListener('open', (event: Event) => {
+      console.log('WebSocket连接成功:', event);
+      this.retryCount = 0;
+    });
+    this.socket.addEventListener('message', (event: MessageEvent) => {
+      try {
+        this.listener?.(JSON.parse(event.data) as T);
+      } catch (error) {
+        this.listener?.(event.data as T);
+      }
+    });
+    this.socket.addEventListener('close', (event: CloseEvent) => {
+      console.error('WebSocket连接关闭:', event);
+    });
+    this.socket.addEventListener('error', (event: Event) => {
+      console.error('WebSocket连接异常:', event);
+      this.connectAfresh();
+    });
+  }
+
+  public connectAfresh(isRestart: boolean = false) {
+    if (!isRestart) {
+      this.retryCount = 0;
+    }
+    if (!this.isConnected() && (this.maxRetries === -1 || this.retryCount < this.maxRetries)) {
+      console.log(`尝试连接中... (${this.retryCount + 1}/${this.maxRetries})`);
+      setTimeout(() => this.connect(), this.getRetryInterval());
+      this.retryCount++;
+    } else if (!this.isConnected()) {
+      console.log('已经达到最大尝试次数,依然无法连接');
+    }
+  }
+  private getRetryInterval(): number {
+    // 为了提高效率,每次重连间隔递增
+    return 1000 * Math.pow(2, 100);
+  }
+  public send(message: string): boolean {
+    if (this.isConnected()) {
+      this.socket?.send(message);
+      return true;
+    } else {
+      console.error('WebSocket未连接,无法发送消息.');
+      return false;
+    }
+  }
+  public close() {
+    this.socket?.close();
+  }
+}
+
+export default SocketClient;

+ 1 - 1
train-web/types/auto-imports.d.ts

@@ -297,7 +297,7 @@ declare global {
 // for type re-export
 declare global {
   // @ts-ignore
-  export type { Component, ComponentPublicInstance, ComputedRef, InjectionKey, PropType, Ref, VNode } from 'vue'
+  export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue'
 }
 // for vue template auto import
 import { UnwrapRef } from 'vue'

+ 3 - 3
train-web/types/components.d.ts

@@ -13,10 +13,10 @@ declare module 'vue' {
     ElMain: typeof import('element-plus/es')['ElMain']
     ElMenu: typeof import('element-plus/es')['ElMenu']
     ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
-    HelloWorld: typeof import('./../src/components/HelloWorld.vue')['default']
+    HelloWorld: typeof import('./../components/HelloWorld.vue')['default']
     IconEpSuccessFilled: typeof import('~icons/ep/success-filled')['default']
-    IconSvg: typeof import('./../src/components/icon-svg/index.vue')['default']
-    MCanvas: typeof import('./../src/components/MCanvas.vue')['default']
+    IconSvg: typeof import('./../components/icon-svg/index.vue')['default']
+    MCanvas: typeof import('./../components/MCanvas.vue')['default']
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']
   }

+ 3 - 3
train-web/tsconfig.json

@@ -36,12 +36,12 @@
     "src/**/*.d.ts",
     "src/**/*.tsx",
     "src/**/*.vue",
-    "types/**/*.d.ts",
-    "types/**/*.ts"
+    "src/types/**/*.d.ts",
+    "src/types/**/*.ts"
   ],
   "references": [
     {
       "path": "./tsconfig.node.json"
     }
   ]
-}
+}

+ 2 - 2
train-web/vite.config.ts

@@ -25,11 +25,11 @@ export default ({ mode }: ConfigEnv) => {
         imports: ["vue", "vue-router", "pinia", "@vueuse/core", { axios: [["default", "axios"]] }],
         resolvers: [ElementPlusResolver(), IconsResolver({})],
         vueTemplate: true,
-        dts: path.resolve(__dirname, "types", "auto-imports.d.ts"),
+        dts: path.resolve(__dirname, "src/types", "auto-imports.d.ts"),
       }),
       Components({
         resolvers: [ElementPlusResolver(), IconsResolver({ prefix: "icon", enabledCollections: ["ep"] }), VueUseComponentsResolver()],
-        dts: path.resolve(__dirname, "types", "components.d.ts"),
+        dts: path.resolve(__dirname, "src/types", "components.d.ts"),
       }),
       // 图标库 'https://fontawesome.com.cn/v5' 'https://icones.netlify.app/' 'https://icon-sets.iconify.design/'
       Icons({ autoInstall: true }),