我们知道可以使用客户端订阅的主题前缀(例如/topic/hello
)向stomp服务器发送消息。我们还知道可以通过Spring提供的convertAndSendToUser(username, destination, message)
API向特定用户发送消息。它接受一个字符串用户名,这意味着如果我们对于每个连接都有一个唯一的用户名,我们应该能够向订阅主题的特定用户发送消息。
较少人理解的是,这个用户名是从哪里来的?
这个用户名是java.security.Principal
接口的一部分。 每个StompHeaderAccessor
或WebSocketSession
对象都具有此Principal实例,您可以从中获取用户名。但是,根据我的实验,它不会自动生成。服务器必须为每个会话手动生成它。
要使用此接口,首先需要实现它。
class StompPrincipal implements Principal {
String name
StompPrincipal(String name) {
this.name = name
}
@Override
String getName() {
return name
}
}
您可以通过覆盖DefaultHandshakeHandler来为每个连接生成唯一的StompPrincipal
。 您可以使用任何逻辑生成用户名。 这里是一个潜在的逻辑,它使用UUID:
class CustomHandshakeHandler extends DefaultHandshakeHandler {
@Override
protected Principal determineUser(
ServerHttpRequest request,
WebSocketHandler wsHandler,
Map<String, Object> attributes
) {
return new StompPrincipal(UUID.randomUUID().toString())
}
}
最后,您需要配置您的websockets以使用您的自定义握手处理程序。
@Override
void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {
stompEndpointRegistry
.addEndpoint("/stomp")
.setHandshakeHandler(new CustomHandshakeHandler())
.withSockJS()
}
那就这样了。现在你的服务器已经配置好,可以为每个连接生成一个唯一的主体名。它将把该主体作为StompHeaderAccessor
对象的一部分传递,你可以通过连接事件监听器、MessageMapping函数等方式访问它们...
从事件监听器:
@EventListener
void handleSessionConnectedEvent(SessionConnectedEvent event) {
StompHeaderAccessor sha = StompHeaderAccessor.wrap(event.getMessage())
}
从消息映射的 API
@MessageMapping('/hello')
protected void hello(SimpMessageHeaderAccessor sha, Map message) {
}
关于使用convertAndSendToUser(...)
的最后一点注意事项。当向用户发送消息时,您将使用类似于以下内容的内容
convertAndSendToUser(sha.session.principal.name, '/topic/hello', message)
然而,要订阅客户端,您将使用
client.subscribe('/user/topic/hello', callback)
如果您将客户端订阅到/topic/hello
,那么您只会收到广播的消息。