Commit 50ac2ca5f6fcd3e5685622ca1ef29c13bf44ef9c

Authored by haohao
1 parent 14336002

feat:【IoT 物联网】优化 MQTT 协议实现

yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayConfiguration.java
... ... @@ -129,12 +129,11 @@ public class IotGatewayConfiguration {
129 129  
130 130 @Bean
131 131 public IotMqttUpstreamProtocol iotMqttUpstreamProtocol(IotGatewayProperties gatewayProperties,
132   - IotDeviceService deviceService,
133 132 IotDeviceMessageService messageService,
134 133 IotMqttConnectionManager connectionManager,
135 134 Vertx mqttVertx) {
136   - return new IotMqttUpstreamProtocol(gatewayProperties.getProtocol().getMqtt(),
137   - deviceService, messageService, connectionManager, mqttVertx);
  135 + return new IotMqttUpstreamProtocol(gatewayProperties.getProtocol().getMqtt(), messageService,
  136 + connectionManager, mqttVertx);
138 137 }
139 138  
140 139 @Bean
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotMqttUpstreamProtocol.java
... ... @@ -4,7 +4,6 @@ import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
4 4 import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties;
5 5 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager.IotMqttConnectionManager;
6 6 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router.IotMqttUpstreamHandler;
7   -import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService;
8 7 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
9 8 import io.vertx.core.Vertx;
10 9 import io.vertx.mqtt.MqttServer;
... ... @@ -24,8 +23,6 @@ public class IotMqttUpstreamProtocol {
24 23  
25 24 private final IotGatewayProperties.MqttProperties mqttProperties;
26 25  
27   - private final IotDeviceService deviceService;
28   -
29 26 private final IotDeviceMessageService messageService;
30 27  
31 28 private final IotMqttConnectionManager connectionManager;
... ... @@ -38,12 +35,10 @@ public class IotMqttUpstreamProtocol {
38 35 private MqttServer mqttServer;
39 36  
40 37 public IotMqttUpstreamProtocol(IotGatewayProperties.MqttProperties mqttProperties,
41   - IotDeviceService deviceService,
42 38 IotDeviceMessageService messageService,
43 39 IotMqttConnectionManager connectionManager,
44 40 Vertx vertx) {
45 41 this.mqttProperties = mqttProperties;
46   - this.deviceService = deviceService;
47 42 this.messageService = messageService;
48 43 this.connectionManager = connectionManager;
49 44 this.vertx = vertx;
... ... @@ -54,22 +49,22 @@ public class IotMqttUpstreamProtocol {
54 49 @PostConstruct
55 50 public void start() {
56 51 // 创建服务器选项
57   - MqttServerOptions options = new MqttServerOptions();
58   - options.setPort(mqttProperties.getPort());
59   - options.setMaxMessageSize(mqttProperties.getMaxMessageSize());
60   - options.setTimeoutOnConnect(mqttProperties.getConnectTimeoutSeconds());
  52 + MqttServerOptions options = new MqttServerOptions()
  53 + .setPort(mqttProperties.getPort())
  54 + .setMaxMessageSize(mqttProperties.getMaxMessageSize())
  55 + .setTimeoutOnConnect(mqttProperties.getConnectTimeoutSeconds());
61 56  
62 57 // 配置 SSL(如果启用)
63 58 if (Boolean.TRUE.equals(mqttProperties.getSslEnabled())) {
64   - options.setSsl(true).setKeyCertOptions(mqttProperties.getSslOptions().getKeyCertOptions())
  59 + options.setSsl(true)
  60 + .setKeyCertOptions(mqttProperties.getSslOptions().getKeyCertOptions())
65 61 .setTrustOptions(mqttProperties.getSslOptions().getTrustOptions());
66 62 }
67 63  
68 64 // 创建服务器并设置连接处理器
69 65 mqttServer = MqttServer.create(vertx, options);
70 66 mqttServer.endpointHandler(endpoint -> {
71   - IotMqttUpstreamHandler handler = new IotMqttUpstreamHandler(this, messageService, deviceService,
72   - connectionManager);
  67 + IotMqttUpstreamHandler handler = new IotMqttUpstreamHandler(this, messageService, connectionManager);
73 68 handler.handle(endpoint);
74 69 });
75 70  
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/manager/IotMqttConnectionManager.java
1 1 package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager;
2 2  
  3 +import cn.hutool.core.util.StrUtil;
3 4 import io.netty.handler.codec.mqtt.MqttQoS;
4 5 import io.vertx.mqtt.MqttEndpoint;
5 6 import lombok.Data;
... ... @@ -24,6 +25,11 @@ import java.util.concurrent.ConcurrentHashMap;
24 25 public class IotMqttConnectionManager {
25 26  
26 27 /**
  28 + * 未知地址常量(当获取端点地址失败时使用)
  29 + */
  30 + private static final String UNKNOWN_ADDRESS = "unknown";
  31 +
  32 + /**
27 33 * 连接信息映射:MqttEndpoint -> 连接信息
28 34 */
29 35 private final Map<MqttEndpoint, ConnectionInfo> connectionMap = new ConcurrentHashMap<>();
... ... @@ -35,21 +41,32 @@ public class IotMqttConnectionManager {
35 41  
36 42 /**
37 43 * 安全获取 endpoint 地址
  44 + * <p>
  45 + * 优先从缓存获取地址,缓存为空时再尝试实时获取
38 46 *
39 47 * @param endpoint MQTT 连接端点
40   - * @return 地址字符串,如果获取失败则返回 "unknown"
  48 + * @return 地址字符串,获取失败时返回 "unknown"
41 49 */
42   - private String getEndpointAddress(MqttEndpoint endpoint) {
  50 + public String getEndpointAddress(MqttEndpoint endpoint) {
  51 + String realTimeAddress = UNKNOWN_ADDRESS;
  52 + if (endpoint == null) {
  53 + return realTimeAddress;
  54 + }
  55 +
  56 + // 1. 优先从缓存获取(避免连接关闭时的异常)
  57 + ConnectionInfo connectionInfo = connectionMap.get(endpoint);
  58 + if (connectionInfo != null && StrUtil.isNotBlank(connectionInfo.getRemoteAddress())) {
  59 + return connectionInfo.getRemoteAddress();
  60 + }
  61 +
  62 + // 2. 缓存为空时尝试实时获取
43 63 try {
44   - if (endpoint != null) {
45   - return endpoint.remoteAddress().toString();
46   - }
47   - } catch (Exception e) {
48   - // 忽略异常,返回默认值
49   - // TODO @haohao:这个比较稳定会出现哇?
  64 + realTimeAddress = endpoint.remoteAddress().toString();
  65 + } catch (Exception ignored) {
  66 + // 连接已关闭,忽略异常
50 67 }
51   - // TODO @haohao:这个要枚举下么?
52   - return "unknown";
  68 +
  69 + return realTimeAddress;
53 70 }
54 71  
55 72 /**
... ... @@ -87,8 +104,9 @@ public class IotMqttConnectionManager {
87 104 if (connectionInfo != null) {
88 105 Long deviceId = connectionInfo.getDeviceId();
89 106 deviceEndpointMap.remove(deviceId);
90   - log.info("[unregisterConnection][注销设备连接,设备 ID: {},连接: {}]",
91   - deviceId, getEndpointAddress(endpoint));
  107 +
  108 + log.info("[unregisterConnection][注销设备连接,设备 ID: {},连接: {}]", deviceId,
  109 + getEndpointAddress(endpoint));
92 110 }
93 111 }
94 112  
... ... @@ -195,6 +213,11 @@ public class IotMqttConnectionManager {
195 213 */
196 214 private boolean authenticated;
197 215  
  216 + /**
  217 + * 连接地址
  218 + */
  219 + private String remoteAddress;
  220 +
198 221 }
199 222  
200 223 }
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/router/IotMqttUpstreamHandler.java
... ... @@ -12,7 +12,6 @@ import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
12 12 import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
13 13 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttUpstreamProtocol;
14 14 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager.IotMqttConnectionManager;
15   -import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService;
16 15 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
17 16 import io.netty.handler.codec.mqtt.MqttConnectReturnCode;
18 17 import io.netty.handler.codec.mqtt.MqttQoS;
... ... @@ -40,8 +39,6 @@ public class IotMqttUpstreamHandler {
40 39  
41 40 public IotMqttUpstreamHandler(IotMqttUpstreamProtocol protocol,
42 41 IotDeviceMessageService deviceMessageService,
43   - // TODO @haohao:用不到的 deviceService 可以删除哈;
44   - IotDeviceService deviceService,
45 42 IotMqttConnectionManager connectionManager) {
46 43 this.deviceMessageService = deviceMessageService;
47 44 this.deviceApi = SpringUtil.getBean(IotDeviceCommonApi.class);
... ... @@ -60,7 +57,7 @@ public class IotMqttUpstreamHandler {
60 57 String password = endpoint.auth() != null ? endpoint.auth().getPassword() : null;
61 58  
62 59 log.debug("[handle][设备连接请求,客户端 ID: {},用户名: {},地址: {}]",
63   - clientId, username, getEndpointAddress(endpoint));
  60 + clientId, username, connectionManager.getEndpointAddress(endpoint));
64 61  
65 62 // 1. 先进行认证
66 63 if (!authenticateDevice(clientId, username, password, endpoint)) {
... ... @@ -71,32 +68,46 @@ public class IotMqttUpstreamHandler {
71 68  
72 69 log.info("[handle][设备认证成功,建立连接,客户端 ID: {},用户名: {}]", clientId, username);
73 70  
74   - // TODO @haohao:这里是不是少了序号哈?
75   - // 设置异常和关闭处理器
  71 + // 2. 设置异常和关闭处理器
76 72 endpoint.exceptionHandler(ex -> {
77   - log.warn("[handle][连接异常,客户端 ID: {},地址: {}]", clientId, getEndpointAddress(endpoint));
  73 + log.warn("[handle][连接异常,客户端 ID: {},地址: {}]", clientId, connectionManager.getEndpointAddress(endpoint));
78 74 cleanupConnection(endpoint);
79 75 });
80 76 endpoint.closeHandler(v -> {
81   - log.debug("[handle][连接关闭,客户端 ID: {},地址: {}]", clientId, getEndpointAddress(endpoint));
82 77 cleanupConnection(endpoint);
83 78 });
84 79  
85   - // 设置消息处理器
  80 + // 3. 设置消息处理器
86 81 endpoint.publishHandler(message -> {
87 82 try {
88   - processMessage(clientId, message.topicName(), message.payload().getBytes(), endpoint);
  83 + processMessage(clientId, message.topicName(), message.payload().getBytes());
  84 +
  85 + // 根据 QoS 级别发送相应的确认消息
  86 + if (message.qosLevel() == MqttQoS.AT_LEAST_ONCE) {
  87 + // QoS 1: 发送 PUBACK 确认
  88 + endpoint.publishAcknowledge(message.messageId());
  89 + } else if (message.qosLevel() == MqttQoS.EXACTLY_ONCE) {
  90 + // QoS 2: 发送 PUBREC 确认
  91 + endpoint.publishReceived(message.messageId());
  92 + }
  93 + // QoS 0 无需确认
  94 +
89 95 } catch (Exception e) {
90 96 log.error("[handle][消息解码失败,断开连接,客户端 ID: {},地址: {},错误: {}]",
91   - clientId, getEndpointAddress(endpoint), e.getMessage());
  97 + clientId, connectionManager.getEndpointAddress(endpoint), e.getMessage());
92 98 cleanupConnection(endpoint);
93 99 endpoint.close();
94 100 }
95 101 });
96 102  
97   - // 设置订阅处理器
  103 + // 4. 设置订阅处理器
98 104 endpoint.subscribeHandler(subscribe -> {
99   - log.debug("[handle][设备订阅,客户端 ID: {},主题: {}]", clientId, subscribe.topicSubscriptions());
  105 + // 提取主题名称列表用于日志显示
  106 + List<String> topicNames = subscribe.topicSubscriptions().stream()
  107 + .map(MqttTopicSubscription::topicName)
  108 + .collect(java.util.stream.Collectors.toList());
  109 + log.debug("[handle][设备订阅,客户端 ID: {},主题: {}]", clientId, topicNames);
  110 +
100 111 // 提取 QoS 列表
101 112 List<MqttQoS> grantedQoSLevels = subscribe.topicSubscriptions().stream()
102 113 .map(MqttTopicSubscription::qualityOfService)
... ... @@ -104,19 +115,22 @@ public class IotMqttUpstreamHandler {
104 115 endpoint.subscribeAcknowledge(subscribe.messageId(), grantedQoSLevels);
105 116 });
106 117  
107   - // 设置取消订阅处理器
  118 + // 5. 设置取消订阅处理器
108 119 endpoint.unsubscribeHandler(unsubscribe -> {
109 120 log.debug("[handle][设备取消订阅,客户端 ID: {},主题: {}]", clientId, unsubscribe.topics());
110 121 endpoint.unsubscribeAcknowledge(unsubscribe.messageId());
111 122 });
112 123  
113   - // 设置断开连接处理器
  124 + // 6. 设置 QoS 2消息的 PUBREL 处理器
  125 + endpoint.publishReleaseHandler(endpoint::publishComplete);
  126 +
  127 + // 7. 设置断开连接处理器
114 128 endpoint.disconnectHandler(v -> {
115 129 log.debug("[handle][设备断开连接,客户端 ID: {}]", clientId);
116 130 cleanupConnection(endpoint);
117 131 });
118 132  
119   - // 接受连接
  133 + // 8. 接受连接
120 134 endpoint.accept(false);
121 135 }
122 136  
... ... @@ -126,10 +140,8 @@ public class IotMqttUpstreamHandler {
126 140 * @param clientId 客户端 ID
127 141 * @param topic 主题
128 142 * @param payload 消息内容
129   - * @param endpoint MQTT 连接端点
130   - * @throws Exception 消息解码失败时抛出异常
131 143 */
132   - private void processMessage(String clientId, String topic, byte[] payload, MqttEndpoint endpoint) throws Exception {
  144 + private void processMessage(String clientId, String topic, byte[] payload) {
133 145 // 1. 基础检查
134 146 if (payload == null || payload.length == 0) {
135 147 return;
... ... @@ -146,14 +158,22 @@ public class IotMqttUpstreamHandler {
146 158 String deviceName = topicParts[3];
147 159  
148 160 // 3. 解码消息(使用从 topic 解析的 productKey 和 deviceName)
149   - IotDeviceMessage message = deviceMessageService.decodeDeviceMessage(payload, productKey, deviceName);
150   - if (message == null) {
151   - log.warn("[processMessage][消息解码失败,客户端 ID: {},主题: {}]", clientId, topic);
152   - return;
153   - }
  161 + try {
  162 + IotDeviceMessage message = deviceMessageService.decodeDeviceMessage(payload, productKey, deviceName);
  163 + if (message == null) {
  164 + log.warn("[processMessage][消息解码失败,客户端 ID: {},主题: {}]", clientId, topic);
  165 + return;
  166 + }
154 167  
155   - // 4. 处理业务消息(认证已在连接时完成)
156   - handleBusinessRequest(clientId, message, productKey, deviceName, endpoint);
  168 + log.info("[processMessage][收到设备消息,设备: {}.{}, 方法: {}]",
  169 + productKey, deviceName, message.getMethod());
  170 +
  171 + // 4. 处理业务消息(认证已在连接时完成)
  172 + handleBusinessRequest(message, productKey, deviceName);
  173 + } catch (Exception e) {
  174 + log.error("[processMessage][消息处理异常,客户端 ID: {},主题: {},错误: {}]",
  175 + clientId, topic, e.getMessage(), e);
  176 + }
157 177 }
158 178  
159 179 /**
... ... @@ -194,9 +214,10 @@ public class IotMqttUpstreamHandler {
194 214 return false;
195 215 }
196 216  
197   - IotDeviceGetReqDTO getReqDTO = new IotDeviceGetReqDTO();
198   - getReqDTO.setProductKey(deviceInfo.getProductKey());
199   - getReqDTO.setDeviceName(deviceInfo.getDeviceName());
  217 + IotDeviceGetReqDTO getReqDTO = new IotDeviceGetReqDTO()
  218 + .setProductKey(deviceInfo.getProductKey())
  219 + .setDeviceName(deviceInfo.getDeviceName());
  220 +
200 221 CommonResult<IotDeviceRespDTO> deviceResult = deviceApi.getDevice(getReqDTO);
201 222 if (!deviceResult.isSuccess() || deviceResult.getData() == null) {
202 223 log.warn("[authenticateDevice][获取设备信息失败,客户端 ID: {},用户名: {},错误: {}]",
... ... @@ -221,8 +242,7 @@ public class IotMqttUpstreamHandler {
221 242 /**
222 243 * 处理业务请求
223 244 */
224   - private void handleBusinessRequest(String clientId, IotDeviceMessage message, String productKey, String deviceName,
225   - MqttEndpoint endpoint) {
  245 + private void handleBusinessRequest(IotDeviceMessage message, String productKey, String deviceName) {
226 246 // 发送消息到消息总线
227 247 message.setServerId(serverId);
228 248 deviceMessageService.sendDeviceMessage(message, productKey, deviceName, serverId);
... ... @@ -233,12 +253,14 @@ public class IotMqttUpstreamHandler {
233 253 */
234 254 private void registerConnection(MqttEndpoint endpoint, IotDeviceRespDTO device,
235 255 String clientId) {
236   - IotMqttConnectionManager.ConnectionInfo connectionInfo = new IotMqttConnectionManager.ConnectionInfo();
237   - connectionInfo.setDeviceId(device.getId());
238   - connectionInfo.setProductKey(device.getProductKey());
239   - connectionInfo.setDeviceName(device.getDeviceName());
240   - connectionInfo.setClientId(clientId);
241   - connectionInfo.setAuthenticated(true);
  256 +
  257 + IotMqttConnectionManager.ConnectionInfo connectionInfo = new IotMqttConnectionManager.ConnectionInfo()
  258 + .setDeviceId(device.getId())
  259 + .setProductKey(device.getProductKey())
  260 + .setDeviceName(device.getDeviceName())
  261 + .setClientId(clientId)
  262 + .setAuthenticated(true)
  263 + .setRemoteAddress(connectionManager.getEndpointAddress(endpoint));
242 264  
243 265 connectionManager.registerConnection(endpoint, device.getId(), connectionInfo);
244 266 }
... ... @@ -258,23 +280,6 @@ public class IotMqttUpstreamHandler {
258 280 }
259 281  
260 282 /**
261   - * 安全获取 endpoint 地址
262   - *
263   - * @param endpoint MQTT 连接端点
264   - * @return 地址字符串,如果获取失败则返回 "unknown"
265   - */
266   - private String getEndpointAddress(MqttEndpoint endpoint) {
267   - try {
268   - if (endpoint != null) {
269   - return endpoint.remoteAddress().toString();
270   - }
271   - } catch (Exception e) {
272   - // 忽略异常,返回默认值
273   - }
274   - return "unknown";
275   - }
276   -
277   - /**
278 283 * 清理连接
279 284 */
280 285 private void cleanupConnection(MqttEndpoint endpoint) {
... ...