spring rabbitmq stomp 实现实时通信

167 阅读5分钟

spring 实现stomp协议的websocket代理,以rabbitmq为代理服务,实现多客户端的实时通信。

核心代码

spring创建基于rabbitmq的代理通信服务。

import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Configuration;import org.springframework.messaging.simp.config.ChannelRegistration;import org.springframework.messaging.simp.config.MessageBrokerRegistry;import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;import org.springframework.web.socket.config.annotation.StompEndpointRegistry;import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;@Configuration@EnableWebSocketMessageBrokerpublic class WebSocketConfig implements WebSocketMessageBrokerConfigurer {    @Autowired    private  HeaderParamInterceptor headerParamInterceptor;    @Autowired    private OutInterceptor outInterceptor;

   //创建websocket接口.解决跨域
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {        registry.addEndpoint("/ws")                .setAllowedOriginPatterns("*")                .withSockJS();//定义前端采用socketjs实现,若前后端通过nginx代理,需要注掉。    }    @Override    public void configureClientInboundChannel(ChannelRegistration registration) {      registration.interceptors(headerParamInterceptor);    }    @Override    public void configureClientOutboundChannel(ChannelRegistration registration) {        registration.interceptors(outInterceptor);    }        @Override    public void configureMessageBroker(MessageBrokerRegistry registry) {        //destination 目的地址 的前缀,下面会针对不同的前缀进行实际代码配置        registry.enableStompBrokerRelay("/topic")                .setAutoStartup(true)                .setRelayHost("192.168.20.211")                .setRelayPort(61613)                .setVirtualHost("/")                .setClientLogin("xx")                .setClientPasscode("xxxxx)                .setSystemLogin("xx")                .setSystemPasscode("xxxxx")                .setSystemHeartbeatReceiveInterval(5000)                .setSystemHeartbeatSendInterval(4000);    }}

js核心代码

let socket = new SockJS('http://localhost:8090/ws');stompClient = Stomp.over(socket);stompClient.connect({username: username}, onConnected, onError);

function onError(error) {    console.log(error)}function onError(error) {    console.log(error)}function onConnected() {    //MQ代理 enableStompBrokerRelay  destinationPrefixes 为 /exchange    // stompClient.subscribe('/exchange/customer.exchange/customer.exchange.routkey', function (payload) {    //    //     console.log('/exchange/customer.exchange/customer.exchange.routkey', payload)    //    //     stompClient.send("/exchange/customer.exchange/customer.exchange.response", {}, JSON.stringify({    //         "key":"/exchange/customer.exchange/customer.exchange.routkey"    //     }));    // });    // stompClient.subscribe('/exchange/amq.topic/default.topic.exchange.routkey', function (payload) {    //     console.log('/exchange/amq.topic/default.topic.exchange.routkey', payload)    //    //     stompClient.send("/exchange/amq.topic/default.topic.exchange.response", {}, JSON.stringify({    //         "key":"/exchange/amq.topic/default.topic.exchange.response"    //     }));    // });    // MQ代理 enableStompBrokerRelay  destinationPrefixes 为 /queue  /amq/queue    // stompClient.subscribe('/queue/default.exchange.queue', function (payload) {    //         console.log('/queue/default.exchange.queue', payload)    //    //         stompClient.send("/queue/default.exchange.response", {}, JSON.stringify({    //             "key":"/queue/default.exchange.queue"    //         }));    // });    stompClient.subscribe('/amq/queue/default.exchange.queue2', function (payload) {        console.log('/amq/queue/default.exchange.queue2', payload)        stompClient.send("/amq/queue/default.exchange.response2", {}, JSON.stringify({            "key":"/amq/queue/default.exchange.queue2"        }));    });    //    // //MQ代理 enableStompBrokerRelay  destinationPrefixes 为 /topic    stompClient.subscribe('/topic/topic.exchange.routkey', function (payload) {        console.log('/topic/topic.exchange.routkey', payload)        stompClient.send("/topic/topic.exchange.routkey.response", {}, JSON.stringify({            "key":"/topic/topic.exchange.routkey"        }));    });}function onConnected() {    //MQ代理 enableStompBrokerRelay  destinationPrefixes 为 /exchange    // stompClient.subscribe('/exchange/customer.exchange/customer.exchange.routkey', function (payload) {    //    //     console.log('/exchange/customer.exchange/customer.exchange.routkey', payload)    //    //     stompClient.send("/exchange/customer.exchange/customer.exchange.response", {}, JSON.stringify({    //         "key":"/exchange/customer.exchange/customer.exchange.routkey"    //     }));    // });    // stompClient.subscribe('/exchange/amq.topic/default.topic.exchange.routkey', function (payload) {    //     console.log('/exchange/amq.topic/default.topic.exchange.routkey', payload)    //    //     stompClient.send("/exchange/amq.topic/default.topic.exchange.response", {}, JSON.stringify({    //         "key":"/exchange/amq.topic/default.topic.exchange.response"    //     }));    // });    // MQ代理 enableStompBrokerRelay  destinationPrefixes 为 /queue  /amq/queue    // stompClient.subscribe('/queue/default.exchange.queue', function (payload) {    //         console.log('/queue/default.exchange.queue', payload)    //    //         stompClient.send("/queue/default.exchange.response", {}, JSON.stringify({    //             "key":"/queue/default.exchange.queue"    //         }));    // });    stompClient.subscribe('/amq/queue/default.exchange.queue2', function (payload) {        console.log('/amq/queue/default.exchange.queue2', payload)        stompClient.send("/amq/queue/default.exchange.response2", {}, JSON.stringify({            "key":"/amq/queue/default.exchange.queue2"        }));    });    //    // //MQ代理 enableStompBrokerRelay  destinationPrefixes 为 /topic    stompClient.subscribe('/topic/topic.exchange.routkey', function (payload) {        console.log('/topic/topic.exchange.routkey', payload)        stompClient.send("/topic/topic.exchange.routkey.response", {}, JSON.stringify({            "key":"/topic/topic.exchange.routkey"        }));    });}

采用mq作为代理服务时,代理设置不同的destinationPrefixes,针对rabbitmq的不用exchange,routkey,queue的设置。

 /exchange 

通过交换机订阅/发布消息, 

 a. /exchange:固定值 

 b. exchangename:交换机名称 

 c. [routing_key]:路由键,可选 

对于接收者端,该 destination 会创建一个唯一的、自动删除的随机queue, 并根据 routing_key将该 queue 绑定到所给的 exchangename,实现对该队列的消息订阅。

 对于发送者端,消息就会被发送到定义的 exchange中,并且指定了 routing_key。

  spring 配置

websocketMessageBrokerConfigurer 

@Overridepublic void configureMessageBroker(MessageBrokerRegistry registry) {    //  /exchange 指定broker响应前缀    registry.enableStompBrokerRelay("/exchange")            .setAutoStartup(true)            .setRelayHost("192.168.20.211") //mq 地址            .setRelayPort(61613)//mq stomp支持的端口            .setVirtualHost("/")            .setClientLogin("xxxx")            .setClientPasscode("xxxx")            .setSystemLogin("xx")            .setSystemPasscode("xxxxxxx")            .setSystemHeartbeatReceiveInterval(5000)            .setSystemHeartbeatSendInterval(4000);}

测试发送消息

/** * rabbbitmq * 向 默认 exchange 交换器 发送消息, * 根据routkey 路由键 创建 queue 队列, 队列名称就是路由键 * 队列属性 默认为 持久化 * * @param routKey */@GetMapping("/customertExchange")public void sendToCustomerExchage(String exchanage , String routKey){    JSONObject msg = new JSONObject();    msg.put("exchange",exchanage);    msg.put("routkey",routKey);    msg.put("queue",routKey);    msg.put("msg","向默认exchanage 发送消息");    rabbitTemplate.convertAndSend(exchanage,routKey,msg.toJSONString());}
/** * rabbbitmq * 向 默认 exchange 交换器 发送消息, * 根据routkey 路由键 创建 queue 队列, 队列名称就是路由键 * 队列属性 默认为 持久化 * * @param routKey */@GetMapping("/defaultTopicExchange")public void sendToDefaultTopicExchage( String routKey){    JSONObject msg = new JSONObject();    msg.put("exchange","amq.topic");    msg.put("routkey",routKey);    msg.put("queue",routKey);    msg.put("msg","向默认exchanage 发送消息");    rabbitTemplate.convertAndSend("amq.topic",routKey,msg.toJSONString());}

@RabbitListener(bindings =            @QueueBinding(value = @Queue(value = "one"),                         exchange = @Exchange(value = "customer.exchange",type = ExchangeTypes.TOPIC),                         key="customer.exchange.response"))public void  handler1(String msg){    log.info("/exchange/customer.exchange/customer.exchange.response--------------------");    log.info(msg);    log.info("/exchange/customer.exchange/customer.exchange.response+++++++++++++++++++++");}@RabbitListener(bindings =@QueueBinding(value = @Queue(value = "two"),        exchange = @Exchange(value = "amq.topic",type = ExchangeTypes.TOPIC),        key="default.topic.exchange.response"))public void  handler2(String msg){    log.info("/exchange/amq.topic/default.topic.exchange.response--------------------");    log.info(msg);    log.info("/exchange/amq.topic/default.topic.exchange.response+++++++++++++++++++++");

 js 代码

stompClient.subscribe('/exchange/customer.exchange/customer.exchange.routkey', function (payload) {    console.log('/exchange/customer.exchange/customer.exchange.routkey', payload)    stompClient.send("/exchange/customer.exchange/customer.exchange.response", {}, JSON.stringify({        "key":"/exchange/customer.exchange/customer.exchange.
"    }));});
stompClient.subscribe('/exchange/amq.topic/default.topic.exchange.routkey', function (payload) {    console.log('/exchange/amq.topic/default.topic.exchange.routkey', payload)    stompClient.send("/exchange/amq.topic/default.topic.exchange.response", {}, JSON.stringify({        "key":"/exchange/amq.topic/default.topic.exchange.response"    }));});

  

 /queue/queuename

使用 默认交换机 订阅/发布消息,默认由stomp自动创建一个持久化队列,参数说明
a. /queue:固定值
b. queuename:自动创建一个持久化队列

对于接收者端,订阅队列queuename的消息
对于接收者端,向queuename发送消息,destination 只会在第一次发送消息的时候会创建一个持久化队列。

 spring 配置

websocketMessageBrokerConfigurer

@Overridepublic void configureMessageBroker(MessageBrokerRegistry registry) {    registry.enableStompBrokerRelay("/queue")            .setAutoStartup(true)            .setRelayHost("192.168.20.211")            .setRelayPort(61613)            .setVirtualHost("/")            .setClientLogin("xxx")            .setClientPasscode("xxxxx")            .setSystemLogin("xx")            .setSystemPasscode("xxxxxx")            .setSystemHeartbeatReceiveInterval(5000)            .setSystemHeartbeatSendInterval(4000);}

 测试发送消息

/** * rabbbitmq * 向 默认 exchange 交换器 发送消息, * 根据routkey 路由键 创建 queue 队列, 队列名称就是路由键 * 队列属性 默认为 持久化 * * @param routKey */@GetMapping("/defaultExchange")public void sendToDefaultExchage(String routKey){    JSONObject msg = new JSONObject();    msg.put("routkey",routKey);    msg.put("queue",routKey);    msg.put("msg","向默认exchanage 发送消息");    rabbitTemplate.convertAndSend(routKey,msg.toJSONString());}

@RabbitListener(queues = "default.exchange.response")public void  handler3(String msg){    log.info("/queue/default.exchange.response--------------------");    log.info(msg);    log.info("/queue/default.exchange.response+++++++++++++++++++++");}

js代码

stompClient.subscribe('/queue/default.exchange.queue', function (payload) {        console.log('/queue/default.exchange.queue', payload)        stompClient.send("/queue/default.exchange.response", {}, JSON.stringify({            "key":"/queue/default.exchange.queue"        }));});

 /amq/queue/queuename

使用 默认交换机 订阅/发布消息

a. /amq/queue:固定值
b. queuename:队列名

对于接收者端,订阅队列queuename的消息,队列不存在时,会报错。
对于接收者端,向queuename发送消息,队列不存在时,发送成功,但是不会创建队列。

与/queue/queuename的区别在于队列不由stomp自动进行创建。

 spring 配置

websocketMessageBrokerConfigurer

@Overridepublic void configureMessageBroker(MessageBrokerRegistry registry) {    registry.enableStompBrokerRelay("/amq/queue")            .setAutoStartup(true)            .setRelayHost("192.168.20.211")            .setRelayPort(61613)            .setVirtualHost("/")            .setClientLogin("xx")            .setClientPasscode("xxxxx.com")            .setSystemLogin("xxx")            .setSystemPasscode("xxxxx")            .setSystemHeartbeatReceiveInterval(5000)            .setSystemHeartbeatSendInterval(4000);}

测试发送消息

/** * rabbbitmq * 向 默认 exchange 交换器 发送消息, * 根据routkey 路由键 创建 queue 队列, 队列名称就是路由键 * 队列属性 默认为 持久化 * * @param routKey */@GetMapping("/defaultExchange")public void sendToDefaultExchage(String routKey){    JSONObject msg = new JSONObject();    msg.put("routkey",routKey);    msg.put("queue",routKey);    msg.put("msg","向默认exchanage 发送消息");    rabbitTemplate.convertAndSend(routKey,msg.toJSONString());}

@RabbitListener(queues = "default.exchange.response2")public void  handler4(String msg){    log.info("/queue/default.exchange.response2--------------------");    log.info(msg);    log.info("/queue/default.exchange.response2+++++++++++++++++++++");}

js代码

stompClient.subscribe('/amq/queue/default.exchange.queue2', function (payload) {    console.log('/amq/queue/default.exchange.queue2', payload)    stompClient.send("/amq/queue/default.exchange.response2", {}, JSON.stringify({        "key":"/amq/queue/default.exchange.queue2"    }));});

/topic/routing_key

通过amq.topic交换机订阅/发布消息,订阅时默认创建一个临时队列(队列属性为自动删除),通过routing_key与topic进行绑定
a. /topic:固定前缀
b. routing_key:路由键

对于接收者端,订阅时默认创建一个临时队列,通过routing_key与topic进行绑定,接收者断开后,临时队列对自动删除。

对于发送者端,消息会被发送到 amq.topic 交换机中,不会自动创建队列。

 spring 配置

websocketMessageBrokerConfigurer

@Overridepublic void configureMessageBroker(MessageBrokerRegistry registry) {    registry.enableStompBrokerRelay("/topic")            .setAutoStartup(true)            .setRelayHost("192.168.20.211")            .setRelayPort(61613)            .setVirtualHost("/")            .setClientLogin("xxx")            .setClientPasscode("xxxxxxx")            .setSystemLogin("xxxx")            .setSystemPasscode("xxxxxx")            .setSystemHeartbeatReceiveInterval(5000)            .setSystemHeartbeatSendInterval(4000);}

测试发送消息

/** * rabbbitmq * 向 默认 exchange 交换器 发送消息, * 根据routkey 路由键 创建 queue 队列, 队列名称就是路由键 * 队列属性 默认为 持久化 * * @param routKey */@GetMapping("/defaultTopicExchange")public void sendToDefaultTopicExchage( String routKey){    JSONObject msg = new JSONObject();    msg.put("exchange","amq.topic");    msg.put("routkey",routKey);    msg.put("queue",routKey);    msg.put("msg","向默认exchanage 发送消息");    rabbitTemplate.convertAndSend("amq.topic",routKey,msg.toJSONString());}

js代码

stompClient.subscribe('/topic/topic.exchange.routkey', function (payload) {    console.log('/topic/topic.exchange.routkey', payload)    stompClient.send("/topic/topic.exchange.routkey.response", {}, JSON.stringify({        "key":"/topic/topic.exchange.routkey"    }));});