Commit 4015e7905f3fcd0590f91ada1b3920a06649b7d3

Authored by 安浩浩
1 parent 53697b55

【功能完善】IoT: 更新 MQTT 客户端逻辑,重构消息处理和重连机制,优化配置文件

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 package cn.iocoder.yudao.module.iot.plugin.emqx.upstream; 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 import cn.hutool.json.JSONObject; 4 import cn.hutool.json.JSONObject;
5 import cn.hutool.json.JSONUtil; 5 import cn.hutool.json.JSONUtil;
6 import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi; 6 import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
@@ -19,7 +19,6 @@ import io.vertx.mqtt.MqttClientOptions; @@ -19,7 +19,6 @@ import io.vertx.mqtt.MqttClientOptions;
19 import lombok.extern.slf4j.Slf4j; 19 import lombok.extern.slf4j.Slf4j;
20 20
21 import java.time.LocalDateTime; 21 import java.time.LocalDateTime;
22 -import java.util.UUID;  
23 22
24 /** 23 /**
25 * IoT 设备下行服务端,接收来自 device 设备的请求,转发给 server 服务器 24 * IoT 设备下行服务端,接收来自 device 设备的请求,转发给 server 服务器
@@ -31,6 +30,10 @@ import java.util.UUID; @@ -31,6 +30,10 @@ import java.util.UUID;
31 @Slf4j 30 @Slf4j
32 public class IotDeviceUpstreamServer { 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 private final Vertx vertx; 37 private final Vertx vertx;
35 private final HttpServer server; 38 private final HttpServer server;
36 private final MqttClient client; 39 private final MqttClient client;
@@ -55,7 +58,7 @@ public class IotDeviceUpstreamServer { @@ -55,7 +58,7 @@ public class IotDeviceUpstreamServer {
55 58
56 // 创建 MQTT 客户端 59 // 创建 MQTT 客户端
57 MqttClientOptions options = new MqttClientOptions() 60 MqttClientOptions options = new MqttClientOptions()
58 - .setClientId("yudao-iot-server-" + UUID.randomUUID()) 61 + .setClientId("yudao-iot-server-" + IdUtil.fastSimpleUUID())
59 .setUsername(emqxProperties.getMqttUsername()) 62 .setUsername(emqxProperties.getMqttUsername())
60 .setPassword(emqxProperties.getMqttPassword()) 63 .setPassword(emqxProperties.getMqttPassword())
61 .setSsl(emqxProperties.isMqttSsl()); 64 .setSsl(emqxProperties.isMqttSsl());
@@ -80,44 +83,24 @@ public class IotDeviceUpstreamServer { @@ -80,44 +83,24 @@ public class IotDeviceUpstreamServer {
80 // 3. 添加 MQTT 断开重连监听器 83 // 3. 添加 MQTT 断开重连监听器
81 client.closeHandler(v -> { 84 client.closeHandler(v -> {
82 log.warn("[closeHandler][MQTT 连接已断开,准备重连]"); 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 // 4. 设置 MQTT 消息处理器 89 // 4. 设置 MQTT 消息处理器
  90 + setupMessageHandler();
  91 + }
  92 +
  93 + /**
  94 + * 设置 MQTT 消息处理器
  95 + */
  96 + private void setupMessageHandler() {
91 client.publishHandler(message -> { 97 client.publishHandler(message -> {
92 String topic = message.topicName(); 98 String topic = message.topicName();
93 String payload = message.payload().toString(); 99 String payload = message.payload().toString();
94 log.info("[messageHandler][接收到消息][topic: {}][payload: {}]", topic, payload); 100 log.info("[messageHandler][接收到消息][topic: {}][payload: {}]", topic, payload);
95 101
96 try { 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 } catch (Exception e) { 104 } catch (Exception e) {
122 log.error("[messageHandler][处理消息失败][topic: {}][payload: {}]", topic, payload, e); 105 log.error("[messageHandler][处理消息失败][topic: {}][payload: {}]", topic, payload, e);
123 } 106 }
@@ -125,34 +108,90 @@ public class IotDeviceUpstreamServer { @@ -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 * 连接 MQTT Broker 并订阅主题 167 * 连接 MQTT Broker 并订阅主题
129 */ 168 */
130 private void connectMqtt() { 169 private void connectMqtt() {
131 - // 连接 MQTT Broker  
132 client.connect(emqxProperties.getMqttPort(), emqxProperties.getMqttHost()) 170 client.connect(emqxProperties.getMqttPort(), emqxProperties.getMqttHost())
133 .onSuccess(connAck -> { 171 .onSuccess(connAck -> {
134 log.info("[connectMqtt][MQTT客户端连接成功]"); 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 .onFailure(err -> { 175 .onFailure(err -> {
146 log.error("[connectMqtt][连接 MQTT Broker 失败]", err); 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 public void stop() { 197 public void stop() {
@@ -187,4 +226,4 @@ public class IotDeviceUpstreamServer { @@ -187,4 +226,4 @@ public class IotDeviceUpstreamServer {
187 throw new RuntimeException(e); 226 throw new RuntimeException(e);
188 } 227 }
189 } 228 }
190 -} 229 -}
  230 +}
191 \ No newline at end of file 231 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/src/main/resources/application.yml
@@ -14,6 +14,6 @@ yudao: @@ -14,6 +14,6 @@ yudao:
14 mqtt-port: 1883 14 mqtt-port: 1883
15 mqtt-ssl: false 15 mqtt-ssl: false
16 mqtt-username: yudao 16 mqtt-username: yudao
17 - mqtt-password: yudao 17 + mqtt-password: 123456
18 mqtt-topics: "/+/#" 18 mqtt-topics: "/+/#"
19 auth-port: 8101 19 auth-port: 8101
yudao-server/src/main/resources/application-local.yaml
@@ -267,23 +267,6 @@ justauth: @@ -267,23 +267,6 @@ justauth:
267 timeout: 24h # 超时时长,目前只对 Redis 缓存生效,默认 3 分钟 267 timeout: 24h # 超时时长,目前只对 Redis 缓存生效,默认 3 分钟
268 268
269 --- #################### iot相关配置 TODO 芋艿:再瞅瞅 #################### 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 pf4j: 270 pf4j:
288 # pluginsDir: /tmp/ 271 # pluginsDir: /tmp/
289 pluginsDir: ../plugins 272 pluginsDir: ../plugins
290 \ No newline at end of file 273 \ No newline at end of file