Commit 4f6ee4c5eb49238cbe5e049a4740df0cd4609ff2

Authored by YunaiV
1 parent 83990086

feat(iot):【协议改造】优化各种 code review 的代码

Showing 12 changed files with 97 additions and 66 deletions
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/IotHttpProtocol.java
@@ -125,7 +125,11 @@ public class IotHttpProtocol implements IotProtocol { @@ -125,7 +125,11 @@ public class IotHttpProtocol implements IotProtocol {
125 this.downstreamSubscriber.start(); 125 this.downstreamSubscriber.start();
126 } catch (Exception e) { 126 } catch (Exception e) {
127 log.error("[start][IoT HTTP 协议 {} 启动失败]", getId(), e); 127 log.error("[start][IoT HTTP 协议 {} 启动失败]", getId(), e);
128 - // 启动失败时关闭 Vertx 128 + // 启动失败时关闭资源
  129 + if (httpServer != null) {
  130 + httpServer.close();
  131 + httpServer = null;
  132 + }
129 if (vertx != null) { 133 if (vertx != null) {
130 vertx.close(); 134 vertx.close();
131 vertx = null; 135 vertx = null;
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotMqttConfig.java
@@ -27,13 +27,6 @@ public class IotMqttConfig { @@ -27,13 +27,6 @@ public class IotMqttConfig {
27 private Integer connectTimeoutSeconds = 60; 27 private Integer connectTimeoutSeconds = 60;
28 28
29 /** 29 /**
30 - * 保持连接超时时间(秒)  
31 - */  
32 - @NotNull(message = "保持连接超时时间不能为空")  
33 - @Min(value = 1, message = "保持连接超时时间不能小于 1 秒")  
34 - private Integer keepAliveTimeoutSeconds = 300;  
35 -  
36 - /**  
37 * 是否启用 SSL 30 * 是否启用 SSL
38 */ 31 */
39 @NotNull(message = "是否启用 SSL 不能为空") 32 @NotNull(message = "是否启用 SSL 不能为空")
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotMqttProtocol.java
1 package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt; 1 package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt;
2 2
  3 +import cn.hutool.core.lang.Assert;
  4 +import cn.hutool.core.util.StrUtil;
3 import cn.hutool.extra.spring.SpringUtil; 5 import cn.hutool.extra.spring.SpringUtil;
4 import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi; 6 import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
5 import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum; 7 import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum;
6 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus; 8 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
  9 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
7 import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; 10 import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
8 import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties.ProtocolInstanceProperties; 11 import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties.ProtocolInstanceProperties;
9 import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocol; 12 import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocol;
10 -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;  
11 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.downstream.IotMqttDownstreamHandler; 13 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.downstream.IotMqttDownstreamHandler;
12 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.downstream.IotMqttDownstreamSubscriber; 14 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.downstream.IotMqttDownstreamSubscriber;
13 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.upstream.IotMqttAuthHandler; 15 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.upstream.IotMqttAuthHandler;
@@ -15,6 +17,7 @@ import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.upstream.IotMqt @@ -15,6 +17,7 @@ import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.upstream.IotMqt
15 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.upstream.IotMqttUpstreamHandler; 17 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.upstream.IotMqttUpstreamHandler;
16 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager.IotMqttConnectionManager; 18 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager.IotMqttConnectionManager;
17 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; 19 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
  20 +import cn.iocoder.yudao.module.iot.gateway.util.IotMqttTopicUtils;
18 import io.netty.handler.codec.mqtt.MqttConnectReturnCode; 21 import io.netty.handler.codec.mqtt.MqttConnectReturnCode;
19 import io.netty.handler.codec.mqtt.MqttQoS; 22 import io.netty.handler.codec.mqtt.MqttQoS;
20 import io.vertx.core.Vertx; 23 import io.vertx.core.Vertx;
@@ -26,13 +29,10 @@ import io.vertx.mqtt.MqttTopicSubscription; @@ -26,13 +29,10 @@ import io.vertx.mqtt.MqttTopicSubscription;
26 import io.vertx.mqtt.messages.MqttPublishMessage; 29 import io.vertx.mqtt.messages.MqttPublishMessage;
27 import lombok.Getter; 30 import lombok.Getter;
28 import lombok.extern.slf4j.Slf4j; 31 import lombok.extern.slf4j.Slf4j;
29 -import cn.hutool.core.lang.Assert;  
30 -import cn.hutool.core.util.StrUtil;  
31 32
  33 +import java.util.ArrayList;
32 import java.util.List; 34 import java.util.List;
33 35
34 -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;  
35 -  
36 /** 36 /**
37 * IoT 网关 MQTT 协议:接收设备上行消息 37 * IoT 网关 MQTT 协议:接收设备上行消息
38 * 38 *
@@ -249,11 +249,22 @@ public class IotMqttProtocol implements IotProtocol { @@ -249,11 +249,22 @@ public class IotMqttProtocol implements IotProtocol {
249 // 3.2 设置 QoS 2 消息的 PUBREL 处理器 249 // 3.2 设置 QoS 2 消息的 PUBREL 处理器
250 endpoint.publishReleaseHandler(endpoint::publishComplete); 250 endpoint.publishReleaseHandler(endpoint::publishComplete);
251 251
252 - // 4.1 设置订阅处理器 252 + // 4.1 设置订阅处理器(带 ACL 校验)
253 endpoint.subscribeHandler(subscribe -> { 253 endpoint.subscribeHandler(subscribe -> {
254 - List<String> topicNames = convertList(subscribe.topicSubscriptions(), MqttTopicSubscription::topicName);  
255 - log.debug("[handleEndpoint][设备订阅,客户端 ID: {},主题: {}]", clientId, topicNames);  
256 - List<MqttQoS> grantedQoSLevels = convertList(subscribe.topicSubscriptions(), MqttTopicSubscription::qualityOfService); 254 + IotMqttConnectionManager.ConnectionInfo connectionInfo = connectionManager.getConnectionInfo(endpoint);
  255 + List<MqttQoS> grantedQoSLevels = new ArrayList<>();
  256 + for (MqttTopicSubscription sub : subscribe.topicSubscriptions()) {
  257 + String topicName = sub.topicName();
  258 + // 校验主题是否属于当前设备
  259 + if (connectionInfo != null && IotMqttTopicUtils.isTopicSubscribeAllowed(
  260 + topicName, connectionInfo.getProductKey(), connectionInfo.getDeviceName())) {
  261 + grantedQoSLevels.add(sub.qualityOfService());
  262 + log.debug("[handleEndpoint][订阅成功,客户端 ID: {},主题: {}]", clientId, topicName);
  263 + } else {
  264 + log.warn("[handleEndpoint][订阅被拒绝,客户端 ID: {},主题: {}]", clientId, topicName);
  265 + grantedQoSLevels.add(MqttQoS.FAILURE);
  266 + }
  267 + }
257 endpoint.subscribeAcknowledge(subscribe.messageId(), grantedQoSLevels); 268 endpoint.subscribeAcknowledge(subscribe.messageId(), grantedQoSLevels);
258 }); 269 });
259 // 4.2 设置取消订阅处理器 270 // 4.2 设置取消订阅处理器
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/handler/upstream/IotMqttAbstractHandler.java
@@ -30,7 +30,6 @@ public abstract class IotMqttAbstractHandler { @@ -30,7 +30,6 @@ public abstract class IotMqttAbstractHandler {
30 protected final IotMqttConnectionManager connectionManager; 30 protected final IotMqttConnectionManager connectionManager;
31 protected final IotDeviceMessageService deviceMessageService; 31 protected final IotDeviceMessageService deviceMessageService;
32 32
33 - // done @AI:基于 method 通过 IotMqttTopicUtils.buildTopicByMethod 计算 reply topic  
34 /** 33 /**
35 * 发送成功响应到设备 34 * 发送成功响应到设备
36 * 35 *
@@ -43,20 +42,8 @@ public abstract class IotMqttAbstractHandler { @@ -43,20 +42,8 @@ public abstract class IotMqttAbstractHandler {
43 */ 42 */
44 protected void sendSuccessResponse(MqttEndpoint endpoint, String productKey, String deviceName, 43 protected void sendSuccessResponse(MqttEndpoint endpoint, String productKey, String deviceName,
45 String requestId, String method, Object data) { 44 String requestId, String method, Object data) {
46 - try {  
47 - // 1. 构建响应消息  
48 - IotDeviceMessage responseMessage = IotDeviceMessage.replyOf(requestId, method, data, 0, null);  
49 -  
50 - // 2. 编码消息(使用默认编解码器)  
51 - byte[] encodedData = deviceMessageService.encodeDeviceMessage(responseMessage, DEFAULT_CODEC_TYPE);  
52 -  
53 - // 3. 构建响应主题并发送  
54 - String replyTopic = IotMqttTopicUtils.buildTopicByMethod(method, productKey, deviceName, true);  
55 - endpoint.publish(replyTopic, Buffer.buffer(encodedData), MqttQoS.AT_LEAST_ONCE, false, false);  
56 - log.debug("[sendSuccessResponse][发送成功响应,主题: {}]", replyTopic);  
57 - } catch (Exception e) {  
58 - log.error("[sendSuccessResponse][发送成功响应异常,客户端 ID: {}]", endpoint.clientIdentifier(), e);  
59 - } 45 + IotDeviceMessage responseMessage = IotDeviceMessage.replyOf(requestId, method, data, 0, null);
  46 + writeResponse(endpoint, productKey, deviceName, method, responseMessage);
60 } 47 }
61 48
62 /** 49 /**
@@ -72,22 +59,32 @@ public abstract class IotMqttAbstractHandler { @@ -72,22 +59,32 @@ public abstract class IotMqttAbstractHandler {
72 */ 59 */
73 protected void sendErrorResponse(MqttEndpoint endpoint, String productKey, String deviceName, 60 protected void sendErrorResponse(MqttEndpoint endpoint, String productKey, String deviceName,
74 String requestId, String method, Integer errorCode, String errorMessage) { 61 String requestId, String method, Integer errorCode, String errorMessage) {
75 - try {  
76 - // 1. 构建响应消息  
77 - IotDeviceMessage responseMessage = IotDeviceMessage.replyOf(requestId, method, null, errorCode, errorMessage); 62 + IotDeviceMessage responseMessage = IotDeviceMessage.replyOf(requestId, method, null, errorCode, errorMessage);
  63 + writeResponse(endpoint, productKey, deviceName, method, responseMessage);
  64 + }
78 65
79 - // 2. 编码消息(使用默认编解码器) 66 + /**
  67 + * 写入响应消息到设备
  68 + *
  69 + * @param endpoint MQTT 连接端点
  70 + * @param productKey 产品 Key
  71 + * @param deviceName 设备名称
  72 + * @param method 方法名
  73 + * @param responseMessage 响应消息
  74 + */
  75 + private void writeResponse(MqttEndpoint endpoint, String productKey, String deviceName,
  76 + String method, IotDeviceMessage responseMessage) {
  77 + try {
  78 + // 1. 编码消息(使用默认编解码器)
80 byte[] encodedData = deviceMessageService.encodeDeviceMessage(responseMessage, DEFAULT_CODEC_TYPE); 79 byte[] encodedData = deviceMessageService.encodeDeviceMessage(responseMessage, DEFAULT_CODEC_TYPE);
81 80
82 - // 3. 构建响应主题并发送 81 + // 2. 构建响应主题,并发送
83 String replyTopic = IotMqttTopicUtils.buildTopicByMethod(method, productKey, deviceName, true); 82 String replyTopic = IotMqttTopicUtils.buildTopicByMethod(method, productKey, deviceName, true);
84 endpoint.publish(replyTopic, Buffer.buffer(encodedData), MqttQoS.AT_LEAST_ONCE, false, false); 83 endpoint.publish(replyTopic, Buffer.buffer(encodedData), MqttQoS.AT_LEAST_ONCE, false, false);
85 - log.debug("[sendErrorResponse][发送错误响应,主题: {}]", replyTopic); 84 + log.debug("[writeResponse][发送响应,主题: {},code: {}]", replyTopic, responseMessage.getCode());
86 } catch (Exception e) { 85 } catch (Exception e) {
87 - log.error("[sendErrorResponse][发送错误响应异常,客户端 ID: {}]", endpoint.clientIdentifier(), e); 86 + log.error("[writeResponse][发送响应异常,客户端 ID: {}]", endpoint.clientIdentifier(), e);
88 } 87 }
89 } 88 }
90 89
91 - // TODO @AI:当前 sendSuccessResponse/sendErrorResponse 已足够清晰,暂不抽取 writeResponse(必须抽取!!!)  
92 -  
93 } 90 }
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/handler/upstream/IotMqttAuthHandler.java
@@ -103,7 +103,7 @@ public class IotMqttAuthHandler extends IotMqttAbstractHandler { @@ -103,7 +103,7 @@ public class IotMqttAuthHandler extends IotMqttAbstractHandler {
103 .setProductKey(device.getProductKey()) 103 .setProductKey(device.getProductKey())
104 .setDeviceName(device.getDeviceName()) 104 .setDeviceName(device.getDeviceName())
105 .setRemoteAddress(connectionManager.getEndpointAddress(endpoint)); 105 .setRemoteAddress(connectionManager.getEndpointAddress(endpoint));
106 - connectionManager.registerConnection(endpoint, device.getId(), connectionInfo); 106 + connectionManager.registerConnection(endpoint, connectionInfo);
107 } 107 }
108 108
109 /** 109 /**
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/handler/upstream/IotMqttRegisterHandler.java
@@ -77,6 +77,9 @@ public class IotMqttRegisterHandler extends IotMqttAbstractHandler { @@ -77,6 +77,9 @@ public class IotMqttRegisterHandler extends IotMqttAbstractHandler {
77 // 接受连接,并发送错误响应 77 // 接受连接,并发送错误响应
78 endpoint.accept(false); 78 endpoint.accept(false);
79 sendErrorResponse(endpoint, productKey, deviceName, null, method, 500, e.getMessage()); 79 sendErrorResponse(endpoint, productKey, deviceName, null, method, 500, e.getMessage());
  80 + } finally {
  81 + // 注册完成后关闭连接(一型一密只用于获取 deviceSecret,不保持连接)
  82 + endpoint.close();
80 } 83 }
81 } 84 }
82 85
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/handler/upstream/IotMqttUpstreamHandler.java
@@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.upstream; @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.upstream;
2 2
3 import cn.hutool.core.lang.Assert; 3 import cn.hutool.core.lang.Assert;
4 import cn.hutool.core.util.ArrayUtil; 4 import cn.hutool.core.util.ArrayUtil;
5 -import cn.hutool.core.util.StrUtil;  
6 import cn.iocoder.yudao.framework.common.exception.ServiceException; 5 import cn.iocoder.yudao.framework.common.exception.ServiceException;
7 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 6 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
8 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager.IotMqttConnectionManager; 7 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager.IotMqttConnectionManager;
@@ -50,12 +49,10 @@ public class IotMqttUpstreamHandler extends IotMqttAbstractHandler { @@ -50,12 +49,10 @@ public class IotMqttUpstreamHandler extends IotMqttAbstractHandler {
50 } 49 }
51 // 1.2 解析主题,获取 productKey 和 deviceName 50 // 1.2 解析主题,获取 productKey 和 deviceName
52 String[] topicParts = topic.split("/"); 51 String[] topicParts = topic.split("/");
53 - if (topicParts.length < 4 || StrUtil.hasBlank(topicParts[2], topicParts[3])) {  
54 - log.warn("[handleBusinessRequest][topic({}) 格式不正确,无法解析有效的 productKey 和 deviceName]", topic);  
55 - return;  
56 - }  
57 - productKey = topicParts[2];  
58 - deviceName = topicParts[3]; 52 + productKey = ArrayUtil.get(topicParts, 2);
  53 + deviceName = ArrayUtil.get(topicParts, 3);
  54 + Assert.notBlank(productKey, "产品 Key 不能为空");
  55 + Assert.notBlank(deviceName, "设备名称不能为空");
59 // 1.3 校验设备信息,防止伪造设备消息 56 // 1.3 校验设备信息,防止伪造设备消息
60 IotMqttConnectionManager.ConnectionInfo connectionInfo = connectionManager.getConnectionInfo(endpoint); 57 IotMqttConnectionManager.ConnectionInfo connectionInfo = connectionManager.getConnectionInfo(endpoint);
61 Assert.notNull(connectionInfo, "无法获取连接信息"); 58 Assert.notNull(connectionInfo, "无法获取连接信息");
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/manager/IotMqttConnectionManager.java
@@ -73,25 +73,24 @@ public class IotMqttConnectionManager { @@ -73,25 +73,24 @@ public class IotMqttConnectionManager {
73 * 注册设备连接(包含认证信息) 73 * 注册设备连接(包含认证信息)
74 * 74 *
75 * @param endpoint MQTT 连接端点 75 * @param endpoint MQTT 连接端点
76 - * @param deviceId 设备 ID  
77 * @param connectionInfo 连接信息 76 * @param connectionInfo 连接信息
78 */ 77 */
79 - // TODO @AI:移除掉 deviceId ???参考别的 tcp 等模块协议  
80 - public void registerConnection(MqttEndpoint endpoint, Long deviceId, ConnectionInfo connectionInfo) { 78 + public void registerConnection(MqttEndpoint endpoint, ConnectionInfo connectionInfo) {
  79 + Long deviceId = connectionInfo.getDeviceId();
81 // 如果设备已有其他连接,先清理旧连接 80 // 如果设备已有其他连接,先清理旧连接
82 MqttEndpoint oldEndpoint = deviceEndpointMap.get(deviceId); 81 MqttEndpoint oldEndpoint = deviceEndpointMap.get(deviceId);
83 if (oldEndpoint != null && oldEndpoint != endpoint) { 82 if (oldEndpoint != null && oldEndpoint != endpoint) {
84 log.info("[registerConnection][设备已有其他连接,断开旧连接,设备 ID: {},旧连接: {}]", 83 log.info("[registerConnection][设备已有其他连接,断开旧连接,设备 ID: {},旧连接: {}]",
85 deviceId, getEndpointAddress(oldEndpoint)); 84 deviceId, getEndpointAddress(oldEndpoint));
86 - oldEndpoint.close();  
87 - // 清理旧连接的映射 85 + // 先清理映射,再关闭连接(避免旧连接处理器干扰)
88 connectionMap.remove(oldEndpoint); 86 connectionMap.remove(oldEndpoint);
  87 + oldEndpoint.close();
89 } 88 }
90 89
91 // 注册新连接 90 // 注册新连接
92 connectionMap.put(endpoint, connectionInfo); 91 connectionMap.put(endpoint, connectionInfo);
93 deviceEndpointMap.put(deviceId, endpoint); 92 deviceEndpointMap.put(deviceId, endpoint);
94 - log.info("[registerConnection][注册设备连接,设备 ID: {},连接: {},product key: {},device name: {}]", 93 + log.info("[registerConnection][注册设备连接,设备 ID: {},连接: {},productKey: {},deviceName: {}]",
95 deviceId, getEndpointAddress(endpoint), connectionInfo.getProductKey(), connectionInfo.getDeviceName()); 94 deviceId, getEndpointAddress(endpoint), connectionInfo.getProductKey(), connectionInfo.getDeviceName());
96 } 95 }
97 96
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/manager/IotTcpConnectionManager.java
@@ -47,8 +47,8 @@ public class IotTcpConnectionManager { @@ -47,8 +47,8 @@ public class IotTcpConnectionManager {
47 * @param deviceId 设备 ID 47 * @param deviceId 设备 ID
48 * @param connectionInfo 连接信息 48 * @param connectionInfo 连接信息
49 */ 49 */
50 - public void registerConnection(NetSocket socket, Long deviceId, ConnectionInfo connectionInfo) {  
51 - // 检查连接数是否已达上限 50 + public synchronized void registerConnection(NetSocket socket, Long deviceId, ConnectionInfo connectionInfo) {
  51 + // 检查连接数是否已达上限(同步方法确保检查和注册的原子性)
52 if (connectionMap.size() >= maxConnections) { 52 if (connectionMap.size() >= maxConnections) {
53 throw new IllegalStateException("连接数已达上限: " + maxConnections); 53 throw new IllegalStateException("连接数已达上限: " + maxConnections);
54 } 54 }
@@ -57,9 +57,9 @@ public class IotTcpConnectionManager { @@ -57,9 +57,9 @@ public class IotTcpConnectionManager {
57 if (oldSocket != null && oldSocket != socket) { 57 if (oldSocket != null && oldSocket != socket) {
58 log.info("[registerConnection][设备已有其他连接,断开旧连接,设备 ID: {},旧连接: {}]", 58 log.info("[registerConnection][设备已有其他连接,断开旧连接,设备 ID: {},旧连接: {}]",
59 deviceId, oldSocket.remoteAddress()); 59 deviceId, oldSocket.remoteAddress());
60 - oldSocket.close();  
61 - // 清理旧连接的映射 60 + // 先清理映射,再关闭连接
62 connectionMap.remove(oldSocket); 61 connectionMap.remove(oldSocket);
  62 + oldSocket.close();
63 } 63 }
64 64
65 // 注册新连接 65 // 注册新连接
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/udp/manager/IotUdpSessionManager.java
@@ -9,6 +9,7 @@ import lombok.Data; @@ -9,6 +9,7 @@ import lombok.Data;
9 import lombok.extern.slf4j.Slf4j; 9 import lombok.extern.slf4j.Slf4j;
10 10
11 import java.net.InetSocketAddress; 11 import java.net.InetSocketAddress;
  12 +import java.util.concurrent.CompletableFuture;
12 import java.util.concurrent.TimeUnit; 13 import java.util.concurrent.TimeUnit;
13 14
14 /** 15 /**
@@ -47,8 +48,8 @@ public class IotUdpSessionManager { @@ -47,8 +48,8 @@ public class IotUdpSessionManager {
47 * @param deviceId 设备 ID 48 * @param deviceId 设备 ID
48 * @param sessionInfo 会话信息 49 * @param sessionInfo 会话信息
49 */ 50 */
50 - public void registerSession(Long deviceId, SessionInfo sessionInfo) {  
51 - // 检查是否为新设备,且会话数已达上限 51 + public synchronized void registerSession(Long deviceId, SessionInfo sessionInfo) {
  52 + // 检查是否为新设备,且会话数已达上限(同步方法确保检查和注册的原子性)
52 if (deviceSessionCache.getIfPresent(deviceId) == null 53 if (deviceSessionCache.getIfPresent(deviceId) == null
53 && deviceSessionCache.size() >= maxSessions) { 54 && deviceSessionCache.size() >= maxSessions) {
54 throw new IllegalStateException("会话数已达上限: " + maxSessions); 55 throw new IllegalStateException("会话数已达上限: " + maxSessions);
@@ -113,16 +114,21 @@ public class IotUdpSessionManager { @@ -113,16 +114,21 @@ public class IotUdpSessionManager {
113 } 114 }
114 InetSocketAddress address = sessionInfo.getAddress(); 115 InetSocketAddress address = sessionInfo.getAddress();
115 try { 116 try {
  117 + // 使用 CompletableFuture 同步等待发送结果
  118 + CompletableFuture<Boolean> future = new CompletableFuture<>();
116 socket.send(Buffer.buffer(data), address.getPort(), address.getHostString(), result -> { 119 socket.send(Buffer.buffer(data), address.getPort(), address.getHostString(), result -> {
117 if (result.succeeded()) { 120 if (result.succeeded()) {
118 log.debug("[sendToDevice][发送消息成功,设备 ID: {},地址: {},数据长度: {} 字节]", 121 log.debug("[sendToDevice][发送消息成功,设备 ID: {},地址: {},数据长度: {} 字节]",
119 deviceId, buildAddressKey(address), data.length); 122 deviceId, buildAddressKey(address), data.length);
120 - return; 123 + future.complete(true);
  124 + } else {
  125 + log.error("[sendToDevice][发送消息失败,设备 ID: {},地址: {}]",
  126 + deviceId, buildAddressKey(address), result.cause());
  127 + future.complete(false);
121 } 128 }
122 - log.error("[sendToDevice][发送消息失败,设备 ID: {},地址: {}]",  
123 - deviceId, buildAddressKey(address), result.cause());  
124 }); 129 });
125 - return true; 130 + // 同步等待结果,超时 5 秒
  131 + return future.get(5, TimeUnit.SECONDS);
126 } catch (Exception e) { 132 } catch (Exception e) {
127 log.error("[sendToDevice][发送消息异常,设备 ID: {}]", deviceId, e); 133 log.error("[sendToDevice][发送消息异常,设备 ID: {}]", deviceId, e);
128 return false; 134 return false;
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/util/IotMqttTopicUtils.java
@@ -63,4 +63,26 @@ public final class IotMqttTopicUtils { @@ -63,4 +63,26 @@ public final class IotMqttTopicUtils {
63 return SYS_TOPIC_PREFIX + productKey + "/" + deviceName + "/" + topicSuffix; 63 return SYS_TOPIC_PREFIX + productKey + "/" + deviceName + "/" + topicSuffix;
64 } 64 }
65 65
  66 + /**
  67 + * 校验主题是否允许订阅
  68 + * <p>
  69 + * 规则:主题必须以 /sys/{productKey}/{deviceName}/ 开头,
  70 + * 或者是通配符形式 /sys/{productKey}/{deviceName}/#
  71 + *
  72 + * @param topic 订阅的主题
  73 + * @param productKey 产品 Key
  74 + * @param deviceName 设备名称
  75 + * @return 是否允许订阅
  76 + */
  77 + public static boolean isTopicSubscribeAllowed(String topic, String productKey, String deviceName) {
  78 + if (!StrUtil.isAllNotBlank(topic, productKey, deviceName)) {
  79 + return false;
  80 + }
  81 + // 构建设备主题前缀
  82 + String deviceTopicPrefix = SYS_TOPIC_PREFIX + productKey + "/" + deviceName + "/";
  83 + // 主题必须以设备前缀开头,或者是设备前缀的通配符形式
  84 + return topic.startsWith(deviceTopicPrefix)
  85 + || topic.equals(SYS_TOPIC_PREFIX + productKey + "/" + deviceName + "/#");
  86 + }
  87 +
66 } 88 }
67 \ No newline at end of file 89 \ No newline at end of file
yudao-module-iot/yudao-module-iot-gateway/src/main/resources/application.yaml
@@ -124,7 +124,6 @@ yudao: @@ -124,7 +124,6 @@ yudao:
124 mqtt: 124 mqtt:
125 max-message-size: 8192 # 最大消息大小(字节) 125 max-message-size: 8192 # 最大消息大小(字节)
126 connect-timeout-seconds: 60 # 连接超时时间(秒) 126 connect-timeout-seconds: 60 # 连接超时时间(秒)
127 - keep-alive-timeout-seconds: 300 # 保持连接超时时间(秒)  
128 ssl-enabled: false # 是否启用 SSL 127 ssl-enabled: false # 是否启用 SSL
129 128
130 # 协议配置(旧版,保持兼容) 129 # 协议配置(旧版,保持兼容)