如何根据条件限制 Spring 订阅特定主题的 stomp 客户端数量?

How to limit the number of stomp clients in Spring, subscribing to a specific topic, based on a condition?

我一直在研究一种方法来限制可以订阅特定 stomp 主题但尚未理解的客户数量,这可能是符合我需要的正确方法。

我的用例是一个游戏,我正在 Angular(ng2-stompjs stomp 客户端)和 Spring Boot Websockets(目前,Spring 在-内存消息代理正在使用中)。

这个想法是,用户可以连接并订阅“/lobby”踩踏主题,然后他会看到打开的游戏室,它们可能处于不同的状态。例如,进行中或由于加入的玩家数量少而尚未开始。 我想拦截并以编程方式将客户端的可能订阅限制为特定的“/room/{roomId}”主题,如果玩家的最大数量已达到,例如 4。也可以是一些简单的客户端验证来限制这一点,但我认为只有客户端是不够的

所以我的主要问题是: 如何在Spring中拦截特定的stomp主题订阅? 是否可以 return 向客户端请求者发送某种无法完成订阅的错误消息?

非常感谢您的帮助,提前致谢!

您可以实现一个监听订阅的 StompEventListener,在此我们可以使用地图映射目的地(房间号)与该特定房间中的玩家数量。如果计数已经达到最大值,则拒绝订阅。

@Service
class StompEventListener() {
   private Map<String, int> roomIdVsPlayerCount = new HashMap<>();
   
   @EventListener
   public void handleSubscription(SessionSubscribe event) {
     StompHeaderAccessor accessor = StompHeaderAccessor.wrap(event.getMessage());
     String destination = accessor.getDestination();

     String roomId = destination.substring(...); //Parsed RoomID
     if(roomIdVsPlayerCount.get(roomId) == MAX_ALLOWED_PLAYERS) {
       //Throw exception which will terminate that client connection 
         or, send an error message like so:
       simpMessagingTemplate.convertAndSend(<some_error_message>);
       return;
     }
     //So it is not at maximum do further logic to actually subscribe 
       user and
     roomIdVsPlayerCount.get(roomId) += 1; 
   }

   @EventListener
   public void handleUnsubscription(SessionUnsubscribe event) {
    ...
   }
}

有用的参考资料:

  1. SessionSubscribeEvent(用于处理订阅)
  2. ConvertAndSend。 (用于向客户端发送错误消息。)

编辑

请尝试从 channel Interceptor 发送异常,因为上面没有发送异常,以便它传播到客户端。我们之前定义的映射可以定义为单独的 class 事件处理程序(用于递增和递减)和 TopicSubscriptionInterceptor(用于验证)可访问(使用 @Autowired)的 bean。

@Component
class TopicSubscriptionInterceptor implements ChannelInterceptor {
    @Override
    public Message<?> preSend(Message<?> message, MessageChannel channel){
      StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
      String destination = accessor.getDestination();

      String roomId = destination.substring(...); //Parsed RoomID
      if(roomIdVsPlayerCount.get(roomId) == MAX_ALLOWED_PLAYERS) {
        //Throw exception which will terminate that client connection 
      }
     
     //Since it is not at limit continue 
    
    }
}

实现 TopicSubscriptionInterceptor 的有用参考:TopicSubscriptionInterceptor