Springboot+Websocket+Stomp實現PC兼容,及微信小程序連接

導入maven依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

配置websocket

import org.springframework.context.annotation.Configuration;
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
@EnableWebSocketMessageBroker  //註解開啓STOMP協議來傳輸基於代理的消息,此時控制器支持使用@MessageMapping
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic", "/user");//topic用來廣播,user用來實現p2p
        //點對點使用的訂閱前綴(客戶端訂閱路徑上會體現出來),不設置的話,默認也是/user/
        config.setUserDestinationPrefix("/user");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
    	//使用sockjs兼容pc端各大瀏覽器(微信小程序連接不了)
        registry.addEndpoint("/webServer").setAllowedOrigins("*").addInterceptors().withSockJS();
        //小程序連接,重點在withSockJS()
        registry.addEndpoint("/wxServer").setAllowedOrigins("*").addInterceptors();
    }

}

發送消息(點對點)

    @Autowired
    public SimpMessagingTemplate messagingTemplate;

    @GetMapping("/send/test1")
    public Result sendTest1() {
        MsgCarrier carrier = MsgCarrier.build().withType(MessageType.ORDER_CONFIRM)
                .withMsg("發送單人成功").withData(new Date());
        messagingTemplate.convertAndSendToUser("123456", "/wx/push/singleton", carrier);
        return Result.success("發送成功!");
    }

發送消息(一對多)

    @Autowired
    public SimpMessagingTemplate messagingTemplate;

    public Result sendTest2() {
	     MsgCarrier carrier = MsgCarrier.build().withType(MessageType.ORDER_CONFIRM)
	             .withMsg("發送多人成功").withData(new Date());
	     messagingTemplate.convertAndSend("/topic/wx/push/all", carrier);
	     return Result.success("發送成功!");
    }

##在線人數計數器

import org.springframework.stereotype.Component;

/**
 * 在線人數計數器(線程安全)
 *
 * @Author xs
 * @Date 2019/4/23 10:43
 */
@Component
public class Counter {

    private Long onlineCount = 0L;

    /**
     * 在線人數增長
     */
    public synchronized void incrementCount(){
        onlineCount++;
    }

    /**
     * 在線人數減少
     */
    public synchronized void decrementCount(){
        onlineCount--;
    }

    /**
     * 獲取在線人數
     * @return 在線人數
     */
    public Long getOnlineCount(){
        if(this.onlineCount <= 0){
            return 0L;
        }
        return this.onlineCount;
    }

}

消息載體實體類

import com.yikesong.favourablelife.pojo.enums.MessageType;

import java.io.Serializable;
import java.util.Date;

/**
 * websocket 消息載體
 *
 * @Author xs
 * @Date 2019/6/15 11:01
 */
public class MsgCarrier implements Serializable {

    private MessageType type;//消息類型

    private String msg;//消息內容

    private Object data;//消息載體數據

    private Date time;//消息時間

    public Date getTime() {
        return time;
    }

    public void setTime(Date time) {
        this.time = time;
    }

    public MessageType getType() {
        return type;
    }

    public void setType(MessageType type) {
        this.type = type;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public static MsgCarrier build() {
        MsgCarrier msgCarrier = new MsgCarrier();
        msgCarrier.setTime(new Date());
        return msgCarrier;
    }

    public MsgCarrier withType(MessageType type) {
        this.type = type;
        return this;
    }


    public MsgCarrier withData(Object data) {
        this.data = data;
        return this;
    }

    public MsgCarrier withMsg(String msg) {
        this.msg = msg;
        return this;
    }

    public MsgCarrier withTime(Date time) {
        this.time = time;
        return this;
    }

}

小程序端代碼實現

1.Socket工具類(重連機制)
/* Created by zfh on 2018/11/2 */
import { Stomp } from "./stomp";

class WebSocket {
  /**
   * 微信 WebSocket 任務
   */
  socketTask = null;

  /**
   * Stomp代理
   */
  stompClient = null;

  /**
   * 默認監聽的消息頻道
   */
  channel = null;

  /**
   * 消息監聽器
   */
  messageMonitor = null;

  /**
   * 消息處理器
   */
  messageHandler = null;

  /**
   * 重連成功的回調
   */
  reconnectCallback = null;

  /**
   * 主動斷開連接的標識
   */
  disconnectFlag = false;

  /**
   * 默認最大重連次數
   */
  RECONNECT_MAX_COUNT = 50;

  /**
   * 默認重連時間間隔(單位:ms)
   */
  RECONNECT_TIME_INTERVAL = 1500;

  /**
   * 斷線重連計數
   */
  RECONNECT_COUNT = 0;

  constructor() {
    /*setInterval是用來發心跳包的,而小程序沒有window對象*/
    Stomp.setInterval = function (interval, f) {
      return setInterval(f, interval);
    };
    Stomp.clearInterval = function (id) {
      return clearInterval(id);
    };
  }

  /**
   * 建立websocket連接和頻道監聽,綁定消息處理器
   * @param header            消息頭
   * @param webSocketUrl      連接地址
   * @param channel           監聽的頻道
   * @param messageHandler    消息處理器
   * @param reconnectCallback 成功回調
   */
  bulidConnectAndMonitor(header, webSocketUrl, channel, messageHandler, reconnectCallback) {
    var that = this;
    if (!this.getSocketStatus()) {
      var socketTask = wx.connectSocket({
        url: webSocketUrl
      });
      var ws = {
        send: function (frame) {
          socketTask.send({ data: frame });
        },
        close: function (frame) {
          socketTask.close(frame);
        }
      };
      socketTask.onOpen(function (frame) {
        ws.onopen(frame);
        if (that.RECONNECT_COUNT > 0) {
          that.reconnectCallback()
        }
        that.RECONNECT_COUNT = 0;
        console.log("websocket連接成功");
      });
      socketTask.onMessage(function (frame) {
        ws.onmessage(frame);
      });
      socketTask.onClose(function (frame) {
        that.stompClient._cleanUp();
        /*客戶端主動斷開連接,不啓動重連。*/
        if (that.disconnectFlag) {
          that.disconnectFlag = false;
          console.log("websocket斷開連接");
          return;
        }
        /*因爲是遞歸,所以使用setTimeout()來做定時器*/
        setTimeout(function () {
          that.RECONNECT_COUNT += 1;
          console.log("重連次數:", that.RECONNECT_COUNT);
          if (that.RECONNECT_COUNT >= that.RECONNECT_MAX_COUNT) {
            console.log("websocket連接失敗");
            return;
          }
          that.bulidConnectAndMonitor({}, webSocketUrl, that.channel, that.messageHandler, that.reconnectCallback);
        }, that.RECONNECT_TIME_INTERVAL);
      });
      var stompClient = Stomp.over(ws);
      that.stompClient = stompClient;
      stompClient.connect(
        header,
        function () {
          that.messageMonitor = stompClient.subscribe(channel, messageHandler);
          that.socketTask = socketTask;
          that.channel = channel;
          that.messageHandler = messageHandler;
          that.reconnectCallback = reconnectCallback;
          reconnectCallback('默認通道已開啓');
          console.log("默認監聽的頻道:", channel);
        }
      );
    }
  }

  /**
   * 設置默認消息監聽器
   * @param messageHandler    消息處理器
   * @param reconnectCallback 重連成功的回調
   */
  setDefaultMessageMonitor(messageHandler, reconnectCallback) {
    if (this.getSocketStatus()) {
      this.removeDefaultMessageMonitor();
      this.messageMonitor = this.stompClient.subscribe(this.channel, messageHandler);
      /*更新消息處理器*/
      this.messageHandler = messageHandler;
      /*更新重連的回調*/
      this.reconnectCallback = reconnectCallback;
      console.log("默認監聽頻道:", this.channel);
    }
  }

  /**
   * 移除默認消息監聽器
   */
  removeDefaultMessageMonitor() {
    if (this.getSocketStatus()) {
      this.messageMonitor.unsubscribe();
      console.log("The default listener was removed successfully");
    }
  }

  /**
   * 自定義消息監聽器
   * @param channel         監聽的頻道
   * @param messageHandler  消息處理器
   * @return messageMonitor 消息監聽器
   */
  addMessageMonitor(channel, messageHandler) {
    console.log("addMessageMonitor Socket狀態",this.getSocketStatus());
    if (this.getSocketStatus()) {
      console.log("新監聽頻道:", channel);
      return this.stompClient.subscribe(channel, messageHandler);
    }
  }

  /**
   * 移除消息監聽器
   * @param messageMonitor 消息監聽器
   */
  removeMessageMonitor(messageMonitor) {
    if (messageMonitor == null || JSON.stringify(messageMonitor) === '{}') {
      console.log("監聽器不能爲空");
      return;
    }
    if (this.getSocketStatus()) {
      messageMonitor.unsubscribe();
      console.log("The listener was removed successfully");
    }
  }

  /**
   * 發送消息
   * @param channel 頻道
   * @param header  消息頭
   * @param body    消息體
   */
  sendMessage(channel, header, body) {
    if (this.getSocketStatus()) {
      this.stompClient.send(channel, header, JSON.stringify(body));
    }
  }

  /**
   * 關閉連接
   */
  close() {
    if (this.getSocketStatus()) {
      this.stompClient.disconnect();
      this.disconnectFlag = true;
    }
  }

  /**
   * 獲取連接狀態
   * @return boolean
   */
  getSocketStatus() {
    var boolean = false;
    if (this.socketTask && this.socketTask.readyState) {
      boolean = this.socketTask.readyState === 1;
    }
    console.log("websocket連接狀態:" + boolean);
    return boolean;
  }
}

export {
  WebSocket
}

2.簡單使用
import { WebSocket} from '../utils/Socket'
Page({
  data: {

  },
  onLoad: function () {
    let socket = new WebSocket();
    socket.bulidConnectAndMonitor(
      {}, 
      'ws://127.0.0.1:9101/wxServer',
      '/topic/wx/push/all',
      function(body){
        //接收廣播頻道
        console.log(body);
      },
      function(con){
        console.log(con);
        //添加一對一頻道
        socket.addMessageMonitor("/user/123456/wx/push/singleton", function (body) {
          console.log(body);
        });
      }
    );

PC端連接Socket(sockjs兼容)

1.下載sockjs依賴
cnpm i sockjs --save
2.引入sockjs以及stomp
import SockJS from "sockjs-client";
import Stomp from "stompjs";
3.創建sicket連接
//scoket
initWebSocket() {
  this.connection();
},
connection() {
  //創建連接
  var _this = this;
  // 建立連接對象
  let socket = new SockJS(this.webSocketUrl);
  // 獲取STOMP子協議的客戶端對象
  this.stompClient = Stomp.over(socket);
  // 定義客戶端的認證信息,按需求配置
  let headers = {
    Authorization: ""
  };
  // 向服務器發起websocket連接
  this.stompClient.connect(
    headers,
    frame => {
      if (this.connectLock) {
        console.log("鎖線程鎖已還原");
        clearInterval(this.timer);
        this.connectLock = false; //還原鎖
      }
      //點對點接收消息
      this.stompClient.subscribe(
        `/user/${this.userInfo.id}/spell/order`,
        msg => {
          
        }
      );
      //一對多訂閱消息
      this.stompClient.subscribe("/topic/super/to/agent", 
        msg => {
          
      });
    },
    err => {
      // 連接發生錯誤時的處理函數
      console.log("連接失敗...");
      if (!this.connectLock) {
        this.reconnect();
      }
    }
  );
}, //連接 後臺
disconnect() {
  if (this.stompClient) {
    this.stompClient.disconnect();
  }
}, // 重新連接
reconnect() {
  let that = this;
  if (this.connectLock) return;
  this.connectLock = true;
  // 斷開重連機制,嘗試發送消息,捕獲異常發生時重連
  this.timer = setInterval(() => {
    that.connection();
  }, 5000);
},
validateToken() {
  let token = getToken();
  if (!token) {
    console.log("未登錄,關閉連接");
    if (this.timer) {
      clearInterval(this.timer);
    }
    this.connectLock = false; //還原鎖
    //斷開連接
    console.log("連接已斷開");
  }
},

這種方式既可以兼容PC端的Socket連接,又可以實現單個需求的socket連接(如微信小程序)

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