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连接(如微信小程序)

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