Commit 4015e7905f3fcd0590f91ada1b3920a06649b7d3
1 parent
53697b55
【功能完善】IoT: 更新 MQTT 客户端逻辑,重构消息处理和重连机制,优化配置文件
Showing
3 changed files
with
88 additions
and
66 deletions
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/src/main/java/cn/iocoder/yudao/module/iot/plugin/emqx/upstream/IotDeviceUpstreamServer.java
| 1 | 1 | package cn.iocoder.yudao.module.iot.plugin.emqx.upstream; |
| 2 | 2 | |
| 3 | -import cn.hutool.core.date.DateUtil; | |
| 3 | +import cn.hutool.core.util.IdUtil; | |
| 4 | 4 | import cn.hutool.json.JSONObject; |
| 5 | 5 | import cn.hutool.json.JSONUtil; |
| 6 | 6 | import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi; |
| ... | ... | @@ -19,7 +19,6 @@ import io.vertx.mqtt.MqttClientOptions; |
| 19 | 19 | import lombok.extern.slf4j.Slf4j; |
| 20 | 20 | |
| 21 | 21 | import java.time.LocalDateTime; |
| 22 | -import java.util.UUID; | |
| 23 | 22 | |
| 24 | 23 | /** |
| 25 | 24 | * IoT 设备下行服务端,接收来自 device 设备的请求,转发给 server 服务器 |
| ... | ... | @@ -31,6 +30,10 @@ import java.util.UUID; |
| 31 | 30 | @Slf4j |
| 32 | 31 | public class IotDeviceUpstreamServer { |
| 33 | 32 | |
| 33 | + private static final String PROPERTY_POST_TOPIC = "/event/property/post"; | |
| 34 | + private static final int RECONNECT_DELAY = 5000; // 重连延迟时间(毫秒) | |
| 35 | + private static final int QOS_LEVEL = 1; | |
| 36 | + | |
| 34 | 37 | private final Vertx vertx; |
| 35 | 38 | private final HttpServer server; |
| 36 | 39 | private final MqttClient client; |
| ... | ... | @@ -55,7 +58,7 @@ public class IotDeviceUpstreamServer { |
| 55 | 58 | |
| 56 | 59 | // 创建 MQTT 客户端 |
| 57 | 60 | MqttClientOptions options = new MqttClientOptions() |
| 58 | - .setClientId("yudao-iot-server-" + UUID.randomUUID()) | |
| 61 | + .setClientId("yudao-iot-server-" + IdUtil.fastSimpleUUID()) | |
| 59 | 62 | .setUsername(emqxProperties.getMqttUsername()) |
| 60 | 63 | .setPassword(emqxProperties.getMqttPassword()) |
| 61 | 64 | .setSsl(emqxProperties.isMqttSsl()); |
| ... | ... | @@ -80,44 +83,24 @@ public class IotDeviceUpstreamServer { |
| 80 | 83 | // 3. 添加 MQTT 断开重连监听器 |
| 81 | 84 | client.closeHandler(v -> { |
| 82 | 85 | log.warn("[closeHandler][MQTT 连接已断开,准备重连]"); |
| 83 | - // 等待 5 秒后重连,避免频繁重连 | |
| 84 | - vertx.setTimer(5000, id -> { | |
| 85 | - log.info("[closeHandler][开始重新连接 MQTT]"); | |
| 86 | - connectMqtt(); | |
| 87 | - }); | |
| 86 | + reconnectWithDelay(); | |
| 88 | 87 | }); |
| 89 | 88 | |
| 90 | 89 | // 4. 设置 MQTT 消息处理器 |
| 90 | + setupMessageHandler(); | |
| 91 | + } | |
| 92 | + | |
| 93 | + /** | |
| 94 | + * 设置 MQTT 消息处理器 | |
| 95 | + */ | |
| 96 | + private void setupMessageHandler() { | |
| 91 | 97 | client.publishHandler(message -> { |
| 92 | 98 | String topic = message.topicName(); |
| 93 | 99 | String payload = message.payload().toString(); |
| 94 | 100 | log.info("[messageHandler][接收到消息][topic: {}][payload: {}]", topic, payload); |
| 95 | 101 | |
| 96 | 102 | try { |
| 97 | - // 4.1 处理设备属性上报消息: /{productKey}/{deviceName}/event/property/post | |
| 98 | - if (topic.contains("/event/property/post")) { | |
| 99 | - // 4.2 解析消息内容 | |
| 100 | - JSONObject jsonObject = JSONUtil.parseObj(payload); | |
| 101 | - String requestId = jsonObject.getStr("id"); | |
| 102 | - Long timestamp = jsonObject.getLong("timestamp"); | |
| 103 | - | |
| 104 | - // 4.3 从 topic 中解析设备标识 | |
| 105 | - String[] topicParts = topic.split("/"); | |
| 106 | - String productKey = topicParts[1]; | |
| 107 | - String deviceName = topicParts[2]; | |
| 108 | - | |
| 109 | - // 4.4 构建设备属性上报请求对象 | |
| 110 | - IotDevicePropertyReportReqDTO devicePropertyReportReqDTO = ((IotDevicePropertyReportReqDTO) new IotDevicePropertyReportReqDTO() | |
| 111 | - .setRequestId(requestId) | |
| 112 | - .setProcessId(IotPluginCommonUtils.getProcessId()).setReportTime(LocalDateTime.now()) | |
| 113 | - .setProductKey(productKey).setDeviceName(deviceName)) | |
| 114 | - .setProperties(jsonObject.getJSONObject("params")); | |
| 115 | - | |
| 116 | - // 4.5 调用上游 API 处理设备上报数据 | |
| 117 | - deviceUpstreamApi.reportDeviceProperty(devicePropertyReportReqDTO); | |
| 118 | - log.info("[messageHandler][处理设备上行消息成功][topic: {}][devicePropertyReportReqDTO: {}]", | |
| 119 | - topic, JSONUtil.toJsonStr(devicePropertyReportReqDTO)); | |
| 120 | - } | |
| 103 | + handleMessage(topic, payload); | |
| 121 | 104 | } catch (Exception e) { |
| 122 | 105 | log.error("[messageHandler][处理消息失败][topic: {}][payload: {}]", topic, payload, e); |
| 123 | 106 | } |
| ... | ... | @@ -125,34 +108,90 @@ public class IotDeviceUpstreamServer { |
| 125 | 108 | } |
| 126 | 109 | |
| 127 | 110 | /** |
| 111 | + * 处理 MQTT 消息 | |
| 112 | + */ | |
| 113 | + private void handleMessage(String topic, String payload) { | |
| 114 | + // 处理设备属性上报消息 | |
| 115 | + if (topic.contains(PROPERTY_POST_TOPIC)) { | |
| 116 | + handlePropertyPost(topic, payload); | |
| 117 | + } | |
| 118 | + } | |
| 119 | + | |
| 120 | + /** | |
| 121 | + * 处理设备属性上报 | |
| 122 | + */ | |
| 123 | + private void handlePropertyPost(String topic, String payload) { | |
| 124 | + // 解析消息内容 | |
| 125 | + JSONObject jsonObject = JSONUtil.parseObj(payload); | |
| 126 | + String[] topicParts = topic.split("/"); | |
| 127 | + | |
| 128 | + // 构建设备属性上报请求对象 | |
| 129 | + IotDevicePropertyReportReqDTO reportReqDTO = buildPropertyReportDTO( | |
| 130 | + jsonObject, | |
| 131 | + topicParts[1], // productKey | |
| 132 | + topicParts[2] // deviceName | |
| 133 | + ); | |
| 134 | + | |
| 135 | + // 调用上游 API 处理设备上报数据 | |
| 136 | + deviceUpstreamApi.reportDeviceProperty(reportReqDTO); | |
| 137 | + log.info("[handlePropertyPost][处理设备上行消息成功][topic: {}][reportReqDTO: {}]", | |
| 138 | + topic, JSONUtil.toJsonStr(reportReqDTO)); | |
| 139 | + } | |
| 140 | + | |
| 141 | + /** | |
| 142 | + * 构建设备属性上报请求对象 | |
| 143 | + */ | |
| 144 | + private IotDevicePropertyReportReqDTO buildPropertyReportDTO(JSONObject jsonObject, | |
| 145 | + String productKey, | |
| 146 | + String deviceName) { | |
| 147 | + return ((IotDevicePropertyReportReqDTO) new IotDevicePropertyReportReqDTO() | |
| 148 | + .setRequestId(jsonObject.getStr("id")) | |
| 149 | + .setProcessId(IotPluginCommonUtils.getProcessId()) | |
| 150 | + .setReportTime(LocalDateTime.now()) | |
| 151 | + .setProductKey(productKey) | |
| 152 | + .setDeviceName(deviceName)) | |
| 153 | + .setProperties(jsonObject.getJSONObject("params")); | |
| 154 | + } | |
| 155 | + | |
| 156 | + /** | |
| 157 | + * 重连 MQTT 客户端 | |
| 158 | + */ | |
| 159 | + private void reconnectWithDelay() { | |
| 160 | + vertx.setTimer(RECONNECT_DELAY, id -> { | |
| 161 | + log.info("[reconnectWithDelay][开始重新连接 MQTT]"); | |
| 162 | + connectMqtt(); | |
| 163 | + }); | |
| 164 | + } | |
| 165 | + | |
| 166 | + /** | |
| 128 | 167 | * 连接 MQTT Broker 并订阅主题 |
| 129 | 168 | */ |
| 130 | 169 | private void connectMqtt() { |
| 131 | - // 连接 MQTT Broker | |
| 132 | 170 | client.connect(emqxProperties.getMqttPort(), emqxProperties.getMqttHost()) |
| 133 | 171 | .onSuccess(connAck -> { |
| 134 | 172 | log.info("[connectMqtt][MQTT客户端连接成功]"); |
| 135 | - // 连接成功后订阅主题 | |
| 136 | - String mqttTopics = emqxProperties.getMqttTopics(); | |
| 137 | - String[] topics = mqttTopics.split(","); | |
| 138 | - for (String topic : topics) { | |
| 139 | - client.subscribe(topic, 1) | |
| 140 | - .onSuccess(v -> log.info("[connectMqtt][成功订阅主题: {}]", topic)) | |
| 141 | - .onFailure(err -> log.error("[connectMqtt][订阅主题失败: {}]", topic, err)); | |
| 142 | - } | |
| 143 | - log.info("[connectMqtt][开始订阅设备上行消息主题]"); | |
| 173 | + subscribeToTopics(); | |
| 144 | 174 | }) |
| 145 | 175 | .onFailure(err -> { |
| 146 | 176 | log.error("[connectMqtt][连接 MQTT Broker 失败]", err); |
| 147 | - // 连接失败后,等待 5 秒重试 | |
| 148 | - vertx.setTimer(5000, id -> { | |
| 149 | - log.info("[connectMqtt][准备重新连接 MQTT]"); | |
| 150 | - connectMqtt(); | |
| 151 | - }); | |
| 177 | + reconnectWithDelay(); | |
| 152 | 178 | }); |
| 153 | 179 | } |
| 154 | 180 | |
| 155 | 181 | /** |
| 182 | + * 订阅设备上行消息主题 | |
| 183 | + */ | |
| 184 | + private void subscribeToTopics() { | |
| 185 | + String[] topics = emqxProperties.getMqttTopics().split(","); | |
| 186 | + for (String topic : topics) { | |
| 187 | + client.subscribe(topic, QOS_LEVEL) | |
| 188 | + .onSuccess(v -> log.info("[subscribeToTopics][成功订阅主题: {}]", topic)) | |
| 189 | + .onFailure(err -> log.error("[subscribeToTopics][订阅主题失败: {}]", topic, err)); | |
| 190 | + } | |
| 191 | + log.info("[subscribeToTopics][开始订阅设备上行消息主题]"); | |
| 192 | + } | |
| 193 | + | |
| 194 | + /** | |
| 156 | 195 | * 停止所有 |
| 157 | 196 | */ |
| 158 | 197 | public void stop() { |
| ... | ... | @@ -187,4 +226,4 @@ public class IotDeviceUpstreamServer { |
| 187 | 226 | throw new RuntimeException(e); |
| 188 | 227 | } |
| 189 | 228 | } |
| 190 | 229 | -} |
| 230 | +} | |
| 191 | 231 | \ No newline at end of file | ... | ... |
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/src/main/resources/application.yml
yudao-server/src/main/resources/application-local.yaml
| ... | ... | @@ -267,23 +267,6 @@ justauth: |
| 267 | 267 | timeout: 24h # 超时时长,目前只对 Redis 缓存生效,默认 3 分钟 |
| 268 | 268 | |
| 269 | 269 | --- #################### iot相关配置 TODO 芋艿:再瞅瞅 #################### |
| 270 | -iot: | |
| 271 | - emq: | |
| 272 | - # 账号 | |
| 273 | - username: haohao | |
| 274 | - # 密码 | |
| 275 | - password: ahh@123456 | |
| 276 | - # 主机地址 | |
| 277 | - hostUrl: tcp://chaojiniu.top:1883 | |
| 278 | - # 客户端Id,不能相同,采用随机数 ${random.value} | |
| 279 | - client-id: ${random.int} | |
| 280 | - # 默认主题 | |
| 281 | - default-topic: test | |
| 282 | - # 保持连接 | |
| 283 | - keepalive: 60 | |
| 284 | - # 清除会话(设置为false,断开连接,重连后使用原来的会话 保留订阅的主题,能接收离线期间的消息) | |
| 285 | - clearSession: true | |
| 286 | - | |
| 287 | 270 | pf4j: |
| 288 | 271 | # pluginsDir: /tmp/ |
| 289 | 272 | pluginsDir: ../plugins |
| 290 | 273 | \ No newline at end of file | ... | ... |