Commit 7e49c901562889b22cbe85e153ad0cf23d51d9bd

Authored by YunaiV
1 parent f1368d9e

review:【IoT 物联网】MqTT 协议

yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotDeviceMessageMethodEnum.java
@@ -23,12 +23,13 @@ public enum IotDeviceMessageMethodEnum implements ArrayValuable<String> { @@ -23,12 +23,13 @@ public enum IotDeviceMessageMethodEnum implements ArrayValuable<String> {
23 STATE_OFFLINE("thing.state.offline", true), 23 STATE_OFFLINE("thing.state.offline", true),
24 24
25 // ========== 设备属性 ========== 25 // ========== 设备属性 ==========
26 - // 可参考  
27 - // https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services 26 + // 可参考:https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services
  27 +
28 PROPERTY_POST("thing.property.post", true), 28 PROPERTY_POST("thing.property.post", true),
29 PROPERTY_SET("thing.property.set", false), 29 PROPERTY_SET("thing.property.set", false),
30 30
31 // ========== 设备事件 ========== 31 // ========== 设备事件 ==========
  32 +
32 EVENT_POST("thing.event.post", true), 33 EVENT_POST("thing.event.post", true),
33 34
34 ; 35 ;
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayProperties.java
@@ -105,6 +105,7 @@ public class IotGatewayProperties { @@ -105,6 +105,7 @@ public class IotGatewayProperties {
105 @NotNull(message = "是否开启不能为空") 105 @NotNull(message = "是否开启不能为空")
106 private Boolean enabled; 106 private Boolean enabled;
107 107
  108 + // TODO @haohao:是不是改成 httpPort?不只认证,目前看。
108 /** 109 /**
109 * HTTP 认证端口(默认:8090) 110 * HTTP 认证端口(默认:8090)
110 */ 111 */
@@ -115,42 +116,35 @@ public class IotGatewayProperties { @@ -115,42 +116,35 @@ public class IotGatewayProperties {
115 */ 116 */
116 @NotEmpty(message = "MQTT 服务器地址不能为空") 117 @NotEmpty(message = "MQTT 服务器地址不能为空")
117 private String mqttHost; 118 private String mqttHost;
118 -  
119 /** 119 /**
120 * MQTT 服务器端口(默认:1883) 120 * MQTT 服务器端口(默认:1883)
121 */ 121 */
122 @NotNull(message = "MQTT 服务器端口不能为空") 122 @NotNull(message = "MQTT 服务器端口不能为空")
123 private Integer mqttPort = 1883; 123 private Integer mqttPort = 1883;
124 -  
125 /** 124 /**
126 * MQTT 用户名 125 * MQTT 用户名
127 */ 126 */
128 @NotEmpty(message = "MQTT 用户名不能为空") 127 @NotEmpty(message = "MQTT 用户名不能为空")
129 private String mqttUsername; 128 private String mqttUsername;
130 -  
131 /** 129 /**
132 * MQTT 密码 130 * MQTT 密码
133 */ 131 */
134 @NotEmpty(message = "MQTT 密码不能为空") 132 @NotEmpty(message = "MQTT 密码不能为空")
135 private String mqttPassword; 133 private String mqttPassword;
136 -  
137 /** 134 /**
138 * MQTT 客户端的 SSL 开关 135 * MQTT 客户端的 SSL 开关
139 */ 136 */
140 @NotNull(message = "MQTT 是否开启 SSL 不能为空") 137 @NotNull(message = "MQTT 是否开启 SSL 不能为空")
141 private Boolean mqttSsl = false; 138 private Boolean mqttSsl = false;
142 -  
143 /** 139 /**
144 * MQTT 客户端 ID(如果为空,系统将自动生成) 140 * MQTT 客户端 ID(如果为空,系统将自动生成)
145 */ 141 */
146 private String mqttClientId; 142 private String mqttClientId;
147 -  
148 /** 143 /**
149 * MQTT 订阅的主题 144 * MQTT 订阅的主题
150 */ 145 */
151 @NotEmpty(message = "MQTT 主题不能为空") 146 @NotEmpty(message = "MQTT 主题不能为空")
152 - private List<String> mqttTopics;  
153 - 147 + private List<@NotEmpty(message = "MQTT 主题不能为空") String> mqttTopics;
154 /** 148 /**
155 * 默认 QoS 级别 149 * 默认 QoS 级别
156 * <p> 150 * <p>
@@ -164,12 +158,12 @@ public class IotGatewayProperties { @@ -164,12 +158,12 @@ public class IotGatewayProperties {
164 * 连接超时时间(秒) 158 * 连接超时时间(秒)
165 */ 159 */
166 private Integer connectTimeoutSeconds = 10; 160 private Integer connectTimeoutSeconds = 10;
167 -  
168 /** 161 /**
169 * 重连延迟时间(毫秒) 162 * 重连延迟时间(毫秒)
170 */ 163 */
171 private Long reconnectDelayMs = 5000L; 164 private Long reconnectDelayMs = 5000L;
172 165
  166 + // TODO @haohao:貌似可以通过配置文件 + el 表达式;尽量还是配置文件;
173 /** 167 /**
174 * 获取 MQTT 客户端 ID,如果未配置则自动生成 168 * 获取 MQTT 客户端 ID,如果未配置则自动生成
175 * 169 *
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotMqttUpstreamProtocol.java
@@ -35,6 +35,11 @@ public class IotMqttUpstreamProtocol { @@ -35,6 +35,11 @@ public class IotMqttUpstreamProtocol {
35 35
36 private final IotGatewayProperties.EmqxProperties emqxProperties; 36 private final IotGatewayProperties.EmqxProperties emqxProperties;
37 37
  38 + /**
  39 + * 服务运行状态标志
  40 + */
  41 + private volatile boolean isRunning = false;
  42 +
38 private Vertx vertx; 43 private Vertx vertx;
39 44
40 @Getter 45 @Getter
@@ -47,11 +52,6 @@ public class IotMqttUpstreamProtocol { @@ -47,11 +52,6 @@ public class IotMqttUpstreamProtocol {
47 // HTTP 认证服务相关 52 // HTTP 认证服务相关
48 private HttpServer httpAuthServer; 53 private HttpServer httpAuthServer;
49 54
50 - /**  
51 - * 服务运行状态标志  
52 - */  
53 - private volatile boolean isRunning = false;  
54 -  
55 public IotMqttUpstreamProtocol(IotGatewayProperties.EmqxProperties emqxProperties) { 55 public IotMqttUpstreamProtocol(IotGatewayProperties.EmqxProperties emqxProperties) {
56 this.emqxProperties = emqxProperties; 56 this.emqxProperties = emqxProperties;
57 this.serverId = IotDeviceMessageUtils.generateServerId(emqxProperties.getMqttPort()); 57 this.serverId = IotDeviceMessageUtils.generateServerId(emqxProperties.getMqttPort());
@@ -66,13 +66,12 @@ public class IotMqttUpstreamProtocol { @@ -66,13 +66,12 @@ public class IotMqttUpstreamProtocol {
66 log.info("[start][启动 MQTT 协议服务]"); 66 log.info("[start][启动 MQTT 协议服务]");
67 67
68 try { 68 try {
69 - // 1. 创建共享的 Vertx 实例  
70 this.vertx = Vertx.vertx(); 69 this.vertx = Vertx.vertx();
71 70
72 - // 2. 启动 HTTP 认证服务 71 + // 1. 启动 HTTP 认证服务
73 startHttpAuthServer(); 72 startHttpAuthServer();
74 73
75 - // 3. 启动 MQTT 客户端 74 + // 2. 启动 MQTT 客户端
76 startMqttClient(); 75 startMqttClient();
77 76
78 isRunning = true; 77 isRunning = true;
@@ -119,16 +118,15 @@ public class IotMqttUpstreamProtocol { @@ -119,16 +118,15 @@ public class IotMqttUpstreamProtocol {
119 private void startHttpAuthServer() { 118 private void startHttpAuthServer() {
120 log.info("[startHttpAuthServer][启动 HTTP 认证服务]"); 119 log.info("[startHttpAuthServer][启动 HTTP 认证服务]");
121 120
122 - // 创建路由 121 + // 1.1 创建路由
123 Router router = Router.router(vertx); 122 Router router = Router.router(vertx);
124 router.route().handler(BodyHandler.create()); 123 router.route().handler(BodyHandler.create());
125 -  
126 - // 创建认证处理器 124 + // 1.2 创建认证处理器
127 IotMqttHttpAuthHandler authHandler = new IotMqttHttpAuthHandler(this); 125 IotMqttHttpAuthHandler authHandler = new IotMqttHttpAuthHandler(this);
128 router.post(IotMqttTopicUtils.MQTT_AUTH_PATH).handler(authHandler::handleAuth); 126 router.post(IotMqttTopicUtils.MQTT_AUTH_PATH).handler(authHandler::handleAuth);
129 router.post(IotMqttTopicUtils.MQTT_EVENT_PATH).handler(authHandler::handleEvent); 127 router.post(IotMqttTopicUtils.MQTT_EVENT_PATH).handler(authHandler::handleEvent);
130 128
131 - // 启动 HTTP 服务器 129 + // 2. 启动 HTTP 服务器
132 int authPort = emqxProperties.getHttpAuthPort(); 130 int authPort = emqxProperties.getHttpAuthPort();
133 try { 131 try {
134 httpAuthServer = vertx.createHttpServer() 132 httpAuthServer = vertx.createHttpServer()
@@ -172,7 +170,7 @@ public class IotMqttUpstreamProtocol { @@ -172,7 +170,7 @@ public class IotMqttUpstreamProtocol {
172 createMqttClient(); 170 createMqttClient();
173 171
174 // 3. 连接 MQTT Broker(异步连接,不会抛出异常) 172 // 3. 连接 MQTT Broker(异步连接,不会抛出异常)
175 - connectMqtt(); 173 + connectMqtt(false);
176 174
177 log.info("[startMqttClient][MQTT 客户端启动完成,正在异步连接中...]"); 175 log.info("[startMqttClient][MQTT 客户端启动完成,正在异步连接中...]");
178 } catch (Exception e) { 176 } catch (Exception e) {
@@ -213,13 +211,6 @@ public class IotMqttUpstreamProtocol { @@ -213,13 +211,6 @@ public class IotMqttUpstreamProtocol {
213 211
214 /** 212 /**
215 * 连接 MQTT Broker 并订阅主题 213 * 连接 MQTT Broker 并订阅主题
216 - */  
217 - private void connectMqtt() {  
218 - connectMqtt(false);  
219 - }  
220 -  
221 - /**  
222 - * 连接 MQTT Broker 并订阅主题  
223 * 214 *
224 * @param isReconnect 是否为重连 215 * @param isReconnect 是否为重连
225 */ 216 */
@@ -227,6 +218,7 @@ public class IotMqttUpstreamProtocol { @@ -227,6 +218,7 @@ public class IotMqttUpstreamProtocol {
227 // 1. 参数校验 218 // 1. 参数校验
228 String host = emqxProperties.getMqttHost(); 219 String host = emqxProperties.getMqttHost();
229 Integer port = emqxProperties.getMqttPort(); 220 Integer port = emqxProperties.getMqttPort();
  221 + // TODO @haohao:这些参数校验,交给 validator;
230 if (StrUtil.isBlank(host)) { 222 if (StrUtil.isBlank(host)) {
231 log.error("[connectMqtt][MQTT Host 为空, 无法连接]"); 223 log.error("[connectMqtt][MQTT Host 为空, 无法连接]");
232 throw new IllegalArgumentException("MQTT Host 不能为空"); 224 throw new IllegalArgumentException("MQTT Host 不能为空");
@@ -246,6 +238,7 @@ public class IotMqttUpstreamProtocol { @@ -246,6 +238,7 @@ public class IotMqttUpstreamProtocol {
246 238
247 // 2. 异步连接 239 // 2. 异步连接
248 mqttClient.connect(port, host, connectResult -> { 240 mqttClient.connect(port, host, connectResult -> {
  241 + // TODO @haohao:if return,减少括号哈;
249 if (connectResult.succeeded()) { 242 if (connectResult.succeeded()) {
250 if (isReconnect) { 243 if (isReconnect) {
251 log.info("[connectMqtt][MQTT 客户端重连成功, host: {}, port: {}]", host, port); 244 log.info("[connectMqtt][MQTT 客户端重连成功, host: {}, port: {}]", host, port);
@@ -253,16 +246,15 @@ public class IotMqttUpstreamProtocol { @@ -253,16 +246,15 @@ public class IotMqttUpstreamProtocol {
253 log.info("[connectMqtt][MQTT 客户端连接成功, host: {}, port: {}]", host, port); 246 log.info("[connectMqtt][MQTT 客户端连接成功, host: {}, port: {}]", host, port);
254 } 247 }
255 248
256 - // 3. 设置处理器 249 + // 设置处理器
257 setupMqttHandlers(); 250 setupMqttHandlers();
258 -  
259 - // 4. 订阅主题 251 + // 订阅主题
260 subscribeToTopics(); 252 subscribeToTopics();
261 -  
262 } else { 253 } else {
263 log.error("[connectMqtt][连接 MQTT Broker 失败, host: {}, port: {}, isReconnect: {}]", 254 log.error("[connectMqtt][连接 MQTT Broker 失败, host: {}, port: {}, isReconnect: {}]",
264 host, port, isReconnect, connectResult.cause()); 255 host, port, isReconnect, connectResult.cause());
265 256
  257 + // TODO @haohao:体感上,是不是首次必须连接成功?类似 mysql;首次要连接上,然后后续可以重连;
266 if (!isReconnect) { 258 if (!isReconnect) {
267 // 首次连接失败时,也要尝试重连 259 // 首次连接失败时,也要尝试重连
268 log.warn("[connectMqtt][首次连接失败,将开始重连机制]"); 260 log.warn("[connectMqtt][首次连接失败,将开始重连机制]");
@@ -279,14 +271,11 @@ public class IotMqttUpstreamProtocol { @@ -279,14 +271,11 @@ public class IotMqttUpstreamProtocol {
279 * 创建 MQTT 客户端 271 * 创建 MQTT 客户端
280 */ 272 */
281 private void createMqttClient() { 273 private void createMqttClient() {
282 - log.debug("[createMqttClient][创建 MQTT 客户端, clientId: {}]", emqxProperties.getMqttClientId());  
283 -  
284 MqttClientOptions options = new MqttClientOptions() 274 MqttClientOptions options = new MqttClientOptions()
285 .setClientId(emqxProperties.getMqttClientId()) 275 .setClientId(emqxProperties.getMqttClientId())
286 .setUsername(emqxProperties.getMqttUsername()) 276 .setUsername(emqxProperties.getMqttUsername())
287 .setPassword(emqxProperties.getMqttPassword()) 277 .setPassword(emqxProperties.getMqttPassword())
288 .setSsl(emqxProperties.getMqttSsl()); 278 .setSsl(emqxProperties.getMqttSsl());
289 -  
290 this.mqttClient = MqttClient.create(vertx, options); 279 this.mqttClient = MqttClient.create(vertx, options);
291 } 280 }
292 281
@@ -294,6 +283,7 @@ public class IotMqttUpstreamProtocol { @@ -294,6 +283,7 @@ public class IotMqttUpstreamProtocol {
294 * 设置 MQTT 处理器 283 * 设置 MQTT 处理器
295 */ 284 */
296 private void setupMqttHandlers() { 285 private void setupMqttHandlers() {
  286 + // TODO @haohao:mqttClient 一定非空;
297 if (mqttClient == null) { 287 if (mqttClient == null) {
298 log.warn("[setupMqttHandlers][MQTT 客户端为空,跳过处理器设置]"); 288 log.warn("[setupMqttHandlers][MQTT 客户端为空,跳过处理器设置]");
299 return; 289 return;
@@ -311,6 +301,7 @@ public class IotMqttUpstreamProtocol { @@ -311,6 +301,7 @@ public class IotMqttUpstreamProtocol {
311 }); 301 });
312 302
313 // 设置消息处理器 303 // 设置消息处理器
  304 + // TODO @haohao:upstreamHandler 一定非空;
314 if (upstreamHandler != null) { 305 if (upstreamHandler != null) {
315 mqttClient.publishHandler(upstreamHandler::handle); 306 mqttClient.publishHandler(upstreamHandler::handle);
316 log.debug("[setupMqttHandlers][MQTT 消息处理器设置完成]"); 307 log.debug("[setupMqttHandlers][MQTT 消息处理器设置完成]");
@@ -328,7 +319,6 @@ public class IotMqttUpstreamProtocol { @@ -328,7 +319,6 @@ public class IotMqttUpstreamProtocol {
328 log.warn("[subscribeToTopics][订阅主题列表为空, 跳过订阅]"); 319 log.warn("[subscribeToTopics][订阅主题列表为空, 跳过订阅]");
329 return; 320 return;
330 } 321 }
331 -  
332 if (mqttClient == null || !mqttClient.isConnected()) { 322 if (mqttClient == null || !mqttClient.isConnected()) {
333 log.warn("[subscribeToTopics][MQTT 客户端未连接, 跳过订阅]"); 323 log.warn("[subscribeToTopics][MQTT 客户端未连接, 跳过订阅]");
334 return; 324 return;
@@ -337,10 +327,12 @@ public class IotMqttUpstreamProtocol { @@ -337,10 +327,12 @@ public class IotMqttUpstreamProtocol {
337 int qos = emqxProperties.getMqttQos(); 327 int qos = emqxProperties.getMqttQos();
338 log.info("[subscribeToTopics][开始订阅主题, 共 {} 个, QoS: {}]", topicList.size(), qos); 328 log.info("[subscribeToTopics][开始订阅主题, 共 {} 个, QoS: {}]", topicList.size(), qos);
339 329
  330 + // TODO @haohao:使用 atomicinteger 会更合适;
340 int[] successCount = { 0 }; // 使用数组以便在 lambda 中修改 331 int[] successCount = { 0 }; // 使用数组以便在 lambda 中修改
341 int[] failCount = { 0 }; 332 int[] failCount = { 0 };
342 333
343 for (String topic : topicList) { 334 for (String topic : topicList) {
  335 + // TODO @haohao:MqttClient subscribe(Map<String, Integer> topics, 是不是更简洁哈;
344 mqttClient.subscribe(topic, qos, subscribeResult -> { 336 mqttClient.subscribe(topic, qos, subscribeResult -> {
345 if (subscribeResult.succeeded()) { 337 if (subscribeResult.succeeded()) {
346 successCount[0]++; 338 successCount[0]++;
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/router/IotMqttDownstreamHandler.java
@@ -15,7 +15,7 @@ import lombok.extern.slf4j.Slf4j; @@ -15,7 +15,7 @@ import lombok.extern.slf4j.Slf4j;
15 /** 15 /**
16 * IoT 网关 MQTT 下行消息处理器 16 * IoT 网关 MQTT 下行消息处理器
17 * <p> 17 * <p>
18 - * 从消息总线接收到下行消息,然后发布到 MQTT Broker 18 + * 从消息总线接收到下行消息,然后发布到 MQTT Broker,从而被设备所接收
19 * 19 *
20 * @author 芋道源码 20 * @author 芋道源码
21 */ 21 */
@@ -23,7 +23,9 @@ import lombok.extern.slf4j.Slf4j; @@ -23,7 +23,9 @@ import lombok.extern.slf4j.Slf4j;
23 public class IotMqttDownstreamHandler { 23 public class IotMqttDownstreamHandler {
24 24
25 private final IotMqttUpstreamProtocol protocol; 25 private final IotMqttUpstreamProtocol protocol;
  26 +
26 private final IotDeviceService deviceService; 27 private final IotDeviceService deviceService;
  28 +
27 private final IotDeviceMessageService deviceMessageService; 29 private final IotDeviceMessageService deviceMessageService;
28 30
29 public IotMqttDownstreamHandler(IotMqttUpstreamProtocol protocol) { 31 public IotMqttDownstreamHandler(IotMqttUpstreamProtocol protocol) {
@@ -41,25 +43,24 @@ public class IotMqttDownstreamHandler { @@ -41,25 +43,24 @@ public class IotMqttDownstreamHandler {
41 // 1. 获取设备信息(使用缓存) 43 // 1. 获取设备信息(使用缓存)
42 IotDeviceRespDTO deviceInfo = deviceService.getDeviceFromCache(message.getDeviceId()); 44 IotDeviceRespDTO deviceInfo = deviceService.getDeviceFromCache(message.getDeviceId());
43 if (deviceInfo == null) { 45 if (deviceInfo == null) {
44 - log.warn("[handle][设备信息不存在, deviceId: {}]", message.getDeviceId()); 46 + log.error("[handle][设备信息({})不存在]", message.getDeviceId());
45 return; 47 return;
46 } 48 }
47 49
48 - // 2. 根据方法构建主题 50 + // 2.1 根据方法构建主题
49 String topic = buildTopicByMethod(message.getMethod(), deviceInfo.getProductKey(), deviceInfo.getDeviceName()); 51 String topic = buildTopicByMethod(message.getMethod(), deviceInfo.getProductKey(), deviceInfo.getDeviceName());
50 if (StrUtil.isBlank(topic)) { 52 if (StrUtil.isBlank(topic)) {
51 log.warn("[handle][未知的消息方法: {}]", message.getMethod()); 53 log.warn("[handle][未知的消息方法: {}]", message.getMethod());
52 return; 54 return;
53 } 55 }
54 -  
55 - // 3. 构建载荷 56 + // 2.2 构建载荷
  57 + // TODO @haohao:这里是不是 encode 就可以发拉?因为本身就 json 化了。
56 JSONObject payload = buildDownstreamPayload(message); 58 JSONObject payload = buildDownstreamPayload(message);
57 -  
58 - // 4. 发布消息 59 + // 2.3 发布消息
59 protocol.publishMessage(topic, payload.toString()); 60 protocol.publishMessage(topic, payload.toString());
60 - log.debug("[handle][发布下行消息成功, method: {}, topic: {}]", message.getMethod(), topic);  
61 } 61 }
62 62
  63 + // TODO @haohao:这个是不是也可以计算;IotDeviceMessageUtils 的 isReplyMessage;这样就直接生成了;
63 /** 64 /**
64 * 根据方法构建主题 65 * 根据方法构建主题
65 * 66 *
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/router/IotMqttHttpAuthHandler.java
@@ -35,12 +35,10 @@ public class IotMqttHttpAuthHandler { @@ -35,12 +35,10 @@ public class IotMqttHttpAuthHandler {
35 * 认证允许结果 35 * 认证允许结果
36 */ 36 */
37 private static final String RESULT_ALLOW = "allow"; 37 private static final String RESULT_ALLOW = "allow";
38 -  
39 /** 38 /**
40 * 认证拒绝结果 39 * 认证拒绝结果
41 */ 40 */
42 private static final String RESULT_DENY = "deny"; 41 private static final String RESULT_DENY = "deny";
43 -  
44 /** 42 /**
45 * 认证忽略结果 43 * 认证忽略结果
46 */ 44 */
@@ -53,14 +51,11 @@ public class IotMqttHttpAuthHandler { @@ -53,14 +51,11 @@ public class IotMqttHttpAuthHandler {
53 private static final String EVENT_CLIENT_DISCONNECTED = "client.disconnected"; 51 private static final String EVENT_CLIENT_DISCONNECTED = "client.disconnected";
54 52
55 private final IotMqttUpstreamProtocol protocol; 53 private final IotMqttUpstreamProtocol protocol;
  54 +
56 private final IotDeviceMessageService deviceMessageService; 55 private final IotDeviceMessageService deviceMessageService;
  56 +
57 private final IotDeviceCommonApi deviceApi; 57 private final IotDeviceCommonApi deviceApi;
58 58
59 - /**  
60 - * 构造器  
61 - *  
62 - * @param protocol MQTT 协议实例  
63 - */  
64 public IotMqttHttpAuthHandler(IotMqttUpstreamProtocol protocol) { 59 public IotMqttHttpAuthHandler(IotMqttUpstreamProtocol protocol) {
65 this.protocol = protocol; 60 this.protocol = protocol;
66 this.deviceMessageService = SpringUtil.getBean(IotDeviceMessageService.class); 61 this.deviceMessageService = SpringUtil.getBean(IotDeviceMessageService.class);
@@ -72,19 +67,15 @@ public class IotMqttHttpAuthHandler { @@ -72,19 +67,15 @@ public class IotMqttHttpAuthHandler {
72 */ 67 */
73 public void handleAuth(RoutingContext context) { 68 public void handleAuth(RoutingContext context) {
74 try { 69 try {
75 - // 解析请求体 70 + // 参数校验
76 JsonObject body = parseRequestBody(context); 71 JsonObject body = parseRequestBody(context);
77 if (body == null) { 72 if (body == null) {
78 return; 73 return;
79 } 74 }
80 -  
81 String clientId = body.getString("clientid"); 75 String clientId = body.getString("clientid");
82 String username = body.getString("username"); 76 String username = body.getString("username");
83 String password = body.getString("password"); 77 String password = body.getString("password");
84 -  
85 log.debug("[handleAuth][设备认证请求: clientId={}, username={}]", clientId, username); 78 log.debug("[handleAuth][设备认证请求: clientId={}, username={}]", clientId, username);
86 -  
87 - // 参数校验  
88 if (StrUtil.hasEmpty(clientId, username, password)) { 79 if (StrUtil.hasEmpty(clientId, username, password)) {
89 log.info("[handleAuth][认证参数不完整: clientId={}, username={}]", clientId, username); 80 log.info("[handleAuth][认证参数不完整: clientId={}, username={}]", clientId, username);
90 sendAuthResponse(context, RESULT_DENY, false, "认证参数不完整"); 81 sendAuthResponse(context, RESULT_DENY, false, "认证参数不完整");
@@ -94,13 +85,13 @@ public class IotMqttHttpAuthHandler { @@ -94,13 +85,13 @@ public class IotMqttHttpAuthHandler {
94 // 执行设备认证 85 // 执行设备认证
95 boolean authResult = performDeviceAuth(clientId, username, password); 86 boolean authResult = performDeviceAuth(clientId, username, password);
96 if (authResult) { 87 if (authResult) {
  88 + // TODO @haohao:是不是两条 info,直接打认证结果:authResult
97 log.info("[handleAuth][设备认证成功: {}]", username); 89 log.info("[handleAuth][设备认证成功: {}]", username);
98 sendAuthResponse(context, RESULT_ALLOW, false, null); 90 sendAuthResponse(context, RESULT_ALLOW, false, null);
99 } else { 91 } else {
100 log.info("[handleAuth][设备认证失败: {}]", username); 92 log.info("[handleAuth][设备认证失败: {}]", username);
101 sendAuthResponse(context, RESULT_DENY, false, DEVICE_AUTH_FAIL.getMsg()); 93 sendAuthResponse(context, RESULT_DENY, false, DEVICE_AUTH_FAIL.getMsg());
102 } 94 }
103 -  
104 } catch (Exception e) { 95 } catch (Exception e) {
105 log.error("[handleAuth][设备认证异常]", e); 96 log.error("[handleAuth][设备认证异常]", e);
106 sendAuthResponse(context, RESULT_IGNORE, false, "认证服务异常"); 97 sendAuthResponse(context, RESULT_IGNORE, false, "认证服务异常");
@@ -119,10 +110,8 @@ public class IotMqttHttpAuthHandler { @@ -119,10 +110,8 @@ public class IotMqttHttpAuthHandler {
119 if (body == null) { 110 if (body == null) {
120 return; 111 return;
121 } 112 }
122 -  
123 String event = body.getString("event"); 113 String event = body.getString("event");
124 String username = body.getString("username"); 114 String username = body.getString("username");
125 -  
126 log.debug("[handleEvent][收到事件: {} - {}]", event, username); 115 log.debug("[handleEvent][收到事件: {} - {}]", event, username);
127 116
128 // 根据事件类型进行分发处理 117 // 根据事件类型进行分发处理
@@ -138,12 +127,12 @@ public class IotMqttHttpAuthHandler { @@ -138,12 +127,12 @@ public class IotMqttHttpAuthHandler {
138 break; 127 break;
139 } 128 }
140 129
141 - // EMQX Webhook 只需要200状态码,无需响应体 130 + // EMQX Webhook 只需要 200 状态码,无需响应体
142 context.response().setStatusCode(SUCCESS_STATUS_CODE).end(); 131 context.response().setStatusCode(SUCCESS_STATUS_CODE).end();
143 -  
144 } catch (Exception e) { 132 } catch (Exception e) {
  133 + // TODO @haohao:body 可以打印出来
145 log.error("[handleEvent][事件处理失败]", e); 134 log.error("[handleEvent][事件处理失败]", e);
146 - // 即使处理失败,也返回200避免EMQX重试 135 + // 即使处理失败,也返回 200 避免EMQX重试
147 context.response().setStatusCode(SUCCESS_STATUS_CODE).end(); 136 context.response().setStatusCode(SUCCESS_STATUS_CODE).end();
148 } 137 }
149 } 138 }
@@ -163,7 +152,6 @@ public class IotMqttHttpAuthHandler { @@ -163,7 +152,6 @@ public class IotMqttHttpAuthHandler {
163 private void handleClientDisconnected(JsonObject body) { 152 private void handleClientDisconnected(JsonObject body) {
164 String username = body.getString("username"); 153 String username = body.getString("username");
165 String reason = body.getString("reason"); 154 String reason = body.getString("reason");
166 -  
167 log.info("[handleClientDisconnected][设备下线: {} ({})]", username, reason); 155 log.info("[handleClientDisconnected][设备下线: {} ({})]", username, reason);
168 handleDeviceStateChange(username, false); 156 handleDeviceStateChange(username, false);
169 } 157 }
@@ -184,6 +172,7 @@ public class IotMqttHttpAuthHandler { @@ -184,6 +172,7 @@ public class IotMqttHttpAuthHandler {
184 } 172 }
185 return body; 173 return body;
186 } catch (Exception e) { 174 } catch (Exception e) {
  175 + // TODO @haohao:最好把 body 打印出来;
187 log.error("[parseRequestBody][解析请求体失败]", e); 176 log.error("[parseRequestBody][解析请求体失败]", e);
188 sendAuthResponse(context, RESULT_IGNORE, false, "请求体格式错误"); 177 sendAuthResponse(context, RESULT_IGNORE, false, "请求体格式错误");
189 return null; 178 return null;
@@ -201,10 +190,7 @@ public class IotMqttHttpAuthHandler { @@ -201,10 +190,7 @@ public class IotMqttHttpAuthHandler {
201 private boolean performDeviceAuth(String clientId, String username, String password) { 190 private boolean performDeviceAuth(String clientId, String username, String password) {
202 try { 191 try {
203 CommonResult<Boolean> result = deviceApi.authDevice(new IotDeviceAuthReqDTO() 192 CommonResult<Boolean> result = deviceApi.authDevice(new IotDeviceAuthReqDTO()
204 - .setClientId(clientId)  
205 - .setUsername(username)  
206 - .setPassword(password));  
207 - 193 + .setClientId(clientId).setUsername(username).setPassword(password));
208 result.checkError(); 194 result.checkError();
209 return BooleanUtil.isTrue(result.getData()); 195 return BooleanUtil.isTrue(result.getData());
210 } catch (Exception e) { 196 } catch (Exception e) {
@@ -220,11 +206,10 @@ public class IotMqttHttpAuthHandler { @@ -220,11 +206,10 @@ public class IotMqttHttpAuthHandler {
220 * @param online 是否在线 206 * @param online 是否在线
221 */ 207 */
222 private void handleDeviceStateChange(String username, boolean online) { 208 private void handleDeviceStateChange(String username, boolean online) {
  209 + // 解析设备信息
223 if (StrUtil.isEmpty(username) || "undefined".equals(username)) { 210 if (StrUtil.isEmpty(username) || "undefined".equals(username)) {
224 return; 211 return;
225 } 212 }
226 -  
227 - // 解析设备信息  
228 IotDeviceAuthUtils.DeviceInfo deviceInfo = IotDeviceAuthUtils.parseUsername(username); 213 IotDeviceAuthUtils.DeviceInfo deviceInfo = IotDeviceAuthUtils.parseUsername(username);
229 if (deviceInfo == null) { 214 if (deviceInfo == null) {
230 log.debug("[handleDeviceStateChange][跳过非设备连接: {}]", username); 215 log.debug("[handleDeviceStateChange][跳过非设备连接: {}]", username);
@@ -232,6 +217,7 @@ public class IotMqttHttpAuthHandler { @@ -232,6 +217,7 @@ public class IotMqttHttpAuthHandler {
232 } 217 }
233 218
234 try { 219 try {
  220 + // TODO @haohao:serverId 获取非空,可以忽略掉;
235 String serverId = protocol.getServerId(); 221 String serverId = protocol.getServerId();
236 if (StrUtil.isEmpty(serverId)) { 222 if (StrUtil.isEmpty(serverId)) {
237 log.error("[handleDeviceStateChange][获取服务器ID失败]"); 223 log.error("[handleDeviceStateChange][获取服务器ID失败]");
@@ -241,15 +227,14 @@ public class IotMqttHttpAuthHandler { @@ -241,15 +227,14 @@ public class IotMqttHttpAuthHandler {
241 // 构建设备状态消息 227 // 构建设备状态消息
242 IotDeviceMessage message = online ? IotDeviceMessage.buildStateOnline() 228 IotDeviceMessage message = online ? IotDeviceMessage.buildStateOnline()
243 : IotDeviceMessage.buildStateOffline(); 229 : IotDeviceMessage.buildStateOffline();
244 -  
245 // 发送消息到消息总线 230 // 发送消息到消息总线
246 deviceMessageService.sendDeviceMessage(message, 231 deviceMessageService.sendDeviceMessage(message,
247 deviceInfo.getProductKey(), deviceInfo.getDeviceName(), serverId); 232 deviceInfo.getProductKey(), deviceInfo.getDeviceName(), serverId);
248 233
  234 + // TODO @haohao:online 不用翻译
249 log.info("[handleDeviceStateChange][设备状态更新: {}/{} -> {}]", 235 log.info("[handleDeviceStateChange][设备状态更新: {}/{} -> {}]",
250 deviceInfo.getProductKey(), deviceInfo.getDeviceName(), 236 deviceInfo.getProductKey(), deviceInfo.getDeviceName(),
251 online ? "在线" : "离线"); 237 online ? "在线" : "离线");
252 -  
253 } catch (Exception e) { 238 } catch (Exception e) {
254 log.error("[handleDeviceStateChange][发送设备状态消息失败: {}]", username, e); 239 log.error("[handleDeviceStateChange][发送设备状态消息失败: {}]", username, e);
255 } 240 }
@@ -277,10 +262,6 @@ public class IotMqttHttpAuthHandler { @@ -277,10 +262,6 @@ public class IotMqttHttpAuthHandler {
277 // response.put("expire_at", System.currentTimeMillis() / 1000 + 3600); 262 // response.put("expire_at", System.currentTimeMillis() / 1000 + 3600);
278 263
279 // 记录详细的响应日志(message仅用于日志,不返回给EMQX) 264 // 记录详细的响应日志(message仅用于日志,不返回给EMQX)
280 - if (StrUtil.isNotEmpty(message)) {  
281 - log.debug("[sendAuthResponse][响应详情: result={}, message={}]", result, message);  
282 - }  
283 -  
284 context.response() 265 context.response()
285 .setStatusCode(SUCCESS_STATUS_CODE) 266 .setStatusCode(SUCCESS_STATUS_CODE)
286 .putHeader("Content-Type", "application/json; charset=utf-8") 267 .putHeader("Content-Type", "application/json; charset=utf-8")
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/router/IotMqttUpstreamHandler.java
@@ -20,6 +20,7 @@ import java.util.Arrays; @@ -20,6 +20,7 @@ import java.util.Arrays;
20 public class IotMqttUpstreamHandler { 20 public class IotMqttUpstreamHandler {
21 21
22 private final IotDeviceMessageService deviceMessageService; 22 private final IotDeviceMessageService deviceMessageService;
  23 +
23 private final String serverId; 24 private final String serverId;
24 25
25 public IotMqttUpstreamHandler(IotMqttUpstreamProtocol protocol) { 26 public IotMqttUpstreamHandler(IotMqttUpstreamProtocol protocol) {
@@ -30,56 +31,50 @@ public class IotMqttUpstreamHandler { @@ -30,56 +31,50 @@ public class IotMqttUpstreamHandler {
30 /** 31 /**
31 * 处理 MQTT 发布消息 32 * 处理 MQTT 发布消息
32 */ 33 */
33 - public void handle(MqttPublishMessage message) {  
34 - String topic = message.topicName();  
35 - byte[] payload = message.payload().getBytes();  
36 -  
37 - log.debug("[handle][收到 MQTT 消息, topic: {}]", topic);  
38 - 34 + public void handle(MqttPublishMessage mqttMessage) {
  35 + String topic = mqttMessage.topicName();
  36 + byte[] payload = mqttMessage.payload().getBytes();
39 try { 37 try {
40 // 1. 前置校验 38 // 1. 前置校验
41 if (StrUtil.isBlank(topic)) { 39 if (StrUtil.isBlank(topic)) {
42 log.warn("[handle][主题为空, 忽略消息]"); 40 log.warn("[handle][主题为空, 忽略消息]");
43 return; 41 return;
44 } 42 }
45 - // 注意:payload 可以为空  
46 43
47 - // 2. 识别并验证消息类型 44 + // 2.1 识别并验证消息类型
48 String messageType = getMessageType(topic); 45 String messageType = getMessageType(topic);
  46 + // TODO @haohao:可以使用 hutool 的,它的字符串拼接更简单;
49 Assert.notNull(messageType, String.format("未知的消息类型, topic(%s)", topic)); 47 Assert.notNull(messageType, String.format("未知的消息类型, topic(%s)", topic));
50 - log.debug("[handle][接收到上行消息({}), topic: {}]", messageType, topic);  
51 -  
52 - // 3. 解析主题,获取 productKey 和 deviceName 48 + // 2.2 解析主题,获取 productKey 和 deviceName
  49 + // TODO @haohao:体感 getMessageType 和下面,都 split;是不是一次就 ok 拉;1)split 掉;2)2、3 位置是 productKey、deviceName;3)4 开始还是 method
53 String[] topicParts = topic.split("/"); 50 String[] topicParts = topic.split("/");
54 if (topicParts.length < 4) { 51 if (topicParts.length < 4) {
55 - log.warn("[handle][主题格式不正确,无法解析 productKey 和 deviceName][topic: {}]", topic); 52 + log.warn("[handle][topic({}) 格式不正确,无法解析 productKey 和 deviceName]", topic);
56 return; 53 return;
57 } 54 }
58 String productKey = topicParts[2]; 55 String productKey = topicParts[2];
59 String deviceName = topicParts[3]; 56 String deviceName = topicParts[3];
  57 + // TODO @haohao:是不是要判断,部分为空,就不行呀;
60 if (StrUtil.isAllBlank(productKey, deviceName)) { 58 if (StrUtil.isAllBlank(productKey, deviceName)) {
61 - log.warn("[handle][主题中 productKey 或 deviceName 为空][topic: {}]", topic); 59 + log.warn("[handle][topic({}) 格式不正确,productKey 和 deviceName 部分为空]", topic);
62 return; 60 return;
63 } 61 }
64 62
65 - // 4. 解码消息  
66 - IotDeviceMessage deviceMessage = deviceMessageService.decodeDeviceMessage(  
67 - payload, productKey, deviceName);  
68 - if (deviceMessage == null) {  
69 - log.warn("[handle][消息解码失败][topic: {}]", topic); 63 + // 3. 解码消息
  64 + IotDeviceMessage message = deviceMessageService.decodeDeviceMessage(payload, productKey, deviceName);
  65 + if (message == null) {
  66 + log.warn("[handle][topic({}) payload({}) 消息解码失败", topic, new String(payload));
70 return; 67 return;
71 } 68 }
72 69
73 - // 5. 发送消息到队列  
74 - deviceMessageService.sendDeviceMessage(deviceMessage, productKey, deviceName, serverId);  
75 -  
76 - // 6. 记录成功日志  
77 - log.debug("[handle][处理上行消息({})成功, topic: {}]", messageType, topic); 70 + // 4. 发送消息到队列
  71 + deviceMessageService.sendDeviceMessage(message, productKey, deviceName, serverId);
78 } catch (Exception e) { 72 } catch (Exception e) {
79 - log.error("[handle][处理 MQTT 消息失败][topic: {}][payload: {}]", topic, new String(payload), e); 73 + log.error("[handle][topic({}) payload({}) 处理异常]", topic, new String(payload), e);
80 } 74 }
81 } 75 }
82 76
  77 + // TODO @haohao:是不是 getMethodFromTopic?
83 /** 78 /**
84 * 从主题中,获得消息类型 79 * 从主题中,获得消息类型
85 * 80 *
@@ -89,9 +84,9 @@ public class IotMqttUpstreamHandler { @@ -89,9 +84,9 @@ public class IotMqttUpstreamHandler {
89 private String getMessageType(String topic) { 84 private String getMessageType(String topic) {
90 String[] topicParts = topic.split("/"); 85 String[] topicParts = topic.split("/");
91 // 约定:topic 第 4 个部分开始为消息类型 86 // 约定:topic 第 4 个部分开始为消息类型
92 - // 例如:/sys/{productKey}/{deviceName}/thing/property/post ->  
93 - // thing/property/post 87 + // 例如:/sys/{productKey}/{deviceName}/thing/property/post -> thing/property/post
94 if (topicParts.length > 4) { 88 if (topicParts.length > 4) {
  89 + // TODO @haohao:是不是 subString 前 3 个,性能更好;
95 return String.join("/", Arrays.copyOfRange(topicParts, 4, topicParts.length)); 90 return String.join("/", Arrays.copyOfRange(topicParts, 4, topicParts.length));
96 } 91 }
97 return topicParts[topicParts.length - 1]; 92 return topicParts[topicParts.length - 1];