Commit 2737ffa1162dbb92041119ebe0db4de8756f6780
1 parent
569eef4a
reactor:【IoT 物联网】重构 MQTT 客户端连接和订阅主题流程,更新配置属性
Showing
7 changed files
with
308 additions
and
127 deletions
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayProperties.java
| 1 | package cn.iocoder.yudao.module.iot.gateway.config; | 1 | package cn.iocoder.yudao.module.iot.gateway.config; |
| 2 | 2 | ||
| 3 | +import cn.hutool.core.util.StrUtil; | ||
| 3 | import jakarta.validation.constraints.NotEmpty; | 4 | import jakarta.validation.constraints.NotEmpty; |
| 4 | import jakarta.validation.constraints.NotNull; | 5 | import jakarta.validation.constraints.NotNull; |
| 5 | import lombok.Data; | 6 | import lombok.Data; |
| @@ -175,7 +176,7 @@ public class IotGatewayProperties { | @@ -175,7 +176,7 @@ public class IotGatewayProperties { | ||
| 175 | * @return MQTT 客户端 ID | 176 | * @return MQTT 客户端 ID |
| 176 | */ | 177 | */ |
| 177 | public String getMqttClientId() { | 178 | public String getMqttClientId() { |
| 178 | - if (cn.hutool.core.util.StrUtil.isBlank(mqttClientId)) { | 179 | + if (StrUtil.isBlank(mqttClientId)) { |
| 179 | mqttClientId = "iot-gateway-mqtt-" + System.currentTimeMillis(); | 180 | mqttClientId = "iot-gateway-mqtt-" + System.currentTimeMillis(); |
| 180 | } | 181 | } |
| 181 | return mqttClientId; | 182 | return mqttClientId; |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotMqttDownstreamSubscriber.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.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; | ||
| 4 | import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus; | 3 | import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus; |
| 5 | import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber; | 4 | import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber; |
| 6 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | 5 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| @@ -45,13 +44,13 @@ public class IotMqttDownstreamSubscriber implements IotMessageSubscriber<IotDevi | @@ -45,13 +44,13 @@ public class IotMqttDownstreamSubscriber implements IotMessageSubscriber<IotDevi | ||
| 45 | 44 | ||
| 46 | @Override | 45 | @Override |
| 47 | public void onMessage(IotDeviceMessage message) { | 46 | public void onMessage(IotDeviceMessage message) { |
| 48 | - log.info("[onMessage][接收到下行消息][messageId: {}][method: {}][deviceId: {}]", | 47 | + log.debug("[onMessage][接收到下行消息, messageId: {}, method: {}, deviceId: {}]", |
| 49 | message.getId(), message.getMethod(), message.getDeviceId()); | 48 | message.getId(), message.getMethod(), message.getDeviceId()); |
| 50 | try { | 49 | try { |
| 51 | // 1. 校验 | 50 | // 1. 校验 |
| 52 | String method = message.getMethod(); | 51 | String method = message.getMethod(); |
| 53 | if (method == null) { | 52 | if (method == null) { |
| 54 | - log.warn("[onMessage][消息方法为空][messageId: {}][deviceId: {}]", | 53 | + log.warn("[onMessage][消息方法为空, messageId: {}, deviceId: {}]", |
| 55 | message.getId(), message.getDeviceId()); | 54 | message.getId(), message.getDeviceId()); |
| 56 | return; | 55 | return; |
| 57 | } | 56 | } |
| @@ -59,20 +58,9 @@ public class IotMqttDownstreamSubscriber implements IotMessageSubscriber<IotDevi | @@ -59,20 +58,9 @@ public class IotMqttDownstreamSubscriber implements IotMessageSubscriber<IotDevi | ||
| 59 | // 2. 处理下行消息 | 58 | // 2. 处理下行消息 |
| 60 | downstreamHandler.handle(message); | 59 | downstreamHandler.handle(message); |
| 61 | } catch (Exception e) { | 60 | } catch (Exception e) { |
| 62 | - log.error("[onMessage][处理下行消息失败][messageId: {}][method: {}][deviceId: {}]", | 61 | + log.error("[onMessage][处理下行消息失败, messageId: {}, method: {}, deviceId: {}]", |
| 63 | message.getId(), message.getMethod(), message.getDeviceId(), e); | 62 | message.getId(), message.getMethod(), message.getDeviceId(), e); |
| 64 | } | 63 | } |
| 65 | } | 64 | } |
| 66 | 65 | ||
| 67 | - /** | ||
| 68 | - * 判断是否为上行消息 | ||
| 69 | - * | ||
| 70 | - * @param method 消息方法 | ||
| 71 | - * @return 是否为上行消息 | ||
| 72 | - */ | ||
| 73 | - private boolean isUpstreamMessage(String method) { | ||
| 74 | - IotDeviceMessageMethodEnum methodEnum = IotDeviceMessageMethodEnum.of(method); | ||
| 75 | - return methodEnum != null && methodEnum.getUpstream(); | ||
| 76 | - } | ||
| 77 | - | ||
| 78 | } | 66 | } |
| 79 | \ No newline at end of file | 67 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotMqttUpstreamProtocol.java
| @@ -21,7 +21,6 @@ import lombok.Getter; | @@ -21,7 +21,6 @@ import lombok.Getter; | ||
| 21 | import lombok.extern.slf4j.Slf4j; | 21 | import lombok.extern.slf4j.Slf4j; |
| 22 | 22 | ||
| 23 | import java.util.List; | 23 | import java.util.List; |
| 24 | -import java.util.concurrent.TimeUnit; | ||
| 25 | 24 | ||
| 26 | /** | 25 | /** |
| 27 | * IoT 网关 MQTT 协议:接收设备上行消息 | 26 | * IoT 网关 MQTT 协议:接收设备上行消息 |
| @@ -61,10 +60,10 @@ public class IotMqttUpstreamProtocol { | @@ -61,10 +60,10 @@ public class IotMqttUpstreamProtocol { | ||
| 61 | @PostConstruct | 60 | @PostConstruct |
| 62 | public void start() { | 61 | public void start() { |
| 63 | if (isRunning) { | 62 | if (isRunning) { |
| 64 | - log.warn("[start][MQTT 统一协议服务已经在运行中,请勿重复启动]"); | 63 | + log.warn("[start][MQTT 协议服务已经在运行中,请勿重复启动]"); |
| 65 | return; | 64 | return; |
| 66 | } | 65 | } |
| 67 | - log.info("[start][开始启动 MQTT 统一协议服务]"); | 66 | + log.info("[start][启动 MQTT 协议服务]"); |
| 68 | 67 | ||
| 69 | try { | 68 | try { |
| 70 | // 1. 创建共享的 Vertx 实例 | 69 | // 1. 创建共享的 Vertx 实例 |
| @@ -77,9 +76,9 @@ public class IotMqttUpstreamProtocol { | @@ -77,9 +76,9 @@ public class IotMqttUpstreamProtocol { | ||
| 77 | startMqttClient(); | 76 | startMqttClient(); |
| 78 | 77 | ||
| 79 | isRunning = true; | 78 | isRunning = true; |
| 80 | - log.info("[start][MQTT 统一协议服务启动完成]"); | 79 | + log.info("[start][MQTT 协议服务启动完成]"); |
| 81 | } catch (Exception e) { | 80 | } catch (Exception e) { |
| 82 | - log.error("[start][MQTT 统一协议服务启动失败]", e); | 81 | + log.error("[start][MQTT 协议服务启动失败]", e); |
| 83 | // 启动失败时清理资源 | 82 | // 启动失败时清理资源 |
| 84 | stop(); | 83 | stop(); |
| 85 | throw e; | 84 | throw e; |
| @@ -89,10 +88,10 @@ public class IotMqttUpstreamProtocol { | @@ -89,10 +88,10 @@ public class IotMqttUpstreamProtocol { | ||
| 89 | @PreDestroy | 88 | @PreDestroy |
| 90 | public void stop() { | 89 | public void stop() { |
| 91 | if (!isRunning) { | 90 | if (!isRunning) { |
| 92 | - log.warn("[stop][MQTT 统一协议服务已经停止,无需再次停止]"); | 91 | + log.warn("[stop][MQTT 协议服务已经停止,无需再次停止]"); |
| 93 | return; | 92 | return; |
| 94 | } | 93 | } |
| 95 | - log.info("[stop][开始停止 MQTT 统一协议服务]"); | 94 | + log.info("[stop][停止 MQTT 协议服务]"); |
| 96 | 95 | ||
| 97 | // 1. 停止 MQTT 客户端 | 96 | // 1. 停止 MQTT 客户端 |
| 98 | stopMqttClient(); | 97 | stopMqttClient(); |
| @@ -104,28 +103,28 @@ public class IotMqttUpstreamProtocol { | @@ -104,28 +103,28 @@ public class IotMqttUpstreamProtocol { | ||
| 104 | if (vertx != null) { | 103 | if (vertx != null) { |
| 105 | try { | 104 | try { |
| 106 | vertx.close(); | 105 | vertx.close(); |
| 107 | - log.info("[stop][Vertx 实例已关闭]"); | 106 | + log.debug("[stop][Vertx 实例已关闭]"); |
| 108 | } catch (Exception e) { | 107 | } catch (Exception e) { |
| 109 | log.warn("[stop][关闭 Vertx 实例失败]", e); | 108 | log.warn("[stop][关闭 Vertx 实例失败]", e); |
| 110 | } | 109 | } |
| 111 | } | 110 | } |
| 112 | 111 | ||
| 113 | isRunning = false; | 112 | isRunning = false; |
| 114 | - log.info("[stop][MQTT 统一协议服务已停止]"); | 113 | + log.info("[stop][MQTT 协议服务已停止]"); |
| 115 | } | 114 | } |
| 116 | 115 | ||
| 117 | /** | 116 | /** |
| 118 | * 启动 HTTP 认证服务 | 117 | * 启动 HTTP 认证服务 |
| 119 | */ | 118 | */ |
| 120 | private void startHttpAuthServer() { | 119 | private void startHttpAuthServer() { |
| 121 | - log.info("[startHttpAuthServer][开始启动 HTTP 认证服务]"); | 120 | + log.info("[startHttpAuthServer][启动 HTTP 认证服务]"); |
| 122 | 121 | ||
| 123 | // 创建路由 | 122 | // 创建路由 |
| 124 | Router router = Router.router(vertx); | 123 | Router router = Router.router(vertx); |
| 125 | router.route().handler(BodyHandler.create()); | 124 | router.route().handler(BodyHandler.create()); |
| 126 | 125 | ||
| 127 | // 创建认证处理器 | 126 | // 创建认证处理器 |
| 128 | - IotMqttHttpAuthHandler authHandler = new IotMqttHttpAuthHandler(); | 127 | + IotMqttHttpAuthHandler authHandler = new IotMqttHttpAuthHandler(this); |
| 129 | router.post(IotMqttTopicUtils.MQTT_AUTH_AUTHENTICATE_PATH).handler(authHandler::authenticate); | 128 | router.post(IotMqttTopicUtils.MQTT_AUTH_AUTHENTICATE_PATH).handler(authHandler::authenticate); |
| 130 | router.post(IotMqttTopicUtils.MQTT_AUTH_CONNECTED_PATH).handler(authHandler::connected); | 129 | router.post(IotMqttTopicUtils.MQTT_AUTH_CONNECTED_PATH).handler(authHandler::connected); |
| 131 | router.post(IotMqttTopicUtils.MQTT_AUTH_DISCONNECTED_PATH).handler(authHandler::disconnected); | 130 | router.post(IotMqttTopicUtils.MQTT_AUTH_DISCONNECTED_PATH).handler(authHandler::disconnected); |
| @@ -137,7 +136,7 @@ public class IotMqttUpstreamProtocol { | @@ -137,7 +136,7 @@ public class IotMqttUpstreamProtocol { | ||
| 137 | .requestHandler(router) | 136 | .requestHandler(router) |
| 138 | .listen(authPort) | 137 | .listen(authPort) |
| 139 | .result(); | 138 | .result(); |
| 140 | - log.info("[startHttpAuthServer][HTTP 认证服务启动成功,端口:{}]", authPort); | 139 | + log.info("[startHttpAuthServer][HTTP 认证服务启动成功, 端口: {}]", authPort); |
| 141 | } catch (Exception e) { | 140 | } catch (Exception e) { |
| 142 | log.error("[startHttpAuthServer][HTTP 认证服务启动失败]", e); | 141 | log.error("[startHttpAuthServer][HTTP 认证服务启动失败]", e); |
| 143 | throw e; | 142 | throw e; |
| @@ -163,23 +162,24 @@ public class IotMqttUpstreamProtocol { | @@ -163,23 +162,24 @@ public class IotMqttUpstreamProtocol { | ||
| 163 | * 启动 MQTT 客户端 | 162 | * 启动 MQTT 客户端 |
| 164 | */ | 163 | */ |
| 165 | private void startMqttClient() { | 164 | private void startMqttClient() { |
| 166 | - log.info("[startMqttClient][开始启动 MQTT 客户端]"); | 165 | + log.info("[startMqttClient][启动 MQTT 客户端]"); |
| 167 | 166 | ||
| 168 | - // 1. 初始化消息处理器 | ||
| 169 | - this.upstreamHandler = new IotMqttUpstreamHandler(this); | 167 | + try { |
| 168 | + // 1. 初始化消息处理器 | ||
| 169 | + this.upstreamHandler = new IotMqttUpstreamHandler(this); | ||
| 170 | 170 | ||
| 171 | - // 2. 创建 MQTT 客户端 | ||
| 172 | - log.info("[startMqttClient][使用 MQTT 客户端 ID: {}]", emqxProperties.getMqttClientId()); | 171 | + // 2. 创建 MQTT 客户端 |
| 172 | + log.info("[startMqttClient][使用 MQTT 客户端 ID: {}]", emqxProperties.getMqttClientId()); | ||
| 173 | + createMqttClient(); | ||
| 173 | 174 | ||
| 174 | - MqttClientOptions options = new MqttClientOptions() | ||
| 175 | - .setClientId(emqxProperties.getMqttClientId()) | ||
| 176 | - .setUsername(emqxProperties.getMqttUsername()) | ||
| 177 | - .setPassword(emqxProperties.getMqttPassword()) | ||
| 178 | - .setSsl(emqxProperties.getMqttSsl()); | ||
| 179 | - this.mqttClient = MqttClient.create(vertx, options); | 175 | + // 3. 连接 MQTT Broker(异步连接,不会抛出异常) |
| 176 | + connectMqtt(); | ||
| 180 | 177 | ||
| 181 | - // 3. 连接 MQTT Broker | ||
| 182 | - connectMqtt(); | 178 | + log.info("[startMqttClient][MQTT 客户端启动完成,正在异步连接中...]"); |
| 179 | + } catch (Exception e) { | ||
| 180 | + log.error("[startMqttClient][MQTT 客户端启动失败]", e); | ||
| 181 | + throw new RuntimeException("MQTT 客户端启动失败", e); | ||
| 182 | + } | ||
| 183 | } | 183 | } |
| 184 | 184 | ||
| 185 | /** | 185 | /** |
| @@ -195,7 +195,7 @@ public class IotMqttUpstreamProtocol { | @@ -195,7 +195,7 @@ public class IotMqttUpstreamProtocol { | ||
| 195 | mqttClient.unsubscribe(topic); | 195 | mqttClient.unsubscribe(topic); |
| 196 | log.debug("[stopMqttClient][取消订阅主题: {}]", topic); | 196 | log.debug("[stopMqttClient][取消订阅主题: {}]", topic); |
| 197 | } catch (Exception e) { | 197 | } catch (Exception e) { |
| 198 | - log.warn("[stopMqttClient][取消订阅主题异常: {}]", topic, e); | 198 | + log.warn("[stopMqttClient][取消订阅主题({})异常]", topic, e); |
| 199 | } | 199 | } |
| 200 | } | 200 | } |
| 201 | } | 201 | } |
| @@ -216,43 +216,107 @@ public class IotMqttUpstreamProtocol { | @@ -216,43 +216,107 @@ public class IotMqttUpstreamProtocol { | ||
| 216 | * 连接 MQTT Broker 并订阅主题 | 216 | * 连接 MQTT Broker 并订阅主题 |
| 217 | */ | 217 | */ |
| 218 | private void connectMqtt() { | 218 | private void connectMqtt() { |
| 219 | + connectMqtt(false); | ||
| 220 | + } | ||
| 221 | + | ||
| 222 | + /** | ||
| 223 | + * 连接 MQTT Broker 并订阅主题 | ||
| 224 | + * | ||
| 225 | + * @param isReconnect 是否为重连 | ||
| 226 | + */ | ||
| 227 | + private void connectMqtt(boolean isReconnect) { | ||
| 219 | // 1. 参数校验 | 228 | // 1. 参数校验 |
| 220 | String host = emqxProperties.getMqttHost(); | 229 | String host = emqxProperties.getMqttHost(); |
| 221 | Integer port = emqxProperties.getMqttPort(); | 230 | Integer port = emqxProperties.getMqttPort(); |
| 222 | if (StrUtil.isBlank(host)) { | 231 | if (StrUtil.isBlank(host)) { |
| 223 | - log.error("[connectMqtt][MQTT Host 为空,无法连接]"); | 232 | + log.error("[connectMqtt][MQTT Host 为空, 无法连接]"); |
| 224 | throw new IllegalArgumentException("MQTT Host 不能为空"); | 233 | throw new IllegalArgumentException("MQTT Host 不能为空"); |
| 225 | } | 234 | } |
| 226 | if (port == null || port <= 0) { | 235 | if (port == null || port <= 0) { |
| 227 | - log.error("[connectMqtt][MQTT Port 无效:{}]", port); | 236 | + log.error("[connectMqtt][MQTT Port({}) 无效]", port); |
| 228 | throw new IllegalArgumentException("MQTT Port 必须为正整数"); | 237 | throw new IllegalArgumentException("MQTT Port 必须为正整数"); |
| 229 | } | 238 | } |
| 230 | - log.info("[connectMqtt][开始连接 MQTT Broker][host: {}][port: {}]", host, port); | ||
| 231 | 239 | ||
| 232 | - // 2. 连接 | ||
| 233 | - try { | ||
| 234 | - mqttClient.connect(port, host) | ||
| 235 | - .toCompletionStage() | ||
| 236 | - .toCompletableFuture() | ||
| 237 | - .get(emqxProperties.getConnectTimeoutSeconds(), TimeUnit.SECONDS); | ||
| 238 | - log.info("[connectMqtt][MQTT 客户端连接成功][host: {}][port: {}]", host, port); | ||
| 239 | - | ||
| 240 | - // 3. 设置处理器 | ||
| 241 | - // 3.1 设置断开重连监听器 | ||
| 242 | - mqttClient.closeHandler(closeEvent -> { | ||
| 243 | - log.warn("[closeHandler][MQTT 连接已断开,准备重连]"); | ||
| 244 | - reconnectWithDelay(); | ||
| 245 | - }); | ||
| 246 | - // 3.2 设置消息处理器 | ||
| 247 | - mqttClient.publishHandler(upstreamHandler::handle); | ||
| 248 | - log.debug("[setupMessageHandler][MQTT 消息处理器设置完成]"); | 240 | + if (isReconnect) { |
| 241 | + log.info("[connectMqtt][开始重连 MQTT Broker, host: {}, port: {}]", host, port); | ||
| 242 | + // 重连时重新创建客户端实例 | ||
| 243 | + createMqttClient(); | ||
| 244 | + } else { | ||
| 245 | + log.info("[connectMqtt][开始连接 MQTT Broker, host: {}, port: {}]", host, port); | ||
| 246 | + } | ||
| 249 | 247 | ||
| 250 | - // 4. 订阅主题 | ||
| 251 | - subscribeToTopics(); | ||
| 252 | - } catch (Exception e) { | ||
| 253 | - log.error("[connectMqtt][连接 MQTT Broker 失败][host: {}][port: {}]", host, port, e); | ||
| 254 | - reconnectWithDelay(); // 连接失败时,也要尝试重连 | ||
| 255 | - throw new RuntimeException("MQTT 客户端启动失败", e); | 248 | + // 2. 异步连接 |
| 249 | + mqttClient.connect(port, host, connectResult -> { | ||
| 250 | + if (connectResult.succeeded()) { | ||
| 251 | + if (isReconnect) { | ||
| 252 | + log.info("[connectMqtt][MQTT 客户端重连成功, host: {}, port: {}]", host, port); | ||
| 253 | + } else { | ||
| 254 | + log.info("[connectMqtt][MQTT 客户端连接成功, host: {}, port: {}]", host, port); | ||
| 255 | + } | ||
| 256 | + | ||
| 257 | + // 3. 设置处理器 | ||
| 258 | + setupMqttHandlers(); | ||
| 259 | + | ||
| 260 | + // 4. 订阅主题 | ||
| 261 | + subscribeToTopics(); | ||
| 262 | + | ||
| 263 | + } else { | ||
| 264 | + log.error("[connectMqtt][连接 MQTT Broker 失败, host: {}, port: {}, isReconnect: {}]", | ||
| 265 | + host, port, isReconnect, connectResult.cause()); | ||
| 266 | + | ||
| 267 | + if (!isReconnect) { | ||
| 268 | + // 首次连接失败时,也要尝试重连 | ||
| 269 | + log.warn("[connectMqtt][首次连接失败,将开始重连机制]"); | ||
| 270 | + reconnectWithDelay(); | ||
| 271 | + } else { | ||
| 272 | + // 重连失败时,继续尝试重连 | ||
| 273 | + reconnectWithDelay(); | ||
| 274 | + } | ||
| 275 | + } | ||
| 276 | + }); | ||
| 277 | + } | ||
| 278 | + | ||
| 279 | + /** | ||
| 280 | + * 创建 MQTT 客户端 | ||
| 281 | + */ | ||
| 282 | + private void createMqttClient() { | ||
| 283 | + log.debug("[createMqttClient][创建 MQTT 客户端, clientId: {}]", emqxProperties.getMqttClientId()); | ||
| 284 | + | ||
| 285 | + MqttClientOptions options = new MqttClientOptions() | ||
| 286 | + .setClientId(emqxProperties.getMqttClientId()) | ||
| 287 | + .setUsername(emqxProperties.getMqttUsername()) | ||
| 288 | + .setPassword(emqxProperties.getMqttPassword()) | ||
| 289 | + .setSsl(emqxProperties.getMqttSsl()); | ||
| 290 | + | ||
| 291 | + this.mqttClient = MqttClient.create(vertx, options); | ||
| 292 | + } | ||
| 293 | + | ||
| 294 | + /** | ||
| 295 | + * 设置 MQTT 处理器 | ||
| 296 | + */ | ||
| 297 | + private void setupMqttHandlers() { | ||
| 298 | + if (mqttClient == null) { | ||
| 299 | + log.warn("[setupMqttHandlers][MQTT 客户端为空,跳过处理器设置]"); | ||
| 300 | + return; | ||
| 301 | + } | ||
| 302 | + | ||
| 303 | + // 设置断开重连监听器 | ||
| 304 | + mqttClient.closeHandler(closeEvent -> { | ||
| 305 | + log.warn("[closeHandler][MQTT 连接已断开, 准备重连]"); | ||
| 306 | + reconnectWithDelay(); | ||
| 307 | + }); | ||
| 308 | + | ||
| 309 | + // 设置异常处理器 | ||
| 310 | + mqttClient.exceptionHandler(exception -> { | ||
| 311 | + log.error("[exceptionHandler][MQTT 客户端异常]", exception); | ||
| 312 | + }); | ||
| 313 | + | ||
| 314 | + // 设置消息处理器 | ||
| 315 | + if (upstreamHandler != null) { | ||
| 316 | + mqttClient.publishHandler(upstreamHandler::handle); | ||
| 317 | + log.debug("[setupMqttHandlers][MQTT 消息处理器设置完成]"); | ||
| 318 | + } else { | ||
| 319 | + log.warn("[setupMqttHandlers][上行消息处理器为空,跳过设置]"); | ||
| 256 | } | 320 | } |
| 257 | } | 321 | } |
| 258 | 322 | ||
| @@ -261,15 +325,43 @@ public class IotMqttUpstreamProtocol { | @@ -261,15 +325,43 @@ public class IotMqttUpstreamProtocol { | ||
| 261 | */ | 325 | */ |
| 262 | private void subscribeToTopics() { | 326 | private void subscribeToTopics() { |
| 263 | List<String> topicList = emqxProperties.getMqttTopics(); | 327 | List<String> topicList = emqxProperties.getMqttTopics(); |
| 328 | + if (CollUtil.isEmpty(topicList)) { | ||
| 329 | + log.warn("[subscribeToTopics][订阅主题列表为空, 跳过订阅]"); | ||
| 330 | + return; | ||
| 331 | + } | ||
| 332 | + | ||
| 333 | + if (mqttClient == null || !mqttClient.isConnected()) { | ||
| 334 | + log.warn("[subscribeToTopics][MQTT 客户端未连接, 跳过订阅]"); | ||
| 335 | + return; | ||
| 336 | + } | ||
| 337 | + | ||
| 264 | int qos = emqxProperties.getMqttQos(); | 338 | int qos = emqxProperties.getMqttQos(); |
| 339 | + log.info("[subscribeToTopics][开始订阅主题, 共 {} 个, QoS: {}]", topicList.size(), qos); | ||
| 340 | + | ||
| 341 | + int[] successCount = {0}; // 使用数组以便在 lambda 中修改 | ||
| 342 | + int[] failCount = {0}; | ||
| 265 | 343 | ||
| 266 | - log.info("[subscribeToTopics][开始订阅主题,共 {} 个]", topicList.size()); | ||
| 267 | for (String topic : topicList) { | 344 | for (String topic : topicList) { |
| 268 | mqttClient.subscribe(topic, qos, subscribeResult -> { | 345 | mqttClient.subscribe(topic, qos, subscribeResult -> { |
| 269 | if (subscribeResult.succeeded()) { | 346 | if (subscribeResult.succeeded()) { |
| 270 | - log.info("[subscribeToTopics][订阅主题成功: {}][QoS: {}]", topic, qos); | 347 | + successCount[0]++; |
| 348 | + log.debug("[subscribeToTopics][订阅主题成功, topic: {}, qos: {}]", topic, qos); | ||
| 349 | + | ||
| 350 | + // 当所有主题都处理完成时,记录汇总日志 | ||
| 351 | + if (successCount[0] + failCount[0] == topicList.size()) { | ||
| 352 | + log.info("[subscribeToTopics][主题订阅完成, 成功: {}, 失败: {}, 总计: {}]", | ||
| 353 | + successCount[0], failCount[0], topicList.size()); | ||
| 354 | + } | ||
| 271 | } else { | 355 | } else { |
| 272 | - log.error("[subscribeToTopics][订阅主题失败: {}]", topic, subscribeResult.cause()); | 356 | + failCount[0]++; |
| 357 | + log.error("[subscribeToTopics][订阅主题失败, topic: {}, qos: {}, 原因: {}]", | ||
| 358 | + topic, qos, subscribeResult.cause().getMessage(), subscribeResult.cause()); | ||
| 359 | + | ||
| 360 | + // 当所有主题都处理完成时,记录汇总日志 | ||
| 361 | + if (successCount[0] + failCount[0] == topicList.size()) { | ||
| 362 | + log.info("[subscribeToTopics][主题订阅完成, 成功: {}, 失败: {}, 总计: {}]", | ||
| 363 | + successCount[0], failCount[0], topicList.size()); | ||
| 364 | + } | ||
| 273 | } | 365 | } |
| 274 | }); | 366 | }); |
| 275 | } | 367 | } |
| @@ -281,15 +373,21 @@ public class IotMqttUpstreamProtocol { | @@ -281,15 +373,21 @@ public class IotMqttUpstreamProtocol { | ||
| 281 | private void reconnectWithDelay() { | 373 | private void reconnectWithDelay() { |
| 282 | long delay = emqxProperties.getReconnectDelayMs(); | 374 | long delay = emqxProperties.getReconnectDelayMs(); |
| 283 | vertx.setTimer(delay, timerId -> { | 375 | vertx.setTimer(delay, timerId -> { |
| 284 | - if (!isRunning || (mqttClient != null && mqttClient.isConnected())) { | 376 | + if (!isRunning) { |
| 377 | + log.debug("[reconnectWithDelay][服务已停止, 取消重连]"); | ||
| 378 | + return; | ||
| 379 | + } | ||
| 380 | + // 检查连接状态,如果已连接则无需重连 | ||
| 381 | + if (mqttClient != null && mqttClient.isConnected()) { | ||
| 382 | + log.debug("[reconnectWithDelay][MQTT 客户端已连接, 无需重连]"); | ||
| 285 | return; | 383 | return; |
| 286 | } | 384 | } |
| 287 | - log.info("[reconnectWithDelay][开始重连 MQTT Broker,延迟 {} 毫秒]", delay); | 385 | + log.info("[reconnectWithDelay][开始重连 MQTT Broker, 延迟: {} ms]", delay); |
| 288 | try { | 386 | try { |
| 289 | - connectMqtt(); | 387 | + connectMqtt(true); // 标记为重连 |
| 290 | } catch (Exception e) { | 388 | } catch (Exception e) { |
| 291 | - log.error("[reconnectWithDelay][重连失败,将继续尝试重连]", e); | ||
| 292 | - reconnectWithDelay(); // 失败后,继续尝试 | 389 | + log.error("[reconnectWithDelay][重连失败, 将继续尝试]", e); |
| 390 | + // 重连失败时,不需要重复调用,因为 connectMqtt(true) 内部已经处理了重连逻辑 | ||
| 293 | } | 391 | } |
| 294 | }); | 392 | }); |
| 295 | } | 393 | } |
| @@ -302,7 +400,7 @@ public class IotMqttUpstreamProtocol { | @@ -302,7 +400,7 @@ public class IotMqttUpstreamProtocol { | ||
| 302 | */ | 400 | */ |
| 303 | public void publishMessage(String topic, String payload) { | 401 | public void publishMessage(String topic, String payload) { |
| 304 | if (mqttClient == null || !mqttClient.isConnected()) { | 402 | if (mqttClient == null || !mqttClient.isConnected()) { |
| 305 | - log.warn("[publishMessage][MQTT 客户端未连接,无法发布消息][topic: {}]", topic); | 403 | + log.warn("[publishMessage][MQTT 客户端未连接, 无法发布消息到 topic({})]", topic); |
| 306 | return; | 404 | return; |
| 307 | } | 405 | } |
| 308 | MqttQoS qos = MqttQoS.valueOf(emqxProperties.getMqttQos()); | 406 | MqttQoS qos = MqttQoS.valueOf(emqxProperties.getMqttQos()); |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/router/IotMqttDownstreamHandler.java
| @@ -41,14 +41,14 @@ public class IotMqttDownstreamHandler { | @@ -41,14 +41,14 @@ public class IotMqttDownstreamHandler { | ||
| 41 | // 1. 获取设备信息(使用缓存) | 41 | // 1. 获取设备信息(使用缓存) |
| 42 | IotDeviceRespDTO deviceInfo = deviceService.getDeviceFromCache(message.getDeviceId()); | 42 | IotDeviceRespDTO deviceInfo = deviceService.getDeviceFromCache(message.getDeviceId()); |
| 43 | if (deviceInfo == null) { | 43 | if (deviceInfo == null) { |
| 44 | - log.warn("[handle][设备信息不存在][deviceId: {}]", message.getDeviceId()); | 44 | + log.warn("[handle][设备信息不存在, deviceId: {}]", message.getDeviceId()); |
| 45 | return; | 45 | return; |
| 46 | } | 46 | } |
| 47 | 47 | ||
| 48 | // 2. 根据方法构建主题 | 48 | // 2. 根据方法构建主题 |
| 49 | String topic = buildTopicByMethod(message.getMethod(), deviceInfo.getProductKey(), deviceInfo.getDeviceName()); | 49 | String topic = buildTopicByMethod(message.getMethod(), deviceInfo.getProductKey(), deviceInfo.getDeviceName()); |
| 50 | if (StrUtil.isBlank(topic)) { | 50 | if (StrUtil.isBlank(topic)) { |
| 51 | - log.warn("[handle][未知的消息方法:{}]", message.getMethod()); | 51 | + log.warn("[handle][未知的消息方法: {}]", message.getMethod()); |
| 52 | return; | 52 | return; |
| 53 | } | 53 | } |
| 54 | 54 | ||
| @@ -57,7 +57,7 @@ public class IotMqttDownstreamHandler { | @@ -57,7 +57,7 @@ public class IotMqttDownstreamHandler { | ||
| 57 | 57 | ||
| 58 | // 4. 发布消息 | 58 | // 4. 发布消息 |
| 59 | protocol.publishMessage(topic, payload.toString()); | 59 | protocol.publishMessage(topic, payload.toString()); |
| 60 | - log.info("[handle][发布下行消息成功][method: {}][topic: {}]", message.getMethod(), topic); | 60 | + log.debug("[handle][发布下行消息成功, method: {}, topic: {}]", message.getMethod(), topic); |
| 61 | } | 61 | } |
| 62 | 62 | ||
| 63 | /** | 63 | /** |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/router/IotMqttHttpAuthHandler.java
| @@ -8,6 +8,7 @@ import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi; | @@ -8,6 +8,7 @@ import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi; | ||
| 8 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; | 8 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; |
| 9 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | 9 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 10 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; | 10 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; |
| 11 | +import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttUpstreamProtocol; | ||
| 11 | import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; | 12 | import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; |
| 12 | import io.vertx.core.json.JsonObject; | 13 | import io.vertx.core.json.JsonObject; |
| 13 | import io.vertx.ext.web.RoutingContext; | 14 | import io.vertx.ext.web.RoutingContext; |
| @@ -46,14 +47,27 @@ public class IotMqttHttpAuthHandler { | @@ -46,14 +47,27 @@ public class IotMqttHttpAuthHandler { | ||
| 46 | private static final int INTERNAL_ERROR_STATUS_CODE = 500; | 47 | private static final int INTERNAL_ERROR_STATUS_CODE = 500; |
| 47 | 48 | ||
| 48 | /** | 49 | /** |
| 50 | + * MQTT 协议实例,用于获取服务器ID | ||
| 51 | + */ | ||
| 52 | + private final IotMqttUpstreamProtocol protocol; | ||
| 53 | + | ||
| 54 | + /** | ||
| 55 | + * 构造器 | ||
| 56 | + * | ||
| 57 | + * @param protocol MQTT 协议实例 | ||
| 58 | + */ | ||
| 59 | + public IotMqttHttpAuthHandler(IotMqttUpstreamProtocol protocol) { | ||
| 60 | + this.protocol = protocol; | ||
| 61 | + } | ||
| 62 | + | ||
| 63 | + /** | ||
| 49 | * EMQX 认证接口 | 64 | * EMQX 认证接口 |
| 50 | */ | 65 | */ |
| 51 | public void authenticate(RoutingContext context) { | 66 | public void authenticate(RoutingContext context) { |
| 52 | try { | 67 | try { |
| 53 | // 解析请求体 | 68 | // 解析请求体 |
| 54 | - JsonObject body = context.body().asJsonObject(); | 69 | + JsonObject body = parseRequestBody(context); |
| 55 | if (body == null) { | 70 | if (body == null) { |
| 56 | - sendErrorResponse(context, 400, "请求体不能为空"); | ||
| 57 | return; | 71 | return; |
| 58 | } | 72 | } |
| 59 | 73 | ||
| @@ -61,34 +75,23 @@ public class IotMqttHttpAuthHandler { | @@ -61,34 +75,23 @@ public class IotMqttHttpAuthHandler { | ||
| 61 | String username = body.getString("username"); | 75 | String username = body.getString("username"); |
| 62 | String password = body.getString("password"); | 76 | String password = body.getString("password"); |
| 63 | 77 | ||
| 64 | - log.info("[authenticate][EMQX 设备认证请求][clientId: {}][username: {}]", clientid, username); | 78 | + log.debug("[authenticate][EMQX 设备认证, clientId: {}, username: {}]", clientid, username); |
| 65 | 79 | ||
| 66 | // 参数校验 | 80 | // 参数校验 |
| 67 | - if (StrUtil.isEmpty(clientid) || StrUtil.isEmpty(username) || StrUtil.isEmpty(password)) { | ||
| 68 | - log.warn("[authenticate][认证参数不完整][clientId: {}][username: {}]", clientid, username); | ||
| 69 | - sendErrorResponse(context, BAD_REQUEST_STATUS_CODE, "认证参数不完整"); | 81 | + if (!validateAuthParams(context, clientid, username, password)) { |
| 70 | return; | 82 | return; |
| 71 | } | 83 | } |
| 72 | 84 | ||
| 73 | // 执行设备认证 | 85 | // 执行设备认证 |
| 74 | - IotDeviceCommonApi deviceApi = SpringUtil.getBean(IotDeviceCommonApi.class); | ||
| 75 | - CommonResult<Boolean> result = deviceApi.authDevice(new IotDeviceAuthReqDTO() | ||
| 76 | - .setClientId(clientid) | ||
| 77 | - .setUsername(username) | ||
| 78 | - .setPassword(password)); | ||
| 79 | - | ||
| 80 | - result.checkError(); | ||
| 81 | - if (!BooleanUtil.isTrue(result.getData())) { | ||
| 82 | - log.warn("[authenticate][设备认证失败][clientId: {}][username: {}]", clientid, username); | ||
| 83 | - sendErrorResponse(context, UNAUTHORIZED_STATUS_CODE, DEVICE_AUTH_FAIL.getMsg()); | 86 | + if (!performDeviceAuth(context, clientid, username, password)) { |
| 84 | return; | 87 | return; |
| 85 | } | 88 | } |
| 86 | 89 | ||
| 87 | - log.info("[authenticate][设备认证成功][clientId: {}][username: {}]", clientid, username); | 90 | + log.debug("[authenticate][设备认证成功, clientId: {}, username: {}]", clientid, username); |
| 88 | sendSuccessResponse(context, "认证成功"); | 91 | sendSuccessResponse(context, "认证成功"); |
| 89 | 92 | ||
| 90 | } catch (Exception e) { | 93 | } catch (Exception e) { |
| 91 | - log.error("[authenticate][设备认证异常]", e); | 94 | + log.error("[authenticate][设备认证异常, 详细信息: {}]", e.getMessage(), e); |
| 92 | sendErrorResponse(context, INTERNAL_ERROR_STATUS_CODE, "认证服务异常"); | 95 | sendErrorResponse(context, INTERNAL_ERROR_STATUS_CODE, "认证服务异常"); |
| 93 | } | 96 | } |
| 94 | } | 97 | } |
| @@ -99,9 +102,8 @@ public class IotMqttHttpAuthHandler { | @@ -99,9 +102,8 @@ public class IotMqttHttpAuthHandler { | ||
| 99 | public void connected(RoutingContext context) { | 102 | public void connected(RoutingContext context) { |
| 100 | try { | 103 | try { |
| 101 | // 解析请求体 | 104 | // 解析请求体 |
| 102 | - JsonObject body = context.body().asJsonObject(); | 105 | + JsonObject body = parseRequestBody(context); |
| 103 | if (body == null) { | 106 | if (body == null) { |
| 104 | - sendErrorResponse(context, BAD_REQUEST_STATUS_CODE, "请求体不能为空"); | ||
| 105 | return; | 107 | return; |
| 106 | } | 108 | } |
| 107 | 109 | ||
| @@ -109,13 +111,14 @@ public class IotMqttHttpAuthHandler { | @@ -109,13 +111,14 @@ public class IotMqttHttpAuthHandler { | ||
| 109 | String username = body.getString("username"); | 111 | String username = body.getString("username"); |
| 110 | Long timestamp = body.getLong("timestamp"); | 112 | Long timestamp = body.getLong("timestamp"); |
| 111 | 113 | ||
| 112 | - log.info("[connected][设备连接事件][clientId: {}][username: {}]", clientid, username); | 114 | + log.debug("[connected][设备连接, clientId: {}, username: {}, timestamp: {}]", |
| 115 | + clientid, username, timestamp); | ||
| 113 | 116 | ||
| 114 | - handleDeviceStateChange(username, true); | 117 | + handleDeviceStateChange(username, true, "设备连接"); |
| 115 | sendSuccessResponse(context, "处理成功"); | 118 | sendSuccessResponse(context, "处理成功"); |
| 116 | 119 | ||
| 117 | } catch (Exception e) { | 120 | } catch (Exception e) { |
| 118 | - log.error("[connected][处理设备连接事件失败]", e); | 121 | + log.error("[connected][处理设备连接事件失败, 详细信息: {}]", e.getMessage(), e); |
| 119 | sendErrorResponse(context, INTERNAL_ERROR_STATUS_CODE, "处理失败"); | 122 | sendErrorResponse(context, INTERNAL_ERROR_STATUS_CODE, "处理失败"); |
| 120 | } | 123 | } |
| 121 | } | 124 | } |
| @@ -126,9 +129,8 @@ public class IotMqttHttpAuthHandler { | @@ -126,9 +129,8 @@ public class IotMqttHttpAuthHandler { | ||
| 126 | public void disconnected(RoutingContext context) { | 129 | public void disconnected(RoutingContext context) { |
| 127 | try { | 130 | try { |
| 128 | // 解析请求体 | 131 | // 解析请求体 |
| 129 | - JsonObject body = context.body().asJsonObject(); | 132 | + JsonObject body = parseRequestBody(context); |
| 130 | if (body == null) { | 133 | if (body == null) { |
| 131 | - sendErrorResponse(context, BAD_REQUEST_STATUS_CODE, "请求体不能为空"); | ||
| 132 | return; | 134 | return; |
| 133 | } | 135 | } |
| 134 | 136 | ||
| @@ -137,58 +139,144 @@ public class IotMqttHttpAuthHandler { | @@ -137,58 +139,144 @@ public class IotMqttHttpAuthHandler { | ||
| 137 | String reason = body.getString("reason"); | 139 | String reason = body.getString("reason"); |
| 138 | Long timestamp = body.getLong("timestamp"); | 140 | Long timestamp = body.getLong("timestamp"); |
| 139 | 141 | ||
| 140 | - log.info("[disconnected][设备断开连接事件][clientId: {}][username: {}][reason: {}]", | ||
| 141 | - clientid, username, reason); | 142 | + log.debug("[disconnected][设备断开连接, clientId: {}, username: {}, reason: {}, timestamp: {}]", |
| 143 | + clientid, username, reason, timestamp); | ||
| 142 | 144 | ||
| 143 | - handleDeviceStateChange(username, false); | 145 | + handleDeviceStateChange(username, false, "设备断开连接,原因:" + reason); |
| 144 | sendSuccessResponse(context, "处理成功"); | 146 | sendSuccessResponse(context, "处理成功"); |
| 145 | 147 | ||
| 146 | } catch (Exception e) { | 148 | } catch (Exception e) { |
| 147 | - log.error("[disconnected][处理设备断开连接事件失败]", e); | 149 | + log.error("[disconnected][处理设备断开连接事件失败, 详细信息: {}]", e.getMessage(), e); |
| 148 | sendErrorResponse(context, INTERNAL_ERROR_STATUS_CODE, "处理失败"); | 150 | sendErrorResponse(context, INTERNAL_ERROR_STATUS_CODE, "处理失败"); |
| 149 | } | 151 | } |
| 150 | } | 152 | } |
| 151 | 153 | ||
| 152 | /** | 154 | /** |
| 153 | - * 处理设备状态变化 | 155 | + * 解析请求体 |
| 156 | + * | ||
| 157 | + * @param context 路由上下文 | ||
| 158 | + * @return 请求体JSON对象,解析失败时返回null | ||
| 159 | + */ | ||
| 160 | + private JsonObject parseRequestBody(RoutingContext context) { | ||
| 161 | + try { | ||
| 162 | + JsonObject body = context.body().asJsonObject(); | ||
| 163 | + if (body == null) { | ||
| 164 | + log.warn("[parseRequestBody][请求体为空]"); | ||
| 165 | + sendErrorResponse(context, BAD_REQUEST_STATUS_CODE, "请求体不能为空"); | ||
| 166 | + return null; | ||
| 167 | + } | ||
| 168 | + return body; | ||
| 169 | + } catch (Exception e) { | ||
| 170 | + log.error("[parseRequestBody][解析请求体失败]", e); | ||
| 171 | + sendErrorResponse(context, BAD_REQUEST_STATUS_CODE, "请求体格式错误"); | ||
| 172 | + return null; | ||
| 173 | + } | ||
| 174 | + } | ||
| 175 | + | ||
| 176 | + /** | ||
| 177 | + * 验证认证参数 | ||
| 178 | + * | ||
| 179 | + * @param context 路由上下文 | ||
| 180 | + * @param clientid 客户端ID | ||
| 181 | + * @param username 用户名 | ||
| 182 | + * @param password 密码 | ||
| 183 | + * @return 验证是否通过 | ||
| 184 | + */ | ||
| 185 | + private boolean validateAuthParams(RoutingContext context, String clientid, String username, String password) { | ||
| 186 | + if (StrUtil.hasEmpty(clientid, username, password)) { | ||
| 187 | + log.warn("[validateAuthParams][认证参数不完整, clientId: {}, username: {}, password: {}]", | ||
| 188 | + clientid, username, StrUtil.isNotEmpty(password) ? "***" : "空"); | ||
| 189 | + sendErrorResponse(context, BAD_REQUEST_STATUS_CODE, "认证参数不完整"); | ||
| 190 | + return false; | ||
| 191 | + } | ||
| 192 | + return true; | ||
| 193 | + } | ||
| 194 | + | ||
| 195 | + /** | ||
| 196 | + * 执行设备认证 | ||
| 154 | * | 197 | * |
| 198 | + * @param context 路由上下文 | ||
| 199 | + * @param clientid 客户端ID | ||
| 155 | * @param username 用户名 | 200 | * @param username 用户名 |
| 156 | - * @param online 是否在线 | 201 | + * @param password 密码 |
| 202 | + * @return 认证是否成功 | ||
| 157 | */ | 203 | */ |
| 158 | - private void handleDeviceStateChange(String username, boolean online) { | 204 | + private boolean performDeviceAuth(RoutingContext context, String clientid, String username, String password) { |
| 205 | + try { | ||
| 206 | + IotDeviceCommonApi deviceApi = SpringUtil.getBean(IotDeviceCommonApi.class); | ||
| 207 | + CommonResult<Boolean> result = deviceApi.authDevice(new IotDeviceAuthReqDTO() | ||
| 208 | + .setClientId(clientid) | ||
| 209 | + .setUsername(username) | ||
| 210 | + .setPassword(password)); | ||
| 211 | + | ||
| 212 | + result.checkError(); | ||
| 213 | + if (!BooleanUtil.isTrue(result.getData())) { | ||
| 214 | + log.warn("[performDeviceAuth][设备认证失败, clientId: {}, username: {}]", clientid, username); | ||
| 215 | + sendErrorResponse(context, UNAUTHORIZED_STATUS_CODE, DEVICE_AUTH_FAIL.getMsg()); | ||
| 216 | + return false; | ||
| 217 | + } | ||
| 218 | + return true; | ||
| 219 | + } catch (Exception e) { | ||
| 220 | + log.error("[performDeviceAuth][设备认证异常, clientId: {}, username: {}]", clientid, username, e); | ||
| 221 | + sendErrorResponse(context, INTERNAL_ERROR_STATUS_CODE, "认证服务异常"); | ||
| 222 | + return false; | ||
| 223 | + } | ||
| 224 | + } | ||
| 225 | + | ||
| 226 | + /** | ||
| 227 | + * 处理设备状态变化 | ||
| 228 | + * | ||
| 229 | + * @param username 用户名 | ||
| 230 | + * @param online 是否在线 | ||
| 231 | + * @param actionDesc 操作描述 | ||
| 232 | + */ | ||
| 233 | + private void handleDeviceStateChange(String username, boolean online, String actionDesc) { | ||
| 159 | if (StrUtil.isEmpty(username) || "undefined".equals(username)) { | 234 | if (StrUtil.isEmpty(username) || "undefined".equals(username)) { |
| 160 | - log.warn("[handleDeviceStateChange][用户名为空或未定义][username: {}]", username); | 235 | + log.warn("[handleDeviceStateChange][用户名为空或'undefined', username: {}, action: {}]", |
| 236 | + username, actionDesc); | ||
| 161 | return; | 237 | return; |
| 162 | } | 238 | } |
| 163 | 239 | ||
| 164 | // 解析设备信息 | 240 | // 解析设备信息 |
| 165 | IotDeviceAuthUtils.DeviceInfo deviceInfo = IotDeviceAuthUtils.parseUsername(username); | 241 | IotDeviceAuthUtils.DeviceInfo deviceInfo = IotDeviceAuthUtils.parseUsername(username); |
| 166 | if (deviceInfo == null) { | 242 | if (deviceInfo == null) { |
| 167 | - log.warn("[handleDeviceStateChange][无法解析设备信息][username: {}]", username); | 243 | + log.warn("[handleDeviceStateChange][无法从 username({}) 解析设备信息, action: {}]", |
| 244 | + username, actionDesc); | ||
| 168 | return; | 245 | return; |
| 169 | } | 246 | } |
| 170 | 247 | ||
| 171 | try { | 248 | try { |
| 172 | - // 获取服务器 ID | ||
| 173 | - String serverId = "mqtt_auth_gateway"; | 249 | + // 从协议实例获取服务器 ID |
| 250 | + String serverId = protocol.getServerId(); | ||
| 251 | + if (StrUtil.isEmpty(serverId)) { | ||
| 252 | + log.error("[handleDeviceStateChange][获取服务器ID失败, username: {}, action: {}]", | ||
| 253 | + username, actionDesc); | ||
| 254 | + return; | ||
| 255 | + } | ||
| 174 | 256 | ||
| 175 | // 构建设备状态消息 | 257 | // 构建设备状态消息 |
| 176 | IotDeviceMessageService deviceMessageService = SpringUtil.getBean(IotDeviceMessageService.class); | 258 | IotDeviceMessageService deviceMessageService = SpringUtil.getBean(IotDeviceMessageService.class); |
| 177 | IotDeviceMessage message; | 259 | IotDeviceMessage message; |
| 178 | if (online) { | 260 | if (online) { |
| 179 | message = IotDeviceMessage.buildStateOnline(); | 261 | message = IotDeviceMessage.buildStateOnline(); |
| 180 | - log.info("[handleDeviceStateChange][发送设备上线消息成功][username: {}]", username); | 262 | + log.debug("[handleDeviceStateChange][发送设备上线消息, username: {}, serverId: {}]", |
| 263 | + username, serverId); | ||
| 181 | } else { | 264 | } else { |
| 182 | message = IotDeviceMessage.buildStateOffline(); | 265 | message = IotDeviceMessage.buildStateOffline(); |
| 183 | - log.info("[handleDeviceStateChange][发送设备下线消息成功][username: {}]", username); | 266 | + log.debug("[handleDeviceStateChange][发送设备下线消息, username: {}, serverId: {}]", |
| 267 | + username, serverId); | ||
| 184 | } | 268 | } |
| 185 | 269 | ||
| 186 | // 发送消息到消息总线 | 270 | // 发送消息到消息总线 |
| 187 | deviceMessageService.sendDeviceMessage(message, | 271 | deviceMessageService.sendDeviceMessage(message, |
| 188 | deviceInfo.getProductKey(), deviceInfo.getDeviceName(), serverId); | 272 | deviceInfo.getProductKey(), deviceInfo.getDeviceName(), serverId); |
| 273 | + | ||
| 274 | + log.info("[handleDeviceStateChange][{}处理成功, productKey: {}, deviceName: {}, serverId: {}]", | ||
| 275 | + actionDesc, deviceInfo.getProductKey(), deviceInfo.getDeviceName(), serverId); | ||
| 276 | + | ||
| 189 | } catch (Exception e) { | 277 | } catch (Exception e) { |
| 190 | - log.error("[handleDeviceStateChange][发送设备状态消息失败][username: {}][online: {}]", | ||
| 191 | - username, online, e); | 278 | + log.error("[handleDeviceStateChange][发送设备状态消息失败, username: {}, online: {}, action: {}]", |
| 279 | + username, online, actionDesc, e); | ||
| 192 | } | 280 | } |
| 193 | } | 281 | } |
| 194 | 282 |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/router/IotMqttUpstreamHandler.java
| @@ -9,6 +9,8 @@ import io.vertx.mqtt.messages.MqttPublishMessage; | @@ -9,6 +9,8 @@ import io.vertx.mqtt.messages.MqttPublishMessage; | ||
| 9 | import lombok.extern.slf4j.Slf4j; | 9 | import lombok.extern.slf4j.Slf4j; |
| 10 | import org.springframework.util.Assert; | 10 | import org.springframework.util.Assert; |
| 11 | 11 | ||
| 12 | +import java.util.Arrays; | ||
| 13 | + | ||
| 12 | /** | 14 | /** |
| 13 | * IoT 网关 MQTT 上行消息处理器 | 15 | * IoT 网关 MQTT 上行消息处理器 |
| 14 | * | 16 | * |
| @@ -32,12 +34,12 @@ public class IotMqttUpstreamHandler { | @@ -32,12 +34,12 @@ public class IotMqttUpstreamHandler { | ||
| 32 | String topic = message.topicName(); | 34 | String topic = message.topicName(); |
| 33 | byte[] payload = message.payload().getBytes(); | 35 | byte[] payload = message.payload().getBytes(); |
| 34 | 36 | ||
| 35 | - log.debug("[handle][收到 MQTT 消息][topic: {}]", topic); | 37 | + log.debug("[handle][收到 MQTT 消息, topic: {}]", topic); |
| 36 | 38 | ||
| 37 | try { | 39 | try { |
| 38 | // 1. 前置校验 | 40 | // 1. 前置校验 |
| 39 | if (StrUtil.isBlank(topic)) { | 41 | if (StrUtil.isBlank(topic)) { |
| 40 | - log.warn("[validateInput][主题为空,忽略消息]"); | 42 | + log.warn("[handle][主题为空, 忽略消息]"); |
| 41 | return; | 43 | return; |
| 42 | } | 44 | } |
| 43 | // 注意:payload 可以为空 | 45 | // 注意:payload 可以为空 |
| @@ -45,7 +47,7 @@ public class IotMqttUpstreamHandler { | @@ -45,7 +47,7 @@ public class IotMqttUpstreamHandler { | ||
| 45 | // 2. 识别并验证消息类型 | 47 | // 2. 识别并验证消息类型 |
| 46 | String messageType = getMessageType(topic); | 48 | String messageType = getMessageType(topic); |
| 47 | Assert.notNull(messageType, String.format("未知的消息类型, topic(%s)", topic)); | 49 | Assert.notNull(messageType, String.format("未知的消息类型, topic(%s)", topic)); |
| 48 | - log.info("[handle][接收到{}][topic: {}]", messageType, topic); | 50 | + log.debug("[handle][接收到上行消息({}), topic: {}]", messageType, topic); |
| 49 | 51 | ||
| 50 | // 3. 解析主题,获取 productKey 和 deviceName | 52 | // 3. 解析主题,获取 productKey 和 deviceName |
| 51 | String[] topicParts = topic.split("/"); | 53 | String[] topicParts = topic.split("/"); |
| @@ -72,7 +74,7 @@ public class IotMqttUpstreamHandler { | @@ -72,7 +74,7 @@ public class IotMqttUpstreamHandler { | ||
| 72 | deviceMessageService.sendDeviceMessage(deviceMessage, productKey, deviceName, serverId); | 74 | deviceMessageService.sendDeviceMessage(deviceMessage, productKey, deviceName, serverId); |
| 73 | 75 | ||
| 74 | // 6. 记录成功日志 | 76 | // 6. 记录成功日志 |
| 75 | - log.info("[handle][处理{}成功,已转发到 MQ][topic: {}]", messageType, topic); | 77 | + log.debug("[handle][处理上行消息({})成功, topic: {}]", messageType, topic); |
| 76 | } catch (Exception e) { | 78 | } catch (Exception e) { |
| 77 | log.error("[handle][处理 MQTT 消息失败][topic: {}][payload: {}]", topic, new String(payload), e); | 79 | log.error("[handle][处理 MQTT 消息失败][topic: {}][payload: {}]", topic, new String(payload), e); |
| 78 | } | 80 | } |
| @@ -86,10 +88,13 @@ public class IotMqttUpstreamHandler { | @@ -86,10 +88,13 @@ public class IotMqttUpstreamHandler { | ||
| 86 | */ | 88 | */ |
| 87 | private String getMessageType(String topic) { | 89 | private String getMessageType(String topic) { |
| 88 | String[] topicParts = topic.split("/"); | 90 | String[] topicParts = topic.split("/"); |
| 89 | - if (topicParts.length < 7) { | ||
| 90 | - return null; | 91 | + // 约定:topic 第 4 个部分开始为消息类型 |
| 92 | + // 例如:/sys/{productKey}/{deviceName}/thing/property/post -> | ||
| 93 | + // thing/property/post | ||
| 94 | + if (topicParts.length > 4) { | ||
| 95 | + return String.join("/", Arrays.copyOfRange(topicParts, 4, topicParts.length)); | ||
| 91 | } | 96 | } |
| 92 | - return topicParts[3]; | 97 | + return topicParts[topicParts.length - 1]; |
| 93 | } | 98 | } |
| 94 | 99 | ||
| 95 | } | 100 | } |
| 96 | \ No newline at end of file | 101 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-gateway/src/main/resources/application-local.yaml
| @@ -37,6 +37,7 @@ yudao: | @@ -37,6 +37,7 @@ yudao: | ||
| 37 | mqtt-port: 1883 # MQTT Broker 端口 | 37 | mqtt-port: 1883 # MQTT Broker 端口 |
| 38 | mqtt-username: admin # MQTT 用户名 | 38 | mqtt-username: admin # MQTT 用户名 |
| 39 | mqtt-password: public # MQTT 密码 | 39 | mqtt-password: public # MQTT 密码 |
| 40 | + mqtt-client-id: iot-gateway-mqtt # MQTT 客户端 ID | ||
| 40 | mqtt-ssl: false # 是否开启 SSL | 41 | mqtt-ssl: false # 是否开启 SSL |
| 41 | mqtt-topics: | 42 | mqtt-topics: |
| 42 | - "/sys/#" # 系统主题 | 43 | - "/sys/#" # 系统主题 |