SpringBoot STOMP 开发

1,204 阅读3分钟

1.开发准备

假定读者已经有一定的Spring Boot 的基础

并且了解 RabbitMQ 中 Exchange Queue Topic Topic相关概念已经很了解,

可以借此文章更深入的了解如何使用 STOMP 以及 RabbitMQ 来作为消息代理来配置 STOMP集群。

安装 Chrome 插件 ```Apic``` 用来测试 STOMP请求

STOMP 简介

STOMP是一个用于C/S之间进行异步消息传输的简单文本协议, 全称是Simple Text Oriented Messaging Protocol。

RabbitMQ

** RabbitMQ 作为老牌的消息队列功能完善**

RabbitMQ 常用命令

./rabbitmq-server 开启本地rabbitmq 服务

./rabbitmqctl stop 停止本地rabbitmq 服务

./rabbitmqctl add_user {username} {password} 添加用户

./rabbitmqctl set_permissions [-p vhostpath] {user} {conf} {write} {read} 给用户添加权限

开发实战

RabbitMQ 添加 STOMP 插件

./rabbitmq-plugins enable rabbitmq_management rabbitmq_web_stomp rabbitmq_stomp 安装插件,看到下图中红框所示,标识插件安装成功

gradle 添加如下依赖

    implementation 'org.springframework.boot:spring-boot-starter-websocket'
    implementation 'org.springframework.boot:spring-boot-starter-amqp'
    implementation 'org.springframework.boot:spring-boot-starter-reactor-netty'
    implementation 'io.projectreactor:reactor-net:2.0.8.RELEASE'
    implementation 'io.netty:netty-all:4.1.22.Final'

STOMP 配置

@EnableWebSocketMessageBroker
@EnableWebSocket
@Configuration
@Slf4j
public class StompConfig implements WebSocketMessageBrokerConfigurer {

  @Override
  public void registerStompEndpoints(StompEndpointRegistry registry) {
    // 注册连接点 path
    registry.addEndpoint("/ws-endpoint")
        .setHandshakeHandler(new HandshakeHandler() {
          @Override
          public boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response,
              WebSocketHandler wsHandler, Map<String, Object> attributes)
              throws HandshakeFailureException {
            // 如果未登录用户在这里拦截
            return true;
          }
        })
        .setHandshakeHandler(new DefaultHandshakeHandler(){
          @Override
          protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler,
              Map<String, Object> attributes) {
//            return super.determineUser(request, wsHandler, attributes);
            return new Principal() {
              @Override
              public String getName() {
                // TODO 根据session 返回连接的用户米看那个成
                return "default-user";
              }
            };
          }
        })
        .setAllowedOrigins("*");
  }

  @Override
  public void configureMessageBroker(MessageBrokerRegistry registry) {


    // 设置消息代理为RabbitMQ
    registry.enableStompBrokerRelay("/topic")
        .setVirtualHost("/")
        .setClientLogin("guest")
        .setClientPasscode("guest")
        .setSystemLogin("guest")
        .setSystemPasscode("guest")
        .setSystemHeartbeatSendInterval(5000)
        .setSystemHeartbeatReceiveInterval(5000)
        .setRelayHost("127.0.0.1")// RabbitMQ 地址
        .setRelayPort(61613)    // STOMP 端口
        // 设置用户信息注册
        .setUserDestinationBroadcast("/topic/log-unresolved-user")
        .setUserRegistryBroadcast("/topic/log-user-registry");
  }

  @Override
  public void configureWebSocketTransport(WebSocketTransportRegistration registry) {
    registry.addDecoratorFactory(new WebSocketHandlerDecoratorFactory() {
      @Override
      public WebSocketHandler decorate(WebSocketHandler handler) {
        return new WebSocketHandlerDecorator(handler) {
          @Override
          public void afterConnectionEstablished(WebSocketSession session) throws Exception {
            super.afterConnectionEstablished(session);
            log.info("连接建立 " + session);
            // WS 连接建立前
          }

          @Override
          public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus)
              throws Exception {
            super.afterConnectionClosed(session, closeStatus);
            // WS 连接建立后
            log.info("连接断开 " + session);
          }
        };
      }
    });
  }

}
说明

大概介绍一下 SimpUserRegistry 这个类,默认情况下 SimpUserRegistry 的实现是DefaultSimpUserRegistry 这个类型,它是用来管理本地用户的登录信息。

如果在设置消息代理的时候添加了如下代码

    setUserDestinationBroadcast("/topic/log-unresolved-user")
    .setUserRegistryBroadcast("/topic/log-user-registry");

那么它的实现类型将变成MultiServerUserRegistry,可以用来建立STOMP集群之间的用户信息联系。具体原理还需要分析。

给所有用户发送请求


  public void sendAll(){
    messagingTemplate.convertAndSend("/topic/follow", "Test Message");
  }

此时需要监听的路径为 /topic/follow

给指定用户发送请求

 messagingTemplate.convertAndSendToUser("user-demo", "/topic/follow", "Test Message");

此时需要监听的路径为 /user/topic/follow

说明下 destination 的路由以下构成 /{type}/{routingkey}

type 分为

  1. exchange

    对于 SUBCRIBE frame,destination 一般为/exchange//[/pattern] 的形式。 该 destination 会创建一个唯一的、自动删除的随机queue, 并根据 pattern 将该 queue 绑定到所给的 exchange,实现对该队列的消息订阅。

    对于 SEND frame,destination 一般为/exchange//[/routingKey] 的形式。 这种情况下消息就会被发送到定义的 exchange 中,并且指定了 routingKey。

  2. topic

    对于 SUBCRIBE frame,destination 创建出自动删除的、非持久的 queue 并根据 routingkey 为 绑定到 amq.topic exchange 上,同时实现对该 queue 的订阅。

    对于 SEND frame,消息会被发送到 amq.topic exchange 中,routingKey 为。

    对于在页面发送消息的例子跟订阅类似,这里就不再演示。

  3. queue
  4. amp/queue

原代码 github.com/jerboy/Spri…