diff --git a/lab-25/lab-websocket-25-03/pom.xml b/lab-25/lab-websocket-25-03/pom.xml
new file mode 100644
index 00000000..4d95a7ed
--- /dev/null
+++ b/lab-25/lab-websocket-25-03/pom.xml
@@ -0,0 +1,31 @@
+
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.1.10.RELEASE
+
+
+ 4.0.0
+
+ lab-websocket-25-03
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-websocket
+
+
+
+
+ com.alibaba
+ fastjson
+ 1.2.62
+
+
+
+
+
diff --git a/lab-25/lab-websocket-25-03/src/main/java/cn/iocoder/springboot/lab25/springwebsocket/Application.java b/lab-25/lab-websocket-25-03/src/main/java/cn/iocoder/springboot/lab25/springwebsocket/Application.java
new file mode 100644
index 00000000..a6f84bbf
--- /dev/null
+++ b/lab-25/lab-websocket-25-03/src/main/java/cn/iocoder/springboot/lab25/springwebsocket/Application.java
@@ -0,0 +1,13 @@
+package cn.iocoder.springboot.lab25.springwebsocket;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class Application {
+
+ public static void main(String[] args) {
+ SpringApplication.run(Application.class, args);
+ }
+
+}
diff --git a/lab-25/lab-websocket-25-03/src/main/java/cn/iocoder/springboot/lab25/springwebsocket/config/WebSocketConfiguration.java b/lab-25/lab-websocket-25-03/src/main/java/cn/iocoder/springboot/lab25/springwebsocket/config/WebSocketConfiguration.java
new file mode 100644
index 00000000..2adf15a7
--- /dev/null
+++ b/lab-25/lab-websocket-25-03/src/main/java/cn/iocoder/springboot/lab25/springwebsocket/config/WebSocketConfiguration.java
@@ -0,0 +1,51 @@
+package cn.iocoder.springboot.lab25.springwebsocket.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 // 开启 Spring WebSocket 对 Stomp 的支持
+public class WebSocketConfiguration implements WebSocketMessageBrokerConfigurer {
+
+ @Override
+ public void configureMessageBroker(MessageBrokerRegistry registry) {
+ registry.enableSimpleBroker("/topic");
+ registry.setApplicationDestinationPrefixes("/app");
+ }
+
+ @Override
+ public void registerStompEndpoints(StompEndpointRegistry registry) {
+// registry.addEndpoint("/send_to_all")
+// .setAllowedOrigins("*")
+// .withSockJS();
+ registry.addEndpoint("/")
+ .setAllowedOrigins("*")
+ .withSockJS();
+
+// RequestUpgradeStrategy upgradeStrategy = new TomcatRequestUpgradeStrategy();
+// registry.addEndpoint("/")
+// .setHandshakeHandler(new DefaultHandshakeHandler(upgradeStrategy))
+// .setAllowedOrigins("*");
+ }
+
+// @Override
+// public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
+// registry.addHandler(this.webSocketHandler(), "/") // 配置处理器
+// .addInterceptors(new DemoWebSocketShakeInterceptor()) // 配置拦截器
+// .setAllowedOrigins("*"); // 解决跨域问题
+// }
+
+// @Bean
+// public DemoWebSocketHandler webSocketHandler() {
+// return new DemoWebSocketHandler();
+// }
+
+// @Bean
+// public DemoWebSocketShakeInterceptor webSocketShakeInterceptor() {
+// return new DemoWebSocketShakeInterceptor();
+// }
+
+}
diff --git a/lab-25/lab-websocket-25-03/src/main/java/cn/iocoder/springboot/lab25/springwebsocket/controller/SendController.java b/lab-25/lab-websocket-25-03/src/main/java/cn/iocoder/springboot/lab25/springwebsocket/controller/SendController.java
new file mode 100644
index 00000000..769b15d2
--- /dev/null
+++ b/lab-25/lab-websocket-25-03/src/main/java/cn/iocoder/springboot/lab25/springwebsocket/controller/SendController.java
@@ -0,0 +1,37 @@
+package cn.iocoder.springboot.lab25.springwebsocket.controller;
+
+import cn.iocoder.springboot.lab25.springwebsocket.message.SendToAllRequest;
+import cn.iocoder.springboot.lab25.springwebsocket.message.SendToUserRequest;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.messaging.handler.annotation.MessageMapping;
+import org.springframework.messaging.handler.annotation.SendTo;
+import org.springframework.stereotype.Controller;
+
+@Controller
+public class SendController {
+
+ private Logger logger = LoggerFactory.getLogger(getClass());
+
+ @MessageMapping("/send_to_all")
+ @SendTo(value = "/topic/send_to_all")
+ public SendToUserRequest sendToAll(SendToAllRequest message) {
+ logger.info("[sendToAll][SendToAllRequest({})]", message);
+// // 创建转发的消息
+// SendToUserRequest sendToUserRequest = new SendToUserRequest().setMsgId(message.getMsgId())
+// .setContent(message.getContent());
+// // 广播发送
+// WebSocketUtil.broadcast(SendToUserRequest.TYPE, sendToUserRequest);
+
+ // 这里,假装直接成功
+// return new SendResponse().setMsgId(message.getMsgId()).setCode(0);
+ return new SendToUserRequest().setMsgId(message.getMsgId())
+ .setContent(message.getContent());
+ }
+
+// @SubscribeMapping("/topic/send_to_all")
+// public void subSendToAll(SendToUserRequest message) {
+// logger.info("[subSendToAll][SendToUserRequest({})]", message);
+// }
+
+}
diff --git a/lab-25/lab-websocket-25-03/src/main/java/cn/iocoder/springboot/lab25/springwebsocket/message/AuthRequest.java b/lab-25/lab-websocket-25-03/src/main/java/cn/iocoder/springboot/lab25/springwebsocket/message/AuthRequest.java
new file mode 100644
index 00000000..29b8bf11
--- /dev/null
+++ b/lab-25/lab-websocket-25-03/src/main/java/cn/iocoder/springboot/lab25/springwebsocket/message/AuthRequest.java
@@ -0,0 +1,24 @@
+package cn.iocoder.springboot.lab25.springwebsocket.message;
+
+/**
+ * 用户认证请求
+ */
+public class AuthRequest implements Message {
+
+ public static final String TYPE = "AUTH_REQUEST";
+
+ /**
+ * 认证 Token
+ */
+ private String accessToken;
+
+ public String getAccessToken() {
+ return accessToken;
+ }
+
+ public AuthRequest setAccessToken(String accessToken) {
+ this.accessToken = accessToken;
+ return this;
+ }
+
+}
diff --git a/lab-25/lab-websocket-25-03/src/main/java/cn/iocoder/springboot/lab25/springwebsocket/message/AuthResponse.java b/lab-25/lab-websocket-25-03/src/main/java/cn/iocoder/springboot/lab25/springwebsocket/message/AuthResponse.java
new file mode 100644
index 00000000..cfa4fbda
--- /dev/null
+++ b/lab-25/lab-websocket-25-03/src/main/java/cn/iocoder/springboot/lab25/springwebsocket/message/AuthResponse.java
@@ -0,0 +1,37 @@
+package cn.iocoder.springboot.lab25.springwebsocket.message;
+
+/**
+ * 用户认证响应
+ */
+public class AuthResponse implements Message {
+
+ public static final String TYPE = "AUTH_RESPONSE";
+
+ /**
+ * 响应状态码
+ */
+ private Integer code;
+ /**
+ * 响应提示
+ */
+ private String message;
+
+ public Integer getCode() {
+ return code;
+ }
+
+ public AuthResponse setCode(Integer code) {
+ this.code = code;
+ return this;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public AuthResponse setMessage(String message) {
+ this.message = message;
+ return this;
+ }
+
+}
diff --git a/lab-25/lab-websocket-25-03/src/main/java/cn/iocoder/springboot/lab25/springwebsocket/message/Message.java b/lab-25/lab-websocket-25-03/src/main/java/cn/iocoder/springboot/lab25/springwebsocket/message/Message.java
new file mode 100644
index 00000000..974b146a
--- /dev/null
+++ b/lab-25/lab-websocket-25-03/src/main/java/cn/iocoder/springboot/lab25/springwebsocket/message/Message.java
@@ -0,0 +1,7 @@
+package cn.iocoder.springboot.lab25.springwebsocket.message;
+
+/**
+ * 基础消息体
+ */
+public interface Message {
+}
diff --git a/lab-25/lab-websocket-25-03/src/main/java/cn/iocoder/springboot/lab25/springwebsocket/message/SendResponse.java b/lab-25/lab-websocket-25-03/src/main/java/cn/iocoder/springboot/lab25/springwebsocket/message/SendResponse.java
new file mode 100644
index 00000000..c695cf83
--- /dev/null
+++ b/lab-25/lab-websocket-25-03/src/main/java/cn/iocoder/springboot/lab25/springwebsocket/message/SendResponse.java
@@ -0,0 +1,50 @@
+package cn.iocoder.springboot.lab25.springwebsocket.message;
+
+/**
+ * 发送消息响应结果的 Message
+ */
+public class SendResponse implements Message {
+
+ public static final String TYPE = "SEND_RESPONSE";
+
+ /**
+ * 消息编号
+ */
+ private String msgId;
+ /**
+ * 响应状态码
+ */
+ private Integer code;
+ /**
+ * 响应提示
+ */
+ private String message;
+
+ public String getMsgId() {
+ return msgId;
+ }
+
+ public SendResponse setMsgId(String msgId) {
+ this.msgId = msgId;
+ return this;
+ }
+
+ public Integer getCode() {
+ return code;
+ }
+
+ public SendResponse setCode(Integer code) {
+ this.code = code;
+ return this;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public SendResponse setMessage(String message) {
+ this.message = message;
+ return this;
+ }
+
+}
diff --git a/lab-25/lab-websocket-25-03/src/main/java/cn/iocoder/springboot/lab25/springwebsocket/message/SendToAllRequest.java b/lab-25/lab-websocket-25-03/src/main/java/cn/iocoder/springboot/lab25/springwebsocket/message/SendToAllRequest.java
new file mode 100644
index 00000000..f75e0f9b
--- /dev/null
+++ b/lab-25/lab-websocket-25-03/src/main/java/cn/iocoder/springboot/lab25/springwebsocket/message/SendToAllRequest.java
@@ -0,0 +1,37 @@
+package cn.iocoder.springboot.lab25.springwebsocket.message;
+
+/**
+ * 发送给所有人的群聊消息的 Message
+ */
+public class SendToAllRequest implements Message {
+
+ public static final String TYPE = "SEND_TO_ALL_REQUEST";
+
+ /**
+ * 消息编号
+ */
+ private String msgId;
+ /**
+ * 内容
+ */
+ private String content;
+
+ public String getContent() {
+ return content;
+ }
+
+ public SendToAllRequest setContent(String content) {
+ this.content = content;
+ return this;
+ }
+
+ public String getMsgId() {
+ return msgId;
+ }
+
+ public SendToAllRequest setMsgId(String msgId) {
+ this.msgId = msgId;
+ return this;
+ }
+
+}
diff --git a/lab-25/lab-websocket-25-03/src/main/java/cn/iocoder/springboot/lab25/springwebsocket/message/SendToOneRequest.java b/lab-25/lab-websocket-25-03/src/main/java/cn/iocoder/springboot/lab25/springwebsocket/message/SendToOneRequest.java
new file mode 100644
index 00000000..8daa74ca
--- /dev/null
+++ b/lab-25/lab-websocket-25-03/src/main/java/cn/iocoder/springboot/lab25/springwebsocket/message/SendToOneRequest.java
@@ -0,0 +1,49 @@
+package cn.iocoder.springboot.lab25.springwebsocket.message;
+
+/**
+ * 发送给指定人的私聊消息的 Message
+ */
+public class SendToOneRequest implements Message {
+
+ public static final String TYPE = "SEND_TO_ONE_REQUEST";
+
+ /**
+ * 发送给的用户
+ */
+ private String toUser;
+ /**
+ * 消息编号
+ */
+ private String msgId;
+ /**
+ * 内容
+ */
+ private String content;
+
+ public String getToUser() {
+ return toUser;
+ }
+
+ public SendToOneRequest setToUser(String toUser) {
+ this.toUser = toUser;
+ return this;
+ }
+
+ public String getMsgId() {
+ return msgId;
+ }
+
+ public SendToOneRequest setMsgId(String msgId) {
+ this.msgId = msgId;
+ return this;
+ }
+
+ public String getContent() {
+ return content;
+ }
+
+ public SendToOneRequest setContent(String content) {
+ this.content = content;
+ return this;
+ }
+}
diff --git a/lab-25/lab-websocket-25-03/src/main/java/cn/iocoder/springboot/lab25/springwebsocket/message/SendToUserRequest.java b/lab-25/lab-websocket-25-03/src/main/java/cn/iocoder/springboot/lab25/springwebsocket/message/SendToUserRequest.java
new file mode 100644
index 00000000..67ad15c0
--- /dev/null
+++ b/lab-25/lab-websocket-25-03/src/main/java/cn/iocoder/springboot/lab25/springwebsocket/message/SendToUserRequest.java
@@ -0,0 +1,37 @@
+package cn.iocoder.springboot.lab25.springwebsocket.message;
+
+/**
+ * 发送消息给一个用户的 Message
+ */
+public class SendToUserRequest implements Message {
+
+ public static final String TYPE = "SEND_TO_USER_REQUEST";
+
+ /**
+ * 消息编号
+ */
+ private String msgId;
+ /**
+ * 内容
+ */
+ private String content;
+
+ public String getMsgId() {
+ return msgId;
+ }
+
+ public SendToUserRequest setMsgId(String msgId) {
+ this.msgId = msgId;
+ return this;
+ }
+
+ public String getContent() {
+ return content;
+ }
+
+ public SendToUserRequest setContent(String content) {
+ this.content = content;
+ return this;
+ }
+
+}
diff --git a/lab-25/lab-websocket-25-03/src/main/java/cn/iocoder/springboot/lab25/springwebsocket/message/UserJoinNoticeRequest.java b/lab-25/lab-websocket-25-03/src/main/java/cn/iocoder/springboot/lab25/springwebsocket/message/UserJoinNoticeRequest.java
new file mode 100644
index 00000000..e1e923af
--- /dev/null
+++ b/lab-25/lab-websocket-25-03/src/main/java/cn/iocoder/springboot/lab25/springwebsocket/message/UserJoinNoticeRequest.java
@@ -0,0 +1,24 @@
+package cn.iocoder.springboot.lab25.springwebsocket.message;
+
+/**
+ * 用户加入群聊的通知 Message
+ */
+public class UserJoinNoticeRequest implements Message {
+
+ public static final String TYPE = "USER_JOIN_NOTICE_REQUEST";
+
+ /**
+ * 昵称
+ */
+ private String nickname;
+
+ public String getNickname() {
+ return nickname;
+ }
+
+ public UserJoinNoticeRequest setNickname(String nickname) {
+ this.nickname = nickname;
+ return this;
+ }
+
+}
diff --git a/lab-25/lab-websocket-25-03/src/test/java/cn/iocoder/springboot/lab25/springwebsocket/Main.java b/lab-25/lab-websocket-25-03/src/test/java/cn/iocoder/springboot/lab25/springwebsocket/Main.java
new file mode 100644
index 00000000..bfc8c3c3
--- /dev/null
+++ b/lab-25/lab-websocket-25-03/src/test/java/cn/iocoder/springboot/lab25/springwebsocket/Main.java
@@ -0,0 +1,68 @@
+package cn.iocoder.springboot.lab25.springwebsocket;
+
+import cn.iocoder.springboot.lab25.springwebsocket.message.SendToAllRequest;
+import org.springframework.messaging.converter.MappingJackson2MessageConverter;
+import org.springframework.messaging.simp.stomp.StompCommand;
+import org.springframework.messaging.simp.stomp.StompHeaders;
+import org.springframework.messaging.simp.stomp.StompSession;
+import org.springframework.messaging.simp.stomp.StompSessionHandler;
+import org.springframework.web.socket.client.WebSocketClient;
+import org.springframework.web.socket.client.standard.StandardWebSocketClient;
+import org.springframework.web.socket.messaging.WebSocketStompClient;
+import org.springframework.web.socket.sockjs.client.SockJsClient;
+import org.springframework.web.socket.sockjs.client.Transport;
+import org.springframework.web.socket.sockjs.client.WebSocketTransport;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Scanner;
+import java.util.UUID;
+
+public class Main {
+
+ public static void main(String[] args) {
+ List transports = new ArrayList<>(1);
+ transports.add(new WebSocketTransport( new StandardWebSocketClient()) );
+ WebSocketClient transport = new SockJsClient(transports); // TODO 芋艿,参考 https://codeday.me/bug/20190115/521240.html 文章
+// WebSocketClient client = new StandardWebSocketClient();
+
+ WebSocketStompClient stompClient = new WebSocketStompClient(transport);
+ stompClient.setMessageConverter(new MappingJackson2MessageConverter());
+
+ StompSessionHandler sessionHandler = new StompSessionHandler() {
+
+ @Override
+ public void afterConnected(StompSession stompSession, StompHeaders stompHeaders) {
+// stompSession.subscribe("/topic/send_to_all", this);
+ stompSession.send("/app/send_to_all", new SendToAllRequest().setMsgId(UUID.randomUUID().toString())
+ .setContent("测试消息"));
+ }
+
+ @Override
+ public void handleException(StompSession stompSession, StompCommand stompCommand, StompHeaders stompHeaders, byte[] bytes, Throwable throwable) {
+ System.out.println();
+ }
+
+ @Override
+ public void handleTransportError(StompSession stompSession, Throwable throwable) {
+ System.out.println();
+ }
+
+ @Override
+ public Type getPayloadType(StompHeaders stompHeaders) {
+ return null;
+ }
+
+ @Override
+ public void handleFrame(StompHeaders stompHeaders, Object o) {
+ System.out.println();
+ }
+ };
+
+ stompClient.connect("ws://127.0.0.1:8080/", sessionHandler);
+
+ new Scanner(System.in).nextLine(); // Don't close immediately.
+ }
+
+}
diff --git a/lab-25/lab-websocket-25-03/target/classes/META-INF/lab-websocket-25-02.kotlin_module b/lab-25/lab-websocket-25-03/target/classes/META-INF/lab-websocket-25-02.kotlin_module
new file mode 100644
index 00000000..2983af70
Binary files /dev/null and b/lab-25/lab-websocket-25-03/target/classes/META-INF/lab-websocket-25-02.kotlin_module differ
diff --git a/lab-25/pom.xml b/lab-25/pom.xml
index da81aa31..54295ac6 100644
--- a/lab-25/pom.xml
+++ b/lab-25/pom.xml
@@ -14,6 +14,7 @@
lab-websocket-25-01
lab-websocket-25-02
+ lab-websocket-25-03