Spring boot:WebSocket+SockJS+Stomp實現廣播和點對點訊息傳送
阿新 • • 發佈:2019-02-02
筆記
廣播式
STS工具新建spring boot專案
使用Thymeleaf和Websocket依賴
pom.xml:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.wisely</groupId> <artifactId>ch7_6</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>ch7_6_Websocket</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.6.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.7</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
配置WebSocket
需要在配置類上使用@EnableWebSocketMessageBroker開啟WebSocket支援
客戶端向服務端傳送訊息的接受類WiselyMessage:package com.wisely.ch7_6; import org.springframework.context.annotation.Configuration; import org.springframework.messaging.simp.config.MessageBrokerRegistry; import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer; import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; import org.springframework.web.socket.config.annotation.StompEndpointRegistry; @Configuration @EnableWebSocketMessageBroker /* * 通過@EnableWebSocketMessageBroker註解開啟試用STOMP協議來傳輸基於代理(message broker)的訊息,這時控制器支援使用@MessageMapping * 就像使用@RequestMapping一樣 * */ public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer{ /** * 將"/endpointWisely"路徑註冊為STOMP端點,這個路徑與傳送和接收訊息的目的路徑有所不同 * 這是一個端點,客戶端在訂閱或釋出訊息到目的地址前,要連線該端點, */ @Override public void registerStompEndpoints(StompEndpointRegistry registry) {//註冊STOMP協議的節點(endpoint),並對映的指定的URL registry.addEndpoint("/endpointWisely").withSockJS();//註冊一個STOMP的endpoint,並指定使用SockJS協議 //registry.addEndpoint("/hello").setAllowedOrigins("*").withSokJS(); //這個和客戶端建立連線時的url有關,其中setAllowedOrigins()方法表示允許連線的域名,withSockJS()方法表示支援以SockJS方式連線伺服器。 } /** * 配置了一個簡單的訊息代理,如果不過載,預設情況下回自動配置一個簡單的記憶體訊息代理,用來處理以"/topic"為字首的訊息。 * 這裡過載configureMessageBroker()方法, * 訊息代理將會處理字首為"/topic"的訊息。 */ @Override public void configureMessageBroker(MessageBrokerRegistry registry) {//配置訊息代理(Message Broker) registry.enableSimpleBroker("/topic");//廣播式應配置一個/topic訊息代理 } /* PS * registry.enableSimpleBroker("/topic", "/user");這句話表示在topic和user這兩個域上可以向客戶端發訊息。 * registry.setUserDestinationPrefix("/user");這句話表示給指定使用者傳送一對一的主題字首是"/user"。 * registry.setApplicationDestinationPrefixes("/app");這句話表示客戶單向伺服器端傳送時的主題上面需要加"/app"作為字首。 */ }
package com.wisely.ch7_6;
/*
* 瀏覽器向服務端傳送的訊息用此類接收
*
*/
public class WiselyMessage {
private String name;
public String getName(){
return name;
}
}
服務端向客戶端傳送的訊息類WiselyResponse:
package com.wisely.ch7_6; /* * 服務端向瀏覽器傳送的此類的訊息 * */ public class WiselyResponse { private String responseMessage; public WiselyResponse(String responseMessage){ this.responseMessage = responseMessage; } public String getResponseMessage() { return responseMessage; } }
控制器WsController:控制器WsController:
package com.wisely.ch7_6;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;
@Controller
public class WsController {
@MessageMapping("/welcome")//當瀏覽器向服務端傳送請求時,通過@MessageMapping對映/welcome這個地址,類似於@RequestMapping
@SendTo("/topic/getResponse")//當服務端有訊息時,監聽了/topic/getResponse的客戶端會接收訊息
public WiselyResponse say(WiselyMessage message) throws Exception{
Thread.sleep(3000);
return new WiselyResponse("Welcome," + message.getName() + "!");
}
}
前端頁面ws.html:
<!--spring boot: 將stomp.min.js(STOMP協議的客戶端指令碼)、sockjs.min.js(SockJS的客戶端指令碼)以及jQuery放置在src/main/resources/static下 -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"> <!-- 頁面使用Thymeleaf模板引擎 -->
<head>
<meta charset="UTF-8" />
<title>Spring Boot+WebSocket+廣播式</title>
</head>
<body onload="disconnect()">
<noscript><h2 style="color: #ff0000">貌似你的瀏覽器不支援websocket</h2></noscript>
<div>
<div>
<button id="connect" onclick="connect();">連線</button>
<button id="disconnect" disabled="disabled" onclick="disconnect();">斷開連線</button>
</div>
<div id="conversationDiv">
<label>輸入你的名字</label><input type="text" id="name" />
<button id="sendName" onclick="sendName();">傳送</button>
<p id="response"></p>
</div>
</div>
<script th:src="@{sockjs.min.js}"></script>
<script th:src="@{stomp.min.js}"></script>
<script th:src="@{jquery.js}"></script>
<script type="text/javascript">
var stompClient = null;
function setConnected(connected) {
document.getElementById('connect').disabled = connected; //connected為false時則可用
document.getElementById('disconnect').disabled = !connected; //connected為false時則不可用
document.getElementById('conversationDiv').style.visibility = connected ? 'visible' : 'hidden'; //設定conversationDiv是否可見
$('#response').html();
}
function connect() {
var socket = new SockJS('/endpointWisely'); //連線SockJS的endpoint名稱為"/endpointWisely"
stompClient = Stomp.over(socket);//使用STOMP自協議的WebSocket客戶端
stompClient.connect({}, function(frame) { //連線WebSocket服務端
setConnected(true);
console.log('Connected: ' + frame);
stompClient.subscribe('/topic/getResponse', function(respnose){
//通過stompClient.subscribe訂閱/topic/getResponse目標(destination)傳送的訊息,這個是在控制器的@SendTo中定義的
showResponse(JSON.parse(respnose.body).responseMessage);//頁面顯示接收的訊息
});
});
}
function disconnect() {
if (stompClient != null) {
stompClient.disconnect();
}
setConnected(false);
console.log("Disconnected");
}
function sendName() {
var name = $('#name').val();
stompClient.send("/welcome", {}, JSON.stringify({ 'name': name }));
//通過stompClient.send 向/welcome目標(destination)傳送訊息,這個實在控制器的@MessageMapping中定義的
} function showResponse(message) { var response = $("#response"); response.html(message); }</script></body></html>
WebMvcConfig中設定viewController,為ws提供路徑對映
package com.wisely.ch7_6;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter{
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/ws").setViewName("/ws");
}
}
點對點式:
新增Spring Security的依賴:向pom.xml新增
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>1.4.1.RELEASE</version>
</dependency>
新建WebSecurityConfig類用於配置Spring Security
package com.wisely.ch7_6;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
/*
* 在記憶體中配置兩個使用者wyf和wisely,密碼與使用者名稱一致,角色為USER
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("wyf").password("wyf").roles("USER")
.and()
.withUser("wisely").password("wisely").roles("USER");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/login").permitAll()//設定Spring Security對"/"和"login"不攔截
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")//設定Spring Security的登陸頁面訪問的路徑為/login
.defaultSuccessUrl("/chat")//登陸成功後轉向/chat路徑
.permitAll()
.and()
.logout()
.permitAll();
}
/*
* -/resources/static目錄下的靜態資源,Spring Security不攔截
*/
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/resources/static/**");
}
}
在WebSocket配置類中的
registerStompEndpoints方法中加入:
registry.addEndpoint("/endpointChat").withSockJS();
configureMessageBroker方法中:registry.enableSimpleBroker("/topic");
改為:
registry.enableSimpleBroker("/queue","/topic");//新增一個用於點對點的訊息代理/queue
向WsController新增以下程式碼:
@Autowired
private SimpMessagingTemplate messageingTemplate;//通過SimpMessagingTemplate向瀏覽器傳送資訊
@MessageMapping("/chat")
public void handleChat(Principal principal, String msg){
//Spring MVC中可以直接在引數中獲得principal,principal中包含當前使用者的資訊
if(principal.getName().equals("wyf")){
//測試用,實際情況需根據需求編寫這裡判斷若發件人是wyf,則發給wisely;若是wisely則發給wyf
messageingTemplate.convertAndSendToUser("wisely", "/queue/notifications", principal.getName() + "-send:" + msg);
//通過messageingTemplate.convertAndSendToUser向用戶傳送資訊
//第一個引數為接收使用者,第二個引數為訂閱地址,第三個為訊息
}else{
messageingTemplate.convertAndSendToUser("wyf", "/queue/notifications", principal.getName() + "-send:" + msg);
}
}
登陸頁面login.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<meta charset="UTF-8" />
<head>
<title>登陸頁面</title>
</head>
<body>
<div th:if="${param.error}">
無效的賬號和密碼
</div>
<div th:if="${param.logout}">
你已登出
</div>
<form th:action="@{/login}" method="post">
<div><label> 賬號 : <input type="text" name="username"/> </label></div>
<div><label> 密碼: <input type="password" name="password"/> </label></div>
<div><input type="submit" value="登陸"/></div>
</form>
</body>
</html>
聊天頁面chat.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<meta charset="UTF-8" />
<head>
<title>Home</title>
<script th:src="@{sockjs.min.js}"></script>
<script th:src="@{stomp.min.js}"></script>
<script th:src="@{jquery.js}"></script>
</head>
<body>
<p>
聊天室
</p>
<form id="wiselyForm">
<textarea rows="4" cols="60" name="text"></textarea>
<input type="submit"/>
</form>
<script th:inline="javascript">
$('#wiselyForm').submit(function(e){
e.preventDefault();
var text = $('#wiselyForm').find('textarea[name="text"]').val();
sendSpittle(text);
});
var sock = new SockJS("/endpointChat"); //連線endpoint
var stomp = Stomp.over(sock);
stomp.connect('guest', 'guest', function(frame) {
stomp.subscribe("/user/queue/notifications", handleNotification);
//訂閱/user/queue/notifications的資訊,/user必須,使用user才會傳送訊息到指定的使用者
//第二引數的地址和WsController中的
//messageingTemplate.convertAndSendToUser("wisely", "/queue/notifications", principal.getName() + "-send:" + msg);
//第二引數地址一致,相當於標誌符
});
function handleNotification(message) {
$('#output').append("<b>Received: " + message.body + "</b><br/>")
}
function sendSpittle(text) {
stomp.send("/chat", {}, text);
}
$('#stop').click(function() {sock.close()});
</script>
<div id="output"></div>
</body>
</html>
WebMvcConfig新增路徑對映registry.addViewController("/login").setViewName("/login");
registry.addViewController("/chat").setViewName("/chat");
測試:略