环境 :
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
org.springframework.boot
spring-boot-starter-amqp
org.springframework.boot
spring-boot-starter-websocket
com.alibaba
fastjson
1.2.46
net.sf.json-lib
json-lib
2.4
jdk15
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
配置类
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);
}
}
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 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);
}
});
}
}
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)
}
}