ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Spring 系列 (11) - Springboot+WebSocket 实现发送 JSON 消息实例 (二)

2022-05-20 22:04:36  阅读:282  来源: 互联网

标签:11 return Springboot Spring stomp springframework import org public


STOMP 即 Simple (or Streaming) Text Orientated Messaging Protocol,简单(流)文本定向消息协议,它提供了一个可互操作的连接格式,允许 STOMP 客户端与任意 STOMP 消息代理(Broker)进行交互。

本文使用 STOMP 来实现发送 JSON 消息实例。


1. 开发环境

    Windows版本:Windows 10 Home (20H2)   
    IntelliJ IDEA (https://www.jetbrains.com/idea/download/):Community Edition for Windows 2020.1.4
    Apache Maven (https://maven.apache.org/):3.8.1

    注:Spring 开发环境的搭建,可以参考 “ Spring基础知识(1)- Spring简介、Spring体系结构和开发环境配置 ”。


2. 创建 Spring Boot 基础项目

    项目实例名称:SpringbootExample11
    Spring Boot 版本:2.6.6

    创建步骤:

        (1) 创建 Maven 项目实例 SpringbootExample11;
        (2) Spring Boot Web 配置;
        (3) 导入 Thymeleaf 依赖包;
        (4) 配置 jQuery;
        
    具体操作请参考 “Spring 系列 (2) - 在 Spring Boot 项目里使用 Thymeleaf、JQuery+Bootstrap 和国际化” 里的项目实例 SpringbootExample02,文末包含如何使用 spring-boot-maven-plugin 插件运行打包的内容。

    SpringbootExample11 和 SpringbootExample02 相比,SpringbootExample11 不配置 Bootstrap、模版文件(templates/*.html)和国际化。


3. 配置 Security

    1) 修改 pom.xml,导入 Security 依赖包

 1         <project ... >
 2             ...
 3             <dependencies>
 4                 ...
 5 
 6                 <!-- Spring security -->
 7                 <dependency>
 8                     <groupId>org.springframework.boot</groupId>
 9                     <artifactId>spring-boot-starter-security</artifactId>
10                 </dependency>
11 
12                 ...
13             </dependencies>
14 
15             ...
16         </project>


        在IDE中项目列表 -> SpringbootExample11 -> 点击鼠标右键 -> Maven -> Reload Project

     2) 修改 src/main/resources/application.properties 文件,添加如下配置

        # security
        spring.security.user.name=admin
        spring.security.user.password=123456
        spring.security.user.roles=admin

        运行并访问 http://localhost:9090/test,自动跳转到 http://localhost:9090/login (Spring security 的默认页面),输入上面的用户名和密码登录,登录后跳转到 http://localhost:9090/test。


4. 配置 STOMP

    1) 修改 pom.xml,导入 WebSocket 依赖包

 1        <project ... >
 2             ...
 3             <dependencies>
 4                 ...
 5 
 6                 <dependency>
 7                     <groupId>org.springframework.boot</groupId>
 8                     <artifactId>spring-boot-starter-websocket</artifactId>
 9                 </dependency>
10 
11                 ...
12             </dependencies>
13 
14             ...
15         </project>


        在IDE中项目列表 -> SpringbootExample11 -> 点击鼠标右键 -> Maven -> Reload Project

    2) 创建 src/main/java/com/example/ws/WSStompConfig.java 文件

 1         package com.example.ws;
 2 
 3         import org.springframework.beans.factory.annotation.Autowired;
 4         import org.springframework.context.annotation.Configuration;
 5         import org.springframework.messaging.simp.config.MessageBrokerRegistry;
 6         import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
 7         import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
 8         import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
 9 
10         @Configuration
11         @EnableWebSocketMessageBroker
12         public class WSStompConfig implements WebSocketMessageBrokerConfigurer {
13             @Autowired
14             private WSStompInterceptor wsStompInterceptor;
15 
16             @Override
17             public void registerStompEndpoints(StompEndpointRegistry registry) {
18                 // 配置客户端尝试连接地址
19                 registry.addEndpoint("/websocket")    // 设置连接节点,http://hostname:port/websocket
20                         //.setHandshakeHandler()               // 握手处理,主要是连接的时候认证获取其他数据验证等
21                         .addInterceptors(wsStompInterceptor)   // 设置握手拦截器
22                         .setAllowedOriginPatterns("*")         // 配置跨域, 不能用 setAllowedOrigins("*")
23                         .withSockJS();                         // 开启 sockJS 支持,这里可以对不支持 stomp 的浏览器进行兼容
24             }
25 
26             @Override
27             public void configureMessageBroker(MessageBrokerRegistry registry) {
28                 // 这里使用的是内存模式,生产环境可以使用 RabbitMQ 或者其他 MQ。
29                 // 点对点应配置一个 /queue 消息代理,广播式应配置一个 /topic 消息代理
30                 registry.enableSimpleBroker("/topic", "/queue");
31 
32                 // 客户端向服务端发送消息需有 /app 前缀
33                 registry.setApplicationDestinationPrefixes("/app");
34 
35                 // 指定用户发送(一对一)的前缀 /user/
36                 registry.setUserDestinationPrefix("/user/");
37             }
38         }


    3) 创建 src/main/java/com/example/ws/WSStompInterceptor.java 文件

 1         package com.example.ws;
 2 
 3         import java.util.Map;
 4 
 5         import org.springframework.stereotype.Component;
 6         import org.springframework.http.server.ServerHttpRequest;
 7         import org.springframework.http.server.ServerHttpResponse;
 8         import org.springframework.web.socket.WebSocketHandler;
 9         import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
10 
11         @Component
12         public class WSStompInterceptor extends HttpSessionHandshakeInterceptor {
13             @Override
14             public boolean beforeHandshake(ServerHttpRequest request,
15                                         ServerHttpResponse response,
16                                         WebSocketHandler wsHandler,
17                                         Map<String, Object> attributes) throws Exception {
18                 return super.beforeHandshake(request, response, wsHandler, attributes);
19             }
20 
21             @Override
22             public void afterHandshake(ServerHttpRequest request,
23                                     ServerHttpResponse response,
24                                     WebSocketHandler wsHandler,
25                                     Exception ex) {
26                 super.afterHandshake(request, response, wsHandler, ex);
27             }
28 
29         }


    4) 创建 src/main/java/com/example/ws/WSMessage.java 文件

 1         package com.example.ws;
 2 
 3         public class WSMessage {
 4             private String content;
 5 
 6             public String getContent() {
 7                 return content;
 8             }
 9 
10             public void setContent(String content) {
11                 this.content = content;
12             }
13         }


    5) 创建 src/main/java/com/example/ws/WSStompHandler.java 文件

 1         package com.example.ws;
 2 
 3         import java.security.Principal;
 4 
 5         import org.springframework.beans.factory.annotation.Autowired;
 6         import org.springframework.web.bind.annotation.RestController;
 7         import org.springframework.messaging.simp.SimpMessagingTemplate;
 8         import org.springframework.messaging.simp.annotation.SubscribeMapping;
 9         import org.springframework.messaging.handler.annotation.DestinationVariable;
10         import org.springframework.messaging.handler.annotation.MessageMapping;
11         import org.springframework.messaging.handler.annotation.SendTo;
12 
13         @RestController
14         public class WSStompHandler {
15             @Autowired
16             private SimpMessagingTemplate simpMessagingTemplate;
17 
18             // Broadcast
19             @MessageMapping("/broadcast")
20             @SendTo("/topic/broadcast")
21             public WSMessage broadcast(WSMessage requestMsg) {
22                 // 这里是有 return,如果不写 @SendTo 默认和 /topic/broadcast 一样
23 
24                 WSMessage responseMsg = new WSMessage();
25                 responseMsg.setContent(requestMsg.getContent() + " - from server (Broadcast)");
26 
27                 return responseMsg;
28             }
29 
30             // User
31             @MessageMapping("/one")
32             //@SendToUser("/queue/one") 如果存在 return, 可以使用这种方式
33             public void one(WSMessage requestMsg, Principal principal) {
34                 // 注意为什么使用 queue,主要目的是为了区分广播和队列的方式。实际采用 topic,也没有关系。但是为了好理解
35 
36                 WSMessage responseMsg = new WSMessage();
37                 responseMsg.setContent(requestMsg.getContent() + " - from server (User)");
38 
39                 simpMessagingTemplate.convertAndSendToUser(principal.getName(), "/queue/one", responseMsg);
40             }
41 
42             // Subscribe
43             @SubscribeMapping("/subscribe/{id}")
44             public WSMessage subscribe(@DestinationVariable Long id) {
45 
46                 WSMessage responseMsg = new WSMessage();
47                 responseMsg.setContent("Subscribe success - from server (Subscribe)");
48 
49                 return responseMsg;
50             }
51 
52         }

 

  注:WSStompHandler 是 @RestController 注解修饰的类,或许定义成 WSStompController 更符合习惯,这里暂时作为 Handler 来定义。


5. 配置 SockJS、StompJS

    SockJS: https://github.com/sockjs/sockjs-client/tree/main/dist
    StomJS:https://github.com/jmesnil/stomp-websocket/tree/master/lib
    
    本文使用 sockjs.min.js(1.6.0) 和 stomp.min.js(2.3.4),两者放到 src/main/resources/static/lib/ 目录下。
    
    目录结构如下

      static
        |
        |- lib
            |- jquery
            |     |- jquery-3.6.0.min.js
            |
            |- sockjs.min.js
            |- sockjs.min.js.map
            |- stomp.min.js


6. 测试实例 (Web 模式)

    1) 创建 src/main/resources/templates/stomp_client.html 文件

  1         <html lang="en" xmlns:th="http://www.thymeleaf.org">
  2         <head>
  3             <meta charset="UTF-8">
  4             <title>STOMP Client</title>
  5             <script language="javascript" th:src="@{/lib/jquery/jquery-3.6.0.min.js}"></script>
  6             <script language="javascript" th:src="@{/lib/sockjs.min.js}"></script>
  7             <script language="javascript" th:src="@{/lib/stomp.min.js}"></script>
  8         </head>
  9         <body>
 10             <h4>WebSocket STOMP - Client</h4>
 11             <hr>
 12 
 13             <p>
 14                 <label>WebSocket Url:</label><br>
 15                 <input id="url" type="text" th:value="@{/websocket}" value="http://localhost:9090/websocket" style="width: 240px;"><br><br>
 16                 <button id="connect">Connect</button>&nbsp;<button id="disconnect" disabled="disabled">Disconnect</button><br><br>
 17 
 18                 <label>Subscribed Message: </label><br>
 19                 <input id="subscribeMsg" type="text" disabled="disabled" style="width: 50%;"><br><br>
 20             </p>
 21             <hr>
 22             <p>
 23                 <label>Broadcast: </label><br>
 24                 <input id="broadcastText" type="text" style="width: 50%;" value="broadcast message"><button id="broadcastButton">Send</button><br><br>
 25 
 26                 <label>Return Message: </label><br>
 27                 <input id="broadcastMsg" type="text" disabled="disabled" style="width: 50%;"><br><br>
 28             </p>
 29             <hr>
 30             <p>
 31                 <label>User: </label><br>
 32                 <input id="userText" type="text" style="width: 50%;" value="user message"><button id="userButton">Send</button><br><br>
 33 
 34                 <label>Return Message: </label><br>
 35                 <input id="userMsg" type="text" disabled="disabled" style="width: 50%;"><br><br>
 36             </p>
 37             <hr>
 38             <p>
 39                 <label>App: </label><br>
 40                 <input id="appText" type="text" style="width: 50%;" value="app message"><button id="appButton">Send</button><br><br>
 41 
 42                 <label>Return Message: </label><br>
 43                 <input id="appMsg" type="text" disabled="disabled" style="width: 50%;"><br><br>
 44             </p>
 45 
 46             <p>&nbsp;</p>
 47 
 48             <script type="text/javascript">
 49                 var stomp = null;
 50 
 51                 $(document).ready(function() {
 52 
 53                     $("#connect").click(function () {
 54 
 55                         var url = $("#url").val();
 56                         if (url == "") {
 57                             alert("Please enter url");
 58                             $("#url").focus();
 59                             return;
 60                         }
 61 
 62                         var socket = new SockJS(url);
 63                         stomp = Stomp.over(socket);
 64 
 65                         // Connect
 66                         stomp.connect({}, function (frame) {
 67                             // Subscribe broadcast
 68                             stomp.subscribe("/topic/broadcast", function (res) {
 69                                 $("#broadcastMsg").val(res.body);
 70                             });
 71 
 72                             // Subscribe
 73                             stomp.subscribe("/app/subscribe/1", function (res) {
 74                                 $("#subscribeMsg").val(res.body);
 75                             });
 76 
 77                             // User
 78                             stomp.subscribe("/user/queue/one", function (res) {
 79                                 $("#userMsg").val(res.body);
 80                             });
 81 
 82                             // App
 83                             stomp.subscribe("/topic/app", function (res) {
 84                                 $("#appMsg").val(res.body);
 85                             });
 86                             setConnect(true);
 87                         });
 88                     });
 89 
 90                     $("#disconnect").click(function () {
 91                         if (stomp != null) {
 92                             stomp.disconnect();
 93                             stomp = null;
 94                         }
 95                         setConnect(false);
 96                     });
 97 
 98                     // Send broadcast message
 99                     $("#broadcastButton").click(function () {
100                         if (stomp == null) {
101                             alert("Please connect to server");
102                             return;
103                         }
104                         var msg = $("#broadcastText").val();
105                         if (msg == '') {
106                             alert("Please enter broadcast text");
107                             $("#broadcastText").focus();
108                             return;
109                         }
110                         stomp.send("/app/broadcast", {}, JSON.stringify({"content": msg}))
111                     });
112 
113                     // Send user message
114                     $("#userButton").click(function () {
115                         if (stomp == null) {
116                             alert("Please connect to server");
117                             return;
118                         }
119                         var msg = $("#userText").val();
120                         if (msg == '') {
121                             alert("Please enter user text");
122                             $("#userText").focus();
123                             return;
124                         }
125                         stomp.send("/app/one", {}, JSON.stringify({"content": msg}))
126                     });
127 
128                     // Send app message
129                     $("#appButton").click(function () {
130                         if (stomp == null) {
131                             alert("Please connect to server");
132                             return;
133                         }
134                         var msg = $("#appText").val();
135                         if (msg == '') {
136                             alert("Please enter app text");
137                             $("#appText").focus();
138                             return;
139                         }
140                         stomp.send("/topic/app", {}, JSON.stringify({"content": msg}))
141                     });
142 
143                 });
144 
145                 // Set buttons
146                 function setConnect(connectStatus) {
147                     $("#connect").attr("disabled", connectStatus);
148                     $("#disconnect").attr("disabled", !connectStatus);
149                 }
150 
151             </script>
152         </body>
153         </html>


    2) 修改 src/main/java/com/example/controller/IndexController.java 文件

 1         package com.example.controller;
 2 
 3         import org.springframework.stereotype.Controller;
 4         import org.springframework.web.bind.annotation.RequestMapping;
 5         import org.springframework.web.bind.annotation.ResponseBody;
 6 
 7         @Controller
 8         public class IndexController {
 9             @ResponseBody
10             @RequestMapping("/test")
11             public String test() {
12                 return "Test Page";
13             }
14 
15             @RequestMapping("/stomp/client")
16             public String stompClient() {
17                 return "stomp_client";
18             }
19 
20         }


    访问 http://localhost:9090/stomp/client


标签:11,return,Springboot,Spring,stomp,springframework,import,org,public
来源: https://www.cnblogs.com/tkuang/p/16293821.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有