
. « Spring Framework» . , , : « Spring», .
在使用“服务器发送事件”和“ Redis”构建类似于Facebook的可扩展通知中,我们使用了“服务器发送事件”将消息从服务器发送到客户端。它还提到了WebSocket,这是服务器和客户端之间的双向通信技术。
在本文中,我们将介绍WebSocket的一种常见用例。我们将编写一个私人消息传递应用程序。
以下视频演示了我们将要做的事情。
WebSockets和STOMP简介
WebSocket是用于服务器和客户端之间双向通信的协议。
WebSocket与HTTP不同,应用层协议是一种传输层协议(TCP)。尽管HTTP用于初始连接,但是该连接随后被“升级”为WebSocket中使用的TCP连接。
WebSocket是不定义消息格式的低级协议。因此,WebSocket RFC定义了描述消息结构和标准的子协议。我们将在WebSockets上使用STOMP(在WebSockets上使用STOMP)。
协议STOMP(面向消息的文本的简单/流式传输协议)定义了服务器与客户端之间的消息交换规则。
STOMP与HTTP相似,并且使用以下命令在TCP之上运行:
- 连接
- 订阅
- 取消订阅
- 发送
- 开始
- 承诺
- 确认
可以在此处找到STOMP命令的规范和完整列表。
建筑

- 该验证服务是负责认证和管理用户。我们不会在这里重新发明轮子,而是将使用JWT的身份验证服务和使用Spring Boot的Social Authentication。
- 在聊天服务是负责配置的WebSocket,处理STOMP消息,以及存储和处理用户的消息。
- 在聊天客户端是使用STOMP客户端连接并订阅聊天ReactJS应用。这里也是用户界面。
讯息模型
首先要考虑的是消息模型。ChatMessage看起来像这样:
public class ChatMessage {
@Id
private String id;
private String chatId;
private String senderId;
private String recipientId;
private String senderName;
private String recipientName;
private String content;
private Date timestamp;
private MessageStatus status;
}
该类
ChatMessage
非常简单,其中包含用于标识发件人和收件人的字段。
它还具有一个状态字段,指示该消息是否已传递到客户端。
public enum MessageStatus {
RECEIVED, DELIVERED
}
当服务器从聊天中收到消息时,它不会直接将消息发送给收件人,而是会发送通知(ChatNotification)通知客户端已收到新消息。此后,客户自己可以收到一条新消息。客户端一收到消息,便将其标记为“已发送”。
通知如下所示:
public class ChatNotification {
private String id;
private String senderId;
private String senderName;
}
通知包含新消息的ID和有关发件人的信息,以便客户端可以显示有关新消息的信息或新消息的数量,如下所示。


在Spring中配置WebSocket和STOMP
第一步是配置STOMP端点和消息代理。
为此,我们使用注解和创建一个WebSocketConfig类。
@Configuration
@EnableWebSocketMessageBroker
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker( "/user");
config.setApplicationDestinationPrefixes("/app");
config.setUserDestinationPrefix("/user");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry
.addEndpoint("/ws")
.setAllowedOrigins("*")
.withSockJS();
}
@Override
public boolean configureMessageConverters(List<MessageConverter> messageConverters) {
DefaultContentTypeResolver resolver = new DefaultContentTypeResolver();
resolver.setDefaultMimeType(MimeTypeUtils.APPLICATION_JSON);
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
converter.setObjectMapper(new ObjectMapper());
converter.setContentTypeResolver(resolver);
messageConverters.add(converter);
return false;
}
}
第一种方法使用一个带前缀的地址配置一个简单的内存中消息代理,
/user
以发送和接收消息。前缀地址/app
用于通过带注释的方法处理的消息@MessageMapping
,我们将在下一节中讨论。
第二种方法注册STOMP端点
/ws
。客户端将使用此终结点连接到STOMP服务器。这还包括一个后备SockJS,如果WebSocket不可用,则将使用它。
最后一种方法配置Spring用来将消息转换为JSON或从JSON转换的JSON转换器。
处理消息的控制器
在本节中,我们将创建一个处理请求的控制器。它将接收来自用户的消息并将其发送给收件人。
@Controller
public class ChatController {
@Autowired private SimpMessagingTemplate messagingTemplate;
@Autowired private ChatMessageService chatMessageService;
@Autowired private ChatRoomService chatRoomService;
@MessageMapping("/chat")
public void processMessage(@Payload ChatMessage chatMessage) {
var chatId = chatRoomService
.getChatId(chatMessage.getSenderId(), chatMessage.getRecipientId(), true);
chatMessage.setChatId(chatId.get());
ChatMessage saved = chatMessageService.save(chatMessage);
messagingTemplate.convertAndSendToUser(
chatMessage.getRecipientId(),"/queue/messages",
new ChatNotification(
saved.getId(),
saved.getSenderId(),
saved.getSenderName()));
}
}
借助注释,
@MessageMapping
我们配置将消息发送到/app/chat
方法时调用processMessage
。请注意,先前配置的应用程序前缀将添加到映射中/app
。
此方法将消息存储在MongoDB中,然后调用该方法
convertAndSendToUser
以将通知发送到目标。在地址
上
convertAndSendToUser
添加前缀/user
和的方法。最终地址将如下所示:recipientId
/queue/messages
/user/{recipientId}/queue/messages
该地址的所有订户(在我们的示例中为一个)将收到该消息。
产生chatId
对于两个用户之间的每次对话,我们创建一个聊天室并生成一个唯一的聊天室以进行识别
chatId
。ChatRoom
类如下所示:
public class ChatRoom {
private String id;
private String chatId;
private String senderId;
private String recipientId;
}
该值
chatId
等于串联senderId_recipientId
。对于每个会话,我们保留两个相同的实体chatId
:一个在发送者和接收者之间,另一个在接收者和发送者之间,以便两个用户都相同chatId
。
JavaScript客户端
在本部分中,我们将创建一个JavaScript客户端,该客户端将向该客户端发送消息并从中接收WebSocket / STOMP服务器。
我们将使用SockJS和Stomp.js通过WebSocket通过STOMP与服务器进行通信。
const connect = () => {
const Stomp = require("stompjs");
var SockJS = require("sockjs-client");
SockJS = new SockJS("http://localhost:8080/ws");
stompClient = Stomp.over(SockJS);
stompClient.connect({}, onConnected, onError);
};
该方法
connect()
建立与的连接/ws
,我们的服务器正在其中等待连接,还定义了一个回调函数onConnected
,该函数将在成功连接后onError
调用,并在连接到服务器时发生错误时调用。
const onConnected = () => {
console.log("connected");
stompClient.subscribe(
"/user/" + currentUser.id + "/queue/messages",
onMessageReceived
);
};
该方法
onConnected()
订阅特定的地址并接收在那里发送的所有消息。
const sendMessage = (msg) => {
if (msg.trim() !== "") {
const message = {
senderId: currentUser.id,
recipientId: activeContact.id,
senderName: currentUser.name,
recipientName: activeContact.name,
content: msg,
timestamp: new Date(),
};
stompClient.send("/app/chat", {}, JSON.stringify(message));
}
};
在方法的最后,一条
sendMessage()
消息被发送到/app/chat
我们的控制器中指定的地址。
结论
在本文中,我们介绍了使用Spring Boot和基于WebSocket的STOMP创建聊天的所有要点。
我们还使用SockJs和Stomp.js库构建了一个JavaScript客户端。
示例源代码可以在这里找到。
了解有关该课程的更多信息。
- SpringBoot TestContainers-
- Spring Data (Klara Dan von) Neumann