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 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
... ... @@ -14,6 +14,6 @@ yudao:
14 14 mqtt-port: 1883
15 15 mqtt-ssl: false
16 16 mqtt-username: yudao
17   - mqtt-password: yudao
  17 + mqtt-password: 123456
18 18 mqtt-topics: "/+/#"
19 19 auth-port: 8101
... ...
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
... ...