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 5 import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxAuthEventProtocol;
6 6 import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxDownstreamSubscriber;
7 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 8 import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializerManager;
13   -import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
14 9 import io.vertx.core.Vertx;
15 10 import lombok.extern.slf4j.Slf4j;
16 11 import org.springframework.beans.factory.annotation.Qualifier;
... ... @@ -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 1 package cn.iocoder.yudao.module.iot.gateway.config;
2 2  
3   -import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum;
4 3 import cn.iocoder.yudao.module.iot.gateway.protocol.coap.IotCoapConfig;
5 4 import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpConfig;
  5 +import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttConfig;
6 6 import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.IotTcpConfig;
7 7 import cn.iocoder.yudao.module.iot.gateway.protocol.udp.IotUdpConfig;
8 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 9 import jakarta.validation.Valid;
12 10 import jakarta.validation.constraints.NotEmpty;
13 11 import jakarta.validation.constraints.NotNull;
... ... @@ -87,11 +85,6 @@ public class IotGatewayProperties {
87 85 */
88 86 private EmqxProperties emqx;
89 87  
90   - /**
91   - * MQTT 组件配置
92   - */
93   - private MqttProperties mqtt;
94   -
95 88 }
96 89  
97 90 @Data
... ... @@ -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 259 // NOTE:暂未统一为 ProtocolProperties,待协议改造完成再调整
340 260 /**
341 261 * 协议实例配置
... ... @@ -376,6 +296,8 @@ public class IotGatewayProperties {
376 296 */
377 297 private String serialize;
378 298  
  299 + // ========== 各协议配置 ==========
  300 +
379 301 /**
380 302 * HTTP 协议配置
381 303 */
... ... @@ -406,6 +328,12 @@ public class IotGatewayProperties {
406 328 @Valid
407 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 import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties;
7 7 import cn.iocoder.yudao.module.iot.gateway.protocol.coap.IotCoapProtocol;
8 8 import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpProtocol;
  9 +import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttProtocol;
9 10 import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.IotTcpProtocol;
10 11 import cn.iocoder.yudao.module.iot.gateway.protocol.udp.IotUdpProtocol;
11 12 import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.IotWebSocketProtocol;
... ... @@ -106,6 +107,8 @@ public class IotProtocolManager implements SmartLifecycle {
106 107 return createCoapProtocol(config);
107 108 case WEBSOCKET:
108 109 return createWebSocketProtocol(config);
  110 + case MQTT:
  111 + return createMqttProtocol(config);
109 112 default:
110 113 throw new IllegalArgumentException(String.format(
111 114 "[createProtocol][协议实例 %s 的协议类型 %s 暂不支持]", config.getId(), protocolType));
... ... @@ -162,4 +165,14 @@ public class IotProtocolManager implements SmartLifecycle {
162 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 3 import cn.hutool.core.util.StrUtil;
4 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 7 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
8 8 import cn.iocoder.yudao.module.iot.gateway.util.IotMqttTopicUtils;
9 9 import io.netty.handler.codec.mqtt.MqttQoS;
  10 +import lombok.RequiredArgsConstructor;
10 11 import lombok.extern.slf4j.Slf4j;
11 12  
12 13 /**
13 14 * IoT 网关 MQTT 协议:下行消息处理器
14   - * <p>
15   - * 专门处理下行消息的业务逻辑,包括:
16   - * 1. 消息编码
17   - * 2. 主题构建
18   - * 3. 消息发送
19 15 *
20 16 * @author 芋道源码
21 17 */
22 18 @Slf4j
  19 +@RequiredArgsConstructor
23 20 public class IotMqttDownstreamHandler {
24 21  
25 22 private final IotDeviceMessageService deviceMessageService;
26 23  
27 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 31 */
41 32 public boolean handleDownstreamMessage(IotDeviceMessage message) {
42 33 try {
  34 + // TODO @AI:参考 IotTcpDownstreamHandler 逻辑;
43 35 // 1. 基础校验
44 36 if (message == null || message.getDeviceId() == null) {
45 37 log.warn("[handleDownstreamMessage][消息或设备 ID 为空,忽略处理]");
... ... @@ -47,6 +39,7 @@ public class IotMqttDownstreamHandler {
47 39 }
48 40  
49 41 // 2. 检查设备是否在线
  42 + // TODO @AI:这块逻辑,是不是冗余?直接使用 3. 获取连接信息判断不就行了?
50 43 if (connectionManager.isDeviceOffline(message.getDeviceId())) {
51 44 log.warn("[handleDownstreamMessage][设备离线,无法发送消息,设备 ID:{}]", message.getDeviceId());
52 45 return false;
... ... @@ -59,7 +52,7 @@ public class IotMqttDownstreamHandler {
59 52 return false;
60 53 }
61 54  
62   - // 4. 编码消息
  55 + // 4. 序列化
63 56 byte[] payload = deviceMessageService.encodeDeviceMessage(message, connectionInfo.getProductKey(),
64 57 connectionInfo.getDeviceName());
65 58 if (payload == null || payload.length == 0) {
... ... @@ -68,6 +61,7 @@ public class IotMqttDownstreamHandler {
68 61 }
69 62  
70 63 // 5. 发送消息到设备
  64 + // TODO @AI:参考 IotTcpDownstreamHandler 的逻辑;
71 65 return sendMessageToDevice(message, connectionInfo, payload);
72 66 } catch (Exception e) {
73 67 if (message != null) {
... ... @@ -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 86 byte[] payload) {
92 87 // 1. 构建主题
93 88 String topic = buildDownstreamTopic(message, connectionInfo);
  89 + // TODO @AI:直接断言,非空!
94 90 if (StrUtil.isBlank(topic)) {
95 91 log.warn("[sendMessageToDevice][主题构建失败,设备 ID:{},方法:{}]",
96 92 message.getDeviceId(), message.getMethod());
... ... @@ -118,6 +114,7 @@ public class IotMqttDownstreamHandler {
118 114 */
119 115 private String buildDownstreamTopic(IotDeviceMessage message,
120 116 IotMqttConnectionManager.ConnectionInfo connectionInfo) {
  117 + // TODO @AI:直接断言,非空!
121 118 String method = message.getMethod();
122 119 if (StrUtil.isBlank(method)) {
123 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 3 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
4 4 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
5 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 7 import lombok.extern.slf4j.Slf4j;
8 8  
9 9 /**
... ... @@ -18,7 +18,7 @@ public class IotMqttDownstreamSubscriber extends IotProtocolDownstreamSubscriber
18 18  
19 19 private final IotMqttDownstreamHandler downstreamHandler;
20 20  
21   - public IotMqttDownstreamSubscriber(IotMqttUpstreamProtocol protocol,
  21 + public IotMqttDownstreamSubscriber(IotMqttProtocol protocol,
22 22 IotMqttDownstreamHandler downstreamHandler,
23 23 IotMessageBus messageBus) {
24 24 super(protocol, messageBus);
... ... @@ -27,6 +27,7 @@ public class IotMqttDownstreamSubscriber extends IotProtocolDownstreamSubscriber
27 27  
28 28 @Override
29 29 protected void handleMessage(IotDeviceMessage message) {
  30 + // TODO @AI:参考 IotTcpDownstreamHandler 不处理返回值,甚至不用返回值;
30 31 boolean success = downstreamHandler.handleDownstreamMessage(message);
31 32 if (success) {
32 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 40 */
41 41 private final Map<Long, MqttEndpoint> deviceEndpointMap = new ConcurrentHashMap<>();
42 42  
  43 + // TODO @AI:这里会存在返回 "unknown" 的情况么?是不是必须返回,否则还是异常更合理点?
43 44 /**
44 45 * 安全获取 endpoint 地址
45 46 * <p>
... ... @@ -66,7 +67,6 @@ public class IotMqttConnectionManager {
66 67 } catch (Exception ignored) {
67 68 // 连接已关闭,忽略异常
68 69 }
69   -
70 70 return realTimeAddress;
71 71 }
72 72  
... ... @@ -129,7 +129,6 @@ public class IotMqttConnectionManager {
129 129 if (endpoint == null) {
130 130 return null;
131 131 }
132   -
133 132 // 获取连接信息
134 133 return getConnectionInfo(endpoint);
135 134 }
... ... @@ -208,6 +207,7 @@ public class IotMqttConnectionManager {
208 207 */
209 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 45 }
46 46  
47 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 51 // 3. 发送到设备
52 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 57 # 针对引入的 TCP 组件的配置
58 58 # ====================================
59 59 - id: tcp-json
  60 + enabled: false
60 61 type: tcp
61 62 port: 8091
62   - enabled: false
63 63 serialize: json
64 64 tcp:
65 65 max-connections: 1000
... ... @@ -79,9 +79,9 @@ yudao:
79 79 # 针对引入的 UDP 组件的配置
80 80 # ====================================
81 81 - id: udp-json
  82 + enabled: false
82 83 type: udp
83 84 port: 8093
84   - enabled: false
85 85 serialize: json
86 86 udp:
87 87 max-sessions: 1000 # 最大会话数
... ... @@ -92,9 +92,9 @@ yudao:
92 92 # 针对引入的 WebSocket 组件的配置
93 93 # ====================================
94 94 - id: websocket-json
  95 + enabled: true
95 96 type: websocket
96 97 port: 8094
97   - enabled: true
98 98 serialize: json
99 99 websocket:
100 100 path: /ws
... ... @@ -106,13 +106,26 @@ yudao:
106 106 # 针对引入的 CoAP 组件的配置
107 107 # ====================================
108 108 - id: coap-json
  109 + enabled: false
109 110 type: coap
110 111 port: 5683
111   - enabled: true
112 112 coap:
113 113 max-message-size: 1024 # 最大消息大小(字节)
114 114 ack-timeout-ms: 2000 # ACK 超时时间(毫秒)
115 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 131 protocol:
... ... @@ -150,15 +163,6 @@ yudao:
150 163 key-store-password: "your-keystore-password" # 客户端证书库密码
151 164 trust-store-path: "classpath:certs/trust.jks" # 信任的 CA 证书库路径
152 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  
... ...