|
|
@@ -0,0 +1,230 @@
|
|
|
+package com.usky.cdi.service.mqtt;
|
|
|
+
|
|
|
+import lombok.RequiredArgsConstructor;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.context.support.GenericApplicationContext;
|
|
|
+import org.springframework.integration.endpoint.EventDrivenConsumer;
|
|
|
+import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory;
|
|
|
+import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler;
|
|
|
+import org.springframework.integration.mqtt.support.MqttHeaders;
|
|
|
+import org.springframework.messaging.MessageHandler;
|
|
|
+import org.springframework.messaging.SubscribableChannel;
|
|
|
+import org.springframework.integration.annotation.MessagingGateway;
|
|
|
+import org.springframework.messaging.handler.annotation.Header;
|
|
|
+import org.springframework.stereotype.Component;
|
|
|
+import org.springframework.util.Assert;
|
|
|
+
|
|
|
+import java.util.Map;
|
|
|
+import java.util.concurrent.ConcurrentHashMap;
|
|
|
+
|
|
|
+/**
|
|
|
+ *
|
|
|
+ * @author fyc
|
|
|
+ * @email yuchuan.fu@chinausky.com
|
|
|
+ * @date 2025/12/22
|
|
|
+ * 动态 MQTT 连接工具类
|
|
|
+ * 用法:注入后调用 connectOrRefresh(...) 即可
|
|
|
+ *
|
|
|
+ */
|
|
|
+
|
|
|
+@Slf4j
|
|
|
+@Component
|
|
|
+@RequiredArgsConstructor
|
|
|
+public class MqttConnectionTool {
|
|
|
+
|
|
|
+ private final GenericApplicationContext context;
|
|
|
+
|
|
|
+ /* 默认 topic,可外部再 set */
|
|
|
+ private String defaultTopic = "testTopic";
|
|
|
+
|
|
|
+ /* 默认 keep-alive,可外部再 set */
|
|
|
+ private int keepAlive = 60;
|
|
|
+
|
|
|
+ private static final String MQTT_URL = "ssl://114.80.201.143:8883";
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 存储每个用户名对应的连接信息
|
|
|
+ */
|
|
|
+ private final Map<String, ConnectionInfo> connectionMap = new ConcurrentHashMap<>();
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 连接信息内部类
|
|
|
+ */
|
|
|
+ private static class ConnectionInfo {
|
|
|
+ private final String handlerBeanName;
|
|
|
+ private final String consumerBeanName;
|
|
|
+ private final String factoryBeanName;
|
|
|
+ private final String gatewayBeanName;
|
|
|
+
|
|
|
+ public ConnectionInfo(String username) {
|
|
|
+ this.handlerBeanName = "mqttHandler_" + username;
|
|
|
+ this.consumerBeanName = "mqttConsumer_" + username;
|
|
|
+ this.factoryBeanName = "mqttFactory_" + username;
|
|
|
+ this.gatewayBeanName = "mqttGateway_" + username;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 一键创建/刷新连接
|
|
|
+ *
|
|
|
+ * @param username 用户名
|
|
|
+ * @param password 密码
|
|
|
+ * @return 可直接发消息的 MqttGateway
|
|
|
+ */
|
|
|
+ public synchronized MqttGateway connectOrRefresh(String username, String password) {
|
|
|
+ Assert.notNull(username, "username cannot be null");
|
|
|
+ Assert.notNull(password, "password cannot be null");
|
|
|
+
|
|
|
+ String clientId = "mqttx-" + username;
|
|
|
+ try {
|
|
|
+ /* 1. 获取或创建连接信息 */
|
|
|
+ ConnectionInfo connectionInfo = connectionMap.computeIfAbsent(username, ConnectionInfo::new);
|
|
|
+
|
|
|
+ /* 2. 创建或更新专属工厂 */
|
|
|
+ DefaultMqttPahoClientFactory factory;
|
|
|
+ if (context.containsBean(connectionInfo.factoryBeanName)) {
|
|
|
+ factory = context.getBean(connectionInfo.factoryBeanName, DefaultMqttPahoClientFactory.class);
|
|
|
+ factory.setConnectionOptions(buildOptions(username, password, MQTT_URL));
|
|
|
+ log.info("已更新 MQTT 客户端工厂 -> {}", connectionInfo.factoryBeanName);
|
|
|
+ } else {
|
|
|
+ factory = new DefaultMqttPahoClientFactory();
|
|
|
+ factory.setConnectionOptions(buildOptions(username, password, MQTT_URL));
|
|
|
+ context.registerBean(connectionInfo.factoryBeanName, DefaultMqttPahoClientFactory.class, () -> factory);
|
|
|
+ log.info("已创建 MQTT 客户端工厂 -> {}", connectionInfo.factoryBeanName);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 3. 移除旧的 Handler 和 Consumer */
|
|
|
+ removeOldConnection(connectionInfo);
|
|
|
+
|
|
|
+ /* 4. 创建新的 Handler */
|
|
|
+ MqttPahoMessageHandler handler = new MqttPahoMessageHandler(clientId, factory);
|
|
|
+ handler.setAsync(true);
|
|
|
+ handler.setDefaultTopic(defaultTopic);
|
|
|
+ handler.afterPropertiesSet();
|
|
|
+
|
|
|
+ /* 5. 注册新的 Handler */
|
|
|
+ context.registerBean(connectionInfo.handlerBeanName, MqttPahoMessageHandler.class, () -> handler);
|
|
|
+
|
|
|
+ /* 6. 创建并注册新的专属网关 */
|
|
|
+ // 创建一个简单的Gateway实现,直接使用Handler发送消息
|
|
|
+ MqttGateway gateway = new MqttGateway() {
|
|
|
+ @Override
|
|
|
+ public void sendToMqtt(String payload) {
|
|
|
+ try {
|
|
|
+ handler.handleMessage(org.springframework.messaging.support.MessageBuilder
|
|
|
+ .withPayload(payload)
|
|
|
+ .setHeader(MqttHeaders.TOPIC, defaultTopic)
|
|
|
+ .build());
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("发送MQTT消息失败: {}", e.getMessage(), e);
|
|
|
+ throw new RuntimeException("发送MQTT消息失败", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic, String payload) {
|
|
|
+ try {
|
|
|
+ handler.handleMessage(org.springframework.messaging.support.MessageBuilder
|
|
|
+ .withPayload(payload)
|
|
|
+ .setHeader(MqttHeaders.TOPIC, topic)
|
|
|
+ .build());
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("发送MQTT消息失败: {}", e.getMessage(), e);
|
|
|
+ throw new RuntimeException("发送MQTT消息失败", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic,
|
|
|
+ @Header(MqttHeaders.QOS) int qos, String payload) {
|
|
|
+ try {
|
|
|
+ handler.handleMessage(org.springframework.messaging.support.MessageBuilder
|
|
|
+ .withPayload(payload)
|
|
|
+ .setHeader(MqttHeaders.TOPIC, topic)
|
|
|
+ .setHeader(MqttHeaders.QOS, qos)
|
|
|
+ .build());
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("发送MQTT消息失败: {}", e.getMessage(), e);
|
|
|
+ throw new RuntimeException("发送MQTT消息失败", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ log.info("MQTT 连接刷新完成 -> {} / {}", username, clientId);
|
|
|
+ return gateway;
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("MQTT 连接失败 -> {}", clientId, e);
|
|
|
+ throw new RuntimeException("MQTT 连接失败", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* ---------- 私有辅助 ---------- */
|
|
|
+
|
|
|
+ private org.eclipse.paho.client.mqttv3.MqttConnectOptions
|
|
|
+ buildOptions(String u, String p, String url) {
|
|
|
+ org.eclipse.paho.client.mqttv3.MqttConnectOptions opt =
|
|
|
+ new org.eclipse.paho.client.mqttv3.MqttConnectOptions();
|
|
|
+ opt.setServerURIs(new String[]{url});
|
|
|
+ opt.setUserName(u);
|
|
|
+ if (p != null) opt.setPassword(p.toCharArray());
|
|
|
+ opt.setKeepAliveInterval(keepAlive);
|
|
|
+ return opt;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 移除旧的连接实例
|
|
|
+ * @param connectionInfo 连接信息
|
|
|
+ */
|
|
|
+ private void removeOldConnection(ConnectionInfo connectionInfo) {
|
|
|
+ // 移除旧的 Handler
|
|
|
+ if (context.containsBeanDefinition(connectionInfo.handlerBeanName)) {
|
|
|
+ try {
|
|
|
+ MqttPahoMessageHandler oldHandler = context.getBean(connectionInfo.handlerBeanName, MqttPahoMessageHandler.class);
|
|
|
+ oldHandler.stop();
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("停止旧的MQTT处理器时出错: {}", e.getMessage(), e);
|
|
|
+ }
|
|
|
+ context.removeBeanDefinition(connectionInfo.handlerBeanName);
|
|
|
+ log.info("已移除旧的 MQTT 处理器 -> {}", connectionInfo.handlerBeanName);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 从单例缓存中移除旧的 Handler
|
|
|
+ if (context.getDefaultListableBeanFactory().containsSingleton(connectionInfo.handlerBeanName)) {
|
|
|
+ context.getDefaultListableBeanFactory().destroySingleton(connectionInfo.handlerBeanName);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 移除旧的 Factory
|
|
|
+ if (context.containsBeanDefinition(connectionInfo.factoryBeanName)) {
|
|
|
+ context.removeBeanDefinition(connectionInfo.factoryBeanName);
|
|
|
+ log.info("已移除旧的 MQTT 工厂 -> {}", connectionInfo.factoryBeanName);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 从单例缓存中移除旧的 Factory
|
|
|
+ if (context.getDefaultListableBeanFactory().containsSingleton(connectionInfo.factoryBeanName)) {
|
|
|
+ context.getDefaultListableBeanFactory().destroySingleton(connectionInfo.factoryBeanName);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* ---------- 对外可调用的 setter ---------- */
|
|
|
+
|
|
|
+ public MqttConnectionTool defaultTopic(String topic) {
|
|
|
+ this.defaultTopic = topic;
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ public MqttConnectionTool keepAlive(int keepAlive) {
|
|
|
+ this.keepAlive = keepAlive;
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* ---------- 复用原来的 Gateway 接口 ---------- */
|
|
|
+ @MessagingGateway(defaultRequestChannel = "mqttOutboundChannel")
|
|
|
+ public interface MqttGateway {
|
|
|
+ void sendToMqtt(String payload);
|
|
|
+
|
|
|
+ void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic, String payload);
|
|
|
+
|
|
|
+ void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic,
|
|
|
+ @Header(MqttHeaders.QOS) int qos, String payload);
|
|
|
+ }
|
|
|
+}
|