Springboot+RabbitMQ + WebSocket 給前端推送消息 + 定時隊列

環境 :
JDK : 1.8
Springboot : 2.1.6.RELEASE
ErLang : otp_win64_22.0.exe
RabbitMQ : 3.7.16

下載地址

開啓RabbitMQ的stomp插件 .
在RabbitMQ安裝目錄sbin文件夾裏執行命令 :

rabbitmq-plugins enable rabbitmq_stomp

Springboot pom.xml引入RabbitMQ 和 WebSocket

	<!--RabbitMQ-->
    <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-amqp</artifactId>
     </dependency>
     <!--WebSocket-->
     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-websocket</artifactId>
     </dependency>
     <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.46</version>
    </dependency>
    <!--JSON-->
    <dependency>
        <groupId>net.sf.json-lib</groupId>
        <artifactId>json-lib</artifactId>
        <version>2.4</version>
        <classifier>jdk15</classifier>
    </dependency>

application.yml

 # RabbitMQ 配置
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
    virtual-host: /
    # 開啓消息發送確認
    publisher-confirms: true
    publisher-returns: true
    listener:
      simple:
        acknowledge-mode: manual

RabbitMQ

配置類

import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;


/**
 * @ClassName RabbitConfig
 * @Description RabbitMQ 配置類
 * @Date 2019-07-20 11:44
 * @Version 1.0.0
 **/
@Configuration
public class RabbitConfig {

    //websocket 消息隊列
    public static final String msg_queue = "msg_queue";

    //消息交換機
    public static final String msg_exchang = "msg_exchang";

    //消息路由鍵
    public static final String msg_routing_key = "msg_routing_key";

    /**
     * 消息隊列
     * @return
     */
    @Bean
    public Queue msgQueue(){
        return new Queue(msg_queue);
    }

    @Bean
    public DirectExchange directExchange(){
        return new DirectExchange(msg_exchang);
    }

    /**
     * 消息隊列綁定消息交換機
     * @return
     */
    @Bean
    public Binding msgBinding(){
        return BindingBuilder.bind(msgQueue()).to(directExchange()).with(msg_routing_key);
    }
}

消息提供者

import com.alibaba.fastjson.JSONObject;
import com.lawyer.entity.Msg;
import com.lawyer.webSocket.WebSocketServerEndpoint;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.UUID;

/**
 * @ClassName RabbitConsultProduct
 * @Description RabbitMQ 消息提供者
 * @Date 2019-07-20 11:54
 * @Version 1.0.0
 **/
@Slf4j
@Component
public class RabbitProduct {

    @Resource
    private RabbitTemplate rabbitTemplate;

    /**
     * 構造方法注入rabbitTemplate
     */
    @Autowired
    public RabbitProduct(RabbitTemplate rabbitTemplate){
        this.rabbitTemplate = rabbitTemplate;
    }



    //發送消息 推送到websocket    參數自定義 轉爲String發送消息
    public void sendMSG(Msg msg){
        CorrelationData correlationId = new CorrelationData(UUID.randomUUID().toString());
        //把消息對象放入路由對應的隊列當中去
        rabbitTemplate.convertAndSend(RabbitConfig.msg_exchang,RabbitConfig.msg_routing_key, JSONObject.toJSON(msg).toString(), correlationId);
    }

}

消息消費者

import com.lawyer.entity.Msg;
import com.lawyer.utils.DateUtils;
import com.lawyer.webSocket.WebSocketServerEndpoint;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.AcknowledgeMode;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.annotation.RabbitListenerConfigurer;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistrar;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
import org.springframework.messaging.handler.annotation.support.DefaultMessageHandlerMethodFactory;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.IOException;

/**
 * @ClassName RabbitReceive
 * @Description RabbitMQ 定時消息隊列 消費監聽回調
 * @Date 2019-07-20 12:09
 * @Version 1.0.0
 **/
@Slf4j
@Component
public class RabbitConsumer {

    private static RabbitConsumer rabbitConsumer;
    @Resource
    private WebSocketServerEndpoint webSocketServerEndpoint; //引入WebSocket

    /**
     * 構造方法注入rabbitTemplate
     */
    @PostConstruct
    public void init() {
        rabbitConsumer = this;
        rabbitConsumer.webSocketServerEndpoint = webSocketServerEndpoint;
    }

    @RabbitListener(queues = RabbitConfig.msg_queue) //監聽隊列
    public void msgReceive(String content, Message message, Channel channel) throws IOException {
        log.info("----------------接收到消息--------------------"+content);
        //發送給WebSocket 由WebSocket推送給前端
        rabbitConsumer.webSocketServerEndpoint.sendMessageOnline(content);
        // 確認消息已接收
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }

}

WebSocket配置

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
 * WebSocket配置類
 */
@Configuration
public class WebSocketConfig {

    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }

}
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;


/**
 *  WebSocket 服務配置類
 *  定義 userId 爲當前連接(在線) WebSocket 的用戶
 */
@Slf4j
@Component
@ServerEndpoint(value = "/ws/{userId}")
public class WebSocketServerEndpoint {

    private Session session; //建立連接的會話
    private String userId; //當前連接用戶id   路徑參數

    /**
     * 存放存活的Session集合(map保存)
     */
    private static ConcurrentHashMap<String , WebSocketServerEndpoint> livingSession = new ConcurrentHashMap<>();

    /**
     *  建立連接的回調
     *  session 建立連接的會話
     *  userId 當前連接用戶id   路徑參數
     */
    @OnOpen
    public void onOpen(Session session,  @PathParam("userId") String userId){
        this.session = session;
        this.userId = userId;
        livingSession.put(userId, this);

        log.debug("----[ WebSocket ]---- 用戶id爲 : {} 的用戶進入WebSocket連接 ! 當前在線人數爲 : {} 人 !--------",userId,livingSession.size());
    }

    /**
     *  關閉連接的回調
     *  移除用戶在線狀態
     */
    @OnClose
    public void onClose(){
        livingSession.remove(userId);
        log.debug("----[ WebSocket ]---- 用戶id爲 : {} 的用戶退出WebSocket連接 ! 當前在線人數爲 : {} 人 !--------",userId,livingSession.size());
    }

    @OnMessage
    public void onMessage(String message, Session session,  @PathParam("userId") String userId) {
        log.debug("--------收到用戶id爲 : {} 的用戶發送的消息 ! 消息內容爲 : ------------------",userId,message);
        //sendMessageToAll(userId + " : " + message);
    }

    @OnError
    public void onError(Session session, Throwable error) {
        log.error("----------------WebSocket發生錯誤----------------");
        log.error(error.getStackTrace() + "");
    }

    /**
     *  根據userId發送給用戶
     * @param userId
     * @param message
     */
    public void sendMessageById(String userId, String message) {
        livingSession.forEach((sessionId, session) -> {
            //發給指定的接收用戶
            if (userId.equals(session.userId)) {
                sendMessageBySession(session.session, message);
            }
        });
    }

    /**
     *  根據Session發送消息給用戶
     * @param session
     * @param message
     */
    public void sendMessageBySession(Session session, String message) {
        try {
            session.getBasicRemote().sendText(message);
        } catch (IOException e) {
            log.error("----[ WebSocket ]------給用戶發送消息失敗---------");
            e.printStackTrace();
        }
    }

    /**
     *  給在線用戶發送消息
     * @param message
     */
    public void sendMessageOnline(String message) {
        livingSession.forEach((sessionId, session) -> {
            if(session.session.isOpen()){
                sendMessageBySession(session.session, message);
            }
        });
    }

}

前端VUE代碼

created() {
    var websocket = null
    if ('WebSocket' in window) {
      var protocol = window.location.protocol === 'http:' ? 'ws://' : 'wss://'
      websocket = new WebSocket(protocol + 'localhost:8090/ws/0/1')
    } else {
      alert('該瀏覽器不支持WebSocket')
    }

    websocket.onopen = function(event) {
      // heartCheck.reset().start();
      console.log('建立WebSocket連接')
    }

    websocket.onclose = function(event) {
      console.log('斷開WebSocket連接')
    }

    websocket.onmessage = function(event) {
      console.log('收到消息' + event.data)
    }

    websocket.onerror = function(event) {
      console.log('websocket通信發生錯誤')
    }

    window.onbeforeunload = function(event) {
      websocket.close()
    }
  },
  methods: {
    handleClickOutside() {
      this.$store.dispatch('app/closeSideBar', { withoutAnimation: false })
    }
  }

這裏需要做心跳連接機制 :
自行設置

var heartCheck = {
        timeout: 55000,        // 在接近斷開的情況下以通信的方式去重置連接時間。
        timeoutObj: null,
        serverTimeoutObj: null,
        reset: function(){
            clearTimeout(this.timeoutObj);
            clearTimeout(this.serverTimeoutObj);
            return this;
        },
        start: function(){
            this.serverTimeoutObj = setInterval(function(){
                if(websocket.readyState == 1){
                    console.log("連接狀態,發送消息保持連接");
                    websocket.send("ping");
                    heartCheck.reset().start();    // 如果獲取到消息,說明連接是正常的,重置心跳檢測
                }else{
                    console.log("斷開狀態,嘗試重連");
                }
            }, this.timeout)
        }
    }

延遲(定時)隊列文章地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章