Commit 7b10b5954164a70ac9a8eb036aaa686b1ded48bf

Authored by haohao
1 parent a4e80d45

feat:【IoT 物联网】优化 MQTT 配置属性,新增 MQTT 消息处理抽象基类

yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayProperties.java
@@ -103,39 +103,65 @@ public class IotGatewayProperties { @@ -103,39 +103,65 @@ public class IotGatewayProperties {
103 */ 103 */
104 @NotNull(message = "是否开启不能为空") 104 @NotNull(message = "是否开启不能为空")
105 private Boolean enabled; 105 private Boolean enabled;
  106 +
106 /** 107 /**
107 - * HTTP 认证端口 108 + * HTTP 认证端口(默认:8090)
108 */ 109 */
109 private Integer httpAuthPort = 8090; 110 private Integer httpAuthPort = 8090;
  111 +
110 /** 112 /**
111 * MQTT 服务器地址 113 * MQTT 服务器地址
112 */ 114 */
  115 + @NotEmpty(message = "MQTT 服务器地址不能为空")
113 private String mqttHost; 116 private String mqttHost;
  117 +
114 /** 118 /**
115 - * MQTT 服务器端口 119 + * MQTT 服务器端口(默认:1883)
116 */ 120 */
117 - private Integer mqttPort; 121 + @NotNull(message = "MQTT 服务器端口不能为空")
  122 + private Integer mqttPort = 1883;
  123 +
118 /** 124 /**
119 * MQTT 用户名 125 * MQTT 用户名
120 */ 126 */
  127 + @NotEmpty(message = "MQTT 用户名不能为空")
121 private String mqttUsername; 128 private String mqttUsername;
  129 +
122 /** 130 /**
123 * MQTT 密码 131 * MQTT 密码
124 */ 132 */
  133 + @NotEmpty(message = "MQTT 密码不能为空")
125 private String mqttPassword; 134 private String mqttPassword;
  135 +
126 /** 136 /**
127 - * MQTT 是否开启 SSL 137 + * MQTT 是否开启 SSL(默认:false)
128 */ 138 */
129 - private Boolean mqttSsl; 139 + @NotNull(message = "MQTT 是否开启 SSL 不能为空")
  140 + private Boolean mqttSsl = false;
  141 +
130 /** 142 /**
131 - * MQTT客户端 ID 143 + * MQTT 客户端 ID(如果为空,系统将自动生成)
132 */ 144 */
133 private String mqttClientId; 145 private String mqttClientId;
  146 +
134 /** 147 /**
135 - * MQTT 主题 148 + * MQTT 主题列表
136 */ 149 */
  150 + @NotEmpty(message = "MQTT 主题不能为空")
137 private List<String> mqttTopics; 151 private List<String> mqttTopics;
138 152
  153 + /**
  154 + * 获取 MQTT 客户端 ID,如果未配置则自动生成
  155 + *
  156 + * @return MQTT 客户端 ID
  157 + */
  158 + public String getMqttClientId() {
  159 + if (cn.hutool.core.util.StrUtil.isBlank(mqttClientId)) {
  160 + mqttClientId = "iot-gateway-mqtt-" + System.currentTimeMillis();
  161 + }
  162 + return mqttClientId;
  163 + }
  164 +
139 } 165 }
140 166
141 } 167 }
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotMqttDownstreamSubscriber.java
@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt; @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt;
3 import cn.hutool.core.util.StrUtil; 3 import cn.hutool.core.util.StrUtil;
4 import cn.hutool.json.JSONObject; 4 import cn.hutool.json.JSONObject;
5 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO; 5 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO;
  6 +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
6 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus; 7 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
7 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber; 8 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber;
8 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 9 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
@@ -47,19 +48,28 @@ public class IotMqttDownstreamSubscriber implements IotMessageSubscriber&lt;IotDevi @@ -47,19 +48,28 @@ public class IotMqttDownstreamSubscriber implements IotMessageSubscriber&lt;IotDevi
47 48
48 @Override 49 @Override
49 public void onMessage(IotDeviceMessage message) { 50 public void onMessage(IotDeviceMessage message) {
50 - log.info("[onMessage][接收到下行消息:{}]", message); 51 + log.info("[onMessage][接收到下行消息][messageId: {}][method: {}][deviceId: {}]",
  52 + message.getId(), message.getMethod(), message.getDeviceId());
51 try { 53 try {
52 // 根据消息方法处理不同的下行消息 54 // 根据消息方法处理不同的下行消息
53 String method = message.getMethod(); 55 String method = message.getMethod();
54 if (method == null) { 56 if (method == null) {
55 - log.warn("[onMessage][消息({})方法为空]", message); 57 + log.warn("[onMessage][消息方法为空][messageId: {}][deviceId: {}]",
  58 + message.getId(), message.getDeviceId());
  59 + return;
  60 + }
  61 +
  62 + // 过滤上行消息:下行订阅者只处理下行消息
  63 + if (isUpstreamMessage(method)) {
  64 + log.debug("[onMessage][忽略上行消息][method: {}][messageId: {}]", method, message.getId());
56 return; 65 return;
57 } 66 }
58 67
59 // 处理下行消息 68 // 处理下行消息
60 handleDownstreamMessage(message); 69 handleDownstreamMessage(message);
61 } catch (Exception e) { 70 } catch (Exception e) {
62 - log.error("[onMessage][处理下行消息失败:{}]", message, e); 71 + log.error("[onMessage][处理下行消息失败][messageId: {}][method: {}][deviceId: {}]",
  72 + message.getId(), message.getMethod(), message.getDeviceId(), e);
63 } 73 }
64 } 74 }
65 75
@@ -155,4 +165,15 @@ public class IotMqttDownstreamSubscriber implements IotMessageSubscriber&lt;IotDevi @@ -155,4 +165,15 @@ public class IotMqttDownstreamSubscriber implements IotMessageSubscriber&lt;IotDevi
155 return payload; 165 return payload;
156 } 166 }
157 167
  168 + /**
  169 + * 判断是否为上行消息
  170 + *
  171 + * @param method 消息方法
  172 + * @return 是否为上行消息
  173 + */
  174 + private boolean isUpstreamMessage(String method) {
  175 + IotDeviceMessageMethodEnum methodEnum = IotDeviceMessageMethodEnum.of(method);
  176 + return methodEnum != null && methodEnum.getUpstream();
  177 + }
  178 +
158 } 179 }
159 \ No newline at end of file 180 \ 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
@@ -37,10 +37,20 @@ import java.util.concurrent.TimeUnit; @@ -37,10 +37,20 @@ import java.util.concurrent.TimeUnit;
37 public class IotMqttUpstreamProtocol { 37 public class IotMqttUpstreamProtocol {
38 38
39 /** 39 /**
40 - * 默认 QoS 级别 40 + * 默认 QoS 级别 - 至少一次
41 */ 41 */
42 private static final MqttQoS DEFAULT_QOS = MqttQoS.AT_LEAST_ONCE; 42 private static final MqttQoS DEFAULT_QOS = MqttQoS.AT_LEAST_ONCE;
43 43
  44 + /**
  45 + * 连接超时时间(秒)
  46 + */
  47 + private static final int CONNECT_TIMEOUT_SECONDS = 10;
  48 +
  49 + /**
  50 + * 重连延迟时间(毫秒)
  51 + */
  52 + private static final long RECONNECT_DELAY_MS = 5000;
  53 +
44 private final IotGatewayProperties.EmqxProperties emqxProperties; 54 private final IotGatewayProperties.EmqxProperties emqxProperties;
45 55
46 // 共享资源 56 // 共享资源
@@ -227,25 +237,26 @@ public class IotMqttUpstreamProtocol { @@ -227,25 +237,26 @@ public class IotMqttUpstreamProtocol {
227 * 连接 MQTT Broker 并订阅主题 237 * 连接 MQTT Broker 并订阅主题
228 */ 238 */
229 private void connectMqtt() { 239 private void connectMqtt() {
  240 + // 参数校验
230 String host = emqxProperties.getMqttHost(); 241 String host = emqxProperties.getMqttHost();
231 Integer port = emqxProperties.getMqttPort(); 242 Integer port = emqxProperties.getMqttPort();
232 243
233 if (StrUtil.isBlank(host)) { 244 if (StrUtil.isBlank(host)) {
234 - String msg = "[connectMqtt][MQTT Host 为空,无法连接]";  
235 - log.error(msg);  
236 - return; 245 + log.error("[connectMqtt][MQTT Host 为空,无法连接]");
  246 + throw new IllegalArgumentException("MQTT Host 不能为空");
237 } 247 }
238 - if (port == null) {  
239 - log.warn("[connectMqtt][MQTT Port 为 null,使用默认端口 1883]");  
240 - port = 1883; 248 + if (port == null || port <= 0) {
  249 + log.error("[connectMqtt][MQTT Port 无效:{}]", port);
  250 + throw new IllegalArgumentException("MQTT Port 必须为正整数");
241 } 251 }
242 252
243 - final Integer finalPort = port;  
244 - CompletableFuture<Void> connectFuture = mqttClient.connect(finalPort, host) 253 + log.info("[connectMqtt][开始连接 MQTT Broker][host: {}][port: {}]", host, port);
  254 +
  255 + CompletableFuture<Void> connectFuture = mqttClient.connect(port, host)
245 .toCompletionStage() 256 .toCompletionStage()
246 .toCompletableFuture() 257 .toCompletableFuture()
247 .thenAccept(connAck -> { 258 .thenAccept(connAck -> {
248 - log.info("[connectMqtt][MQTT 客户端连接成功]"); 259 + log.info("[connectMqtt][MQTT 客户端连接成功][host: {}][port: {}]", host, port);
249 // 设置断开重连监听器 260 // 设置断开重连监听器
250 mqttClient.closeHandler(closeEvent -> { 261 mqttClient.closeHandler(closeEvent -> {
251 log.warn("[closeHandler][MQTT 连接已断开,准备重连]"); 262 log.warn("[closeHandler][MQTT 连接已断开,准备重连]");
@@ -257,17 +268,19 @@ public class IotMqttUpstreamProtocol { @@ -257,17 +268,19 @@ public class IotMqttUpstreamProtocol {
257 subscribeToTopics(); 268 subscribeToTopics();
258 }) 269 })
259 .exceptionally(error -> { 270 .exceptionally(error -> {
260 - log.error("[connectMqtt][连接 MQTT Broker 失败]", error); 271 + log.error("[connectMqtt][连接 MQTT Broker 失败][host: {}][port: {}]", host, port, error);
  272 + // 连接失败时也要尝试重连
261 reconnectWithDelay(); 273 reconnectWithDelay();
262 return null; 274 return null;
263 }); 275 });
264 276
265 // 等待连接完成 277 // 等待连接完成
266 try { 278 try {
267 - connectFuture.get(10, TimeUnit.SECONDS); 279 + connectFuture.get(CONNECT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
268 log.info("[connectMqtt][MQTT 客户端启动完成]"); 280 log.info("[connectMqtt][MQTT 客户端启动完成]");
269 } catch (Exception e) { 281 } catch (Exception e) {
270 log.error("[connectMqtt][MQTT 客户端启动失败]", e); 282 log.error("[connectMqtt][MQTT 客户端启动失败]", e);
  283 + throw new RuntimeException("MQTT 客户端启动失败", e);
271 } 284 }
272 } 285 }
273 286
@@ -284,15 +297,19 @@ public class IotMqttUpstreamProtocol { @@ -284,15 +297,19 @@ public class IotMqttUpstreamProtocol {
284 */ 297 */
285 private void subscribeToTopics() { 298 private void subscribeToTopics() {
286 List<String> topicList = emqxProperties.getMqttTopics(); 299 List<String> topicList = emqxProperties.getMqttTopics();
287 - if (CollUtil.isEmpty(topicList)) {  
288 - log.warn("[subscribeToTopics][没有配置要订阅的主题]");  
289 - return;  
290 - } 300 + // @NotEmpty 注解已保证 topicList 不为空,无需重复校验
  301 +
  302 + log.info("[subscribeToTopics][开始订阅主题,共 {} 个]", topicList.size());
291 303
292 for (String topic : topicList) { 304 for (String topic : topicList) {
  305 + if (StrUtil.isBlank(topic)) {
  306 + log.warn("[subscribeToTopics][跳过空主题]");
  307 + continue;
  308 + }
  309 +
293 mqttClient.subscribe(topic, DEFAULT_QOS.value(), subscribeResult -> { 310 mqttClient.subscribe(topic, DEFAULT_QOS.value(), subscribeResult -> {
294 if (subscribeResult.succeeded()) { 311 if (subscribeResult.succeeded()) {
295 - log.info("[subscribeToTopics][订阅主题成功: {}]", topic); 312 + log.info("[subscribeToTopics][订阅主题成功: {}][QoS: {}]", topic, DEFAULT_QOS.value());
296 } else { 313 } else {
297 log.error("[subscribeToTopics][订阅主题失败: {}]", topic, subscribeResult.cause()); 314 log.error("[subscribeToTopics][订阅主题失败: {}]", topic, subscribeResult.cause());
298 } 315 }
@@ -304,10 +321,16 @@ public class IotMqttUpstreamProtocol { @@ -304,10 +321,16 @@ public class IotMqttUpstreamProtocol {
304 * 延迟重连 321 * 延迟重连
305 */ 322 */
306 private void reconnectWithDelay() { 323 private void reconnectWithDelay() {
307 - vertx.setTimer(5000, timerId -> { 324 + vertx.setTimer(RECONNECT_DELAY_MS, timerId -> {
308 if (isRunning && (mqttClient == null || !mqttClient.isConnected())) { 325 if (isRunning && (mqttClient == null || !mqttClient.isConnected())) {
309 - log.info("[reconnectWithDelay][开始重连 MQTT Broker]");  
310 - connectMqtt(); 326 + log.info("[reconnectWithDelay][开始重连 MQTT Broker,延迟 {} 毫秒]", RECONNECT_DELAY_MS);
  327 + try {
  328 + connectMqtt();
  329 + } catch (Exception e) {
  330 + log.error("[reconnectWithDelay][重连失败,将继续尝试重连]", e);
  331 + // 重连失败时继续尝试重连
  332 + reconnectWithDelay();
  333 + }
311 } 334 }
312 }); 335 });
313 } 336 }
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/router/IotMqttAbstractHandler.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import lombok.extern.slf4j.Slf4j;
  5 +
  6 +/**
  7 + * IoT 网关 MQTT 协议的处理器抽象基类
  8 + * <p>
  9 + * 提供通用的异常处理、参数校验等功能
  10 + *
  11 + * @author 芋道源码
  12 + */
  13 +@Slf4j
  14 +public abstract class IotMqttAbstractHandler {
  15 +
  16 + /**
  17 + * 处理 MQTT 消息的模板方法
  18 + *
  19 + * @param topic 主题
  20 + * @param payload 消息内容
  21 + */
  22 + public final void handle(String topic, String payload) {
  23 + try {
  24 + // 1. 前置校验
  25 + if (!validateInput(topic, payload)) {
  26 + return;
  27 + }
  28 +
  29 + // 2. 执行具体逻辑
  30 + doHandle(topic, payload);
  31 +
  32 + } catch (Exception e) {
  33 + log.error("[handle][处理 MQTT 消息失败][topic: {}][payload: {}]", topic, payload, e);
  34 + handleException(topic, payload, e);
  35 + }
  36 + }
  37 +
  38 + /**
  39 + * 具体的处理逻辑,由子类实现
  40 + *
  41 + * @param topic 主题
  42 + * @param payload 消息内容
  43 + */
  44 + protected abstract void doHandle(String topic, String payload);
  45 +
  46 + /**
  47 + * 输入参数校验
  48 + *
  49 + * @param topic 主题
  50 + * @param payload 消息内容
  51 + * @return 校验是否通过
  52 + */
  53 + protected boolean validateInput(String topic, String payload) {
  54 + if (StrUtil.isBlank(topic)) {
  55 + log.warn("[validateInput][主题为空,忽略消息]");
  56 + return false;
  57 + }
  58 +
  59 + if (StrUtil.isBlank(payload)) {
  60 + log.warn("[validateInput][消息内容为空][topic: {}]", topic);
  61 + return false;
  62 + }
  63 +
  64 + return true;
  65 + }
  66 +
  67 + /**
  68 + * 异常处理
  69 + *
  70 + * @param topic 主题
  71 + * @param payload 消息内容
  72 + * @param e 异常
  73 + */
  74 + protected void handleException(String topic, String payload, Exception e) {
  75 + // 默认实现:记录错误日志
  76 + // 子类可以重写此方法,添加特定的异常处理逻辑
  77 + log.error("[handleException][MQTT 消息处理异常][topic: {}]", topic, e);
  78 + }
  79 +
  80 + /**
  81 + * 解析主题,获取主题各部分
  82 + *
  83 + * @param topic 主题
  84 + * @return 主题各部分数组,如果解析失败返回 null
  85 + */
  86 + protected String[] parseTopic(String topic) {
  87 + String[] topicParts = topic.split("/");
  88 + if (topicParts.length < 7) {
  89 + log.warn("[parseTopic][主题格式不正确][topic: {}]", topic);
  90 + return null;
  91 + }
  92 + return topicParts;
  93 + }
  94 +}
0 \ No newline at end of file 95 \ No newline at end of file
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/router/IotMqttHttpAuthHandler.java
@@ -13,10 +13,12 @@ import io.vertx.core.json.JsonObject; @@ -13,10 +13,12 @@ import io.vertx.core.json.JsonObject;
13 import io.vertx.ext.web.RoutingContext; 13 import io.vertx.ext.web.RoutingContext;
14 import lombok.extern.slf4j.Slf4j; 14 import lombok.extern.slf4j.Slf4j;
15 15
  16 +import static cn.iocoder.yudao.module.iot.gateway.enums.ErrorCodeConstants.DEVICE_AUTH_FAIL;
  17 +
16 /** 18 /**
17 * IoT 网关 MQTT HTTP 认证处理器 19 * IoT 网关 MQTT HTTP 认证处理器
18 * <p> 20 * <p>
19 - * 处理 EMQX 的认证请求和事件钩子 21 + * 处理 EMQX 的认证请求和事件钩子,提供统一的错误处理和参数校验
20 * 22 *
21 * @author 芋道源码 23 * @author 芋道源码
22 */ 24 */
@@ -24,6 +26,26 @@ import lombok.extern.slf4j.Slf4j; @@ -24,6 +26,26 @@ import lombok.extern.slf4j.Slf4j;
24 public class IotMqttHttpAuthHandler { 26 public class IotMqttHttpAuthHandler {
25 27
26 /** 28 /**
  29 + * 认证成功状态码
  30 + */
  31 + private static final int SUCCESS_STATUS_CODE = 200;
  32 +
  33 + /**
  34 + * 参数错误状态码
  35 + */
  36 + private static final int BAD_REQUEST_STATUS_CODE = 400;
  37 +
  38 + /**
  39 + * 认证失败状态码
  40 + */
  41 + private static final int UNAUTHORIZED_STATUS_CODE = 401;
  42 +
  43 + /**
  44 + * 服务器错误状态码
  45 + */
  46 + private static final int INTERNAL_ERROR_STATUS_CODE = 500;
  47 +
  48 + /**
27 * EMQX 认证接口 49 * EMQX 认证接口
28 */ 50 */
29 public void authenticate(RoutingContext context) { 51 public void authenticate(RoutingContext context) {
@@ -44,7 +66,7 @@ public class IotMqttHttpAuthHandler { @@ -44,7 +66,7 @@ public class IotMqttHttpAuthHandler {
44 // 参数校验 66 // 参数校验
45 if (StrUtil.isEmpty(clientid) || StrUtil.isEmpty(username) || StrUtil.isEmpty(password)) { 67 if (StrUtil.isEmpty(clientid) || StrUtil.isEmpty(username) || StrUtil.isEmpty(password)) {
46 log.warn("[authenticate][认证参数不完整][clientId: {}][username: {}]", clientid, username); 68 log.warn("[authenticate][认证参数不完整][clientId: {}][username: {}]", clientid, username);
47 - sendErrorResponse(context, 400, "认证参数不完整"); 69 + sendErrorResponse(context, BAD_REQUEST_STATUS_CODE, "认证参数不完整");
48 return; 70 return;
49 } 71 }
50 72
@@ -58,7 +80,7 @@ public class IotMqttHttpAuthHandler { @@ -58,7 +80,7 @@ public class IotMqttHttpAuthHandler {
58 result.checkError(); 80 result.checkError();
59 if (!BooleanUtil.isTrue(result.getData())) { 81 if (!BooleanUtil.isTrue(result.getData())) {
60 log.warn("[authenticate][设备认证失败][clientId: {}][username: {}]", clientid, username); 82 log.warn("[authenticate][设备认证失败][clientId: {}][username: {}]", clientid, username);
61 - sendErrorResponse(context, 401, "设备认证失败"); 83 + sendErrorResponse(context, UNAUTHORIZED_STATUS_CODE, DEVICE_AUTH_FAIL.getMsg());
62 return; 84 return;
63 } 85 }
64 86
@@ -67,7 +89,7 @@ public class IotMqttHttpAuthHandler { @@ -67,7 +89,7 @@ public class IotMqttHttpAuthHandler {
67 89
68 } catch (Exception e) { 90 } catch (Exception e) {
69 log.error("[authenticate][设备认证异常]", e); 91 log.error("[authenticate][设备认证异常]", e);
70 - sendErrorResponse(context, 500, "认证服务异常"); 92 + sendErrorResponse(context, INTERNAL_ERROR_STATUS_CODE, "认证服务异常");
71 } 93 }
72 } 94 }
73 95
@@ -79,7 +101,7 @@ public class IotMqttHttpAuthHandler { @@ -79,7 +101,7 @@ public class IotMqttHttpAuthHandler {
79 // 解析请求体 101 // 解析请求体
80 JsonObject body = context.body().asJsonObject(); 102 JsonObject body = context.body().asJsonObject();
81 if (body == null) { 103 if (body == null) {
82 - sendErrorResponse(context, 400, "请求体不能为空"); 104 + sendErrorResponse(context, BAD_REQUEST_STATUS_CODE, "请求体不能为空");
83 return; 105 return;
84 } 106 }
85 107
@@ -94,7 +116,7 @@ public class IotMqttHttpAuthHandler { @@ -94,7 +116,7 @@ public class IotMqttHttpAuthHandler {
94 116
95 } catch (Exception e) { 117 } catch (Exception e) {
96 log.error("[connected][处理设备连接事件失败]", e); 118 log.error("[connected][处理设备连接事件失败]", e);
97 - sendErrorResponse(context, 500, "处理失败"); 119 + sendErrorResponse(context, INTERNAL_ERROR_STATUS_CODE, "处理失败");
98 } 120 }
99 } 121 }
100 122
@@ -106,7 +128,7 @@ public class IotMqttHttpAuthHandler { @@ -106,7 +128,7 @@ public class IotMqttHttpAuthHandler {
106 // 解析请求体 128 // 解析请求体
107 JsonObject body = context.body().asJsonObject(); 129 JsonObject body = context.body().asJsonObject();
108 if (body == null) { 130 if (body == null) {
109 - sendErrorResponse(context, 400, "请求体不能为空"); 131 + sendErrorResponse(context, BAD_REQUEST_STATUS_CODE, "请求体不能为空");
110 return; 132 return;
111 } 133 }
112 134
@@ -123,7 +145,7 @@ public class IotMqttHttpAuthHandler { @@ -123,7 +145,7 @@ public class IotMqttHttpAuthHandler {
123 145
124 } catch (Exception e) { 146 } catch (Exception e) {
125 log.error("[disconnected][处理设备断开连接事件失败]", e); 147 log.error("[disconnected][处理设备断开连接事件失败]", e);
126 - sendErrorResponse(context, 500, "处理失败"); 148 + sendErrorResponse(context, INTERNAL_ERROR_STATUS_CODE, "处理失败");
127 } 149 }
128 } 150 }
129 151
@@ -175,7 +197,7 @@ public class IotMqttHttpAuthHandler { @@ -175,7 +197,7 @@ public class IotMqttHttpAuthHandler {
175 */ 197 */
176 private void sendSuccessResponse(RoutingContext context, String message) { 198 private void sendSuccessResponse(RoutingContext context, String message) {
177 context.response() 199 context.response()
178 - .setStatusCode(200) 200 + .setStatusCode(SUCCESS_STATUS_CODE)
179 .putHeader("Content-Type", "text/plain; charset=utf-8") 201 .putHeader("Content-Type", "text/plain; charset=utf-8")
180 .end(message); 202 .end(message);
181 } 203 }
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/router/IotMqttUpstreamHandler.java
1 package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router; 1 package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router;
2 2
3 -import cn.hutool.core.util.StrUtil;  
4 import cn.hutool.extra.spring.SpringUtil; 3 import cn.hutool.extra.spring.SpringUtil;
5 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 4 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
6 -import cn.iocoder.yudao.module.iot.core.mq.producer.IotDeviceMessageProducer;  
7 import cn.iocoder.yudao.module.iot.gateway.enums.IotDeviceTopicEnum; 5 import cn.iocoder.yudao.module.iot.gateway.enums.IotDeviceTopicEnum;
8 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; 6 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
9 import io.vertx.mqtt.messages.MqttPublishMessage; 7 import io.vertx.mqtt.messages.MqttPublishMessage;
@@ -19,14 +17,12 @@ import java.nio.charset.StandardCharsets; @@ -19,14 +17,12 @@ import java.nio.charset.StandardCharsets;
19 * @author 芋道源码 17 * @author 芋道源码
20 */ 18 */
21 @Slf4j 19 @Slf4j
22 -public class IotMqttUpstreamHandler { 20 +public class IotMqttUpstreamHandler extends IotMqttAbstractHandler {
23 21
24 - private final IotDeviceMessageProducer deviceMessageProducer;  
25 private final IotDeviceMessageService deviceMessageService; 22 private final IotDeviceMessageService deviceMessageService;
26 private final String serverId; 23 private final String serverId;
27 24
28 public IotMqttUpstreamHandler(cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttUpstreamProtocol protocol) { 25 public IotMqttUpstreamHandler(cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttUpstreamProtocol protocol) {
29 - this.deviceMessageProducer = SpringUtil.getBean(IotDeviceMessageProducer.class);  
30 this.deviceMessageService = SpringUtil.getBean(IotDeviceMessageService.class); 26 this.deviceMessageService = SpringUtil.getBean(IotDeviceMessageService.class);
31 this.serverId = protocol.getServerId(); 27 this.serverId = protocol.getServerId();
32 } 28 }
@@ -38,41 +34,22 @@ public class IotMqttUpstreamHandler { @@ -38,41 +34,22 @@ public class IotMqttUpstreamHandler {
38 String topic = message.topicName(); 34 String topic = message.topicName();
39 String payload = message.payload().toString(StandardCharsets.UTF_8); 35 String payload = message.payload().toString(StandardCharsets.UTF_8);
40 36
41 - if (StrUtil.isBlank(topic)) {  
42 - log.warn("[handle][主题为空,忽略消息]");  
43 - return;  
44 - }  
45 -  
46 - if (StrUtil.isBlank(payload)) {  
47 - log.warn("[handle][消息内容为空][topic: {}]", topic);  
48 - return;  
49 - }  
50 -  
51 log.debug("[handle][收到 MQTT 消息][topic: {}]", topic); 37 log.debug("[handle][收到 MQTT 消息][topic: {}]", topic);
  38 + // 调用父类的 handle 方法,父类会进行参数校验
52 handle(topic, payload); 39 handle(topic, payload);
53 } 40 }
54 41
55 - /**  
56 - * 处理 MQTT 消息  
57 - *  
58 - * @param topic 主题  
59 - * @param payload 消息内容  
60 - */  
61 - public void handle(String topic, String payload) {  
62 - try {  
63 - // 1. 识别并验证消息类型  
64 - String messageType = getMessageType(topic);  
65 - if (messageType == null) {  
66 - log.warn("[handle][未知的消息类型][topic: {}]", topic);  
67 - return;  
68 - }  
69 -  
70 - // 2. 处理消息  
71 - processMessage(topic, payload, messageType);  
72 -  
73 - } catch (Exception e) {  
74 - log.error("[handle][处理消息失败][topic: {}][payload: {}]", topic, payload, e); 42 + @Override
  43 + protected void doHandle(String topic, String payload) {
  44 + // 1. 识别并验证消息类型
  45 + String messageType = getMessageType(topic);
  46 + if (messageType == null) {
  47 + log.warn("[doHandle][未知的消息类型][topic: {}]", topic);
  48 + return;
75 } 49 }
  50 +
  51 + // 2. 处理消息
  52 + processMessage(topic, payload, messageType);
76 } 53 }
77 54
78 /** 55 /**
@@ -109,39 +86,37 @@ public class IotMqttUpstreamHandler { @@ -109,39 +86,37 @@ public class IotMqttUpstreamHandler {
109 * @return 消息类型描述,如果不支持返回 null 86 * @return 消息类型描述,如果不支持返回 null
110 */ 87 */
111 private String getMessageType(String topic) { 88 private String getMessageType(String topic) {
112 - if (StrUtil.isBlank(topic)) {  
113 - return null;  
114 - } 89 + // 此方法由 doHandle 调用,topic 已经在父类中校验过,无需重复校验
115 90
116 // 按优先级匹配主题类型,避免误匹配 91 // 按优先级匹配主题类型,避免误匹配
117 92
118 - // 1. 设备属性上报: /sys/{productKey}/{deviceName}/thing/event/property/post 93 + // 1. 设备属性上报: /sys/{productKey}/{deviceName}/thing/property/post
119 if (isPropertyPostTopic(topic)) { 94 if (isPropertyPostTopic(topic)) {
120 return IotDeviceTopicEnum.PROPERTY_POST_TOPIC.getDescription(); 95 return IotDeviceTopicEnum.PROPERTY_POST_TOPIC.getDescription();
121 } 96 }
122 97
123 - // 2. 设备事件上报: /sys/{productKey}/{deviceName}/thing/event/{eventIdentifier}/post 98 + // 2. 设备事件上报: /sys/{productKey}/{deviceName}/thing/{eventIdentifier}/post
124 if (isEventPostTopic(topic)) { 99 if (isEventPostTopic(topic)) {
125 return "设备事件上报"; 100 return "设备事件上报";
126 } 101 }
127 102
128 - // 3. 设备属性设置响应: /sys/{productKey}/{deviceName}/thing/service/property/set_reply 103 + // 3. 设备属性设置响应: /sys/{productKey}/{deviceName}/thing/property/set_reply
129 if (isPropertySetReplyTopic(topic)) { 104 if (isPropertySetReplyTopic(topic)) {
130 return "设备属性设置响应"; 105 return "设备属性设置响应";
131 } 106 }
132 107
133 - // 4. 设备属性获取响应: /sys/{productKey}/{deviceName}/thing/service/property/get_reply 108 + // 4. 设备属性获取响应: /sys/{productKey}/{deviceName}/thing/property/get_reply
134 if (isPropertyGetReplyTopic(topic)) { 109 if (isPropertyGetReplyTopic(topic)) {
135 return "设备属性获取响应"; 110 return "设备属性获取响应";
136 } 111 }
137 112
138 - // 5. 设备配置设置响应: /sys/{productKey}/{deviceName}/thing/service/config/set_reply 113 + // 5. 设备配置设置响应: /sys/{productKey}/{deviceName}/thing/config/set_reply
139 if (isConfigSetReplyTopic(topic)) { 114 if (isConfigSetReplyTopic(topic)) {
140 return IotDeviceTopicEnum.CONFIG_SET_TOPIC.getDescription() + "响应"; 115 return IotDeviceTopicEnum.CONFIG_SET_TOPIC.getDescription() + "响应";
141 } 116 }
142 117
143 // 6. 设备 OTA 升级响应: 118 // 6. 设备 OTA 升级响应:
144 - // /sys/{productKey}/{deviceName}/thing/service/ota/upgrade_reply 119 + // /sys/{productKey}/{deviceName}/thing/ota/upgrade_reply
145 if (isOtaUpgradeReplyTopic(topic)) { 120 if (isOtaUpgradeReplyTopic(topic)) {
146 return IotDeviceTopicEnum.OTA_UPGRADE_TOPIC.getDescription() + "响应"; 121 return IotDeviceTopicEnum.OTA_UPGRADE_TOPIC.getDescription() + "响应";
147 } 122 }
@@ -214,18 +189,4 @@ public class IotMqttUpstreamHandler { @@ -214,18 +189,4 @@ public class IotMqttUpstreamHandler {
214 && !topic.contains("ota"); 189 && !topic.contains("ota");
215 } 190 }
216 191
217 - /**  
218 - * 解析主题,获取主题各部分  
219 - *  
220 - * @param topic 主题  
221 - * @return 主题各部分数组,如果解析失败返回 null  
222 - */  
223 - private String[] parseTopic(String topic) {  
224 - String[] topicParts = topic.split("/");  
225 - if (topicParts.length < 7) {  
226 - log.warn("[parseTopic][主题格式不正确][topic: {}]", topic);  
227 - return null;  
228 - }  
229 - return topicParts;  
230 - }  
231 } 192 }
232 \ No newline at end of file 193 \ No newline at end of file