環境 :
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)
}
}