-
- - - -
- - - - -

ElementPlus 提供了丰富的组件库,满足各种业务需求

- 了解更多 -
-
- - - - -

基于现代设计理念,提供美观的用户界面

- 查看设计 -
-
- - - - -

基于Vue 3构建,享受最新的响应式系统带来的性能提升

- 性能测试 -
-
-
-
- -
-

组件演示

- - -
- - 默认按钮 - 主要按钮 - 成功按钮 - 信息按钮 - 警告按钮 - 危险按钮 - - - - - - -
-
-
-
-
diff --git a/src/components/WebSocketChat.vue b/src/components/WebSocketChat.vue new file mode 100644 index 0000000..2e1549d --- /dev/null +++ b/src/components/WebSocketChat.vue @@ -0,0 +1,576 @@ + + + + + \ No newline at end of file diff --git a/src/components/WebSocketDemo.vue b/src/components/WebSocketDemo.vue new file mode 100644 index 0000000..31d7327 --- /dev/null +++ b/src/components/WebSocketDemo.vue @@ -0,0 +1,426 @@ + + + + + \ No newline at end of file diff --git a/src/components/WebSocketUsage.vue b/src/components/WebSocketUsage.vue new file mode 100644 index 0000000..2c4ebf8 --- /dev/null +++ b/src/components/WebSocketUsage.vue @@ -0,0 +1,223 @@ + + + + + \ No newline at end of file diff --git a/src/router/index.ts b/src/router/index.ts index 3e49915..0bd70e0 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -17,6 +17,16 @@ const router = createRouter({ // which is lazy-loaded when the route is visited. component: () => import('../views/AboutView.vue'), }, + { + path: '/websocket', + name: 'websocket', + component: () => import('../views/WebSocketDemoView.vue'), + }, + { + path: '/test', + name: 'test', + component: () => import('../views/TestView.vue'), + }, ], }) diff --git a/src/util/websocket.ts b/src/util/websocket.ts new file mode 100644 index 0000000..d8be2f0 --- /dev/null +++ b/src/util/websocket.ts @@ -0,0 +1,109 @@ +import { Client } from '@stomp/stompjs'; +import type { IMessage, StompSubscription } from '@stomp/stompjs'; +import SockJS from 'sockjs-client'; + +interface StompClientOptions { + endpoint: string; // WebSocket endpoint,例如:/ws + sendPath?: string; // 默认发送路径(可选) + host: string; // 服务器地址,例如:http://localhost:8080 + useSockJS?: boolean; // 是否使用 SockJS(默认 false) + + onConnect?: () => void; // 连接成功回调 + onError?: (err: any) => void; // 错误回调 + onMessage?: (msg: any, path: string) => void; // 全局消息处理回调(含路径) +} + +export default class StompClient { + private options: StompClientOptions; + private stompClient: Client; + private subscriptions: Map = new Map(); + private messageHandlers: Map void> = new Map(); + + constructor(options: StompClientOptions) { + this.options = options; + + this.stompClient = new Client({ + brokerURL: options.useSockJS + ? undefined + : `ws://${options.host.replace(/^http(s)?:\/\//, '')}${options.endpoint}`, + webSocketFactory: options.useSockJS + ? () => new SockJS(`${options.host}${options.endpoint}`) + : undefined, + reconnectDelay: 5000, + debug: () => {}, + }); + + this.stompClient.onConnect = () => { + // 连接成功后,自动重新订阅所有路径 + this.messageHandlers.forEach((handler, path) => { + const sub = this.stompClient.subscribe(path, (msg: IMessage) => { + const body = JSON.parse(msg.body); + handler(body); + this.options.onMessage?.(body, path); + }); + this.subscriptions.set(path, sub); + }); + + this.options.onConnect?.(); + }; + + this.stompClient.onStompError = (frame) => { + console.error('STOMP 错误:', frame); + this.options.onError?.(frame); + }; + } + + /** 建立连接 */ + connect() { + this.stompClient.activate(); + } + + /** 发送消息,支持指定 destination,未指定则用默认 sendPath */ + send(data: any, sendPath?: string) { + if (!this.stompClient.connected) { + console.warn('STOMP 未连接,无法发送消息'); + return; + } + + const destination = sendPath ?? this.options.sendPath; + if (!destination) { + console.warn('未提供发送路径'); + return; + } + + this.stompClient.publish({ + destination, + body: JSON.stringify(data), + }); + } + + /** 订阅指定路径 */ + subscribe(path: string, handler: (msg: any) => void) { + this.messageHandlers.set(path, handler); + + if (this.stompClient.connected) { + const sub = this.stompClient.subscribe(path, (msg: IMessage) => { + const body = JSON.parse(msg.body); + handler(body); + this.options.onMessage?.(body, path); + }); + this.subscriptions.set(path, sub); + } + } + + /** 取消订阅指定路径 */ + unsubscribe(path: string) { + this.subscriptions.get(path)?.unsubscribe(); + this.subscriptions.delete(path); + this.messageHandlers.delete(path); + } + + /** 断开连接并清理订阅 */ + disconnect() { + this.subscriptions.forEach((sub) => sub.unsubscribe()); + this.subscriptions.clear(); + this.messageHandlers.clear(); + this.stompClient.deactivate(); + console.log('STOMP 已断开连接'); + } +} diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue index 0f8cc54..f201f8f 100644 --- a/src/views/HomeView.vue +++ b/src/views/HomeView.vue @@ -1,7 +1,8 @@ + + + + \ No newline at end of file diff --git a/src/views/WebSocketDemoView.vue b/src/views/WebSocketDemoView.vue new file mode 100644 index 0000000..680fa3b --- /dev/null +++ b/src/views/WebSocketDemoView.vue @@ -0,0 +1,46 @@ + + + + + \ No newline at end of file diff --git a/tsconfig.app.json b/tsconfig.app.json index 913b8f2..a2a31be 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -4,6 +4,7 @@ "exclude": ["src/**/__tests__/*"], "compilerOptions": { "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "verbatimModuleSyntax": false, "paths": { "@/*": ["./src/*"] diff --git a/vite.config.ts b/vite.config.ts index d49d708..32bc912 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -17,4 +17,7 @@ export default defineConfig({ '@': fileURLToPath(new URL('./src', import.meta.url)) }, }, + define: { + global: 'globalThis', + }, }) diff --git a/ws-begin/.gitignore b/ws-begin/.gitignore new file mode 100644 index 0000000..5ff6309 --- /dev/null +++ b/ws-begin/.gitignore @@ -0,0 +1,38 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/ws-begin/pom.xml b/ws-begin/pom.xml new file mode 100644 index 0000000..3095344 --- /dev/null +++ b/ws-begin/pom.xml @@ -0,0 +1,56 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 3.2.0 + + + + org.example + ws-begin + 1.0-SNAPSHOT + jar + + + 21 + 21 + 21 + UTF-8 + + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-websocket + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + \ No newline at end of file diff --git a/ws-begin/src/main/java/org/example/WebSocketApplication.java b/ws-begin/src/main/java/org/example/WebSocketApplication.java new file mode 100644 index 0000000..8c1a456 --- /dev/null +++ b/ws-begin/src/main/java/org/example/WebSocketApplication.java @@ -0,0 +1,11 @@ +package org.example; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class WebSocketApplication { + public static void main(String[] args) { + SpringApplication.run(WebSocketApplication.class, args); + } +} \ No newline at end of file diff --git a/ws-begin/src/main/java/org/example/config/CorsConfig.java b/ws-begin/src/main/java/org/example/config/CorsConfig.java new file mode 100644 index 0000000..df800d9 --- /dev/null +++ b/ws-begin/src/main/java/org/example/config/CorsConfig.java @@ -0,0 +1,24 @@ +package org.example.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class CorsConfig { + + @Bean + public WebMvcConfigurer corsConfigurer() { + return new WebMvcConfigurer() { + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedOriginPatterns("*") + .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") + .allowedHeaders("*") + .allowCredentials(true); + } + }; + } +} \ No newline at end of file diff --git a/ws-begin/src/main/java/org/example/config/WebSocketConfig.java b/ws-begin/src/main/java/org/example/config/WebSocketConfig.java new file mode 100644 index 0000000..171a532 --- /dev/null +++ b/ws-begin/src/main/java/org/example/config/WebSocketConfig.java @@ -0,0 +1,32 @@ +package org.example.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.messaging.simp.config.MessageBrokerRegistry; +import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; +import org.springframework.web.socket.config.annotation.StompEndpointRegistry; +import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; + +@Configuration +@EnableWebSocketMessageBroker +public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { + + @Override + public void configureMessageBroker(MessageBrokerRegistry config) { + // 启用简单的消息代理,消息的目的地前缀为 /topic 和 /queue + config.enableSimpleBroker("/topic", "/queue"); + // 客户端发送消息的目的地前缀为 /app + config.setApplicationDestinationPrefixes("/app"); + } + + @Override + public void registerStompEndpoints(StompEndpointRegistry registry) { + // 注册 STOMP 端点,允许所有来源访问 + registry.addEndpoint("/ws") + .setAllowedOriginPatterns("*") + .withSockJS(); // 启用 SockJS 支持 + + // 也注册一个不使用 SockJS 的端点(可选) + registry.addEndpoint("/ws") + .setAllowedOriginPatterns("*"); + } +} \ No newline at end of file diff --git a/ws-begin/src/main/java/org/example/controller/MessageController.java b/ws-begin/src/main/java/org/example/controller/MessageController.java new file mode 100644 index 0000000..8fb23d0 --- /dev/null +++ b/ws-begin/src/main/java/org/example/controller/MessageController.java @@ -0,0 +1,50 @@ +package org.example.controller; + +import org.example.model.Message; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.messaging.handler.annotation.MessageMapping; +import org.springframework.messaging.handler.annotation.SendTo; +import org.springframework.messaging.simp.SimpMessagingTemplate; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Date; +@RestController +public class MessageController { + + private final SimpMessagingTemplate simpMessagingTemplate; + + private static final Logger log = LoggerFactory.getLogger(MessageController.class); + + + public MessageController(SimpMessagingTemplate simpMessagingTemplate) { + this.simpMessagingTemplate = simpMessagingTemplate; + } + + @MessageMapping("/send") + @SendTo("/topic/messages") + public Message sendMessage(Message message) { + log.info("Received message: {}", message.getContent()); + // 处理接收到的消息 + message.setContent("Echo: " + message.getContent()); + return message; + } + + @PostMapping("/sendMessage") + public String test() { + Message testMessage = new Message("Hello from server!"); + simpMessagingTemplate.convertAndSend("/topic/messages", testMessage); + return "success"; + } + + @PostMapping("/testMessages") + public String test2() { + Message testMessage = new Message("Hello from server2!"); + simpMessagingTemplate.convertAndSend("/topic/testMessages", testMessage); + return "success"; + } + +} \ No newline at end of file diff --git a/ws-begin/src/main/java/org/example/model/Message.java b/ws-begin/src/main/java/org/example/model/Message.java new file mode 100644 index 0000000..9bdf3e1 --- /dev/null +++ b/ws-begin/src/main/java/org/example/model/Message.java @@ -0,0 +1,21 @@ +package org.example.model; + +public class Message { + private String content; + + public Message() { + } + + public Message(String content) { + this.content = content; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + +} \ No newline at end of file diff --git a/ws-begin/src/main/resources/application.yml b/ws-begin/src/main/resources/application.yml new file mode 100644 index 0000000..3b6d4e7 --- /dev/null +++ b/ws-begin/src/main/resources/application.yml @@ -0,0 +1,12 @@ +server: + port: 8080 + +spring: + application: + name: websocket-stomp-app + +logging: + level: + org.example: INFO + org.springframework.web.socket: DEBUG + org.springframework.messaging: DEBUG \ No newline at end of file diff --git a/ws-begin/src/main/resources/static/index.html b/ws-begin/src/main/resources/static/index.html new file mode 100644 index 0000000..6b9924f --- /dev/null +++ b/ws-begin/src/main/resources/static/index.html @@ -0,0 +1,258 @@ + + + + + + WebSocket STOMP 消息应用 + + + +
+

WebSocket STOMP 消息应用

+ +
+ + +
+ +
+ +
+
+
+ + +
+ +
+
+ + + + + + \ No newline at end of file