package cn.iocoder.yudao.module.iot.service.rule.data.action; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; import cn.iocoder.yudao.module.iot.dal.dataobject.rule.config.IotDataSinkMqttConfig; import cn.iocoder.yudao.module.iot.enums.rule.IotDataSinkTypeEnum; import lombok.extern.slf4j.Slf4j; import org.eclipse.paho.client.mqttv3.MqttClient; import org.eclipse.paho.client.mqttv3.MqttConnectOptions; import org.eclipse.paho.client.mqttv3.MqttMessage; import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.stereotype.Component; import java.nio.charset.StandardCharsets; /** * MQTT 的 {@link IotDataRuleAction} 实现类 * * @author HUIHUI */ @ConditionalOnClass(name = "org.eclipse.paho.client.mqttv3.MqttClient") @Component @Slf4j public class IotMqttDataRuleAction extends IotDataRuleCacheableAction { /** * 默认 QoS 等级(至少一次) */ private static final int DEFAULT_QOS = 1; @Override public Integer getType() { return IotDataSinkTypeEnum.MQTT.getType(); } @Override public void execute(IotDeviceMessage message, IotDataSinkMqttConfig config) throws Exception { try { // 1. 获取或创建 MqttClient MqttClient mqttClient = getProducer(config); // 2.1 检查连接状态,如果断开则踢出缓存并重新创建 if (!mqttClient.isConnected()) { log.warn("[execute][MQTT 连接已断开,重新创建客户端,服务器: {}]", config.getUrl()); invalidateProducer(config); // 踢出旧的断连客户端,触发 closeProducer mqttClient = getProducer(config); // 触发 initProducer 创建全新连接 } // 2.2 构建并发送消息 MqttMessage mqttMessage = new MqttMessage(JsonUtils.toJsonString(message).getBytes(StandardCharsets.UTF_8)); mqttMessage.setQos(DEFAULT_QOS); mqttClient.publish(config.getTopic(), mqttMessage); log.info("[execute][message({}) 发送成功,MQTT 服务器: {},topic: {}]", message.getId(), config.getUrl(), config.getTopic()); } catch (Exception e) { log.error("[execute][message({}) 发送失败,MQTT 服务器: {}]", message.getId(), config.getUrl(), e); throw e; } } @Override protected MqttClient initProducer(IotDataSinkMqttConfig config) throws Exception { // 1. 创建 MqttClient,使用内存持久化 // 拼接时间戳后缀,避免多个规则指向同一 Broker 时 clientId 冲突 String clientId = config.getClientId() + "_" + System.currentTimeMillis(); MqttClient mqttClient = new MqttClient(config.getUrl(), clientId, new MemoryPersistence()); // 2. 连接到 MQTT Broker mqttClient.connect(buildConnectOptions(config)); log.info("[initProducer][MQTT 客户端创建并连接成功,服务器: {},clientId: {}]", config.getUrl(), clientId); return mqttClient; } @Override protected void closeProducer(MqttClient producer) throws Exception { if (producer.isConnected()) { producer.disconnect(); } producer.close(); } /** * 构建 MQTT 连接选项 * * @param config MQTT 配置 * @return 连接选项 */ private MqttConnectOptions buildConnectOptions(IotDataSinkMqttConfig config) { MqttConnectOptions options = new MqttConnectOptions(); options.setCleanSession(true); options.setConnectionTimeout(10); // 连接超时 10 秒 options.setKeepAliveInterval(20); // 心跳间隔 20 秒 // 注意:不开启 automaticReconnect,由 execute() 中的 isConnected() 手动控制重连,避免竞争 // 设置认证信息(如果有) if (config.getUsername() != null) { options.setUserName(config.getUsername()); } if (config.getPassword() != null) { options.setPassword(config.getPassword().toCharArray()); } return options; } }