Commit 1e2dc281e3ad2f7064eb0a857e2a0bc9ece9733a

Authored by YunaiV
1 parent c105ecea

feat(iot):【协议改造】mqtt 初步改造(20%)

Showing 16 changed files with 964 additions and 784 deletions
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayConfiguration.java
@@ -5,12 +5,7 @@ import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocolManager; @@ -5,12 +5,7 @@ import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocolManager;
5 import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxAuthEventProtocol; 5 import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxAuthEventProtocol;
6 import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxDownstreamSubscriber; 6 import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxDownstreamSubscriber;
7 import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxUpstreamProtocol; 7 import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxUpstreamProtocol;
8 -import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttDownstreamSubscriber;  
9 -import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttUpstreamProtocol;  
10 -import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager.IotMqttConnectionManager;  
11 -import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router.IotMqttDownstreamHandler;  
12 import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializerManager; 8 import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializerManager;
13 -import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;  
14 import io.vertx.core.Vertx; 9 import io.vertx.core.Vertx;
15 import lombok.extern.slf4j.Slf4j; 10 import lombok.extern.slf4j.Slf4j;
16 import org.springframework.beans.factory.annotation.Qualifier; 11 import org.springframework.beans.factory.annotation.Qualifier;
@@ -67,41 +62,4 @@ public class IotGatewayConfiguration { @@ -67,41 +62,4 @@ public class IotGatewayConfiguration {
67 } 62 }
68 } 63 }
69 64
70 - /**  
71 - * IoT 网关 MQTT 协议配置类  
72 - */  
73 - @Configuration  
74 - @ConditionalOnProperty(prefix = "yudao.iot.gateway.protocol.mqtt", name = "enabled", havingValue = "true")  
75 - @Slf4j  
76 - public static class MqttProtocolConfiguration {  
77 -  
78 - @Bean(name = "mqttVertx", destroyMethod = "close")  
79 - public Vertx mqttVertx() {  
80 - return Vertx.vertx();  
81 - }  
82 -  
83 - @Bean  
84 - public IotMqttUpstreamProtocol iotMqttUpstreamProtocol(IotGatewayProperties gatewayProperties,  
85 - IotDeviceMessageService messageService,  
86 - IotMqttConnectionManager connectionManager,  
87 - @Qualifier("mqttVertx") Vertx mqttVertx) {  
88 - return new IotMqttUpstreamProtocol(gatewayProperties.getProtocol().getMqtt(), messageService,  
89 - connectionManager, mqttVertx);  
90 - }  
91 -  
92 - @Bean  
93 - public IotMqttDownstreamHandler iotMqttDownstreamHandler(IotDeviceMessageService messageService,  
94 - IotMqttConnectionManager connectionManager) {  
95 - return new IotMqttDownstreamHandler(messageService, connectionManager);  
96 - }  
97 -  
98 - @Bean  
99 - public IotMqttDownstreamSubscriber iotMqttDownstreamSubscriber(IotMqttUpstreamProtocol mqttUpstreamProtocol,  
100 - IotMqttDownstreamHandler downstreamHandler,  
101 - IotMessageBus messageBus) {  
102 - return new IotMqttDownstreamSubscriber(mqttUpstreamProtocol, downstreamHandler, messageBus);  
103 - }  
104 -  
105 - }  
106 -  
107 } 65 }
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayProperties.java
1 package cn.iocoder.yudao.module.iot.gateway.config; 1 package cn.iocoder.yudao.module.iot.gateway.config;
2 2
3 -import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum;  
4 import cn.iocoder.yudao.module.iot.gateway.protocol.coap.IotCoapConfig; 3 import cn.iocoder.yudao.module.iot.gateway.protocol.coap.IotCoapConfig;
5 import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpConfig; 4 import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpConfig;
  5 +import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttConfig;
6 import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.IotTcpConfig; 6 import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.IotTcpConfig;
7 import cn.iocoder.yudao.module.iot.gateway.protocol.udp.IotUdpConfig; 7 import cn.iocoder.yudao.module.iot.gateway.protocol.udp.IotUdpConfig;
8 import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.IotWebSocketConfig; 8 import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.IotWebSocketConfig;
9 -import io.vertx.core.net.KeyCertOptions;  
10 -import io.vertx.core.net.TrustOptions;  
11 import jakarta.validation.Valid; 9 import jakarta.validation.Valid;
12 import jakarta.validation.constraints.NotEmpty; 10 import jakarta.validation.constraints.NotEmpty;
13 import jakarta.validation.constraints.NotNull; 11 import jakarta.validation.constraints.NotNull;
@@ -87,11 +85,6 @@ public class IotGatewayProperties { @@ -87,11 +85,6 @@ public class IotGatewayProperties {
87 */ 85 */
88 private EmqxProperties emqx; 86 private EmqxProperties emqx;
89 87
90 - /**  
91 - * MQTT 组件配置  
92 - */  
93 - private MqttProperties mqtt;  
94 -  
95 } 88 }
96 89
97 @Data 90 @Data
@@ -263,79 +256,6 @@ public class IotGatewayProperties { @@ -263,79 +256,6 @@ public class IotGatewayProperties {
263 256
264 } 257 }
265 258
266 - @Data  
267 - public static class MqttProperties {  
268 -  
269 - /**  
270 - * 是否开启  
271 - */  
272 - @NotNull(message = "是否开启不能为空")  
273 - private Boolean enabled;  
274 -  
275 - /**  
276 - * 服务器端口  
277 - */  
278 - private Integer port = 1883;  
279 -  
280 - /**  
281 - * 最大消息大小(字节)  
282 - */  
283 - private Integer maxMessageSize = 8192;  
284 -  
285 - /**  
286 - * 连接超时时间(秒)  
287 - */  
288 - private Integer connectTimeoutSeconds = 60;  
289 - /**  
290 - * 保持连接超时时间(秒)  
291 - */  
292 - private Integer keepAliveTimeoutSeconds = 300;  
293 -  
294 - // NOTE:SSL 相关参数后续统一到 protocol 层级(优先级低)  
295 - /**  
296 - * 是否启用 SSL  
297 - */  
298 - private Boolean sslEnabled = false;  
299 - /**  
300 - * SSL 配置  
301 - */  
302 - private SslOptions sslOptions = new SslOptions();  
303 -  
304 - /**  
305 - * SSL 配置选项  
306 - */  
307 - @Data  
308 - public static class SslOptions {  
309 -  
310 - /**  
311 - * 密钥证书选项  
312 - */  
313 - private KeyCertOptions keyCertOptions;  
314 - /**  
315 - * 信任选项  
316 - */  
317 - private TrustOptions trustOptions;  
318 - /**  
319 - * SSL 证书路径  
320 - */  
321 - private String certPath;  
322 - /**  
323 - * SSL 私钥路径  
324 - */  
325 - private String keyPath;  
326 - /**  
327 - * 信任存储路径  
328 - */  
329 - private String trustStorePath;  
330 - /**  
331 - * 信任存储密码  
332 - */  
333 - private String trustStorePassword;  
334 -  
335 - }  
336 -  
337 - }  
338 -  
339 // NOTE:暂未统一为 ProtocolProperties,待协议改造完成再调整 259 // NOTE:暂未统一为 ProtocolProperties,待协议改造完成再调整
340 /** 260 /**
341 * 协议实例配置 261 * 协议实例配置
@@ -376,6 +296,8 @@ public class IotGatewayProperties { @@ -376,6 +296,8 @@ public class IotGatewayProperties {
376 */ 296 */
377 private String serialize; 297 private String serialize;
378 298
  299 + // ========== 各协议配置 ==========
  300 +
379 /** 301 /**
380 * HTTP 协议配置 302 * HTTP 协议配置
381 */ 303 */
@@ -406,6 +328,12 @@ public class IotGatewayProperties { @@ -406,6 +328,12 @@ public class IotGatewayProperties {
406 @Valid 328 @Valid
407 private IotWebSocketConfig websocket; 329 private IotWebSocketConfig websocket;
408 330
  331 + /**
  332 + * MQTT 协议配置
  333 + */
  334 + @Valid
  335 + private IotMqttConfig mqtt;
  336 +
409 } 337 }
410 338
411 } 339 }
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/IotProtocolManager.java
@@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum; @@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum;
6 import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties; 6 import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties;
7 import cn.iocoder.yudao.module.iot.gateway.protocol.coap.IotCoapProtocol; 7 import cn.iocoder.yudao.module.iot.gateway.protocol.coap.IotCoapProtocol;
8 import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpProtocol; 8 import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpProtocol;
  9 +import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttProtocol;
9 import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.IotTcpProtocol; 10 import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.IotTcpProtocol;
10 import cn.iocoder.yudao.module.iot.gateway.protocol.udp.IotUdpProtocol; 11 import cn.iocoder.yudao.module.iot.gateway.protocol.udp.IotUdpProtocol;
11 import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.IotWebSocketProtocol; 12 import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.IotWebSocketProtocol;
@@ -106,6 +107,8 @@ public class IotProtocolManager implements SmartLifecycle { @@ -106,6 +107,8 @@ public class IotProtocolManager implements SmartLifecycle {
106 return createCoapProtocol(config); 107 return createCoapProtocol(config);
107 case WEBSOCKET: 108 case WEBSOCKET:
108 return createWebSocketProtocol(config); 109 return createWebSocketProtocol(config);
  110 + case MQTT:
  111 + return createMqttProtocol(config);
109 default: 112 default:
110 throw new IllegalArgumentException(String.format( 113 throw new IllegalArgumentException(String.format(
111 "[createProtocol][协议实例 %s 的协议类型 %s 暂不支持]", config.getId(), protocolType)); 114 "[createProtocol][协议实例 %s 的协议类型 %s 暂不支持]", config.getId(), protocolType));
@@ -162,4 +165,14 @@ public class IotProtocolManager implements SmartLifecycle { @@ -162,4 +165,14 @@ public class IotProtocolManager implements SmartLifecycle {
162 return new IotWebSocketProtocol(config); 165 return new IotWebSocketProtocol(config);
163 } 166 }
164 167
  168 + /**
  169 + * 创建 MQTT 协议实例
  170 + *
  171 + * @param config 协议实例配置
  172 + * @return MQTT 协议实例
  173 + */
  174 + private IotMqttProtocol createMqttProtocol(IotGatewayProperties.ProtocolInstanceProperties config) {
  175 + return new IotMqttProtocol(config);
  176 + }
  177 +
165 } 178 }
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotMqttConfig.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt;
  2 +
  3 +import lombok.Data;
  4 +
  5 +// TODO @AI:validator 参数校验。也看看其他几个配置类有没有类似问题
  6 +/**
  7 + * IoT 网关 MQTT 协议配置
  8 + *
  9 + * @author 芋道源码
  10 + */
  11 +@Data
  12 +public class IotMqttConfig {
  13 +
  14 + /**
  15 + * 是否启用 SSL
  16 + */
  17 + private Boolean sslEnabled = false;
  18 +
  19 + /**
  20 + * SSL 证书路径
  21 + */
  22 + private String sslCertPath;
  23 +
  24 + /**
  25 + * SSL 私钥路径
  26 + */
  27 + private String sslKeyPath;
  28 +
  29 + /**
  30 + * 最大消息大小(字节)
  31 + */
  32 + private Integer maxMessageSize = 8192;
  33 +
  34 + /**
  35 + * 连接超时时间(秒)
  36 + */
  37 + private Integer connectTimeoutSeconds = 60;
  38 +
  39 + /**
  40 + * 保持连接超时时间(秒)
  41 + */
  42 + private Integer keepAliveTimeoutSeconds = 300;
  43 +
  44 +}
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotMqttProtocol.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt;
  2 +
  3 +import cn.hutool.extra.spring.SpringUtil;
  4 +import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
  5 +import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum;
  6 +import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
  7 +import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
  8 +import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties.ProtocolInstanceProperties;
  9 +import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocol;
  10 +import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.downstream.IotMqttDownstreamHandler;
  11 +import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.downstream.IotMqttDownstreamSubscriber;
  12 +import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.upstream.IotMqttConnectionHandler;
  13 +import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.upstream.IotMqttRegisterHandler;
  14 +import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.upstream.IotMqttUpstreamHandler;
  15 +import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager.IotMqttConnectionManager;
  16 +import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
  17 +import io.netty.handler.codec.mqtt.MqttConnectReturnCode;
  18 +import io.netty.handler.codec.mqtt.MqttQoS;
  19 +import io.vertx.core.Vertx;
  20 +import io.vertx.core.net.PemKeyCertOptions;
  21 +import io.vertx.mqtt.MqttEndpoint;
  22 +import io.vertx.mqtt.MqttServer;
  23 +import io.vertx.mqtt.MqttServerOptions;
  24 +import io.vertx.mqtt.MqttTopicSubscription;
  25 +import io.vertx.mqtt.messages.MqttPublishMessage;
  26 +import lombok.Getter;
  27 +import lombok.extern.slf4j.Slf4j;
  28 +
  29 +import java.util.List;
  30 +import java.util.stream.Collectors;
  31 +
  32 +/**
  33 + * IoT 网关 MQTT 协议:接收设备上行消息
  34 + *
  35 + * @author 芋道源码
  36 + */
  37 +@Slf4j
  38 +public class IotMqttProtocol implements IotProtocol {
  39 +
  40 + /**
  41 + * 协议配置
  42 + */
  43 + private final ProtocolInstanceProperties properties;
  44 + /**
  45 + * 服务器 ID(用于消息追踪,全局唯一)
  46 + */
  47 + @Getter
  48 + private final String serverId;
  49 +
  50 + /**
  51 + * 运行状态
  52 + */
  53 + @Getter
  54 + private volatile boolean running = false;
  55 +
  56 + /**
  57 + * Vert.x 实例
  58 + */
  59 + private Vertx vertx;
  60 + /**
  61 + * MQTT 服务器
  62 + */
  63 + private MqttServer mqttServer;
  64 + /**
  65 + * 连接管理器
  66 + */
  67 + private IotMqttConnectionManager connectionManager;
  68 +
  69 + /**
  70 + * 下行消息订阅者
  71 + */
  72 + private IotMqttDownstreamSubscriber downstreamSubscriber;
  73 +
  74 + // TODO @AI:这个是不是提前创建下?因为是无状态的。
  75 + private IotMqttConnectionHandler connectionHandler;
  76 + private IotMqttRegisterHandler registerHandler;
  77 + private IotMqttUpstreamHandler upstreamHandler;
  78 +
  79 + public IotMqttProtocol(ProtocolInstanceProperties properties) {
  80 + this.properties = properties;
  81 + this.serverId = IotDeviceMessageUtils.generateServerId(properties.getPort());
  82 +
  83 + // TODO @AI:初始化连接器,参考 IotTcpProtocol
  84 +
  85 + // TODO @AI:初始化下行消息订阅者,参考 IotTcpProtocol
  86 + }
  87 +
  88 + @Override
  89 + public String getId() {
  90 + return properties.getId();
  91 + }
  92 +
  93 + @Override
  94 + public IotProtocolTypeEnum getType() {
  95 + return IotProtocolTypeEnum.MQTT;
  96 + }
  97 +
  98 + // TODO @AI:这个方法的整体注释风格,参考 IotTcpProtocol 的 start 方法。
  99 + @Override
  100 + public void start() {
  101 + if (running) {
  102 + log.warn("[start][IoT MQTT 协议 {} 已经在运行中]", getId());
  103 + return;
  104 + }
  105 +
  106 + // 1.1 创建 Vertx 实例(每个 Protocol 独立管理)
  107 + this.vertx = Vertx.vertx();
  108 +
  109 + // 1.2 创建连接管理器
  110 + this.connectionManager = new IotMqttConnectionManager();
  111 +
  112 + // 1.3 初始化 Handler
  113 + initHandlers();
  114 +
  115 + // 2. 创建服务器选项
  116 + IotMqttConfig mqttConfig = properties.getMqtt();
  117 + // TODO @AI:default 值,在 IotMqttConfig 处理;
  118 + MqttServerOptions options = new MqttServerOptions()
  119 + .setPort(properties.getPort())
  120 + .setMaxMessageSize(mqttConfig != null ? mqttConfig.getMaxMessageSize() : 8192)
  121 + .setTimeoutOnConnect(mqttConfig != null ? mqttConfig.getConnectTimeoutSeconds() : 60);
  122 +
  123 + // 3. 配置 SSL(如果启用)
  124 + if (mqttConfig != null && Boolean.TRUE.equals(mqttConfig.getSslEnabled())) {
  125 + PemKeyCertOptions pemKeyCertOptions = new PemKeyCertOptions()
  126 + .setKeyPath(mqttConfig.getSslKeyPath())
  127 + .setCertPath(mqttConfig.getSslCertPath());
  128 + options.setSsl(true).setKeyCertOptions(pemKeyCertOptions);
  129 + }
  130 +
  131 + // 4. 创建服务器并设置连接处理器
  132 + mqttServer = MqttServer.create(vertx, options);
  133 + mqttServer.endpointHandler(this::handleEndpoint);
  134 +
  135 + // 5. 启动服务器
  136 + try {
  137 + mqttServer.listen().result();
  138 + running = true;
  139 + log.info("[start][IoT MQTT 协议 {} 启动成功,端口:{},serverId:{}]",
  140 + getId(), properties.getPort(), serverId);
  141 +
  142 + // 6. 启动下行消息订阅者
  143 + IotMessageBus messageBus = SpringUtil.getBean(IotMessageBus.class);
  144 + IotMqttDownstreamHandler downstreamHandler = new IotMqttDownstreamHandler(
  145 + SpringUtil.getBean(IotDeviceMessageService.class), connectionManager);
  146 + this.downstreamSubscriber = new IotMqttDownstreamSubscriber(this, downstreamHandler, messageBus);
  147 + this.downstreamSubscriber.start();
  148 + } catch (Exception e) {
  149 + log.error("[start][IoT MQTT 协议 {} 启动失败]", getId(), e);
  150 + // 启动失败时关闭 Vertx
  151 + if (vertx != null) {
  152 + vertx.close();
  153 + vertx = null;
  154 + }
  155 + throw e;
  156 + }
  157 + }
  158 +
  159 + @Override
  160 + public void stop() {
  161 + if (!running) {
  162 + return;
  163 + }
  164 + // 1. 停止下行消息订阅者
  165 + if (downstreamSubscriber != null) {
  166 + try {
  167 + downstreamSubscriber.stop();
  168 + log.info("[stop][IoT MQTT 协议 {} 下行消息订阅者已停止]", getId());
  169 + } catch (Exception e) {
  170 + log.error("[stop][IoT MQTT 协议 {} 下行消息订阅者停止失败]", getId(), e);
  171 + }
  172 + downstreamSubscriber = null;
  173 + }
  174 +
  175 + // 2.1 关闭 MQTT 服务器
  176 + if (mqttServer != null) {
  177 + try {
  178 + mqttServer.close().result();
  179 + log.info("[stop][IoT MQTT 协议 {} 服务器已停止]", getId());
  180 + } catch (Exception e) {
  181 + log.error("[stop][IoT MQTT 协议 {} 服务器停止失败]", getId(), e);
  182 + }
  183 + mqttServer = null;
  184 + }
  185 + // 2.2 关闭 Vertx 实例
  186 + if (vertx != null) {
  187 + try {
  188 + vertx.close().result();
  189 + log.info("[stop][IoT MQTT 协议 {} Vertx 已关闭]", getId());
  190 + } catch (Exception e) {
  191 + log.error("[stop][IoT MQTT 协议 {} Vertx 关闭失败]", getId(), e);
  192 + }
  193 + vertx = null;
  194 + }
  195 + running = false;
  196 + log.info("[stop][IoT MQTT 协议 {} 已停止]", getId());
  197 + }
  198 +
  199 + /**
  200 + * 初始化 Handler
  201 + */
  202 + private void initHandlers() {
  203 + IotDeviceMessageService messageService = SpringUtil.getBean(IotDeviceMessageService.class);
  204 + IotDeviceCommonApi deviceApi = SpringUtil.getBean(IotDeviceCommonApi.class);
  205 + this.connectionHandler = new IotMqttConnectionHandler(connectionManager, messageService, deviceApi, serverId);
  206 + this.registerHandler = new IotMqttRegisterHandler(connectionManager, messageService, deviceApi);
  207 + this.upstreamHandler = new IotMqttUpstreamHandler(connectionManager, messageService, serverId);
  208 + }
  209 +
  210 + /**
  211 + * 处理 MQTT 连接端点
  212 + *
  213 + * @param endpoint MQTT 连接端点
  214 + */
  215 + private void handleEndpoint(MqttEndpoint endpoint) {
  216 + String clientId = endpoint.clientIdentifier();
  217 +
  218 + // 1. 委托 connectionHandler 处理连接认证
  219 + // TODO @AI:register topic 不需要注册,需要判断下;
  220 + if (!connectionHandler.handleConnect(endpoint)) {
  221 + endpoint.reject(MqttConnectReturnCode.CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD);
  222 + return;
  223 + }
  224 +
  225 + // 2.1 设置异常和关闭处理器
  226 + endpoint.exceptionHandler(ex -> {
  227 + log.warn("[handleEndpoint][连接异常,客户端 ID: {},地址: {}]",
  228 + clientId, connectionManager.getEndpointAddress(endpoint));
  229 + // TODO @AI:是不是改成 endpoint close 更合适?
  230 + connectionHandler.cleanupConnection(endpoint);
  231 + });
  232 + endpoint.closeHandler(v -> connectionHandler.cleanupConnection(endpoint));
  233 + endpoint.disconnectHandler(v -> {
  234 + log.debug("[handleEndpoint][设备断开连接,客户端 ID: {}]", clientId);
  235 + connectionHandler.cleanupConnection(endpoint);
  236 + });
  237 + // 2.2 设置心跳处理器
  238 + endpoint.pingHandler(v -> log.debug("[handleEndpoint][收到客户端心跳,客户端 ID: {}]", clientId));
  239 +
  240 + // 3.1 设置消息处理器
  241 + endpoint.publishHandler(message -> processMessage(endpoint, message));
  242 + // 3.2 设置 QoS 2 消息的 PUBREL 处理器
  243 + endpoint.publishReleaseHandler(endpoint::publishComplete);
  244 +
  245 + // 4.1 设置订阅处理器
  246 + endpoint.subscribeHandler(subscribe -> {
  247 + // TODO @AI:convertList 简化;
  248 + List<String> topicNames = subscribe.topicSubscriptions().stream()
  249 + .map(MqttTopicSubscription::topicName)
  250 + .collect(Collectors.toList());
  251 + log.debug("[handleEndpoint][设备订阅,客户端 ID: {},主题: {}]", clientId, topicNames);
  252 +
  253 + // TODO @AI:convertList 简化;
  254 + List<MqttQoS> grantedQoSLevels = subscribe.topicSubscriptions().stream()
  255 + .map(MqttTopicSubscription::qualityOfService)
  256 + .collect(Collectors.toList());
  257 + endpoint.subscribeAcknowledge(subscribe.messageId(), grantedQoSLevels);
  258 + });
  259 + // 4.2 设置取消订阅处理器
  260 + endpoint.unsubscribeHandler(unsubscribe -> {
  261 + log.debug("[handleEndpoint][设备取消订阅,客户端 ID: {},主题: {}]", clientId, unsubscribe.topics());
  262 + endpoint.unsubscribeAcknowledge(unsubscribe.messageId());
  263 + });
  264 +
  265 + // 5. 接受连接
  266 + endpoint.accept(false);
  267 + }
  268 +
  269 + /**
  270 + * 处理消息(发布)
  271 + *
  272 + * @param endpoint MQTT 连接端点
  273 + * @param message 发布消息
  274 + */
  275 + // TODO @AI:看看要不要一定程度,参考 IotTcpUpstreamHandler 的 processMessage 方法;
  276 + private void processMessage(MqttEndpoint endpoint, MqttPublishMessage message) {
  277 + String clientId = endpoint.clientIdentifier();
  278 + try {
  279 + String topic = message.topicName();
  280 + byte[] payload = message.payload().getBytes();
  281 +
  282 + // 根据 topic 分发到不同 handler
  283 + if (registerHandler.isRegisterMessage(topic)) {
  284 + registerHandler.handleRegister(endpoint, topic, payload);
  285 + } else {
  286 + upstreamHandler.handleMessage(endpoint, topic, payload);
  287 + }
  288 +
  289 + // 根据 QoS 级别发送相应的确认消息
  290 + handleQoSAck(endpoint, message);
  291 + } catch (Exception e) {
  292 + // TODO @AI:异常的时候,直接断开;
  293 + log.error("[handlePublish][消息处理失败,断开连接,客户端 ID: {},地址: {},错误: {}]",
  294 + clientId, connectionManager.getEndpointAddress(endpoint), e.getMessage());
  295 + connectionHandler.cleanupConnection(endpoint);
  296 + endpoint.close();
  297 + }
  298 + }
  299 +
  300 + /**
  301 + * 处理 QoS 确认
  302 + *
  303 + * @param endpoint MQTT 连接端点
  304 + * @param message 发布消息
  305 + */
  306 + private void handleQoSAck(MqttEndpoint endpoint, MqttPublishMessage message) {
  307 + if (message.qosLevel() == MqttQoS.AT_LEAST_ONCE) {
  308 + // QoS 1: 发送 PUBACK 确认
  309 + endpoint.publishAcknowledge(message.messageId());
  310 + } else if (message.qosLevel() == MqttQoS.EXACTLY_ONCE) {
  311 + // QoS 2: 发送 PUBREC 确认
  312 + endpoint.publishReceived(message.messageId());
  313 + }
  314 + // QoS 0 无需确认
  315 + }
  316 +
  317 +}
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotMqttUpstreamProtocol.java deleted
1 -package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt;  
2 -  
3 -import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum;  
4 -import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;  
5 -import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties;  
6 -import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocol;  
7 -import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager.IotMqttConnectionManager;  
8 -import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router.IotMqttUpstreamHandler;  
9 -import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;  
10 -import io.vertx.core.Vertx;  
11 -import io.vertx.mqtt.MqttServer;  
12 -import io.vertx.mqtt.MqttServerOptions;  
13 -import jakarta.annotation.PostConstruct;  
14 -import jakarta.annotation.PreDestroy;  
15 -import lombok.Getter;  
16 -import lombok.extern.slf4j.Slf4j;  
17 -  
18 -/**  
19 - * IoT 网关 MQTT 协议:接收设备上行消息  
20 - *  
21 - * @author 芋道源码  
22 - */  
23 -@Slf4j  
24 -public class IotMqttUpstreamProtocol implements IotProtocol {  
25 -  
26 - private static final String ID = "mqtt";  
27 -  
28 - private volatile boolean running = false;  
29 -  
30 - private final IotGatewayProperties.MqttProperties mqttProperties;  
31 -  
32 - private final IotDeviceMessageService messageService;  
33 -  
34 - private final IotMqttConnectionManager connectionManager;  
35 -  
36 - private final Vertx vertx;  
37 -  
38 - @Getter  
39 - private final String serverId;  
40 -  
41 - private MqttServer mqttServer;  
42 -  
43 - public IotMqttUpstreamProtocol(IotGatewayProperties.MqttProperties mqttProperties,  
44 - IotDeviceMessageService messageService,  
45 - IotMqttConnectionManager connectionManager,  
46 - Vertx vertx) {  
47 - this.mqttProperties = mqttProperties;  
48 - this.messageService = messageService;  
49 - this.connectionManager = connectionManager;  
50 - this.vertx = vertx;  
51 - this.serverId = IotDeviceMessageUtils.generateServerId(mqttProperties.getPort());  
52 - }  
53 -  
54 - @Override  
55 - public String getId() {  
56 - return ID;  
57 - }  
58 -  
59 - @Override  
60 - public IotProtocolTypeEnum getType() {  
61 - return IotProtocolTypeEnum.MQTT;  
62 - }  
63 -  
64 - @Override  
65 - public boolean isRunning() {  
66 - return running;  
67 - }  
68 -  
69 - // TODO @haohao:这里的编写,是不是和 tcp 对应的,风格保持一致哈;  
70 - @Override  
71 - @PostConstruct  
72 - public void start() {  
73 - // 创建服务器选项  
74 - MqttServerOptions options = new MqttServerOptions()  
75 - .setPort(mqttProperties.getPort())  
76 - .setMaxMessageSize(mqttProperties.getMaxMessageSize())  
77 - .setTimeoutOnConnect(mqttProperties.getConnectTimeoutSeconds());  
78 -  
79 - // 配置 SSL(如果启用)  
80 - if (Boolean.TRUE.equals(mqttProperties.getSslEnabled())) {  
81 - options.setSsl(true)  
82 - .setKeyCertOptions(mqttProperties.getSslOptions().getKeyCertOptions())  
83 - .setTrustOptions(mqttProperties.getSslOptions().getTrustOptions());  
84 - }  
85 -  
86 - // 创建服务器并设置连接处理器  
87 - mqttServer = MqttServer.create(vertx, options);  
88 - mqttServer.endpointHandler(endpoint -> {  
89 - IotMqttUpstreamHandler handler = new IotMqttUpstreamHandler(this, messageService, connectionManager);  
90 - handler.handle(endpoint);  
91 - });  
92 -  
93 - // 启动服务器  
94 - try {  
95 - mqttServer.listen().result();  
96 - running = true;  
97 - log.info("[start][IoT 网关 MQTT 协议启动成功,端口:{}]", mqttProperties.getPort());  
98 - } catch (Exception e) {  
99 - log.error("[start][IoT 网关 MQTT 协议启动失败]", e);  
100 - throw e;  
101 - }  
102 - }  
103 -  
104 - @Override  
105 - @PreDestroy  
106 - public void stop() {  
107 - if (mqttServer != null) {  
108 - try {  
109 - mqttServer.close().result();  
110 - running = false;  
111 - log.info("[stop][IoT 网关 MQTT 协议已停止]");  
112 - } catch (Exception e) {  
113 - log.error("[stop][IoT 网关 MQTT 协议停止失败]", e);  
114 - }  
115 - }  
116 - }  
117 -}  
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/router/IotMqttDownstreamHandler.java renamed to yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/handler/downstream/IotMqttDownstreamHandler.java
1 -package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router; 1 +package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.downstream;
2 2
3 import cn.hutool.core.util.StrUtil; 3 import cn.hutool.core.util.StrUtil;
4 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 4 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
@@ -7,31 +7,22 @@ import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager.IotMqttConnecti @@ -7,31 +7,22 @@ import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager.IotMqttConnecti
7 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; 7 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
8 import cn.iocoder.yudao.module.iot.gateway.util.IotMqttTopicUtils; 8 import cn.iocoder.yudao.module.iot.gateway.util.IotMqttTopicUtils;
9 import io.netty.handler.codec.mqtt.MqttQoS; 9 import io.netty.handler.codec.mqtt.MqttQoS;
  10 +import lombok.RequiredArgsConstructor;
10 import lombok.extern.slf4j.Slf4j; 11 import lombok.extern.slf4j.Slf4j;
11 12
12 /** 13 /**
13 * IoT 网关 MQTT 协议:下行消息处理器 14 * IoT 网关 MQTT 协议:下行消息处理器
14 - * <p>  
15 - * 专门处理下行消息的业务逻辑,包括:  
16 - * 1. 消息编码  
17 - * 2. 主题构建  
18 - * 3. 消息发送  
19 * 15 *
20 * @author 芋道源码 16 * @author 芋道源码
21 */ 17 */
22 @Slf4j 18 @Slf4j
  19 +@RequiredArgsConstructor
23 public class IotMqttDownstreamHandler { 20 public class IotMqttDownstreamHandler {
24 21
25 private final IotDeviceMessageService deviceMessageService; 22 private final IotDeviceMessageService deviceMessageService;
26 23
27 private final IotMqttConnectionManager connectionManager; 24 private final IotMqttConnectionManager connectionManager;
28 25
29 - public IotMqttDownstreamHandler(IotDeviceMessageService deviceMessageService,  
30 - IotMqttConnectionManager connectionManager) {  
31 - this.deviceMessageService = deviceMessageService;  
32 - this.connectionManager = connectionManager;  
33 - }  
34 -  
35 /** 26 /**
36 * 处理下行消息 27 * 处理下行消息
37 * 28 *
@@ -40,6 +31,7 @@ public class IotMqttDownstreamHandler { @@ -40,6 +31,7 @@ public class IotMqttDownstreamHandler {
40 */ 31 */
41 public boolean handleDownstreamMessage(IotDeviceMessage message) { 32 public boolean handleDownstreamMessage(IotDeviceMessage message) {
42 try { 33 try {
  34 + // TODO @AI:参考 IotTcpDownstreamHandler 逻辑;
43 // 1. 基础校验 35 // 1. 基础校验
44 if (message == null || message.getDeviceId() == null) { 36 if (message == null || message.getDeviceId() == null) {
45 log.warn("[handleDownstreamMessage][消息或设备 ID 为空,忽略处理]"); 37 log.warn("[handleDownstreamMessage][消息或设备 ID 为空,忽略处理]");
@@ -47,6 +39,7 @@ public class IotMqttDownstreamHandler { @@ -47,6 +39,7 @@ public class IotMqttDownstreamHandler {
47 } 39 }
48 40
49 // 2. 检查设备是否在线 41 // 2. 检查设备是否在线
  42 + // TODO @AI:这块逻辑,是不是冗余?直接使用 3. 获取连接信息判断不就行了?
50 if (connectionManager.isDeviceOffline(message.getDeviceId())) { 43 if (connectionManager.isDeviceOffline(message.getDeviceId())) {
51 log.warn("[handleDownstreamMessage][设备离线,无法发送消息,设备 ID:{}]", message.getDeviceId()); 44 log.warn("[handleDownstreamMessage][设备离线,无法发送消息,设备 ID:{}]", message.getDeviceId());
52 return false; 45 return false;
@@ -59,7 +52,7 @@ public class IotMqttDownstreamHandler { @@ -59,7 +52,7 @@ public class IotMqttDownstreamHandler {
59 return false; 52 return false;
60 } 53 }
61 54
62 - // 4. 编码消息 55 + // 4. 序列化
63 byte[] payload = deviceMessageService.encodeDeviceMessage(message, connectionInfo.getProductKey(), 56 byte[] payload = deviceMessageService.encodeDeviceMessage(message, connectionInfo.getProductKey(),
64 connectionInfo.getDeviceName()); 57 connectionInfo.getDeviceName());
65 if (payload == null || payload.length == 0) { 58 if (payload == null || payload.length == 0) {
@@ -68,6 +61,7 @@ public class IotMqttDownstreamHandler { @@ -68,6 +61,7 @@ public class IotMqttDownstreamHandler {
68 } 61 }
69 62
70 // 5. 发送消息到设备 63 // 5. 发送消息到设备
  64 + // TODO @AI:参考 IotTcpDownstreamHandler 的逻辑;
71 return sendMessageToDevice(message, connectionInfo, payload); 65 return sendMessageToDevice(message, connectionInfo, payload);
72 } catch (Exception e) { 66 } catch (Exception e) {
73 if (message != null) { 67 if (message != null) {
@@ -78,6 +72,7 @@ public class IotMqttDownstreamHandler { @@ -78,6 +72,7 @@ public class IotMqttDownstreamHandler {
78 } 72 }
79 } 73 }
80 74
  75 + // TODO @AI 是不是合并到 handleDownstreamMessage 里;
81 /** 76 /**
82 * 发送消息到设备 77 * 发送消息到设备
83 * 78 *
@@ -91,6 +86,7 @@ public class IotMqttDownstreamHandler { @@ -91,6 +86,7 @@ public class IotMqttDownstreamHandler {
91 byte[] payload) { 86 byte[] payload) {
92 // 1. 构建主题 87 // 1. 构建主题
93 String topic = buildDownstreamTopic(message, connectionInfo); 88 String topic = buildDownstreamTopic(message, connectionInfo);
  89 + // TODO @AI:直接断言,非空!
94 if (StrUtil.isBlank(topic)) { 90 if (StrUtil.isBlank(topic)) {
95 log.warn("[sendMessageToDevice][主题构建失败,设备 ID:{},方法:{}]", 91 log.warn("[sendMessageToDevice][主题构建失败,设备 ID:{},方法:{}]",
96 message.getDeviceId(), message.getMethod()); 92 message.getDeviceId(), message.getMethod());
@@ -118,6 +114,7 @@ public class IotMqttDownstreamHandler { @@ -118,6 +114,7 @@ public class IotMqttDownstreamHandler {
118 */ 114 */
119 private String buildDownstreamTopic(IotDeviceMessage message, 115 private String buildDownstreamTopic(IotDeviceMessage message,
120 IotMqttConnectionManager.ConnectionInfo connectionInfo) { 116 IotMqttConnectionManager.ConnectionInfo connectionInfo) {
  117 + // TODO @AI:直接断言,非空!
121 String method = message.getMethod(); 118 String method = message.getMethod();
122 if (StrUtil.isBlank(method)) { 119 if (StrUtil.isBlank(method)) {
123 return null; 120 return null;
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotMqttDownstreamSubscriber.java renamed to yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/handler/downstream/IotMqttDownstreamSubscriber.java
1 -package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt; 1 +package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.downstream;
2 2
3 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus; 3 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
4 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 4 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
5 import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocolDownstreamSubscriber; 5 import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocolDownstreamSubscriber;
6 -import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router.IotMqttDownstreamHandler; 6 +import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttProtocol;
7 import lombok.extern.slf4j.Slf4j; 7 import lombok.extern.slf4j.Slf4j;
8 8
9 /** 9 /**
@@ -18,7 +18,7 @@ public class IotMqttDownstreamSubscriber extends IotProtocolDownstreamSubscriber @@ -18,7 +18,7 @@ public class IotMqttDownstreamSubscriber extends IotProtocolDownstreamSubscriber
18 18
19 private final IotMqttDownstreamHandler downstreamHandler; 19 private final IotMqttDownstreamHandler downstreamHandler;
20 20
21 - public IotMqttDownstreamSubscriber(IotMqttUpstreamProtocol protocol, 21 + public IotMqttDownstreamSubscriber(IotMqttProtocol protocol,
22 IotMqttDownstreamHandler downstreamHandler, 22 IotMqttDownstreamHandler downstreamHandler,
23 IotMessageBus messageBus) { 23 IotMessageBus messageBus) {
24 super(protocol, messageBus); 24 super(protocol, messageBus);
@@ -27,6 +27,7 @@ public class IotMqttDownstreamSubscriber extends IotProtocolDownstreamSubscriber @@ -27,6 +27,7 @@ public class IotMqttDownstreamSubscriber extends IotProtocolDownstreamSubscriber
27 27
28 @Override 28 @Override
29 protected void handleMessage(IotDeviceMessage message) { 29 protected void handleMessage(IotDeviceMessage message) {
  30 + // TODO @AI:参考 IotTcpDownstreamHandler 不处理返回值,甚至不用返回值;
30 boolean success = downstreamHandler.handleDownstreamMessage(message); 31 boolean success = downstreamHandler.handleDownstreamMessage(message);
31 if (success) { 32 if (success) {
32 log.debug("[handleMessage][下行消息处理成功, messageId: {}, method: {}, deviceId: {}]", 33 log.debug("[handleMessage][下行消息处理成功, messageId: {}, method: {}, deviceId: {}]",
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/handler/upstream/IotMqttAbstractHandler.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.upstream;
  2 +
  3 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  4 +import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager.IotMqttConnectionManager;
  5 +import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
  6 +import cn.iocoder.yudao.module.iot.gateway.util.IotMqttTopicUtils;
  7 +import io.netty.handler.codec.mqtt.MqttQoS;
  8 +import io.vertx.core.buffer.Buffer;
  9 +import io.vertx.mqtt.MqttEndpoint;
  10 +import lombok.RequiredArgsConstructor;
  11 +import lombok.extern.slf4j.Slf4j;
  12 +
  13 +/**
  14 + * IoT 网关 MQTT 协议的处理器抽象基类
  15 + * <p>
  16 + * 提供通用的连接校验、响应发送等功能
  17 + *
  18 + * @author 芋道源码
  19 + */
  20 +@Slf4j
  21 +@RequiredArgsConstructor
  22 +public abstract class IotMqttAbstractHandler {
  23 +
  24 + // TODO @AI:不使用 codec,使用 serializer 来进行编解码;
  25 + /**
  26 + * 默认编解码类型(MQTT 使用 Alink 协议)
  27 + */
  28 + protected static final String DEFAULT_CODEC_TYPE = "Alink";
  29 +
  30 + protected final IotMqttConnectionManager connectionManager;
  31 + protected final IotDeviceMessageService deviceMessageService;
  32 +
  33 + // TODO @AI:这个是否需要???!!!
  34 + /**
  35 + * 是否需要连接已认证(默认 true)
  36 + * <p>
  37 + * 仅 IotMqttConnectionHandler 覆盖为 false
  38 + *
  39 + * @return 是否需要连接已认证
  40 + */
  41 + protected boolean requiresAuthenticated() {
  42 + return true;
  43 + }
  44 +
  45 + // TODO @AI:不确定,是不是基于 method 就可以算出来 reply topic ???!!!
  46 + // TODO @AI:需要传递 seriabler 序列对象,不是通过 deviceMessageService.encodeDeviceMessage 获取到合适的;
  47 + /**
  48 + * 发送成功响应到设备
  49 + *
  50 + * @param endpoint MQTT 连接端点
  51 + * @param productKey 产品 Key
  52 + * @param deviceName 设备名称
  53 + * @param requestId 请求 ID
  54 + * @param method 方法名
  55 + * @param data 响应数据
  56 + */
  57 + protected void sendSuccessResponse(MqttEndpoint endpoint, String productKey, String deviceName,
  58 + String requestId, String method, Object data) {
  59 + try {
  60 + // 1. 构建响应消息
  61 + IotDeviceMessage responseMessage = IotDeviceMessage.replyOf(requestId, method, data, 0, null);
  62 +
  63 + // 2. 编码消息(使用默认编解码器)
  64 + byte[] encodedData = deviceMessageService.encodeDeviceMessage(responseMessage, DEFAULT_CODEC_TYPE);
  65 +
  66 + // 3. 构建响应主题并发送
  67 + String replyTopic = IotMqttTopicUtils.buildTopicByMethod(method, productKey, deviceName, true);
  68 + endpoint.publish(replyTopic, Buffer.buffer(encodedData), MqttQoS.AT_LEAST_ONCE, false, false);
  69 + log.debug("[sendSuccessResponse][发送成功响应,主题: {}]", replyTopic);
  70 + } catch (Exception e) {
  71 + log.error("[sendSuccessResponse][发送成功响应异常,客户端 ID: {}]", endpoint.clientIdentifier(), e);
  72 + }
  73 + }
  74 +
  75 + /**
  76 + * 发送错误响应到设备
  77 + *
  78 + * @param endpoint MQTT 连接端点
  79 + * @param productKey 产品 Key
  80 + * @param deviceName 设备名称
  81 + * @param requestId 请求 ID
  82 + * @param method 方法名
  83 + * @param errorMessage 错误消息
  84 + */
  85 + protected void sendErrorResponse(MqttEndpoint endpoint, String productKey, String deviceName,
  86 + String requestId, String method, String errorMessage) {
  87 + try {
  88 + // 1. 构建响应消息
  89 + IotDeviceMessage responseMessage = IotDeviceMessage.replyOf(requestId, method, null, 400, errorMessage);
  90 +
  91 + // 2. 编码消息(使用默认编解码器)
  92 + byte[] encodedData = deviceMessageService.encodeDeviceMessage(responseMessage, DEFAULT_CODEC_TYPE);
  93 +
  94 + // 3. 构建响应主题并发送
  95 + String replyTopic = IotMqttTopicUtils.buildTopicByMethod(method, productKey, deviceName, true);
  96 + endpoint.publish(replyTopic, Buffer.buffer(encodedData), MqttQoS.AT_LEAST_ONCE, false, false);
  97 + log.debug("[sendErrorResponse][发送错误响应,主题: {}]", replyTopic);
  98 + } catch (Exception e) {
  99 + log.error("[sendErrorResponse][发送错误响应异常,客户端 ID: {}]", endpoint.clientIdentifier(), e);
  100 + }
  101 + }
  102 +
  103 + // TODO @AI:搞个基础的 writeResponse 会不会更好?参考 IotTcpUpstreamHandler 里;
  104 +
  105 +}
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/handler/upstream/IotMqttConnectionHandler.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.upstream;
  2 +
  3 +import cn.hutool.core.util.BooleanUtil;
  4 +import cn.hutool.core.util.StrUtil;
  5 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  6 +import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
  7 +import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
  8 +import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceGetReqDTO;
  9 +import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO;
  10 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  11 +import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity;
  12 +import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
  13 +import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager.IotMqttConnectionManager;
  14 +import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
  15 +import io.vertx.mqtt.MqttEndpoint;
  16 +import lombok.extern.slf4j.Slf4j;
  17 +
  18 +// TODO @AI:改成 auth 这样的命名,更好理解;
  19 +/**
  20 + * IoT 网关 MQTT 连接处理器
  21 + * <p>
  22 + * 处理 MQTT CONNECT 事件,完成设备认证、连接注册、上线通知
  23 + *
  24 + * @author 芋道源码
  25 + */
  26 +@Slf4j
  27 +public class IotMqttConnectionHandler extends IotMqttAbstractHandler {
  28 +
  29 + // TODO @AI:通过 springutil 去获取!
  30 + private final IotDeviceCommonApi deviceApi;
  31 + private final String serverId;
  32 +
  33 + public IotMqttConnectionHandler(IotMqttConnectionManager connectionManager,
  34 + IotDeviceMessageService deviceMessageService,
  35 + IotDeviceCommonApi deviceApi,
  36 + String serverId) {
  37 + super(connectionManager, deviceMessageService);
  38 + this.deviceApi = deviceApi;
  39 + this.serverId = serverId;
  40 + }
  41 +
  42 + @Override
  43 + protected boolean requiresAuthenticated() {
  44 + return false; // 连接阶段不需要已认证
  45 + }
  46 +
  47 + /**
  48 + * 处理 MQTT 连接请求
  49 + *
  50 + * @param endpoint MQTT 连接端点
  51 + * @return 认证是否成功
  52 + */
  53 + public boolean handleConnect(MqttEndpoint endpoint) {
  54 + // TODO @AI:整个 try catch 下;
  55 + // TODO @AI:是不是参考 IotTcpUpstreamHandler 的代码结构
  56 + String clientId = endpoint.clientIdentifier();
  57 + String username = endpoint.auth() != null ? endpoint.auth().getUsername() : null;
  58 + String password = endpoint.auth() != null ? endpoint.auth().getPassword() : null;
  59 + log.debug("[handleConnect][设备连接请求,客户端 ID: {},用户名: {},地址: {}]",
  60 + clientId, username, connectionManager.getEndpointAddress(endpoint));
  61 +
  62 + // 进行认证
  63 + if (!authenticateDevice(clientId, username, password, endpoint)) {
  64 + log.warn("[handleConnect][设备认证失败,拒绝连接,客户端 ID: {},用户名: {}]", clientId, username);
  65 + return false;
  66 + }
  67 + log.info("[handleConnect][设备认证成功,建立连接,客户端 ID: {},用户名: {}]", clientId, username);
  68 + return true;
  69 + }
  70 +
  71 + /**
  72 + * 在 MQTT 连接时进行设备认证
  73 + *
  74 + * @param clientId 客户端 ID
  75 + * @param username 用户名
  76 + * @param password 密码
  77 + * @param endpoint MQTT 连接端点
  78 + * @return 认证是否成功
  79 + */
  80 + private boolean authenticateDevice(String clientId, String username, String password, MqttEndpoint endpoint) {
  81 + try {
  82 + // 1.1 解析认证参数
  83 + // TODO @AI:断言,统一交给上层打印日志;
  84 + if (StrUtil.hasEmpty(clientId, username, password)) {
  85 + log.warn("[authenticateDevice][认证参数不完整,客户端 ID: {},用户名: {}]", clientId, username);
  86 + return false;
  87 + }
  88 + // 1.2 构建认证参数
  89 + IotDeviceAuthReqDTO authParams = new IotDeviceAuthReqDTO()
  90 + .setClientId(clientId)
  91 + .setUsername(username)
  92 + .setPassword(password);
  93 +
  94 + // 2.1 执行认证
  95 + CommonResult<Boolean> authResult = deviceApi.authDevice(authParams);
  96 + // TODO @AI:断言,统一交给上层打印日志;
  97 + if (!authResult.isSuccess() || !BooleanUtil.isTrue(authResult.getData())) {
  98 + log.warn("[authenticateDevice][设备认证失败,客户端 ID: {},用户名: {},错误: {}]",
  99 + clientId, username, authResult.getMsg());
  100 + return false;
  101 + }
  102 + // 2.2 获取设备信息
  103 + IotDeviceIdentity deviceInfo = IotDeviceAuthUtils.parseUsername(username);
  104 + if (deviceInfo == null) {
  105 + log.warn("[authenticateDevice][用户名格式不正确,客户端 ID: {},用户名: {}]", clientId, username);
  106 + return false;
  107 + }
  108 + // 2.3 获取设备信息
  109 + // TODO @AI:报错需要处理下;
  110 + IotDeviceRespDTO device = deviceService.getDeviceFromCache(deviceInfo.getProductKey(), deviceInfo.getDeviceName());
  111 + if (device == null) {
  112 + log.warn("[authenticateDevice][设备不存在,客户端 ID: {},用户名: {}]", clientId, username);
  113 + return false;
  114 + }
  115 +
  116 + // 3.1 注册连接
  117 + registerConnection(endpoint, device, clientId);
  118 + // 3.2 发送设备上线消息
  119 + sendOnlineMessage(device);
  120 + return true;
  121 + } catch (Exception e) {
  122 + log.error("[authenticateDevice][设备认证异常,客户端 ID: {},用户名: {}]", clientId, username, e);
  123 + return false;
  124 + }
  125 + }
  126 +
  127 + /**
  128 + * 注册连接
  129 + */
  130 + private void registerConnection(MqttEndpoint endpoint, IotDeviceRespDTO device, String clientId) {
  131 + IotMqttConnectionManager.ConnectionInfo connectionInfo = new IotMqttConnectionManager.ConnectionInfo()
  132 + .setDeviceId(device.getId())
  133 + .setProductKey(device.getProductKey())
  134 + .setDeviceName(device.getDeviceName())
  135 + .setClientId(clientId)
  136 + .setAuthenticated(true)
  137 + .setRemoteAddress(connectionManager.getEndpointAddress(endpoint));
  138 + connectionManager.registerConnection(endpoint, device.getId(), connectionInfo);
  139 + }
  140 +
  141 + /**
  142 + * 发送设备上线消息
  143 + */
  144 + private void sendOnlineMessage(IotDeviceRespDTO device) {
  145 + try {
  146 + IotDeviceMessage onlineMessage = IotDeviceMessage.buildStateUpdateOnline();
  147 + deviceMessageService.sendDeviceMessage(onlineMessage, device.getProductKey(),
  148 + device.getDeviceName(), serverId);
  149 + log.info("[sendOnlineMessage][设备上线,设备 ID: {},设备名称: {}]", device.getId(), device.getDeviceName());
  150 + } catch (Exception e) {
  151 + log.error("[sendOnlineMessage][发送设备上线消息失败,设备 ID: {},错误: {}]", device.getId(), e.getMessage());
  152 + }
  153 + }
  154 +
  155 + /**
  156 + * 清理连接
  157 + *
  158 + * @param endpoint MQTT 连接端点
  159 + */
  160 + public void cleanupConnection(MqttEndpoint endpoint) {
  161 + try {
  162 + // 1. 发送设备离线消息
  163 + IotMqttConnectionManager.ConnectionInfo connectionInfo = connectionManager.getConnectionInfo(endpoint);
  164 + if (connectionInfo != null) {
  165 + IotDeviceMessage offlineMessage = IotDeviceMessage.buildStateOffline();
  166 + deviceMessageService.sendDeviceMessage(offlineMessage, connectionInfo.getProductKey(),
  167 + connectionInfo.getDeviceName(), serverId);
  168 + }
  169 +
  170 + // 2. 注销连接
  171 + connectionManager.unregisterConnection(endpoint);
  172 + } catch (Exception e) {
  173 + log.error("[cleanupConnection][清理连接失败,客户端 ID: {},错误: {}]",
  174 + endpoint.clientIdentifier(), e.getMessage());
  175 + }
  176 + }
  177 +
  178 +}
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/handler/upstream/IotMqttRegisterHandler.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.upstream;
  2 +
  3 +import cn.hutool.core.map.MapUtil;
  4 +import cn.hutool.core.util.ArrayUtil;
  5 +import cn.hutool.core.util.StrUtil;
  6 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  7 +import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
  8 +import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
  9 +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
  10 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  11 +import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO;
  12 +import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterRespDTO;
  13 +import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager.IotMqttConnectionManager;
  14 +import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
  15 +import io.vertx.mqtt.MqttEndpoint;
  16 +import lombok.extern.slf4j.Slf4j;
  17 +
  18 +import java.util.Map;
  19 +
  20 +/**
  21 + * IoT 网关 MQTT 设备注册处理器
  22 + * <p>
  23 + * 处理设备动态注册消息(一型一密)
  24 + *
  25 + * @author 芋道源码
  26 + */
  27 +@Slf4j
  28 +public class IotMqttRegisterHandler extends IotMqttAbstractHandler {
  29 +
  30 + // TODO IotDeviceMessageMethodEnum.DEVICE_REGISTER 计算出来?IotMqttTopicUtils?
  31 + /**
  32 + * register 请求的 topic 后缀
  33 + */
  34 + public static final String REGISTER_TOPIC_SUFFIX = "/thing/auth/register";
  35 +
  36 + private final IotDeviceCommonApi deviceApi;
  37 +
  38 + public IotMqttRegisterHandler(IotMqttConnectionManager connectionManager,
  39 + IotDeviceMessageService deviceMessageService,
  40 + IotDeviceCommonApi deviceApi) {
  41 + super(connectionManager, deviceMessageService);
  42 + // TODO @AI:通过 springutil 处理;
  43 + this.deviceApi = deviceApi;
  44 + }
  45 +
  46 + /**
  47 + * 判断是否为注册消息
  48 + *
  49 + * @param topic 主题
  50 + * @return 是否为注册消息
  51 + */
  52 + public boolean isRegisterMessage(String topic) {
  53 + // TODO @AI:是不是搞到 IotMqttTopicUtils 里?
  54 + return topic != null && topic.endsWith(REGISTER_TOPIC_SUFFIX);
  55 + }
  56 +
  57 + /**
  58 + * 处理注册消息
  59 + *
  60 + * @param endpoint MQTT 连接端点
  61 + * @param topic 主题
  62 + * @param payload 消息内容
  63 + */
  64 + public void handleRegister(MqttEndpoint endpoint, String topic, byte[] payload) {
  65 + String clientId = endpoint.clientIdentifier();
  66 + // 1.1 基础检查
  67 + if (ArrayUtil.isEmpty(payload)) {
  68 + return;
  69 + }
  70 + // 1.2 解析主题,获取 productKey 和 deviceName
  71 + // TODO @AI:直接断言报错;
  72 + String[] topicParts = topic.split("/");
  73 + if (topicParts.length < 4 || StrUtil.hasBlank(topicParts[2], topicParts[3])) {
  74 + log.warn("[handleRegister][topic({}) 格式不正确]", topic);
  75 + return;
  76 + }
  77 + String productKey = topicParts[2];
  78 + String deviceName = topicParts[3];
  79 + // TODO @AI:直接断言报错;
  80 +
  81 + // 2. 使用默认编解码器解码消息(设备可能未注册,无法获取 codecType)
  82 + // TODO @AI:使用默认的 json;
  83 + IotDeviceMessage message;
  84 + try {
  85 + message = deviceMessageService.decodeDeviceMessage(payload, DEFAULT_CODEC_TYPE);
  86 + if (message == null) {
  87 + log.warn("[handleRegister][消息解码失败,客户端 ID: {},主题: {}]", clientId, topic);
  88 + return;
  89 + }
  90 + } catch (Exception e) {
  91 + log.error("[handleRegister][消息解码异常,客户端 ID: {},主题: {},错误: {}]",
  92 + clientId, topic, e.getMessage(), e);
  93 + return;
  94 + }
  95 +
  96 + // 3. 处理设备动态注册请求
  97 + log.info("[handleRegister][收到设备注册消息,设备: {}.{}, 方法: {}]",
  98 + productKey, deviceName, message.getMethod());
  99 + try {
  100 + processRegisterRequest(message, productKey, deviceName, endpoint);
  101 + } catch (Exception e) {
  102 + // TODO @AI:各种情况下的翻译;
  103 + log.error("[handleRegister][消息处理异常,客户端 ID: {},主题: {},错误: {}]",
  104 + clientId, topic, e.getMessage(), e);
  105 + }
  106 + }
  107 +
  108 + /**
  109 + * 处理设备动态注册请求(一型一密,不需要 deviceSecret)
  110 + *
  111 + * @param message 消息信息
  112 + * @param productKey 产品 Key
  113 + * @param deviceName 设备名称
  114 + * @param endpoint MQTT 连接端点
  115 + * @see <a href="https://help.aliyun.com/zh/iot/user-guide/unique-certificate-per-product-verification">阿里云 - 一型一密</a>
  116 + */
  117 + private void processRegisterRequest(IotDeviceMessage message, String productKey, String deviceName,
  118 + MqttEndpoint endpoint) {
  119 + String clientId = endpoint.clientIdentifier();
  120 + String method = IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod();
  121 + try {
  122 + // 1. 解析注册参数
  123 + IotDeviceRegisterReqDTO params = parseRegisterParams(message.getParams());
  124 + if (params == null) {
  125 + log.warn("[processRegisterRequest][注册参数解析失败,客户端 ID: {}]", clientId);
  126 + sendErrorResponse(endpoint, productKey, deviceName, message.getRequestId(), method, "注册参数不完整");
  127 + return;
  128 + }
  129 +
  130 + // 2. 调用动态注册 API
  131 + CommonResult<IotDeviceRegisterRespDTO> result = deviceApi.registerDevice(params);
  132 + if (result.isError()) {
  133 + log.warn("[processRegisterRequest][注册失败,客户端 ID: {},错误: {}]", clientId, result.getMsg());
  134 + sendErrorResponse(endpoint, productKey, deviceName, message.getRequestId(), method, result.getMsg());
  135 + return;
  136 + }
  137 +
  138 + // 3. 发送成功响应(包含 deviceSecret)
  139 + sendSuccessResponse(endpoint, productKey, deviceName, message.getRequestId(), method, result.getData());
  140 + log.info("[processRegisterRequest][注册成功,设备名: {},客户端 ID: {}]",
  141 + params.getDeviceName(), clientId);
  142 + } catch (Exception e) {
  143 + log.error("[processRegisterRequest][注册处理异常,客户端 ID: {}]", clientId, e);
  144 + sendErrorResponse(endpoint, productKey, deviceName, message.getRequestId(), method, "注册处理异常");
  145 + }
  146 + }
  147 +
  148 + // TODO @AI:解析可以简化,参考别的 tcp 对应的
  149 + /**
  150 + * 解析注册参数
  151 + *
  152 + * @param params 参数对象(通常为 Map 类型)
  153 + * @return 注册参数 DTO,解析失败时返回 null
  154 + */
  155 + @SuppressWarnings("unchecked")
  156 + private IotDeviceRegisterReqDTO parseRegisterParams(Object params) {
  157 + if (params == null) {
  158 + return null;
  159 + }
  160 + try {
  161 + // 参数默认为 Map 类型,直接转换
  162 + if (params instanceof Map) {
  163 + Map<String, Object> paramMap = (Map<String, Object>) params;
  164 + return new IotDeviceRegisterReqDTO()
  165 + .setProductKey(MapUtil.getStr(paramMap, "productKey"))
  166 + .setDeviceName(MapUtil.getStr(paramMap, "deviceName"))
  167 + .setProductSecret(MapUtil.getStr(paramMap, "productSecret"));
  168 + }
  169 + // 如果已经是目标类型,直接返回
  170 + if (params instanceof IotDeviceRegisterReqDTO) {
  171 + return (IotDeviceRegisterReqDTO) params;
  172 + }
  173 +
  174 + // 其他情况尝试 JSON 转换
  175 + return JsonUtils.convertObject(params, IotDeviceRegisterReqDTO.class);
  176 + } catch (Exception e) {
  177 + log.error("[parseRegisterParams][解析注册参数({})失败]", params, e);
  178 + return null;
  179 + }
  180 + }
  181 +
  182 +}
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/handler/upstream/IotMqttUpstreamHandler.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.upstream;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  5 +import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager.IotMqttConnectionManager;
  6 +import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
  7 +import io.vertx.mqtt.MqttEndpoint;
  8 +import lombok.extern.slf4j.Slf4j;
  9 +
  10 +/**
  11 + * IoT 网关 MQTT 上行消息处理器
  12 + * <p>
  13 + * 处理业务消息(属性上报、事件上报等)
  14 + *
  15 + * @author 芋道源码
  16 + */
  17 +@Slf4j
  18 +public class IotMqttUpstreamHandler extends IotMqttAbstractHandler {
  19 +
  20 + private final String serverId;
  21 +
  22 + public IotMqttUpstreamHandler(IotMqttConnectionManager connectionManager,
  23 + IotDeviceMessageService deviceMessageService,
  24 + String serverId) {
  25 + super(connectionManager, deviceMessageService);
  26 + this.serverId = serverId;
  27 + }
  28 +
  29 + /**
  30 + * 处理业务消息
  31 + *
  32 + * @param endpoint MQTT 连接端点
  33 + * @param topic 主题
  34 + * @param payload 消息内容
  35 + */
  36 + public void handleMessage(MqttEndpoint endpoint, String topic, byte[] payload) {
  37 + String clientId = endpoint.clientIdentifier();
  38 +
  39 + // 1. 基础检查
  40 + if (payload == null || payload.length == 0) {
  41 + return;
  42 + }
  43 +
  44 + // 2. 解析主题,获取 productKey 和 deviceName
  45 + String[] topicParts = topic.split("/");
  46 + if (topicParts.length < 4 || StrUtil.hasBlank(topicParts[2], topicParts[3])) {
  47 + log.warn("[handleMessage][topic({}) 格式不正确,无法解析有效的 productKey 和 deviceName]", topic);
  48 + return;
  49 + }
  50 +
  51 + // 3. 解码消息(使用从 topic 解析的 productKey 和 deviceName)
  52 + String productKey = topicParts[2];
  53 + String deviceName = topicParts[3];
  54 + try {
  55 + IotDeviceMessage message = deviceMessageService.decodeDeviceMessage(payload, productKey, deviceName);
  56 + if (message == null) {
  57 + log.warn("[handleMessage][消息解码失败,客户端 ID: {},主题: {}]", clientId, topic);
  58 + return;
  59 + }
  60 +
  61 + // 4. 处理业务消息(认证已在连接时完成)
  62 + log.info("[handleMessage][收到设备消息,设备: {}.{}, 方法: {}]",
  63 + productKey, deviceName, message.getMethod());
  64 + handleBusinessRequest(message, productKey, deviceName);
  65 + } catch (Exception e) {
  66 + // TODO @AI:各种情况下的翻译;
  67 + log.error("[handleMessage][消息处理异常,客户端 ID: {},主题: {},错误: {}]",
  68 + clientId, topic, e.getMessage(), e);
  69 + }
  70 + }
  71 +
  72 + /**
  73 + * 处理业务请求
  74 + */
  75 + private void handleBusinessRequest(IotDeviceMessage message, String productKey, String deviceName) {
  76 + // 发送消息到消息总线
  77 + message.setServerId(serverId);
  78 + deviceMessageService.sendDeviceMessage(message, productKey, deviceName, serverId);
  79 + }
  80 +
  81 +}
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/manager/IotMqttConnectionManager.java
@@ -40,6 +40,7 @@ public class IotMqttConnectionManager { @@ -40,6 +40,7 @@ public class IotMqttConnectionManager {
40 */ 40 */
41 private final Map<Long, MqttEndpoint> deviceEndpointMap = new ConcurrentHashMap<>(); 41 private final Map<Long, MqttEndpoint> deviceEndpointMap = new ConcurrentHashMap<>();
42 42
  43 + // TODO @AI:这里会存在返回 "unknown" 的情况么?是不是必须返回,否则还是异常更合理点?
43 /** 44 /**
44 * 安全获取 endpoint 地址 45 * 安全获取 endpoint 地址
45 * <p> 46 * <p>
@@ -66,7 +67,6 @@ public class IotMqttConnectionManager { @@ -66,7 +67,6 @@ public class IotMqttConnectionManager {
66 } catch (Exception ignored) { 67 } catch (Exception ignored) {
67 // 连接已关闭,忽略异常 68 // 连接已关闭,忽略异常
68 } 69 }
69 -  
70 return realTimeAddress; 70 return realTimeAddress;
71 } 71 }
72 72
@@ -129,7 +129,6 @@ public class IotMqttConnectionManager { @@ -129,7 +129,6 @@ public class IotMqttConnectionManager {
129 if (endpoint == null) { 129 if (endpoint == null) {
130 return null; 130 return null;
131 } 131 }
132 -  
133 // 获取连接信息 132 // 获取连接信息
134 return getConnectionInfo(endpoint); 133 return getConnectionInfo(endpoint);
135 } 134 }
@@ -208,6 +207,7 @@ public class IotMqttConnectionManager { @@ -208,6 +207,7 @@ public class IotMqttConnectionManager {
208 */ 207 */
209 private String clientId; 208 private String clientId;
210 209
  210 + // TODO @AI:是不是要去掉!感觉没用啊;
211 /** 211 /**
212 * 是否已认证 212 * 是否已认证
213 */ 213 */
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/router/IotMqttUpstreamHandler.java deleted
1 -package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router;  
2 -  
3 -import cn.hutool.core.map.MapUtil;  
4 -import cn.hutool.core.util.ArrayUtil;  
5 -import cn.hutool.core.util.BooleanUtil;  
6 -import cn.hutool.core.util.StrUtil;  
7 -import cn.hutool.extra.spring.SpringUtil;  
8 -import cn.iocoder.yudao.framework.common.pojo.CommonResult;  
9 -import cn.iocoder.yudao.framework.common.util.json.JsonUtils;  
10 -import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;  
11 -import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;  
12 -import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceGetReqDTO;  
13 -import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO;  
14 -import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;  
15 -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;  
16 -import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity;  
17 -import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO;  
18 -import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterRespDTO;  
19 -import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;  
20 -import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttUpstreamProtocol;  
21 -import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager.IotMqttConnectionManager;  
22 -import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;  
23 -import cn.iocoder.yudao.module.iot.gateway.util.IotMqttTopicUtils;  
24 -import io.netty.handler.codec.mqtt.MqttConnectReturnCode;  
25 -import io.netty.handler.codec.mqtt.MqttQoS;  
26 -import io.vertx.mqtt.MqttEndpoint;  
27 -import io.vertx.mqtt.MqttTopicSubscription;  
28 -import lombok.extern.slf4j.Slf4j;  
29 -  
30 -import java.util.List;  
31 -import java.util.Map;  
32 -  
33 -/**  
34 - * MQTT 上行消息处理器  
35 - *  
36 - * @author 芋道源码  
37 - */  
38 -@Slf4j  
39 -public class IotMqttUpstreamHandler {  
40 -  
41 - /**  
42 - * 默认编解码类型(MQTT 使用 Alink 协议)  
43 - */  
44 - private static final String DEFAULT_CODEC_TYPE = "Alink";  
45 -  
46 - /**  
47 - * register 请求的 topic 后缀  
48 - */  
49 - private static final String REGISTER_TOPIC_SUFFIX = "/thing/auth/register";  
50 -  
51 - private final IotDeviceMessageService deviceMessageService;  
52 -  
53 - private final IotMqttConnectionManager connectionManager;  
54 -  
55 - private final IotDeviceCommonApi deviceApi;  
56 -  
57 - private final String serverId;  
58 -  
59 - public IotMqttUpstreamHandler(IotMqttUpstreamProtocol protocol,  
60 - IotDeviceMessageService deviceMessageService,  
61 - IotMqttConnectionManager connectionManager) {  
62 - this.deviceMessageService = deviceMessageService;  
63 - this.deviceApi = SpringUtil.getBean(IotDeviceCommonApi.class);  
64 - this.connectionManager = connectionManager;  
65 - this.serverId = protocol.getServerId();  
66 - }  
67 -  
68 - /**  
69 - * 处理 MQTT 连接  
70 - *  
71 - * @param endpoint MQTT 连接端点  
72 - */  
73 - public void handle(MqttEndpoint endpoint) {  
74 - String clientId = endpoint.clientIdentifier();  
75 - String username = endpoint.auth() != null ? endpoint.auth().getUsername() : null;  
76 - String password = endpoint.auth() != null ? endpoint.auth().getPassword() : null;  
77 -  
78 - log.debug("[handle][设备连接请求,客户端 ID: {},用户名: {},地址: {}]",  
79 - clientId, username, connectionManager.getEndpointAddress(endpoint));  
80 -  
81 - // 1. 先进行认证  
82 - if (!authenticateDevice(clientId, username, password, endpoint)) {  
83 - log.warn("[handle][设备认证失败,拒绝连接,客户端 ID: {},用户名: {}]", clientId, username);  
84 - endpoint.reject(MqttConnectReturnCode.CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD);  
85 - return;  
86 - }  
87 -  
88 - log.info("[handle][设备认证成功,建立连接,客户端 ID: {},用户名: {}]", clientId, username);  
89 -  
90 - // 2. 设置心跳处理器(监听客户端的 PINGREQ 消息)  
91 - endpoint.pingHandler(v -> {  
92 - log.debug("[handle][收到客户端心跳,客户端 ID: {}]", clientId);  
93 - // Vert.x 会自动发送 PINGRESP 响应,无需手动处理  
94 - });  
95 -  
96 - // 3. 设置异常和关闭处理器  
97 - endpoint.exceptionHandler(ex -> {  
98 - log.warn("[handle][连接异常,客户端 ID: {},地址: {}]", clientId, connectionManager.getEndpointAddress(endpoint));  
99 - cleanupConnection(endpoint);  
100 - });  
101 - endpoint.closeHandler(v -> {  
102 - cleanupConnection(endpoint);  
103 - });  
104 -  
105 - // 4. 设置消息处理器  
106 - endpoint.publishHandler(mqttMessage -> {  
107 - try {  
108 - // 4.1 根据 topic 判断是否为 register 请求  
109 - String topic = mqttMessage.topicName();  
110 - byte[] payload = mqttMessage.payload().getBytes();  
111 - if (topic.endsWith(REGISTER_TOPIC_SUFFIX)) {  
112 - // register 请求:使用默认编解码器处理(设备可能未注册)  
113 - processRegisterMessage(clientId, topic, payload, endpoint);  
114 - } else {  
115 - // 业务请求:正常处理  
116 - processMessage(clientId, topic, payload);  
117 - }  
118 -  
119 - // 4.2 根据 QoS 级别发送相应的确认消息  
120 - if (mqttMessage.qosLevel() == MqttQoS.AT_LEAST_ONCE) {  
121 - // QoS 1: 发送 PUBACK 确认  
122 - endpoint.publishAcknowledge(mqttMessage.messageId());  
123 - } else if (mqttMessage.qosLevel() == MqttQoS.EXACTLY_ONCE) {  
124 - // QoS 2: 发送 PUBREC 确认  
125 - endpoint.publishReceived(mqttMessage.messageId());  
126 - }  
127 - // QoS 0 无需确认  
128 - } catch (Exception e) {  
129 - log.error("[handle][消息解码失败,断开连接,客户端 ID: {},地址: {},错误: {}]",  
130 - clientId, connectionManager.getEndpointAddress(endpoint), e.getMessage());  
131 - cleanupConnection(endpoint);  
132 - endpoint.close();  
133 - }  
134 - });  
135 -  
136 - // 5. 设置订阅处理器  
137 - endpoint.subscribeHandler(subscribe -> {  
138 - // 提取主题名称列表用于日志显示  
139 - List<String> topicNames = subscribe.topicSubscriptions().stream()  
140 - .map(MqttTopicSubscription::topicName)  
141 - .collect(java.util.stream.Collectors.toList());  
142 - log.debug("[handle][设备订阅,客户端 ID: {},主题: {}]", clientId, topicNames);  
143 -  
144 - // 提取 QoS 列表  
145 - List<MqttQoS> grantedQoSLevels = subscribe.topicSubscriptions().stream()  
146 - .map(MqttTopicSubscription::qualityOfService)  
147 - .collect(java.util.stream.Collectors.toList());  
148 - endpoint.subscribeAcknowledge(subscribe.messageId(), grantedQoSLevels);  
149 - });  
150 -  
151 - // 6. 设置取消订阅处理器  
152 - endpoint.unsubscribeHandler(unsubscribe -> {  
153 - log.debug("[handle][设备取消订阅,客户端 ID: {},主题: {}]", clientId, unsubscribe.topics());  
154 - endpoint.unsubscribeAcknowledge(unsubscribe.messageId());  
155 - });  
156 -  
157 - // 7. 设置 QoS 2消息的 PUBREL 处理器  
158 - endpoint.publishReleaseHandler(endpoint::publishComplete);  
159 -  
160 - // 8. 设置断开连接处理器  
161 - endpoint.disconnectHandler(v -> {  
162 - log.debug("[handle][设备断开连接,客户端 ID: {}]", clientId);  
163 - cleanupConnection(endpoint);  
164 - });  
165 -  
166 - // 9. 接受连接  
167 - endpoint.accept(false);  
168 - }  
169 -  
170 - /**  
171 - * 处理消息  
172 - *  
173 - * @param clientId 客户端 ID  
174 - * @param topic 主题  
175 - * @param payload 消息内容  
176 - */  
177 - private void processMessage(String clientId, String topic, byte[] payload) {  
178 - // 1. 基础检查  
179 - if (payload == null || payload.length == 0) {  
180 - return;  
181 - }  
182 -  
183 - // 2. 解析主题,获取 productKey 和 deviceName  
184 - String[] topicParts = topic.split("/");  
185 - if (topicParts.length < 4 || StrUtil.hasBlank(topicParts[2], topicParts[3])) {  
186 - log.warn("[processMessage][topic({}) 格式不正确,无法解析有效的 productKey 和 deviceName]", topic);  
187 - return;  
188 - }  
189 -  
190 - // 3. 解码消息(使用从 topic 解析的 productKey 和 deviceName)  
191 - String productKey = topicParts[2];  
192 - String deviceName = topicParts[3];  
193 - try {  
194 - IotDeviceMessage message = deviceMessageService.decodeDeviceMessage(payload, productKey, deviceName);  
195 - if (message == null) {  
196 - log.warn("[processMessage][消息解码失败,客户端 ID: {},主题: {}]", clientId, topic);  
197 - return;  
198 - }  
199 -  
200 - // 4. 处理业务消息(认证已在连接时完成)  
201 - log.info("[processMessage][收到设备消息,设备: {}.{}, 方法: {}]",  
202 - productKey, deviceName, message.getMethod());  
203 - handleBusinessRequest(message, productKey, deviceName);  
204 - } catch (Exception e) {  
205 - log.error("[processMessage][消息处理异常,客户端 ID: {},主题: {},错误: {}]",  
206 - clientId, topic, e.getMessage(), e);  
207 - }  
208 - }  
209 -  
210 - /**  
211 - * 在 MQTT 连接时进行设备认证  
212 - *  
213 - * @param clientId 客户端 ID  
214 - * @param username 用户名  
215 - * @param password 密码  
216 - * @param endpoint MQTT 连接端点  
217 - * @return 认证是否成功  
218 - */  
219 - private boolean authenticateDevice(String clientId, String username, String password, MqttEndpoint endpoint) {  
220 - try {  
221 - // 1. 参数校验  
222 - if (StrUtil.hasEmpty(clientId, username, password)) {  
223 - log.warn("[authenticateDevice][认证参数不完整,客户端 ID: {},用户名: {}]", clientId, username);  
224 - return false;  
225 - }  
226 -  
227 - // 2. 构建认证参数  
228 - IotDeviceAuthReqDTO authParams = new IotDeviceAuthReqDTO()  
229 - .setClientId(clientId)  
230 - .setUsername(username)  
231 - .setPassword(password);  
232 -  
233 - // 3. 调用设备认证 API  
234 - CommonResult<Boolean> authResult = deviceApi.authDevice(authParams);  
235 - if (!authResult.isSuccess() || !BooleanUtil.isTrue(authResult.getData())) {  
236 - log.warn("[authenticateDevice][设备认证失败,客户端 ID: {},用户名: {},错误: {}]",  
237 - clientId, username, authResult.getMsg());  
238 - return false;  
239 - }  
240 -  
241 - // 4. 获取设备信息  
242 - IotDeviceIdentity deviceInfo = IotDeviceAuthUtils.parseUsername(username);  
243 - if (deviceInfo == null) {  
244 - log.warn("[authenticateDevice][用户名格式不正确,客户端 ID: {},用户名: {}]", clientId, username);  
245 - return false;  
246 - }  
247 -  
248 - IotDeviceGetReqDTO getReqDTO = new IotDeviceGetReqDTO()  
249 - .setProductKey(deviceInfo.getProductKey())  
250 - .setDeviceName(deviceInfo.getDeviceName());  
251 -  
252 - CommonResult<IotDeviceRespDTO> deviceResult = deviceApi.getDevice(getReqDTO);  
253 - if (!deviceResult.isSuccess() || deviceResult.getData() == null) {  
254 - log.warn("[authenticateDevice][获取设备信息失败,客户端 ID: {},用户名: {},错误: {}]",  
255 - clientId, username, deviceResult.getMsg());  
256 - return false;  
257 - }  
258 -  
259 - // 5. 注册连接  
260 - IotDeviceRespDTO device = deviceResult.getData();  
261 - registerConnection(endpoint, device, clientId);  
262 -  
263 - // 6. 发送设备上线消息  
264 - sendOnlineMessage(device);  
265 -  
266 - return true;  
267 - } catch (Exception e) {  
268 - log.error("[authenticateDevice][设备认证异常,客户端 ID: {},用户名: {}]", clientId, username, e);  
269 - return false;  
270 - }  
271 - }  
272 -  
273 - /**  
274 - * 处理 register 消息(设备动态注册,使用默认编解码器)  
275 - *  
276 - * @param clientId 客户端 ID  
277 - * @param topic 主题  
278 - * @param payload 消息内容  
279 - * @param endpoint MQTT 连接端点  
280 - */  
281 - private void processRegisterMessage(String clientId, String topic, byte[] payload, MqttEndpoint endpoint) {  
282 - // 1.1 基础检查  
283 - if (ArrayUtil.isEmpty(payload)) {  
284 - return;  
285 - }  
286 - // 1.2 解析主题,获取 productKey 和 deviceName  
287 - String[] topicParts = topic.split("/");  
288 - if (topicParts.length < 4 || StrUtil.hasBlank(topicParts[2], topicParts[3])) {  
289 - log.warn("[processRegisterMessage][topic({}) 格式不正确]", topic);  
290 - return;  
291 - }  
292 - String productKey = topicParts[2];  
293 - String deviceName = topicParts[3];  
294 -  
295 - // 2. 使用默认编解码器解码消息(设备可能未注册,无法获取 codecType)  
296 - IotDeviceMessage message;  
297 - try {  
298 - message = deviceMessageService.decodeDeviceMessage(payload, DEFAULT_CODEC_TYPE);  
299 - if (message == null) {  
300 - log.warn("[processRegisterMessage][消息解码失败,客户端 ID: {},主题: {}]", clientId, topic);  
301 - return;  
302 - }  
303 - } catch (Exception e) {  
304 - log.error("[processRegisterMessage][消息解码异常,客户端 ID: {},主题: {},错误: {}]",  
305 - clientId, topic, e.getMessage(), e);  
306 - return;  
307 - }  
308 -  
309 - // 3. 处理设备动态注册请求  
310 - log.info("[processRegisterMessage][收到设备注册消息,设备: {}.{}, 方法: {}]",  
311 - productKey, deviceName, message.getMethod());  
312 - try {  
313 - handleRegisterRequest(message, productKey, deviceName, endpoint);  
314 - } catch (Exception e) {  
315 - log.error("[processRegisterMessage][消息处理异常,客户端 ID: {},主题: {},错误: {}]",  
316 - clientId, topic, e.getMessage(), e);  
317 - }  
318 - }  
319 -  
320 - /**  
321 - * 处理设备动态注册请求(一型一密,不需要 deviceSecret)  
322 - *  
323 - * @param message 消息信息  
324 - * @param productKey 产品 Key  
325 - * @param deviceName 设备名称  
326 - * @param endpoint MQTT 连接端点  
327 - * @see <a href="https://help.aliyun.com/zh/iot/user-guide/unique-certificate-per-product-verification">阿里云 - 一型一密</a>  
328 - */  
329 - private void handleRegisterRequest(IotDeviceMessage message, String productKey, String deviceName, MqttEndpoint endpoint) {  
330 - String clientId = endpoint.clientIdentifier();  
331 - try {  
332 - // 1. 解析注册参数  
333 - IotDeviceRegisterReqDTO params = parseRegisterParams(message.getParams());  
334 - if (params == null) {  
335 - log.warn("[handleRegisterRequest][注册参数解析失败,客户端 ID: {}]", clientId);  
336 - sendRegisterErrorResponse(endpoint, productKey, deviceName, message.getRequestId(), "注册参数不完整");  
337 - return;  
338 - }  
339 -  
340 - // 2. 调用动态注册 API  
341 - CommonResult<IotDeviceRegisterRespDTO> result = deviceApi.registerDevice(params);  
342 - if (result.isError()) {  
343 - log.warn("[handleRegisterRequest][注册失败,客户端 ID: {},错误: {}]", clientId, result.getMsg());  
344 - sendRegisterErrorResponse(endpoint, productKey, deviceName, message.getRequestId(), result.getMsg());  
345 - return;  
346 - }  
347 -  
348 - // 3. 发送成功响应(包含 deviceSecret)  
349 - sendRegisterSuccessResponse(endpoint, productKey, deviceName, message.getRequestId(), result.getData());  
350 - log.info("[handleRegisterRequest][注册成功,设备名: {},客户端 ID: {}]",  
351 - params.getDeviceName(), clientId);  
352 - } catch (Exception e) {  
353 - log.error("[handleRegisterRequest][注册处理异常,客户端 ID: {}]", clientId, e);  
354 - sendRegisterErrorResponse(endpoint, productKey, deviceName, message.getRequestId(), "注册处理异常");  
355 - }  
356 - }  
357 -  
358 - /**  
359 - * 解析注册参数  
360 - *  
361 - * @param params 参数对象(通常为 Map 类型)  
362 - * @return 注册参数 DTO,解析失败时返回 null  
363 - */  
364 - @SuppressWarnings({"unchecked", "DuplicatedCode"})  
365 - private IotDeviceRegisterReqDTO parseRegisterParams(Object params) {  
366 - if (params == null) {  
367 - return null;  
368 - }  
369 - try {  
370 - // 参数默认为 Map 类型,直接转换  
371 - if (params instanceof Map) {  
372 - Map<String, Object> paramMap = (Map<String, Object>) params;  
373 - return new IotDeviceRegisterReqDTO()  
374 - .setProductKey(MapUtil.getStr(paramMap, "productKey"))  
375 - .setDeviceName(MapUtil.getStr(paramMap, "deviceName"))  
376 - .setProductSecret(MapUtil.getStr(paramMap, "productSecret"));  
377 - }  
378 - // 如果已经是目标类型,直接返回  
379 - if (params instanceof IotDeviceRegisterReqDTO) {  
380 - return (IotDeviceRegisterReqDTO) params;  
381 - }  
382 -  
383 - // 其他情况尝试 JSON 转换  
384 - return JsonUtils.convertObject(params, IotDeviceRegisterReqDTO.class);  
385 - } catch (Exception e) {  
386 - log.error("[parseRegisterParams][解析注册参数({})失败]", params, e);  
387 - return null;  
388 - }  
389 - }  
390 -  
391 - /**  
392 - * 发送注册成功响应(包含 deviceSecret)  
393 - *  
394 - * @param endpoint MQTT 连接端点  
395 - * @param productKey 产品 Key  
396 - * @param deviceName 设备名称  
397 - * @param requestId 请求 ID  
398 - * @param registerResp 注册响应  
399 - */  
400 - private void sendRegisterSuccessResponse(MqttEndpoint endpoint, String productKey, String deviceName,  
401 - String requestId, IotDeviceRegisterRespDTO registerResp) {  
402 - try {  
403 - // 1. 构建响应消息(参考 HTTP 返回格式,直接返回 IotDeviceRegisterRespDTO)  
404 - IotDeviceMessage responseMessage = IotDeviceMessage.replyOf(requestId,  
405 - IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod(), registerResp, 0, null);  
406 -  
407 - // 2. 编码消息(使用默认编解码器,因为设备可能还未注册)  
408 - byte[] encodedData = deviceMessageService.encodeDeviceMessage(responseMessage, DEFAULT_CODEC_TYPE);  
409 -  
410 - // 3. 构建响应主题并发送(格式:/sys/{productKey}/{deviceName}/thing/auth/register_reply)  
411 - String replyTopic = IotMqttTopicUtils.buildTopicByMethod(  
412 - IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod(), productKey, deviceName, true);  
413 - endpoint.publish(replyTopic, io.vertx.core.buffer.Buffer.buffer(encodedData),  
414 - MqttQoS.AT_LEAST_ONCE, false, false);  
415 - log.debug("[sendRegisterSuccessResponse][发送注册成功响应,主题: {}]", replyTopic);  
416 - } catch (Exception e) {  
417 - log.error("[sendRegisterSuccessResponse][发送注册成功响应异常,客户端 ID: {}]",  
418 - endpoint.clientIdentifier(), e);  
419 - }  
420 - }  
421 -  
422 - /**  
423 - * 发送注册错误响应  
424 - *  
425 - * @param endpoint MQTT 连接端点  
426 - * @param productKey 产品 Key  
427 - * @param deviceName 设备名称  
428 - * @param requestId 请求 ID  
429 - * @param errorMessage 错误消息  
430 - */  
431 - private void sendRegisterErrorResponse(MqttEndpoint endpoint, String productKey, String deviceName,  
432 - String requestId, String errorMessage) {  
433 - try {  
434 - // 1. 构建响应消息  
435 - IotDeviceMessage responseMessage = IotDeviceMessage.replyOf(requestId,  
436 - IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod(), null, 400, errorMessage);  
437 -  
438 - // 2. 编码消息(使用默认编解码器,因为设备可能还未注册)  
439 - byte[] encodedData = deviceMessageService.encodeDeviceMessage(responseMessage, DEFAULT_CODEC_TYPE);  
440 -  
441 - // 3. 构建响应主题并发送(格式:/sys/{productKey}/{deviceName}/thing/auth/register_reply)  
442 - String replyTopic = IotMqttTopicUtils.buildTopicByMethod(  
443 - IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod(), productKey, deviceName, true);  
444 - endpoint.publish(replyTopic, io.vertx.core.buffer.Buffer.buffer(encodedData),  
445 - MqttQoS.AT_LEAST_ONCE, false, false);  
446 - log.debug("[sendRegisterErrorResponse][发送注册错误响应,主题: {}]", replyTopic);  
447 - } catch (Exception e) {  
448 - log.error("[sendRegisterErrorResponse][发送注册错误响应异常,客户端 ID: {}]",  
449 - endpoint.clientIdentifier(), e);  
450 - }  
451 - }  
452 -  
453 - /**  
454 - * 处理业务请求  
455 - */  
456 - private void handleBusinessRequest(IotDeviceMessage message, String productKey, String deviceName) {  
457 - // 发送消息到消息总线  
458 - message.setServerId(serverId);  
459 - deviceMessageService.sendDeviceMessage(message, productKey, deviceName, serverId);  
460 - }  
461 -  
462 - /**  
463 - * 注册连接  
464 - */  
465 - private void registerConnection(MqttEndpoint endpoint, IotDeviceRespDTO device, String clientId) {  
466 - IotMqttConnectionManager.ConnectionInfo connectionInfo = new IotMqttConnectionManager.ConnectionInfo()  
467 - .setDeviceId(device.getId())  
468 - .setProductKey(device.getProductKey())  
469 - .setDeviceName(device.getDeviceName())  
470 - .setClientId(clientId)  
471 - .setAuthenticated(true)  
472 - .setRemoteAddress(connectionManager.getEndpointAddress(endpoint));  
473 - connectionManager.registerConnection(endpoint, device.getId(), connectionInfo);  
474 - }  
475 -  
476 - /**  
477 - * 发送设备上线消息  
478 - */  
479 - private void sendOnlineMessage(IotDeviceRespDTO device) {  
480 - try {  
481 - IotDeviceMessage onlineMessage = IotDeviceMessage.buildStateUpdateOnline();  
482 - deviceMessageService.sendDeviceMessage(onlineMessage, device.getProductKey(),  
483 - device.getDeviceName(), serverId);  
484 - log.info("[sendOnlineMessage][设备上线,设备 ID: {},设备名称: {}]", device.getId(), device.getDeviceName());  
485 - } catch (Exception e) {  
486 - log.error("[sendOnlineMessage][发送设备上线消息失败,设备 ID: {},错误: {}]", device.getId(), e.getMessage());  
487 - }  
488 - }  
489 -  
490 - /**  
491 - * 清理连接  
492 - */  
493 - private void cleanupConnection(MqttEndpoint endpoint) {  
494 - try {  
495 - IotMqttConnectionManager.ConnectionInfo connectionInfo = connectionManager.getConnectionInfo(endpoint);  
496 - if (connectionInfo != null) {  
497 - // 发送设备离线消息  
498 - IotDeviceMessage offlineMessage = IotDeviceMessage.buildStateOffline();  
499 - deviceMessageService.sendDeviceMessage(offlineMessage, connectionInfo.getProductKey(),  
500 - connectionInfo.getDeviceName(), serverId);  
501 - log.info("[cleanupConnection][设备离线,设备 ID: {},设备名称: {}]", connectionInfo.getDeviceId(), connectionInfo.getDeviceName());  
502 - }  
503 -  
504 - // 注销连接  
505 - connectionManager.unregisterConnection(endpoint);  
506 - } catch (Exception e) {  
507 - log.error("[cleanupConnection][清理连接失败,客户端 ID: {},错误: {}]", endpoint.clientIdentifier(), e.getMessage());  
508 - }  
509 - }  
510 -  
511 -}  
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/handler/downstream/IotTcpDownstreamHandler.java
@@ -45,8 +45,8 @@ public class IotTcpDownstreamHandler { @@ -45,8 +45,8 @@ public class IotTcpDownstreamHandler {
45 } 45 }
46 46
47 // 2. 序列化 + 帧编码 47 // 2. 序列化 + 帧编码
48 - byte[] serializedData = serializer.serialize(message);  
49 - Buffer frameData = codec.encode(serializedData); 48 + byte[] payload = serializer.serialize(message);
  49 + Buffer frameData = codec.encode(payload);
50 50
51 // 3. 发送到设备 51 // 3. 发送到设备
52 boolean success = connectionManager.sendToDevice(message.getDeviceId(), frameData.getBytes()); 52 boolean success = connectionManager.sendToDevice(message.getDeviceId(), frameData.getBytes());
yudao-module-iot/yudao-module-iot-gateway/src/main/resources/application.yaml
@@ -57,9 +57,9 @@ yudao: @@ -57,9 +57,9 @@ yudao:
57 # 针对引入的 TCP 组件的配置 57 # 针对引入的 TCP 组件的配置
58 # ==================================== 58 # ====================================
59 - id: tcp-json 59 - id: tcp-json
  60 + enabled: false
60 type: tcp 61 type: tcp
61 port: 8091 62 port: 8091
62 - enabled: false  
63 serialize: json 63 serialize: json
64 tcp: 64 tcp:
65 max-connections: 1000 65 max-connections: 1000
@@ -79,9 +79,9 @@ yudao: @@ -79,9 +79,9 @@ yudao:
79 # 针对引入的 UDP 组件的配置 79 # 针对引入的 UDP 组件的配置
80 # ==================================== 80 # ====================================
81 - id: udp-json 81 - id: udp-json
  82 + enabled: false
82 type: udp 83 type: udp
83 port: 8093 84 port: 8093
84 - enabled: false  
85 serialize: json 85 serialize: json
86 udp: 86 udp:
87 max-sessions: 1000 # 最大会话数 87 max-sessions: 1000 # 最大会话数
@@ -92,9 +92,9 @@ yudao: @@ -92,9 +92,9 @@ yudao:
92 # 针对引入的 WebSocket 组件的配置 92 # 针对引入的 WebSocket 组件的配置
93 # ==================================== 93 # ====================================
94 - id: websocket-json 94 - id: websocket-json
  95 + enabled: true
95 type: websocket 96 type: websocket
96 port: 8094 97 port: 8094
97 - enabled: true  
98 serialize: json 98 serialize: json
99 websocket: 99 websocket:
100 path: /ws 100 path: /ws
@@ -106,13 +106,26 @@ yudao: @@ -106,13 +106,26 @@ yudao:
106 # 针对引入的 CoAP 组件的配置 106 # 针对引入的 CoAP 组件的配置
107 # ==================================== 107 # ====================================
108 - id: coap-json 108 - id: coap-json
  109 + enabled: false
109 type: coap 110 type: coap
110 port: 5683 111 port: 5683
111 - enabled: true  
112 coap: 112 coap:
113 max-message-size: 1024 # 最大消息大小(字节) 113 max-message-size: 1024 # 最大消息大小(字节)
114 ack-timeout-ms: 2000 # ACK 超时时间(毫秒) 114 ack-timeout-ms: 2000 # ACK 超时时间(毫秒)
115 max-retransmit: 4 # 最大重传次数 115 max-retransmit: 4 # 最大重传次数
  116 + # ====================================
  117 + # 针对引入的 MQTT 组件的配置
  118 + # ====================================
  119 + - id: mqtt-json
  120 + enabled: true
  121 + type: mqtt
  122 + port: 1883
  123 + serialize: json
  124 + mqtt:
  125 + max-message-size: 8192 # 最大消息大小(字节)
  126 + connect-timeout-seconds: 60 # 连接超时时间(秒)
  127 + keep-alive-timeout-seconds: 300 # 保持连接超时时间(秒)
  128 + ssl-enabled: false # 是否启用 SSL
116 129
117 # 协议配置(旧版,保持兼容) 130 # 协议配置(旧版,保持兼容)
118 protocol: 131 protocol:
@@ -150,15 +163,6 @@ yudao: @@ -150,15 +163,6 @@ yudao:
150 key-store-password: "your-keystore-password" # 客户端证书库密码 163 key-store-password: "your-keystore-password" # 客户端证书库密码
151 trust-store-path: "classpath:certs/trust.jks" # 信任的 CA 证书库路径 164 trust-store-path: "classpath:certs/trust.jks" # 信任的 CA 证书库路径
152 trust-store-password: "your-truststore-password" # 信任的 CA 证书库密码 165 trust-store-password: "your-truststore-password" # 信任的 CA 证书库密码
153 - # ====================================  
154 - # 针对引入的 MQTT 组件的配置  
155 - # ====================================  
156 - mqtt:  
157 - enabled: false  
158 - port: 1883  
159 - max-message-size: 8192  
160 - connect-timeout-seconds: 60  
161 - ssl-enabled: false  
162 166
163 --- #################### 日志相关配置 #################### 167 --- #################### 日志相关配置 ####################
164 168