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