SpringBoot 学习笔记_整合 WebSocket —— 消息点对点发送
声明:
本次学习参考 《SpringBoot + Vue 开发实战》 · 王松(著) 一书。
本文的目的是记录我学习的过程和遇到的一些问题以及解决办法,其内容主要来源于原书。
如有侵权,请联系我删除
文章目录
SpringBoot 整合 WebSocket
SpringBoot 整合 WebSocket —— 消息点对点发送(单聊)
SpringBoot 对 WebSocket 提供了非常友好的支持,可以方便开发者在项目中快速集成 WebSocket 功能,实现单聊或者群聊。
-
创建 SpringBoot 项目,添加依赖
<!-- 添加 WebSocket 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <!-- 添加 webjars 依赖 --> <dependency> <groupId>org.webjars</groupId> <artifactId>webjars-locator-core</artifactId> </dependency> <!-- 添加 sockjs-client 依赖 --> <dependency> <groupId>org.webjars</groupId> <artifactId>sockjs-client</artifactId> <version>1.1.2</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>stomp-websocket</artifactId> <version>2.3.3</version> </dependency> <!-- 添加 jquery 依赖 --> <dependency> <groupId>org.webjars</groupId> <artifactId>jquery</artifactId> <version>3.3.1</version> </dependency>
既然要点位点单聊发送消息,就应该有用户的概念,因此还需要 Spring Security 依赖
<!-- 添加 Spring Security 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
-
配置 Spring Security
/** * Spring Security 配置 */ @Configuration public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Bean PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } /** * 创建两个用户。 * * 用户名 : admin 角色 :admin 密码 : 123 * 用户名 : sang 角色: user 密码: 123 * * @param auth * @throws Exception */ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("admin") .password("$2a$10$8nd6PQJkSYRcZJivePm3y.0/Bk3OO0mSOal5P2/oSPTGjjt8/KLra") .roles("admin") .and() .withUser("sang") .password("$2a$10$8nd6PQJkSYRcZJivePm3y.0/Bk3OO0mSOal5P2/oSPTGjjt8/KLra") .roles("user"); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest() .authenticated() .and() .formLogin() .permitAll(); } }
-
配置 WebSocket
/** * 自定义类 WebSocketConfig 继承自 WebSocketMessageBrokerConfigurer 进行 WebSocket 配置 */ @Configuration // @EnableWebSocketMessageBroker 注解开启 WebSocket 消息代理 @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void configureMessageBroker(MessageBrokerRegistry registry) { // 设置消息代理前缀 // 即如果消息前缀是 /topic 就会将消息转发给消息代理(broker),再由消息代理将消息广播给当前连接的客户端 // 在群发的基础上增加一个 broker 前缀 "/queue" 方便对群发消息和点对点消息进行管理 registry.enableSimpleBroker("/topic", "/queue"); // 配置一个或多个前缀,通过这些前缀过滤出需要被注解方法处理的消息 // 例如,前缀 /app 的 destination 可以通过 @MessageMapping 注解的方法处理 // 而其他 destination(/topic /queue) 将被直接交给 broker 处理 registry.setApplicationDestinationPrefixes("/app"); } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { // 定义一个前缀为 /chat 的 endPoint, 并开启 sockjs 支持,sockjs 可以解决浏览器对 WebSocket 的兼容性问题 // 客户端将通过这里配置的 URL 来建立 WebSocket 连接 registry.addEndpoint("/chat").withSockJS(); } }
-
定义实体类
public class Chat { private String to; private String from; private String content; /* Getter & Setter */ }
-
定义 Controller
@Controller public class GreetingController { /** * 群发消息,与之前相同,依然用 @SendTo 实现 */ // 用来接收 /app/hello 路径发送来的消息,在注解方法中对消息进行处理后,再将消息转发到 @SendTo 定义的路径上 @MessageMapping("/hello") // @SendTo 路径是一个前缀为 /topic 的路径,因此该消息将被交给消息代理 broker,再由 broker 进行广播 @SendTo("/topic/greetings") public Message greeting(Message message) throws Exception{ return message; } /** * 点对点发送消息则使用 SimpMessagingTemplate 来实现 */ @Autowired SimpMessagingTemplate messagingTemplate; // 表示处理来自 /app/chat 的消息 @MessageMapping("/chat") // 第一个参数 Principal 用来获取当前登录用户的信息,第二个参数为客户端发送来的消息 public void chat(Principal principal, Chat chat){ // 获取当前用户的用户名 String from = principal.getName(); // 设置给 chat 对象的 from 属性 chat.setFrom(from); // 将消息发送出去,发送目标就是 chat 对象的 to 属性。 // 该方法内部调用了 convertAndSend 方法,并对消息进行了处理 messagingTemplate.convertAndSendToUser(chat.getTo(), "/queue/chat", chat); } }
-
构建聊天页面
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>在线聊天</title> <script src="/webjars/jquery/jquery.min.js"></script> <script src="/webjars/sockjs-client/sockjs.min.js"></script> <script src="/webjars/stomp-websocket/stomp.min.js"></script> <script src="/chat.js"></script> </head> <body> <div id="chat"> <div id="chatsContent"></div> <div> 请输入聊天内容: <input type="text" id="content" placeholder="聊天内容"> 目标用户: <input type="text" id="to" placeholder="目标用户"> <button id="send">发送</button> </div> </div> </body> </html>
let stompClient = null; function connect() { let socket = new SockJS('/chat'); stompClient = Stomp.over(socket); stompClient.connect({}, frame => // 连接成功后,订阅的地址为 /user/queue/chat // 该地址比服务端配置多了 /user 前缀,这是因为 SimpMessagingTemplate 类中自动添加了路径前缀 stompClient.subscribe('/user/queue/chat', chat => showGreeting(JSON.parse(chat.body))) ) } function sendMsg() { // 聊天消息发送路径 /app/chat stompClient.send('/app/chat', {}, JSON.stringify({'content': $('#content').val(), 'to': $('#to').val()})); showGreeting({from: '我', content: $('#content').val()}) } function showGreeting(message) { $('#chatsContent').append(`<div>${message.from}:${message.content}</div>`) } $(function () { connect(); $('#send').click(() => sendMsg()); })