feat(device): 添加MQTT设备在线状态管理与Redis容错机制

- 集成Spring Integration MQTT与Eclipse Paho客户端
- 添加设备在线状态填充逻辑,支持批量查询心跳数据
- 优化Redis配置,使用Lazy初始化与通用序列化器
- 增加Redis异常处理与防重复提交Fail-Open机制
- 新增Redis设备模块常量与在线状态VO
- 完善MQTT与Redis集群连接配置
This commit is contained in:
TongTongStudio
2026-08-08 20:31:29 +08:00
parent e5e3f2c741
commit 927332ad5d
23 changed files with 1368 additions and 14 deletions

View File

@@ -0,0 +1,117 @@
package com.youlai.boot.config;
import com.youlai.boot.config.property.MqttProperties;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.inbound.MqttPahoMessageDrivenChannelAdapter;
import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
/**
* MQTT 配置
* <p>
* 基于 spring-integration-mqtt (Eclipse Paho) 实现消息的发布与订阅。
* 通过 {@code mqtt.enabled=false} 可整体关闭 MQTT 功能。
*
* @author TongTongStudio
* @since 2026/8/8
*/
@Configuration
@ConditionalOnProperty(prefix = "mqtt", name = "enabled", havingValue = "true")
public class MqttConfig {
/**
* MQTT 连接工厂
*/
@Bean
public DefaultMqttPahoClientFactory mqttClientFactory(MqttProperties properties) {
MqttConnectOptions options = new MqttConnectOptions();
options.setServerURIs(new String[]{normalizeServerUri(properties.getUrl())});
if (properties.getUsername() != null && !properties.getUsername().isEmpty()) {
options.setUserName(properties.getUsername());
}
if (properties.getPassword() != null && !properties.getPassword().isEmpty()) {
options.setPassword(properties.getPassword().toCharArray());
}
options.setConnectionTimeout(properties.getConnectionTimeout());
options.setKeepAliveInterval(properties.getKeepAliveInterval());
options.setAutomaticReconnect(properties.isAutomaticReconnect());
options.setCleanSession(true);
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
factory.setConnectionOptions(options);
return factory;
}
/**
* 将 Broker 地址统一规范为 Paho 支持的 scheme。
* <p>Eclipse Paho 仅内置 {@code tcp://} 与 {@code ssl://} 两种 scheme
* 配置中常见的 {@code mqtt://} 需转换为 {@code tcp://}(明文)或 {@code ssl://}(加密)。</p>
*/
private String normalizeServerUri(String uri) {
if (uri == null || uri.isEmpty()) {
return uri;
}
if (uri.startsWith("mqtt://")) {
return "tcp://" + uri.substring("mqtt://".length());
}
if (uri.startsWith("mqtts://")) {
return "ssl://" + uri.substring("mqtts://".length());
}
return uri;
}
/* ============================ 发布 (Publish) ============================ */
/**
* 发布消息的出站通道
*/
@Bean
public MessageChannel mqttOutboundChannel() {
return new DirectChannel();
}
/**
* 发布消息处理器,通过 {@link org.springframework.messaging.support.MessageBuilder} 发送到出站通道即可发布
*/
@Bean
@ServiceActivator(inputChannel = "mqttOutboundChannel")
public MessageHandler mqttOutbound(DefaultMqttPahoClientFactory factory, MqttProperties properties) {
MqttPahoMessageHandler handler = new MqttPahoMessageHandler(properties.getClientId() + "-pub", factory);
handler.setAsync(false);
handler.setDefaultQos(properties.getQos());
return handler;
}
/* ============================ 订阅 (Subscribe) ============================ */
/**
* 订阅消息的入站通道
*/
@Bean
public MessageChannel mqttInboundChannel() {
return new DirectChannel();
}
/**
* 入站订阅适配器:订阅 {@link MqttProperties#getTopics()} 中配置的主题
*/
@Bean
public MessageProducer mqttInbound(DefaultMqttPahoClientFactory factory, MqttProperties properties) {
String[] topics = properties.getTopics().toArray(new String[0]);
MqttPahoMessageDrivenChannelAdapter adapter =
new MqttPahoMessageDrivenChannelAdapter(
properties.getClientId() + "-sub", factory, topics);
adapter.setCompletionTimeout(5000L);
adapter.setQos(properties.getQos());
adapter.setOutputChannel(mqttInboundChannel());
return adapter;
}
}