限制特定浏览器选项卡上的@SendToUser 广播

Limit a @SendToUser broadcast on a specific browser tab

我在 Springboot 上使用 STOMP websocket 并想限制对特定页面的广播。这是我的过程:

  1. User 将消息填入 HTML 输入。
  2. 浏览器将通过 STOMP 客户端发送消息。
  3. 服务器接收消息并验证它。如果消息有效,它将向用户处理的所有选项卡广播 User 已发送消息。如果它无效,它会发回错误消息 仅发送到发送消息的特定浏览器选项卡 而不是其他选项卡,即使这些选项卡具有相同的 User 记录英寸

虽然我不能限制将错误消息发送到特定选项卡,但我已经使它的某些部分起作用,它总是将错误消息广播到共享相同 User 的所有选项卡。这是我的初始代码:

@MessageMapping("/api/secure/message")
@SendToUser("/api/secure/broadcast")
public HttpEntity createMessage(Message message, Authentication authentication) throws Exception {
    Set<String> errors = TreeSet<String>();
    // Process Message message and add every exceptions encountered to Set errors.
    boolean valid = (errors.size() > 0);
    if(valid) {
        // Broadcast to all.
        return new ResponseEntity(message, HttpStatus.OK);
    }
    else {
        // Send the message to that particular tab only.
        return new ResponseEntity(errors, HttpStatus.UNPROCESSABLE_ENTITY);
    }
}

这可以通过 websocket 实现吗?或者我应该 return 回到 XHR

对于每个选项卡,您都将创建一个新的 websocket 会话,因此您的 stomp session-id 也会有所不同。因此我们可以决定是发送到特定会话还是特定用户的所有会话。

@Autowired
private SimpMessagingTemplate template;
....
@MessageMapping(...)
public void sendMessage(Message<?> message...) {
 .....
 StompHeaderAccessor headerAccessor = 
 StompHeaderAccessor.wrap(message);
 String sessionId = headerAccessor.getSessionId();
 ....
 if(valid) {
   //Not specifying session Id so sends all users of 
    <user_name>
   template.cnvertAndSendToUser(<user_name>, 
   <destination>, <payload>)
 }
 else {
  SimpMessageHeaderAccessor headerAccessor = 
  
  SimpMessagingHeaderAccessor.create(SimpMessageType.MESSAGE);
  headerAccessor.setSessionId(sessionId);

  //This will send it to particular session.
  template.convertAndSendToUser(<user_name>, 
                   <destination>, <payload>,  
          headerAccessor.getMessageHeaders());
  }
}

有用的参考资料:

  1. Medium post on sending to particular session.
  2. convertAndSendToUser Documentation

用户@Srinivas 提供了一个很好的起始参考点。我已经用我的工作代码修改了问题中的代码块:

// inject the [messagingTemplate] bean.
// class org.springframework.messaging.simp.SimpMessagingTemplate
@Autowired
private SimpMessagingTemplate messagingTemplate;

@MessageMapping("/api/secure/message")
// Remove the @SendToUser annotation and change return type to void.
// @SendToUser("/api/secure/broadcast")
// public HttpEntity createMessage(Message message…
public void createMessage(Message message, Authentication authentication) throws Exception {
    Set<String> errors = TreeSet<String>();
    // Process Message message and add every exceptions encountered to Set errors.
    boolean valid = (errors.size() > 0);
    if(valid) {
        // Broadcast to all.
        
        // Instead of returning to send the message, use the [messagingTemplate] instead.
        // return new ResponseEntity(message, HttpStatus.OK);
        messagingTemplate.convertAndSendToUser("/api/secure/broadcast", errors);
    }
    else {
        // Send the message to that particular tab only.
        
        // Each STOMP WebSocket connection has a unique ID that effectively differentiate
        // it to the other browser tabs. Retrieve that ID so we can target that specific
        // tab to send our error message with.

        // class org.springframework.messaging.simp.stomp.StompHeaderAccessor
        StompHeaderAccessor stompHeaderAccessor = StompHeaderAccessor.wrap(message);
        String sessionId = stompHeaderAccessor.getSessionId();

        // class org.springframework.messaging.simp.SimpMessageHeaderAccessor
        // class org.springframework.messaging.simp.SimpMessageType
        // class org.springframework.util.MimeType
        // class java.nio.charset.StandardCharsets
        SimpMessageHeaderAccessor simpHeaderAccessor =
            SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
        simpHeaderAccessor.setSessionId(sessionId);
        simpHeaderAccessor.setContentType(new MimeType("application", "json",
            StandardCharsets.UTF_8));
        simpHeaderAccessor.setLeaveMutable(true);

        // Instead of returning to send the message, use the [messagingTemplate] instead.
        // It will ensure that it will only broadcast the message to the specific
        // STOMP WebSocket sessionId.
        // return new ResponseEntity(errors, HttpStatus.UNPROCESSABLE_ENTITY);
        messagingTemplate.convertAndSendToUser(sessionId, "/api/secure/broadcast",
            errors, simpHeaderAccessor.getMessageHeaders());
    }
}

如果您在控制器方法参数上使用 @ResponseBody @Valid,则必须将逻辑行移动到 ControllerAdvice exceptionHandler().