当前位置:   article > 正文

使用 Spring Boot + WebSocket + RabbitMQ 构建聊天应用程序

使用 Spring Boot + WebSocket + RabbitMQ 构建聊天应用程序
上一篇文章中,我们创建了一个 Spring Boot + WebSocket Hello World 示例。 在这篇文章中,我们将创建一个实时多用途聊天应用程序。
上一篇文章中,我们还看到了如何将 Spring Boot + RabbitMQ 应用程序部署到 Pivotal Cloud Foundry。我已经将我们正在创建的实时聊天应用程序托管到 Pivotal Cloud Foundry 并使用可以在 JavaInUse 聊天应用程序上查看演示。
JavaInUse 聊天应用程序演示

在本教程中,我们将使用 STOMP 协议。STOMP 是一个简单的面向文本的消息传递协议,我们的 UI 客户端(浏览器)使用它连接到企业消息代理。
客户端可以使用 SEND 或 SUBSCRIBE 命令发送或订阅消息以及描述消息内容和接收人的“destination”标头。
它定义了客户端和服务器与消息传递语义进行通信的协议。它没有定义任何实现细节,而是解决了一个易于实现的用于消息传递集成的有线协议。该协议与 HTTP 大体相似,并使用以下命令在 TCP 上运行:
  • CONNECT
  • SEND
  • SUBSCRIBE
  • UNSUBSCRIBE
  • BEGIN
  • COMMIT
  • ABORT
  • ACK
  • NACK
  • DISCONNECT
 

当使用 Spring 的 STOMP 支持时,Spring WebSocket 应用程序充当客户端的 STOMP 代理。消息被路由到@Controller 消息处理方法或一个简单的内存代理,该代理跟踪订阅并将消息广播给订阅用户。
您还可以将 Spring 配置为使用专用的 STOMP 代理(例如 RabbitMQ、ActiveMQ 等)来实际广播消息。在这种情况下,Spring 维护与代理的 TCP 连接,将消息中继给它,并将消息从它向下传递到连接的 WebSocket 客户端。

 

视频

本教程在下面的 Youtube 视频中进行了解释。
 

让我们开始-

创建 Spring Boot WebSocket 应用程序-

该项目将如下 -



定义 pom.xml 如下 - 添加spring-boot-starter-websocketspring-boot-starter-amqp依赖项。
 
 
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  4. <modelVersion>4.0.0</modelVersion>
  5. <groupId>com.example</groupId>
  6. <artifactId>spring-boot-websocket-chat</artifactId>
  7. <version>0.0.1-SNAPSHOT</version>
  8. <packaging>jar</packaging>
  9. <name>spring-boot-websocket-chat</name>
  10. <parent>
  11. <groupId>org.springframework.boot</groupId>
  12. <artifactId>spring-boot-starter-parent</artifactId>
  13. <version>2.0.0.RELEASE</version>
  14. <relativePath /> <!-- lookup parent from repository -->
  15. </parent>
  16. <properties>
  17. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  18. <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  19. <java.version>1.8</java.version>
  20. </properties>
  21. <dependencies>
  22. <dependency>
  23. <groupId>org.springframework.boot</groupId>
  24. <artifactId>spring-boot-starter-websocket</artifactId>
  25. </dependency>
  26. <dependency>
  27. <groupId>org.springframework.boot</groupId>
  28. <artifactId>spring-boot-starter-amqp</artifactId>
  29. </dependency>
  30. <dependency>
  31. <groupId>org.springframework.boot</groupId>
  32. <artifactId>spring-boot-starter-reactor-netty</artifactId>
  33. </dependency>
  34. </dependencies>
  35. </project>
  
定义域类 WebSocketChatMessage 如下-
  1. package com.javainuse.domain;
  2. public class WebSocketChatMessage {
  3. private String type;
  4. private String content;
  5. private String sender;
  6. public String getType() {
  7. return type;
  8. }
  9. public void setType(String type) {
  10. this.type = type;
  11. }
  12. public String getContent() {
  13. return content;
  14. }
  15. public void setContent(String content) {
  16. this.content = content;
  17. }
  18. public String getSender() {
  19. return sender;
  20. }
  21. public void setSender(String sender) {
  22. this.sender = sender;
  23. }
  24. }
定义 WebSocket 配置类。
@Configuration告诉它是一个 Spring 配置类。 @EnableWebSocketMessageBroker启用由消息代理支持的 WebSocket 消息处理。在这里,我们使用 STOMP 作为消息代理。configureMessageBroker() 方法使rabbitmq 消息代理能够将消息传送回客户端,目的地为前缀为“/topic”和“/queue”。
同样在这里,我们配置了所有带有“/app”前缀的消息将被路由到控制器类中的@MessageMapping-annotated 方法。
例如,“/app/chat.sendMessage”是 WebSocketController.sendMessage() 方法映射到处理的端点。

  1. package com.javainuse.config;
  2. import org.springframework.context.annotation.Configuration;
  3. import org.springframework.messaging.simp.config.MessageBrokerRegistry;
  4. import org.springframework.web.socket.config.annotation.*;
  5. @Configuration
  6. @EnableWebSocketMessageBroker
  7. public class WebSocketChatConfig implements WebSocketMessageBrokerConfigurer {
  8. @Override
  9. public void registerStompEndpoints(StompEndpointRegistry registry) {
  10. registry.addEndpoint("/websocketApp").withSockJS();
  11. }
  12. @Override
  13. public void configureMessageBroker(MessageBrokerRegistry registry) {
  14. registry.setApplicationDestinationPrefixes("/app");
  15. registry.enableStompBrokerRelay("/topic").setRelayHost("localhost").setRelayPort(61613).setClientLogin("guest")
  16. .setClientPasscode("guest");
  17. }
  18. }
定义 WebSocker 监听器类。此类监听诸如新用户加入聊天或用户离开聊天等事件。
  1. package com.javainuse.config;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. import org.springframework.context.event.EventListener;
  4. import org.springframework.messaging.simp.SimpMessageSendingOperations;
  5. import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
  6. import org.springframework.stereotype.Component;
  7. import org.springframework.web.socket.messaging.SessionConnectedEvent;
  8. import org.springframework.web.socket.messaging.SessionDisconnectEvent;
  9. import com.javainuse.domain.WebSocketChatMessage;
  10. @Component
  11. public class WebSocketChatEventListener {
  12. @Autowired
  13. private SimpMessageSendingOperations messagingTemplate;
  14. @EventListener
  15. public void handleWebSocketConnectListener(SessionConnectedEvent event) {
  16. System.out.println("Received a new web socket connection");
  17. }
  18. @EventListener
  19. public void handleWebSocketDisconnectListener(SessionDisconnectEvent event) {
  20. StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(event.getMessage());
  21. String username = (String) headerAccessor.getSessionAttributes().get("username");
  22. if(username != null) {
  23. WebSocketChatMessage chatMessage = new WebSocketChatMessage();
  24. chatMessage.setType("Leave");
  25. chatMessage.setSender(username);
  26. messagingTemplate.convertAndSend("/topic/public", chatMessage);
  27. }
  28. }
  29. }
定义控制器类。之前我们已经配置了 websocket,所有来自客户端的带有前缀“/app”的消息都将被路由到带有@MessageMapping 注释的适当消息处理方法。
例如,目标为 /app/chat.newUser 的消息将被路由到 newUser() 方法,目标为 /app/chat.sendMessage 的消息将被路由到 sendMessage() 方法。
  1. package com.javainuse.controller;
  2. import org.springframework.messaging.handler.annotation.MessageMapping;
  3. import org.springframework.messaging.handler.annotation.Payload;
  4. import org.springframework.messaging.handler.annotation.SendTo;
  5. import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
  6. import org.springframework.stereotype.Controller;
  7. import com.javainuse.domain.WebSocketChatMessage;
  8. @Controller
  9. public class WebSocketChatController {
  10. @MessageMapping("/chat.sendMessage")
  11. @SendTo("/topic/javainuse")
  12. public WebSocketChatMessage sendMessage(@Payload WebSocketChatMessage webSocketChatMessage) {
  13. return webSocketChatMessage;
  14. }
  15. @MessageMapping("/chat.newUser")
  16. @SendTo("/topic/javainuse")
  17. public WebSocketChatMessage newUser(@Payload WebSocketChatMessage webSocketChatMessage,
  18. SimpMessageHeaderAccessor headerAccessor) {
  19. headerAccessor.getSessionAttributes().put("username", webSocketChatMessage.getSender());
  20. return webSocketChatMessage;
  21. }
  22. }
最后用@SpringBootApplication注解定义Spring Boot类
  1. package com.javainuse;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. @SpringBootApplication
  5. public class SpringBootChatApplication {
  6. public static void main(String[] args) {
  7. SpringApplication.run(
  8. SpringBootChatApplication.class , args);
  9. }
  10. }

定义 index.html。在这里,我们已经为我们的聊天应用程序定义了 UI。它还利用了 sockjs 和 stomp 库。HTML 文件包含用于显示聊天消息的用户界面。它包括 sockjs 和 stomp javascript 库。SockJS 是一个提供类 WebSocket 对象的浏览器 JavaScript 库。SockJS 为您提供了一个连贯的、跨浏览器的 Javascript API,它在浏览器和 Web 服务器之间创建了一个低延迟、全双工、跨域的通信通道。
STOMP JS 是 javascript 的 stomp 客户端。
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta name="viewport"
  5. content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
  6. <title>JavaInUse Chat Application | JavaInUse</title>
  7. <link rel="stylesheet" href="/css/style.css" />
  8. <link
  9. href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css"
  10. rel="stylesheet" id="bootstrap-css">
  11. <script
  12. src="//netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script>
  13. </head>
  14. <body>
  15. <div id="welcome-page">
  16. <div class="welcome-page-container">
  17. <h1 class="title">Welcome - To join the chat group enter your
  18. name</h1>
  19. <form id="welcomeForm" name="welcomeForm">
  20. <div class="form-group">
  21. <input type="text" id="name" placeholder="name"
  22. class="form-control" />
  23. </div>
  24. <div class="form-group">
  25. <button type="submit" onclass="accent username-submit">Lets
  26. Begin</button>
  27. </div>
  28. </form>
  29. </div>
  30. </div>
  31. <div id="dialogue-page" class="hidden">
  32. <div class="dialogue-container">
  33. <div class="dialogue-header">
  34. <h2>JavaInUse Chat Application</h2>
  35. </div>
  36. <ul id="messageList">
  37. </ul>
  38. <form id="dialogueForm" name="dialogueForm" nameForm="dialogueForm">
  39. <div class="form-group">
  40. <div class="input-group clearfix">
  41. <input type="text" id="chatMessage"
  42. placeholder="Enter a message...." autocomplete="off"
  43. class="form-control" />
  44. <button type="submit" class="glyphicon glyphicon-share-alt">Send</button>
  45. </div>
  46. </div>
  47. </form>
  48. </div>
  49. </div>
  50. <script
  51. src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.1.4/sockjs.min.js"></script>
  52. <script
  53. src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script>
  54. <script src="/js/script.js"></script>
  55. </body>
  56. </html>
定义 javascript 文件。stompClient.subscribe()函数采用一个回调方法,只要消息到达订阅的主题,就会调用该方法。connect()函数利用 SockJS 和 stomp 客户端建立到我们在 Spring Boot 应用程序中配置的 /websocketApp 端点的连接。客户端订阅 /topic/javainuse 目的地。
  1. 'use strict';
  2. document.querySelector('#welcomeForm').addEventListener('submit', connect, true)
  3. document.querySelector('#dialogueForm').addEventListener('submit', sendMessage, true)
  4. var stompClient = null;
  5. var name = null;
  6. function connect(event) {
  7. name = document.querySelector('#name').value.trim();
  8. if (name) {
  9. document.querySelector('#welcome-page').classList.add('hidden');
  10. document.querySelector('#dialogue-page').classList.remove('hidden');
  11. var socket = new SockJS('/websocketApp');
  12. stompClient = Stomp.over(socket);
  13. stompClient.connect({}, connectionSuccess);
  14. }
  15. event.preventDefault();
  16. }
  17. function connectionSuccess() {
  18. stompClient.subscribe('/topic/javainuse', onMessageReceived);
  19. stompClient.send("/app/chat.newUser", {}, JSON.stringify({
  20. sender : name,
  21. type : 'newUser'
  22. }))
  23. }
  24. function sendMessage(event) {
  25. var messageContent = document.querySelector('#chatMessage').value.trim();
  26. if (messageContent && stompClient) {
  27. var chatMessage = {
  28. sender : name,
  29. content : document.querySelector('#chatMessage').value,
  30. type : 'CHAT'
  31. };
  32. stompClient.send("/app/chat.sendMessage", {}, JSON
  33. .stringify(chatMessage));
  34. document.querySelector('#chatMessage').value = '';
  35. }
  36. event.preventDefault();
  37. }
  38. function onMessageReceived(payload) {
  39. var message = JSON.parse(payload.body);
  40. var messageElement = document.createElement('li');
  41. if (message.type === 'newUser') {
  42. messageElement.classList.add('event-data');
  43. message.content = message.sender + 'has joined the chat';
  44. } else if (message.type === 'Leave') {
  45. messageElement.classList.add('event-data');
  46. message.content = message.sender + 'has left the chat';
  47. } else {
  48. messageElement.classList.add('message-data');
  49. var element = document.createElement('i');
  50. var text = document.createTextNode(message.sender[0]);
  51. element.appendChild(text);
  52. messageElement.appendChild(element);
  53. var usernameElement = document.createElement('span');
  54. var usernameText = document.createTextNode(message.sender);
  55. usernameElement.appendChild(usernameText);
  56. messageElement.appendChild(usernameElement);
  57. }
  58. var textElement = document.createElement('p');
  59. var messageText = document.createTextNode(message.content);
  60. textElement.appendChild(messageText);
  61. messageElement.appendChild(textElement);
  62. document.querySelector('#messageList').appendChild(messageElement);
  63. document.querySelector('#messageList').scrollTop = document
  64. .querySelector('#messageList').scrollHeight;
  65. }
定义 CSS-
  1. {
  2. -webkit-box-sizing: border-box;
  3. -moz-box-sizing: border-box;
  4. box-sizing: border-box;
  5. }
  6. html, body {
  7. height: 100%;
  8. overflow: hidden;
  9. }
  10. body {
  11. margin: 0;
  12. padding: 0;
  13. font-weight: 400;
  14. font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
  15. font-size: 1rem;
  16. line-height: 1.58;
  17. color: #333;
  18. background-color: #f4f4f4;
  19. height: 100%;
  20. }
  21. .clearfix:after {
  22. display: block;
  23. content: "";
  24. clear: both;
  25. }
  26. .hidden {
  27. display: none;
  28. }
  29. input {
  30. padding-left: 10px;
  31. outline: none;
  32. }
  33. h1, h2, h3, h4, h5, h6 {
  34. margin-top: 20px;
  35. margin-bottom: 20px;
  36. }
  37. h1 {
  38. font-size: 1.7em;
  39. }
  40. a {
  41. color: #128ff2;
  42. }
  43. button {
  44. box-shadow: none;
  45. border: 1px solid transparent;
  46. font-size: 14px;
  47. outline: none;
  48. line-height: 100%;
  49. white-space: nowrap;
  50. vertical-align: middle;
  51. padding: 0.6rem 1rem;
  52. border-radius: 2px;
  53. transition: all 0.2s ease-in-out;
  54. cursor: pointer;
  55. min-height: 38px;
  56. }
  57. button.default {
  58. background-color: #e8e8e8;
  59. color: #333;
  60. box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.12);
  61. }
  62. button.primary {
  63. background-color: #128ff2;
  64. box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.12);
  65. color: #fff;
  66. }
  67. }
  68. button.accent {
  69. background-color: #ff4743;
  70. box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.12);
  71. color: #fff;
  72. }
  73. #welcome-page {
  74. text-align: center;
  75. }
  76. .welcome-page-container {
  77. background-color: grey;
  78. width: 100%;
  79. max-width: 500px;
  80. display: inline-block;
  81. margin-top: 42px;
  82. vertical-align: middle;
  83. position: relative;
  84. padding: 35px 55px 35px;
  85. min-height: 250px;
  86. position: absolute;
  87. top: 50%;
  88. left: 0;
  89. right: 0;
  90. margin: 0 auto;
  91. margin-top: -160px;
  92. }
  93. #dialogue-page {
  94. position: relative;
  95. height: 100%;
  96. }
  97. .dialogue-container {
  98. background-color: green;
  99. margin: 10px 0;
  100. max-width: 700px;
  101. margin-left: auto;
  102. margin-right: auto;
  103. box-shadow: 0 1px 11px rgba(0, 0, 0, 0.27);
  104. margin-top: 30px;
  105. height: calc(100% - 60px);
  106. max-height: 600px;
  107. position: relative;
  108. }
  109. #dialogue-page ul {
  110. list-style-type: none;
  111. background-color: #FFF;
  112. margin: 0;
  113. overflow: auto;
  114. overflow-y: scroll;
  115. padding: 0 20px 0px 20px;
  116. height: calc(100% - 150px);
  117. }
  118. #dialogue-page #dialogueForm {
  119. padding: 20px;
  120. }
  121. #dialogue-page ul li {
  122. line-height: 1.5rem;
  123. padding: 10px 20px;
  124. margin: 0;
  125. border-bottom: 1px solid #f4f4f4;
  126. }
  127. #dialogue-page ul li p {
  128. margin: 0;
  129. }
  130. #dialogue-page .event-data {
  131. width: 100%;
  132. text-align: center;
  133. clear: both;
  134. }
  135. #dialogue-page .event-data p {
  136. color: #777;
  137. font-size: 14px;
  138. word-wrap: break-word;
  139. }
  140. #dialogue-page .message-data {
  141. padding-left: 68px;
  142. position: relative;
  143. }
  144. #dialogue-page .message-data i {
  145. position: absolute;
  146. width: 42px;
  147. height: 42px;
  148. overflow: hidden;
  149. left: 10px;
  150. display: inline-block;
  151. vertical-align: middle;
  152. font-size: 18px;
  153. line-height: 42px;
  154. color: #fff;
  155. text-align: center;
  156. border-radius: 50%;
  157. font-style: normal;
  158. text-transform: uppercase;
  159. }
  160. #dialogue-page .message-data span {
  161. color: #333;
  162. font-weight: 600;
  163. }
  164. #dialogue-page .message-data p {
  165. color: #43464b;
  166. }
  167. #dialogueForm .input-group input {
  168. border: 0;
  169. padding: 10px;
  170. background: whitesmoke;
  171. float: left;
  172. width: calc(100% - 85px);
  173. }
  174. #dialogueForm .input-group button {
  175. float: left;
  176. width: 80px;
  177. height: 38px;
  178. margin-left: 5px;
  179. }
  180. .dialogue-header {
  181. text-align: center;
  182. padding: 15px;
  183. border-bottom: 1px solid #ececec;
  184. }
  185. .dialogue-header h2 {
  186. margin: 0;
  187. font-weight: 500;
  188. }
  189. @media screen and (max-width: 730px) {
  190. .dialogue-container {
  191. margin-left: 10px;
  192. margin-right: 10px;
  193. margin-top: 10px;
  194. }
  195. }
我们完成了所需的 Java 代码。现在让我们启动 RabbitMQ。正如我们在 RabbitMQ 入门中详细解释的那样,执行启动 RabbitMQ 的步骤。
我们需要对 RabbitMQ 执行一个额外的步骤 - 为 RabbitMQ 安装 STOMP 插件,以便它可以与 STOMP 消息一起使用

接下来通过将 Spring Boot Chat 应用程序作为 Java 应用程序运行来启动它。按如下方式点击 url - http://localhost:8080


输入用户名
然后我们会看到聊天窗口。
 如果我们得到 rabbitMQconsole,我们可以看到它已经创建了一个队列。

下载源代码

下载 -
Spring Boot + WebSocket + RabbitMQ 聊天示例
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小桥流水78/article/detail/1005701
推荐阅读
相关标签
  

闽ICP备14008679号