1. 程式人生 > >Spring boot:WebSocket+SockJS+Stomp實現廣播和點對點訊息傳送

Spring boot:WebSocket+SockJS+Stomp實現廣播和點對點訊息傳送

筆記

廣播式

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支援

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"作為字首。
	 */

	
}
客戶端向服務端傳送訊息的接受類WiselyMessage
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");

測試:略