package cn.iocoder.yudao.module.mes.framework.collect.mqtt; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.module.mes.framework.collect.config.MesCollectProperties; import cn.iocoder.yudao.module.mes.service.collect.MesCollectMessageDispatcher; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.eclipse.paho.client.mqttv3.*; import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; import org.springframework.context.SmartLifecycle; import org.springframework.stereotype.Component; import java.net.InetAddress; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; /** * MES 数采 MQTT 客户端 * * @author 超级管理员 */ @Component @Slf4j public class MesCollectMqttClient implements SmartLifecycle { private static final long RECONNECT_DELAY_SECONDS = 30L; @Resource private MesCollectProperties properties; @Resource private MesCollectMessageDispatcher dispatcher; private volatile boolean running = false; private MqttClient client; private ScheduledExecutorService scheduler; @Override public void start() { MesCollectProperties.Mqtt mqtt = properties.getMqtt(); if (!Boolean.TRUE.equals(mqtt.getEnabled())) { log.info("[start][数采 MQTT 未启用]"); return; } if (StrUtil.isBlank(mqtt.getUrl())) { log.warn("[start][数采 MQTT 已启用但 url 为空,跳过连接]"); return; } running = true; scheduler = Executors.newSingleThreadScheduledExecutor(runnable -> { Thread thread = new Thread(runnable, "mes-collect-mqtt-reconnect"); thread.setDaemon(true); return thread; }); scheduler.scheduleWithFixedDelay(this::ensureConnected, 0, RECONNECT_DELAY_SECONDS, TimeUnit.SECONDS); } private void ensureConnected() { if (!running) { return; } MesCollectProperties.Mqtt mqtt = properties.getMqtt(); try { if (client == null) { client = new MqttClient(mqtt.getUrl(), buildClientId(), new MemoryPersistence()); client.setCallback(new MqttCallbackExtended() { @Override public void connectComplete(boolean reconnect, String serverURI) { subscribe(); } @Override public void connectionLost(Throwable cause) { log.warn("[connectionLost][数采 MQTT 连接断开:{}]", cause == null ? "未知原因" : cause.getMessage()); } @Override public void messageArrived(String topic, MqttMessage message) { String payload = new String(message.getPayload(), StandardCharsets.UTF_8); try { dispatcher.dispatch(topic, payload); } catch (Exception e) { log.error("[messageArrived][主题({}) 入队失败]", topic, e); } } @Override public void deliveryComplete(IMqttDeliveryToken token) { // 当前只订阅,不发布 } }); } if (!client.isConnected()) { MqttConnectOptions options = new MqttConnectOptions(); options.setAutomaticReconnect(true); options.setCleanSession(true); options.setKeepAliveInterval(mqtt.getKeepAlive()); options.setConnectionTimeout(mqtt.getConnectionTimeout()); if (StrUtil.isNotBlank(mqtt.getUsername())) { options.setUserName(mqtt.getUsername()); } if (mqtt.getPassword() != null) { options.setPassword(mqtt.getPassword().toCharArray()); } client.connect(options); log.info("[ensureConnected][数采 MQTT 已连接:{}]", mqtt.getUrl()); } } catch (Exception e) { log.warn("[ensureConnected][数采 MQTT 连接/订阅失败,{} 秒后重试:{}]", RECONNECT_DELAY_SECONDS, e.getMessage()); } } private void subscribe() { MesCollectProperties.Mqtt mqtt = properties.getMqtt(); for (String topic : resolveSubscribeTopics(mqtt)) { try { client.subscribe(topic, mqtt.getQos()); log.info("[subscribe][数采 MQTT 订阅成功:{}]", topic); } catch (Exception e) { log.error("[subscribe][数采 MQTT 订阅失败:{}]", topic, e); } } } /** * 解析需要订阅的主题。 * *
显式配置 topics 优先;网关格式订阅整个前缀(gateway/#), * 因为网关除 attributes 外还会发 connect / disconnect,后续也可能新增主题; * 其余格式沿用 /YLCY/IMM/+/+ 两级通配。
*/ private List