SpringBoot基于integration整合MQTT
部署MQTT
部署MQTT可以参照博客:Linux安装mosquitto mqtt几种方式 这里不做阐述。
本文提供了高并发情况下消息推送方案,代码仅供参考,请结合实际情况进行重载。
1.引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-integration</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-stream</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-mqtt</artifactId>
</dependency>
2.yml配置
spring:
mqtt:
url: "tcp://ip:port"
clientId: "clientId"
username: "admin"
password: "admin"
defaultTopic: "default"
completion-timeout: 10000
3.客户端配置(消息生产者)
3.1.核心参数配置
package com.zbwd.esms.edge.config;
import com.zbwd.esms.edge.enums.TopicEnum;
import com.zbwd.esms.edge.mqtt.handler.MultiMqttMessageHandler;
import com.zbwd.esms.edge.mqtt.handler.MyMqttPahoMessageHandler;
import com.zbwd.esms.edge.util.SnowFlakeUtil;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener;
import org.springframework.integration.annotation.IntegrationComponentScan;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.core.MessageProducer;
import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory;
import org.springframework.integration.mqtt.core.MqttPahoClientFactory;
import org.springframework.integration.mqtt.event.MqttConnectionFailedEvent;
import org.springframework.integration.mqtt.event.MqttMessageDeliveredEvent;
import org.springframework.integration.mqtt.event.MqttMessageSentEvent;
import org.springframework.integration.mqtt.event.MqttSubscribedEvent;
import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter;
import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import java.util.Date;
/**
* mqtt连接信息
*
* @author : mwx
* @date : 2022/12/28 13:39
*/
@Slf4j
@Configuration
@IntegrationComponentScan
public class MqttConfig {
@Value("${spring.mqtt.url}")
private String url;
@Value("${spring.mqtt.clientId}")
private String clientId;
@Value("${spring.mqtt.username}")
private String userName;
@Value("${spring.mqtt.password}")
private String passWord;
@Value("${spring.mqtt.completion-timeout}")
private int completionTimeout;
@Value("${spring.mqtt.defaultTopic}")
private String defaultTopic;
private SnowFlakeUtil snowFlakeUtil = new SnowFlakeUtil(0, 0);
@Bean
public MqttConnectOptions getMqttConnectOptions() {
// MQTT的连接设置
MqttConnectOptions mqttConnectOptions = new MqttConnectOptions();
// 设置连接的用户名
mqttConnectOptions.setUserName(userName);
// 设置连接的密码
mqttConnectOptions.setPassword(passWord.toCharArray());
// 设置是否清空session,这里如果设置为false表示服务器会保留客户端的连接记录,
// 把配置里的 cleanSession 设为false,客户端掉线后 服务器端不会清除session,
// 当重连后可以接收之前订阅主题的消息。当客户端上线后会接受到它离线的这段时间的消息
mqttConnectOptions.setCleanSession(false);
// 设置发布端地址,多个用逗号分隔, 如:tcp://111:1883,tcp://222:1883
// 当第一个111连接上后,222不会在连,如果111挂掉后,重试连111几次失败后,会自动去连接222
String[] urls = new String[]{url};
mqttConnectOptions.setServerURIs(urls);
// 设置会话心跳时间 单位为秒 服务器会每隔1.5*20秒的时间向客户端发送个消息判断客户端是否在线,但这个方法并没有重连的机制
mqttConnectOptions.setKeepAliveInterval(20);
mqttConnectOptions.setAutomaticReconnect(true);
// 设置“遗嘱”消息的话题,若客户端与服务器之间的连接意外中断,服务器将发布客户端的“遗嘱”消息。
//mqttConnectOptions.setWill(willTopic, willContent.getBytes(), 2, false);
mqttConnectOptions.setMaxInflight(1000000);
return mqttConnectOptions;
}
@Bean
public MqttPahoClientFactory mqttClientFactory() {
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
factory.setConnectionOptions(getMqttConnectOptions());
return factory;
}
@Bean
@ServiceActivator(inputChannel = "mqttOutboundChannel")
public MessageHandler mqttOutbound() {
//==========方案一(基本普通方案)==========
//clientId每个连接必须唯一,否则,两个相同的clientId相互挤掉线
//踩坑记录:1.相同clientId相互挤掉线导致mqtt重复断线重连和消息无法正常消费
//String clientIdStr = clientId + snowFlakeUtil.nextId();
//MqttPahoMessageHandler messageHandler = new MqttPahoMessageHandler(clientIdStr, mqttClientFactory());
// //async如果为true,则调用方不会阻塞。而是在发送消息时等待传递确认。默认值为false(发送将阻塞,直到确认发送)
//messageHandler.setAsync(true);
//messageHandler.setAsyncEvents(true);
//messageHandler.setDefaultTopic(defaultTopic);
//messageHandler.setDefaultQos(0);
//return messageHandler;
//==========方案二(并发生产消息方案)==========
return new MultiMqttMessageHandler();
}
public MessageHandler create() {
String clientIdStr = clientId + snowFlakeUtil.nextId();
MyMqttPahoMessageHandler myMqttPahoMessageHandler = new MyMqttPahoMessageHandler(url, clientIdStr, mqttClientFactory());
myMqttPahoMessageHandler.setAsync(true);
myMqttPahoMessageHandler.setAsyncEvents(true);
myMqttPahoMessageHandler.setDefaultTopic(defaultTopic);
myMqttPahoMessageHandler.setDefaultQos(0);
myMqttPahoMessageHandler.onInit();
return myMqttPahoMessageHandler;
}
/**
* 发消息通道
*/
@Bean
public MessageChannel mqttOutboundChannel() {
return new DirectChannel();
}
/**
* 接收消息通道
*/
@Bean
public MessageChannel mqttInputChannel() {
return new DirectChannel();
}
@Bean
public MessageProducer inbound() {
//clientId每个连接必须唯一,否则,两个相同的clientId相互挤掉线
String serverIdStr = clientId + snowFlakeUtil.nextId();
//动态设置adapter的的topic值
String[] allTopics = TopicEnum.getAllValues();
MqttPahoMessageDrivenChannelAdapter adapter =
new MqttPahoMessageDrivenChannelAdapter(serverIdStr, mqttClientFactory(), allTopics);
adapter.setCompletionTimeout(completionTimeout);
adapter.setConverter(new DefaultPahoMessageConverter());
//这里设置为0 是因为代码对上传失败的消息做了处理
adapter.setQos(0);
adapter.setOutputChannel(mqttInputChannel());
return adapter;
}
/**
* 通过通道获取数据 订阅的数据
*/
@Bean
@ServiceActivator(inputChannel = "mqttInputChannel")
public MessageHandler handler() {
return message -> {
//String payload = message.getPayload().toString();
//String topic = message.getHeaders().get(MqttHeaders.RECEIVED_TOPIC).toString();
//边缘计算不做接收消息处理
};
}
@EventListener(MqttConnectionFailedEvent.class)
public void mqttConnectionFailedEvent(MqttConnectionFailedEvent event) {
log.error("mqttConnectionFailedEvent连接mqtt失败: " +
"date={}, hostUrl={}, username={}, error={}",
new Date(), url, userName, event.getCause().getMessage());
}
/**
* 备注:当使用并发产生消息时下列日志监听时间无法触发
**/
@EventListener(MqttMessageSentEvent.class)
public void mqttMessageSentEvent(MqttMessageSentEvent event) {
log.info("mqttMessageSentEvent发送信息: date={}, info={}", new Date(), event.toString());
}
@EventListener(MqttMessageDeliveredEvent.class)
public void mqttMessageDeliveredEvent(MqttMessageDeliveredEvent event) {
//log.info("mqttMessageDeliveredEvent发送成功信息: date={}, info={}", new Date(), event.toString());
}
@EventListener(MqttSubscribedEvent.class)
public void mqttSubscribedEvent(MqttSubscribedEvent event) {
log.info("mqttSubscribedEvent订阅成功信息: date={}, info={}", new Date(), event.toString());
}
}
代码中包含了两个消息推送方案,根据并发量进行选择。(这里发送的荷载主要是字节数组数据,本文会提供压缩数据的工具类)
3.2.自定义消息处理器
package com.zbwd.esms.edge.mqtt.handler;
import org.springframework.integration.mqtt.core.MqttPahoClientFactory;
import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler;
import org.springframework.messaging.Message;
/**
* mqtt并发生产消息优化配置项
*
*
* @author : mwx
* @date : 2023/1/13 11:08
*/
public class MyMqttPahoMessageHandler extends MqttPahoMessageHandler {
public MyMqttPahoMessageHandler(String url, String clientId, MqttPahoClientFactory clientFactory) {
super(url, clientId, clientFactory);
}
@Override
public void doStop() {
super.doStop();
}
@Override
public void handleMessageInternal(Message<?> message) throws Exception {
super.handleMessageInternal(message);
}
@Override
public void onInit() {
try {
super.onInit();
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.3.自定义消息转发器
package com.zbwd.esms.edge.mqtt.handler;
import com.zbwd.esms.edge.config.MqttConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.Lifecycle;
import org.springframework.integration.handler.AbstractMessageHandler;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandler;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* mqtt并发生产消息优化项配置
*
* <p>通知实例化多个消息处理器并随机取实现负载均衡产生消息</p>
*
* @author : mwx
* @date : 2023/1/13 11:11
*/
@Component
@Slf4j
public class MultiMqttMessageHandler extends AbstractMessageHandler implements Lifecycle {
private final AtomicBoolean running = new AtomicBoolean();
private volatile Map<Integer, MessageHandler> mqttHandlerMap;
/**
* 模拟3个Client端
*/
private Integer handlerCount = 3;
@Autowired
private MqttConfig senderConfig;
@Override
public void start() {
if (!this.running.getAndSet(true)) {
doStart();
}
}
private void doStart() {
mqttHandlerMap = new ConcurrentHashMap<>();
for (int i = 0; i < handlerCount; i++) {
mqttHandlerMap.put(i, senderConfig.create());
}
}
@Override
public void stop() {
if (this.running.getAndSet(false)) {
doStop();
}
}
private void doStop() {
for (Map.Entry<Integer, MessageHandler> e : mqttHandlerMap.entrySet()) {
MessageHandler handler = e.getValue();
((MyMqttPahoMessageHandler) handler).doStop();
}
}
@Override
public boolean isRunning() {
return this.running.get();
}
@Override
protected void handleMessageInternal(Message<?> message) throws Exception {
//负载均衡
Random random = new Random();
MyMqttPahoMessageHandler messageHandler = (MyMqttPahoMessageHandler) mqttHandlerMap.get(random.nextInt(handlerCount));
log.info("开始处理信息:{}", message.toString());
messageHandler.handleMessageInternal(message);
}
}
3.4.消息推送Component
package com.zbwd.esms.edge.component;
import org.springframework.integration.annotation.MessagingGateway;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.stereotype.Component;
/**
* mqtt发送网关
*
* @author : mwx
* @date : 2022/12/28 14:02
*/
@Component
@MessagingGateway(defaultRequestChannel = "mqttOutboundChannel")
public interface MqttGatewayComponent {
/**
* 发送消息至指定topic
* <p color = red>此方法不推荐使用!!发送消息时请先调用GzipUtil内的数据压缩方法发送字节数组</p>
*
* @param payload 消息内容
* @param topic topic
* @see com.zbwd.esms.edge.util.GzipUtil
*/
@Deprecated
void sendToMqtt(String payload, @Header(MqttHeaders.TOPIC) String topic);
/**
* 发送使用Gzip压缩后的字节数组
*
* <p color = green>推荐使用此方法!!发送消息时请先调用GzipUtil内的数据压缩方法发送字节数组</p>
*
* @param payload 消息内容
* @param topic topic
* @see com.zbwd.esms.edge.util.GzipUtil
*/
void sendToMqtt(byte[] payload, @Header(MqttHeaders.TOPIC) String topic);
/**
* 发送消息至指定topic 能指定qos
*
* @param payload 消息内容
* @param topic topic
* @param qos 值为 0 1 2
*/
void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic, @Header(MqttHeaders.QOS) int qos, String payload);
}
4.消息消费者配置
4.1.核心配置
package com.xaaef.shark.mqtt.config;
import com.xaaef.shark.mqtt.enums.TopicEnum;
import com.xaaef.shark.mqtt.handler.MqttMessageHandler;
import com.xaaef.shark.util.SnowFlakeUtil;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.internal.wire.MqttConnect;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener;
import org.springframework.integration.annotation.IntegrationComponentScan;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.core.MessageProducer;
import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory;
import org.springframework.integration.mqtt.core.MqttPahoClientFactory;
import org.springframework.integration.mqtt.event.MqttConnectionFailedEvent;
import org.springframework.integration.mqtt.event.MqttMessageSentEvent;
import org.springframework.integration.mqtt.event.MqttSubscribedEvent;
import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter;
import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler;
import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import javax.annotation.Resource;
import java.util.Date;
/**
* mqtt连接信息
*
* @author : mwx
* @date : 2022/12/28 13:39
*/
@Slf4j
@Configuration
@IntegrationComponentScan
public class MqttConfig {
@Value("${spring.mqtt.url}")
private String url;
@Value("${spring.mqtt.clientId}")
private String clientId;
@Value("${spring.mqtt.username}")
private String userName;
@Value("${spring.mqtt.password}")
private String passWord;
@Value("${spring.mqtt.completion-timeout}")
private int completionTimeout;
@Value("${spring.mqtt.defaultTopic}")
private String defaultTopic;
private SnowFlakeUtil snowFlakeUtil = new SnowFlakeUtil(1, 0);
@Resource
private MqttMessageHandler mqttMessageHandler;
@Bean
public MqttConnectOptions getMqttConnectOptions() {
// MQTT的连接设置
MqttConnectOptions mqttConnectOptions = new MqttConnectOptions();
// 设置连接的用户名
mqttConnectOptions.setUserName(userName);
// 设置连接的密码
mqttConnectOptions.setPassword(passWord.toCharArray());
// 设置是否清空session,这里如果设置为false表示服务器会保留客户端的连接记录,
// 把配置里的 cleanSession 设为false,客户端掉线后 服务器端不会清除session,
// 当重连后可以接收之前订阅主题的消息。当客户端上线后会接受到它离线的这段时间的消息
mqttConnectOptions.setCleanSession(false);
// 设置发布端地址,多个用逗号分隔, 如:tcp://111:1883,tcp://222:1883
// 当第一个111连接上后,222不会在连,如果111挂掉后,重试连111几次失败后,会自动去连接222
String[] urls = new String[]{url};
mqttConnectOptions.setServerURIs(urls);
// 设置会话心跳时间 单位为秒 服务器会每隔1.5*20秒的时间向客户端发送个消息判断客户端是否在线,但这个方法并没有重连的机制
mqttConnectOptions.setKeepAliveInterval(20);
//该属性设置后能完成重连但是要另外实现订阅
//经过对topic的测试,发现只注册到default时也能接收到消息 是因为对入站消息进行了处理(消息先入站再获取topic进行处理)
mqttConnectOptions.setAutomaticReconnect(true);
// 设置“遗嘱”消息的话题,若客户端与服务器之间的连接意外中断,服务器将发布客户端的“遗嘱”消息。
//mqttConnectOptions.setWill(willTopic, willContent.getBytes(), 2, false);
mqttConnectOptions.setMaxInflight(1000000);
return mqttConnectOptions;
}
@Bean
public MqttPahoClientFactory mqttClientFactory() {
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
factory.setConnectionOptions(getMqttConnectOptions());
return factory;
}
@Bean
@ServiceActivator(inputChannel = "mqttOutboundChannel")
public MessageHandler mqttOutbound() {
//clientId每个连接必须唯一,否则,两个相同的clientId相互挤掉线
String clientIdStr = clientId + snowFlakeUtil.nextId();
MqttPahoMessageHandler messageHandler = new MqttPahoMessageHandler(clientIdStr, mqttClientFactory());
//async如果为true,则调用方不会阻塞。而是在发送消息时等待传递确认。默认值为false(发送将阻塞,直到确认发送)
messageHandler.setAsync(true);
messageHandler.setAsyncEvents(true);
messageHandler.setDefaultTopic(defaultTopic);
messageHandler.setDefaultQos(0);
//发送默认按字节类型发送消息
// defaultPahoMessageConverter.setPayloadAsBytes(true);
return messageHandler;
}
/**
* 发消息通道
*/
@Bean
public MessageChannel mqttOutboundChannel() {
return new DirectChannel();
}
/**
* 接收消息通道
*/
@Bean
public MessageChannel mqttInputChannel() {
return new DirectChannel();
}
@Bean
public MessageProducer inbound() {
//clientId每个连接必须唯一,否则,两个相同的clientId相互挤掉线
String serverIdStr = clientId + snowFlakeUtil.nextId();
//动态设置adapter的的topic值
String[] allTopics = TopicEnum.getAllValues();
MqttPahoMessageDrivenChannelAdapter adapter =
new MqttPahoMessageDrivenChannelAdapter(serverIdStr, mqttClientFactory(), allTopics);
adapter.setCompletionTimeout(completionTimeout);
DefaultPahoMessageConverter defaultPahoMessageConverter = new DefaultPahoMessageConverter();
//接收字节数组类型有效荷载
defaultPahoMessageConverter.setPayloadAsBytes(true);
adapter.setConverter(defaultPahoMessageConverter);
adapter.setQos(2);
adapter.setOutputChannel(mqttInputChannel());
return adapter;
}
/**
* 通过通道获取数据 订阅的数据
*/
@Bean
@ServiceActivator(inputChannel = "mqttInputChannel")
public MessageHandler handler() {
return message -> {
//接收到的消息由这个处理器进行处理
mqttMessageHandler.handleMessage(message);
};
}
@EventListener(MqttConnectionFailedEvent.class)
public void mqttConnectionFailedEvent(MqttConnectionFailedEvent event) {
log.error("mqttConnectionFailedEvent连接mqtt失败: " +
"date={}, hostUrl={}, username={}, error={}",
new Date(), url, userName, event.getCause().getMessage());
}
@EventListener(MqttConnect.class)
public void reSubscribed() {
}
@EventListener(MqttMessageSentEvent.class)
public void mqttMessageSentEvent(MqttMessageSentEvent event) {
log.info("mqttMessageSentEvent发送信息: date={}, info={}", new Date(), event.toString());
}
// @EventListener(MqttMessageDeliveredEvent.class)
// public void mqttMessageDeliveredEvent(MqttMessageDeliveredEvent event) {
// //log.info("mqttMessageDeliveredEvent发送成功信息: date={}, info={}", new Date(), event.toString());
// }
@EventListener(MqttSubscribedEvent.class)
public void mqttSubscribedEvent(MqttSubscribedEvent event) {
log.info("mqttSubscribedEvent订阅成功信息: date={}, info={}", new Date(), event.toString());
}
}
4.2消息处理器
package com.xaaef.shark.mqtt.handler;
import cn.hutool.core.util.ObjectUtil;
import com.xaaef.shark.mqtt.enums.TopicEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* mqtt topic中转
*
* @author : mwx
* @date : 2022/12/28 15:02
*/
@Slf4j
@Component
public class MqttMessageHandler implements MessageHandler {
@Resource
private MqttTopicHandler topicHandler;
/**
*
*消息会在这进行对topic的处理
* <p>重连后</p>
* */
@Override
public void handleMessage(Message<?> message) throws MessagingException {
//从这里来对消息进行分topic处理
String topic = message.getHeaders().get(MqttHeaders.RECEIVED_TOPIC).toString();
boolean exist = TopicEnum.isExist(topic);
if (exist == false) {
log.info("当前topic:{}未注册!不做处理!",topic);
return;
}
//MqttTopicHandler topicHandler = SpringUtil.getBean(mqttTopicEnum.getTopicHandlerBeanName(), TopicHandler.class);
if (ObjectUtil.isNull(topicHandler)) {
log.warn("消息处理器未被实现,无法消费消息!");
return;
}
// topic消费处理
topicHandler.handler(message);
}
}
4.3.根据Topic进行分类处理Handler
package com.xaaef.shark.mqtt.handler;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import com.xaaef.shark.entity.Heap;
import com.xaaef.shark.service.AlgorithmModelService;
import com.xaaef.shark.service.EmsService;
import com.xaaef.shark.service.SysHeapService;
import com.xaaef.shark.util.GzipUtil;
import com.xaaef.shark.util.JsonUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
* 消息处理器
*
* @author : mwx
* @date : 2022/12/28 15:00
*/
@Slf4j
@Service
public class MqttTopicHandler {
@Autowired
private SysHeapService sysHeapService;
@Autowired
private AlgorithmModelService algorithmModelService;
@Autowired
private EmsService emsService;
/**
* 这一步主要是对消息的有效荷载进行非空校验以及日志打印
*/
public void handler(Message<?> message) {
String topic = message.getHeaders().get(MqttHeaders.RECEIVED_TOPIC).toString();
log.info("topic:{}-->message received!", topic);
Object payload = message.getPayload();
Assert.notNull(payload, "接收到的消息为空!");
byte[] data = (byte[]) payload;
dispose(data, topic);
}
/**
* 对mqtt传来的消息有效荷载进行数据处理
* <p color = yellow>这里的字节数据是Gzip压缩后的字节数组,需要使用GzipUtil进行解析</p>
*
* @see GzipUtil
*/
private void dispose(byte[] payload, String topic) {
String strDecompress = GzipUtil.strDecompress(payload);
if (!ObjectUtil.isNull(strDecompress)){
try {
switch (topic) {
//基础数据 以及 重传数据
case "base":
Heap baseHeap = JsonUtils.jsonToPojo(strDecompress, Heap.class);
//若接收到的base数据该属性为空则为实时数据
Integer flag = Optional.ofNullable(baseHeap.getDataUpload()).orElse(1);
if (flag == 1) {
sysHeapService.insertHeapData(baseHeap);
} else if (flag == 0) {
sysHeapService.reuploadHeapData(baseHeap);
} else {
}
break;
//安全模型数据
case "safety":
List<Map<String, Object>> safetyData = JsonUtils.jsonToListMap(strDecompress, String.class, Object.class);
algorithmModelService.insertSafetyResultData(safetyData);
break;
//健康寿命模型算法数据
case "rul":
List<Map<String, Object>> rulData = JsonUtils.jsonToListMap(strDecompress, String.class, Object.class);
algorithmModelService.insertRulResultData(rulData);
break;
//遥测数据
case "yc":
Map<String, Object> ycData = JsonUtils.jsonToMap(strDecompress, String.class, Object.class);
emsService.saveEms(ycData);
break;
//遥信数据
case "yx":
Map<String, Object> yxData = JsonUtils.jsonToMap(strDecompress, String.class, Object.class);
emsService.saveYxInfo(yxData);
break;
default:
log.warn("未知topic:{}的消息,请检查您的配置!", topic);
break;
}
log.info("来自topic:{}的消息处理完毕!", topic);
} catch (Exception e) {
log.error("来自topic:{}的消息处理异常!原因:{}", topic, e);
}
}
}
}
5.公共类
5.1.Topic枚举
package com.xaaef.shark.mqtt.enums;
import java.util.ArrayList;
import java.util.List;
/**
* mqtt消息发送topic枚举
* <p color = red>注意:所有在mqttServer上面需要用到的topic都要在这里有值,否则绑定不了topic接收不到消息</p>
*
* @author : mwx
* @date : 2022/12/28 14:18
*/
public enum TopicEnum {
/**
* 基础数据 包含了重传数据
*/
BASE("base"),
/**
* 安全模型算法数据
*/
SAFETY("safety"),
/**
* 健康寿命模型算法数据
*/
RUL("rul"),
/**
* 遥信.
*/
YX("yx"),
/**
* 遥测.
*/
YC("yc"),
/**
* 测试分支用
*/
TEST("test");
private String topic;
public String getTopic() {
return topic;
}
TopicEnum(String topic) {
this.topic = topic;
}
public static boolean isExist(String topic) {
for (TopicEnum topicEnum : TopicEnum.values()) {
if (topicEnum.getTopic().equalsIgnoreCase(topic)) {
return true;
}
}
return false;
}
/**
* 获取该枚举类的所有值
*/
public static String[] getAllValues() {
List<String> values = new ArrayList<>();
for (TopicEnum topicEnum : TopicEnum.values()) {
values.add(topicEnum.getTopic());
}
return values.toArray(new String[values.size()]);
}
}
5.2.数据压缩工具
package com.xaaef.shark.util;
import cn.hutool.core.util.ObjectUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
/**
* 压缩工具类
*
* @author : mwx
* @date : 2023/1/12 16:32
*/
@Slf4j
public class GzipUtil {
private final static String CHARACTER_PROTOCOL = "ISO-8859-1";
/**
* 字符串压缩
*
* <p>实测长度为24000的字符串能压缩到9000左右</p>
*
* @param str 压缩的字符串
* @return byte[] 压缩后的字节数组
* @author mwx
*/
public static byte[] StrCompress(String str) {
if (StringUtils.isNotBlank(str)) {
ByteArrayOutputStream byteArrayOutputStream = null;
GZIPOutputStream gzipOutputStream = null;
try {
byteArrayOutputStream = new ByteArrayOutputStream();
gzipOutputStream = new GZIPOutputStream(byteArrayOutputStream);
gzipOutputStream.write(str.getBytes(CHARACTER_PROTOCOL));
gzipOutputStream.finish();
return byteArrayOutputStream.toByteArray();
} catch (Exception e) {
log.error("【异常-GzipUtil】字符串压缩(StrCompress)发生异常:{}", e.getMessage(), e);
return null;
} finally {
Close(byteArrayOutputStream, gzipOutputStream);
}
} else {
return null;
}
}
/**
* 字符串解压缩
*
* @param target 解压缩的字节数组
* @return String 解压缩后的字符串
* @author mwx
*/
public static String strDecompress(byte[] target) {
if (!ObjectUtil.isNull(target)) {
ByteArrayOutputStream byteArrayOutputStream = null;
GZIPInputStream gzipInputStream = null;
try {
byteArrayOutputStream = new ByteArrayOutputStream();
gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(target));
byte[] res = new byte[10240];
int n;
while ((n = gzipInputStream.read(res)) > -1) {
byteArrayOutputStream.write(res, 0, n);
}
byteArrayOutputStream.flush();
return new String(byteArrayOutputStream.toByteArray(), CHARACTER_PROTOCOL);
} catch (Exception e) {
log.error("【异常-GzipUtil】字符串解压缩(strDecompress)发生异常:{}", e.getMessage(), e);
return null;
} finally {
Close(byteArrayOutputStream, gzipInputStream);
}
} else {
return null;
}
}
/**
* 关闭流方法
*
* @param stream 关闭流的集合
*/
private static void Close(Closeable... stream) {
try {
for (Closeable closeable : stream) {
if (ObjectUtil.isNotNull(closeable)) {
closeable.close();
}
}
log.info("【GzipUtil】关闭流成功!");
} catch (Exception e) {
log.error("【异常-GzipUtil】流关闭失败!原因:{}", e.getMessage(), e);
}
}
}