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。现在,订阅该目的地的所有客户端都将收到发送给所有客户端的消息。
来看看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实例,实现更灵活和可定制的消息传递。通过这种方式,可以轻松地实现推送通知功能,为用户提供实时的信息更新和交互体验。希望这篇文章对读者有所帮助!