Commit 3ca4cf265a0323bc06768a5be88955b1a9c1cdda
1 parent
bf41d47f
feat:【IoT 物联网】优化根据消息方法和回复状态构建主题逻辑
Showing
2 changed files
with
20 additions
and
138 deletions
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/emqx/router/IotEmqxDownstreamHandler.java
| @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.emqx.router; | @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.emqx.router; | ||
| 3 | import cn.hutool.core.util.StrUtil; | 3 | import cn.hutool.core.util.StrUtil; |
| 4 | import cn.hutool.extra.spring.SpringUtil; | 4 | import cn.hutool.extra.spring.SpringUtil; |
| 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; | ||
| 7 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | 6 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 8 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; | 7 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; |
| 9 | import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxUpstreamProtocol; | 8 | import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxUpstreamProtocol; |
| @@ -69,58 +68,11 @@ public class IotEmqxDownstreamHandler { | @@ -69,58 +68,11 @@ public class IotEmqxDownstreamHandler { | ||
| 69 | * @return 构建的主题,如果方法不支持返回 null | 68 | * @return 构建的主题,如果方法不支持返回 null |
| 70 | */ | 69 | */ |
| 71 | private String buildTopicByMethod(IotDeviceMessage message, String productKey, String deviceName) { | 70 | private String buildTopicByMethod(IotDeviceMessage message, String productKey, String deviceName) { |
| 72 | - // 1. 解析消息方法 | ||
| 73 | - IotDeviceMessageMethodEnum methodEnum = IotDeviceMessageMethodEnum.of(message.getMethod()); | ||
| 74 | - if (methodEnum == null) { | ||
| 75 | - log.warn("[buildTopicByMethod][未知的消息方法: {}]", message.getMethod()); | ||
| 76 | - return null; | ||
| 77 | - } | ||
| 78 | - | ||
| 79 | - // 2. 根据消息方法和回复状态,构建 topic | 71 | + // 1. 判断是否为回复消息 |
| 80 | boolean isReply = IotDeviceMessageUtils.isReplyMessage(message); | 72 | boolean isReply = IotDeviceMessageUtils.isReplyMessage(message); |
| 81 | 73 | ||
| 82 | - // TODO @haohao:看看基于 message 的 method 去反向推导; | ||
| 83 | - // 3. 根据消息方法类型构建对应的主题 | ||
| 84 | - switch (methodEnum) { | ||
| 85 | - case PROPERTY_POST: | ||
| 86 | - // 属性上报:只支持回复消息(下行) | ||
| 87 | - if (isReply) { | ||
| 88 | - return IotMqttTopicUtils.buildPropertyPostReplyTopic(productKey, deviceName); | ||
| 89 | - } | ||
| 90 | - break; | ||
| 91 | - case PROPERTY_SET: | ||
| 92 | - // 属性设置:只支持非回复消息(下行) | ||
| 93 | - if (!isReply) { | ||
| 94 | - return IotMqttTopicUtils.buildPropertySetTopic(productKey, deviceName); | ||
| 95 | - } | ||
| 96 | - break; | ||
| 97 | - case EVENT_POST: | ||
| 98 | - // 事件上报:只支持回复消息(下行) | ||
| 99 | - if (isReply) { | ||
| 100 | - return IotMqttTopicUtils.buildEventPostReplyTopicGeneric(productKey, deviceName); | ||
| 101 | - } | ||
| 102 | - break; | ||
| 103 | - case SERVICE_INVOKE: | ||
| 104 | - // 服务调用:支持请求和回复 | ||
| 105 | - if (isReply) { | ||
| 106 | - return IotMqttTopicUtils.buildServiceReplyTopicGeneric(productKey, deviceName); | ||
| 107 | - } else { | ||
| 108 | - return IotMqttTopicUtils.buildServiceTopicGeneric(productKey, deviceName); | ||
| 109 | - } | ||
| 110 | - case CONFIG_PUSH: | ||
| 111 | - // 配置推送:平台向设备推送配置(下行请求),设备回复确认(上行回复) | ||
| 112 | - if (!isReply) { | ||
| 113 | - return IotMqttTopicUtils.buildConfigPushTopic(productKey, deviceName); | ||
| 114 | - } | ||
| 115 | - break; | ||
| 116 | - default: | ||
| 117 | - log.warn("[buildTopicByMethod][未处理的消息方法: {}]", methodEnum); | ||
| 118 | - break; | ||
| 119 | - } | ||
| 120 | - | ||
| 121 | - log.warn("[buildTopicByMethod][暂时不支持的下行消息: method={}, isReply={}]", | ||
| 122 | - message.getMethod(), isReply); | ||
| 123 | - return null; | 74 | + // 2. 根据消息方法类型构建对应的主题 |
| 75 | + return IotMqttTopicUtils.buildTopicByMethod(message.getMethod(), productKey, deviceName, isReply); | ||
| 124 | } | 76 | } |
| 125 | 77 | ||
| 126 | } | 78 | } |
| 127 | \ No newline at end of file | 79 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/util/IotMqttTopicUtils.java
| 1 | package cn.iocoder.yudao.module.iot.gateway.util; | 1 | package cn.iocoder.yudao.module.iot.gateway.util; |
| 2 | 2 | ||
| 3 | +import cn.hutool.core.util.StrUtil; | ||
| 4 | + | ||
| 3 | /** | 5 | /** |
| 4 | * IoT 网关 MQTT 主题工具类 | 6 | * IoT 网关 MQTT 主题工具类 |
| 5 | * <p> | 7 | * <p> |
| @@ -16,12 +18,6 @@ public final class IotMqttTopicUtils { | @@ -16,12 +18,6 @@ public final class IotMqttTopicUtils { | ||
| 16 | */ | 18 | */ |
| 17 | private static final String SYS_TOPIC_PREFIX = "/sys/"; | 19 | private static final String SYS_TOPIC_PREFIX = "/sys/"; |
| 18 | 20 | ||
| 19 | - // TODO @haohao:这个要删除哇? | ||
| 20 | - /** | ||
| 21 | - * 服务调用主题前缀 | ||
| 22 | - */ | ||
| 23 | - private static final String SERVICE_TOPIC_PREFIX = "/thing/"; | ||
| 24 | - | ||
| 25 | // ========== MQTT HTTP 接口路径常量 ========== | 21 | // ========== MQTT HTTP 接口路径常量 ========== |
| 26 | 22 | ||
| 27 | /** | 23 | /** |
| @@ -37,98 +33,32 @@ public final class IotMqttTopicUtils { | @@ -37,98 +33,32 @@ public final class IotMqttTopicUtils { | ||
| 37 | */ | 33 | */ |
| 38 | public static final String MQTT_EVENT_PATH = "/mqtt/event"; | 34 | public static final String MQTT_EVENT_PATH = "/mqtt/event"; |
| 39 | 35 | ||
| 40 | - // TODO @haohao:这个要删除哇? | ||
| 41 | - /** | ||
| 42 | - * MQTT 授权接口路径(预留) | ||
| 43 | - * 对应 EMQX HTTP 授权插件的授权检查接口 | ||
| 44 | - */ | ||
| 45 | - public static final String MQTT_AUTHZ_PATH = "/mqtt/authz"; | ||
| 46 | - | ||
| 47 | // ========== 工具方法 ========== | 36 | // ========== 工具方法 ========== |
| 48 | 37 | ||
| 49 | /** | 38 | /** |
| 50 | - * 构建设备主题前缀 | ||
| 51 | - * | ||
| 52 | - * @param productKey 产品 Key | ||
| 53 | - * @param deviceName 设备名称 | ||
| 54 | - * @return 设备主题前缀:/sys/{productKey}/{deviceName} | ||
| 55 | - */ | ||
| 56 | - private static String buildDeviceTopicPrefix(String productKey, String deviceName) { | ||
| 57 | - return SYS_TOPIC_PREFIX + productKey + "/" + deviceName; | ||
| 58 | - } | ||
| 59 | - | ||
| 60 | - /** | ||
| 61 | - * 构建设备属性设置主题 | 39 | + * 根据消息方法构建对应的主题 |
| 62 | * | 40 | * |
| 41 | + * @param method 消息方法,例如 thing.property.post | ||
| 63 | * @param productKey 产品 Key | 42 | * @param productKey 产品 Key |
| 64 | * @param deviceName 设备名称 | 43 | * @param deviceName 设备名称 |
| 44 | + * @param isReply 是否为回复消息 | ||
| 65 | * @return 完整的主题路径 | 45 | * @return 完整的主题路径 |
| 66 | */ | 46 | */ |
| 67 | - public static String buildPropertySetTopic(String productKey, String deviceName) { | ||
| 68 | - return buildDeviceTopicPrefix(productKey, deviceName) + "/thing/property/set"; | ||
| 69 | - } | 47 | + public static String buildTopicByMethod(String method, String productKey, String deviceName, boolean isReply) { |
| 48 | + if (StrUtil.isBlank(method)) { | ||
| 49 | + return null; | ||
| 50 | + } | ||
| 70 | 51 | ||
| 71 | - /** | ||
| 72 | - * 构建设备属性上报回复主题 | ||
| 73 | - * <p> | ||
| 74 | - * 当设备上报属性时,会收到该主题的回复 | ||
| 75 | - * | ||
| 76 | - * @param productKey 产品 Key | ||
| 77 | - * @param deviceName 设备名称 | ||
| 78 | - * @return 完整的主题路径 | ||
| 79 | - */ | ||
| 80 | - public static String buildPropertyPostReplyTopic(String productKey, String deviceName) { | ||
| 81 | - return buildDeviceTopicPrefix(productKey, deviceName) + "/thing/property/post_reply"; | ||
| 82 | - } | 52 | + // 1. 将点分隔符转换为斜杠 |
| 53 | + String topicSuffix = method.replace('.', '/'); | ||
| 83 | 54 | ||
| 84 | - /** | ||
| 85 | - * 构建设备配置推送主题 | ||
| 86 | - * | ||
| 87 | - * @param productKey 产品 Key | ||
| 88 | - * @param deviceName 设备名称 | ||
| 89 | - * @return 完整的主题路径 | ||
| 90 | - */ | ||
| 91 | - public static String buildConfigPushTopic(String productKey, String deviceName) { | ||
| 92 | - return buildDeviceTopicPrefix(productKey, deviceName) + "/thing/config/push"; | ||
| 93 | - } | 55 | + // 2. 对于回复消息,添加 _reply 后缀 |
| 56 | + if (isReply) { | ||
| 57 | + topicSuffix += "_reply"; | ||
| 58 | + } | ||
| 94 | 59 | ||
| 95 | - /** | ||
| 96 | - * 构建设备事件上报通用回复主题 | ||
| 97 | - * <p> | ||
| 98 | - * 不包含具体的事件标识符,事件标识符通过消息 data 中的 identifier 字段传递 | ||
| 99 | - * | ||
| 100 | - * @param productKey 产品 Key | ||
| 101 | - * @param deviceName 设备名称 | ||
| 102 | - * @return 完整的主题路径 | ||
| 103 | - */ | ||
| 104 | - public static String buildEventPostReplyTopicGeneric(String productKey, String deviceName) { | ||
| 105 | - return buildDeviceTopicPrefix(productKey, deviceName) + "/thing/event/post_reply"; | ||
| 106 | - } | ||
| 107 | - | ||
| 108 | - /** | ||
| 109 | - * 构建设备服务调用通用主题 | ||
| 110 | - * <p> | ||
| 111 | - * 不包含具体的服务标识符,服务标识符通过消息 data 中的 identifier 字段传递 | ||
| 112 | - * | ||
| 113 | - * @param productKey 产品 Key | ||
| 114 | - * @param deviceName 设备名称 | ||
| 115 | - * @return 完整的主题路径 | ||
| 116 | - */ | ||
| 117 | - public static String buildServiceTopicGeneric(String productKey, String deviceName) { | ||
| 118 | - return buildDeviceTopicPrefix(productKey, deviceName) + "/thing/service/invoke"; | ||
| 119 | - } | ||
| 120 | - | ||
| 121 | - /** | ||
| 122 | - * 构建设备服务调用通用回复主题 | ||
| 123 | - * <p> | ||
| 124 | - * 不包含具体的服务标识符,服务标识符通过消息 data 中的 identifier 字段传递 | ||
| 125 | - * | ||
| 126 | - * @param productKey 产品 Key | ||
| 127 | - * @param deviceName 设备名称 | ||
| 128 | - * @return 完整的主题路径 | ||
| 129 | - */ | ||
| 130 | - public static String buildServiceReplyTopicGeneric(String productKey, String deviceName) { | ||
| 131 | - return buildDeviceTopicPrefix(productKey, deviceName) + "/thing/service/invoke_reply"; | 60 | + // 3. 构建完整主题 |
| 61 | + return SYS_TOPIC_PREFIX + productKey + "/" + deviceName + "/" + topicSuffix; | ||
| 132 | } | 62 | } |
| 133 | 63 | ||
| 134 | } | 64 | } |
| 135 | \ No newline at end of file | 65 | \ No newline at end of file |