目录
引言
代码实现
配置类WebSocketMessageBrokerConfig
DTO
工具类
Controller
common.html
stomp-broadcast.html
运行效果
完整代码地址
STOMP(Simple Text Oriented Messaging Protocol)作为一种简单文本导向的消息传递协议,提供了一种轻量级且易于使用的方式来实现实时通信。本篇博客将讲解如何使用Spring Boot创建一个基于STOMP的WebSocket应用程序,并展示相关的配置类。同时,还会介绍如何使用Thymeleaf模板引擎生成动态的HTML页面,以展示实时通信的效果。
package com.wsl.websocket.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketMessageBrokerConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
// it is OK to leave it here
registry.addEndpoint("/broadcast");
//registry.addEndpoint("/broadcast").withSockJS();
// custom heartbeat, every 60 sec
//registry.addEndpoint("/broadcast").withSockJS().setHeartbeatTime(60_000);
}
}
package com.wsl.websocket.dto;
import com.wsl.websocket.util.StringUtils;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class ChatMessage {
private String from;
private String text;
private String recipient;
private String time;
public ChatMessage() {
}
public ChatMessage(String from, String text, String recipient) {
this.from = from;
this.text = text;
this.recipient = recipient;
this.time = StringUtils.getCurrentTimeStamp();
}
}
package com.wsl.websocket.util;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class StringUtils {
private static final String TIME_FORMATTER= "HH:mm:ss";
public static String getCurrentTimeStamp() {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(TIME_FORMATTER);
return LocalDateTime.now().format(formatter);
}
}
package com.wsl.websocket.controller;
import com.wsl.websocket.dto.ChatMessage;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class WebSocketBroadcastController {
@GetMapping("/stomp-broadcast")
public String getWebSocketBroadcast() {
return "stomp-broadcast";
}
@GetMapping("/sockjs-broadcast")
public String getWebSocketWithSockJsBroadcast() {
return "sockjs-broadcast";
}
@MessageMapping("/broadcast")
@SendTo("/topic/broadcast")
public ChatMessage send(ChatMessage chatMessage) {
return new ChatMessage(chatMessage.getFrom(), chatMessage.getText(), "ALL");
}
}
src/main/resources/templates/common.html
src/main/resources/templates/stomp-broadcast.html
WebSocket With STOMP Broadcast Example
WebSocket
WebSocket Broadcast - with STOMP
http://localhost:8080/stomp-broadcast
GitHub - wangsilingwsl/springboot-stomp: springboot integrates stomp
此功能基于Spring Boot,和上面代码分隔开,没有关联关系。请结合实际情况参考下列代码。
package com.twqc.config.websocket;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
/**
* WebSocket消息代理配置类
*
* 注解@EnableWebSocketMessageBroker表示启用WebSocket消息代理功能。不开启不能使用@MessageMapping和@SendTo注解。
* 注意:@MessageMapping和@SendTo的使用方法:
*
* 注解@MessageMapping的方法用于接收客户端发送的消息,方法的参数用于接收消息内容,方法的返回值用于发送消息内容。
* 注解@SendTo的方法用于发送消息内容,方法的返回值用于发送消息内容。
*
* 示例:@MessageMapping("/example")注解的方法用于接收客户端发送的消息,@SendTo("/topic/example")注解的方法用于发送消息内容。
* 3.1 对应的客户端连接websocket的路径为:ws://localhost:8080/example
* 3.2 对应的客户端发送消息的路径为:/app/example
* 3.3 对应的客户端接收消息的路径为:/topic/example
* 3.4 app和topic在WebSocketMessageBrokerConfigurer.configureMessageBroker方法中配置
* 3.5 具体的路径需要自己定义,上文仅为示例,与本项目中使用的路径无关。
*
*
* @author wsl
* @date 2024/2/29
* @see WebSocketMessageBrokerConfigurer
* @see MessageMapping
* @see SendTo
* @see Spring Boot WebSocket with STOMP Tutorial
*/
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketStompConfig implements WebSocketMessageBrokerConfigurer {
/**
* 配置消息代理
*
* @param config 消息代理配置注册器
*/
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
// 设置消息代理的目的地前缀,所有以"/websocket/topic"开头的消息都会被代理发送给订阅了该目的地的客户端
config.enableSimpleBroker("/websocket/topic");
// 设置应用的目的地前缀,所有以"/websocket/app"开头的消息都会被路由到带有@MessageMapping注解的方法中进行处理
config.setApplicationDestinationPrefixes("/websocket/app");
// 设置用户的目的地前缀,所有以"/user"开头的消息都会被代理发送给订阅了该目的地的用户
config.setUserDestinationPrefix("/user");
}
/**
* 注册消息端点
* 可以注册多个消息端点,每个消息端点对应一个URL路径。
*
* @param registry 消息端点注册器
*/
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
// 注册消息端点,参数为消息端点的URL路径(对应@MessageMapping注解的路径,也是客户端连接的路径)
registry.addEndpoint("/websocket/bpm/runFlow")
// 设置允许的跨域请求来源
.setAllowedOrigins("*");
}
/**
* 配置客户端入站通道
*
* @param registration 客户端入站通道注册器
*/
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
// 设置客户端入站通道的自定义拦截器
registration.interceptors(new MyWebSocketInterceptor());
}
}
package com.twqc.config.websocket;
import lombok.extern.slf4j.Slf4j;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.simp.stomp.StompCommand;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.messaging.support.MessageHeaderAccessor;
import java.util.List;
/**
* WebSocket拦截器
*
* @author wsl
* @date 2024/3/4
*/
@Slf4j
public class MyWebSocketInterceptor implements ChannelInterceptor {
@Override
public Message> preSend(Message> message, MessageChannel channel) {
StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
if (StompCommand.CONNECT.equals(accessor.getCommand())) {
List nativeHeaders = accessor.getNativeHeader("username");
if (nativeHeaders != null) {
String username = nativeHeaders.get(0);
// 存入Principal
accessor.setUser(() -> username);
log.info("用户:{}发起了stomp连接", username);
} else {
log.info("未提供用户名的stomp连接");
return message;
}
}
return message;
}
}
用户的消息推送有两种实现方式
@MessageMapping("/websocket/bpm/runFlow")
@SendToUser("/websocket/topic/websocket/bpm/runFlow")
public String runFlow2(Principal principal) {
if (principal == null || principal.getName() == null) {
log.error("未提供用户名的stomp连接,无法运行流程");
}
String username = principal.getName();
return "success" + username;
}
@MessageMapping("/websocket/bpm/runFlow")
public void runFlow(FlowRequest request, Principal principal) {
if (principal == null || principal.getName() == null) {
log.error("未提供用户名的stomp连接,无法运行流程");
}
String username = principal.getName();
flowService.runFlow(request, username);
}
flowService
@Autowired
private SimpMessagingTemplate messagingTemplate;
private void sendNodeResult(FlowResponse response, String username) {
messagingTemplate.convertAndSendToUser(username, BpmConstant.Flow.TOPIC_FLOW_RESULTS, response);
}
提交
请注意,客户端接收信号时,需要在订阅地址前加上/app,否则接收失败。
GitHub - wangsilingwsl/vue-stomp
参考:Spring Boot + WebSocket With STOMP Tutorial | Dariawan