首页 > 解决方案 > 跨测功机向用户发送 websocket 消息

问题描述

我有一个在 heroku 上运行的 spring boot 应用程序。我利用 websockets 为特定用户向客户端和服务器发送消息和从客户端和服务器发送消息。我使用 spring boot 的 SimpMessagingTemplate.convertAndSendToUser 来发送和接收消息,当用户需要从服务器返回消息时,它可以正常工作。我使用 Heroku 会话亲和性,这意味着即使我扩大会话数量,用户和 websocket 仍然共享同一个会话。

当我需要一个用户向另一个用户发送消息时,我的问题就出现了。如果两个用户都共享会话,它可以正常工作,但如果消息无法通过,则不会。

是否可以使用 SimpMessagingTemple 在不同的会话中从一个用户向另一个用户发送消息?或者我需要使用消息代理,例如 Redis。

我正在研究使用 StringRedisTemplate 实现发送消息,但不确定如何向特定用户发送消息。

    private SimpMessagingTemplate messagingTemplate;
    @Autowired
    public MessageController(SimpMessagingTemplate messagingTemplate) {
        this.messagingTemplate = messagingTemplate;
    }

    @MessageMapping("/secured/user-in")
    public void sendToDevice(Message msg, @AuthenticationPrincipal User principal) throws Exception {

        if (msg.getTo() != null) {
        String email = msg.getTo();
        Message out = new Message();
        out.setMsg(msg.getMsg());
        out.setFrom(msg.getFrom());
        out.setTo(msg.getTo());
        out.setSentTime(new Date());
        out.setStatus(msg.getStatus());
        messagingTemplate.convertAndSendToUser(email, "/secured/topic", out);
        }       

    }

JS

function connect() {
    var socket = new SockJS('/secured/user-in');
    ST.stompClient = Stomp.over(socket);
    var headers = {};
    headers[ST.getHeader()] = ST.getToken();

    ST.getStompClient().connect(headers, function (frame) {
        retries = 1;
        console.log('Connected: ' + frame);
        ST.getStompClient().subscribe('/user/secured/topic', function (event){

            var msg = JSON.parse(event.body);
            showMessage(msg.msg); 

        });

    }); 

}

更新 1

我猜我可以做这样的事情,就像这里所做的那样:

    SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor
.create(SimpMessageType.MESSAGE);
    headerAccessor.setSessionId(sessionId);
    headerAccessor.setLeaveMutable(true);

    messagingTemplate.convertAndSendToUser(sessionId,"/queue/something", payload, 
    headerAccessor.getMessageHeaders());

但是我怎么能得到另一个用户的会话 ID,我使用 Redis 来存储会话信息:@EnableRedisHttpSession

标签: spring-bootsessionherokuwebsocket

解决方案


我的术语有点混乱,我试图在另一个测功机而不是会话上向另一个用户发送消息。

最终使用了 redis sub/pub。

因此,当控制器接收到消息时,它会发布到 redis,并且 redis MessageListenerAdapter 会调用 convertAndSendToUser 方法。

@MessageMapping("/secured/user-in")
public void sendToDevice(Message msg, @AuthenticationPrincipal User principal) throws Exception {
    publishMessageToRedis(msg);
}

private void publishMessageToRedis(Message message) throws JsonProcessingException {
    ObjectMapper objectMapper = new ObjectMapper();
    String messageString = objectMapper.writeValueAsString(message);
    stringRedisTemplate.convertAndSend("message", messageString);

}   

redis 配置

@Bean
RedisMessageListenerContainer container( MessageListenerAdapter chatMessageListenerAdapter) throws URISyntaxException {

    RedisMessageListenerContainer container = new RedisMessageListenerContainer();
    container.setConnectionFactory(connectionFactory());
    container.addMessageListener(chatMessageListenerAdapter,  new PatternTopic("message"));
    return container;
}

@Bean("chatMessageListenerAdapter")
MessageListenerAdapter chatMessageListenerAdapter(RedisReceiver redisReceiver) {
    return new MessageListenerAdapter(redisReceiver, "receiveChatMessage");
}

public class RedisReceiver {
    private static final Logger LOG = LogManager.getLogger(RedisReceiver.class);
    private final WebSocketMessageService webSocketMessageService;

    @Autowired
    public RedisReceiver(WebSocketMessageService webSocketMessageService) {
        this.webSocketMessageService = webSocketMessageService;
    }

    // Invoked when message is publish to "chat" channel
    public void receiveChatMessage(String messageStr) throws IOException {

        ObjectMapper objectMapper = new ObjectMapper();
        Message message = objectMapper.readValue(messageStr, Message.class);
        webSocketMessageService.sendChatMessage(message);

    }

}

@Service
public class WebSocketMessageService {

    private final SimpMessagingTemplate template;
    private static final Logger LOG = LogManager.getLogger(WebSocketMessageService.class);

    public WebSocketMessageService(SimpMessagingTemplate template) {

        this.template = template;
    }

    public void sendChatMessage(Message message) {
        template.convertAndSendToUser(message.getTo(), "/secured/topic", message);
    }

}

解决方案基于此git 存储库


推荐阅读