基于Spring Boot,一步步教你用Websockets和STOMP進行消息推送
1 引言
推送通知是一種實時消息傳遞形式,通過它網站可以向用戶實時通知特定事件。通常使用WebSockets實現推送通知,這種技術提供了客戶端和服務器之間的雙向通信,從而實現了實時消息的處理。
本文使用WebSockets來實現推送通知,并使用STOMP協議在客戶端和服務器之間進行通信。
2 什么是STOMP
STOMP代表簡單文本導向的消息協議(Simple Text Oriented Messaging Protocol)。由于WebSockets是一種低級協議,使用幀(frames)來傳輸數據,而STOMP是一種高級協議,定義了如何解釋某些幀類型中的數據。這些幀類型包括CONNECT、SEND、ACK等。因此,使用STOMP能夠更加簡化使用WebSockets進行數據的發送、接收和解析過程。
有了這個基礎,接下來創建服務器應用程序。
3 創建一個應用程序
到https://start.spring.io創建一個Spring Boot應用程序,并添加以下依賴項:
Spring Boot Starter Websockets
現在,使用一個嵌入式消息代理,它將是一個提供WebSocket功能的內存中代理。給代理添加一些目的地。這些目的地指的是將要發送消息的路徑。
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/all","/specific");
config.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws");
registry.addEndpoint("/ws").withSockJS();
}
}
在第一部分中,啟用了一個帶有兩個目的地(/all和/specific)的代理。/all目的地將用于向所有用戶發送通知,/specific目的地用于向特定用戶發送通知。
接下來,設置應用程序的目的地,即 /app,這樣就可以向應用程序發送信息了。
在第二部分中,注冊了STOMP端點。其中一個啟用了SockJS,另一個僅使用WebSocket。之所以這樣做,是因為并非所有瀏覽器都支持WebSocket,當不可用時,可以回退到使用SockJS。
4 向所有用戶發送推送通知
先看一下第一個用例,即向所有用戶發送推送通知。
為此,首先實現一個控制器,該控制器會把來自一個客戶端的信息轉發給所有客戶端。
@org.springframework.stereotype.Controller
public class Controller {
@Autowired
SimpMessagingTemplate simpMessagingTemplate;
@MessageMapping("/application")
@SendTo("/all/messages")
public Message send(final Message message) throws Exception {
return message;
}
}
在上面的代碼中,我們接受/application端點上的消息。這實際上是之前定義的應用程序目的地/app的子目的地。這意味著客戶端必須把消息發送到/app/application目的地才能到達該處理程序。
接下來,把傳入的消息轉發到/all/messages?,F在,訂閱該目的地的所有客戶端都將收到發送給所有客戶端的消息。
來看看HTML頁面上的客戶端代碼:
<script type="text/javascript">
var stompClient = null;
var socket = new SockJS('/ws');
stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
console.log(frame);
stompClient.subscribe('/all/messages', function(result) {
show(JSON.parse(result.body));
});
});
在這里,使用一個STOMP客戶端,在WebSocket上建立連接,然后訂閱/all/messages上的消息。
現在,為了將消息發送給應用程序,有以下的JavaScript函數,它將消息發送到/app/application:
function sendMessage() {
var text = document.getElementById('text').value;
stompClient.send("/app/application", {},
JSON.stringify({'from':from, 'text':text}));
}
它簡單地從文本字段中獲取文本值,并將其發送到代理的應用程序目標。
這是通過下面顯示的一個簡單表單進行連接的。
圖片
為了測試這個,我們向所有連接的客戶端發送一個推送通知"Notification to all"。
圖片
這里有兩個連接的客戶端,兩個客戶端都立即收到了通知。
現在,在這里只是顯示了從WebSocket接收到的內容,但可以根據需要使用CSS和JavaScript來自定義通知彈出窗口或通知標簽。
這就是如何向所有用戶發送通知。那么如何向特定用戶發送通知呢?
5 向特定用戶發送推送通知
要向特定用戶發送通知,我們需要收件人的用戶ID。這意味著接收方用戶需要登錄并提供一個有效的會話來標識用戶的用戶ID。
為此,我們將集成Spring Security。因此,添加以下依賴項。
Spring Boot Starter Security
添加了Spring Security依賴項后,我們需要定義一個安全配置來允許使用WebSockets進行連接。
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests()
.mvcMatchers("/","/ws/**")
.permitAll()
.and()
.authorizeHttpRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.and()
.logout( logout -> logout.logoutSuccessUrl("/"));
return http.build();
}
@Bean
public InMemoryUserDetailsManager userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("test")
.password("test")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
在這里,我們允許所有連接到/ws路徑的連接,以便在沒有任何身份驗證的情況下進行WebSocket通信,還定義了一個名為"test"的靜態用戶。
還記得在上面的消息代理設置中創建的/specific目標嗎?現在將使用它來發送特定的消息。
首先,在控制器中添加一個處理程序,用于接收消息并將其發送給特定的用戶,這些用戶將使用它們的用戶名進行標識。
@org.springframework.stereotype.Controller
public class Controller {
@Autowired
SimpMessagingTemplate simpMessagingTemplate;
@MessageMapping("/application")
@SendTo("/all/messages")
public Message send(final Message message) throws Exception {
return message;
}
@MessageMapping("/private")
public void sendToSpecificUser(@Payload Message message) {
simpMessagingTemplate.convertAndSendToUser(message.getTo(), "/specific", message);
}
}
現在,在sendToSpecificUser方法中,我們接受使用/app/private發送的消息。消息包含要發送給接收者的文本以及接收者的用戶ID。
消息模板所做的是將消息發送到以/user開頭的目標,然后將其附加到我們在convertAndSendToUser函數調用中指定的目標,即/specific,然后附加所指定的用戶的用戶會話ID。
因此,convertAndSendToUser將消息發送到目標/user/specific-<user-session-id>。這個目標是在用戶登錄并訂閱/user/specific時創建的。
當用戶登錄并訂閱/user/specific時,它會發送有效的已登錄會話ID。然后,Spring自動處理訂閱/user/specific將自動訂閱已登錄用戶的特定目標,即/user/specific-<user-session-id>。
這也意味著只有用戶登錄時才能發送通知。
現在,添加一個新的文本塊并訂閱用戶特定的目標。
socket = new SockJS('/ws');
privateStompClient = Stomp.over(socket);
privateStompClient.connect({}, function(frame) {
console.log(frame);
privateStompClient.subscribe('/user/specific', function(result) {
console.log(result.body)
show(JSON.parse(result.body));
});
});
圖片
打開兩個客戶端,并使用"test"用戶登錄第二個客戶端??梢允褂?login端點觸發登錄。
在上面的圖像中,正在以"test"用戶登錄第二個客戶端。
登錄后,首先向所有客戶端發送消息。
圖片
所以,即使已登錄的用戶也會收到發送給所有客戶端的通知。
現在,向"test"用戶發送一個私有通知。
圖片
在上面的圖像中,我們為特定用戶提供了一條消息,并指定了特定用戶的用戶ID,即"test",通知只會傳遞給已登錄的用戶。
這是一個關于它是如何工作的簡短演示。
圖片
6 結語
在本文中,我們學習了如何使用Spring Boot應用程序、WebSockets和STOMP協議發送推送通知。如果希望使用外部的ActiveMQ實例,只需將其連接到應用程序,因為ActiveMQ也支持STOMP協議。這樣,我們就可以通過應用程序將消息中繼到外部的ActiveMQ實例,實現更靈活和可定制的消息傳遞。通過這種方式,可以輕松地實現推送通知功能,為用戶提供實時的信息更新和交互體驗。希望這篇文章對讀者有所幫助!