В настоящее время у меня есть простой WebSocket с настройкой STOMP, где клиент подключается к теме (с идентификатором). Контроллер немедленно отвечает тем, что было запрошено, и устанавливается переменная, указывающая, на что подписался клиент. Метод, помеченный @Scheduled, теперь отправляет клиенту то, что он запросил, каждые несколько секунд.
Запланированный метод ничего не делает, пока клиент не подключится в первый раз. Однако после первой подписки он продолжит публикацию независимо от того, подписан клиент или нет.
@Controller
public class ServiceWebSocketController {
@Autowired
private ServiceService serviceService;
@Autowired
WebSocketSessionController webSocketSessionController;
@Autowired
private SimpMessagingTemplate simpMessagingTemplate;
private Set<Long> services = new HashSet<>();
@SubscribeMapping("/service/{serviceId}")
public ServiceDTO subscribe(@DestinationVariable("serviceId") final Long serviceId) throws SQLException {
System.out.println("Subscribed to Service with ID: " + serviceId);
services.add(serviceId);
return serviceService.getServiceWithProperties(serviceId).orElseThrow(() -> new ResourceNotFoundException("Service", "id", serviceId));
}
@Scheduled(fixedDelay = 2000)
public void service() throws SQLException {
services.removeIf(serviceId -> !webSocketSessionController.hasSubscriptionTo("/topic/service/" + serviceId));
// Publish specified Service data to each anonymously subscribed client.
services.forEach(serviceId -> {
try {
System.out.println("Publishing Service with ID: " + serviceId);
// We don't use .convertAndSendToUser here, because all our clients are anonymous.
simpMessagingTemplate.convertAndSend("/topic/service/" + serviceId, serviceService.getServiceWithProperties(serviceId));
} catch (SQLException e) {
e.printStackTrace();
}
});
}
}
Как узнать, отписался ли клиент? Если что-то похожее на @UnsubscribeMapping существует, я мог бы просто снова установить переменную currentSubscriptionServiceId в null, чтобы предотвратить непрерывную публикацию данных запланированным методом.
@chrylis Вы абсолютно правы. Я начинающий программист, который только начинает работать с Spring/WebSockets, поэтому я все еще учусь. В итоге я использовал ConcurrentHashMap<String, Long> для хранения того, какие клиенты подписаны на какие услуги. Используя ответ @Bertrand Pestre ниже, я смог добиться того, чего хотел :)
Рад, что помогает. Обратите внимание, что это будет хранить состояние навсегда, если вы не очистите его, поэтому в производственной системе вы обычно будете делать что-то вроде сохранения этой информации в Redis или аналогичном бэкэнде ключ-значение, который может истечь записи.
@chrylis Я обновил свой ответ, чтобы отразить ваши предложения. Я отслеживаю подключения/отключения/подписки/отписки (используя ApplicationListeners, как предложено в ответе Бертрана Пестре ниже), чтобы определить, какие клиенты подключены и подписаны на определенные услуги. Если клиент отписывается/отключается, я удаляю его подписки и прекращаю публиковать данные.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Прослушать событие SessionUnsubscribeEvent можно так:
@Controller
public class SessionUnsubscribeListener implements ApplicationListener<SessionUnsubscribeEvent> {
@Override
public void onApplicationEvent(SessionUnsubscribeEvent event) {
GenericMessage message = (GenericMessage) event.getMessage();
String simpDestination = (String) message.getHeaders().get("simpDestination");
if ("/topic/service".equals(simpDestination)) {
// do stuff
}
}
}
Это именно то, что я искал! Большое спасибо :)
К сожалению, message.getHeaders().get("simpDestination")` возвращает null, когда происходит событие SessionUnsubscribeEvent.
@AnonymousAngelo Ну, по крайней мере, слушатель работает! Что касается заголовков, возможно, структура данных немного отличается. Вы можете запустить это в режиме отладки и попытаться заглянуть в объект event, чтобы найти нужную тему.
В итоге я пошел с ConcurrentMap<String, String>, который я назвал subscriptions. Всякий раз, когда клиент подписывается на публикацию, я сохраняю subscriptionId и simpDestination. Таким образом, когда клиент отписывается, я просто удаляю запись карты на основе subscriptionId (которую вы получаете из SessionUnsubscribeEvent).
Я недостаточно знаком с поддержкой STOMP, чтобы рассказать вам о правильном подходе, но я могу сказать, что тот факт, что вы храните глобальное состояние (идентификатор) внутри общего контроллера, указывает на общую проблему дизайна.