Commit af37176d5074c7f4cd6771add1a2c8c1b07e1bfc
1 parent
fbb66402
feat:【IOT】新增 MQTT 协议支持及相关消息解析器,完善协议转换器功能
Showing
15 changed files
with
558 additions
and
109 deletions
yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/java/cn/iocoder/yudao/module/iot/net/component/core/message/IotAlinkMessage.java renamed to yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/java/cn/iocoder/yudao/module/iot/net/component/core/message/IotMqttMessage.java
| ... | ... | @@ -8,16 +8,15 @@ import lombok.Data; |
| 8 | 8 | import java.util.Map; |
| 9 | 9 | |
| 10 | 10 | /** |
| 11 | - * IoT Alink 消息模型 | |
| 11 | + * IoT MQTT 消息模型 | |
| 12 | 12 | * <p> |
| 13 | - * 基于阿里云 Alink 协议规范实现的标准消息格式 | |
| 14 | - * @see <a href="https://help.aliyun.com/zh/iot/user-guide/alink-protocol-1">阿里云物联网 —— Alink 协议</a> | |
| 13 | + * 基于 MQTT 协议规范实现的标准消息格式,兼容 Alink 协议 | |
| 15 | 14 | * |
| 16 | 15 | * @author haohao |
| 17 | 16 | */ |
| 18 | 17 | @Data |
| 19 | 18 | @Builder |
| 20 | -public class IotAlinkMessage { | |
| 19 | +public class IotMqttMessage { | |
| 21 | 20 | |
| 22 | 21 | /** |
| 23 | 22 | * 消息 ID |
| ... | ... | @@ -69,11 +68,11 @@ public class IotAlinkMessage { |
| 69 | 68 | * @param requestId 请求 ID,为空时自动生成 |
| 70 | 69 | * @param serviceIdentifier 服务标识符 |
| 71 | 70 | * @param params 服务参数 |
| 72 | - * @return Alink 消息对象 | |
| 71 | + * @return MQTT 消息对象 | |
| 73 | 72 | */ |
| 74 | - public static IotAlinkMessage createServiceInvokeMessage(String requestId, String serviceIdentifier, | |
| 73 | + public static IotMqttMessage createServiceInvokeMessage(String requestId, String serviceIdentifier, | |
| 75 | 74 | Map<String, Object> params) { |
| 76 | - return IotAlinkMessage.builder() | |
| 75 | + return IotMqttMessage.builder() | |
| 77 | 76 | .id(requestId != null ? requestId : generateRequestId()) |
| 78 | 77 | .method("thing.service." + serviceIdentifier) |
| 79 | 78 | .params(params) |
| ... | ... | @@ -85,10 +84,10 @@ public class IotAlinkMessage { |
| 85 | 84 | * |
| 86 | 85 | * @param requestId 请求 ID,为空时自动生成 |
| 87 | 86 | * @param properties 设备属性 |
| 88 | - * @return Alink 消息对象 | |
| 87 | + * @return MQTT 消息对象 | |
| 89 | 88 | */ |
| 90 | - public static IotAlinkMessage createPropertySetMessage(String requestId, Map<String, Object> properties) { | |
| 91 | - return IotAlinkMessage.builder() | |
| 89 | + public static IotMqttMessage createPropertySetMessage(String requestId, Map<String, Object> properties) { | |
| 90 | + return IotMqttMessage.builder() | |
| 92 | 91 | .id(requestId != null ? requestId : generateRequestId()) |
| 93 | 92 | .method("thing.service.property.set") |
| 94 | 93 | .params(properties) |
| ... | ... | @@ -100,13 +99,13 @@ public class IotAlinkMessage { |
| 100 | 99 | * |
| 101 | 100 | * @param requestId 请求 ID,为空时自动生成 |
| 102 | 101 | * @param identifiers 要获取的属性标识符列表 |
| 103 | - * @return Alink 消息对象 | |
| 102 | + * @return MQTT 消息对象 | |
| 104 | 103 | */ |
| 105 | - public static IotAlinkMessage createPropertyGetMessage(String requestId, String[] identifiers) { | |
| 104 | + public static IotMqttMessage createPropertyGetMessage(String requestId, String[] identifiers) { | |
| 106 | 105 | JSONObject params = new JSONObject(); |
| 107 | 106 | params.set("identifiers", identifiers); |
| 108 | 107 | |
| 109 | - return IotAlinkMessage.builder() | |
| 108 | + return IotMqttMessage.builder() | |
| 110 | 109 | .id(requestId != null ? requestId : generateRequestId()) |
| 111 | 110 | .method("thing.service.property.get") |
| 112 | 111 | .params(params) |
| ... | ... | @@ -118,10 +117,10 @@ public class IotAlinkMessage { |
| 118 | 117 | * |
| 119 | 118 | * @param requestId 请求 ID,为空时自动生成 |
| 120 | 119 | * @param configs 设备配置 |
| 121 | - * @return Alink 消息对象 | |
| 120 | + * @return MQTT 消息对象 | |
| 122 | 121 | */ |
| 123 | - public static IotAlinkMessage createConfigSetMessage(String requestId, Map<String, Object> configs) { | |
| 124 | - return IotAlinkMessage.builder() | |
| 122 | + public static IotMqttMessage createConfigSetMessage(String requestId, Map<String, Object> configs) { | |
| 123 | + return IotMqttMessage.builder() | |
| 125 | 124 | .id(requestId != null ? requestId : generateRequestId()) |
| 126 | 125 | .method("thing.service.config.set") |
| 127 | 126 | .params(configs) |
| ... | ... | @@ -133,10 +132,10 @@ public class IotAlinkMessage { |
| 133 | 132 | * |
| 134 | 133 | * @param requestId 请求 ID,为空时自动生成 |
| 135 | 134 | * @param otaInfo OTA 升级信息 |
| 136 | - * @return Alink 消息对象 | |
| 135 | + * @return MQTT 消息对象 | |
| 137 | 136 | */ |
| 138 | - public static IotAlinkMessage createOtaUpgradeMessage(String requestId, Map<String, Object> otaInfo) { | |
| 139 | - return IotAlinkMessage.builder() | |
| 137 | + public static IotMqttMessage createOtaUpgradeMessage(String requestId, Map<String, Object> otaInfo) { | |
| 138 | + return IotMqttMessage.builder() | |
| 140 | 139 | .id(requestId != null ? requestId : generateRequestId()) |
| 141 | 140 | .method("thing.service.ota.upgrade") |
| 142 | 141 | .params(otaInfo) | ... | ... |
yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/net/component/emqx/downstream/IotDeviceDownstreamHandlerImpl.java
| ... | ... | @@ -7,7 +7,7 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 7 | 7 | import cn.iocoder.yudao.module.iot.api.device.dto.control.downstream.*; |
| 8 | 8 | import cn.iocoder.yudao.module.iot.net.component.core.constants.IotDeviceTopicEnum; |
| 9 | 9 | import cn.iocoder.yudao.module.iot.net.component.core.downstream.IotDeviceDownstreamHandler; |
| 10 | -import cn.iocoder.yudao.module.iot.net.component.core.message.IotAlinkMessage; | |
| 10 | +import cn.iocoder.yudao.module.iot.net.component.core.message.IotMqttMessage; | |
| 11 | 11 | import cn.iocoder.yudao.module.iot.net.component.core.util.IotNetComponentCommonUtils; |
| 12 | 12 | import io.netty.handler.codec.mqtt.MqttQoS; |
| 13 | 13 | import io.vertx.core.buffer.Buffer; |
| ... | ... | @@ -56,7 +56,7 @@ public class IotDeviceDownstreamHandlerImpl implements IotDeviceDownstreamHandle |
| 56 | 56 | // 构建请求消息 |
| 57 | 57 | String requestId = StrUtil.isNotEmpty(reqDTO.getRequestId()) ? reqDTO.getRequestId() |
| 58 | 58 | : IotNetComponentCommonUtils.generateRequestId(); |
| 59 | - IotAlinkMessage message = IotAlinkMessage.createServiceInvokeMessage( | |
| 59 | + IotMqttMessage message = IotMqttMessage.createServiceInvokeMessage( | |
| 60 | 60 | requestId, reqDTO.getIdentifier(), reqDTO.getParams()); |
| 61 | 61 | |
| 62 | 62 | // 发送消息 |
| ... | ... | @@ -93,7 +93,7 @@ public class IotDeviceDownstreamHandlerImpl implements IotDeviceDownstreamHandle |
| 93 | 93 | // 构建请求消息 |
| 94 | 94 | String requestId = StrUtil.isNotEmpty(reqDTO.getRequestId()) ? reqDTO.getRequestId() |
| 95 | 95 | : IotNetComponentCommonUtils.generateRequestId(); |
| 96 | - IotAlinkMessage message = IotAlinkMessage.createPropertySetMessage(requestId, reqDTO.getProperties()); | |
| 96 | + IotMqttMessage message = IotMqttMessage.createPropertySetMessage(requestId, reqDTO.getProperties()); | |
| 97 | 97 | |
| 98 | 98 | // 发送消息 |
| 99 | 99 | publishMessage(topic, message.toJsonObject()); | ... | ... |
yudao-module-iot/yudao-module-iot-protocol/README.md
0 → 100644
| 1 | +# IoT 协议模块 (yudao-module-iot-protocol) | |
| 2 | + | |
| 3 | +## 概述 | |
| 4 | + | |
| 5 | +本模块是物联网协议处理的核心组件,提供统一的协议解析、转换和消息处理功能。作为 `yudao-module-iot-biz` 和 | |
| 6 | +`yudao-module-iot-gateway-server` 等模块的共享包,实现了协议层面的抽象和统一。 | |
| 7 | + | |
| 8 | +## 主要功能 | |
| 9 | + | |
| 10 | +### 1. 协议消息模型 | |
| 11 | + | |
| 12 | +- **IotMqttMessage**: 基于 MQTT 协议规范的标准消息模型(默认实现) | |
| 13 | +- **IotStandardResponse**: 统一的响应格式,支持 MQTT 和 HTTP 协议 | |
| 14 | + | |
| 15 | +### 2. 主题管理 | |
| 16 | + | |
| 17 | +- **IotTopicConstants**: 主题常量定义 | |
| 18 | +- **IotTopicUtils**: MQTT 主题构建和解析工具 | |
| 19 | +- **IotHttpTopicUtils**: HTTP 主题构建和解析工具 | |
| 20 | +- **IotTopicParser**: 高级主题解析器,支持提取设备信息、消息类型等 | |
| 21 | + | |
| 22 | +### 3. 协议转换 | |
| 23 | + | |
| 24 | +- **IotMessageParser**: 消息解析器接口 | |
| 25 | +- **IotMqttMessageParser**: MQTT 协议解析器实现(默认) | |
| 26 | +- **IotHttpMessageParser**: HTTP 协议解析器实现 | |
| 27 | +- **IotProtocolConverter**: 协议转换器接口 | |
| 28 | +- **DefaultIotProtocolConverter**: 默认协议转换器实现 | |
| 29 | + | |
| 30 | +### 4. 枚举定义 | |
| 31 | + | |
| 32 | +- **IotProtocolTypeEnum**: 协议类型枚举 | |
| 33 | +- **IotMessageTypeEnum**: 消息类型枚举 | |
| 34 | +- **IotMessageDirectionEnum**: 消息方向枚举 | |
| 35 | + | |
| 36 | +## 使用示例 | |
| 37 | + | |
| 38 | +### 1. 构建主题 | |
| 39 | + | |
| 40 | +#### MQTT 主题 | |
| 41 | + | |
| 42 | +```java | |
| 43 | +// 构建设备属性设置主题 | |
| 44 | +String topic = IotTopicUtils.buildPropertySetTopic("productKey", "deviceName"); | |
| 45 | +// 结果: /sys/productKey/deviceName/thing/service/property/set | |
| 46 | + | |
| 47 | +// 构建事件上报主题 | |
| 48 | +String eventTopic = IotTopicUtils.buildEventPostTopic("productKey", "deviceName", "temperature"); | |
| 49 | +// 结果: /sys/productKey/deviceName/thing/event/temperature/post | |
| 50 | + | |
| 51 | +// 获取响应主题 | |
| 52 | +String replyTopic = IotTopicUtils.getReplyTopic(topic); | |
| 53 | +// 结果: /sys/productKey/deviceName/thing/service/property/set_reply | |
| 54 | +``` | |
| 55 | + | |
| 56 | +#### HTTP 主题 | |
| 57 | + | |
| 58 | +```java | |
| 59 | +// 构建属性设置路径 | |
| 60 | +String propSetPath = IotHttpTopicUtils.buildPropertySetPath("productKey", "deviceName"); | |
| 61 | +// 结果: /topic/sys/productKey/deviceName/thing/service/property/set | |
| 62 | + | |
| 63 | +// 构建属性获取路径 | |
| 64 | +String propGetPath = IotHttpTopicUtils.buildPropertyGetPath("productKey", "deviceName"); | |
| 65 | +// 结果: /topic/sys/productKey/deviceName/thing/service/property/get | |
| 66 | + | |
| 67 | +// 构建事件上报路径 | |
| 68 | +String eventPath = IotHttpTopicUtils.buildEventPostPath("productKey", "deviceName", "alarm"); | |
| 69 | +// 结果: /topic/sys/productKey/deviceName/thing/event/alarm/post | |
| 70 | + | |
| 71 | +// 构建自定义主题路径 | |
| 72 | +String customPath = IotHttpTopicUtils.buildCustomTopicPath("productKey", "deviceName", "user/get"); | |
| 73 | +// 结果: /topic/productKey/deviceName/user/get | |
| 74 | +``` | |
| 75 | + | |
| 76 | +### 2. 解析主题 | |
| 77 | + | |
| 78 | +```java | |
| 79 | +// 解析 MQTT 主题信息 | |
| 80 | +IotTopicParser.TopicInfo info = IotTopicParser.parse("/sys/pk/dn/thing/service/property/set"); | |
| 81 | +System.out. | |
| 82 | + | |
| 83 | +println("产品Key: "+info.getProductKey()); // pk | |
| 84 | + System.out. | |
| 85 | + | |
| 86 | +println("设备名称: "+info.getDeviceName()); // dn | |
| 87 | + System.out. | |
| 88 | + | |
| 89 | +println("消息类型: "+info.getMessageType()); // PROPERTY_SET | |
| 90 | + System.out. | |
| 91 | + | |
| 92 | +println("消息方向: "+info.getDirection()); // DOWNSTREAM | |
| 93 | + | |
| 94 | +// 解析 HTTP 主题信息 | |
| 95 | +String httpPath = "/topic/sys/pk/dn/thing/service/property/set"; | |
| 96 | +String actualTopic = IotHttpTopicUtils.extractActualTopic(httpPath); // /sys/pk/dn/thing/service/property/set | |
| 97 | +String productKey = IotHttpTopicUtils.parseProductKeyFromTopic(actualTopic); // pk | |
| 98 | +String deviceName = IotHttpTopicUtils.parseDeviceNameFromTopic(actualTopic); // dn | |
| 99 | +``` | |
| 100 | + | |
| 101 | +### 3. 创建 MQTT 消息 | |
| 102 | + | |
| 103 | +```java | |
| 104 | +// 创建属性设置消息 | |
| 105 | +Map<String, Object> properties = new HashMap<>(); | |
| 106 | +properties. | |
| 107 | + | |
| 108 | +put("temperature",25.5); | |
| 109 | + | |
| 110 | +IotMqttMessage message = IotMqttMessage.createPropertySetMessage("123456", properties); | |
| 111 | + | |
| 112 | +// 转换为 JSON 字符串 | |
| 113 | +String json = message.toJsonString(); | |
| 114 | +``` | |
| 115 | + | |
| 116 | +### 4. HTTP 协议消息处理 | |
| 117 | + | |
| 118 | +#### HTTP 消息格式 | |
| 119 | + | |
| 120 | +```json | |
| 121 | +{ | |
| 122 | + "deviceKey": "productKey/deviceName", | |
| 123 | + "messageId": "123456", | |
| 124 | + "action": "property.set", | |
| 125 | + "version": "1.0", | |
| 126 | + "data": { | |
| 127 | + "temperature": 25.5, | |
| 128 | + "humidity": 60.0 | |
| 129 | + } | |
| 130 | +} | |
| 131 | +``` | |
| 132 | + | |
| 133 | +#### 使用 HTTP 协议解析器 | |
| 134 | + | |
| 135 | +```java | |
| 136 | +// 创建 HTTP 协议解析器 | |
| 137 | +IotHttpMessageParser httpParser = new IotHttpMessageParser(); | |
| 138 | + | |
| 139 | +// 解析 HTTP 消息 | |
| 140 | +String topic = "/topic/sys/productKey/deviceName/thing/service/property/set"; | |
| 141 | +byte[] payload = httpMessage.getBytes(StandardCharsets.UTF_8); | |
| 142 | +IotMqttMessage message = httpParser.parse(topic, payload); | |
| 143 | + | |
| 144 | +// 格式化 HTTP 响应 | |
| 145 | +IotStandardResponse response = IotStandardResponse.success("123456", "property.set", data); | |
| 146 | +byte[] responseBytes = httpParser.formatResponse(response); | |
| 147 | +``` | |
| 148 | + | |
| 149 | +### 5. 使用协议转换器 | |
| 150 | + | |
| 151 | +```java | |
| 152 | + | |
| 153 | +@Autowired | |
| 154 | +private IotProtocolConverter protocolConverter; | |
| 155 | + | |
| 156 | +// 转换 MQTT 消息(推荐使用) | |
| 157 | +IotMqttMessage mqttMessage = protocolConverter.convertToStandardMessage(mqttTopic, mqttPayload, "mqtt"); | |
| 158 | + | |
| 159 | +// 转换 HTTP 消息 | |
| 160 | +IotMqttMessage httpMessage = protocolConverter.convertToStandardMessage(httpTopic, httpPayload, "http"); | |
| 161 | + | |
| 162 | +// 创建响应 | |
| 163 | +IotStandardResponse response = IotStandardResponse.success("123456", "property.set", data); | |
| 164 | +byte[] responseBytes = protocolConverter.convertFromStandardResponse(response, "mqtt"); | |
| 165 | +``` | |
| 166 | + | |
| 167 | +### 6. 自定义协议解析器 | |
| 168 | + | |
| 169 | +```java | |
| 170 | + | |
| 171 | +@Component | |
| 172 | +public class CustomMessageParser implements IotMessageParser { | |
| 173 | + | |
| 174 | + @Override | |
| 175 | + public IotMqttMessage parse(String topic, byte[] payload) { | |
| 176 | + // 实现自定义协议解析逻辑 | |
| 177 | + return null; | |
| 178 | + } | |
| 179 | + | |
| 180 | + @Override | |
| 181 | + public byte[] formatResponse(IotStandardResponse response) { | |
| 182 | + // 实现自定义响应格式化逻辑 | |
| 183 | + return new byte[0]; | |
| 184 | + } | |
| 185 | + | |
| 186 | + @Override | |
| 187 | + public boolean canHandle(String topic) { | |
| 188 | + // 判断是否能处理该主题 | |
| 189 | + return topic.startsWith("/custom/"); | |
| 190 | + } | |
| 191 | +} | |
| 192 | + | |
| 193 | +// 注册到协议转换器 | |
| 194 | +@Autowired | |
| 195 | +private DefaultIotProtocolConverter converter; | |
| 196 | + | |
| 197 | +@PostConstruct | |
| 198 | +public void init() { | |
| 199 | + converter.registerParser("custom", new CustomMessageParser()); | |
| 200 | +} | |
| 201 | +``` | |
| 202 | + | |
| 203 | +## 支持的协议类型 | |
| 204 | + | |
| 205 | +- **MQTT**: 标准 MQTT 协议,支持设备属性、事件、服务调用(默认协议) | |
| 206 | +- **HTTP**: HTTP 协议,支持设备通过 HTTP API 进行通信 | |
| 207 | +- **MQTT_RAW**: MQTT 原始协议 | |
| 208 | +- **TCP**: TCP 协议 | |
| 209 | +- **UDP**: UDP 协议 | |
| 210 | +- **CUSTOM**: 自定义协议 | |
| 211 | + | |
| 212 | +## 协议对比 | |
| 213 | + | |
| 214 | +| 协议类型 | 传输方式 | 消息格式 | 主题格式 | 适用场景 | | |
| 215 | +|----------|------|------|----------------------------------------------------------------------------------------------------------------------------|---------------| | |
| 216 | +| MQTT | MQTT | JSON | `/sys/{productKey}/{deviceName}/...`<br/>`/mqtt/{productKey}/{deviceName}/...`<br/>`/device/{productKey}/{deviceName}/...` | 实时性要求高的设备(推荐) | | |
| 217 | +| HTTP | HTTP | JSON | `/topic/sys/{productKey}/{deviceName}/...`<br/>`/topic/{productKey}/{deviceName}/...` | 间歇性通信的设备 | | |
| 218 | +| MQTT_RAW | MQTT | 原始 | 自定义格式 | 特殊协议设备 | | |
| 219 | + | |
| 220 | +## 模块依赖 | |
| 221 | + | |
| 222 | +本模块是一个基础模块,依赖项最小化: | |
| 223 | + | |
| 224 | +- `yudao-common`: 基础工具类 | |
| 225 | +- `hutool-all`: 工具库 | |
| 226 | +- `lombok`: 简化代码 | |
| 227 | +- `spring-boot-starter`: Spring Boot 基础支持 | |
| 228 | + | |
| 229 | +## 扩展点 | |
| 230 | + | |
| 231 | +### 1. 自定义消息解析器 | |
| 232 | + | |
| 233 | +实现 `IotMessageParser` 接口,支持新的协议格式。 | |
| 234 | + | |
| 235 | +### 2. 自定义协议转换器 | |
| 236 | + | |
| 237 | +实现 `IotProtocolConverter` 接口,提供更复杂的转换逻辑。 | |
| 238 | + | |
| 239 | +### 3. 自定义主题格式 | |
| 240 | + | |
| 241 | +扩展 `IotTopicParser` 的 `parseCustomTopic` 方法,支持自定义主题格式。 | |
| 242 | + | |
| 243 | +## 注意事项 | |
| 244 | + | |
| 245 | +1. 本模块设计为无状态的工具模块,避免引入有状态的组件 | |
| 246 | +2. 所有的工具类都采用静态方法,便于直接调用 | |
| 247 | +3. 异常处理采用返回 null 的方式,调用方需要做好空值检查 | |
| 248 | +4. 日志级别建议设置为 INFO 或 WARN,避免过多的 DEBUG 日志 | |
| 249 | +5. HTTP 协议解析器使用设备标识 `deviceKey`(格式:`productKey/deviceName`)来标识设备 | |
| 250 | + | |
| 251 | +## 版本更新 | |
| 252 | + | |
| 253 | +- v1.0.0: 基础功能实现,支持 MQTT 协议和 HTTP 协议支持 | |
| 254 | +- 后续版本将支持更多协议类型和高级功能 | |
| 0 | 255 | \ No newline at end of file | ... | ... |
yudao-module-iot/yudao-module-iot-protocol/pom.xml
| 1 | 1 | <?xml version="1.0" encoding="UTF-8"?> |
| 2 | 2 | <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
| 3 | - xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | |
| 3 | + xmlns="http://maven.apache.org/POM/4.0.0" | |
| 4 | + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | |
| 4 | 5 | <parent> |
| 5 | 6 | <artifactId>yudao-module-iot</artifactId> |
| 6 | 7 | <groupId>cn.iocoder.boot</groupId> | ... | ... |
yudao-module-iot/yudao-module-iot-protocol/src/main/java/cn/iocoder/yudao/module/iot/protocol/config/IotProtocolAutoConfiguration.java
| ... | ... | @@ -4,8 +4,8 @@ import cn.iocoder.yudao.module.iot.protocol.convert.IotProtocolConverter; |
| 4 | 4 | import cn.iocoder.yudao.module.iot.protocol.convert.impl.DefaultIotProtocolConverter; |
| 5 | 5 | import cn.iocoder.yudao.module.iot.protocol.enums.IotProtocolTypeEnum; |
| 6 | 6 | import cn.iocoder.yudao.module.iot.protocol.message.IotMessageParser; |
| 7 | -import cn.iocoder.yudao.module.iot.protocol.message.impl.IotAlinkMessageParser; | |
| 8 | 7 | import cn.iocoder.yudao.module.iot.protocol.message.impl.IotHttpMessageParser; |
| 8 | +import cn.iocoder.yudao.module.iot.protocol.message.impl.IotMqttMessageParser; | |
| 9 | 9 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; |
| 10 | 10 | import org.springframework.context.annotation.Bean; |
| 11 | 11 | import org.springframework.context.annotation.Configuration; |
| ... | ... | @@ -21,20 +21,21 @@ public class IotProtocolAutoConfiguration { |
| 21 | 21 | /** |
| 22 | 22 | * Bean 名称常量 |
| 23 | 23 | */ |
| 24 | - public static final String IOT_ALINK_MESSAGE_PARSER_BEAN_NAME = "iotAlinkMessageParser"; | |
| 24 | + public static final String IOT_MQTT_MESSAGE_PARSER_BEAN_NAME = "iotMqttMessageParser"; | |
| 25 | 25 | public static final String IOT_HTTP_MESSAGE_PARSER_BEAN_NAME = "iotHttpMessageParser"; |
| 26 | 26 | |
| 27 | 27 | /** |
| 28 | - * 注册 Alink 协议消息解析器 | |
| 28 | + * 注册 MQTT 协议消息解析器 | |
| 29 | 29 | * |
| 30 | - * @return Alink 协议消息解析器 | |
| 30 | + * @return MQTT 协议消息解析器 | |
| 31 | 31 | */ |
| 32 | 32 | @Bean |
| 33 | - @ConditionalOnMissingBean(name = IOT_ALINK_MESSAGE_PARSER_BEAN_NAME) | |
| 34 | - public IotMessageParser iotAlinkMessageParser() { | |
| 35 | - return new IotAlinkMessageParser(); | |
| 33 | + @ConditionalOnMissingBean(name = IOT_MQTT_MESSAGE_PARSER_BEAN_NAME) | |
| 34 | + public IotMessageParser iotMqttMessageParser() { | |
| 35 | + return new IotMqttMessageParser(); | |
| 36 | 36 | } |
| 37 | 37 | |
| 38 | + | |
| 38 | 39 | /** |
| 39 | 40 | * 注册 HTTP 协议消息解析器 |
| 40 | 41 | * |
| ... | ... | @@ -50,26 +51,24 @@ public class IotProtocolAutoConfiguration { |
| 50 | 51 | * 注册默认协议转换器 |
| 51 | 52 | * <p> |
| 52 | 53 | * 如果用户没有自定义协议转换器,则使用默认实现 |
| 53 | - * 默认会注册 Alink 和 HTTP 协议解析器 | |
| 54 | + * 默认会注册 MQTT 和 HTTP 协议解析器 | |
| 54 | 55 | * |
| 55 | - * @param iotAlinkMessageParser Alink 协议解析器 | |
| 56 | - * @param iotHttpMessageParser HTTP 协议解析器 | |
| 56 | + * @param iotMqttMessageParser MQTT 协议解析器 | |
| 57 | + * @param iotHttpMessageParser HTTP 协议解析器 | |
| 57 | 58 | * @return 默认协议转换器 |
| 58 | 59 | */ |
| 59 | 60 | @Bean |
| 60 | 61 | @ConditionalOnMissingBean |
| 61 | - public IotProtocolConverter iotProtocolConverter(IotMessageParser iotAlinkMessageParser, | |
| 62 | + public IotProtocolConverter iotProtocolConverter(IotMessageParser iotMqttMessageParser, | |
| 62 | 63 | IotMessageParser iotHttpMessageParser) { |
| 63 | 64 | DefaultIotProtocolConverter converter = new DefaultIotProtocolConverter(); |
| 64 | 65 | |
| 66 | + // 注册 MQTT 协议解析器(默认实现) | |
| 67 | + converter.registerParser(IotProtocolTypeEnum.MQTT.getCode(), iotMqttMessageParser); | |
| 68 | + | |
| 65 | 69 | // 注册 HTTP 协议解析器 |
| 66 | 70 | converter.registerParser(IotProtocolTypeEnum.HTTP.getCode(), iotHttpMessageParser); |
| 67 | 71 | |
| 68 | - // 注意:Alink 协议解析器已经在 DefaultIotProtocolConverter 构造函数中注册 | |
| 69 | - // 如果需要使用自定义的 Alink 解析器实例,可以重新注册 | |
| 70 | - // converter.registerParser(IotProtocolTypeEnum.ALINK.getCode(), | |
| 71 | - // iotAlinkMessageParser); | |
| 72 | - | |
| 73 | 72 | return converter; |
| 74 | 73 | } |
| 75 | 74 | } |
| 76 | 75 | \ No newline at end of file | ... | ... |
yudao-module-iot/yudao-module-iot-protocol/src/main/java/cn/iocoder/yudao/module/iot/protocol/convert/IotProtocolConverter.java
| 1 | 1 | package cn.iocoder.yudao.module.iot.protocol.convert; |
| 2 | 2 | |
| 3 | -import cn.iocoder.yudao.module.iot.protocol.message.IotAlinkMessage; | |
| 3 | +import cn.iocoder.yudao.module.iot.protocol.message.IotMqttMessage; | |
| 4 | 4 | import cn.iocoder.yudao.module.iot.protocol.message.IotStandardResponse; |
| 5 | 5 | |
| 6 | 6 | /** |
| ... | ... | @@ -20,7 +20,7 @@ public interface IotProtocolConverter { |
| 20 | 20 | * @param protocol 协议类型 |
| 21 | 21 | * @return 标准消息对象,转换失败返回 null |
| 22 | 22 | */ |
| 23 | - IotAlinkMessage convertToStandardMessage(String topic, byte[] payload, String protocol); | |
| 23 | + IotMqttMessage convertToStandardMessage(String topic, byte[] payload, String protocol); | |
| 24 | 24 | |
| 25 | 25 | /** |
| 26 | 26 | * 将标准响应转换为字节数组 | ... | ... |
yudao-module-iot/yudao-module-iot-protocol/src/main/java/cn/iocoder/yudao/module/iot/protocol/convert/impl/DefaultIotProtocolConverter.java
| ... | ... | @@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.iot.protocol.convert.impl; |
| 3 | 3 | import cn.iocoder.yudao.module.iot.protocol.constants.IotLogConstants; |
| 4 | 4 | import cn.iocoder.yudao.module.iot.protocol.convert.IotProtocolConverter; |
| 5 | 5 | import cn.iocoder.yudao.module.iot.protocol.enums.IotProtocolTypeEnum; |
| 6 | -import cn.iocoder.yudao.module.iot.protocol.message.IotAlinkMessage; | |
| 7 | 6 | import cn.iocoder.yudao.module.iot.protocol.message.IotMessageParser; |
| 7 | +import cn.iocoder.yudao.module.iot.protocol.message.IotMqttMessage; | |
| 8 | 8 | import cn.iocoder.yudao.module.iot.protocol.message.IotStandardResponse; |
| 9 | -import cn.iocoder.yudao.module.iot.protocol.message.impl.IotAlinkMessageParser; | |
| 9 | +import cn.iocoder.yudao.module.iot.protocol.message.impl.IotMqttMessageParser; | |
| 10 | 10 | import lombok.extern.slf4j.Slf4j; |
| 11 | 11 | |
| 12 | 12 | import java.util.HashMap; |
| ... | ... | @@ -33,8 +33,9 @@ public class DefaultIotProtocolConverter implements IotProtocolConverter { |
| 33 | 33 | * 构造函数,初始化默认支持的协议 |
| 34 | 34 | */ |
| 35 | 35 | public DefaultIotProtocolConverter() { |
| 36 | - // 注册 Alink 协议解析器 | |
| 37 | - registerParser(IotProtocolTypeEnum.ALINK.getCode(), new IotAlinkMessageParser()); | |
| 36 | + // 注册 MQTT 协议解析器作为默认实现 | |
| 37 | + IotMqttMessageParser mqttParser = new IotMqttMessageParser(); | |
| 38 | + registerParser(IotProtocolTypeEnum.MQTT.getCode(), mqttParser); | |
| 38 | 39 | } |
| 39 | 40 | |
| 40 | 41 | /** |
| ... | ... | @@ -59,7 +60,7 @@ public class DefaultIotProtocolConverter implements IotProtocolConverter { |
| 59 | 60 | } |
| 60 | 61 | |
| 61 | 62 | @Override |
| 62 | - public IotAlinkMessage convertToStandardMessage(String topic, byte[] payload, String protocol) { | |
| 63 | + public IotMqttMessage convertToStandardMessage(String topic, byte[] payload, String protocol) { | |
| 63 | 64 | IotMessageParser parser = parsers.get(protocol); |
| 64 | 65 | if (parser == null) { |
| 65 | 66 | log.warn(IotLogConstants.Converter.UNSUPPORTED_PROTOCOL, protocol); |
| ... | ... | @@ -108,13 +109,13 @@ public class DefaultIotProtocolConverter implements IotProtocolConverter { |
| 108 | 109 | * @param payload 消息负载 |
| 109 | 110 | * @return 解析后的标准消息,如果无法解析返回 null |
| 110 | 111 | */ |
| 111 | - public IotAlinkMessage autoConvert(String topic, byte[] payload) { | |
| 112 | + public IotMqttMessage autoConvert(String topic, byte[] payload) { | |
| 112 | 113 | // 遍历所有解析器,找到能处理该主题的解析器 |
| 113 | 114 | for (Map.Entry<String, IotMessageParser> entry : parsers.entrySet()) { |
| 114 | 115 | IotMessageParser parser = entry.getValue(); |
| 115 | 116 | if (parser.canHandle(topic)) { |
| 116 | 117 | try { |
| 117 | - IotAlinkMessage message = parser.parse(topic, payload); | |
| 118 | + IotMqttMessage message = parser.parse(topic, payload); | |
| 118 | 119 | if (message != null) { |
| 119 | 120 | log.debug(IotLogConstants.Converter.AUTO_SELECT_PROTOCOL, entry.getKey(), topic); |
| 120 | 121 | return message; | ... | ... |
yudao-module-iot/yudao-module-iot-protocol/src/main/java/cn/iocoder/yudao/module/iot/protocol/enums/IotProtocolTypeEnum.java
yudao-module-iot/yudao-module-iot-protocol/src/main/java/cn/iocoder/yudao/module/iot/protocol/message/IotMessageParser.java
yudao-module-iot/yudao-module-iot-protocol/src/main/java/cn/iocoder/yudao/module/iot/protocol/message/IotAlinkMessage.java renamed to yudao-module-iot/yudao-module-iot-protocol/src/main/java/cn/iocoder/yudao/module/iot/protocol/message/IotMqttMessage.java
| ... | ... | @@ -8,16 +8,16 @@ import lombok.Data; |
| 8 | 8 | import java.util.Map; |
| 9 | 9 | |
| 10 | 10 | /** |
| 11 | - * IoT Alink 消息模型 | |
| 11 | + * IoT MQTT 消息模型 | |
| 12 | 12 | * <p> |
| 13 | - * 基于阿里云 Alink 协议规范实现的标准消息格式 | |
| 13 | + * 基于 MQTT 协议规范实现的标准消息格式,支持设备属性、事件、服务调用等标准功能 | |
| 14 | 14 | * |
| 15 | 15 | * @author haohao |
| 16 | - * @see <a href="https://help.aliyun.com/zh/iot/user-guide/alink-protocol-1">阿里云物联网 —— Alink 协议</a> | |
| 16 | + * @see <a href="https://mqtt.org/">MQTT 协议官方规范</a> | |
| 17 | 17 | */ |
| 18 | 18 | @Data |
| 19 | 19 | @Builder |
| 20 | -public class IotAlinkMessage { | |
| 20 | +public class IotMqttMessage { | |
| 21 | 21 | |
| 22 | 22 | /** |
| 23 | 23 | * 消息 ID |
| ... | ... | @@ -69,11 +69,11 @@ public class IotAlinkMessage { |
| 69 | 69 | * @param requestId 请求 ID,为空时自动生成 |
| 70 | 70 | * @param serviceIdentifier 服务标识符 |
| 71 | 71 | * @param params 服务参数 |
| 72 | - * @return Alink 消息对象 | |
| 72 | + * @return MQTT 消息对象 | |
| 73 | 73 | */ |
| 74 | - public static IotAlinkMessage createServiceInvokeMessage(String requestId, String serviceIdentifier, | |
| 75 | - Map<String, Object> params) { | |
| 76 | - return IotAlinkMessage.builder() | |
| 74 | + public static IotMqttMessage createServiceInvokeMessage(String requestId, String serviceIdentifier, | |
| 75 | + Map<String, Object> params) { | |
| 76 | + return IotMqttMessage.builder() | |
| 77 | 77 | .id(requestId != null ? requestId : generateRequestId()) |
| 78 | 78 | .method("thing.service." + serviceIdentifier) |
| 79 | 79 | .params(params) |
| ... | ... | @@ -85,10 +85,10 @@ public class IotAlinkMessage { |
| 85 | 85 | * |
| 86 | 86 | * @param requestId 请求 ID,为空时自动生成 |
| 87 | 87 | * @param properties 设备属性 |
| 88 | - * @return Alink 消息对象 | |
| 88 | + * @return MQTT 消息对象 | |
| 89 | 89 | */ |
| 90 | - public static IotAlinkMessage createPropertySetMessage(String requestId, Map<String, Object> properties) { | |
| 91 | - return IotAlinkMessage.builder() | |
| 90 | + public static IotMqttMessage createPropertySetMessage(String requestId, Map<String, Object> properties) { | |
| 91 | + return IotMqttMessage.builder() | |
| 92 | 92 | .id(requestId != null ? requestId : generateRequestId()) |
| 93 | 93 | .method("thing.service.property.set") |
| 94 | 94 | .params(properties) |
| ... | ... | @@ -100,13 +100,13 @@ public class IotAlinkMessage { |
| 100 | 100 | * |
| 101 | 101 | * @param requestId 请求 ID,为空时自动生成 |
| 102 | 102 | * @param identifiers 要获取的属性标识符列表 |
| 103 | - * @return Alink 消息对象 | |
| 103 | + * @return MQTT 消息对象 | |
| 104 | 104 | */ |
| 105 | - public static IotAlinkMessage createPropertyGetMessage(String requestId, String[] identifiers) { | |
| 105 | + public static IotMqttMessage createPropertyGetMessage(String requestId, String[] identifiers) { | |
| 106 | 106 | JSONObject params = new JSONObject(); |
| 107 | 107 | params.set("identifiers", identifiers); |
| 108 | 108 | |
| 109 | - return IotAlinkMessage.builder() | |
| 109 | + return IotMqttMessage.builder() | |
| 110 | 110 | .id(requestId != null ? requestId : generateRequestId()) |
| 111 | 111 | .method("thing.service.property.get") |
| 112 | 112 | .params(params) |
| ... | ... | @@ -118,10 +118,10 @@ public class IotAlinkMessage { |
| 118 | 118 | * |
| 119 | 119 | * @param requestId 请求 ID,为空时自动生成 |
| 120 | 120 | * @param configs 设备配置 |
| 121 | - * @return Alink 消息对象 | |
| 121 | + * @return MQTT 消息对象 | |
| 122 | 122 | */ |
| 123 | - public static IotAlinkMessage createConfigSetMessage(String requestId, Map<String, Object> configs) { | |
| 124 | - return IotAlinkMessage.builder() | |
| 123 | + public static IotMqttMessage createConfigSetMessage(String requestId, Map<String, Object> configs) { | |
| 124 | + return IotMqttMessage.builder() | |
| 125 | 125 | .id(requestId != null ? requestId : generateRequestId()) |
| 126 | 126 | .method("thing.service.config.set") |
| 127 | 127 | .params(configs) |
| ... | ... | @@ -133,10 +133,10 @@ public class IotAlinkMessage { |
| 133 | 133 | * |
| 134 | 134 | * @param requestId 请求 ID,为空时自动生成 |
| 135 | 135 | * @param otaInfo OTA 升级信息 |
| 136 | - * @return Alink 消息对象 | |
| 136 | + * @return MQTT 消息对象 | |
| 137 | 137 | */ |
| 138 | - public static IotAlinkMessage createOtaUpgradeMessage(String requestId, Map<String, Object> otaInfo) { | |
| 139 | - return IotAlinkMessage.builder() | |
| 138 | + public static IotMqttMessage createOtaUpgradeMessage(String requestId, Map<String, Object> otaInfo) { | |
| 139 | + return IotMqttMessage.builder() | |
| 140 | 140 | .id(requestId != null ? requestId : generateRequestId()) |
| 141 | 141 | .method("thing.service.ota.upgrade") |
| 142 | 142 | .params(otaInfo) | ... | ... |
yudao-module-iot/yudao-module-iot-protocol/src/main/java/cn/iocoder/yudao/module/iot/protocol/message/impl/IotHttpMessageParser.java
| ... | ... | @@ -6,8 +6,8 @@ import cn.hutool.json.JSONUtil; |
| 6 | 6 | import cn.iocoder.yudao.module.iot.protocol.constants.IotHttpConstants; |
| 7 | 7 | import cn.iocoder.yudao.module.iot.protocol.constants.IotLogConstants; |
| 8 | 8 | import cn.iocoder.yudao.module.iot.protocol.constants.IotTopicConstants; |
| 9 | -import cn.iocoder.yudao.module.iot.protocol.message.IotAlinkMessage; | |
| 10 | 9 | import cn.iocoder.yudao.module.iot.protocol.message.IotMessageParser; |
| 10 | +import cn.iocoder.yudao.module.iot.protocol.message.IotMqttMessage; | |
| 11 | 11 | import cn.iocoder.yudao.module.iot.protocol.message.IotStandardResponse; |
| 12 | 12 | import lombok.extern.slf4j.Slf4j; |
| 13 | 13 | |
| ... | ... | @@ -61,7 +61,7 @@ public class IotHttpMessageParser implements IotMessageParser { |
| 61 | 61 | public static final String TOPIC_PATH_PREFIX = IotHttpConstants.Path.TOPIC_PREFIX; |
| 62 | 62 | |
| 63 | 63 | @Override |
| 64 | - public IotAlinkMessage parse(String topic, byte[] payload) { | |
| 64 | + public IotMqttMessage parse(String topic, byte[] payload) { | |
| 65 | 65 | if (payload == null || payload.length == 0) { |
| 66 | 66 | log.warn(IotLogConstants.Http.RECEIVED_EMPTY_MESSAGE, topic); |
| 67 | 67 | return null; |
| ... | ... | @@ -92,7 +92,7 @@ public class IotHttpMessageParser implements IotMessageParser { |
| 92 | 92 | * @param message 认证消息JSON |
| 93 | 93 | * @return 标准消息格式 |
| 94 | 94 | */ |
| 95 | - private IotAlinkMessage parseAuthMessage(String message) { | |
| 95 | + private IotMqttMessage parseAuthMessage(String message) { | |
| 96 | 96 | if (!JSONUtil.isTypeJSON(message)) { |
| 97 | 97 | log.warn(IotLogConstants.Http.AUTH_MESSAGE_NOT_JSON, message); |
| 98 | 98 | return null; |
| ... | ... | @@ -121,7 +121,7 @@ public class IotHttpMessageParser implements IotMessageParser { |
| 121 | 121 | params.put(IotHttpConstants.AuthField.SIGN_METHOD, |
| 122 | 122 | json.getStr(IotHttpConstants.AuthField.SIGN_METHOD, IotHttpConstants.DefaultValue.SIGN_METHOD)); |
| 123 | 123 | |
| 124 | - return IotAlinkMessage.builder() | |
| 124 | + return IotMqttMessage.builder() | |
| 125 | 125 | .id(generateMessageId()) |
| 126 | 126 | .method(IotHttpConstants.Method.DEVICE_AUTH) |
| 127 | 127 | .version(json.getStr(IotHttpConstants.AuthField.VERSION, IotHttpConstants.DefaultValue.VERSION)) |
| ... | ... | @@ -136,7 +136,7 @@ public class IotHttpMessageParser implements IotMessageParser { |
| 136 | 136 | * @param message 消息内容 |
| 137 | 137 | * @return 标准消息格式 |
| 138 | 138 | */ |
| 139 | - private IotAlinkMessage parseDataMessage(String topic, String message) { | |
| 139 | + private IotMqttMessage parseDataMessage(String topic, String message) { | |
| 140 | 140 | // 提取实际的主题,去掉 /topic 前缀 |
| 141 | 141 | String actualTopic = topic.substring(TOPIC_PATH_PREFIX.length()); // 直接移除/topic前缀 |
| 142 | 142 | |
| ... | ... | @@ -156,7 +156,7 @@ public class IotHttpMessageParser implements IotMessageParser { |
| 156 | 156 | * @param message JSON消息 |
| 157 | 157 | * @return 标准消息格式 |
| 158 | 158 | */ |
| 159 | - private IotAlinkMessage parseJsonDataMessage(String topic, String message) { | |
| 159 | + private IotMqttMessage parseJsonDataMessage(String topic, String message) { | |
| 160 | 160 | JSONObject json = JSONUtil.parseObj(message); |
| 161 | 161 | |
| 162 | 162 | // 生成消息ID |
| ... | ... | @@ -181,7 +181,7 @@ public class IotHttpMessageParser implements IotMessageParser { |
| 181 | 181 | paramsMap.put(IotHttpConstants.MessageField.DATA, params); |
| 182 | 182 | } |
| 183 | 183 | |
| 184 | - return IotAlinkMessage.builder() | |
| 184 | + return IotMqttMessage.builder() | |
| 185 | 185 | .id(messageId) |
| 186 | 186 | .method(method) |
| 187 | 187 | .version(json.getStr(IotHttpConstants.MessageField.VERSION, |
| ... | ... | @@ -197,11 +197,11 @@ public class IotHttpMessageParser implements IotMessageParser { |
| 197 | 197 | * @param message 原始消息 |
| 198 | 198 | * @return 标准消息格式 |
| 199 | 199 | */ |
| 200 | - private IotAlinkMessage parseRawDataMessage(String topic, String message) { | |
| 200 | + private IotMqttMessage parseRawDataMessage(String topic, String message) { | |
| 201 | 201 | Map<String, Object> params = new HashMap<>(); |
| 202 | 202 | params.put(IotHttpConstants.MessageField.DATA, message); |
| 203 | 203 | |
| 204 | - return IotAlinkMessage.builder() | |
| 204 | + return IotMqttMessage.builder() | |
| 205 | 205 | .id(generateMessageId()) |
| 206 | 206 | .method(inferMethodFromTopic(topic)) |
| 207 | 207 | .version(IotHttpConstants.DefaultValue.MESSAGE_VERSION) |
| ... | ... | @@ -263,7 +263,7 @@ public class IotHttpMessageParser implements IotMessageParser { |
| 263 | 263 | * @return 消息ID |
| 264 | 264 | */ |
| 265 | 265 | private String generateMessageId() { |
| 266 | - return IotAlinkMessage.generateRequestId(); | |
| 266 | + return IotMqttMessage.generateRequestId(); | |
| 267 | 267 | } |
| 268 | 268 | |
| 269 | 269 | @Override | ... | ... |
yudao-module-iot/yudao-module-iot-protocol/src/main/java/cn/iocoder/yudao/module/iot/protocol/message/impl/IotAlinkMessageParser.java renamed to yudao-module-iot/yudao-module-iot-protocol/src/main/java/cn/iocoder/yudao/module/iot/protocol/message/impl/IotMqttMessageParser.java
| ... | ... | @@ -4,8 +4,8 @@ import cn.hutool.core.util.StrUtil; |
| 4 | 4 | import cn.hutool.json.JSONObject; |
| 5 | 5 | import cn.hutool.json.JSONUtil; |
| 6 | 6 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; |
| 7 | -import cn.iocoder.yudao.module.iot.protocol.message.IotAlinkMessage; | |
| 8 | 7 | import cn.iocoder.yudao.module.iot.protocol.message.IotMessageParser; |
| 8 | +import cn.iocoder.yudao.module.iot.protocol.message.IotMqttMessage; | |
| 9 | 9 | import cn.iocoder.yudao.module.iot.protocol.message.IotStandardResponse; |
| 10 | 10 | import cn.iocoder.yudao.module.iot.protocol.util.IotTopicUtils; |
| 11 | 11 | import lombok.extern.slf4j.Slf4j; |
| ... | ... | @@ -14,26 +14,26 @@ import java.nio.charset.StandardCharsets; |
| 14 | 14 | import java.util.Map; |
| 15 | 15 | |
| 16 | 16 | /** |
| 17 | - * IoT Alink 协议消息解析器实现 | |
| 17 | + * IoT MQTT 协议消息解析器实现 | |
| 18 | 18 | * <p> |
| 19 | - * 基于阿里云 Alink 协议规范实现的消息解析器 | |
| 19 | + * 基于 MQTT 协议规范实现的消息解析器,支持设备属性、事件、服务调用等标准功能 | |
| 20 | 20 | * |
| 21 | 21 | * @author haohao |
| 22 | 22 | */ |
| 23 | 23 | @Slf4j |
| 24 | -public class IotAlinkMessageParser implements IotMessageParser { | |
| 24 | +public class IotMqttMessageParser implements IotMessageParser { | |
| 25 | 25 | |
| 26 | 26 | @Override |
| 27 | - public IotAlinkMessage parse(String topic, byte[] payload) { | |
| 27 | + public IotMqttMessage parse(String topic, byte[] payload) { | |
| 28 | 28 | if (payload == null || payload.length == 0) { |
| 29 | - log.warn("[Alink] 收到空消息内容, topic={}", topic); | |
| 29 | + log.warn("[MQTT] 收到空消息内容, topic={}", topic); | |
| 30 | 30 | return null; |
| 31 | 31 | } |
| 32 | 32 | |
| 33 | 33 | try { |
| 34 | 34 | String message = new String(payload, StandardCharsets.UTF_8); |
| 35 | 35 | if (!JSONUtil.isTypeJSON(message)) { |
| 36 | - log.warn("[Alink] 收到非JSON格式消息, topic={}, message={}", topic, message); | |
| 36 | + log.warn("[MQTT] 收到非JSON格式消息, topic={}, message={}", topic, message); | |
| 37 | 37 | return null; |
| 38 | 38 | } |
| 39 | 39 | |
| ... | ... | @@ -45,20 +45,21 @@ public class IotAlinkMessageParser implements IotMessageParser { |
| 45 | 45 | // 尝试从 topic 中解析方法 |
| 46 | 46 | method = IotTopicUtils.parseMethodFromTopic(topic); |
| 47 | 47 | if (StrUtil.isBlank(method)) { |
| 48 | - log.warn("[Alink] 无法确定消息方法, topic={}, message={}", topic, message); | |
| 48 | + log.warn("[MQTT] 无法确定消息方法, topic={}, message={}", topic, message); | |
| 49 | 49 | return null; |
| 50 | 50 | } |
| 51 | 51 | } |
| 52 | 52 | |
| 53 | + @SuppressWarnings("unchecked") | |
| 53 | 54 | Map<String, Object> params = (Map<String, Object>) json.getObj("params", Map.class); |
| 54 | - return IotAlinkMessage.builder() | |
| 55 | + return IotMqttMessage.builder() | |
| 55 | 56 | .id(id) |
| 56 | 57 | .method(method) |
| 57 | 58 | .version(json.getStr("version", "1.0")) |
| 58 | 59 | .params(params) |
| 59 | 60 | .build(); |
| 60 | 61 | } catch (Exception e) { |
| 61 | - log.error("[Alink] 解析消息失败, topic={}", topic, e); | |
| 62 | + log.error("[MQTT] 解析消息失败, topic={}", topic, e); | |
| 62 | 63 | return null; |
| 63 | 64 | } |
| 64 | 65 | } |
| ... | ... | @@ -69,14 +70,18 @@ public class IotAlinkMessageParser implements IotMessageParser { |
| 69 | 70 | String json = JsonUtils.toJsonString(response); |
| 70 | 71 | return json.getBytes(StandardCharsets.UTF_8); |
| 71 | 72 | } catch (Exception e) { |
| 72 | - log.error("[Alink] 格式化响应失败", e); | |
| 73 | + log.error("[MQTT] 格式化响应失败", e); | |
| 73 | 74 | return new byte[0]; |
| 74 | 75 | } |
| 75 | 76 | } |
| 76 | 77 | |
| 77 | 78 | @Override |
| 78 | 79 | public boolean canHandle(String topic) { |
| 79 | - // Alink 协议处理所有系统主题 | |
| 80 | - return topic != null && topic.startsWith("/sys/"); | |
| 80 | + // MQTT 协议支持更多主题格式 | |
| 81 | + return topic != null && ( | |
| 82 | + topic.startsWith("/sys/") || // 兼容现有系统主题 | |
| 83 | + topic.startsWith("/mqtt/") || // 新的通用 MQTT 主题 | |
| 84 | + topic.startsWith("/device/") // 设备主题 | |
| 85 | + ); | |
| 81 | 86 | } |
| 82 | 87 | } |
| 83 | 88 | \ No newline at end of file | ... | ... |
yudao-module-iot/yudao-module-iot-protocol/src/test/java/cn/iocoder/yudao/module/iot/protocol/config/IotProtocolAutoConfigurationTest.java
| ... | ... | @@ -3,8 +3,8 @@ package cn.iocoder.yudao.module.iot.protocol.config; |
| 3 | 3 | import cn.iocoder.yudao.module.iot.protocol.convert.IotProtocolConverter; |
| 4 | 4 | import cn.iocoder.yudao.module.iot.protocol.enums.IotProtocolTypeEnum; |
| 5 | 5 | import cn.iocoder.yudao.module.iot.protocol.message.IotMessageParser; |
| 6 | -import cn.iocoder.yudao.module.iot.protocol.message.impl.IotAlinkMessageParser; | |
| 7 | 6 | import cn.iocoder.yudao.module.iot.protocol.message.impl.IotHttpMessageParser; |
| 7 | +import cn.iocoder.yudao.module.iot.protocol.message.impl.IotMqttMessageParser; | |
| 8 | 8 | import org.junit.jupiter.api.BeforeEach; |
| 9 | 9 | import org.junit.jupiter.api.Test; |
| 10 | 10 | |
| ... | ... | @@ -25,12 +25,12 @@ class IotProtocolAutoConfigurationTest { |
| 25 | 25 | } |
| 26 | 26 | |
| 27 | 27 | @Test |
| 28 | - void testIotAlinkMessageParser() { | |
| 29 | - // 测试 Alink 协议解析器 Bean 创建 | |
| 30 | - IotMessageParser parser = configuration.iotAlinkMessageParser(); | |
| 28 | + void testIotMqttMessageParser() { | |
| 29 | + // 测试 MQTT 协议解析器 Bean 创建 | |
| 30 | + IotMessageParser parser = configuration.iotMqttMessageParser(); | |
| 31 | 31 | |
| 32 | 32 | assertNotNull(parser); |
| 33 | - assertInstanceOf(IotAlinkMessageParser.class, parser); | |
| 33 | + assertInstanceOf(IotMqttMessageParser.class, parser); | |
| 34 | 34 | } |
| 35 | 35 | |
| 36 | 36 | @Test |
| ... | ... | @@ -45,16 +45,16 @@ class IotProtocolAutoConfigurationTest { |
| 45 | 45 | @Test |
| 46 | 46 | void testIotProtocolConverter() { |
| 47 | 47 | // 创建解析器实例 |
| 48 | - IotMessageParser alinkParser = configuration.iotAlinkMessageParser(); | |
| 48 | + IotMessageParser mqttParser = configuration.iotMqttMessageParser(); | |
| 49 | 49 | IotMessageParser httpParser = configuration.iotHttpMessageParser(); |
| 50 | 50 | |
| 51 | 51 | // 测试协议转换器 Bean 创建 |
| 52 | - IotProtocolConverter converter = configuration.iotProtocolConverter(alinkParser, httpParser); | |
| 52 | + IotProtocolConverter converter = configuration.iotProtocolConverter(mqttParser, httpParser); | |
| 53 | 53 | |
| 54 | 54 | assertNotNull(converter); |
| 55 | 55 | |
| 56 | 56 | // 验证支持的协议 |
| 57 | - assertTrue(converter.supportsProtocol(IotProtocolTypeEnum.ALINK.getCode())); | |
| 57 | + assertTrue(converter.supportsProtocol(IotProtocolTypeEnum.MQTT.getCode())); | |
| 58 | 58 | assertTrue(converter.supportsProtocol(IotProtocolTypeEnum.HTTP.getCode())); |
| 59 | 59 | |
| 60 | 60 | // 验证支持的协议数量 |
| ... | ... | @@ -65,7 +65,7 @@ class IotProtocolAutoConfigurationTest { |
| 65 | 65 | @Test |
| 66 | 66 | void testBeanNameConstants() { |
| 67 | 67 | // 测试 Bean 名称常量定义 |
| 68 | - assertEquals("iotAlinkMessageParser", IotProtocolAutoConfiguration.IOT_ALINK_MESSAGE_PARSER_BEAN_NAME); | |
| 68 | + assertEquals("iotMqttMessageParser", IotProtocolAutoConfiguration.IOT_MQTT_MESSAGE_PARSER_BEAN_NAME); | |
| 69 | 69 | assertEquals("iotHttpMessageParser", IotProtocolAutoConfiguration.IOT_HTTP_MESSAGE_PARSER_BEAN_NAME); |
| 70 | 70 | } |
| 71 | 71 | } |
| 72 | 72 | \ No newline at end of file | ... | ... |
yudao-module-iot/yudao-module-iot-protocol/src/test/java/cn/iocoder/yudao/module/iot/protocol/message/impl/IotHttpMessageParserTest.java
| 1 | 1 | package cn.iocoder.yudao.module.iot.protocol.message.impl; |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.json.JSONObject; |
| 4 | -import cn.iocoder.yudao.module.iot.protocol.message.IotAlinkMessage; | |
| 4 | +import cn.iocoder.yudao.module.iot.protocol.message.IotMqttMessage; | |
| 5 | 5 | import cn.iocoder.yudao.module.iot.protocol.message.IotStandardResponse; |
| 6 | 6 | import org.junit.jupiter.api.BeforeEach; |
| 7 | 7 | import org.junit.jupiter.api.Test; |
| ... | ... | @@ -58,7 +58,7 @@ class IotHttpMessageParserTest { |
| 58 | 58 | byte[] payload = authMessage.toString().getBytes(StandardCharsets.UTF_8); |
| 59 | 59 | |
| 60 | 60 | // 解析消息 |
| 61 | - IotAlinkMessage result = parser.parse(topic, payload); | |
| 61 | + IotMqttMessage result = parser.parse(topic, payload); | |
| 62 | 62 | |
| 63 | 63 | // 验证结果 |
| 64 | 64 | assertNotNull(result); |
| ... | ... | @@ -88,7 +88,7 @@ class IotHttpMessageParserTest { |
| 88 | 88 | byte[] payload = authMessage.toString().getBytes(StandardCharsets.UTF_8); |
| 89 | 89 | |
| 90 | 90 | // 解析消息 |
| 91 | - IotAlinkMessage result = parser.parse(topic, payload); | |
| 91 | + IotMqttMessage result = parser.parse(topic, payload); | |
| 92 | 92 | |
| 93 | 93 | // 验证结果 |
| 94 | 94 | assertNull(result); |
| ... | ... | @@ -113,7 +113,7 @@ class IotHttpMessageParserTest { |
| 113 | 113 | byte[] payload = dataMessage.toString().getBytes(StandardCharsets.UTF_8); |
| 114 | 114 | |
| 115 | 115 | // 解析消息 |
| 116 | - IotAlinkMessage result = parser.parse(topic, payload); | |
| 116 | + IotMqttMessage result = parser.parse(topic, payload); | |
| 117 | 117 | |
| 118 | 118 | // 验证结果 |
| 119 | 119 | assertNotNull(result); |
| ... | ... | @@ -132,7 +132,7 @@ class IotHttpMessageParserTest { |
| 132 | 132 | byte[] payload = rawData.getBytes(StandardCharsets.UTF_8); |
| 133 | 133 | |
| 134 | 134 | // 解析消息 |
| 135 | - IotAlinkMessage result = parser.parse(topic, payload); | |
| 135 | + IotMqttMessage result = parser.parse(topic, payload); | |
| 136 | 136 | |
| 137 | 137 | // 验证结果 |
| 138 | 138 | assertNotNull(result); |
| ... | ... | @@ -161,7 +161,7 @@ class IotHttpMessageParserTest { |
| 161 | 161 | String rawData = "test data"; |
| 162 | 162 | byte[] payload = rawData.getBytes(StandardCharsets.UTF_8); |
| 163 | 163 | |
| 164 | - IotAlinkMessage result = parser.parse(topic, payload); | |
| 164 | + IotMqttMessage result = parser.parse(topic, payload); | |
| 165 | 165 | assertNotNull(result); |
| 166 | 166 | assertEquals(expectedMethod, result.getMethod()); |
| 167 | 167 | } | ... | ... |
yudao-module-iot/yudao-module-iot-protocol/src/test/java/cn/iocoder/yudao/module/iot/protocol/message/impl/IotMqttMessageParserTest.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.protocol.message.impl; | |
| 2 | + | |
| 3 | +import cn.hutool.json.JSONObject; | |
| 4 | +import cn.iocoder.yudao.module.iot.protocol.message.IotMqttMessage; | |
| 5 | +import cn.iocoder.yudao.module.iot.protocol.message.IotStandardResponse; | |
| 6 | +import org.junit.jupiter.api.BeforeEach; | |
| 7 | +import org.junit.jupiter.api.Test; | |
| 8 | + | |
| 9 | +import java.nio.charset.StandardCharsets; | |
| 10 | +import java.util.HashMap; | |
| 11 | +import java.util.Map; | |
| 12 | + | |
| 13 | +import static org.junit.jupiter.api.Assertions.*; | |
| 14 | + | |
| 15 | +/** | |
| 16 | + * IoT MQTT 消息解析器测试类 | |
| 17 | + * | |
| 18 | + * @author haohao | |
| 19 | + */ | |
| 20 | +class IotMqttMessageParserTest { | |
| 21 | + | |
| 22 | + private IotMqttMessageParser parser; | |
| 23 | + | |
| 24 | + @BeforeEach | |
| 25 | + void setUp() { | |
| 26 | + parser = new IotMqttMessageParser(); | |
| 27 | + } | |
| 28 | + | |
| 29 | + @Test | |
| 30 | + void testParseValidJsonMessage() { | |
| 31 | + // 构建有效的 JSON 消息 | |
| 32 | + JSONObject message = new JSONObject(); | |
| 33 | + message.set("id", "123456"); | |
| 34 | + message.set("version", "1.0"); | |
| 35 | + message.set("method", "thing.service.property.set"); | |
| 36 | + | |
| 37 | + Map<String, Object> params = new HashMap<>(); | |
| 38 | + params.put("temperature", 25.5); | |
| 39 | + params.put("humidity", 60.0); | |
| 40 | + message.set("params", params); | |
| 41 | + | |
| 42 | + String topic = "/sys/productKey/deviceName/thing/service/property/set"; | |
| 43 | + byte[] payload = message.toString().getBytes(StandardCharsets.UTF_8); | |
| 44 | + | |
| 45 | + // 解析消息 | |
| 46 | + IotMqttMessage result = parser.parse(topic, payload); | |
| 47 | + | |
| 48 | + // 验证结果 | |
| 49 | + assertNotNull(result); | |
| 50 | + assertEquals("123456", result.getId()); | |
| 51 | + assertEquals("1.0", result.getVersion()); | |
| 52 | + assertEquals("thing.service.property.set", result.getMethod()); | |
| 53 | + assertNotNull(result.getParams()); | |
| 54 | + assertEquals(25.5, ((Number) result.getParams().get("temperature")).doubleValue()); | |
| 55 | + assertEquals(60.0, ((Number) result.getParams().get("humidity")).doubleValue()); | |
| 56 | + } | |
| 57 | + | |
| 58 | + @Test | |
| 59 | + void testParseMessageWithoutMethod() { | |
| 60 | + // 构建没有 method 字段的消息,应该从 topic 中解析 | |
| 61 | + JSONObject message = new JSONObject(); | |
| 62 | + message.set("id", "789012"); | |
| 63 | + message.set("version", "1.0"); | |
| 64 | + | |
| 65 | + Map<String, Object> params = new HashMap<>(); | |
| 66 | + params.put("voltage", 3.3); | |
| 67 | + message.set("params", params); | |
| 68 | + | |
| 69 | + String topic = "/sys/productKey/deviceName/thing/service/property/set"; | |
| 70 | + byte[] payload = message.toString().getBytes(StandardCharsets.UTF_8); | |
| 71 | + | |
| 72 | + // 解析消息 | |
| 73 | + IotMqttMessage result = parser.parse(topic, payload); | |
| 74 | + | |
| 75 | + // 验证结果 | |
| 76 | + assertNotNull(result); | |
| 77 | + assertEquals("789012", result.getId()); | |
| 78 | + assertEquals("1.0", result.getVersion()); | |
| 79 | + assertNotNull(result.getMethod()); // 应该从 topic 中解析出方法 | |
| 80 | + assertNotNull(result.getParams()); | |
| 81 | + assertEquals(3.3, ((Number) result.getParams().get("voltage")).doubleValue()); | |
| 82 | + } | |
| 83 | + | |
| 84 | + @Test | |
| 85 | + void testParseInvalidJsonMessage() { | |
| 86 | + String topic = "/sys/productKey/deviceName/thing/service/property/set"; | |
| 87 | + byte[] payload = "invalid json".getBytes(StandardCharsets.UTF_8); | |
| 88 | + | |
| 89 | + // 解析消息 | |
| 90 | + IotMqttMessage result = parser.parse(topic, payload); | |
| 91 | + | |
| 92 | + // 验证结果 | |
| 93 | + assertNull(result); | |
| 94 | + } | |
| 95 | + | |
| 96 | + @Test | |
| 97 | + void testParseEmptyPayload() { | |
| 98 | + String topic = "/sys/productKey/deviceName/thing/service/property/set"; | |
| 99 | + | |
| 100 | + // 测试 null payload | |
| 101 | + IotMqttMessage result1 = parser.parse(topic, null); | |
| 102 | + assertNull(result1); | |
| 103 | + | |
| 104 | + // 测试空 payload | |
| 105 | + IotMqttMessage result2 = parser.parse(topic, new byte[0]); | |
| 106 | + assertNull(result2); | |
| 107 | + } | |
| 108 | + | |
| 109 | + @Test | |
| 110 | + void testFormatResponse() { | |
| 111 | + // 创建标准响应 | |
| 112 | + IotStandardResponse response = IotStandardResponse.success("123456", "property.set", null); | |
| 113 | + | |
| 114 | + // 格式化响应 | |
| 115 | + byte[] result = parser.formatResponse(response); | |
| 116 | + | |
| 117 | + // 验证结果 | |
| 118 | + assertNotNull(result); | |
| 119 | + assertTrue(result.length > 0); | |
| 120 | + | |
| 121 | + // 验证 JSON 格式 | |
| 122 | + String jsonString = new String(result, StandardCharsets.UTF_8); | |
| 123 | + assertTrue(jsonString.contains("123456")); | |
| 124 | + assertTrue(jsonString.contains("property.set")); | |
| 125 | + } | |
| 126 | + | |
| 127 | + @Test | |
| 128 | + void testCanHandle() { | |
| 129 | + // 测试支持的主题格式 | |
| 130 | + assertTrue(parser.canHandle("/sys/productKey/deviceName/thing/service/property/set")); | |
| 131 | + assertTrue(parser.canHandle("/mqtt/productKey/deviceName/property/set")); | |
| 132 | + assertTrue(parser.canHandle("/device/productKey/deviceName/data")); | |
| 133 | + | |
| 134 | + // 测试不支持的主题格式 | |
| 135 | + assertFalse(parser.canHandle("/http/device/productKey/deviceName/property/set")); | |
| 136 | + assertFalse(parser.canHandle("/unknown/topic")); | |
| 137 | + assertFalse(parser.canHandle(null)); | |
| 138 | + assertFalse(parser.canHandle("")); | |
| 139 | + } | |
| 140 | + | |
| 141 | + @Test | |
| 142 | + void testParseMqttTopicFormat() { | |
| 143 | + // 测试新的 MQTT 主题格式 | |
| 144 | + JSONObject message = new JSONObject(); | |
| 145 | + message.set("id", "mqtt001"); | |
| 146 | + message.set("version", "1.0"); | |
| 147 | + message.set("method", "device.property.report"); | |
| 148 | + | |
| 149 | + Map<String, Object> params = new HashMap<>(); | |
| 150 | + params.put("signal", 85); | |
| 151 | + message.set("params", params); | |
| 152 | + | |
| 153 | + String topic = "/mqtt/productKey/deviceName/property/report"; | |
| 154 | + byte[] payload = message.toString().getBytes(StandardCharsets.UTF_8); | |
| 155 | + | |
| 156 | + // 解析消息 | |
| 157 | + IotMqttMessage result = parser.parse(topic, payload); | |
| 158 | + | |
| 159 | + // 验证结果 | |
| 160 | + assertNotNull(result); | |
| 161 | + assertEquals("mqtt001", result.getId()); | |
| 162 | + assertEquals("device.property.report", result.getMethod()); | |
| 163 | + assertEquals(85, ((Number) result.getParams().get("signal")).intValue()); | |
| 164 | + } | |
| 165 | + | |
| 166 | + @Test | |
| 167 | + void testParseDeviceTopicFormat() { | |
| 168 | + // 测试设备主题格式 | |
| 169 | + JSONObject message = new JSONObject(); | |
| 170 | + message.set("id", "device001"); | |
| 171 | + message.set("version", "1.0"); | |
| 172 | + message.set("method", "sensor.data"); | |
| 173 | + | |
| 174 | + Map<String, Object> params = new HashMap<>(); | |
| 175 | + params.put("timestamp", System.currentTimeMillis()); | |
| 176 | + message.set("params", params); | |
| 177 | + | |
| 178 | + String topic = "/device/productKey/deviceName/sensor/data"; | |
| 179 | + byte[] payload = message.toString().getBytes(StandardCharsets.UTF_8); | |
| 180 | + | |
| 181 | + // 解析消息 | |
| 182 | + IotMqttMessage result = parser.parse(topic, payload); | |
| 183 | + | |
| 184 | + // 验证结果 | |
| 185 | + assertNotNull(result); | |
| 186 | + assertEquals("device001", result.getId()); | |
| 187 | + assertEquals("sensor.data", result.getMethod()); | |
| 188 | + assertNotNull(result.getParams().get("timestamp")); | |
| 189 | + } | |
| 190 | +} | |
| 0 | 191 | \ No newline at end of file | ... | ... |