Commit 7e49c901562889b22cbe85e153ad0cf23d51d9bd
1 parent
f1368d9e
review:【IoT 物联网】MqTT 协议
Showing
6 changed files
with
68 additions
and
104 deletions
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]; |