使用持久队列STOMP向离线用户发送通知

时间:2017-01-25 13:41:52

标签: spring websocket queue subscription stomp

我有这个代码:客户端使用javascript:

   socket = new SockJS(context.backend + '/myWebSocketEndPoint');
   stompClient = Stomp.over(socket);
   stompClient.connect({},function (frame) {
           stompClient.subscribe('/queue/'+clientId+'/notification', function(response){
               alert(angular.fromJson(response.body));
           });
   });

在此代码中,客户端在连接时,使用' / queue /' +他的客户端ID +' / notification /订阅接收通知。所以我为每个客户都排队。我使用stock与sockjs

在我的服务器(Java + spring boot)中,我有一个通知监听器,当事件发布时,它会向所有客户端发送通知。所以我有:

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer{

 @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
    config.enableSimpleBroker("/queue");
}


@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
    registry.addEndpoint("/myWebSocketEndPoint")
            .setAllowedOrigins("*")
            .withSockJS();
}
}

类MenuItemNotificationChannel,它调用MenuItemNotificationSender将通知发送给用户。

@Component
public class MenuItemNotificationChannel extends AbstractNotificationChannel {

@Autowired
private MenuItemNotificationSender menuItemNotificationSender;

@Autowired
private UserRepository userRepository;

@Override
public void sendNotification(KitaiEvent<?> event, Map<String, Object> notificationConfiguration) throws Exception {
    String menuItem = Optional.ofNullable((String) notificationConfiguration.get(MENU_ENTRY_KEY)).orElseThrow(IllegalArgumentException::new);
    List<User> userList = userRepository.findAll();
    for(User u: userList){
        menuItemNotificationSender.sendNotification(new MenuItemDto(menuItem),u.getId());
    }

MenuItemNotificationSender类是:

@Component
public class MenuItemNotificationSender {

@Autowired
private SimpMessagingTemplate messagingTemplate;

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

public void sendNotification(MenuItemDto menuItem,Long id) {
    String address = "/queue/"+id+"/notification";
    messagingTemplate.convertAndSend(address, menuItem);
}
}

此代码完美无缺:将通知发送给每个用户。但是,如果用户不在线,则会丢失通知。我的问题是:

  • 如何验证whit stomp什么订阅是活动的,什么不是? (如果我可以验证订阅是否有效,我解决了我的问题,因为我保存用户离线通知,然后在登录时发送)

  • 我可以使用持久队列吗? (我读了一些关于它的东西,但我不明白我是否只能用它来踩踏板和袜子)

抱歉我的英文! :d

2 个答案:

答案 0 :(得分:1)

您可以在会话连接事件和会话断开连接事件上放置一个spring事件侦听器 我用弹簧4.3.4测试了这个。

@Component
public class WebSocketSessionListener
{
    private static final Logger logger = LoggerFactory.getLogger(WebSocketSessionListener.class.getName());
    private List<String> connectedClientId = new ArrayList<String>();

    @EventListener
    public void connectionEstablished(SessionConnectedEvent sce)
    {
        MessageHeaders msgHeaders = sce.getMessage().getHeaders();
        Principal princ = (Principal) msgHeaders.get("simpUser");
        StompHeaderAccessor sha = StompHeaderAccessor.wrap(sce.getMessage());
        List<String> nativeHeaders = sha.getNativeHeader("userId");
        if( nativeHeaders != null )
        {
            String userId = nativeHeaders.get(0);
            connectedClientId.add(userId);
            if( logger.isDebugEnabled() )
            {
                logger.debug("Connessione websocket stabilita. ID Utente "+userId);
            }
        }
        else
        {
            String userId = princ.getName();
            connectedClientId.add(userId);
            if( logger.isDebugEnabled() )
            {
                logger.debug("Connessione websocket stabilita. ID Utente "+userId);
            }
        }
    }

    @EventListener
    public void webSockectDisconnect(SessionDisconnectEvent sde)
    {
        MessageHeaders msgHeaders = sde.getMessage().getHeaders();
        Principal princ = (Principal) msgHeaders.get("simpUser");
        StompHeaderAccessor sha = StompHeaderAccessor.wrap(sde.getMessage());
        List<String> nativeHeaders = sha.getNativeHeader("userId");
        if( nativeHeaders != null )
        {
            String userId = nativeHeaders.get(0);
            connectedClientId.remove(userId);
            if( logger.isDebugEnabled() )
            {
                logger.debug("Disconnessione websocket. ID Utente "+userId);
            }
        }
        else
        {
            String userId = princ.getName();
            connectedClientId.remove(userId);
            if( logger.isDebugEnabled() )
            {
                logger.debug("Disconnessione websocket. ID Utente "+userId);
            }
        }
    }

    public List<String> getConnectedClientId()
    {
        return connectedClientId;
    }
    public void setConnectedClientId(List<String> connectedClientId)
    {
        this.connectedClientId = connectedClientId;
    }
}

连接客户端时,在客户端ID中添加客户端ID;断开连接时将其删除

然后,您可以将此bean或其List注入您要检查客户端是否处于活动状态的位置,然后您可以检查客户端ID是否在您可以发送消息的已连接客户端ID之间,否则您必须保存它并稍后重新发送

在客户端,您可以执行以下操作:

var socket = new SockJS('/ws');
stompClient = Stomp.over(socket);
stompClient.connect({userId:"customUserId"}, function (frame) {
});

安吉洛

答案 1 :(得分:0)

为什么不使用下面的某些事件,您可以将类导出到不同的文件,并使用SessionConnectedEventSessionDisconnectEventSessionSubscribeEventSessionUnsubscribeEvent。 请参阅此处的文档http://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html#websocket-stomp-appplication-context-events

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.messaging.SessionConnectedEvent;
import org.springframework.web.socket.messaging.SessionDisconnectEvent;
import org.springframework.web.socket.messaging.SessionSubscribeEvent;
import org.springframework.web.socket.messaging.SessionUnsubscribeEvent;

@Component
public class SessionConnectedListener extends SessionsListener implements ApplicationListener<SessionConnectedEvent> {

    @Override
    public void onApplicationEvent(SessionConnectedEvent event) {
        users.add(event.getUser().getName());
    }

}

@Component
class SessionDisconnectListener extends SessionsListener implements ApplicationListener<SessionDisconnectEvent> {

    @Override
    public void onApplicationEvent(SessionDisconnectEvent event) {
        users.remove(event.getUser().getName());
    }
}

@Component
class SessionSubscribeListener extends SessionsListener implements ApplicationListener<SessionSubscribeEvent> {

    @Override
    public void onApplicationEvent(SessionSubscribeEvent event) {
        users.add(event.getUser().getName());
    }
}

@Component
class SessionUnsubscribeListener extends SessionsListener implements ApplicationListener<SessionUnsubscribeEvent> {

    @Override
    public void onApplicationEvent(SessionUnsubscribeEvent event) {
        users.remove(event.getUser().getName());
    }
}

class SessionsListener {

    protected List<String> users = Collections.synchronizedList(new LinkedList<String>());

    public List<String> getUsers() {
        return users;
    }
}

并更改您的代码:

@Autowired
private SessionsListener sessionsListener;

@Override
public void sendNotification(KitaiEvent<?> event, Map<String, Object> notificationConfiguration) throws Exception {
    String menuItem = Optional.ofNullable((String) notificationConfiguration.get(MENU_ENTRY_KEY)).orElseThrow(IllegalArgumentException::new);
    List<String> userList = sessionsListener.getUsers();
    for(String u: userList){
        menuItemNotificationSender.sendNotification(new MenuItemDto(menuItem),u);
    }