Commit 800a85f7bc6a327109a54d62993b7e0be97eb2eb
1 parent
479e0356
reactor:【IoT 物联网】清理 components 和 protocol,基本已经融合到 gateway
Showing
63 changed files
with
1 additions
and
5759 deletions
yudao-module-iot/pom.xml
| @@ -9,8 +9,6 @@ | @@ -9,8 +9,6 @@ | ||
| 9 | <modules> | 9 | <modules> |
| 10 | <module>yudao-module-iot-api</module> | 10 | <module>yudao-module-iot-api</module> |
| 11 | <module>yudao-module-iot-biz</module> | 11 | <module>yudao-module-iot-biz</module> |
| 12 | - <module>yudao-module-iot-net-components</module> | ||
| 13 | - <module>yudao-module-iot-protocol</module> | ||
| 14 | <module>yudao-module-iot-core</module> | 12 | <module>yudao-module-iot-core</module> |
| 15 | <module>yudao-module-iot-gateway</module> | 13 | <module>yudao-module-iot-gateway</module> |
| 16 | </modules> | 14 | </modules> |
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/downstream/IotDeviceConfigSetReqDTO.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.api.device.dto.control.downstream; | ||
| 2 | - | ||
| 3 | -import jakarta.validation.constraints.NotNull; | ||
| 4 | -import lombok.Data; | ||
| 5 | - | ||
| 6 | -import java.util.Map; | ||
| 7 | - | ||
| 8 | -/** | ||
| 9 | - * IoT 设备【配置】设置 Request DTO | ||
| 10 | - * | ||
| 11 | - * @author 芋道源码 | ||
| 12 | - */ | ||
| 13 | -@Data | ||
| 14 | -public class IotDeviceConfigSetReqDTO extends IotDeviceDownstreamAbstractReqDTO { | ||
| 15 | - | ||
| 16 | - /** | ||
| 17 | - * 配置 | ||
| 18 | - */ | ||
| 19 | - @NotNull(message = "配置不能为空") | ||
| 20 | - private Map<String, Object> config; | ||
| 21 | - | ||
| 22 | -} |
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/downstream/IotDeviceDownstreamAbstractReqDTO.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.api.device.dto.control.downstream; | ||
| 2 | - | ||
| 3 | -import jakarta.validation.constraints.NotEmpty; | ||
| 4 | -import lombok.Data; | ||
| 5 | - | ||
| 6 | -/** | ||
| 7 | - * IoT 设备下行的抽象 Request DTO | ||
| 8 | - * | ||
| 9 | - * @author 芋道源码 | ||
| 10 | - */ | ||
| 11 | -@Data | ||
| 12 | -public abstract class IotDeviceDownstreamAbstractReqDTO { | ||
| 13 | - | ||
| 14 | - /** | ||
| 15 | - * 请求编号 | ||
| 16 | - */ | ||
| 17 | - private String requestId; | ||
| 18 | - | ||
| 19 | - /** | ||
| 20 | - * 产品标识 | ||
| 21 | - */ | ||
| 22 | - @NotEmpty(message = "产品标识不能为空") | ||
| 23 | - private String productKey; | ||
| 24 | - /** | ||
| 25 | - * 设备名称 | ||
| 26 | - */ | ||
| 27 | - @NotEmpty(message = "设备名称不能为空") | ||
| 28 | - private String deviceName; | ||
| 29 | - | ||
| 30 | -} |
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/downstream/IotDeviceOtaUpgradeReqDTO.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.api.device.dto.control.downstream; | ||
| 2 | - | ||
| 3 | -import cn.hutool.core.map.MapUtil; | ||
| 4 | -import lombok.Data; | ||
| 5 | - | ||
| 6 | -import java.util.Map; | ||
| 7 | - | ||
| 8 | -/** | ||
| 9 | - * IoT 设备【OTA】升级下发 Request DTO(更新固件消息) | ||
| 10 | - * | ||
| 11 | - * @author 芋道源码 | ||
| 12 | - */ | ||
| 13 | -@Data | ||
| 14 | -public class IotDeviceOtaUpgradeReqDTO extends IotDeviceDownstreamAbstractReqDTO { | ||
| 15 | - | ||
| 16 | - /** | ||
| 17 | - * 固件编号 | ||
| 18 | - */ | ||
| 19 | - private Long firmwareId; | ||
| 20 | - /** | ||
| 21 | - * 固件版本 | ||
| 22 | - */ | ||
| 23 | - private String version; | ||
| 24 | - | ||
| 25 | - /** | ||
| 26 | - * 签名方式 | ||
| 27 | - * | ||
| 28 | - * 例如说:MD5、SHA256 | ||
| 29 | - */ | ||
| 30 | - private String signMethod; | ||
| 31 | - /** | ||
| 32 | - * 固件文件签名 | ||
| 33 | - */ | ||
| 34 | - private String fileSign; | ||
| 35 | - /** | ||
| 36 | - * 固件文件大小 | ||
| 37 | - */ | ||
| 38 | - private Long fileSize; | ||
| 39 | - /** | ||
| 40 | - * 固件文件 URL | ||
| 41 | - */ | ||
| 42 | - private String fileUrl; | ||
| 43 | - | ||
| 44 | - /** | ||
| 45 | - * 自定义信息,建议使用 JSON 格式 | ||
| 46 | - */ | ||
| 47 | - private String information; | ||
| 48 | - | ||
| 49 | - public static IotDeviceOtaUpgradeReqDTO build(Map<?, ?> map) { | ||
| 50 | - return new IotDeviceOtaUpgradeReqDTO() | ||
| 51 | - .setFirmwareId(MapUtil.getLong(map, "firmwareId")).setVersion((String) map.get("version")) | ||
| 52 | - .setSignMethod((String) map.get("signMethod")).setFileSign((String) map.get("fileSign")) | ||
| 53 | - .setFileSize(MapUtil.getLong(map, "fileSize")).setFileUrl((String) map.get("fileUrl")) | ||
| 54 | - .setInformation((String) map.get("information")); | ||
| 55 | - } | ||
| 56 | - | ||
| 57 | - public static Map<?, ?> build(IotDeviceOtaUpgradeReqDTO dto) { | ||
| 58 | - return MapUtil.builder() | ||
| 59 | - .put("firmwareId", dto.getFirmwareId()).put("version", dto.getVersion()) | ||
| 60 | - .put("signMethod", dto.getSignMethod()).put("fileSign", dto.getFileSign()) | ||
| 61 | - .put("fileSize", dto.getFileSize()).put("fileUrl", dto.getFileUrl()) | ||
| 62 | - .put("information", dto.getInformation()) | ||
| 63 | - .build(); | ||
| 64 | - } | ||
| 65 | - | ||
| 66 | -} |
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/downstream/IotDevicePropertyGetReqDTO.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.api.device.dto.control.downstream; | ||
| 2 | - | ||
| 3 | -import jakarta.validation.constraints.NotEmpty; | ||
| 4 | -import lombok.Data; | ||
| 5 | - | ||
| 6 | -import java.util.List; | ||
| 7 | - | ||
| 8 | -// TODO @芋艿:从 server => plugin => device 是否有必要?从阿里云 iot 来看,没有这个功能?! | ||
| 9 | -// TODO @芋艿:是不是改成 read 更好?在看看阿里云的 topic 设计 | ||
| 10 | -/** | ||
| 11 | - * IoT 设备【属性】获取 Request DTO | ||
| 12 | - * | ||
| 13 | - * @author 芋道源码 | ||
| 14 | - */ | ||
| 15 | -@Data | ||
| 16 | -public class IotDevicePropertyGetReqDTO extends IotDeviceDownstreamAbstractReqDTO { | ||
| 17 | - | ||
| 18 | - /** | ||
| 19 | - * 属性标识数组 | ||
| 20 | - */ | ||
| 21 | - @NotEmpty(message = "属性标识数组不能为空") | ||
| 22 | - private List<String> identifiers; | ||
| 23 | - | ||
| 24 | -} |
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/downstream/IotDevicePropertySetReqDTO.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.api.device.dto.control.downstream; | ||
| 2 | - | ||
| 3 | -import jakarta.validation.constraints.NotEmpty; | ||
| 4 | -import lombok.Data; | ||
| 5 | - | ||
| 6 | -import java.util.Map; | ||
| 7 | - | ||
| 8 | -/** | ||
| 9 | - * IoT 设备【属性】设置 Request DTO | ||
| 10 | - * | ||
| 11 | - * @author 芋道源码 | ||
| 12 | - */ | ||
| 13 | -@Data | ||
| 14 | -public class IotDevicePropertySetReqDTO extends IotDeviceDownstreamAbstractReqDTO { | ||
| 15 | - | ||
| 16 | - /** | ||
| 17 | - * 属性参数 | ||
| 18 | - */ | ||
| 19 | - @NotEmpty(message = "属性参数不能为空") | ||
| 20 | - private Map<String, Object> properties; | ||
| 21 | - | ||
| 22 | -} |
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/downstream/IotDeviceServiceInvokeReqDTO.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.api.device.dto.control.downstream; | ||
| 2 | - | ||
| 3 | -import jakarta.validation.constraints.NotEmpty; | ||
| 4 | -import lombok.Data; | ||
| 5 | - | ||
| 6 | -import java.util.Map; | ||
| 7 | - | ||
| 8 | -/** | ||
| 9 | - * IoT 设备【服务】调用 Request DTO | ||
| 10 | - * | ||
| 11 | - * @author 芋道源码 | ||
| 12 | - */ | ||
| 13 | -@Data | ||
| 14 | -public class IotDeviceServiceInvokeReqDTO extends IotDeviceDownstreamAbstractReqDTO { | ||
| 15 | - | ||
| 16 | - /** | ||
| 17 | - * 服务标识 | ||
| 18 | - */ | ||
| 19 | - @NotEmpty(message = "服务标识不能为空") | ||
| 20 | - private String identifier; | ||
| 21 | - /** | ||
| 22 | - * 调用参数 | ||
| 23 | - */ | ||
| 24 | - private Map<String, Object> params; | ||
| 25 | - | ||
| 26 | -} |
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceOtaProgressReqDTO.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.api.device.dto.control.upstream; | ||
| 2 | - | ||
| 3 | -import lombok.Data; | ||
| 4 | - | ||
| 5 | -// TODO @芋艿:待实现:/ota/${productKey}/${deviceName}/progress | ||
| 6 | -/** | ||
| 7 | - * IoT 设备【OTA】升级进度 Request DTO(上报更新固件进度) | ||
| 8 | - * | ||
| 9 | - * @author 芋道源码 | ||
| 10 | - */ | ||
| 11 | -@Data | ||
| 12 | -public class IotDeviceOtaProgressReqDTO extends IotDeviceUpstreamAbstractReqDTO { | ||
| 13 | - | ||
| 14 | - /** | ||
| 15 | - * 固件编号 | ||
| 16 | - */ | ||
| 17 | - private Long firmwareId; | ||
| 18 | - | ||
| 19 | - /** | ||
| 20 | - * 升级状态 | ||
| 21 | - * | ||
| 22 | - * 枚举 {@link cn.iocoder.yudao.module.iot.enums.ota.IotOtaUpgradeRecordStatusEnum} | ||
| 23 | - */ | ||
| 24 | - private Integer status; | ||
| 25 | - /** | ||
| 26 | - * 升级进度,百分比 | ||
| 27 | - */ | ||
| 28 | - private Integer progress; | ||
| 29 | - | ||
| 30 | - /** | ||
| 31 | - * 升级进度描述 | ||
| 32 | - */ | ||
| 33 | - private String description; | ||
| 34 | - | ||
| 35 | -} |
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceOtaPullReqDTO.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.api.device.dto.control.upstream; | ||
| 2 | - | ||
| 3 | -// TODO @芋艿:待实现:/ota/${productKey}/${deviceName}/pull | ||
| 4 | -/** | ||
| 5 | - * IoT 设备【OTA】升级下拉 Request DTO(拉取固件更新) | ||
| 6 | - * | ||
| 7 | - * @author 芋道源码 | ||
| 8 | - */ | ||
| 9 | -public class IotDeviceOtaPullReqDTO { | ||
| 10 | - | ||
| 11 | - /** | ||
| 12 | - * 固件编号 | ||
| 13 | - */ | ||
| 14 | - private Long firmwareId; | ||
| 15 | - | ||
| 16 | - /** | ||
| 17 | - * 固件版本 | ||
| 18 | - */ | ||
| 19 | - private String version; | ||
| 20 | - | ||
| 21 | -} |
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceOtaReportReqDTO.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.api.device.dto.control.upstream; | ||
| 2 | - | ||
| 3 | -// TODO @芋艿:待实现:/ota/${productKey}/${deviceName}/report | ||
| 4 | -/** | ||
| 5 | - * IoT 设备【OTA】上报 Request DTO(上报固件版本) | ||
| 6 | - * | ||
| 7 | - * @author 芋道源码 | ||
| 8 | - */ | ||
| 9 | -public class IotDeviceOtaReportReqDTO { | ||
| 10 | - | ||
| 11 | - /** | ||
| 12 | - * 固件编号 | ||
| 13 | - */ | ||
| 14 | - private Long firmwareId; | ||
| 15 | - | ||
| 16 | - /** | ||
| 17 | - * 固件版本 | ||
| 18 | - */ | ||
| 19 | - private String version; | ||
| 20 | - | ||
| 21 | -} |
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ApiConstants.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.enums; | ||
| 2 | - | ||
| 3 | -import cn.iocoder.yudao.framework.common.enums.RpcConstants; | ||
| 4 | - | ||
| 5 | -/** | ||
| 6 | - * API 相关的枚举 | ||
| 7 | - * | ||
| 8 | - * @author 芋道源码 | ||
| 9 | - */ | ||
| 10 | -public class ApiConstants { | ||
| 11 | - | ||
| 12 | - public static final String PREFIX = RpcConstants.RPC_API_PREFIX + "/iot"; | ||
| 13 | - | ||
| 14 | - public static final String VERSION = "1.0.0"; | ||
| 15 | - | ||
| 16 | -} |
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/DictTypeConstants.java
| @@ -15,8 +15,5 @@ public class DictTypeConstants { | @@ -15,8 +15,5 @@ public class DictTypeConstants { | ||
| 15 | public static final String VALIDATE_TYPE = "iot_validate_type"; | 15 | public static final String VALIDATE_TYPE = "iot_validate_type"; |
| 16 | 16 | ||
| 17 | public static final String DEVICE_STATE = "iot_device_state"; | 17 | public static final String DEVICE_STATE = "iot_device_state"; |
| 18 | - | ||
| 19 | - public static final String IOT_DATA_BRIDGE_DIRECTION_ENUM = "iot_data_bridge_direction_enum"; | ||
| 20 | - public static final String IOT_DATA_BRIDGE_TYPE_ENUM = "iot_data_bridge_type_enum"; | ||
| 21 | 18 | ||
| 22 | } | 19 | } |
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginDeployTypeEnum.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.enums.plugin; | ||
| 2 | - | ||
| 3 | -import cn.iocoder.yudao.framework.common.core.ArrayValuable; | ||
| 4 | -import lombok.Getter; | ||
| 5 | -import lombok.RequiredArgsConstructor; | ||
| 6 | - | ||
| 7 | -import java.util.Arrays; | ||
| 8 | - | ||
| 9 | -/** | ||
| 10 | - * IoT 部署方式枚举 | ||
| 11 | - * | ||
| 12 | - * @author haohao | ||
| 13 | - */ | ||
| 14 | -@RequiredArgsConstructor | ||
| 15 | -@Getter | ||
| 16 | -public enum IotPluginDeployTypeEnum implements ArrayValuable<Integer> { | ||
| 17 | - | ||
| 18 | - JAR(0, "JAR 部署"), | ||
| 19 | - STANDALONE(1, "独立部署"); | ||
| 20 | - | ||
| 21 | - public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotPluginDeployTypeEnum::getDeployType).toArray(Integer[]::new); | ||
| 22 | - | ||
| 23 | - /** | ||
| 24 | - * 部署方式 | ||
| 25 | - */ | ||
| 26 | - private final Integer deployType; | ||
| 27 | - /** | ||
| 28 | - * 部署方式名 | ||
| 29 | - */ | ||
| 30 | - private final String name; | ||
| 31 | - | ||
| 32 | - @Override | ||
| 33 | - public Integer[] array() { | ||
| 34 | - return ARRAYS; | ||
| 35 | - } | ||
| 36 | - | ||
| 37 | -} |
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginStatusEnum.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.enums.plugin; | ||
| 2 | - | ||
| 3 | -import cn.iocoder.yudao.framework.common.core.ArrayValuable; | ||
| 4 | -import lombok.Getter; | ||
| 5 | -import lombok.RequiredArgsConstructor; | ||
| 6 | - | ||
| 7 | -import java.util.Arrays; | ||
| 8 | - | ||
| 9 | -/** | ||
| 10 | - * IoT 插件状态枚举 | ||
| 11 | - * | ||
| 12 | - * @author haohao | ||
| 13 | - */ | ||
| 14 | -@RequiredArgsConstructor | ||
| 15 | -@Getter | ||
| 16 | -public enum IotPluginStatusEnum implements ArrayValuable<Integer> { | ||
| 17 | - | ||
| 18 | - STOPPED(0, "停止"), | ||
| 19 | - RUNNING(1, "运行"); | ||
| 20 | - | ||
| 21 | - public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotPluginStatusEnum::getStatus).toArray(Integer[]::new); | ||
| 22 | - | ||
| 23 | - /** | ||
| 24 | - * 状态 | ||
| 25 | - */ | ||
| 26 | - private final Integer status; | ||
| 27 | - /** | ||
| 28 | - * 状态名 | ||
| 29 | - */ | ||
| 30 | - private final String name; | ||
| 31 | - | ||
| 32 | - @Override | ||
| 33 | - public Integer[] array() { | ||
| 34 | - return ARRAYS; | ||
| 35 | - } | ||
| 36 | - | ||
| 37 | -} |
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginTypeEnum.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.enums.plugin; | ||
| 2 | - | ||
| 3 | -import cn.iocoder.yudao.framework.common.core.ArrayValuable; | ||
| 4 | -import lombok.AllArgsConstructor; | ||
| 5 | -import lombok.Getter; | ||
| 6 | - | ||
| 7 | -import java.util.Arrays; | ||
| 8 | - | ||
| 9 | -/** | ||
| 10 | - * IoT 插件类型枚举 | ||
| 11 | - * | ||
| 12 | - * @author haohao | ||
| 13 | - */ | ||
| 14 | -@AllArgsConstructor | ||
| 15 | -@Getter | ||
| 16 | -public enum IotPluginTypeEnum implements ArrayValuable<Integer> { | ||
| 17 | - | ||
| 18 | - NORMAL(0, "普通插件"), | ||
| 19 | - DEVICE(1, "设备插件"); | ||
| 20 | - | ||
| 21 | - public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotPluginTypeEnum::getType).toArray(Integer[]::new); | ||
| 22 | - | ||
| 23 | - /** | ||
| 24 | - * 类型 | ||
| 25 | - */ | ||
| 26 | - private final Integer type; | ||
| 27 | - /** | ||
| 28 | - * 类型名 | ||
| 29 | - */ | ||
| 30 | - private final String name; | ||
| 31 | - | ||
| 32 | - @Override | ||
| 33 | - public Integer[] array() { | ||
| 34 | - return ARRAYS; | ||
| 35 | - } | ||
| 36 | - | ||
| 37 | -} |
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotProductScriptLanguageEnum.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.enums.product; | ||
| 2 | - | ||
| 3 | -import cn.iocoder.yudao.framework.common.core.ArrayValuable; | ||
| 4 | -import lombok.AllArgsConstructor; | ||
| 5 | -import lombok.Getter; | ||
| 6 | - | ||
| 7 | -import java.util.Arrays; | ||
| 8 | - | ||
| 9 | -/** | ||
| 10 | - * IoT 产品脚本语言枚举 | ||
| 11 | - * | ||
| 12 | - * @author 芋道源码 | ||
| 13 | - */ | ||
| 14 | -@Getter | ||
| 15 | -@AllArgsConstructor | ||
| 16 | -public enum IotProductScriptLanguageEnum implements ArrayValuable<String> { | ||
| 17 | - | ||
| 18 | - JAVASCRIPT("javascript", "JavaScript"), | ||
| 19 | - JAVA("java", "Java"), | ||
| 20 | - PYTHON("python", "Python"), | ||
| 21 | - ; | ||
| 22 | - | ||
| 23 | - public static final String[] ARRAYS = Arrays.stream(values()).map(IotProductScriptLanguageEnum::getCode) | ||
| 24 | - .toArray(String[]::new); | ||
| 25 | - | ||
| 26 | - /** | ||
| 27 | - * 编码 | ||
| 28 | - */ | ||
| 29 | - private final String code; | ||
| 30 | - /** | ||
| 31 | - * 名称 | ||
| 32 | - */ | ||
| 33 | - private final String name; | ||
| 34 | - | ||
| 35 | - @Override | ||
| 36 | - public String[] array() { | ||
| 37 | - return ARRAYS; | ||
| 38 | - } | ||
| 39 | - | ||
| 40 | - public static IotProductScriptLanguageEnum getByCode(String code) { | ||
| 41 | - return Arrays.stream(values()) | ||
| 42 | - .filter(type -> type.getCode().equals(code)) | ||
| 43 | - .findFirst() | ||
| 44 | - .orElse(null); | ||
| 45 | - } | ||
| 46 | - | ||
| 47 | -} | ||
| 48 | \ No newline at end of file | 0 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotProductScriptStatusEnum.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.enums.product; | ||
| 2 | - | ||
| 3 | -import cn.iocoder.yudao.framework.common.core.ArrayValuable; | ||
| 4 | -import lombok.AllArgsConstructor; | ||
| 5 | -import lombok.Getter; | ||
| 6 | - | ||
| 7 | -import java.util.Arrays; | ||
| 8 | - | ||
| 9 | -// TODO @haohao:要不复用 commonstatus? | ||
| 10 | -/** | ||
| 11 | - * IoT 产品脚本状态枚举 | ||
| 12 | - * | ||
| 13 | - * @author 芋道源码 | ||
| 14 | - */ | ||
| 15 | -@Getter | ||
| 16 | -@AllArgsConstructor | ||
| 17 | -public enum IotProductScriptStatusEnum implements ArrayValuable<Integer> { | ||
| 18 | - | ||
| 19 | - ENABLE(0, "启用"), | ||
| 20 | - DISABLE(1, "禁用"), | ||
| 21 | - ; | ||
| 22 | - | ||
| 23 | - public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotProductScriptStatusEnum::getStatus) | ||
| 24 | - .toArray(Integer[]::new); | ||
| 25 | - | ||
| 26 | - /** | ||
| 27 | - * 状态值 | ||
| 28 | - */ | ||
| 29 | - private final Integer status; | ||
| 30 | - /** | ||
| 31 | - * 状态名 | ||
| 32 | - */ | ||
| 33 | - private final String name; | ||
| 34 | - | ||
| 35 | - @Override | ||
| 36 | - public Integer[] array() { | ||
| 37 | - return ARRAYS; | ||
| 38 | - } | ||
| 39 | - | ||
| 40 | - public static IotProductScriptStatusEnum getByStatus(Integer status) { | ||
| 41 | - return Arrays.stream(values()) | ||
| 42 | - .filter(type -> type.getStatus().equals(status)) | ||
| 43 | - .findFirst() | ||
| 44 | - .orElse(null); | ||
| 45 | - } | ||
| 46 | - | ||
| 47 | - public static boolean isEnable(Integer status) { | ||
| 48 | - return ENABLE.getStatus().equals(status); | ||
| 49 | - } | ||
| 50 | - | ||
| 51 | - public static boolean isDisable(Integer status) { | ||
| 52 | - return DISABLE.getStatus().equals(status); | ||
| 53 | - } | ||
| 54 | -} | ||
| 55 | \ No newline at end of file | 0 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotProductScriptTypeEnum.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.enums.product; | ||
| 2 | - | ||
| 3 | -import cn.iocoder.yudao.framework.common.core.ArrayValuable; | ||
| 4 | -import lombok.AllArgsConstructor; | ||
| 5 | -import lombok.Getter; | ||
| 6 | - | ||
| 7 | -import java.util.Arrays; | ||
| 8 | - | ||
| 9 | -/** | ||
| 10 | - * IoT 产品脚本类型枚举 | ||
| 11 | - * | ||
| 12 | - * @author 芋道源码 | ||
| 13 | - */ | ||
| 14 | -@Getter | ||
| 15 | -@AllArgsConstructor | ||
| 16 | -public enum IotProductScriptTypeEnum implements ArrayValuable<Integer> { | ||
| 17 | - | ||
| 18 | - PROPERTY_PARSER(1, "property_parser", "属性解析"), | ||
| 19 | - EVENT_PARSER(2, "event_parser", "事件解析"), | ||
| 20 | - COMMAND_ENCODER(3, "command_encoder", "命令编码"), | ||
| 21 | - ; | ||
| 22 | - | ||
| 23 | - public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotProductScriptTypeEnum::getCode) | ||
| 24 | - .toArray(Integer[]::new); | ||
| 25 | - | ||
| 26 | - /** | ||
| 27 | - * 编码 | ||
| 28 | - */ | ||
| 29 | - private final Integer code; | ||
| 30 | - /** | ||
| 31 | - * 类型 | ||
| 32 | - */ | ||
| 33 | - private final String type; | ||
| 34 | - /** | ||
| 35 | - * 名称 | ||
| 36 | - */ | ||
| 37 | - private final String name; | ||
| 38 | - | ||
| 39 | - @Override | ||
| 40 | - public Integer[] array() { | ||
| 41 | - return ARRAYS; | ||
| 42 | - } | ||
| 43 | - | ||
| 44 | - public static IotProductScriptTypeEnum getByCode(Integer code) { | ||
| 45 | - return Arrays.stream(values()) | ||
| 46 | - .filter(type -> type.getCode().equals(code)) | ||
| 47 | - .findFirst() | ||
| 48 | - .orElse(null); | ||
| 49 | - } | ||
| 50 | -} | ||
| 51 | \ No newline at end of file | 0 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/control/IotDeviceDownstreamServiceImpl.java
| @@ -55,7 +55,7 @@ public class IotDeviceDownstreamServiceImpl implements IotDeviceDownstreamServic | @@ -55,7 +55,7 @@ public class IotDeviceDownstreamServiceImpl implements IotDeviceDownstreamServic | ||
| 55 | if (StrUtil.isEmpty(serverId)) { | 55 | if (StrUtil.isEmpty(serverId)) { |
| 56 | throw exception(DEVICE_DOWNSTREAM_FAILED_SERVER_ID_NULL); | 56 | throw exception(DEVICE_DOWNSTREAM_FAILED_SERVER_ID_NULL); |
| 57 | } | 57 | } |
| 58 | - deviceMessageProducer.sendGatewayDeviceMessage(serverId, message); | 58 | + deviceMessageProducer.sendDeviceMessageToGateway(serverId, message); |
| 59 | 59 | ||
| 60 | // 3.2 发送给服务器(用于设备日志等的记录) | 60 | // 3.2 发送给服务器(用于设备日志等的记录) |
| 61 | deviceMessageProducer.sendDeviceMessage(message); | 61 | deviceMessageProducer.sendDeviceMessage(message); |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/package-info.java deleted
yudao-module-iot/yudao-module-iot-net-components/pom.xml deleted
| 1 | -<?xml version="1.0" encoding="UTF-8"?> | ||
| 2 | -<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
| 3 | - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
| 4 | - <parent> | ||
| 5 | - <artifactId>yudao-module-iot</artifactId> | ||
| 6 | - <groupId>cn.iocoder.boot</groupId> | ||
| 7 | - <version>${revision}</version> | ||
| 8 | - </parent> | ||
| 9 | - <modelVersion>4.0.0</modelVersion> | ||
| 10 | - | ||
| 11 | - <artifactId>yudao-module-iot-net-components</artifactId> | ||
| 12 | - <packaging>pom</packaging> | ||
| 13 | - | ||
| 14 | - <name>${project.artifactId}</name> | ||
| 15 | - <description> | ||
| 16 | - 物联网网络组件模块,提供与物联网设备通讯、管理的网络组件实现 | ||
| 17 | - </description> | ||
| 18 | - | ||
| 19 | - <modules> | ||
| 20 | - <module>yudao-module-iot-net-component-core</module> | ||
| 21 | - <module>yudao-module-iot-net-component-emqx</module> | ||
| 22 | - <module>yudao-module-iot-net-component-server</module> | ||
| 23 | - </modules> | ||
| 24 | - | ||
| 25 | -</project> | ||
| 26 | \ No newline at end of file | 0 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/pom.xml deleted
| 1 | -<?xml version="1.0" encoding="UTF-8"?> | ||
| 2 | -<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
| 3 | - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
| 4 | - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
| 5 | - <parent> | ||
| 6 | - <artifactId>yudao-module-iot-net-components</artifactId> | ||
| 7 | - <groupId>cn.iocoder.boot</groupId> | ||
| 8 | - <version>${revision}</version> | ||
| 9 | - </parent> | ||
| 10 | - <modelVersion>4.0.0</modelVersion> | ||
| 11 | - | ||
| 12 | - <artifactId>yudao-module-iot-net-component-core</artifactId> | ||
| 13 | - <packaging>jar</packaging> | ||
| 14 | - | ||
| 15 | - <name>${project.artifactId}</name> | ||
| 16 | - <description> | ||
| 17 | - 物联网网络组件核心模块 | ||
| 18 | - </description> | ||
| 19 | - | ||
| 20 | - <dependencies> | ||
| 21 | - <dependency> | ||
| 22 | - <groupId>cn.iocoder.boot</groupId> | ||
| 23 | - <artifactId>yudao-module-iot-api</artifactId> | ||
| 24 | - <version>${revision}</version> | ||
| 25 | - </dependency> | ||
| 26 | - | ||
| 27 | - <dependency> | ||
| 28 | - <groupId>cn.iocoder.boot</groupId> | ||
| 29 | - <artifactId>yudao-module-iot-core</artifactId> | ||
| 30 | - <version>${revision}</version> | ||
| 31 | - </dependency> | ||
| 32 | - | ||
| 33 | - <!-- IoT 协议模块 --> | ||
| 34 | - <dependency> | ||
| 35 | - <groupId>cn.iocoder.boot</groupId> | ||
| 36 | - <artifactId>yudao-module-iot-protocol</artifactId> | ||
| 37 | - <version>${revision}</version> | ||
| 38 | - </dependency> | ||
| 39 | - | ||
| 40 | - <dependency> | ||
| 41 | - <groupId>org.springframework.boot</groupId> | ||
| 42 | - <artifactId>spring-boot-starter</artifactId> | ||
| 43 | - </dependency> | ||
| 44 | - | ||
| 45 | - <!-- Web 相关 --> | ||
| 46 | - <dependency> | ||
| 47 | - <groupId>org.springframework</groupId> | ||
| 48 | - <artifactId>spring-web</artifactId> | ||
| 49 | - </dependency> | ||
| 50 | - | ||
| 51 | - <!-- 工具类相关 --> | ||
| 52 | - <dependency> | ||
| 53 | - <groupId>io.vertx</groupId> | ||
| 54 | - <artifactId>vertx-web</artifactId> | ||
| 55 | - <optional>true</optional> | ||
| 56 | - </dependency> | ||
| 57 | - | ||
| 58 | - <!-- 参数校验 --> | ||
| 59 | - <dependency> | ||
| 60 | - <groupId>org.springframework.boot</groupId> | ||
| 61 | - <artifactId>spring-boot-starter-validation</artifactId> | ||
| 62 | - <optional>true</optional> | ||
| 63 | - </dependency> | ||
| 64 | - </dependencies> | ||
| 65 | -</project> | ||
| 66 | \ No newline at end of file | 0 | \ No newline at end of file |
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 deleted
| 1 | -package cn.iocoder.yudao.module.iot.net.component.core.message; | ||
| 2 | - | ||
| 3 | -import cn.hutool.core.util.IdUtil; | ||
| 4 | -import cn.hutool.json.JSONObject; | ||
| 5 | -import lombok.Builder; | ||
| 6 | -import lombok.Data; | ||
| 7 | - | ||
| 8 | -import java.util.Map; | ||
| 9 | - | ||
| 10 | -/** | ||
| 11 | - * IoT MQTT 消息模型 | ||
| 12 | - * <p> | ||
| 13 | - * 基于 MQTT 协议规范实现的标准消息格式,兼容 Alink 协议 | ||
| 14 | - * | ||
| 15 | - * @author haohao | ||
| 16 | - */ | ||
| 17 | -@Data | ||
| 18 | -@Builder | ||
| 19 | -public class IotMqttMessage { | ||
| 20 | - | ||
| 21 | - /** | ||
| 22 | - * 消息 ID | ||
| 23 | - */ | ||
| 24 | - private String id; | ||
| 25 | - | ||
| 26 | - /** | ||
| 27 | - * 协议版本 | ||
| 28 | - */ | ||
| 29 | - @Builder.Default | ||
| 30 | - private String version = "1.0"; | ||
| 31 | - | ||
| 32 | - /** | ||
| 33 | - * 消息方法 | ||
| 34 | - */ | ||
| 35 | - private String method; | ||
| 36 | - | ||
| 37 | - /** | ||
| 38 | - * 消息参数 | ||
| 39 | - */ | ||
| 40 | - private Map<String, Object> params; | ||
| 41 | - | ||
| 42 | - /** | ||
| 43 | - * 转换为 JSONObject | ||
| 44 | - * | ||
| 45 | - * @return JSONObject 对象 | ||
| 46 | - */ | ||
| 47 | - public JSONObject toJsonObject() { | ||
| 48 | - JSONObject json = new JSONObject(); | ||
| 49 | - json.set("id", id); | ||
| 50 | - json.set("version", version); | ||
| 51 | - json.set("method", method); | ||
| 52 | - json.set("params", params != null ? params : new JSONObject()); | ||
| 53 | - return json; | ||
| 54 | - } | ||
| 55 | - | ||
| 56 | - /** | ||
| 57 | - * 转换为 JSON 字符串 | ||
| 58 | - * | ||
| 59 | - * @return JSON 字符串 | ||
| 60 | - */ | ||
| 61 | - public String toJsonString() { | ||
| 62 | - return toJsonObject().toString(); | ||
| 63 | - } | ||
| 64 | - | ||
| 65 | - /** | ||
| 66 | - * 创建设备服务调用消息 | ||
| 67 | - * | ||
| 68 | - * @param requestId 请求 ID,为空时自动生成 | ||
| 69 | - * @param serviceIdentifier 服务标识符 | ||
| 70 | - * @param params 服务参数 | ||
| 71 | - * @return MQTT 消息对象 | ||
| 72 | - */ | ||
| 73 | - public static IotMqttMessage createServiceInvokeMessage(String requestId, String serviceIdentifier, | ||
| 74 | - Map<String, Object> params) { | ||
| 75 | - return IotMqttMessage.builder() | ||
| 76 | - .id(requestId != null ? requestId : generateRequestId()) | ||
| 77 | - .method("thing.service." + serviceIdentifier) | ||
| 78 | - .params(params) | ||
| 79 | - .build(); | ||
| 80 | - } | ||
| 81 | - | ||
| 82 | - /** | ||
| 83 | - * 创建设备属性设置消息 | ||
| 84 | - * | ||
| 85 | - * @param requestId 请求 ID,为空时自动生成 | ||
| 86 | - * @param properties 设备属性 | ||
| 87 | - * @return MQTT 消息对象 | ||
| 88 | - */ | ||
| 89 | - public static IotMqttMessage createPropertySetMessage(String requestId, Map<String, Object> properties) { | ||
| 90 | - return IotMqttMessage.builder() | ||
| 91 | - .id(requestId != null ? requestId : generateRequestId()) | ||
| 92 | - .method("thing.service.property.set") | ||
| 93 | - .params(properties) | ||
| 94 | - .build(); | ||
| 95 | - } | ||
| 96 | - | ||
| 97 | - /** | ||
| 98 | - * 创建设备属性获取消息 | ||
| 99 | - * | ||
| 100 | - * @param requestId 请求 ID,为空时自动生成 | ||
| 101 | - * @param identifiers 要获取的属性标识符列表 | ||
| 102 | - * @return MQTT 消息对象 | ||
| 103 | - */ | ||
| 104 | - public static IotMqttMessage createPropertyGetMessage(String requestId, String[] identifiers) { | ||
| 105 | - JSONObject params = new JSONObject(); | ||
| 106 | - params.set("identifiers", identifiers); | ||
| 107 | - | ||
| 108 | - return IotMqttMessage.builder() | ||
| 109 | - .id(requestId != null ? requestId : generateRequestId()) | ||
| 110 | - .method("thing.service.property.get") | ||
| 111 | - .params(params) | ||
| 112 | - .build(); | ||
| 113 | - } | ||
| 114 | - | ||
| 115 | - /** | ||
| 116 | - * 创建设备配置设置消息 | ||
| 117 | - * | ||
| 118 | - * @param requestId 请求 ID,为空时自动生成 | ||
| 119 | - * @param configs 设备配置 | ||
| 120 | - * @return MQTT 消息对象 | ||
| 121 | - */ | ||
| 122 | - public static IotMqttMessage createConfigSetMessage(String requestId, Map<String, Object> configs) { | ||
| 123 | - return IotMqttMessage.builder() | ||
| 124 | - .id(requestId != null ? requestId : generateRequestId()) | ||
| 125 | - .method("thing.service.config.set") | ||
| 126 | - .params(configs) | ||
| 127 | - .build(); | ||
| 128 | - } | ||
| 129 | - | ||
| 130 | - /** | ||
| 131 | - * 创建设备 OTA 升级消息 | ||
| 132 | - * | ||
| 133 | - * @param requestId 请求 ID,为空时自动生成 | ||
| 134 | - * @param otaInfo OTA 升级信息 | ||
| 135 | - * @return MQTT 消息对象 | ||
| 136 | - */ | ||
| 137 | - public static IotMqttMessage createOtaUpgradeMessage(String requestId, Map<String, Object> otaInfo) { | ||
| 138 | - return IotMqttMessage.builder() | ||
| 139 | - .id(requestId != null ? requestId : generateRequestId()) | ||
| 140 | - .method("thing.service.ota.upgrade") | ||
| 141 | - .params(otaInfo) | ||
| 142 | - .build(); | ||
| 143 | - } | ||
| 144 | - | ||
| 145 | - /** | ||
| 146 | - * 生成请求 ID | ||
| 147 | - * | ||
| 148 | - * @return 请求 ID | ||
| 149 | - */ | ||
| 150 | - public static String generateRequestId() { | ||
| 151 | - return IdUtil.fastSimpleUUID(); | ||
| 152 | - } | ||
| 153 | -} | ||
| 154 | \ No newline at end of file | 0 | \ No newline at end of file |
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/pojo/IotStandardResponse.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.net.component.core.pojo; | ||
| 2 | - | ||
| 3 | -import cn.hutool.core.util.StrUtil; | ||
| 4 | -import lombok.Data; | ||
| 5 | - | ||
| 6 | -/** | ||
| 7 | - * IoT 标准协议响应实体类 | ||
| 8 | - * <p> | ||
| 9 | - * 用于统一 MQTT 和 HTTP 的响应格式 | ||
| 10 | - * | ||
| 11 | - * @author haohao | ||
| 12 | - */ | ||
| 13 | -@Data | ||
| 14 | -public class IotStandardResponse { | ||
| 15 | - | ||
| 16 | - /** | ||
| 17 | - * 消息 ID | ||
| 18 | - */ | ||
| 19 | - private String id; | ||
| 20 | - | ||
| 21 | - /** | ||
| 22 | - * 状态码 | ||
| 23 | - */ | ||
| 24 | - private Integer code; | ||
| 25 | - | ||
| 26 | - /** | ||
| 27 | - * 响应数据 | ||
| 28 | - */ | ||
| 29 | - private Object data; | ||
| 30 | - | ||
| 31 | - /** | ||
| 32 | - * 响应消息 | ||
| 33 | - */ | ||
| 34 | - private String message; | ||
| 35 | - | ||
| 36 | - /** | ||
| 37 | - * 方法名 | ||
| 38 | - */ | ||
| 39 | - private String method; | ||
| 40 | - | ||
| 41 | - /** | ||
| 42 | - * 协议版本 | ||
| 43 | - */ | ||
| 44 | - private String version; | ||
| 45 | - | ||
| 46 | - /** | ||
| 47 | - * 创建成功响应 | ||
| 48 | - * | ||
| 49 | - * @param id 消息 ID | ||
| 50 | - * @param method 方法名 | ||
| 51 | - * @return 成功响应 | ||
| 52 | - */ | ||
| 53 | - public static IotStandardResponse success(String id, String method) { | ||
| 54 | - return success(id, method, null); | ||
| 55 | - } | ||
| 56 | - | ||
| 57 | - /** | ||
| 58 | - * 创建成功响应 | ||
| 59 | - * | ||
| 60 | - * @param id 消息 ID | ||
| 61 | - * @param method 方法名 | ||
| 62 | - * @param data 响应数据 | ||
| 63 | - * @return 成功响应 | ||
| 64 | - */ | ||
| 65 | - public static IotStandardResponse success(String id, String method, Object data) { | ||
| 66 | - return new IotStandardResponse() | ||
| 67 | - .setId(id) | ||
| 68 | - .setCode(200) | ||
| 69 | - .setData(data) | ||
| 70 | - .setMessage("success") | ||
| 71 | - .setMethod(method) | ||
| 72 | - .setVersion("1.0"); | ||
| 73 | - } | ||
| 74 | - | ||
| 75 | - /** | ||
| 76 | - * 创建错误响应 | ||
| 77 | - * | ||
| 78 | - * @param id 消息 ID | ||
| 79 | - * @param method 方法名 | ||
| 80 | - * @param code 错误码 | ||
| 81 | - * @param message 错误消息 | ||
| 82 | - * @return 错误响应 | ||
| 83 | - */ | ||
| 84 | - public static IotStandardResponse error(String id, String method, Integer code, String message) { | ||
| 85 | - return new IotStandardResponse() | ||
| 86 | - .setId(id) | ||
| 87 | - .setCode(code) | ||
| 88 | - .setData(null) | ||
| 89 | - .setMessage(StrUtil.blankToDefault(message, "error")) | ||
| 90 | - .setMethod(method) | ||
| 91 | - .setVersion("1.0"); | ||
| 92 | - } | ||
| 93 | -} | ||
| 94 | \ No newline at end of file | 0 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-emqx/pom.xml deleted
| 1 | -<?xml version="1.0" encoding="UTF-8"?> | ||
| 2 | -<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
| 3 | - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
| 4 | - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
| 5 | - <parent> | ||
| 6 | - <artifactId>yudao-module-iot-net-components</artifactId> | ||
| 7 | - <groupId>cn.iocoder.boot</groupId> | ||
| 8 | - <version>${revision}</version> | ||
| 9 | - </parent> | ||
| 10 | - <modelVersion>4.0.0</modelVersion> | ||
| 11 | - <artifactId>yudao-module-iot-net-component-emqx</artifactId> | ||
| 12 | - <packaging>jar</packaging> | ||
| 13 | - | ||
| 14 | - <name>${project.artifactId}</name> | ||
| 15 | - <description> | ||
| 16 | - 物联网网络组件 EMQX 模块 | ||
| 17 | - </description> | ||
| 18 | - | ||
| 19 | - <dependencies> | ||
| 20 | - <dependency> | ||
| 21 | - <groupId>cn.iocoder.boot</groupId> | ||
| 22 | - <artifactId>yudao-module-iot-net-component-core</artifactId> | ||
| 23 | - <version>${revision}</version> | ||
| 24 | - </dependency> | ||
| 25 | - | ||
| 26 | - <!-- 工具类相关 --> | ||
| 27 | - <dependency> | ||
| 28 | - <groupId>io.vertx</groupId> | ||
| 29 | - <artifactId>vertx-web</artifactId> | ||
| 30 | - </dependency> | ||
| 31 | - <dependency> | ||
| 32 | - <groupId>io.vertx</groupId> | ||
| 33 | - <artifactId>vertx-mqtt</artifactId> | ||
| 34 | - </dependency> | ||
| 35 | - | ||
| 36 | - <!-- 参数校验 --> | ||
| 37 | - <dependency> | ||
| 38 | - <groupId>org.springframework.boot</groupId> | ||
| 39 | - <artifactId>spring-boot-starter-validation</artifactId> | ||
| 40 | - <optional>true</optional> | ||
| 41 | - </dependency> | ||
| 42 | - </dependencies> | ||
| 43 | - | ||
| 44 | -</project> | ||
| 45 | \ No newline at end of file | 0 | \ No newline at end of file |
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/config/IotNetComponentEmqxAutoConfiguration.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.net.component.emqx.config; | ||
| 2 | - | ||
| 3 | -import cn.hutool.core.util.IdUtil; | ||
| 4 | -import cn.hutool.core.util.ObjUtil; | ||
| 5 | -import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi; | ||
| 6 | -import cn.iocoder.yudao.module.iot.net.component.emqx.upstream.IotDeviceUpstreamServer; | ||
| 7 | -import io.vertx.core.Vertx; | ||
| 8 | -import io.vertx.mqtt.MqttClient; | ||
| 9 | -import io.vertx.mqtt.MqttClientOptions; | ||
| 10 | -import lombok.extern.slf4j.Slf4j; | ||
| 11 | -import org.springframework.beans.factory.annotation.Qualifier; | ||
| 12 | -import org.springframework.boot.autoconfigure.AutoConfiguration; | ||
| 13 | -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | ||
| 14 | -import org.springframework.boot.context.event.ApplicationStartedEvent; | ||
| 15 | -import org.springframework.boot.context.properties.EnableConfigurationProperties; | ||
| 16 | -import org.springframework.context.annotation.Bean; | ||
| 17 | -import org.springframework.context.annotation.ComponentScan; | ||
| 18 | -import org.springframework.context.event.EventListener; | ||
| 19 | - | ||
| 20 | -/** | ||
| 21 | - * IoT 网络组件 EMQX 的自动配置类 | ||
| 22 | - * | ||
| 23 | - * @author haohao | ||
| 24 | - */ | ||
| 25 | -@AutoConfiguration | ||
| 26 | -@EnableConfigurationProperties(IotNetComponentEmqxProperties.class) | ||
| 27 | -@ConditionalOnProperty(prefix = "yudao.iot.component.emqx", name = "enabled", havingValue = "true") | ||
| 28 | -@ComponentScan(basePackages = { | ||
| 29 | - "cn.iocoder.yudao.module.iot.net.component.emqx" // 只扫描 EMQX 组件包 | ||
| 30 | -}) // TODO @haohao:自动配置后,不需要这个哈。 | ||
| 31 | -@Slf4j | ||
| 32 | -public class IotNetComponentEmqxAutoConfiguration { | ||
| 33 | - | ||
| 34 | - /** | ||
| 35 | - * 初始化 EMQX 组件 | ||
| 36 | - * | ||
| 37 | - * @param event 应用启动事件 | ||
| 38 | - */ | ||
| 39 | - @EventListener(ApplicationStartedEvent.class) | ||
| 40 | - public void initialize(ApplicationStartedEvent event) { | ||
| 41 | - log.info("[IotNetComponentEmqxAutoConfiguration][开始初始化]"); | ||
| 42 | - | ||
| 43 | - // 从应用上下文中获取需要的 Bean | ||
| 44 | - // TODO @芋艿:看看要不要监听下 | ||
| 45 | - | ||
| 46 | - log.info("[initialize][IoT EMQX 组件初始化完成]"); | ||
| 47 | - } | ||
| 48 | - | ||
| 49 | - /** | ||
| 50 | - * 创建 Vert.x 实例 | ||
| 51 | - */ | ||
| 52 | - @Bean(name = "emqxVertx") | ||
| 53 | - public Vertx vertx() { | ||
| 54 | - return Vertx.vertx(); | ||
| 55 | - } | ||
| 56 | - | ||
| 57 | - /** | ||
| 58 | - * 创建 MQTT 客户端 | ||
| 59 | - */ | ||
| 60 | - @Bean | ||
| 61 | - public MqttClient mqttClient(@Qualifier("emqxVertx") Vertx vertx, IotNetComponentEmqxProperties emqxProperties) { | ||
| 62 | - MqttClientOptions options = new MqttClientOptions() | ||
| 63 | - .setClientId("yudao-iot-downstream-" + IdUtil.fastSimpleUUID()) | ||
| 64 | - .setUsername(emqxProperties.getMqttUsername()) | ||
| 65 | - .setPassword(emqxProperties.getMqttPassword()); | ||
| 66 | - // 设置 SSL 选项 | ||
| 67 | - options.setSsl(ObjUtil.defaultIfNull(emqxProperties.getMqttSsl(), false)); | ||
| 68 | - return MqttClient.create(vertx, options); | ||
| 69 | - } | ||
| 70 | - | ||
| 71 | - /** | ||
| 72 | - * 创建设备上行服务器 | ||
| 73 | - */ | ||
| 74 | - @Bean(name = "emqxDeviceUpstreamServer", initMethod = "start", destroyMethod = "stop") | ||
| 75 | - public IotDeviceUpstreamServer deviceUpstreamServer( | ||
| 76 | - IotDeviceUpstreamApi deviceUpstreamApi, | ||
| 77 | - IotNetComponentEmqxProperties emqxProperties, | ||
| 78 | - @Qualifier("emqxVertx") Vertx vertx, | ||
| 79 | - MqttClient mqttClient) { | ||
| 80 | - return new IotDeviceUpstreamServer(emqxProperties, deviceUpstreamApi, vertx, mqttClient); | ||
| 81 | - } | ||
| 82 | - | ||
| 83 | -} | ||
| 84 | \ No newline at end of file | 0 | \ No newline at end of file |
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/config/IotNetComponentEmqxProperties.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.net.component.emqx.config; | ||
| 2 | - | ||
| 3 | -import jakarta.validation.constraints.NotBlank; | ||
| 4 | -import jakarta.validation.constraints.NotEmpty; | ||
| 5 | -import jakarta.validation.constraints.NotNull; | ||
| 6 | -import lombok.Data; | ||
| 7 | -import org.springframework.boot.context.properties.ConfigurationProperties; | ||
| 8 | -import org.springframework.validation.annotation.Validated; | ||
| 9 | - | ||
| 10 | -/** | ||
| 11 | - * IoT EMQX 网络组件配置属性 | ||
| 12 | - * | ||
| 13 | - * @author haohao | ||
| 14 | - */ | ||
| 15 | -@ConfigurationProperties(prefix = "yudao.iot.component.emqx") | ||
| 16 | -@Data | ||
| 17 | -@Validated | ||
| 18 | -public class IotNetComponentEmqxProperties { | ||
| 19 | - | ||
| 20 | - /** | ||
| 21 | - * 是否启用 EMQX 组件 | ||
| 22 | - */ | ||
| 23 | - private Boolean enabled; | ||
| 24 | - | ||
| 25 | - /** | ||
| 26 | - * MQTT 服务主机 | ||
| 27 | - */ | ||
| 28 | - @NotBlank(message = "MQTT 服务器主机不能为空") | ||
| 29 | - private String mqttHost; | ||
| 30 | - | ||
| 31 | - /** | ||
| 32 | - * MQTT 服务端口 | ||
| 33 | - */ | ||
| 34 | - @NotNull(message = "MQTT 服务器端口不能为空") | ||
| 35 | - private Integer mqttPort; | ||
| 36 | - | ||
| 37 | - /** | ||
| 38 | - * MQTT 服务用户名 | ||
| 39 | - */ | ||
| 40 | - @NotBlank(message = "MQTT 服务器用户名不能为空") | ||
| 41 | - private String mqttUsername; | ||
| 42 | - | ||
| 43 | - /** | ||
| 44 | - * MQTT 服务密码 | ||
| 45 | - */ | ||
| 46 | - @NotBlank(message = "MQTT 服务器密码不能为空") | ||
| 47 | - private String mqttPassword; | ||
| 48 | - | ||
| 49 | - /** | ||
| 50 | - * 是否启用 SSL | ||
| 51 | - */ | ||
| 52 | - @NotNull(message = "MQTT SSL 配置不能为空") | ||
| 53 | - private Boolean mqttSsl; | ||
| 54 | - | ||
| 55 | - /** | ||
| 56 | - * 订阅的主题列表 | ||
| 57 | - */ | ||
| 58 | - @NotEmpty(message = "MQTT 订阅主题不能为空") | ||
| 59 | - private String[] mqttTopics; | ||
| 60 | - | ||
| 61 | - /** | ||
| 62 | - * 认证端口 | ||
| 63 | - */ | ||
| 64 | - @NotNull(message = "认证端口不能为空") | ||
| 65 | - private Integer authPort; | ||
| 66 | - | ||
| 67 | - // TODO @haohao:可以使用 Duration 类型,可读性更好 | ||
| 68 | - /** | ||
| 69 | - * 重连延迟时间(毫秒) | ||
| 70 | - * <p> | ||
| 71 | - * 默认值:5000 毫秒 | ||
| 72 | - */ | ||
| 73 | - private Integer reconnectDelayMs = 5000; | ||
| 74 | - | ||
| 75 | - /** | ||
| 76 | - * 连接超时时间(毫秒) | ||
| 77 | - * <p> | ||
| 78 | - * 默认值:10000 毫秒 | ||
| 79 | - */ | ||
| 80 | - private Integer connectionTimeoutMs = 10000; | ||
| 81 | - | ||
| 82 | -} | ||
| 83 | \ No newline at end of file | 0 | \ No newline at end of file |
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 deleted
| 1 | -package cn.iocoder.yudao.module.iot.net.component.emqx.downstream; | ||
| 2 | - | ||
| 3 | -// TODO @芋艿:后续再支持下;@haohao;改成消费者 | ||
| 4 | -///** | ||
| 5 | -// * EMQX 网络组件的 {@link IotDeviceDownstreamHandler} 实现类 | ||
| 6 | -// * | ||
| 7 | -// * @author 芋道源码 | ||
| 8 | -// */ | ||
| 9 | -//@Slf4j | ||
| 10 | -//public class IotDeviceDownstreamHandlerImpl { | ||
| 11 | -// | ||
| 12 | -// /** | ||
| 13 | -// * MQTT 客户端 | ||
| 14 | -// */ | ||
| 15 | -// private final MqttClient mqttClient; | ||
| 16 | -// | ||
| 17 | -// /** | ||
| 18 | -// * 构造函数 | ||
| 19 | -// * | ||
| 20 | -// * @param mqttClient MQTT 客户端 | ||
| 21 | -// */ | ||
| 22 | -// public IotDeviceDownstreamHandlerImpl(MqttClient mqttClient) { | ||
| 23 | -// this.mqttClient = mqttClient; | ||
| 24 | -// } | ||
| 25 | -// | ||
| 26 | -// @Override | ||
| 27 | -// public CommonResult<Boolean> invokeDeviceService(IotDeviceServiceInvokeReqDTO reqDTO) { | ||
| 28 | -// log.info("[invokeService][开始调用设备服务][reqDTO: {}]", JSONUtil.toJsonStr(reqDTO)); | ||
| 29 | -// | ||
| 30 | -// // 验证参数 | ||
| 31 | -// if (reqDTO.getProductKey() == null || reqDTO.getDeviceName() == null || reqDTO.getIdentifier() == null) { | ||
| 32 | -// log.error("[invokeService][参数不完整][reqDTO: {}]", JSONUtil.toJsonStr(reqDTO)); | ||
| 33 | -// return CommonResult.error(MQTT_TOPIC_ILLEGAL.getCode(), MQTT_TOPIC_ILLEGAL.getMsg()); | ||
| 34 | -// } | ||
| 35 | -// | ||
| 36 | -// try { | ||
| 37 | -// // 构建请求主题 | ||
| 38 | -// String topic = IotDeviceTopicEnum.buildServiceTopic(reqDTO.getProductKey(), reqDTO.getDeviceName(), | ||
| 39 | -// reqDTO.getIdentifier()); | ||
| 40 | -// | ||
| 41 | -// // 构建请求消息 | ||
| 42 | -// String requestId = StrUtil.isNotEmpty(reqDTO.getRequestId()) ? reqDTO.getRequestId() | ||
| 43 | -// : IotNetComponentCommonUtils.generateRequestId(); | ||
| 44 | -// IotMqttMessage message = IotMqttMessage.createServiceInvokeMessage( | ||
| 45 | -// requestId, reqDTO.getIdentifier(), reqDTO.getParams()); | ||
| 46 | -// | ||
| 47 | -// // 发送消息 | ||
| 48 | -// publishMessage(topic, message.toJsonObject()); | ||
| 49 | -// | ||
| 50 | -// log.info("[invokeService][调用设备服务成功][requestId: {}][topic: {}]", requestId, topic); | ||
| 51 | -// return CommonResult.success(true); | ||
| 52 | -// } catch (Exception e) { | ||
| 53 | -// log.error("[invokeService][调用设备服务异常][reqDTO: {}]", JSONUtil.toJsonStr(reqDTO), e); | ||
| 54 | -// return CommonResult.error(MQTT_TOPIC_ILLEGAL.getCode(), MQTT_TOPIC_ILLEGAL.getMsg()); | ||
| 55 | -// } | ||
| 56 | -// } | ||
| 57 | -// | ||
| 58 | -// @Override | ||
| 59 | -// public CommonResult<Boolean> getDeviceProperty(IotDevicePropertyGetReqDTO getReqDTO) { | ||
| 60 | -// // 暂未实现,返回成功 | ||
| 61 | -// return CommonResult.success(true); | ||
| 62 | -// } | ||
| 63 | -// | ||
| 64 | -// @Override | ||
| 65 | -// public CommonResult<Boolean> setDeviceProperty(IotDevicePropertySetReqDTO reqDTO) { | ||
| 66 | -// log.info("[setProperty][开始设置设备属性][reqDTO: {}]", JSONUtil.toJsonStr(reqDTO)); | ||
| 67 | -// | ||
| 68 | -// // 验证参数 | ||
| 69 | -// if (reqDTO.getProductKey() == null || reqDTO.getDeviceName() == null) { | ||
| 70 | -// log.error("[setProperty][参数不完整][reqDTO: {}]", JSONUtil.toJsonStr(reqDTO)); | ||
| 71 | -// return CommonResult.error(MQTT_TOPIC_ILLEGAL.getCode(), MQTT_TOPIC_ILLEGAL.getMsg()); | ||
| 72 | -// } | ||
| 73 | -// | ||
| 74 | -// try { | ||
| 75 | -// // 构建请求主题 | ||
| 76 | -// String topic = IotDeviceTopicEnum.buildPropertySetTopic(reqDTO.getProductKey(), reqDTO.getDeviceName()); | ||
| 77 | -// | ||
| 78 | -// // 构建请求消息 | ||
| 79 | -// String requestId = StrUtil.isNotEmpty(reqDTO.getRequestId()) ? reqDTO.getRequestId() | ||
| 80 | -// : IotNetComponentCommonUtils.generateRequestId(); | ||
| 81 | -// IotMqttMessage message = IotMqttMessage.createPropertySetMessage(requestId, reqDTO.getProperties()); | ||
| 82 | -// | ||
| 83 | -// // 发送消息 | ||
| 84 | -// publishMessage(topic, message.toJsonObject()); | ||
| 85 | -// | ||
| 86 | -// log.info("[setProperty][设置设备属性成功][requestId: {}][topic: {}]", requestId, topic); | ||
| 87 | -// return CommonResult.success(true); | ||
| 88 | -// } catch (Exception e) { | ||
| 89 | -// log.error("[setProperty][设置设备属性异常][reqDTO: {}]", JSONUtil.toJsonStr(reqDTO), e); | ||
| 90 | -// return CommonResult.error(MQTT_TOPIC_ILLEGAL.getCode(), MQTT_TOPIC_ILLEGAL.getMsg()); | ||
| 91 | -// } | ||
| 92 | -// } | ||
| 93 | -// | ||
| 94 | -// @Override | ||
| 95 | -// public CommonResult<Boolean> setDeviceConfig(IotDeviceConfigSetReqDTO setReqDTO) { | ||
| 96 | -// // 暂未实现,返回成功 | ||
| 97 | -// return CommonResult.success(true); | ||
| 98 | -// } | ||
| 99 | -// | ||
| 100 | -// @Override | ||
| 101 | -// public CommonResult<Boolean> upgradeDeviceOta(IotDeviceOtaUpgradeReqDTO upgradeReqDTO) { | ||
| 102 | -// // 暂未实现,返回成功 | ||
| 103 | -// return CommonResult.success(true); | ||
| 104 | -// } | ||
| 105 | -// | ||
| 106 | -// /** | ||
| 107 | -// * 发布 MQTT 消息 | ||
| 108 | -// * | ||
| 109 | -// * @param topic 主题 | ||
| 110 | -// * @param payload 消息内容 | ||
| 111 | -// */ | ||
| 112 | -// private void publishMessage(String topic, JSONObject payload) { | ||
| 113 | -// mqttClient.publish( | ||
| 114 | -// topic, | ||
| 115 | -// Buffer.buffer(payload.toString()), | ||
| 116 | -// MqttQoS.AT_LEAST_ONCE, | ||
| 117 | -// false, | ||
| 118 | -// false); | ||
| 119 | -// log.info("[publishMessage][发送消息成功][topic: {}][payload: {}]", topic, payload); | ||
| 120 | -// } | ||
| 121 | -//} | ||
| 122 | \ No newline at end of file | 0 | \ No newline at end of file |
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/upstream/IotDeviceUpstreamServer.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.net.component.emqx.upstream; | ||
| 2 | - | ||
| 3 | -import cn.hutool.core.util.ArrayUtil; | ||
| 4 | -import cn.hutool.core.util.StrUtil; | ||
| 5 | -import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi; | ||
| 6 | -import cn.iocoder.yudao.module.iot.net.component.emqx.config.IotNetComponentEmqxProperties; | ||
| 7 | -import cn.iocoder.yudao.module.iot.net.component.emqx.upstream.router.IotDeviceAuthVertxHandler; | ||
| 8 | -import cn.iocoder.yudao.module.iot.net.component.emqx.upstream.router.IotDeviceMqttMessageHandler; | ||
| 9 | -import cn.iocoder.yudao.module.iot.net.component.emqx.upstream.router.IotDeviceWebhookVertxHandler; | ||
| 10 | -import io.netty.handler.codec.mqtt.MqttQoS; | ||
| 11 | -import io.vertx.core.Future; | ||
| 12 | -import io.vertx.core.Vertx; | ||
| 13 | -import io.vertx.core.http.HttpServer; | ||
| 14 | -import io.vertx.ext.web.Router; | ||
| 15 | -import io.vertx.ext.web.handler.BodyHandler; | ||
| 16 | -import io.vertx.mqtt.MqttClient; | ||
| 17 | -import lombok.extern.slf4j.Slf4j; | ||
| 18 | - | ||
| 19 | -import java.util.concurrent.CompletableFuture; | ||
| 20 | -import java.util.concurrent.TimeUnit; | ||
| 21 | - | ||
| 22 | -/** | ||
| 23 | - * IoT 设备上行服务端,接收来自 device 设备的请求,转发给 server 服务器 | ||
| 24 | - * <p> | ||
| 25 | - * 协议:HTTP、MQTT | ||
| 26 | - * | ||
| 27 | - * @author haohao | ||
| 28 | - */ | ||
| 29 | -@Slf4j | ||
| 30 | -public class IotDeviceUpstreamServer { | ||
| 31 | - | ||
| 32 | - /** | ||
| 33 | - * 默认 QoS 级别 | ||
| 34 | - */ | ||
| 35 | - private static final MqttQoS DEFAULT_QOS = MqttQoS.AT_LEAST_ONCE; | ||
| 36 | - | ||
| 37 | - private final Vertx vertx; | ||
| 38 | - private final HttpServer server; | ||
| 39 | - private final MqttClient client; | ||
| 40 | - private final IotNetComponentEmqxProperties emqxProperties; | ||
| 41 | - private final IotDeviceMqttMessageHandler mqttMessageHandler; | ||
| 42 | - | ||
| 43 | - /** | ||
| 44 | - * 服务运行状态标志 | ||
| 45 | - */ | ||
| 46 | - private volatile boolean isRunning = false; | ||
| 47 | - | ||
| 48 | - public IotDeviceUpstreamServer(IotNetComponentEmqxProperties emqxProperties, | ||
| 49 | - IotDeviceUpstreamApi deviceUpstreamApi, | ||
| 50 | - Vertx vertx, | ||
| 51 | - MqttClient client) { | ||
| 52 | - this.vertx = vertx; | ||
| 53 | - this.emqxProperties = emqxProperties; | ||
| 54 | - this.client = client; | ||
| 55 | - | ||
| 56 | - // 创建 Router 实例 | ||
| 57 | - Router router = Router.router(vertx); | ||
| 58 | - router.route().handler(BodyHandler.create()); // 处理 Body | ||
| 59 | - router.post(IotDeviceAuthVertxHandler.PATH) | ||
| 60 | - // MQTT 认证不必须通过 HTTP 进行,但 HTTP 认证是 EMQX 等 MQTT 服务器支持的一种灵活的认证方式 | ||
| 61 | - .handler(new IotDeviceAuthVertxHandler(deviceUpstreamApi)); | ||
| 62 | - // 添加 Webhook 处理器,用于处理设备连接和断开连接事件 | ||
| 63 | - router.post(IotDeviceWebhookVertxHandler.PATH) | ||
| 64 | - .handler(new IotDeviceWebhookVertxHandler(deviceUpstreamApi)); | ||
| 65 | - // 创建 HttpServer 实例 | ||
| 66 | - this.server = vertx.createHttpServer().requestHandler(router); | ||
| 67 | - this.mqttMessageHandler = new IotDeviceMqttMessageHandler(deviceUpstreamApi, client); | ||
| 68 | - } | ||
| 69 | - | ||
| 70 | - /** | ||
| 71 | - * 启动 HTTP 服务器、MQTT 客户端 | ||
| 72 | - */ | ||
| 73 | - public void start() { | ||
| 74 | - if (isRunning) { | ||
| 75 | - log.warn("[start][服务已经在运行中,请勿重复启动]"); | ||
| 76 | - return; | ||
| 77 | - } | ||
| 78 | - log.info("[start][开始启动服务]"); | ||
| 79 | - | ||
| 80 | - // 检查 authPort 是否为 null | ||
| 81 | - // TODO @haohao:authPort 里面搞默认值?包括下面,这个类不搞任何默认值,都交给 emqxProperties | ||
| 82 | - Integer authPort = emqxProperties.getAuthPort(); | ||
| 83 | - if (authPort == null) { | ||
| 84 | - log.warn("[start][authPort 为 null,使用默认端口 8080]"); | ||
| 85 | - authPort = 8080; // 默认端口 | ||
| 86 | - } | ||
| 87 | - | ||
| 88 | - // 获取连接超时时间 | ||
| 89 | - int connectionTimeoutMs = emqxProperties.getConnectionTimeoutMs() != null | ||
| 90 | - ? emqxProperties.getConnectionTimeoutMs() | ||
| 91 | - : 10000; | ||
| 92 | - | ||
| 93 | - // 1. 启动 HTTP 服务器 | ||
| 94 | - final Integer finalAuthPort = authPort; // 为 lambda 表达式创建 final 变量 | ||
| 95 | - CompletableFuture<Void> httpFuture = server.listen(finalAuthPort) | ||
| 96 | - .toCompletionStage() | ||
| 97 | - .toCompletableFuture() | ||
| 98 | - .thenAccept(v -> log.info("[start][HTTP 服务器启动完成,端口: {}]", server.actualPort())); | ||
| 99 | - | ||
| 100 | - // 2. 连接 MQTT Broker | ||
| 101 | - CompletableFuture<Void> mqttFuture = connectMqtt() | ||
| 102 | - .toCompletionStage() | ||
| 103 | - .toCompletableFuture() | ||
| 104 | - .thenAccept(v -> { | ||
| 105 | - // 2.1 添加 MQTT 断开重连监听器 | ||
| 106 | - client.closeHandler(closeEvent -> { | ||
| 107 | - log.warn("[closeHandler][MQTT 连接已断开,准备重连]"); | ||
| 108 | - reconnectWithDelay(); | ||
| 109 | - }); | ||
| 110 | - // 2.2 设置 MQTT 消息处理器 | ||
| 111 | - setupMessageHandler(); | ||
| 112 | - }); | ||
| 113 | - | ||
| 114 | - // 3. 等待所有服务启动完成 | ||
| 115 | - CompletableFuture.allOf(httpFuture, mqttFuture) | ||
| 116 | - .orTimeout(connectionTimeoutMs, TimeUnit.MILLISECONDS) | ||
| 117 | - .whenComplete((result, error) -> { | ||
| 118 | - if (error != null) { | ||
| 119 | - log.error("[start][服务启动失败]", error); | ||
| 120 | - } else { | ||
| 121 | - isRunning = true; | ||
| 122 | - log.info("[start][所有服务启动完成]"); | ||
| 123 | - } | ||
| 124 | - }); | ||
| 125 | - } | ||
| 126 | - | ||
| 127 | - /** | ||
| 128 | - * 设置 MQTT 消息处理器 | ||
| 129 | - */ | ||
| 130 | - private void setupMessageHandler() { | ||
| 131 | - client.publishHandler(mqttMessageHandler::handle); | ||
| 132 | - log.debug("[setupMessageHandler][MQTT 消息处理器设置完成]"); | ||
| 133 | - } | ||
| 134 | - | ||
| 135 | - /** | ||
| 136 | - * 重连 MQTT 客户端 | ||
| 137 | - */ | ||
| 138 | - private void reconnectWithDelay() { | ||
| 139 | - if (!isRunning) { | ||
| 140 | - log.info("[reconnectWithDelay][服务已停止,不再尝试重连]"); | ||
| 141 | - return; | ||
| 142 | - } | ||
| 143 | - | ||
| 144 | - // 获取重连延迟时间 | ||
| 145 | - int reconnectDelayMs = emqxProperties.getReconnectDelayMs() != null | ||
| 146 | - ? emqxProperties.getReconnectDelayMs() | ||
| 147 | - : 5000; | ||
| 148 | - | ||
| 149 | - vertx.setTimer(reconnectDelayMs, id -> { | ||
| 150 | - log.info("[reconnectWithDelay][开始重新连接 MQTT]"); | ||
| 151 | - connectMqtt(); | ||
| 152 | - }); | ||
| 153 | - } | ||
| 154 | - | ||
| 155 | - /** | ||
| 156 | - * 连接 MQTT Broker 并订阅主题 | ||
| 157 | - * | ||
| 158 | - * @return 连接结果的 Future | ||
| 159 | - */ | ||
| 160 | - private Future<Void> connectMqtt() { | ||
| 161 | - // 检查必要的 MQTT 配置 | ||
| 162 | - String host = emqxProperties.getMqttHost(); | ||
| 163 | - Integer port = emqxProperties.getMqttPort(); | ||
| 164 | - if (StrUtil.isBlank(host)) { | ||
| 165 | - String msg = "[connectMqtt][MQTT Host 为空,无法连接]"; | ||
| 166 | - log.error(msg); | ||
| 167 | - return Future.failedFuture(new IllegalStateException(msg)); | ||
| 168 | - } | ||
| 169 | - if (port == null) { | ||
| 170 | - log.warn("[connectMqtt][MQTT Port 为 null,使用默认端口 1883]"); | ||
| 171 | - port = 1883; // 默认 MQTT 端口 | ||
| 172 | - } | ||
| 173 | - | ||
| 174 | - final Integer finalPort = port; | ||
| 175 | - return client.connect(finalPort, host) | ||
| 176 | - .compose(connAck -> { | ||
| 177 | - log.info("[connectMqtt][MQTT 客户端连接成功]"); | ||
| 178 | - return subscribeToTopics(); | ||
| 179 | - }) | ||
| 180 | - .recover(error -> { | ||
| 181 | - log.error("[connectMqtt][连接 MQTT Broker 失败:]", error); | ||
| 182 | - reconnectWithDelay(); | ||
| 183 | - return Future.failedFuture(error); | ||
| 184 | - }); | ||
| 185 | - } | ||
| 186 | - | ||
| 187 | - /** | ||
| 188 | - * 订阅设备上行消息主题 | ||
| 189 | - * | ||
| 190 | - * @return 订阅结果的 Future | ||
| 191 | - */ | ||
| 192 | - private Future<Void> subscribeToTopics() { | ||
| 193 | - String[] topics = emqxProperties.getMqttTopics(); | ||
| 194 | - if (ArrayUtil.isEmpty(topics)) { | ||
| 195 | - log.warn("[subscribeToTopics][未配置 MQTT 主题或为 null,使用默认主题]"); | ||
| 196 | - topics = new String[]{"/device/#"}; // 默认订阅所有设备上下行主题 | ||
| 197 | - } | ||
| 198 | - | ||
| 199 | - // 使用协调器追踪多个 Future 的完成状态 | ||
| 200 | - Future<Void> result = Future.succeededFuture(); | ||
| 201 | - for (String topic : topics) { | ||
| 202 | - if (StrUtil.isBlank(topic)) { | ||
| 203 | - log.warn("[subscribeToTopics][跳过空主题]"); | ||
| 204 | - continue; | ||
| 205 | - } | ||
| 206 | - | ||
| 207 | - result = result.compose(v -> client.subscribe(topic, DEFAULT_QOS.value()) | ||
| 208 | - .<Void>map(ack -> { | ||
| 209 | - log.info("[subscribeToTopics][订阅主题成功: {}]", topic); | ||
| 210 | - return null; | ||
| 211 | - }) | ||
| 212 | - .recover(err -> { | ||
| 213 | - log.error("[subscribeToTopics][订阅主题失败: {}]", topic, err); | ||
| 214 | - return Future.failedFuture(err); | ||
| 215 | - })); | ||
| 216 | - } | ||
| 217 | - return result; | ||
| 218 | - } | ||
| 219 | - | ||
| 220 | - /** | ||
| 221 | - * 停止服务 | ||
| 222 | - */ | ||
| 223 | - public void stop() { | ||
| 224 | - if (!isRunning) { | ||
| 225 | - log.warn("[stop][服务已经停止,无需再次停止]"); | ||
| 226 | - return; | ||
| 227 | - } | ||
| 228 | - log.info("[stop][开始停止服务]"); | ||
| 229 | - | ||
| 230 | - // 1. 取消 MQTT 主题订阅 | ||
| 231 | - if (client.isConnected()) { | ||
| 232 | - for (String topic : emqxProperties.getMqttTopics()) { | ||
| 233 | - try { | ||
| 234 | - client.unsubscribe(topic); | ||
| 235 | - } catch (Exception e) { | ||
| 236 | - log.warn("[stop][取消订阅主题异常: {}]", topic, e); | ||
| 237 | - } | ||
| 238 | - } | ||
| 239 | - } | ||
| 240 | - | ||
| 241 | - // 2. 关闭 MQTT 客户端 | ||
| 242 | - try { | ||
| 243 | - if (client.isConnected()) { | ||
| 244 | - client.disconnect(); | ||
| 245 | - } | ||
| 246 | - } catch (Exception e) { | ||
| 247 | - log.warn("[stop][关闭 MQTT 客户端异常]", e); | ||
| 248 | - } | ||
| 249 | - | ||
| 250 | - // 3. 关闭 HTTP 服务器 | ||
| 251 | - try { | ||
| 252 | - server.close(); | ||
| 253 | - } catch (Exception e) { | ||
| 254 | - log.warn("[stop][关闭 HTTP 服务器异常]", e); | ||
| 255 | - } | ||
| 256 | - | ||
| 257 | - // 4. 更新状态 | ||
| 258 | - isRunning = false; | ||
| 259 | - log.info("[stop][服务已停止]"); | ||
| 260 | - } | ||
| 261 | -} | ||
| 262 | \ No newline at end of file | 0 | \ No newline at end of file |
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/upstream/router/IotDeviceAuthVertxHandler.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.net.component.emqx.upstream.router; | ||
| 2 | - | ||
| 3 | -import cn.iocoder.yudao.framework.common.pojo.CommonResult; | ||
| 4 | -import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi; | ||
| 5 | -import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceEmqxAuthReqDTO; | ||
| 6 | -import cn.iocoder.yudao.module.iot.net.component.core.util.IotNetComponentCommonUtils; | ||
| 7 | -import io.vertx.core.Handler; | ||
| 8 | -import io.vertx.core.json.JsonObject; | ||
| 9 | -import io.vertx.ext.web.RoutingContext; | ||
| 10 | -import lombok.RequiredArgsConstructor; | ||
| 11 | -import lombok.extern.slf4j.Slf4j; | ||
| 12 | - | ||
| 13 | -import java.util.Collections; | ||
| 14 | - | ||
| 15 | -/** | ||
| 16 | - * IoT EMQX 连接认证的 Vert.x Handler | ||
| 17 | - * <p> | ||
| 18 | - * 参考:<a href="https://docs.emqx.com/zh/emqx/latest/access-control/authn/http.html">EMQX HTTP</a> | ||
| 19 | - * <p> | ||
| 20 | - * 注意:该处理器需要返回特定格式:{"result": "allow"} 或 {"result": "deny"}, | ||
| 21 | - * 以符合 EMQX 认证插件的要求,因此不使用 IotStandardResponse 实体类 | ||
| 22 | - * | ||
| 23 | - * @author haohao | ||
| 24 | - */ | ||
| 25 | -@RequiredArgsConstructor | ||
| 26 | -@Slf4j | ||
| 27 | -public class IotDeviceAuthVertxHandler implements Handler<RoutingContext> { | ||
| 28 | - | ||
| 29 | - public static final String PATH = "/mqtt/auth"; | ||
| 30 | - | ||
| 31 | - private final IotDeviceUpstreamApi deviceUpstreamApi; | ||
| 32 | - | ||
| 33 | - @Override | ||
| 34 | - public void handle(RoutingContext routingContext) { | ||
| 35 | - try { | ||
| 36 | - // 构建认证请求 DTO | ||
| 37 | - JsonObject json = routingContext.body().asJsonObject(); | ||
| 38 | - String clientId = json.getString("clientid"); | ||
| 39 | - String username = json.getString("username"); | ||
| 40 | - String password = json.getString("password"); | ||
| 41 | - IotDeviceEmqxAuthReqDTO authReqDTO = new IotDeviceEmqxAuthReqDTO() | ||
| 42 | - .setClientId(clientId) | ||
| 43 | - .setUsername(username) | ||
| 44 | - .setPassword(password); | ||
| 45 | - | ||
| 46 | - // 调用认证 API | ||
| 47 | - CommonResult<Boolean> authResult = deviceUpstreamApi.authenticateEmqxConnection(authReqDTO); | ||
| 48 | - if (authResult.getCode() != 0 || !authResult.getData()) { | ||
| 49 | - // 注意:这里必须返回 {"result": "deny"} 格式,以符合 EMQX 认证插件的要求 | ||
| 50 | - IotNetComponentCommonUtils.writeJsonResponse(routingContext, Collections.singletonMap("result", "deny")); | ||
| 51 | - return; | ||
| 52 | - } | ||
| 53 | - | ||
| 54 | - // 响应结果 | ||
| 55 | - // 注意:这里必须返回 {"result": "allow"} 格式,以符合 EMQX 认证插件的要求 | ||
| 56 | - IotNetComponentCommonUtils.writeJsonResponse(routingContext, Collections.singletonMap("result", "allow")); | ||
| 57 | - } catch (Exception e) { | ||
| 58 | - log.error("[handle][EMQX 认证异常]", e); | ||
| 59 | - // 注意:这里必须返回 {"result": "deny"} 格式,以符合 EMQX 认证插件的要求 | ||
| 60 | - IotNetComponentCommonUtils.writeJsonResponse(routingContext, Collections.singletonMap("result", "deny")); | ||
| 61 | - } | ||
| 62 | - } | ||
| 63 | - | ||
| 64 | -} | ||
| 65 | \ No newline at end of file | 0 | \ No newline at end of file |
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/upstream/router/IotDeviceMqttMessageHandler.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.net.component.emqx.upstream.router; | ||
| 2 | - | ||
| 3 | -import cn.hutool.core.util.StrUtil; | ||
| 4 | -import cn.hutool.json.JSONObject; | ||
| 5 | -import cn.hutool.json.JSONUtil; | ||
| 6 | -import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | ||
| 7 | -import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi; | ||
| 8 | -import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceEventReportReqDTO; | ||
| 9 | -import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDevicePropertyReportReqDTO; | ||
| 10 | -import cn.iocoder.yudao.module.iot.net.component.core.constants.IotDeviceTopicEnum; | ||
| 11 | -import cn.iocoder.yudao.module.iot.net.component.core.pojo.IotStandardResponse; | ||
| 12 | -import cn.iocoder.yudao.module.iot.net.component.core.util.IotNetComponentCommonUtils; | ||
| 13 | -import io.netty.handler.codec.mqtt.MqttQoS; | ||
| 14 | -import io.vertx.core.buffer.Buffer; | ||
| 15 | -import io.vertx.mqtt.MqttClient; | ||
| 16 | -import io.vertx.mqtt.messages.MqttPublishMessage; | ||
| 17 | -import lombok.extern.slf4j.Slf4j; | ||
| 18 | - | ||
| 19 | -import java.time.LocalDateTime; | ||
| 20 | -import java.util.Arrays; | ||
| 21 | -import java.util.HashMap; | ||
| 22 | -import java.util.Map; | ||
| 23 | - | ||
| 24 | -/** | ||
| 25 | - * IoT 设备 MQTT 消息处理器 | ||
| 26 | - * <p> | ||
| 27 | - * 参考:<a href= | ||
| 28 | - * "https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services">设备属性、事件、服务</a> | ||
| 29 | - */ | ||
| 30 | -@Slf4j | ||
| 31 | -public class IotDeviceMqttMessageHandler { | ||
| 32 | - | ||
| 33 | - // TODO @haohao:下面的,有办法也抽到 IotDeviceTopicEnum 么?想的是,尽量把这些 method、topic、url 统一化; | ||
| 34 | - private static final String PROPERTY_METHOD = "thing.event.property.post"; | ||
| 35 | - private static final String EVENT_METHOD_PREFIX = "thing.event."; | ||
| 36 | - private static final String EVENT_METHOD_SUFFIX = ".post"; | ||
| 37 | - | ||
| 38 | - private final IotDeviceUpstreamApi deviceUpstreamApi; | ||
| 39 | - private final MqttClient mqttClient; | ||
| 40 | - | ||
| 41 | - public IotDeviceMqttMessageHandler(IotDeviceUpstreamApi deviceUpstreamApi, MqttClient mqttClient) { | ||
| 42 | - this.deviceUpstreamApi = deviceUpstreamApi; | ||
| 43 | - this.mqttClient = mqttClient; | ||
| 44 | - } | ||
| 45 | - | ||
| 46 | - /** | ||
| 47 | - * 处理MQTT消息 | ||
| 48 | - * | ||
| 49 | - * @param message MQTT发布消息 | ||
| 50 | - */ | ||
| 51 | - public void handle(MqttPublishMessage message) { | ||
| 52 | - String topic = message.topicName(); | ||
| 53 | - String payload = message.payload().toString(); | ||
| 54 | - log.info("[messageHandler][接收到消息][topic: {}][payload: {}]", topic, payload); | ||
| 55 | - | ||
| 56 | - try { | ||
| 57 | - if (StrUtil.isEmpty(payload)) { | ||
| 58 | - log.warn("[messageHandler][消息内容为空][topic: {}]", topic); | ||
| 59 | - return; | ||
| 60 | - } | ||
| 61 | - handleMessage(topic, payload); | ||
| 62 | - } catch (Exception e) { | ||
| 63 | - log.error("[messageHandler][处理消息失败][topic: {}][payload: {}]", topic, payload, e); | ||
| 64 | - } | ||
| 65 | - } | ||
| 66 | - | ||
| 67 | - /** | ||
| 68 | - * 根据主题类型处理消息 | ||
| 69 | - * | ||
| 70 | - * @param topic 主题 | ||
| 71 | - * @param payload 消息内容 | ||
| 72 | - */ | ||
| 73 | - private void handleMessage(String topic, String payload) { | ||
| 74 | - // 校验前缀 | ||
| 75 | - if (!topic.startsWith(IotDeviceTopicEnum.SYS_TOPIC_PREFIX.getTopic())) { | ||
| 76 | - log.warn("[handleMessage][未知的消息类型][topic: {}]", topic); | ||
| 77 | - return; | ||
| 78 | - } | ||
| 79 | - | ||
| 80 | - // 处理设备属性上报消息 | ||
| 81 | - if (topic.endsWith(IotDeviceTopicEnum.PROPERTY_POST_TOPIC.getTopic())) { | ||
| 82 | - log.info("[handleMessage][接收到设备属性上报][topic: {}]", topic); | ||
| 83 | - handlePropertyPost(topic, payload); | ||
| 84 | - return; | ||
| 85 | - } | ||
| 86 | - | ||
| 87 | - // 处理设备事件上报消息 | ||
| 88 | - if (topic.contains(IotDeviceTopicEnum.EVENT_POST_TOPIC_PREFIX.getTopic()) && | ||
| 89 | - topic.endsWith(IotDeviceTopicEnum.EVENT_POST_TOPIC_SUFFIX.getTopic())) { | ||
| 90 | - log.info("[handleMessage][接收到设备事件上报][topic: {}]", topic); | ||
| 91 | - handleEventPost(topic, payload); | ||
| 92 | - return; | ||
| 93 | - } | ||
| 94 | - | ||
| 95 | - // 未知消息类型 | ||
| 96 | - log.warn("[handleMessage][未知的消息类型][topic: {}]", topic); | ||
| 97 | - } | ||
| 98 | - | ||
| 99 | - /** | ||
| 100 | - * 处理设备属性上报消息 | ||
| 101 | - * | ||
| 102 | - * @param topic 主题 | ||
| 103 | - * @param payload 消息内容 | ||
| 104 | - */ | ||
| 105 | - private void handlePropertyPost(String topic, String payload) { | ||
| 106 | - try { | ||
| 107 | - // 解析消息内容 | ||
| 108 | - JSONObject jsonObject = JSONUtil.parseObj(payload); | ||
| 109 | - String[] topicParts = parseTopic(topic); | ||
| 110 | - if (topicParts == null) { | ||
| 111 | - return; | ||
| 112 | - } | ||
| 113 | - | ||
| 114 | - // 构建设备属性上报请求对象 | ||
| 115 | - IotDevicePropertyReportReqDTO reportReqDTO = buildPropertyReportDTO(jsonObject, topicParts); | ||
| 116 | - | ||
| 117 | - // 调用上游 API 处理设备上报数据 | ||
| 118 | - deviceUpstreamApi.reportDeviceProperty(reportReqDTO); | ||
| 119 | - log.info("[handlePropertyPost][处理设备属性上报成功][topic: {}]", topic); | ||
| 120 | - | ||
| 121 | - // 发送响应消息 | ||
| 122 | - sendResponse(topic, jsonObject, PROPERTY_METHOD, null); | ||
| 123 | - } catch (Exception e) { | ||
| 124 | - log.error("[handlePropertyPost][处理设备属性上报失败][topic: {}][payload: {}]", topic, payload, e); | ||
| 125 | - } | ||
| 126 | - } | ||
| 127 | - | ||
| 128 | - /** | ||
| 129 | - * 处理设备事件上报消息 | ||
| 130 | - * | ||
| 131 | - * @param topic 主题 | ||
| 132 | - * @param payload 消息内容 | ||
| 133 | - */ | ||
| 134 | - private void handleEventPost(String topic, String payload) { | ||
| 135 | - try { | ||
| 136 | - // 解析消息内容 | ||
| 137 | - JSONObject jsonObject = JSONUtil.parseObj(payload); | ||
| 138 | - String[] topicParts = parseTopic(topic); | ||
| 139 | - if (topicParts == null) { | ||
| 140 | - return; | ||
| 141 | - } | ||
| 142 | - | ||
| 143 | - // 构建设备事件上报请求对象 | ||
| 144 | - IotDeviceEventReportReqDTO reportReqDTO = buildEventReportDTO(jsonObject, topicParts); | ||
| 145 | - | ||
| 146 | - // 调用上游 API 处理设备上报数据 | ||
| 147 | - deviceUpstreamApi.reportDeviceEvent(reportReqDTO); | ||
| 148 | - log.info("[handleEventPost][处理设备事件上报成功][topic: {}]", topic); | ||
| 149 | - | ||
| 150 | - // 从 topic 中获取事件标识符 | ||
| 151 | - String eventIdentifier = getEventIdentifier(topicParts, topic); | ||
| 152 | - if (eventIdentifier == null) { | ||
| 153 | - return; | ||
| 154 | - } | ||
| 155 | - | ||
| 156 | - // 发送响应消息 | ||
| 157 | - String method = EVENT_METHOD_PREFIX + eventIdentifier + EVENT_METHOD_SUFFIX; | ||
| 158 | - sendResponse(topic, jsonObject, method, null); | ||
| 159 | - } catch (Exception e) { | ||
| 160 | - log.error("[handleEventPost][处理设备事件上报失败][topic: {}][payload: {}]", topic, payload, e); | ||
| 161 | - } | ||
| 162 | - } | ||
| 163 | - | ||
| 164 | - /** | ||
| 165 | - * 解析主题,获取主题各部分 | ||
| 166 | - * | ||
| 167 | - * @param topic 主题 | ||
| 168 | - * @return 主题各部分数组,如果解析失败返回null | ||
| 169 | - */ | ||
| 170 | - private String[] parseTopic(String topic) { | ||
| 171 | - String[] topicParts = topic.split("/"); | ||
| 172 | - if (topicParts.length < 7) { | ||
| 173 | - log.warn("[parseTopic][主题格式不正确][topic: {}]", topic); | ||
| 174 | - return null; | ||
| 175 | - } | ||
| 176 | - return topicParts; | ||
| 177 | - } | ||
| 178 | - | ||
| 179 | - /** | ||
| 180 | - * 从主题部分中获取事件标识符 | ||
| 181 | - * | ||
| 182 | - * @param topicParts 主题各部分 | ||
| 183 | - * @param topic 原始主题,用于日志 | ||
| 184 | - * @return 事件标识符,如果获取失败返回null | ||
| 185 | - */ | ||
| 186 | - private String getEventIdentifier(String[] topicParts, String topic) { | ||
| 187 | - try { | ||
| 188 | - return topicParts[6]; | ||
| 189 | - } catch (ArrayIndexOutOfBoundsException e) { | ||
| 190 | - log.warn("[getEventIdentifier][无法从主题中获取事件标识符][topic: {}][topicParts: {}]", | ||
| 191 | - topic, Arrays.toString(topicParts)); | ||
| 192 | - return null; | ||
| 193 | - } | ||
| 194 | - } | ||
| 195 | - | ||
| 196 | - /** | ||
| 197 | - * 发送响应消息 | ||
| 198 | - * | ||
| 199 | - * @param topic 原始主题 | ||
| 200 | - * @param jsonObject 原始消息JSON对象 | ||
| 201 | - * @param method 响应方法 | ||
| 202 | - * @param customData 自定义数据,可为 null | ||
| 203 | - */ | ||
| 204 | - private void sendResponse(String topic, JSONObject jsonObject, String method, Object customData) { | ||
| 205 | - String replyTopic = IotDeviceTopicEnum.getReplyTopic(topic); | ||
| 206 | - | ||
| 207 | - // 响应结果 | ||
| 208 | - IotStandardResponse response = IotStandardResponse.success( | ||
| 209 | - jsonObject.getStr("id"), method, customData); | ||
| 210 | - try { | ||
| 211 | - mqttClient.publish(replyTopic, Buffer.buffer(JsonUtils.toJsonString(response)), | ||
| 212 | - MqttQoS.AT_LEAST_ONCE, false, false); | ||
| 213 | - log.info("[sendResponse][发送响应消息成功][topic: {}]", replyTopic); | ||
| 214 | - } catch (Exception e) { | ||
| 215 | - log.error("[sendResponse][发送响应消息失败][topic: {}][response: {}]", replyTopic, response, e); | ||
| 216 | - } | ||
| 217 | - } | ||
| 218 | - | ||
| 219 | - /** | ||
| 220 | - * 构建设备属性上报请求对象 | ||
| 221 | - * | ||
| 222 | - * @param jsonObject 消息内容 | ||
| 223 | - * @param topicParts 主题部分 | ||
| 224 | - * @return 设备属性上报请求对象 | ||
| 225 | - */ | ||
| 226 | - private IotDevicePropertyReportReqDTO buildPropertyReportDTO(JSONObject jsonObject, String[] topicParts) { | ||
| 227 | - // TODO @haohao:IotDevicePropertyReportReqDTO 可以考虑链式哈。其它也是,尽量让同类参数在一行;这样,阅读起来更聚焦; | ||
| 228 | - IotDevicePropertyReportReqDTO reportReqDTO = new IotDevicePropertyReportReqDTO(); | ||
| 229 | - reportReqDTO.setRequestId(jsonObject.getStr("id")); | ||
| 230 | - reportReqDTO.setProcessId(IotNetComponentCommonUtils.getProcessId()); | ||
| 231 | - reportReqDTO.setReportTime(LocalDateTime.now()); | ||
| 232 | - reportReqDTO.setProductKey(topicParts[2]); | ||
| 233 | - reportReqDTO.setDeviceName(topicParts[3]); | ||
| 234 | - | ||
| 235 | - // 只使用标准 JSON格式处理属性数据 | ||
| 236 | - JSONObject params = jsonObject.getJSONObject("params"); | ||
| 237 | - if (params == null) { | ||
| 238 | - log.warn("[buildPropertyReportDTO][消息格式不正确,缺少params字段][jsonObject: {}]", jsonObject); | ||
| 239 | - params = new JSONObject(); | ||
| 240 | - } | ||
| 241 | - | ||
| 242 | - // 将标准格式的params转换为平台需要的properties格式 | ||
| 243 | - Map<String, Object> properties = new HashMap<>(); | ||
| 244 | - for (Map.Entry<String, Object> entry : params.entrySet()) { | ||
| 245 | - String key = entry.getKey(); | ||
| 246 | - Object valueObj = entry.getValue(); | ||
| 247 | - | ||
| 248 | - // 如果是复杂结构(包含value和time) | ||
| 249 | - if (valueObj instanceof JSONObject valueJson) { | ||
| 250 | - properties.put(key, valueJson.getOrDefault("value", valueObj)); | ||
| 251 | - } else { | ||
| 252 | - properties.put(key, valueObj); | ||
| 253 | - } | ||
| 254 | - } | ||
| 255 | - reportReqDTO.setProperties(properties); | ||
| 256 | - | ||
| 257 | - return reportReqDTO; | ||
| 258 | - } | ||
| 259 | - | ||
| 260 | - /** | ||
| 261 | - * 构建设备事件上报请求对象 | ||
| 262 | - * | ||
| 263 | - * @param jsonObject 消息内容 | ||
| 264 | - * @param topicParts 主题部分 | ||
| 265 | - * @return 设备事件上报请求对象 | ||
| 266 | - */ | ||
| 267 | - private IotDeviceEventReportReqDTO buildEventReportDTO(JSONObject jsonObject, String[] topicParts) { | ||
| 268 | - IotDeviceEventReportReqDTO reportReqDTO = new IotDeviceEventReportReqDTO(); | ||
| 269 | - reportReqDTO.setRequestId(jsonObject.getStr("id")); | ||
| 270 | - reportReqDTO.setProcessId(IotNetComponentCommonUtils.getProcessId()); | ||
| 271 | - reportReqDTO.setReportTime(LocalDateTime.now()); | ||
| 272 | - reportReqDTO.setProductKey(topicParts[2]); | ||
| 273 | - reportReqDTO.setDeviceName(topicParts[3]); | ||
| 274 | - reportReqDTO.setIdentifier(topicParts[6]); | ||
| 275 | - | ||
| 276 | - // 只使用标准JSON格式处理事件参数 | ||
| 277 | - JSONObject params = jsonObject.getJSONObject("params"); | ||
| 278 | - if (params == null) { | ||
| 279 | - log.warn("[buildEventReportDTO][消息格式不正确,缺少params字段][jsonObject: {}]", jsonObject); | ||
| 280 | - params = new JSONObject(); | ||
| 281 | - } | ||
| 282 | - reportReqDTO.setParams(params); | ||
| 283 | - | ||
| 284 | - return reportReqDTO; | ||
| 285 | - } | ||
| 286 | - | ||
| 287 | -} | ||
| 288 | \ No newline at end of file | 0 | \ No newline at end of file |
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/upstream/router/IotDeviceWebhookVertxHandler.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.net.component.emqx.upstream.router; | ||
| 2 | - | ||
| 3 | -import cn.hutool.core.util.StrUtil; | ||
| 4 | -import cn.iocoder.yudao.framework.common.pojo.CommonResult; | ||
| 5 | -import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi; | ||
| 6 | -import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceStateUpdateReqDTO; | ||
| 7 | -import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum; | ||
| 8 | -import cn.iocoder.yudao.module.iot.net.component.core.util.IotNetComponentCommonUtils; | ||
| 9 | -import io.vertx.core.Handler; | ||
| 10 | -import io.vertx.core.json.JsonObject; | ||
| 11 | -import io.vertx.ext.web.RoutingContext; | ||
| 12 | -import lombok.RequiredArgsConstructor; | ||
| 13 | -import lombok.extern.slf4j.Slf4j; | ||
| 14 | - | ||
| 15 | -import java.time.LocalDateTime; | ||
| 16 | -import java.util.Collections; | ||
| 17 | - | ||
| 18 | -/** | ||
| 19 | - * IoT EMQX Webhook 事件处理的 Vert.x Handler | ||
| 20 | - * <p> | ||
| 21 | - * 参考:<a href="https://docs.emqx.com/zh/emqx/latest/data-integration/webhook.html">EMQX Webhook</a> | ||
| 22 | - * <p> | ||
| 23 | - * 注意:该处理器需要返回特定格式:{"result": "success"} 或 {"result": "error"}, | ||
| 24 | - * 以符合 EMQX Webhook 插件的要求,因此不使用 IotStandardResponse 实体类。 | ||
| 25 | - * | ||
| 26 | - * @author haohao | ||
| 27 | - */ | ||
| 28 | -@RequiredArgsConstructor | ||
| 29 | -@Slf4j | ||
| 30 | -public class IotDeviceWebhookVertxHandler implements Handler<RoutingContext> { | ||
| 31 | - | ||
| 32 | - public static final String PATH = "/mqtt/webhook"; | ||
| 33 | - | ||
| 34 | - private final IotDeviceUpstreamApi deviceUpstreamApi; | ||
| 35 | - | ||
| 36 | - @Override | ||
| 37 | - public void handle(RoutingContext routingContext) { | ||
| 38 | - try { | ||
| 39 | - // 解析请求体 | ||
| 40 | - JsonObject json = routingContext.body().asJsonObject(); | ||
| 41 | - String event = json.getString("event"); | ||
| 42 | - String clientId = json.getString("clientid"); | ||
| 43 | - String username = json.getString("username"); | ||
| 44 | - | ||
| 45 | - // 处理不同的事件类型 | ||
| 46 | - switch (event) { | ||
| 47 | - case "client.connected": | ||
| 48 | - handleClientConnected(clientId, username); | ||
| 49 | - break; | ||
| 50 | - case "client.disconnected": | ||
| 51 | - handleClientDisconnected(clientId, username); | ||
| 52 | - break; | ||
| 53 | - default: | ||
| 54 | - log.info("[handle][未处理的 Webhook 事件] event={}, clientId={}, username={}", event, clientId, username); | ||
| 55 | - break; | ||
| 56 | - } | ||
| 57 | - | ||
| 58 | - // 返回成功响应 | ||
| 59 | - // 注意:这里必须返回 {"result": "success"} 格式,以符合 EMQX Webhook 插件的要求 | ||
| 60 | - IotNetComponentCommonUtils.writeJsonResponse(routingContext, Collections.singletonMap("result", "success")); | ||
| 61 | - } catch (Exception e) { | ||
| 62 | - log.error("[handle][处理 Webhook 事件异常]", e); | ||
| 63 | - // 注意:这里必须返回 {"result": "error"} 格式,以符合 EMQX Webhook 插件的要求 | ||
| 64 | - IotNetComponentCommonUtils.writeJsonResponse(routingContext, Collections.singletonMap("result", "error")); | ||
| 65 | - } | ||
| 66 | - } | ||
| 67 | - | ||
| 68 | - /** | ||
| 69 | - * 处理客户端连接事件 | ||
| 70 | - * | ||
| 71 | - * @param clientId 客户端ID | ||
| 72 | - * @param username 用户名 | ||
| 73 | - */ | ||
| 74 | - private void handleClientConnected(String clientId, String username) { | ||
| 75 | - // 解析产品标识和设备名称 | ||
| 76 | - if (StrUtil.isEmpty(username) || "undefined".equals(username)) { | ||
| 77 | - log.warn("[handleClientConnected][客户端连接事件,但用户名为空] clientId={}", clientId); | ||
| 78 | - return; | ||
| 79 | - } | ||
| 80 | - String[] parts = parseUsername(username); | ||
| 81 | - if (parts == null) { | ||
| 82 | - return; | ||
| 83 | - } | ||
| 84 | - | ||
| 85 | - // 更新设备状态为在线 | ||
| 86 | - IotDeviceStateUpdateReqDTO updateReqDTO = new IotDeviceStateUpdateReqDTO(); | ||
| 87 | - updateReqDTO.setProductKey(parts[1]); | ||
| 88 | - updateReqDTO.setDeviceName(parts[0]); | ||
| 89 | - updateReqDTO.setState(IotDeviceStateEnum.ONLINE.getState()); | ||
| 90 | - updateReqDTO.setProcessId(IotNetComponentCommonUtils.getProcessId()); | ||
| 91 | - updateReqDTO.setReportTime(LocalDateTime.now()); | ||
| 92 | - CommonResult<Boolean> result = deviceUpstreamApi.updateDeviceState(updateReqDTO); | ||
| 93 | - if (result.getCode() != 0 || !result.getData()) { | ||
| 94 | - log.error("[handleClientConnected][更新设备状态为在线失败] clientId={}, username={}, code={}, msg={}", | ||
| 95 | - clientId, username, result.getCode(), result.getMsg()); | ||
| 96 | - } else { | ||
| 97 | - log.info("[handleClientConnected][更新设备状态为在线成功] clientId={}, username={}", clientId, username); | ||
| 98 | - } | ||
| 99 | - } | ||
| 100 | - | ||
| 101 | - /** | ||
| 102 | - * 处理客户端断开连接事件 | ||
| 103 | - * | ||
| 104 | - * @param clientId 客户端ID | ||
| 105 | - * @param username 用户名 | ||
| 106 | - */ | ||
| 107 | - private void handleClientDisconnected(String clientId, String username) { | ||
| 108 | - // 解析产品标识和设备名称 | ||
| 109 | - if (StrUtil.isEmpty(username) || "undefined".equals(username)) { | ||
| 110 | - log.warn("[handleClientDisconnected][客户端断开连接事件,但用户名为空] clientId={}", clientId); | ||
| 111 | - return; | ||
| 112 | - } | ||
| 113 | - String[] parts = parseUsername(username); | ||
| 114 | - if (parts == null) { | ||
| 115 | - return; | ||
| 116 | - } | ||
| 117 | - | ||
| 118 | - // 更新设备状态为离线 | ||
| 119 | - IotDeviceStateUpdateReqDTO offlineReqDTO = new IotDeviceStateUpdateReqDTO(); | ||
| 120 | - offlineReqDTO.setProductKey(parts[1]); | ||
| 121 | - offlineReqDTO.setDeviceName(parts[0]); | ||
| 122 | - offlineReqDTO.setState(IotDeviceStateEnum.OFFLINE.getState()); | ||
| 123 | - offlineReqDTO.setProcessId(IotNetComponentCommonUtils.getProcessId()); | ||
| 124 | - offlineReqDTO.setReportTime(LocalDateTime.now()); | ||
| 125 | - CommonResult<Boolean> offlineResult = deviceUpstreamApi.updateDeviceState(offlineReqDTO); | ||
| 126 | - if (offlineResult.getCode() != 0 || !offlineResult.getData()) { | ||
| 127 | - log.error("[handleClientDisconnected][更新设备状态为离线失败] clientId={}, username={}, code={}, msg={}", | ||
| 128 | - clientId, username, offlineResult.getCode(), offlineResult.getMsg()); | ||
| 129 | - } else { | ||
| 130 | - log.info("[handleClientDisconnected][更新设备状态为离线成功] clientId={}, username={}", clientId, username); | ||
| 131 | - } | ||
| 132 | - } | ||
| 133 | - | ||
| 134 | - /** | ||
| 135 | - * 解析用户名,格式为 deviceName&productKey | ||
| 136 | - * | ||
| 137 | - * @param username 用户名 | ||
| 138 | - * @return 解析结果,[0] 为 deviceName,[1] 为 productKey,解析失败返回 null | ||
| 139 | - */ | ||
| 140 | - private String[] parseUsername(String username) { | ||
| 141 | - if (StrUtil.isEmpty(username)) { | ||
| 142 | - return null; | ||
| 143 | - } | ||
| 144 | - String[] parts = username.split("&"); | ||
| 145 | - if (parts.length != 2) { | ||
| 146 | - log.warn("[parseUsername][用户名格式({})不正确,无法解析产品标识和设备名称]", username); | ||
| 147 | - return null; | ||
| 148 | - } | ||
| 149 | - return parts; | ||
| 150 | - } | ||
| 151 | - | ||
| 152 | -} | ||
| 153 | \ No newline at end of file | 0 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-emqx/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports deleted
yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-emqx/src/main/resources/application.yml deleted
| 1 | -# EMQX组件默认配置 | ||
| 2 | -yudao: | ||
| 3 | - iot: | ||
| 4 | - component: | ||
| 5 | - # 核心组件配置 | ||
| 6 | - core: | ||
| 7 | - plugin-key: emqx # 插件的唯一标识 | ||
| 8 | - # EMQX组件配置 | ||
| 9 | -# emqx: | ||
| 10 | -# enabled: true # 启用EMQX组件 | ||
| 11 | -# mqtt-host: 127.0.0.1 # MQTT服务器主机地址 | ||
| 12 | -# mqtt-port: 1883 # MQTT服务器端口 | ||
| 13 | -# mqtt-username: yudao # MQTT服务器用户名 | ||
| 14 | -# mqtt-password: 123456 # MQTT服务器密码 | ||
| 15 | -# mqtt-ssl: false # 是否启用SSL | ||
| 16 | -# mqtt-topics: # 订阅的主题列表 | ||
| 17 | -# - "/sys/#" | ||
| 18 | -# auth-port: 8101 # 认证端口 |
yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-server/pom.xml deleted
| 1 | -<?xml version="1.0" encoding="UTF-8"?> | ||
| 2 | -<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
| 3 | - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
| 4 | - <modelVersion>4.0.0</modelVersion> | ||
| 5 | - <parent> | ||
| 6 | - <groupId>cn.iocoder.boot</groupId> | ||
| 7 | - <artifactId>yudao-module-iot-net-components</artifactId> | ||
| 8 | - <version>${revision}</version> | ||
| 9 | - </parent> | ||
| 10 | - | ||
| 11 | - <artifactId>yudao-module-iot-net-component-server</artifactId> | ||
| 12 | - <packaging>jar</packaging> | ||
| 13 | - | ||
| 14 | - <name>${project.artifactId}</name> | ||
| 15 | - <description> | ||
| 16 | - IoT 网络组件的独立运行服务,用于聚合和启动多个网络组件实例。 | ||
| 17 | - </description> | ||
| 18 | - | ||
| 19 | - <dependencies> | ||
| 20 | - <!-- Web 相关 --> | ||
| 21 | - <dependency> | ||
| 22 | - <groupId>org.springframework.boot</groupId> | ||
| 23 | - <artifactId>spring-boot-starter-web</artifactId> | ||
| 24 | - </dependency> | ||
| 25 | - | ||
| 26 | - <!-- Net Component EMQX --> | ||
| 27 | - <dependency> | ||
| 28 | - <groupId>cn.iocoder.boot</groupId> | ||
| 29 | - <artifactId>yudao-module-iot-net-component-emqx</artifactId> | ||
| 30 | - <version>${revision}</version> | ||
| 31 | - </dependency> | ||
| 32 | - | ||
| 33 | - | ||
| 34 | - | ||
| 35 | - </dependencies> | ||
| 36 | - | ||
| 37 | -</project> | ||
| 38 | \ No newline at end of file | 0 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-server/src/main/java/cn/iocoder/yudao/module/iot/net/component/server/config/IotNetComponentServerConfiguration.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.net.component.server.config; | ||
| 2 | - | ||
| 3 | -import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi; | ||
| 4 | -import cn.iocoder.yudao.module.iot.net.component.server.upstream.IotComponentUpstreamClient; | ||
| 5 | -import org.springframework.boot.context.properties.EnableConfigurationProperties; | ||
| 6 | -import org.springframework.boot.web.client.RestTemplateBuilder; | ||
| 7 | -import org.springframework.context.annotation.Bean; | ||
| 8 | -import org.springframework.context.annotation.Configuration; | ||
| 9 | -import org.springframework.context.annotation.Primary; | ||
| 10 | -import org.springframework.scheduling.annotation.EnableScheduling; | ||
| 11 | -import org.springframework.web.client.RestTemplate; | ||
| 12 | - | ||
| 13 | -/** | ||
| 14 | - * IoT 网络组件服务器配置类 | ||
| 15 | - * | ||
| 16 | - * @author haohao | ||
| 17 | - */ | ||
| 18 | -@Configuration | ||
| 19 | -@EnableConfigurationProperties(IotNetComponentServerProperties.class) | ||
| 20 | -@EnableScheduling | ||
| 21 | -public class IotNetComponentServerConfiguration { | ||
| 22 | - | ||
| 23 | - /** | ||
| 24 | - * 配置 RestTemplate | ||
| 25 | - * | ||
| 26 | - * @param properties 配置 | ||
| 27 | - * @return RestTemplate | ||
| 28 | - */ | ||
| 29 | - @Bean | ||
| 30 | - // TODO @haohao:貌似要独立一个 restTemplate 的名字?不然容易冲突; | ||
| 31 | - public RestTemplate restTemplate(IotNetComponentServerProperties properties) { | ||
| 32 | - return new RestTemplateBuilder() | ||
| 33 | - .connectTimeout(properties.getUpstreamConnectTimeout()) | ||
| 34 | - .readTimeout(properties.getUpstreamReadTimeout()) | ||
| 35 | - .build(); | ||
| 36 | - } | ||
| 37 | - | ||
| 38 | - /** | ||
| 39 | - * 配置设备上行客户端 | ||
| 40 | - * | ||
| 41 | - * @param properties 配置 | ||
| 42 | - * @param restTemplate RestTemplate | ||
| 43 | - * @return 上行客户端 | ||
| 44 | - */ | ||
| 45 | - @Bean | ||
| 46 | - @Primary | ||
| 47 | - public IotDeviceUpstreamApi deviceUpstreamApi(IotNetComponentServerProperties properties, | ||
| 48 | - RestTemplate restTemplate) { | ||
| 49 | - return new IotComponentUpstreamClient(properties, restTemplate); | ||
| 50 | - } | ||
| 51 | - | ||
| 52 | -} | ||
| 53 | \ No newline at end of file | 0 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-server/src/main/java/cn/iocoder/yudao/module/iot/net/component/server/config/IotNetComponentServerProperties.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.net.component.server.config; | ||
| 2 | - | ||
| 3 | -import lombok.Data; | ||
| 4 | -import org.springframework.boot.context.properties.ConfigurationProperties; | ||
| 5 | -import org.springframework.validation.annotation.Validated; | ||
| 6 | - | ||
| 7 | -import java.time.Duration; | ||
| 8 | - | ||
| 9 | -/** | ||
| 10 | - * IoT 网络组件服务配置属性 | ||
| 11 | - * | ||
| 12 | - * @author haohao | ||
| 13 | - */ | ||
| 14 | -@ConfigurationProperties(prefix = "yudao.iot.component.server") | ||
| 15 | -@Validated | ||
| 16 | -@Data | ||
| 17 | -public class IotNetComponentServerProperties { | ||
| 18 | - | ||
| 19 | - /** | ||
| 20 | - * 上行 URL,用于向主应用程序上报数据 | ||
| 21 | - */ | ||
| 22 | - private String upstreamUrl = "http://127.0.0.1:48080"; | ||
| 23 | - | ||
| 24 | - /** | ||
| 25 | - * 上行连接超时时间 | ||
| 26 | - */ | ||
| 27 | - private Duration upstreamConnectTimeout = Duration.ofSeconds(30); | ||
| 28 | - | ||
| 29 | - /** | ||
| 30 | - * 上行读取超时时间 | ||
| 31 | - */ | ||
| 32 | - private Duration upstreamReadTimeout = Duration.ofSeconds(30); | ||
| 33 | - | ||
| 34 | -} | ||
| 35 | \ No newline at end of file | 0 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-protocol/README.md deleted
| 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 | -- 后续版本将支持更多协议类型和高级功能 | ||
| 255 | \ No newline at end of file | 0 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-protocol/pom.xml deleted
| 1 | -<?xml version="1.0" encoding="UTF-8"?> | ||
| 2 | -<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
| 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"> | ||
| 5 | - <parent> | ||
| 6 | - <artifactId>yudao-module-iot</artifactId> | ||
| 7 | - <groupId>cn.iocoder.boot</groupId> | ||
| 8 | - <version>${revision}</version> | ||
| 9 | - </parent> | ||
| 10 | - <modelVersion>4.0.0</modelVersion> | ||
| 11 | - | ||
| 12 | - <artifactId>yudao-module-iot-protocol</artifactId> | ||
| 13 | - <packaging>jar</packaging> | ||
| 14 | - | ||
| 15 | - <name>${project.artifactId}</name> | ||
| 16 | - <description> | ||
| 17 | - 物联网协议模块,提供 topic 解析、协议转换等功能 | ||
| 18 | - 作为 iot-biz 和 iot-gateway 的共享包 | ||
| 19 | - </description> | ||
| 20 | - | ||
| 21 | - <dependencies> | ||
| 22 | - <!-- 项目内部依赖 --> | ||
| 23 | - <dependency> | ||
| 24 | - <groupId>cn.iocoder.boot</groupId> | ||
| 25 | - <artifactId>yudao-common</artifactId> | ||
| 26 | - </dependency> | ||
| 27 | - | ||
| 28 | - <!-- Web 相关 --> | ||
| 29 | - <dependency> | ||
| 30 | - <groupId>cn.iocoder.boot</groupId> | ||
| 31 | - <artifactId>yudao-spring-boot-starter-web</artifactId> | ||
| 32 | - <scope>provided</scope> <!-- 设为 provided,只有 biz 需要使用 --> | ||
| 33 | - </dependency> | ||
| 34 | - | ||
| 35 | - <!-- 工具类相关 --> | ||
| 36 | - <dependency> | ||
| 37 | - <groupId>org.projectlombok</groupId> | ||
| 38 | - <artifactId>lombok</artifactId> | ||
| 39 | - </dependency> | ||
| 40 | - | ||
| 41 | - <dependency> | ||
| 42 | - <groupId>cn.hutool</groupId> | ||
| 43 | - <artifactId>hutool-all</artifactId> | ||
| 44 | - </dependency> | ||
| 45 | - | ||
| 46 | - <!-- IoT 相关依赖 --> | ||
| 47 | - <dependency> | ||
| 48 | - <groupId>io.vertx</groupId> | ||
| 49 | - <artifactId>vertx-core</artifactId> | ||
| 50 | - <scope>provided</scope> | ||
| 51 | - </dependency> | ||
| 52 | - <dependency> | ||
| 53 | - <groupId>io.vertx</groupId> | ||
| 54 | - <artifactId>vertx-web</artifactId> | ||
| 55 | - <scope>provided</scope> | ||
| 56 | - </dependency> | ||
| 57 | - | ||
| 58 | - <!-- 测试相关 --> | ||
| 59 | - <dependency> | ||
| 60 | - <groupId>org.junit.jupiter</groupId> | ||
| 61 | - <artifactId>junit-jupiter</artifactId> | ||
| 62 | - <scope>test</scope> | ||
| 63 | - </dependency> | ||
| 64 | - <dependency> | ||
| 65 | - <groupId>org.springframework.boot</groupId> | ||
| 66 | - <artifactId>spring-boot-starter-test</artifactId> | ||
| 67 | - <scope>test</scope> | ||
| 68 | - </dependency> | ||
| 69 | - </dependencies> | ||
| 70 | - | ||
| 71 | -</project> | ||
| 72 | \ No newline at end of file | 0 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-protocol/src/main/java/cn/iocoder/yudao/module/iot/protocol/config/IotProtocolAutoConfiguration.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.protocol.config; | ||
| 2 | - | ||
| 3 | -import cn.iocoder.yudao.module.iot.protocol.convert.IotProtocolConverter; | ||
| 4 | -import cn.iocoder.yudao.module.iot.protocol.convert.impl.DefaultIotProtocolConverter; | ||
| 5 | -import cn.iocoder.yudao.module.iot.protocol.enums.IotProtocolTypeEnum; | ||
| 6 | -import cn.iocoder.yudao.module.iot.protocol.message.IotMessageParser; | ||
| 7 | -import cn.iocoder.yudao.module.iot.protocol.message.impl.IotHttpMessageParser; | ||
| 8 | -import cn.iocoder.yudao.module.iot.protocol.message.impl.IotMqttMessageParser; | ||
| 9 | -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; | ||
| 10 | -import org.springframework.context.annotation.Bean; | ||
| 11 | -import org.springframework.context.annotation.Configuration; | ||
| 12 | - | ||
| 13 | -/** | ||
| 14 | - * IoT 协议模块自动配置类 | ||
| 15 | - * | ||
| 16 | - * @author haohao | ||
| 17 | - */ | ||
| 18 | -@Configuration(proxyBeanMethods = false) | ||
| 19 | -public class IotProtocolAutoConfiguration { | ||
| 20 | - | ||
| 21 | - /** | ||
| 22 | - * Bean 名称常量 | ||
| 23 | - */ | ||
| 24 | - public static final String IOT_MQTT_MESSAGE_PARSER_BEAN_NAME = "iotMqttMessageParser"; | ||
| 25 | - public static final String IOT_HTTP_MESSAGE_PARSER_BEAN_NAME = "iotHttpMessageParser"; | ||
| 26 | - | ||
| 27 | - /** | ||
| 28 | - * 注册 MQTT 协议消息解析器 | ||
| 29 | - * | ||
| 30 | - * @return MQTT 协议消息解析器 | ||
| 31 | - */ | ||
| 32 | - @Bean | ||
| 33 | - @ConditionalOnMissingBean(name = IOT_MQTT_MESSAGE_PARSER_BEAN_NAME) | ||
| 34 | - public IotMessageParser iotMqttMessageParser() { | ||
| 35 | - return new IotMqttMessageParser(); | ||
| 36 | - } | ||
| 37 | - | ||
| 38 | - | ||
| 39 | - /** | ||
| 40 | - * 注册 HTTP 协议消息解析器 | ||
| 41 | - * | ||
| 42 | - * @return HTTP 协议消息解析器 | ||
| 43 | - */ | ||
| 44 | - @Bean | ||
| 45 | - @ConditionalOnMissingBean(name = IOT_HTTP_MESSAGE_PARSER_BEAN_NAME) | ||
| 46 | - public IotMessageParser iotHttpMessageParser() { | ||
| 47 | - return new IotHttpMessageParser(); | ||
| 48 | - } | ||
| 49 | - | ||
| 50 | - /** | ||
| 51 | - * 注册默认协议转换器 | ||
| 52 | - * <p> | ||
| 53 | - * 如果用户没有自定义协议转换器,则使用默认实现 | ||
| 54 | - * 默认会注册 MQTT 和 HTTP 协议解析器 | ||
| 55 | - * | ||
| 56 | - * @param iotMqttMessageParser MQTT 协议解析器 | ||
| 57 | - * @param iotHttpMessageParser HTTP 协议解析器 | ||
| 58 | - * @return 默认协议转换器 | ||
| 59 | - */ | ||
| 60 | - @Bean | ||
| 61 | - @ConditionalOnMissingBean | ||
| 62 | - public IotProtocolConverter iotProtocolConverter(IotMessageParser iotMqttMessageParser, | ||
| 63 | - IotMessageParser iotHttpMessageParser) { | ||
| 64 | - DefaultIotProtocolConverter converter = new DefaultIotProtocolConverter(); | ||
| 65 | - | ||
| 66 | - // 注册 MQTT 协议解析器(默认实现) | ||
| 67 | - converter.registerParser(IotProtocolTypeEnum.MQTT.getCode(), iotMqttMessageParser); | ||
| 68 | - | ||
| 69 | - // 注册 HTTP 协议解析器 | ||
| 70 | - converter.registerParser(IotProtocolTypeEnum.HTTP.getCode(), iotHttpMessageParser); | ||
| 71 | - | ||
| 72 | - return converter; | ||
| 73 | - } | ||
| 74 | -} | ||
| 75 | \ No newline at end of file | 0 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-protocol/src/main/java/cn/iocoder/yudao/module/iot/protocol/constants/IotHttpConstants.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.protocol.constants; | ||
| 2 | - | ||
| 3 | -/** | ||
| 4 | - * IoT HTTP 协议常量类 | ||
| 5 | - * <p> | ||
| 6 | - * 用于统一管理 HTTP 协议中的常量,包括路径、字段名、默认值等 | ||
| 7 | - * | ||
| 8 | - * @author haohao | ||
| 9 | - */ | ||
| 10 | -public class IotHttpConstants { | ||
| 11 | - | ||
| 12 | - /** | ||
| 13 | - * 路径常量 | ||
| 14 | - */ | ||
| 15 | - public static class Path { | ||
| 16 | - /** | ||
| 17 | - * 认证路径 | ||
| 18 | - */ | ||
| 19 | - public static final String AUTH = "/auth"; | ||
| 20 | - | ||
| 21 | - /** | ||
| 22 | - * 主题路径前缀 | ||
| 23 | - */ | ||
| 24 | - public static final String TOPIC_PREFIX = "/topic"; | ||
| 25 | - } | ||
| 26 | - | ||
| 27 | - /** | ||
| 28 | - * 认证字段常量 | ||
| 29 | - */ | ||
| 30 | - public static class AuthField { | ||
| 31 | - /** | ||
| 32 | - * 产品Key | ||
| 33 | - */ | ||
| 34 | - public static final String PRODUCT_KEY = "productKey"; | ||
| 35 | - | ||
| 36 | - /** | ||
| 37 | - * 设备名称 | ||
| 38 | - */ | ||
| 39 | - public static final String DEVICE_NAME = "deviceName"; | ||
| 40 | - | ||
| 41 | - /** | ||
| 42 | - * 客户端ID | ||
| 43 | - */ | ||
| 44 | - public static final String CLIENT_ID = "clientId"; | ||
| 45 | - | ||
| 46 | - /** | ||
| 47 | - * 时间戳 | ||
| 48 | - */ | ||
| 49 | - public static final String TIMESTAMP = "timestamp"; | ||
| 50 | - | ||
| 51 | - /** | ||
| 52 | - * 签名 | ||
| 53 | - */ | ||
| 54 | - public static final String SIGN = "sign"; | ||
| 55 | - | ||
| 56 | - /** | ||
| 57 | - * 签名方法 | ||
| 58 | - */ | ||
| 59 | - public static final String SIGN_METHOD = "signmethod"; | ||
| 60 | - | ||
| 61 | - /** | ||
| 62 | - * 版本 | ||
| 63 | - */ | ||
| 64 | - public static final String VERSION = "version"; | ||
| 65 | - } | ||
| 66 | - | ||
| 67 | - /** | ||
| 68 | - * 消息字段常量 | ||
| 69 | - */ | ||
| 70 | - public static class MessageField { | ||
| 71 | - /** | ||
| 72 | - * 消息ID | ||
| 73 | - */ | ||
| 74 | - public static final String ID = "id"; | ||
| 75 | - | ||
| 76 | - /** | ||
| 77 | - * 方法名 | ||
| 78 | - */ | ||
| 79 | - public static final String METHOD = "method"; | ||
| 80 | - | ||
| 81 | - /** | ||
| 82 | - * 版本 | ||
| 83 | - */ | ||
| 84 | - public static final String VERSION = "version"; | ||
| 85 | - | ||
| 86 | - /** | ||
| 87 | - * 参数 | ||
| 88 | - */ | ||
| 89 | - public static final String PARAMS = "params"; | ||
| 90 | - | ||
| 91 | - /** | ||
| 92 | - * 数据 | ||
| 93 | - */ | ||
| 94 | - public static final String DATA = "data"; | ||
| 95 | - } | ||
| 96 | - | ||
| 97 | - /** | ||
| 98 | - * 响应字段常量 | ||
| 99 | - */ | ||
| 100 | - public static class ResponseField { | ||
| 101 | - /** | ||
| 102 | - * 状态码 | ||
| 103 | - */ | ||
| 104 | - public static final String CODE = "code"; | ||
| 105 | - | ||
| 106 | - /** | ||
| 107 | - * 消息 | ||
| 108 | - */ | ||
| 109 | - public static final String MESSAGE = "message"; | ||
| 110 | - | ||
| 111 | - /** | ||
| 112 | - * 信息 | ||
| 113 | - */ | ||
| 114 | - public static final String INFO = "info"; | ||
| 115 | - | ||
| 116 | - /** | ||
| 117 | - * 令牌 | ||
| 118 | - */ | ||
| 119 | - public static final String TOKEN = "token"; | ||
| 120 | - | ||
| 121 | - /** | ||
| 122 | - * 消息ID | ||
| 123 | - */ | ||
| 124 | - public static final String MESSAGE_ID = "messageId"; | ||
| 125 | - } | ||
| 126 | - | ||
| 127 | - /** | ||
| 128 | - * 默认值常量 | ||
| 129 | - */ | ||
| 130 | - public static class DefaultValue { | ||
| 131 | - /** | ||
| 132 | - * 默认签名方法 | ||
| 133 | - */ | ||
| 134 | - public static final String SIGN_METHOD = "hmacmd5"; | ||
| 135 | - | ||
| 136 | - /** | ||
| 137 | - * 默认版本 | ||
| 138 | - */ | ||
| 139 | - public static final String VERSION = "default"; | ||
| 140 | - | ||
| 141 | - /** | ||
| 142 | - * 默认消息版本 | ||
| 143 | - */ | ||
| 144 | - public static final String MESSAGE_VERSION = "1.0"; | ||
| 145 | - | ||
| 146 | - /** | ||
| 147 | - * 未知方法名 | ||
| 148 | - */ | ||
| 149 | - public static final String UNKNOWN_METHOD = "unknown"; | ||
| 150 | - } | ||
| 151 | - | ||
| 152 | - /** | ||
| 153 | - * 方法名常量 | ||
| 154 | - */ | ||
| 155 | - public static class Method { | ||
| 156 | - /** | ||
| 157 | - * 设备认证 | ||
| 158 | - */ | ||
| 159 | - public static final String DEVICE_AUTH = "device.auth"; | ||
| 160 | - | ||
| 161 | - /** | ||
| 162 | - * 自定义消息 | ||
| 163 | - */ | ||
| 164 | - public static final String CUSTOM_MESSAGE = "custom.message"; | ||
| 165 | - } | ||
| 166 | -} | ||
| 167 | \ No newline at end of file | 0 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-protocol/src/main/java/cn/iocoder/yudao/module/iot/protocol/constants/IotLogConstants.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.protocol.constants; | ||
| 2 | - | ||
| 3 | -/** | ||
| 4 | - * IoT 协议日志消息常量类 | ||
| 5 | - * <p> | ||
| 6 | - * 用于统一管理协议模块中的日志消息常量 | ||
| 7 | - * | ||
| 8 | - * @author haohao | ||
| 9 | - */ | ||
| 10 | -public class IotLogConstants { | ||
| 11 | - | ||
| 12 | - /** | ||
| 13 | - * HTTP 协议日志消息 | ||
| 14 | - */ | ||
| 15 | - public static class Http { | ||
| 16 | - /** | ||
| 17 | - * 收到空消息内容 | ||
| 18 | - */ | ||
| 19 | - public static final String RECEIVED_EMPTY_MESSAGE = "[HTTP] 收到空消息内容, topic={}"; | ||
| 20 | - | ||
| 21 | - /** | ||
| 22 | - * 不支持的路径格式 | ||
| 23 | - */ | ||
| 24 | - public static final String UNSUPPORTED_PATH_FORMAT = "[HTTP] 不支持的路径格式, topic={}"; | ||
| 25 | - | ||
| 26 | - /** | ||
| 27 | - * 解析消息失败 | ||
| 28 | - */ | ||
| 29 | - public static final String PARSE_MESSAGE_FAILED = "[HTTP] 解析消息失败, topic={}"; | ||
| 30 | - | ||
| 31 | - /** | ||
| 32 | - * 认证消息非JSON格式 | ||
| 33 | - */ | ||
| 34 | - public static final String AUTH_MESSAGE_NOT_JSON = "[HTTP] 认证消息非JSON格式, message={}"; | ||
| 35 | - | ||
| 36 | - /** | ||
| 37 | - * 认证消息缺少必需字段 | ||
| 38 | - */ | ||
| 39 | - public static final String AUTH_MESSAGE_MISSING_REQUIRED_FIELDS = "[HTTP] 认证消息缺少必需字段, message={}"; | ||
| 40 | - | ||
| 41 | - /** | ||
| 42 | - * 格式化响应失败 | ||
| 43 | - */ | ||
| 44 | - public static final String FORMAT_RESPONSE_FAILED = "[HTTP] 格式化响应失败"; | ||
| 45 | - } | ||
| 46 | - | ||
| 47 | - /** | ||
| 48 | - * 协议转换器日志消息 | ||
| 49 | - */ | ||
| 50 | - public static class Converter { | ||
| 51 | - /** | ||
| 52 | - * 注册协议解析器 | ||
| 53 | - */ | ||
| 54 | - public static final String REGISTER_PARSER = "[协议转换器] 注册协议解析器: protocol={}, parser={}"; | ||
| 55 | - | ||
| 56 | - /** | ||
| 57 | - * 移除协议解析器 | ||
| 58 | - */ | ||
| 59 | - public static final String REMOVE_PARSER = "[协议转换器] 移除协议解析器: protocol={}"; | ||
| 60 | - | ||
| 61 | - /** | ||
| 62 | - * 不支持的协议类型 | ||
| 63 | - */ | ||
| 64 | - public static final String UNSUPPORTED_PROTOCOL = "[协议转换器] 不支持的协议类型: protocol={}"; | ||
| 65 | - | ||
| 66 | - /** | ||
| 67 | - * 转换消息失败 | ||
| 68 | - */ | ||
| 69 | - public static final String CONVERT_MESSAGE_FAILED = "[协议转换器] 转换消息失败: protocol={}, topic={}"; | ||
| 70 | - | ||
| 71 | - /** | ||
| 72 | - * 格式化响应失败 | ||
| 73 | - */ | ||
| 74 | - public static final String FORMAT_RESPONSE_FAILED = "[协议转换器] 格式化响应失败: protocol={}"; | ||
| 75 | - | ||
| 76 | - /** | ||
| 77 | - * 自动选择协议 | ||
| 78 | - */ | ||
| 79 | - public static final String AUTO_SELECT_PROTOCOL = "[协议转换器] 自动选择协议: protocol={}, topic={}"; | ||
| 80 | - | ||
| 81 | - /** | ||
| 82 | - * 协议解析失败,尝试下一个 | ||
| 83 | - */ | ||
| 84 | - public static final String PROTOCOL_PARSE_FAILED_TRY_NEXT = "[协议转换器] 协议解析失败,尝试下一个: protocol={}, topic={}"; | ||
| 85 | - | ||
| 86 | - /** | ||
| 87 | - * 无法自动识别协议 | ||
| 88 | - */ | ||
| 89 | - public static final String CANNOT_AUTO_RECOGNIZE_PROTOCOL = "[协议转换器] 无法自动识别协议: topic={}"; | ||
| 90 | - } | ||
| 91 | -} | ||
| 92 | \ No newline at end of file | 0 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-protocol/src/main/java/cn/iocoder/yudao/module/iot/protocol/constants/IotTopicConstants.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.protocol.constants; | ||
| 2 | - | ||
| 3 | -/** | ||
| 4 | - * IoT 设备主题常量类 | ||
| 5 | - * <p> | ||
| 6 | - * 用于统一管理 MQTT 协议中的主题常量,基于 Alink 协议规范 | ||
| 7 | - * | ||
| 8 | - * @author haohao | ||
| 9 | - */ | ||
| 10 | -public class IotTopicConstants { | ||
| 11 | - | ||
| 12 | - /** | ||
| 13 | - * 系统主题前缀 | ||
| 14 | - */ | ||
| 15 | - public static final String SYS_TOPIC_PREFIX = "/sys/"; | ||
| 16 | - | ||
| 17 | - /** | ||
| 18 | - * 服务调用主题前缀 | ||
| 19 | - */ | ||
| 20 | - public static final String SERVICE_TOPIC_PREFIX = "/thing/service/"; | ||
| 21 | - | ||
| 22 | - /** | ||
| 23 | - * 设备属性设置主题 | ||
| 24 | - * 请求Topic:/sys/${productKey}/${deviceName}/thing/service/property/set | ||
| 25 | - * 响应Topic:/sys/${productKey}/${deviceName}/thing/service/property/set_reply | ||
| 26 | - */ | ||
| 27 | - public static final String PROPERTY_SET_TOPIC = "/thing/service/property/set"; | ||
| 28 | - | ||
| 29 | - /** | ||
| 30 | - * 设备属性获取主题 | ||
| 31 | - * 请求Topic:/sys/${productKey}/${deviceName}/thing/service/property/get | ||
| 32 | - * 响应Topic:/sys/${productKey}/${deviceName}/thing/service/property/get_reply | ||
| 33 | - */ | ||
| 34 | - public static final String PROPERTY_GET_TOPIC = "/thing/service/property/get"; | ||
| 35 | - | ||
| 36 | - /** | ||
| 37 | - * 设备配置设置主题 | ||
| 38 | - * 请求Topic:/sys/${productKey}/${deviceName}/thing/service/config/set | ||
| 39 | - * 响应Topic:/sys/${productKey}/${deviceName}/thing/service/config/set_reply | ||
| 40 | - */ | ||
| 41 | - public static final String CONFIG_SET_TOPIC = "/thing/service/config/set"; | ||
| 42 | - | ||
| 43 | - /** | ||
| 44 | - * 设备OTA升级主题 | ||
| 45 | - * 请求Topic:/sys/${productKey}/${deviceName}/thing/service/ota/upgrade | ||
| 46 | - * 响应Topic:/sys/${productKey}/${deviceName}/thing/service/ota/upgrade_reply | ||
| 47 | - */ | ||
| 48 | - public static final String OTA_UPGRADE_TOPIC = "/thing/service/ota/upgrade"; | ||
| 49 | - | ||
| 50 | - /** | ||
| 51 | - * 设备属性上报主题 | ||
| 52 | - * 请求Topic:/sys/${productKey}/${deviceName}/thing/event/property/post | ||
| 53 | - * 响应Topic:/sys/${productKey}/${deviceName}/thing/event/property/post_reply | ||
| 54 | - */ | ||
| 55 | - public static final String PROPERTY_POST_TOPIC = "/thing/event/property/post"; | ||
| 56 | - | ||
| 57 | - /** | ||
| 58 | - * 设备事件上报主题前缀 | ||
| 59 | - */ | ||
| 60 | - public static final String EVENT_POST_TOPIC_PREFIX = "/thing/event/"; | ||
| 61 | - | ||
| 62 | - /** | ||
| 63 | - * 设备事件上报主题后缀 | ||
| 64 | - */ | ||
| 65 | - public static final String EVENT_POST_TOPIC_SUFFIX = "/post"; | ||
| 66 | - | ||
| 67 | - /** | ||
| 68 | - * 响应主题后缀 | ||
| 69 | - */ | ||
| 70 | - public static final String REPLY_SUFFIX = "_reply"; | ||
| 71 | - | ||
| 72 | - /** | ||
| 73 | - * 方法名前缀常量 | ||
| 74 | - */ | ||
| 75 | - public static class MethodPrefix { | ||
| 76 | - /** | ||
| 77 | - * 物模型服务前缀 | ||
| 78 | - */ | ||
| 79 | - public static final String THING_SERVICE = "thing.service."; | ||
| 80 | - | ||
| 81 | - /** | ||
| 82 | - * 物模型事件前缀 | ||
| 83 | - */ | ||
| 84 | - public static final String THING_EVENT = "thing.event."; | ||
| 85 | - } | ||
| 86 | - | ||
| 87 | - /** | ||
| 88 | - * 完整方法名常量 | ||
| 89 | - */ | ||
| 90 | - public static class Method { | ||
| 91 | - /** | ||
| 92 | - * 属性设置方法 | ||
| 93 | - */ | ||
| 94 | - public static final String PROPERTY_SET = "thing.service.property.set"; | ||
| 95 | - | ||
| 96 | - /** | ||
| 97 | - * 属性获取方法 | ||
| 98 | - */ | ||
| 99 | - public static final String PROPERTY_GET = "thing.service.property.get"; | ||
| 100 | - | ||
| 101 | - /** | ||
| 102 | - * 属性上报方法 | ||
| 103 | - */ | ||
| 104 | - public static final String PROPERTY_POST = "thing.event.property.post"; | ||
| 105 | - | ||
| 106 | - /** | ||
| 107 | - * 配置设置方法 | ||
| 108 | - */ | ||
| 109 | - public static final String CONFIG_SET = "thing.service.config.set"; | ||
| 110 | - | ||
| 111 | - /** | ||
| 112 | - * OTA升级方法 | ||
| 113 | - */ | ||
| 114 | - public static final String OTA_UPGRADE = "thing.service.ota.upgrade"; | ||
| 115 | - | ||
| 116 | - /** | ||
| 117 | - * 设备上线方法 | ||
| 118 | - */ | ||
| 119 | - public static final String DEVICE_ONLINE = "device.online"; | ||
| 120 | - | ||
| 121 | - /** | ||
| 122 | - * 设备下线方法 | ||
| 123 | - */ | ||
| 124 | - public static final String DEVICE_OFFLINE = "device.offline"; | ||
| 125 | - | ||
| 126 | - /** | ||
| 127 | - * 心跳方法 | ||
| 128 | - */ | ||
| 129 | - public static final String HEARTBEAT = "heartbeat"; | ||
| 130 | - } | ||
| 131 | - | ||
| 132 | - /** | ||
| 133 | - * 主题关键字常量 | ||
| 134 | - */ | ||
| 135 | - public static class Keyword { | ||
| 136 | - /** | ||
| 137 | - * 事件关键字 | ||
| 138 | - */ | ||
| 139 | - public static final String EVENT = "event"; | ||
| 140 | - | ||
| 141 | - /** | ||
| 142 | - * 服务关键字 | ||
| 143 | - */ | ||
| 144 | - public static final String SERVICE = "service"; | ||
| 145 | - | ||
| 146 | - /** | ||
| 147 | - * 属性关键字 | ||
| 148 | - */ | ||
| 149 | - public static final String PROPERTY = "property"; | ||
| 150 | - | ||
| 151 | - /** | ||
| 152 | - * 上报关键字 | ||
| 153 | - */ | ||
| 154 | - public static final String POST = "post"; | ||
| 155 | - } | ||
| 156 | - | ||
| 157 | -} | ||
| 158 | \ No newline at end of file | 0 | \ 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 deleted
| 1 | -package cn.iocoder.yudao.module.iot.protocol.convert; | ||
| 2 | - | ||
| 3 | -import cn.iocoder.yudao.module.iot.protocol.message.IotMqttMessage; | ||
| 4 | -import cn.iocoder.yudao.module.iot.protocol.message.IotStandardResponse; | ||
| 5 | - | ||
| 6 | -/** | ||
| 7 | - * IoT 协议转换器接口 | ||
| 8 | - * <p> | ||
| 9 | - * 用于在不同协议之间进行转换 | ||
| 10 | - * | ||
| 11 | - * @author haohao | ||
| 12 | - */ | ||
| 13 | -public interface IotProtocolConverter { | ||
| 14 | - | ||
| 15 | - /** | ||
| 16 | - * 将字节数组转换为标准消息 | ||
| 17 | - * | ||
| 18 | - * @param topic 主题 | ||
| 19 | - * @param payload 消息负载 | ||
| 20 | - * @param protocol 协议类型 | ||
| 21 | - * @return 标准消息对象,转换失败返回 null | ||
| 22 | - */ | ||
| 23 | - IotMqttMessage convertToStandardMessage(String topic, byte[] payload, String protocol); | ||
| 24 | - | ||
| 25 | - /** | ||
| 26 | - * 将标准响应转换为字节数组 | ||
| 27 | - * | ||
| 28 | - * @param response 标准响应 | ||
| 29 | - * @param protocol 协议类型 | ||
| 30 | - * @return 字节数组,转换失败返回空数组 | ||
| 31 | - */ | ||
| 32 | - byte[] convertFromStandardResponse(IotStandardResponse response, String protocol); | ||
| 33 | - | ||
| 34 | - /** | ||
| 35 | - * 检查是否支持指定协议 | ||
| 36 | - * | ||
| 37 | - * @param protocol 协议类型 | ||
| 38 | - * @return 如果支持返回 true,否则返回 false | ||
| 39 | - */ | ||
| 40 | - boolean supportsProtocol(String protocol); | ||
| 41 | - | ||
| 42 | - /** | ||
| 43 | - * 获取支持的协议类型列表 | ||
| 44 | - * | ||
| 45 | - * @return 协议类型数组 | ||
| 46 | - */ | ||
| 47 | - String[] getSupportedProtocols(); | ||
| 48 | -} | ||
| 49 | \ No newline at end of file | 0 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-protocol/src/main/java/cn/iocoder/yudao/module/iot/protocol/convert/impl/DefaultIotProtocolConverter.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.protocol.convert.impl; | ||
| 2 | - | ||
| 3 | -import cn.iocoder.yudao.module.iot.protocol.constants.IotLogConstants; | ||
| 4 | -import cn.iocoder.yudao.module.iot.protocol.convert.IotProtocolConverter; | ||
| 5 | -import cn.iocoder.yudao.module.iot.protocol.enums.IotProtocolTypeEnum; | ||
| 6 | -import cn.iocoder.yudao.module.iot.protocol.message.IotMessageParser; | ||
| 7 | -import cn.iocoder.yudao.module.iot.protocol.message.IotMqttMessage; | ||
| 8 | -import cn.iocoder.yudao.module.iot.protocol.message.IotStandardResponse; | ||
| 9 | -import cn.iocoder.yudao.module.iot.protocol.message.impl.IotMqttMessageParser; | ||
| 10 | -import lombok.extern.slf4j.Slf4j; | ||
| 11 | - | ||
| 12 | -import java.util.HashMap; | ||
| 13 | -import java.util.Map; | ||
| 14 | -import java.util.Set; | ||
| 15 | - | ||
| 16 | -/** | ||
| 17 | - * 默认 IoT 协议转换器实现 | ||
| 18 | - * <p> | ||
| 19 | - * 支持多种协议的转换,可以通过注册不同的消息解析器来扩展支持的协议 | ||
| 20 | - * | ||
| 21 | - * @author haohao | ||
| 22 | - */ | ||
| 23 | -@Slf4j | ||
| 24 | -public class DefaultIotProtocolConverter implements IotProtocolConverter { | ||
| 25 | - | ||
| 26 | - /** | ||
| 27 | - * 消息解析器映射 | ||
| 28 | - * Key: 协议类型,Value: 消息解析器 | ||
| 29 | - */ | ||
| 30 | - private final Map<String, IotMessageParser> parsers = new HashMap<>(); | ||
| 31 | - | ||
| 32 | - /** | ||
| 33 | - * 构造函数,初始化默认支持的协议 | ||
| 34 | - */ | ||
| 35 | - public DefaultIotProtocolConverter() { | ||
| 36 | - // 注册 MQTT 协议解析器作为默认实现 | ||
| 37 | - IotMqttMessageParser mqttParser = new IotMqttMessageParser(); | ||
| 38 | - registerParser(IotProtocolTypeEnum.MQTT.getCode(), mqttParser); | ||
| 39 | - } | ||
| 40 | - | ||
| 41 | - /** | ||
| 42 | - * 注册消息解析器 | ||
| 43 | - * | ||
| 44 | - * @param protocol 协议类型 | ||
| 45 | - * @param parser 消息解析器 | ||
| 46 | - */ | ||
| 47 | - public void registerParser(String protocol, IotMessageParser parser) { | ||
| 48 | - parsers.put(protocol, parser); | ||
| 49 | - log.info(IotLogConstants.Converter.REGISTER_PARSER, protocol, parser.getClass().getSimpleName()); | ||
| 50 | - } | ||
| 51 | - | ||
| 52 | - /** | ||
| 53 | - * 移除消息解析器 | ||
| 54 | - * | ||
| 55 | - * @param protocol 协议类型 | ||
| 56 | - */ | ||
| 57 | - public void removeParser(String protocol) { | ||
| 58 | - parsers.remove(protocol); | ||
| 59 | - log.info(IotLogConstants.Converter.REMOVE_PARSER, protocol); | ||
| 60 | - } | ||
| 61 | - | ||
| 62 | - @Override | ||
| 63 | - public IotMqttMessage convertToStandardMessage(String topic, byte[] payload, String protocol) { | ||
| 64 | - IotMessageParser parser = parsers.get(protocol); | ||
| 65 | - if (parser == null) { | ||
| 66 | - log.warn(IotLogConstants.Converter.UNSUPPORTED_PROTOCOL, protocol); | ||
| 67 | - return null; | ||
| 68 | - } | ||
| 69 | - | ||
| 70 | - try { | ||
| 71 | - return parser.parse(topic, payload); | ||
| 72 | - } catch (Exception e) { | ||
| 73 | - log.error(IotLogConstants.Converter.CONVERT_MESSAGE_FAILED, protocol, topic, e); | ||
| 74 | - return null; | ||
| 75 | - } | ||
| 76 | - } | ||
| 77 | - | ||
| 78 | - @Override | ||
| 79 | - public byte[] convertFromStandardResponse(IotStandardResponse response, String protocol) { | ||
| 80 | - IotMessageParser parser = parsers.get(protocol); | ||
| 81 | - if (parser == null) { | ||
| 82 | - log.warn(IotLogConstants.Converter.UNSUPPORTED_PROTOCOL, protocol); | ||
| 83 | - return new byte[0]; | ||
| 84 | - } | ||
| 85 | - | ||
| 86 | - try { | ||
| 87 | - return parser.formatResponse(response); | ||
| 88 | - } catch (Exception e) { | ||
| 89 | - log.error(IotLogConstants.Converter.FORMAT_RESPONSE_FAILED, protocol, e); | ||
| 90 | - return new byte[0]; | ||
| 91 | - } | ||
| 92 | - } | ||
| 93 | - | ||
| 94 | - @Override | ||
| 95 | - public boolean supportsProtocol(String protocol) { | ||
| 96 | - return parsers.containsKey(protocol); | ||
| 97 | - } | ||
| 98 | - | ||
| 99 | - @Override | ||
| 100 | - public String[] getSupportedProtocols() { | ||
| 101 | - Set<String> protocols = parsers.keySet(); | ||
| 102 | - return protocols.toArray(new String[0]); | ||
| 103 | - } | ||
| 104 | - | ||
| 105 | - /** | ||
| 106 | - * 根据主题自动选择合适的协议解析器 | ||
| 107 | - * | ||
| 108 | - * @param topic 主题 | ||
| 109 | - * @param payload 消息负载 | ||
| 110 | - * @return 解析后的标准消息,如果无法解析返回 null | ||
| 111 | - */ | ||
| 112 | - public IotMqttMessage autoConvert(String topic, byte[] payload) { | ||
| 113 | - // 遍历所有解析器,找到能处理该主题的解析器 | ||
| 114 | - for (Map.Entry<String, IotMessageParser> entry : parsers.entrySet()) { | ||
| 115 | - IotMessageParser parser = entry.getValue(); | ||
| 116 | - if (parser.canHandle(topic)) { | ||
| 117 | - try { | ||
| 118 | - IotMqttMessage message = parser.parse(topic, payload); | ||
| 119 | - if (message != null) { | ||
| 120 | - log.debug(IotLogConstants.Converter.AUTO_SELECT_PROTOCOL, entry.getKey(), topic); | ||
| 121 | - return message; | ||
| 122 | - } | ||
| 123 | - } catch (Exception e) { | ||
| 124 | - log.debug(IotLogConstants.Converter.PROTOCOL_PARSE_FAILED_TRY_NEXT, entry.getKey(), topic); | ||
| 125 | - } | ||
| 126 | - } | ||
| 127 | - } | ||
| 128 | - | ||
| 129 | - log.warn(IotLogConstants.Converter.CANNOT_AUTO_RECOGNIZE_PROTOCOL, topic); | ||
| 130 | - return null; | ||
| 131 | - } | ||
| 132 | -} | ||
| 133 | \ No newline at end of file | 0 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-protocol/src/main/java/cn/iocoder/yudao/module/iot/protocol/enums/IotMessageDirectionEnum.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.protocol.enums; | ||
| 2 | - | ||
| 3 | -import lombok.AllArgsConstructor; | ||
| 4 | -import lombok.Getter; | ||
| 5 | - | ||
| 6 | -/** | ||
| 7 | - * IoT 消息方向枚举 | ||
| 8 | - * | ||
| 9 | - * @author haohao | ||
| 10 | - */ | ||
| 11 | -@Getter | ||
| 12 | -@AllArgsConstructor | ||
| 13 | -public enum IotMessageDirectionEnum { | ||
| 14 | - | ||
| 15 | - /** | ||
| 16 | - * 上行消息(设备到平台) | ||
| 17 | - */ | ||
| 18 | - UPSTREAM("upstream", "上行"), | ||
| 19 | - | ||
| 20 | - /** | ||
| 21 | - * 下行消息(平台到设备) | ||
| 22 | - */ | ||
| 23 | - DOWNSTREAM("downstream", "下行"); | ||
| 24 | - | ||
| 25 | - /** | ||
| 26 | - * 方向编码 | ||
| 27 | - */ | ||
| 28 | - private final String code; | ||
| 29 | - | ||
| 30 | - /** | ||
| 31 | - * 方向名称 | ||
| 32 | - */ | ||
| 33 | - private final String name; | ||
| 34 | - | ||
| 35 | - /** | ||
| 36 | - * 根据编码获取消息方向 | ||
| 37 | - * | ||
| 38 | - * @param code 方向编码 | ||
| 39 | - * @return 消息方向枚举,如果未找到返回 null | ||
| 40 | - */ | ||
| 41 | - public static IotMessageDirectionEnum getByCode(String code) { | ||
| 42 | - for (IotMessageDirectionEnum direction : values()) { | ||
| 43 | - if (direction.getCode().equals(code)) { | ||
| 44 | - return direction; | ||
| 45 | - } | ||
| 46 | - } | ||
| 47 | - return null; | ||
| 48 | - } | ||
| 49 | -} | ||
| 50 | \ No newline at end of file | 0 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-protocol/src/main/java/cn/iocoder/yudao/module/iot/protocol/enums/IotMessageTypeEnum.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.protocol.enums; | ||
| 2 | - | ||
| 3 | -import cn.iocoder.yudao.module.iot.protocol.constants.IotTopicConstants; | ||
| 4 | -import lombok.AllArgsConstructor; | ||
| 5 | -import lombok.Getter; | ||
| 6 | - | ||
| 7 | -/** | ||
| 8 | - * IoT 消息类型枚举 | ||
| 9 | - * | ||
| 10 | - * @author haohao | ||
| 11 | - */ | ||
| 12 | -@Getter | ||
| 13 | -@AllArgsConstructor | ||
| 14 | -public enum IotMessageTypeEnum { | ||
| 15 | - | ||
| 16 | - /** | ||
| 17 | - * 属性上报 | ||
| 18 | - */ | ||
| 19 | - PROPERTY_POST("property.post", "属性上报"), | ||
| 20 | - | ||
| 21 | - /** | ||
| 22 | - * 属性设置 | ||
| 23 | - */ | ||
| 24 | - PROPERTY_SET("property.set", "属性设置"), | ||
| 25 | - | ||
| 26 | - /** | ||
| 27 | - * 属性获取 | ||
| 28 | - */ | ||
| 29 | - PROPERTY_GET("property.get", "属性获取"), | ||
| 30 | - | ||
| 31 | - /** | ||
| 32 | - * 事件上报 | ||
| 33 | - */ | ||
| 34 | - EVENT_POST("event.post", "事件上报"), | ||
| 35 | - | ||
| 36 | - /** | ||
| 37 | - * 服务调用 | ||
| 38 | - */ | ||
| 39 | - SERVICE_INVOKE("service.invoke", "服务调用"), | ||
| 40 | - | ||
| 41 | - /** | ||
| 42 | - * 配置设置 | ||
| 43 | - */ | ||
| 44 | - CONFIG_SET("config.set", "配置设置"), | ||
| 45 | - | ||
| 46 | - /** | ||
| 47 | - * OTA 升级 | ||
| 48 | - */ | ||
| 49 | - OTA_UPGRADE("ota.upgrade", "OTA升级"), | ||
| 50 | - | ||
| 51 | - /** | ||
| 52 | - * 设备上线 | ||
| 53 | - */ | ||
| 54 | - DEVICE_ONLINE("device.online", "设备上线"), | ||
| 55 | - | ||
| 56 | - /** | ||
| 57 | - * 设备下线 | ||
| 58 | - */ | ||
| 59 | - DEVICE_OFFLINE("device.offline", "设备下线"), | ||
| 60 | - | ||
| 61 | - /** | ||
| 62 | - * 心跳 | ||
| 63 | - */ | ||
| 64 | - HEARTBEAT("heartbeat", "心跳"); | ||
| 65 | - | ||
| 66 | - /** | ||
| 67 | - * 消息类型编码 | ||
| 68 | - */ | ||
| 69 | - private final String code; | ||
| 70 | - | ||
| 71 | - /** | ||
| 72 | - * 消息类型名称 | ||
| 73 | - */ | ||
| 74 | - private final String name; | ||
| 75 | - | ||
| 76 | - /** | ||
| 77 | - * 根据编码获取消息类型 | ||
| 78 | - * | ||
| 79 | - * @param code 消息类型编码 | ||
| 80 | - * @return 消息类型枚举,如果未找到返回 null | ||
| 81 | - */ | ||
| 82 | - public static IotMessageTypeEnum getByCode(String code) { | ||
| 83 | - for (IotMessageTypeEnum type : values()) { | ||
| 84 | - if (type.getCode().equals(code)) { | ||
| 85 | - return type; | ||
| 86 | - } | ||
| 87 | - } | ||
| 88 | - return null; | ||
| 89 | - } | ||
| 90 | - | ||
| 91 | - /** | ||
| 92 | - * 根据方法名获取消息类型 | ||
| 93 | - * | ||
| 94 | - * @param method 方法名 | ||
| 95 | - * @return 消息类型枚举,如果未找到返回 null | ||
| 96 | - */ | ||
| 97 | - public static IotMessageTypeEnum getByMethod(String method) { | ||
| 98 | - if (method == null) { | ||
| 99 | - return null; | ||
| 100 | - } | ||
| 101 | - | ||
| 102 | - // 处理 thing.service.xxx 格式 | ||
| 103 | - if (method.startsWith(IotTopicConstants.MethodPrefix.THING_SERVICE)) { | ||
| 104 | - String servicePart = method.substring(IotTopicConstants.MethodPrefix.THING_SERVICE.length()); | ||
| 105 | - if ("property.set".equals(servicePart)) { | ||
| 106 | - return PROPERTY_SET; | ||
| 107 | - } else if ("property.get".equals(servicePart)) { | ||
| 108 | - return PROPERTY_GET; | ||
| 109 | - } else if ("config.set".equals(servicePart)) { | ||
| 110 | - return CONFIG_SET; | ||
| 111 | - } else if ("ota.upgrade".equals(servicePart)) { | ||
| 112 | - return OTA_UPGRADE; | ||
| 113 | - } else { | ||
| 114 | - return SERVICE_INVOKE; | ||
| 115 | - } | ||
| 116 | - } | ||
| 117 | - | ||
| 118 | - // 处理 thing.event.xxx 格式 | ||
| 119 | - if (method.startsWith(IotTopicConstants.MethodPrefix.THING_EVENT)) { | ||
| 120 | - String eventPart = method.substring(IotTopicConstants.MethodPrefix.THING_EVENT.length()); | ||
| 121 | - if ("property.post".equals(eventPart)) { | ||
| 122 | - return PROPERTY_POST; | ||
| 123 | - } else { | ||
| 124 | - return EVENT_POST; | ||
| 125 | - } | ||
| 126 | - } | ||
| 127 | - | ||
| 128 | - // 其他类型 | ||
| 129 | - switch (method) { | ||
| 130 | - case IotTopicConstants.Method.DEVICE_ONLINE: | ||
| 131 | - return DEVICE_ONLINE; | ||
| 132 | - case IotTopicConstants.Method.DEVICE_OFFLINE: | ||
| 133 | - return DEVICE_OFFLINE; | ||
| 134 | - case IotTopicConstants.Method.HEARTBEAT: | ||
| 135 | - return HEARTBEAT; | ||
| 136 | - default: | ||
| 137 | - return null; | ||
| 138 | - } | ||
| 139 | - } | ||
| 140 | -} | ||
| 141 | \ No newline at end of file | 0 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-protocol/src/main/java/cn/iocoder/yudao/module/iot/protocol/enums/IotProtocolTypeEnum.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.protocol.enums; | ||
| 2 | - | ||
| 3 | -import lombok.AllArgsConstructor; | ||
| 4 | -import lombok.Getter; | ||
| 5 | - | ||
| 6 | -/** | ||
| 7 | - * IoT 协议类型枚举 | ||
| 8 | - * | ||
| 9 | - * @author haohao | ||
| 10 | - */ | ||
| 11 | -@Getter | ||
| 12 | -@AllArgsConstructor | ||
| 13 | -public enum IotProtocolTypeEnum { | ||
| 14 | - | ||
| 15 | - /** | ||
| 16 | - * MQTT 协议(默认实现) | ||
| 17 | - */ | ||
| 18 | - MQTT("mqtt", "MQTT 协议"), | ||
| 19 | - | ||
| 20 | - /** | ||
| 21 | - * MQTT 原始协议 | ||
| 22 | - */ | ||
| 23 | - MQTT_RAW("mqtt_raw", "MQTT 原始协议"), | ||
| 24 | - | ||
| 25 | - /** | ||
| 26 | - * HTTP 协议 | ||
| 27 | - */ | ||
| 28 | - HTTP("http", "HTTP 协议"), | ||
| 29 | - | ||
| 30 | - /** | ||
| 31 | - * TCP 协议 | ||
| 32 | - */ | ||
| 33 | - TCP("tcp", "TCP 协议"), | ||
| 34 | - | ||
| 35 | - /** | ||
| 36 | - * UDP 协议 | ||
| 37 | - */ | ||
| 38 | - UDP("udp", "UDP 协议"), | ||
| 39 | - | ||
| 40 | - /** | ||
| 41 | - * 自定义协议 | ||
| 42 | - */ | ||
| 43 | - CUSTOM("custom", "自定义协议"); | ||
| 44 | - | ||
| 45 | - /** | ||
| 46 | - * 协议编码 | ||
| 47 | - */ | ||
| 48 | - private final String code; | ||
| 49 | - | ||
| 50 | - /** | ||
| 51 | - * 协议名称 | ||
| 52 | - */ | ||
| 53 | - private final String name; | ||
| 54 | - | ||
| 55 | - /** | ||
| 56 | - * 根据编码获取协议类型 | ||
| 57 | - * | ||
| 58 | - * @param code 协议编码 | ||
| 59 | - * @return 协议类型枚举,如果未找到返回 null | ||
| 60 | - */ | ||
| 61 | - public static IotProtocolTypeEnum getByCode(String code) { | ||
| 62 | - for (IotProtocolTypeEnum type : values()) { | ||
| 63 | - if (type.getCode().equals(code)) { | ||
| 64 | - return type; | ||
| 65 | - } | ||
| 66 | - } | ||
| 67 | - return null; | ||
| 68 | - } | ||
| 69 | - | ||
| 70 | - /** | ||
| 71 | - * 检查是否为有效的协议编码 | ||
| 72 | - * | ||
| 73 | - * @param code 协议编码 | ||
| 74 | - * @return 如果有效返回 true,否则返回 false | ||
| 75 | - */ | ||
| 76 | - public static boolean isValidCode(String code) { | ||
| 77 | - return getByCode(code) != null; | ||
| 78 | - } | ||
| 79 | -} | ||
| 80 | \ No newline at end of file | 0 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-protocol/src/main/java/cn/iocoder/yudao/module/iot/protocol/message/IotMessageParser.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.protocol.message; | ||
| 2 | - | ||
| 3 | -/** | ||
| 4 | - * IoT 消息解析器接口 | ||
| 5 | - * <p> | ||
| 6 | - * 用于解析不同协议的消息内容 | ||
| 7 | - * | ||
| 8 | - * @author haohao | ||
| 9 | - */ | ||
| 10 | -public interface IotMessageParser { | ||
| 11 | - | ||
| 12 | - /** | ||
| 13 | - * 解析消息 | ||
| 14 | - * | ||
| 15 | - * @param topic 主题 | ||
| 16 | - * @param payload 消息负载 | ||
| 17 | - * @return 解析后的标准消息,如果解析失败返回 null | ||
| 18 | - */ | ||
| 19 | - IotMqttMessage parse(String topic, byte[] payload); | ||
| 20 | - | ||
| 21 | - /** | ||
| 22 | - * 格式化响应消息 | ||
| 23 | - * | ||
| 24 | - * @param response 标准响应 | ||
| 25 | - * @return 格式化后的响应字节数组 | ||
| 26 | - */ | ||
| 27 | - byte[] formatResponse(IotStandardResponse response); | ||
| 28 | - | ||
| 29 | - /** | ||
| 30 | - * 检查是否能够处理指定主题的消息 | ||
| 31 | - * | ||
| 32 | - * @param topic 主题 | ||
| 33 | - * @return 如果能处理返回 true,否则返回 false | ||
| 34 | - */ | ||
| 35 | - boolean canHandle(String topic); | ||
| 36 | -} | ||
| 37 | \ No newline at end of file | 0 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-protocol/src/main/java/cn/iocoder/yudao/module/iot/protocol/message/IotMqttMessage.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.protocol.message; | ||
| 2 | - | ||
| 3 | -import cn.hutool.core.util.IdUtil; | ||
| 4 | -import cn.hutool.json.JSONObject; | ||
| 5 | -import lombok.Builder; | ||
| 6 | -import lombok.Data; | ||
| 7 | - | ||
| 8 | -import java.util.Map; | ||
| 9 | - | ||
| 10 | -/** | ||
| 11 | - * IoT MQTT 消息模型 | ||
| 12 | - * <p> | ||
| 13 | - * 基于 MQTT 协议规范实现的标准消息格式,支持设备属性、事件、服务调用等标准功能 | ||
| 14 | - * | ||
| 15 | - * @author haohao | ||
| 16 | - * @see <a href="https://mqtt.org/">MQTT 协议官方规范</a> | ||
| 17 | - */ | ||
| 18 | -@Data | ||
| 19 | -@Builder | ||
| 20 | -public class IotMqttMessage { | ||
| 21 | - | ||
| 22 | - /** | ||
| 23 | - * 消息 ID | ||
| 24 | - */ | ||
| 25 | - private String id; | ||
| 26 | - | ||
| 27 | - /** | ||
| 28 | - * 协议版本 | ||
| 29 | - */ | ||
| 30 | - @Builder.Default | ||
| 31 | - private String version = "1.0"; | ||
| 32 | - | ||
| 33 | - /** | ||
| 34 | - * 消息方法 | ||
| 35 | - */ | ||
| 36 | - private String method; | ||
| 37 | - | ||
| 38 | - /** | ||
| 39 | - * 消息参数 | ||
| 40 | - */ | ||
| 41 | - private Map<String, Object> params; | ||
| 42 | - | ||
| 43 | - /** | ||
| 44 | - * 转换为 JSONObject | ||
| 45 | - * | ||
| 46 | - * @return JSONObject 对象 | ||
| 47 | - */ | ||
| 48 | - public JSONObject toJsonObject() { | ||
| 49 | - JSONObject json = new JSONObject(); | ||
| 50 | - json.set("id", id); | ||
| 51 | - json.set("version", version); | ||
| 52 | - json.set("method", method); | ||
| 53 | - json.set("params", params != null ? params : new JSONObject()); | ||
| 54 | - return json; | ||
| 55 | - } | ||
| 56 | - | ||
| 57 | - /** | ||
| 58 | - * 转换为 JSON 字符串 | ||
| 59 | - * | ||
| 60 | - * @return JSON 字符串 | ||
| 61 | - */ | ||
| 62 | - public String toJsonString() { | ||
| 63 | - return toJsonObject().toString(); | ||
| 64 | - } | ||
| 65 | - | ||
| 66 | - /** | ||
| 67 | - * 创建设备服务调用消息 | ||
| 68 | - * | ||
| 69 | - * @param requestId 请求 ID,为空时自动生成 | ||
| 70 | - * @param serviceIdentifier 服务标识符 | ||
| 71 | - * @param params 服务参数 | ||
| 72 | - * @return MQTT 消息对象 | ||
| 73 | - */ | ||
| 74 | - public static IotMqttMessage createServiceInvokeMessage(String requestId, String serviceIdentifier, | ||
| 75 | - Map<String, Object> params) { | ||
| 76 | - return IotMqttMessage.builder() | ||
| 77 | - .id(requestId != null ? requestId : generateRequestId()) | ||
| 78 | - .method("thing.service." + serviceIdentifier) | ||
| 79 | - .params(params) | ||
| 80 | - .build(); | ||
| 81 | - } | ||
| 82 | - | ||
| 83 | - /** | ||
| 84 | - * 创建设备属性设置消息 | ||
| 85 | - * | ||
| 86 | - * @param requestId 请求 ID,为空时自动生成 | ||
| 87 | - * @param properties 设备属性 | ||
| 88 | - * @return MQTT 消息对象 | ||
| 89 | - */ | ||
| 90 | - public static IotMqttMessage createPropertySetMessage(String requestId, Map<String, Object> properties) { | ||
| 91 | - return IotMqttMessage.builder() | ||
| 92 | - .id(requestId != null ? requestId : generateRequestId()) | ||
| 93 | - .method("thing.service.property.set") | ||
| 94 | - .params(properties) | ||
| 95 | - .build(); | ||
| 96 | - } | ||
| 97 | - | ||
| 98 | - /** | ||
| 99 | - * 创建设备属性获取消息 | ||
| 100 | - * | ||
| 101 | - * @param requestId 请求 ID,为空时自动生成 | ||
| 102 | - * @param identifiers 要获取的属性标识符列表 | ||
| 103 | - * @return MQTT 消息对象 | ||
| 104 | - */ | ||
| 105 | - public static IotMqttMessage createPropertyGetMessage(String requestId, String[] identifiers) { | ||
| 106 | - JSONObject params = new JSONObject(); | ||
| 107 | - params.set("identifiers", identifiers); | ||
| 108 | - | ||
| 109 | - return IotMqttMessage.builder() | ||
| 110 | - .id(requestId != null ? requestId : generateRequestId()) | ||
| 111 | - .method("thing.service.property.get") | ||
| 112 | - .params(params) | ||
| 113 | - .build(); | ||
| 114 | - } | ||
| 115 | - | ||
| 116 | - /** | ||
| 117 | - * 创建设备配置设置消息 | ||
| 118 | - * | ||
| 119 | - * @param requestId 请求 ID,为空时自动生成 | ||
| 120 | - * @param configs 设备配置 | ||
| 121 | - * @return MQTT 消息对象 | ||
| 122 | - */ | ||
| 123 | - public static IotMqttMessage createConfigSetMessage(String requestId, Map<String, Object> configs) { | ||
| 124 | - return IotMqttMessage.builder() | ||
| 125 | - .id(requestId != null ? requestId : generateRequestId()) | ||
| 126 | - .method("thing.service.config.set") | ||
| 127 | - .params(configs) | ||
| 128 | - .build(); | ||
| 129 | - } | ||
| 130 | - | ||
| 131 | - /** | ||
| 132 | - * 创建设备 OTA 升级消息 | ||
| 133 | - * | ||
| 134 | - * @param requestId 请求 ID,为空时自动生成 | ||
| 135 | - * @param otaInfo OTA 升级信息 | ||
| 136 | - * @return MQTT 消息对象 | ||
| 137 | - */ | ||
| 138 | - public static IotMqttMessage createOtaUpgradeMessage(String requestId, Map<String, Object> otaInfo) { | ||
| 139 | - return IotMqttMessage.builder() | ||
| 140 | - .id(requestId != null ? requestId : generateRequestId()) | ||
| 141 | - .method("thing.service.ota.upgrade") | ||
| 142 | - .params(otaInfo) | ||
| 143 | - .build(); | ||
| 144 | - } | ||
| 145 | - | ||
| 146 | - /** | ||
| 147 | - * 生成请求 ID | ||
| 148 | - * | ||
| 149 | - * @return 请求 ID | ||
| 150 | - */ | ||
| 151 | - public static String generateRequestId() { | ||
| 152 | - return IdUtil.fastSimpleUUID(); | ||
| 153 | - } | ||
| 154 | -} | ||
| 155 | \ No newline at end of file | 0 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-protocol/src/main/java/cn/iocoder/yudao/module/iot/protocol/message/IotStandardResponse.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.protocol.message; | ||
| 2 | - | ||
| 3 | -import cn.hutool.core.util.StrUtil; | ||
| 4 | -import lombok.Data; | ||
| 5 | -import lombok.experimental.Accessors; | ||
| 6 | - | ||
| 7 | -/** | ||
| 8 | - * IoT 标准协议响应实体类 | ||
| 9 | - * <p> | ||
| 10 | - * 用于统一 MQTT 和 HTTP 的响应格式 | ||
| 11 | - * | ||
| 12 | - * @author haohao | ||
| 13 | - */ | ||
| 14 | -@Data | ||
| 15 | -@Accessors(chain = true) | ||
| 16 | -public class IotStandardResponse { | ||
| 17 | - | ||
| 18 | - /** | ||
| 19 | - * 消息 ID | ||
| 20 | - */ | ||
| 21 | - private String id; | ||
| 22 | - | ||
| 23 | - /** | ||
| 24 | - * 状态码 | ||
| 25 | - */ | ||
| 26 | - private Integer code; | ||
| 27 | - | ||
| 28 | - /** | ||
| 29 | - * 响应数据 | ||
| 30 | - */ | ||
| 31 | - private Object data; | ||
| 32 | - | ||
| 33 | - /** | ||
| 34 | - * 响应消息 | ||
| 35 | - */ | ||
| 36 | - private String message; | ||
| 37 | - | ||
| 38 | - /** | ||
| 39 | - * 方法名 | ||
| 40 | - */ | ||
| 41 | - private String method; | ||
| 42 | - | ||
| 43 | - /** | ||
| 44 | - * 协议版本 | ||
| 45 | - */ | ||
| 46 | - private String version; | ||
| 47 | - | ||
| 48 | - /** | ||
| 49 | - * 创建成功响应 | ||
| 50 | - * | ||
| 51 | - * @param id 消息 ID | ||
| 52 | - * @param method 方法名 | ||
| 53 | - * @return 成功响应 | ||
| 54 | - */ | ||
| 55 | - public static IotStandardResponse success(String id, String method) { | ||
| 56 | - return success(id, method, null); | ||
| 57 | - } | ||
| 58 | - | ||
| 59 | - /** | ||
| 60 | - * 创建成功响应 | ||
| 61 | - * | ||
| 62 | - * @param id 消息 ID | ||
| 63 | - * @param method 方法名 | ||
| 64 | - * @param data 响应数据 | ||
| 65 | - * @return 成功响应 | ||
| 66 | - */ | ||
| 67 | - public static IotStandardResponse success(String id, String method, Object data) { | ||
| 68 | - return new IotStandardResponse() | ||
| 69 | - .setId(id) | ||
| 70 | - .setCode(200) | ||
| 71 | - .setData(data) | ||
| 72 | - .setMessage("success") | ||
| 73 | - .setMethod(method) | ||
| 74 | - .setVersion("1.0"); | ||
| 75 | - } | ||
| 76 | - | ||
| 77 | - /** | ||
| 78 | - * 创建错误响应 | ||
| 79 | - * | ||
| 80 | - * @param id 消息 ID | ||
| 81 | - * @param method 方法名 | ||
| 82 | - * @param code 错误码 | ||
| 83 | - * @param message 错误消息 | ||
| 84 | - * @return 错误响应 | ||
| 85 | - */ | ||
| 86 | - public static IotStandardResponse error(String id, String method, Integer code, String message) { | ||
| 87 | - return new IotStandardResponse() | ||
| 88 | - .setId(id) | ||
| 89 | - .setCode(code) | ||
| 90 | - .setData(null) | ||
| 91 | - .setMessage(StrUtil.blankToDefault(message, "error")) | ||
| 92 | - .setMethod(method) | ||
| 93 | - .setVersion("1.0"); | ||
| 94 | - } | ||
| 95 | -} | ||
| 96 | \ No newline at end of file | 0 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-protocol/src/main/java/cn/iocoder/yudao/module/iot/protocol/message/impl/IotHttpMessageParser.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.protocol.message.impl; | ||
| 2 | - | ||
| 3 | -import cn.hutool.core.util.StrUtil; | ||
| 4 | -import cn.hutool.json.JSONObject; | ||
| 5 | -import cn.hutool.json.JSONUtil; | ||
| 6 | -import cn.iocoder.yudao.module.iot.protocol.constants.IotHttpConstants; | ||
| 7 | -import cn.iocoder.yudao.module.iot.protocol.constants.IotLogConstants; | ||
| 8 | -import cn.iocoder.yudao.module.iot.protocol.constants.IotTopicConstants; | ||
| 9 | -import cn.iocoder.yudao.module.iot.protocol.message.IotMessageParser; | ||
| 10 | -import cn.iocoder.yudao.module.iot.protocol.message.IotMqttMessage; | ||
| 11 | -import cn.iocoder.yudao.module.iot.protocol.message.IotStandardResponse; | ||
| 12 | -import lombok.extern.slf4j.Slf4j; | ||
| 13 | - | ||
| 14 | -import java.nio.charset.StandardCharsets; | ||
| 15 | -import java.util.HashMap; | ||
| 16 | -import java.util.Map; | ||
| 17 | - | ||
| 18 | -/** | ||
| 19 | - * IoT HTTP 协议消息解析器实现 | ||
| 20 | - * <p> | ||
| 21 | - * 参考阿里云IoT平台HTTPS协议标准,支持设备认证和数据上报两种消息类型: | ||
| 22 | - * <p> | ||
| 23 | - * 1. 设备认证消息格式: | ||
| 24 | - * | ||
| 25 | - * <pre> | ||
| 26 | - * POST /auth HTTP/1.1 | ||
| 27 | - * Content-Type: application/json | ||
| 28 | - * { | ||
| 29 | - * "productKey": "a1AbC***", | ||
| 30 | - * "deviceName": "device01", | ||
| 31 | - * "clientId": "device01_001", | ||
| 32 | - * "timestamp": "1501668289957", | ||
| 33 | - * "sign": "xxxxx", | ||
| 34 | - * "signmethod": "hmacsha1", | ||
| 35 | - * "version": "default" | ||
| 36 | - * } | ||
| 37 | - * </pre> | ||
| 38 | - * <p> | ||
| 39 | - * 2. 数据上报消息格式: | ||
| 40 | - * | ||
| 41 | - * <pre> | ||
| 42 | - * POST /topic/${topic} HTTP/1.1 | ||
| 43 | - * password: ${token} | ||
| 44 | - * Content-Type: application/octet-stream | ||
| 45 | - * ${payload} | ||
| 46 | - * </pre> | ||
| 47 | - * | ||
| 48 | - * @author haohao | ||
| 49 | - */ | ||
| 50 | -@Slf4j | ||
| 51 | -public class IotHttpMessageParser implements IotMessageParser { | ||
| 52 | - | ||
| 53 | - /** | ||
| 54 | - * 认证路径 | ||
| 55 | - */ | ||
| 56 | - public static final String AUTH_PATH = IotHttpConstants.Path.AUTH; | ||
| 57 | - | ||
| 58 | - /** | ||
| 59 | - * 主题路径前缀 | ||
| 60 | - */ | ||
| 61 | - public static final String TOPIC_PATH_PREFIX = IotHttpConstants.Path.TOPIC_PREFIX; | ||
| 62 | - | ||
| 63 | - @Override | ||
| 64 | - public IotMqttMessage parse(String topic, byte[] payload) { | ||
| 65 | - if (payload == null || payload.length == 0) { | ||
| 66 | - log.warn(IotLogConstants.Http.RECEIVED_EMPTY_MESSAGE, topic); | ||
| 67 | - return null; | ||
| 68 | - } | ||
| 69 | - | ||
| 70 | - try { | ||
| 71 | - String message = new String(payload, StandardCharsets.UTF_8); | ||
| 72 | - | ||
| 73 | - // 判断是认证请求还是数据上报 | ||
| 74 | - if (AUTH_PATH.equals(topic)) { | ||
| 75 | - return parseAuthMessage(message); | ||
| 76 | - } else if (topic.startsWith(TOPIC_PATH_PREFIX)) { | ||
| 77 | - return parseDataMessage(topic, message); | ||
| 78 | - } else { | ||
| 79 | - log.warn(IotLogConstants.Http.UNSUPPORTED_PATH_FORMAT, topic); | ||
| 80 | - return null; | ||
| 81 | - } | ||
| 82 | - | ||
| 83 | - } catch (Exception e) { | ||
| 84 | - log.error(IotLogConstants.Http.PARSE_MESSAGE_FAILED, topic, e); | ||
| 85 | - return null; | ||
| 86 | - } | ||
| 87 | - } | ||
| 88 | - | ||
| 89 | - /** | ||
| 90 | - * 解析设备认证消息 | ||
| 91 | - * | ||
| 92 | - * @param message 认证消息JSON | ||
| 93 | - * @return 标准消息格式 | ||
| 94 | - */ | ||
| 95 | - private IotMqttMessage parseAuthMessage(String message) { | ||
| 96 | - if (!JSONUtil.isTypeJSON(message)) { | ||
| 97 | - log.warn(IotLogConstants.Http.AUTH_MESSAGE_NOT_JSON, message); | ||
| 98 | - return null; | ||
| 99 | - } | ||
| 100 | - | ||
| 101 | - JSONObject json = JSONUtil.parseObj(message); | ||
| 102 | - | ||
| 103 | - // 验证必需字段 | ||
| 104 | - String productKey = json.getStr(IotHttpConstants.AuthField.PRODUCT_KEY); | ||
| 105 | - String deviceName = json.getStr(IotHttpConstants.AuthField.DEVICE_NAME); | ||
| 106 | - String clientId = json.getStr(IotHttpConstants.AuthField.CLIENT_ID); | ||
| 107 | - String sign = json.getStr(IotHttpConstants.AuthField.SIGN); | ||
| 108 | - | ||
| 109 | - if (StrUtil.hasBlank(productKey, deviceName, clientId, sign)) { | ||
| 110 | - log.warn(IotLogConstants.Http.AUTH_MESSAGE_MISSING_REQUIRED_FIELDS, message); | ||
| 111 | - return null; | ||
| 112 | - } | ||
| 113 | - | ||
| 114 | - // 构建认证消息 | ||
| 115 | - Map<String, Object> params = new HashMap<>(); | ||
| 116 | - params.put(IotHttpConstants.AuthField.PRODUCT_KEY, productKey); | ||
| 117 | - params.put(IotHttpConstants.AuthField.DEVICE_NAME, deviceName); | ||
| 118 | - params.put(IotHttpConstants.AuthField.CLIENT_ID, clientId); | ||
| 119 | - params.put(IotHttpConstants.AuthField.TIMESTAMP, json.getStr(IotHttpConstants.AuthField.TIMESTAMP)); | ||
| 120 | - params.put(IotHttpConstants.AuthField.SIGN, sign); | ||
| 121 | - params.put(IotHttpConstants.AuthField.SIGN_METHOD, | ||
| 122 | - json.getStr(IotHttpConstants.AuthField.SIGN_METHOD, IotHttpConstants.DefaultValue.SIGN_METHOD)); | ||
| 123 | - | ||
| 124 | - return IotMqttMessage.builder() | ||
| 125 | - .id(generateMessageId()) | ||
| 126 | - .method(IotHttpConstants.Method.DEVICE_AUTH) | ||
| 127 | - .version(json.getStr(IotHttpConstants.AuthField.VERSION, IotHttpConstants.DefaultValue.VERSION)) | ||
| 128 | - .params(params) | ||
| 129 | - .build(); | ||
| 130 | - } | ||
| 131 | - | ||
| 132 | - /** | ||
| 133 | - * 解析数据上报消息 | ||
| 134 | - * | ||
| 135 | - * @param topic 主题路径,格式:/topic/${actualTopic} | ||
| 136 | - * @param message 消息内容 | ||
| 137 | - * @return 标准消息格式 | ||
| 138 | - */ | ||
| 139 | - private IotMqttMessage parseDataMessage(String topic, String message) { | ||
| 140 | - // 提取实际的主题,去掉 /topic 前缀 | ||
| 141 | - String actualTopic = topic.substring(TOPIC_PATH_PREFIX.length()); // 直接移除/topic前缀 | ||
| 142 | - | ||
| 143 | - // 尝试解析为JSON格式 | ||
| 144 | - if (JSONUtil.isTypeJSON(message)) { | ||
| 145 | - return parseJsonDataMessage(actualTopic, message); | ||
| 146 | - } else { | ||
| 147 | - // 原始数据格式 | ||
| 148 | - return parseRawDataMessage(actualTopic, message); | ||
| 149 | - } | ||
| 150 | - } | ||
| 151 | - | ||
| 152 | - /** | ||
| 153 | - * 解析JSON格式的数据消息 | ||
| 154 | - * | ||
| 155 | - * @param topic 实际主题 | ||
| 156 | - * @param message JSON消息 | ||
| 157 | - * @return 标准消息格式 | ||
| 158 | - */ | ||
| 159 | - private IotMqttMessage parseJsonDataMessage(String topic, String message) { | ||
| 160 | - JSONObject json = JSONUtil.parseObj(message); | ||
| 161 | - | ||
| 162 | - // 生成消息ID | ||
| 163 | - String messageId = json.getStr(IotHttpConstants.MessageField.ID); | ||
| 164 | - if (StrUtil.isBlank(messageId)) { | ||
| 165 | - messageId = generateMessageId(); | ||
| 166 | - } | ||
| 167 | - | ||
| 168 | - // 获取方法名 | ||
| 169 | - String method = json.getStr(IotHttpConstants.MessageField.METHOD); | ||
| 170 | - if (StrUtil.isBlank(method)) { | ||
| 171 | - // 根据主题推断方法名 | ||
| 172 | - method = inferMethodFromTopic(topic); | ||
| 173 | - } | ||
| 174 | - | ||
| 175 | - // 获取参数 | ||
| 176 | - Object params = json.get(IotHttpConstants.MessageField.PARAMS); | ||
| 177 | - Map<String, Object> paramsMap = new HashMap<>(); | ||
| 178 | - if (params instanceof Map) { | ||
| 179 | - paramsMap.putAll((Map<String, Object>) params); | ||
| 180 | - } else if (params != null) { | ||
| 181 | - paramsMap.put(IotHttpConstants.MessageField.DATA, params); | ||
| 182 | - } | ||
| 183 | - | ||
| 184 | - return IotMqttMessage.builder() | ||
| 185 | - .id(messageId) | ||
| 186 | - .method(method) | ||
| 187 | - .version(json.getStr(IotHttpConstants.MessageField.VERSION, | ||
| 188 | - IotHttpConstants.DefaultValue.MESSAGE_VERSION)) | ||
| 189 | - .params(paramsMap) | ||
| 190 | - .build(); | ||
| 191 | - } | ||
| 192 | - | ||
| 193 | - /** | ||
| 194 | - * 解析原始数据消息 | ||
| 195 | - * | ||
| 196 | - * @param topic 实际主题 | ||
| 197 | - * @param message 原始消息 | ||
| 198 | - * @return 标准消息格式 | ||
| 199 | - */ | ||
| 200 | - private IotMqttMessage parseRawDataMessage(String topic, String message) { | ||
| 201 | - Map<String, Object> params = new HashMap<>(); | ||
| 202 | - params.put(IotHttpConstants.MessageField.DATA, message); | ||
| 203 | - | ||
| 204 | - return IotMqttMessage.builder() | ||
| 205 | - .id(generateMessageId()) | ||
| 206 | - .method(inferMethodFromTopic(topic)) | ||
| 207 | - .version(IotHttpConstants.DefaultValue.MESSAGE_VERSION) | ||
| 208 | - .params(params) | ||
| 209 | - .build(); | ||
| 210 | - } | ||
| 211 | - | ||
| 212 | - /** | ||
| 213 | - * 根据主题推断方法名 | ||
| 214 | - * | ||
| 215 | - * @param topic 主题 | ||
| 216 | - * @return 方法名 | ||
| 217 | - */ | ||
| 218 | - private String inferMethodFromTopic(String topic) { | ||
| 219 | - if (StrUtil.isBlank(topic)) { | ||
| 220 | - return IotHttpConstants.DefaultValue.UNKNOWN_METHOD; | ||
| 221 | - } | ||
| 222 | - | ||
| 223 | - // 标准系统主题解析 | ||
| 224 | - if (topic.startsWith(IotTopicConstants.SYS_TOPIC_PREFIX)) { | ||
| 225 | - if (topic.contains(IotTopicConstants.PROPERTY_SET_TOPIC)) { | ||
| 226 | - return IotTopicConstants.Method.PROPERTY_SET; | ||
| 227 | - } else if (topic.contains(IotTopicConstants.PROPERTY_GET_TOPIC)) { | ||
| 228 | - return IotTopicConstants.Method.PROPERTY_GET; | ||
| 229 | - } else if (topic.contains(IotTopicConstants.PROPERTY_POST_TOPIC)) { | ||
| 230 | - return IotTopicConstants.Method.PROPERTY_POST; | ||
| 231 | - } else if (topic.contains(IotTopicConstants.EVENT_POST_TOPIC_PREFIX) | ||
| 232 | - && topic.endsWith(IotTopicConstants.EVENT_POST_TOPIC_SUFFIX)) { | ||
| 233 | - // 自定义事件上报 | ||
| 234 | - String[] parts = topic.split("/"); | ||
| 235 | - // 查找event关键字的位置 | ||
| 236 | - for (int i = 0; i < parts.length; i++) { | ||
| 237 | - if (IotTopicConstants.Keyword.EVENT.equals(parts[i]) && i + 1 < parts.length) { | ||
| 238 | - String eventId = parts[i + 1]; | ||
| 239 | - return IotTopicConstants.MethodPrefix.THING_EVENT + eventId + ".post"; | ||
| 240 | - } | ||
| 241 | - } | ||
| 242 | - } else if (topic.contains(IotTopicConstants.SERVICE_TOPIC_PREFIX) | ||
| 243 | - && !topic.contains(IotTopicConstants.Keyword.PROPERTY)) { | ||
| 244 | - // 自定义服务调用 | ||
| 245 | - String[] parts = topic.split("/"); | ||
| 246 | - // 查找service关键字的位置 | ||
| 247 | - for (int i = 0; i < parts.length; i++) { | ||
| 248 | - if (IotTopicConstants.Keyword.SERVICE.equals(parts[i]) && i + 1 < parts.length) { | ||
| 249 | - String serviceId = parts[i + 1]; | ||
| 250 | - return IotTopicConstants.MethodPrefix.THING_SERVICE + serviceId; | ||
| 251 | - } | ||
| 252 | - } | ||
| 253 | - } | ||
| 254 | - } | ||
| 255 | - | ||
| 256 | - // 自定义主题 | ||
| 257 | - return IotHttpConstants.Method.CUSTOM_MESSAGE; | ||
| 258 | - } | ||
| 259 | - | ||
| 260 | - /** | ||
| 261 | - * 生成消息ID | ||
| 262 | - * | ||
| 263 | - * @return 消息ID | ||
| 264 | - */ | ||
| 265 | - private String generateMessageId() { | ||
| 266 | - return IotMqttMessage.generateRequestId(); | ||
| 267 | - } | ||
| 268 | - | ||
| 269 | - @Override | ||
| 270 | - public byte[] formatResponse(IotStandardResponse response) { | ||
| 271 | - try { | ||
| 272 | - JSONObject httpResponse = new JSONObject(); | ||
| 273 | - | ||
| 274 | - // 判断是否为认证响应 | ||
| 275 | - if (IotHttpConstants.Method.DEVICE_AUTH.equals(response.getMethod())) { | ||
| 276 | - // 认证响应格式 | ||
| 277 | - httpResponse.set(IotHttpConstants.ResponseField.CODE, response.getCode()); | ||
| 278 | - httpResponse.set(IotHttpConstants.ResponseField.MESSAGE, response.getMessage()); | ||
| 279 | - | ||
| 280 | - if (response.getCode() == 200 && response.getData() != null) { | ||
| 281 | - JSONObject info = new JSONObject(); | ||
| 282 | - if (response.getData() instanceof Map) { | ||
| 283 | - Map<String, Object> dataMap = (Map<String, Object>) response.getData(); | ||
| 284 | - info.putAll(dataMap); | ||
| 285 | - } else { | ||
| 286 | - info.set(IotHttpConstants.ResponseField.TOKEN, response.getData().toString()); | ||
| 287 | - } | ||
| 288 | - httpResponse.set(IotHttpConstants.ResponseField.INFO, info); | ||
| 289 | - } | ||
| 290 | - } else { | ||
| 291 | - // 数据上报响应格式 | ||
| 292 | - httpResponse.set(IotHttpConstants.ResponseField.CODE, response.getCode()); | ||
| 293 | - httpResponse.set(IotHttpConstants.ResponseField.MESSAGE, response.getMessage()); | ||
| 294 | - | ||
| 295 | - if (response.getCode() == 200) { | ||
| 296 | - JSONObject info = new JSONObject(); | ||
| 297 | - info.set(IotHttpConstants.ResponseField.MESSAGE_ID, response.getId()); | ||
| 298 | - httpResponse.set(IotHttpConstants.ResponseField.INFO, info); | ||
| 299 | - } | ||
| 300 | - } | ||
| 301 | - | ||
| 302 | - String json = httpResponse.toString(); | ||
| 303 | - return json.getBytes(StandardCharsets.UTF_8); | ||
| 304 | - } catch (Exception e) { | ||
| 305 | - log.error(IotLogConstants.Http.FORMAT_RESPONSE_FAILED, e); | ||
| 306 | - return new byte[0]; | ||
| 307 | - } | ||
| 308 | - } | ||
| 309 | - | ||
| 310 | - @Override | ||
| 311 | - public boolean canHandle(String topic) { | ||
| 312 | - // 支持认证路径和主题路径 | ||
| 313 | - return topic != null && (AUTH_PATH.equals(topic) || topic.startsWith(TOPIC_PATH_PREFIX)); | ||
| 314 | - } | ||
| 315 | - | ||
| 316 | - /** | ||
| 317 | - * 从设备标识中解析产品Key和设备名称 | ||
| 318 | - * | ||
| 319 | - * @param deviceKey 设备标识,格式:productKey/deviceName | ||
| 320 | - * @return 包含产品Key和设备名称的数组,[0]为产品Key,[1]为设备名称 | ||
| 321 | - */ | ||
| 322 | - public static String[] parseDeviceKey(String deviceKey) { | ||
| 323 | - if (StrUtil.isBlank(deviceKey)) { | ||
| 324 | - return null; | ||
| 325 | - } | ||
| 326 | - | ||
| 327 | - String[] parts = deviceKey.split("/"); | ||
| 328 | - if (parts.length != 2) { | ||
| 329 | - return null; | ||
| 330 | - } | ||
| 331 | - | ||
| 332 | - return new String[]{parts[0], parts[1]}; | ||
| 333 | - } | ||
| 334 | - | ||
| 335 | - /** | ||
| 336 | - * 构建设备标识 | ||
| 337 | - * | ||
| 338 | - * @param productKey 产品Key | ||
| 339 | - * @param deviceName 设备名称 | ||
| 340 | - * @return 设备标识,格式:productKey/deviceName | ||
| 341 | - */ | ||
| 342 | - public static String buildDeviceKey(String productKey, String deviceName) { | ||
| 343 | - if (StrUtil.isBlank(productKey) || StrUtil.isBlank(deviceName)) { | ||
| 344 | - return null; | ||
| 345 | - } | ||
| 346 | - return productKey + "/" + deviceName; | ||
| 347 | - } | ||
| 348 | -} | ||
| 349 | \ No newline at end of file | 0 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-protocol/src/main/java/cn/iocoder/yudao/module/iot/protocol/message/impl/IotMqttMessageParser.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.protocol.message.impl; | ||
| 2 | - | ||
| 3 | -import cn.hutool.core.util.StrUtil; | ||
| 4 | -import cn.hutool.json.JSONObject; | ||
| 5 | -import cn.hutool.json.JSONUtil; | ||
| 6 | -import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | ||
| 7 | -import cn.iocoder.yudao.module.iot.protocol.message.IotMessageParser; | ||
| 8 | -import cn.iocoder.yudao.module.iot.protocol.message.IotMqttMessage; | ||
| 9 | -import cn.iocoder.yudao.module.iot.protocol.message.IotStandardResponse; | ||
| 10 | -import cn.iocoder.yudao.module.iot.protocol.util.IotTopicUtils; | ||
| 11 | -import lombok.extern.slf4j.Slf4j; | ||
| 12 | - | ||
| 13 | -import java.nio.charset.StandardCharsets; | ||
| 14 | -import java.util.Map; | ||
| 15 | - | ||
| 16 | -/** | ||
| 17 | - * IoT MQTT 协议消息解析器实现 | ||
| 18 | - * <p> | ||
| 19 | - * 基于 MQTT 协议规范实现的消息解析器,支持设备属性、事件、服务调用等标准功能 | ||
| 20 | - * | ||
| 21 | - * @author haohao | ||
| 22 | - */ | ||
| 23 | -@Slf4j | ||
| 24 | -public class IotMqttMessageParser implements IotMessageParser { | ||
| 25 | - | ||
| 26 | - @Override | ||
| 27 | - public IotMqttMessage parse(String topic, byte[] payload) { | ||
| 28 | - if (payload == null || payload.length == 0) { | ||
| 29 | - log.warn("[MQTT] 收到空消息内容, topic={}", topic); | ||
| 30 | - return null; | ||
| 31 | - } | ||
| 32 | - | ||
| 33 | - try { | ||
| 34 | - String message = new String(payload, StandardCharsets.UTF_8); | ||
| 35 | - if (!JSONUtil.isTypeJSON(message)) { | ||
| 36 | - log.warn("[MQTT] 收到非JSON格式消息, topic={}, message={}", topic, message); | ||
| 37 | - return null; | ||
| 38 | - } | ||
| 39 | - | ||
| 40 | - JSONObject json = JSONUtil.parseObj(message); | ||
| 41 | - String id = json.getStr("id"); | ||
| 42 | - String method = json.getStr("method"); | ||
| 43 | - | ||
| 44 | - if (StrUtil.isBlank(method)) { | ||
| 45 | - // 尝试从 topic 中解析方法 | ||
| 46 | - method = IotTopicUtils.parseMethodFromTopic(topic); | ||
| 47 | - if (StrUtil.isBlank(method)) { | ||
| 48 | - log.warn("[MQTT] 无法确定消息方法, topic={}, message={}", topic, message); | ||
| 49 | - return null; | ||
| 50 | - } | ||
| 51 | - } | ||
| 52 | - | ||
| 53 | - @SuppressWarnings("unchecked") | ||
| 54 | - Map<String, Object> params = (Map<String, Object>) json.getObj("params", Map.class); | ||
| 55 | - return IotMqttMessage.builder() | ||
| 56 | - .id(id) | ||
| 57 | - .method(method) | ||
| 58 | - .version(json.getStr("version", "1.0")) | ||
| 59 | - .params(params) | ||
| 60 | - .build(); | ||
| 61 | - } catch (Exception e) { | ||
| 62 | - log.error("[MQTT] 解析消息失败, topic={}", topic, e); | ||
| 63 | - return null; | ||
| 64 | - } | ||
| 65 | - } | ||
| 66 | - | ||
| 67 | - @Override | ||
| 68 | - public byte[] formatResponse(IotStandardResponse response) { | ||
| 69 | - try { | ||
| 70 | - String json = JsonUtils.toJsonString(response); | ||
| 71 | - return json.getBytes(StandardCharsets.UTF_8); | ||
| 72 | - } catch (Exception e) { | ||
| 73 | - log.error("[MQTT] 格式化响应失败", e); | ||
| 74 | - return new byte[0]; | ||
| 75 | - } | ||
| 76 | - } | ||
| 77 | - | ||
| 78 | - @Override | ||
| 79 | - public boolean canHandle(String topic) { | ||
| 80 | - // MQTT 协议支持更多主题格式 | ||
| 81 | - return topic != null && ( | ||
| 82 | - topic.startsWith("/sys/") || // 兼容现有系统主题 | ||
| 83 | - topic.startsWith("/mqtt/") || // 新的通用 MQTT 主题 | ||
| 84 | - topic.startsWith("/device/") // 设备主题 | ||
| 85 | - ); | ||
| 86 | - } | ||
| 87 | -} | ||
| 88 | \ No newline at end of file | 0 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-protocol/src/main/java/cn/iocoder/yudao/module/iot/protocol/util/IotHttpTopicUtils.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.protocol.util; | ||
| 2 | - | ||
| 3 | -import cn.hutool.core.util.StrUtil; | ||
| 4 | - | ||
| 5 | -/** | ||
| 6 | - * IoT HTTP 协议主题工具类 | ||
| 7 | - * <p> | ||
| 8 | - * 参考阿里云IoT平台HTTPS协议标准,支持以下路径格式: | ||
| 9 | - * 1. 设备认证:/auth | ||
| 10 | - * 2. 数据上报:/topic/${actualTopic} | ||
| 11 | - * <p> | ||
| 12 | - * 其中 actualTopic 遵循MQTT主题规范,例如: | ||
| 13 | - * - /sys/{productKey}/{deviceName}/thing/service/property/set | ||
| 14 | - * - /{productKey}/{deviceName}/user/get | ||
| 15 | - * | ||
| 16 | - * @author haohao | ||
| 17 | - */ | ||
| 18 | -public class IotHttpTopicUtils { | ||
| 19 | - | ||
| 20 | - /** | ||
| 21 | - * 设备认证路径 | ||
| 22 | - */ | ||
| 23 | - public static final String AUTH_PATH = "/auth"; | ||
| 24 | - | ||
| 25 | - /** | ||
| 26 | - * 数据上报路径前缀 | ||
| 27 | - */ | ||
| 28 | - public static final String TOPIC_PATH_PREFIX = "/topic"; | ||
| 29 | - | ||
| 30 | - /** | ||
| 31 | - * 系统主题前缀 | ||
| 32 | - */ | ||
| 33 | - public static final String SYS_TOPIC_PREFIX = "/sys"; | ||
| 34 | - | ||
| 35 | - /** | ||
| 36 | - * 构建设备认证路径 | ||
| 37 | - * | ||
| 38 | - * @return 认证路径 | ||
| 39 | - */ | ||
| 40 | - public static String buildAuthPath() { | ||
| 41 | - return AUTH_PATH; | ||
| 42 | - } | ||
| 43 | - | ||
| 44 | - /** | ||
| 45 | - * 构建数据上报路径 | ||
| 46 | - * | ||
| 47 | - * @param actualTopic 实际的MQTT主题 | ||
| 48 | - * @return HTTP数据上报路径 | ||
| 49 | - */ | ||
| 50 | - public static String buildTopicPath(String actualTopic) { | ||
| 51 | - if (StrUtil.isBlank(actualTopic)) { | ||
| 52 | - return null; | ||
| 53 | - } | ||
| 54 | - return TOPIC_PATH_PREFIX + actualTopic; | ||
| 55 | - } | ||
| 56 | - | ||
| 57 | - /** | ||
| 58 | - * 构建系统属性设置路径 | ||
| 59 | - * | ||
| 60 | - * @param productKey 产品Key | ||
| 61 | - * @param deviceName 设备名称 | ||
| 62 | - * @return HTTP路径 | ||
| 63 | - */ | ||
| 64 | - public static String buildPropertySetPath(String productKey, String deviceName) { | ||
| 65 | - if (StrUtil.hasBlank(productKey, deviceName)) { | ||
| 66 | - return null; | ||
| 67 | - } | ||
| 68 | - String actualTopic = SYS_TOPIC_PREFIX + "/" + productKey + "/" + deviceName + "/thing/service/property/set"; | ||
| 69 | - return buildTopicPath(actualTopic); | ||
| 70 | - } | ||
| 71 | - | ||
| 72 | - /** | ||
| 73 | - * 构建系统属性获取路径 | ||
| 74 | - * | ||
| 75 | - * @param productKey 产品Key | ||
| 76 | - * @param deviceName 设备名称 | ||
| 77 | - * @return HTTP路径 | ||
| 78 | - */ | ||
| 79 | - public static String buildPropertyGetPath(String productKey, String deviceName) { | ||
| 80 | - if (StrUtil.hasBlank(productKey, deviceName)) { | ||
| 81 | - return null; | ||
| 82 | - } | ||
| 83 | - String actualTopic = SYS_TOPIC_PREFIX + "/" + productKey + "/" + deviceName + "/thing/service/property/get"; | ||
| 84 | - return buildTopicPath(actualTopic); | ||
| 85 | - } | ||
| 86 | - | ||
| 87 | - /** | ||
| 88 | - * 构建系统属性上报路径 | ||
| 89 | - * | ||
| 90 | - * @param productKey 产品Key | ||
| 91 | - * @param deviceName 设备名称 | ||
| 92 | - * @return HTTP路径 | ||
| 93 | - */ | ||
| 94 | - public static String buildPropertyPostPath(String productKey, String deviceName) { | ||
| 95 | - if (StrUtil.hasBlank(productKey, deviceName)) { | ||
| 96 | - return null; | ||
| 97 | - } | ||
| 98 | - String actualTopic = SYS_TOPIC_PREFIX + "/" + productKey + "/" + deviceName + "/thing/event/property/post"; | ||
| 99 | - return buildTopicPath(actualTopic); | ||
| 100 | - } | ||
| 101 | - | ||
| 102 | - /** | ||
| 103 | - * 构建系统事件上报路径 | ||
| 104 | - * | ||
| 105 | - * @param productKey 产品Key | ||
| 106 | - * @param deviceName 设备名称 | ||
| 107 | - * @param eventIdentifier 事件标识符 | ||
| 108 | - * @return HTTP路径 | ||
| 109 | - */ | ||
| 110 | - public static String buildEventPostPath(String productKey, String deviceName, String eventIdentifier) { | ||
| 111 | - if (StrUtil.hasBlank(productKey, deviceName, eventIdentifier)) { | ||
| 112 | - return null; | ||
| 113 | - } | ||
| 114 | - String actualTopic = SYS_TOPIC_PREFIX + "/" + productKey + "/" + deviceName + "/thing/event/" + eventIdentifier | ||
| 115 | - + "/post"; | ||
| 116 | - return buildTopicPath(actualTopic); | ||
| 117 | - } | ||
| 118 | - | ||
| 119 | - /** | ||
| 120 | - * 构建系统服务调用路径 | ||
| 121 | - * | ||
| 122 | - * @param productKey 产品Key | ||
| 123 | - * @param deviceName 设备名称 | ||
| 124 | - * @param serviceIdentifier 服务标识符 | ||
| 125 | - * @return HTTP路径 | ||
| 126 | - */ | ||
| 127 | - public static String buildServiceInvokePath(String productKey, String deviceName, String serviceIdentifier) { | ||
| 128 | - if (StrUtil.hasBlank(productKey, deviceName, serviceIdentifier)) { | ||
| 129 | - return null; | ||
| 130 | - } | ||
| 131 | - String actualTopic = SYS_TOPIC_PREFIX + "/" + productKey + "/" + deviceName + "/thing/service/" | ||
| 132 | - + serviceIdentifier; | ||
| 133 | - return buildTopicPath(actualTopic); | ||
| 134 | - } | ||
| 135 | - | ||
| 136 | - /** | ||
| 137 | - * 构建自定义主题路径 | ||
| 138 | - * | ||
| 139 | - * @param productKey 产品Key | ||
| 140 | - * @param deviceName 设备名称 | ||
| 141 | - * @param customPath 自定义路径 | ||
| 142 | - * @return HTTP路径 | ||
| 143 | - */ | ||
| 144 | - public static String buildCustomTopicPath(String productKey, String deviceName, String customPath) { | ||
| 145 | - if (StrUtil.hasBlank(productKey, deviceName, customPath)) { | ||
| 146 | - return null; | ||
| 147 | - } | ||
| 148 | - String actualTopic = "/" + productKey + "/" + deviceName + "/" + customPath; | ||
| 149 | - return buildTopicPath(actualTopic); | ||
| 150 | - } | ||
| 151 | - | ||
| 152 | - /** | ||
| 153 | - * 从HTTP路径中提取实际主题 | ||
| 154 | - * | ||
| 155 | - * @param httpPath HTTP路径,格式:/topic/${actualTopic} | ||
| 156 | - * @return 实际主题,如果解析失败返回null | ||
| 157 | - */ | ||
| 158 | - public static String extractActualTopic(String httpPath) { | ||
| 159 | - if (StrUtil.isBlank(httpPath) || !httpPath.startsWith(TOPIC_PATH_PREFIX)) { | ||
| 160 | - return null; | ||
| 161 | - } | ||
| 162 | - return httpPath.substring(TOPIC_PATH_PREFIX.length()); // 直接移除/topic前缀 | ||
| 163 | - } | ||
| 164 | - | ||
| 165 | - /** | ||
| 166 | - * 从主题中解析产品Key | ||
| 167 | - * | ||
| 168 | - * @param topic 主题,支持系统主题和自定义主题 | ||
| 169 | - * @return 产品Key,如果无法解析则返回null | ||
| 170 | - */ | ||
| 171 | - public static String parseProductKeyFromTopic(String topic) { | ||
| 172 | - if (StrUtil.isBlank(topic)) { | ||
| 173 | - return null; | ||
| 174 | - } | ||
| 175 | - | ||
| 176 | - String[] parts = topic.split("/"); | ||
| 177 | - | ||
| 178 | - // 系统主题格式:/sys/{productKey}/{deviceName}/... | ||
| 179 | - if (parts.length >= 4 && "sys".equals(parts[1])) { | ||
| 180 | - return parts[2]; | ||
| 181 | - } | ||
| 182 | - | ||
| 183 | - // 自定义主题格式:/{productKey}/{deviceName}/... | ||
| 184 | - // 确保不是不完整的系统主题格式 | ||
| 185 | - if (parts.length >= 3 && StrUtil.isNotBlank(parts[1]) && !"sys".equals(parts[1])) { | ||
| 186 | - return parts[1]; | ||
| 187 | - } | ||
| 188 | - | ||
| 189 | - return null; | ||
| 190 | - } | ||
| 191 | - | ||
| 192 | - /** | ||
| 193 | - * 从主题中解析设备名称 | ||
| 194 | - * | ||
| 195 | - * @param topic 主题,支持系统主题和自定义主题 | ||
| 196 | - * @return 设备名称,如果无法解析则返回null | ||
| 197 | - */ | ||
| 198 | - public static String parseDeviceNameFromTopic(String topic) { | ||
| 199 | - if (StrUtil.isBlank(topic)) { | ||
| 200 | - return null; | ||
| 201 | - } | ||
| 202 | - | ||
| 203 | - String[] parts = topic.split("/"); | ||
| 204 | - | ||
| 205 | - // 系统主题格式:/sys/{productKey}/{deviceName}/... | ||
| 206 | - if (parts.length >= 4 && "sys".equals(parts[1])) { | ||
| 207 | - return parts[3]; | ||
| 208 | - } | ||
| 209 | - | ||
| 210 | - // 自定义主题格式:/{productKey}/{deviceName}/... | ||
| 211 | - // 确保不是不完整的系统主题格式 | ||
| 212 | - if (parts.length >= 3 && StrUtil.isNotBlank(parts[2]) && !"sys".equals(parts[1])) { | ||
| 213 | - return parts[2]; | ||
| 214 | - } | ||
| 215 | - | ||
| 216 | - return null; | ||
| 217 | - } | ||
| 218 | - | ||
| 219 | - /** | ||
| 220 | - * 检查是否为认证路径 | ||
| 221 | - * | ||
| 222 | - * @param path 路径 | ||
| 223 | - * @return 如果是认证路径返回true,否则返回false | ||
| 224 | - */ | ||
| 225 | - public static boolean isAuthPath(String path) { | ||
| 226 | - return AUTH_PATH.equals(path); | ||
| 227 | - } | ||
| 228 | - | ||
| 229 | - /** | ||
| 230 | - * 检查是否为数据上报路径 | ||
| 231 | - * | ||
| 232 | - * @param path 路径 | ||
| 233 | - * @return 如果是数据上报路径返回true,否则返回false | ||
| 234 | - */ | ||
| 235 | - public static boolean isTopicPath(String path) { | ||
| 236 | - return path != null && path.startsWith(TOPIC_PATH_PREFIX); | ||
| 237 | - } | ||
| 238 | - | ||
| 239 | - /** | ||
| 240 | - * 检查是否为有效的HTTP路径 | ||
| 241 | - * | ||
| 242 | - * @param path 路径 | ||
| 243 | - * @return 如果是有效的HTTP路径返回true,否则返回false | ||
| 244 | - */ | ||
| 245 | - public static boolean isValidHttpPath(String path) { | ||
| 246 | - return isAuthPath(path) || isTopicPath(path); | ||
| 247 | - } | ||
| 248 | - | ||
| 249 | - /** | ||
| 250 | - * 检查是否为系统主题 | ||
| 251 | - * | ||
| 252 | - * @param topic 主题 | ||
| 253 | - * @return 如果是系统主题返回true,否则返回false | ||
| 254 | - */ | ||
| 255 | - public static boolean isSystemTopic(String topic) { | ||
| 256 | - return topic != null && topic.startsWith(SYS_TOPIC_PREFIX); | ||
| 257 | - } | ||
| 258 | - | ||
| 259 | - /** | ||
| 260 | - * 构建响应主题路径 | ||
| 261 | - * | ||
| 262 | - * @param requestPath 请求路径 | ||
| 263 | - * @return 响应路径,如果无法构建返回null | ||
| 264 | - */ | ||
| 265 | - public static String buildReplyPath(String requestPath) { | ||
| 266 | - String actualTopic = extractActualTopic(requestPath); | ||
| 267 | - if (actualTopic == null) { | ||
| 268 | - return null; | ||
| 269 | - } | ||
| 270 | - | ||
| 271 | - // 为系统主题添加_reply后缀 | ||
| 272 | - if (isSystemTopic(actualTopic)) { | ||
| 273 | - String replyTopic = actualTopic + "_reply"; | ||
| 274 | - return buildTopicPath(replyTopic); | ||
| 275 | - } | ||
| 276 | - | ||
| 277 | - return null; | ||
| 278 | - } | ||
| 279 | -} | ||
| 280 | \ No newline at end of file | 0 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-protocol/src/main/java/cn/iocoder/yudao/module/iot/protocol/util/IotTopicParser.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.protocol.util; | ||
| 2 | - | ||
| 3 | -import cn.hutool.core.util.StrUtil; | ||
| 4 | -import cn.iocoder.yudao.module.iot.protocol.constants.IotTopicConstants; | ||
| 5 | -import cn.iocoder.yudao.module.iot.protocol.enums.IotMessageDirectionEnum; | ||
| 6 | -import cn.iocoder.yudao.module.iot.protocol.enums.IotMessageTypeEnum; | ||
| 7 | -import lombok.Data; | ||
| 8 | - | ||
| 9 | -/** | ||
| 10 | - * IoT 主题解析器 | ||
| 11 | - * <p> | ||
| 12 | - * 用于解析各种格式的 IoT 主题,提取其中的关键信息 | ||
| 13 | - * | ||
| 14 | - * @author haohao | ||
| 15 | - */ | ||
| 16 | -public class IotTopicParser { | ||
| 17 | - | ||
| 18 | - /** | ||
| 19 | - * 主题解析结果 | ||
| 20 | - */ | ||
| 21 | - @Data | ||
| 22 | - public static class TopicInfo { | ||
| 23 | - /** | ||
| 24 | - * 产品Key | ||
| 25 | - */ | ||
| 26 | - private String productKey; | ||
| 27 | - | ||
| 28 | - /** | ||
| 29 | - * 设备名称 | ||
| 30 | - */ | ||
| 31 | - private String deviceName; | ||
| 32 | - | ||
| 33 | - /** | ||
| 34 | - * 消息类型 | ||
| 35 | - */ | ||
| 36 | - private IotMessageTypeEnum messageType; | ||
| 37 | - | ||
| 38 | - /** | ||
| 39 | - * 消息方向 | ||
| 40 | - */ | ||
| 41 | - private IotMessageDirectionEnum direction; | ||
| 42 | - | ||
| 43 | - /** | ||
| 44 | - * 服务标识符(仅服务调用时有效) | ||
| 45 | - */ | ||
| 46 | - private String serviceIdentifier; | ||
| 47 | - | ||
| 48 | - /** | ||
| 49 | - * 事件标识符(仅事件上报时有效) | ||
| 50 | - */ | ||
| 51 | - private String eventIdentifier; | ||
| 52 | - | ||
| 53 | - /** | ||
| 54 | - * 是否为响应主题 | ||
| 55 | - */ | ||
| 56 | - private boolean isReply; | ||
| 57 | - | ||
| 58 | - /** | ||
| 59 | - * 原始主题 | ||
| 60 | - */ | ||
| 61 | - private String originalTopic; | ||
| 62 | - } | ||
| 63 | - | ||
| 64 | - /** | ||
| 65 | - * 解析主题 | ||
| 66 | - * | ||
| 67 | - * @param topic 主题字符串 | ||
| 68 | - * @return 解析结果,如果解析失败返回 null | ||
| 69 | - */ | ||
| 70 | - public static TopicInfo parse(String topic) { | ||
| 71 | - if (StrUtil.isBlank(topic)) { | ||
| 72 | - return null; | ||
| 73 | - } | ||
| 74 | - | ||
| 75 | - TopicInfo info = new TopicInfo(); | ||
| 76 | - info.setOriginalTopic(topic); | ||
| 77 | - | ||
| 78 | - // 检查是否为响应主题 | ||
| 79 | - boolean isReply = topic.endsWith(IotTopicConstants.REPLY_SUFFIX); | ||
| 80 | - info.setReply(isReply); | ||
| 81 | - | ||
| 82 | - // 移除响应后缀,便于后续解析 | ||
| 83 | - String normalizedTopic = isReply ? topic.substring(0, topic.length() - IotTopicConstants.REPLY_SUFFIX.length()) | ||
| 84 | - : topic; | ||
| 85 | - | ||
| 86 | - // 解析系统主题 | ||
| 87 | - if (normalizedTopic.startsWith(IotTopicConstants.SYS_TOPIC_PREFIX)) { | ||
| 88 | - return parseSystemTopic(info, normalizedTopic); | ||
| 89 | - } | ||
| 90 | - | ||
| 91 | - // 解析自定义主题 | ||
| 92 | - return parseCustomTopic(info, normalizedTopic); | ||
| 93 | - } | ||
| 94 | - | ||
| 95 | - /** | ||
| 96 | - * 解析系统主题 | ||
| 97 | - * 格式:/sys/{productKey}/{deviceName}/thing/service/{identifier} | ||
| 98 | - * 或:/sys/{productKey}/{deviceName}/thing/event/{identifier}/post | ||
| 99 | - */ | ||
| 100 | - private static TopicInfo parseSystemTopic(TopicInfo info, String topic) { | ||
| 101 | - String[] parts = topic.split("/"); | ||
| 102 | - if (parts.length < 6) { | ||
| 103 | - return null; | ||
| 104 | - } | ||
| 105 | - | ||
| 106 | - // 解析产品Key和设备名称 | ||
| 107 | - info.setProductKey(parts[2]); | ||
| 108 | - info.setDeviceName(parts[3]); | ||
| 109 | - | ||
| 110 | - // 判断消息方向:包含 /post 通常是上行,其他是下行 | ||
| 111 | - info.setDirection(topic.contains("/post") || topic.contains("/reply") ? IotMessageDirectionEnum.UPSTREAM | ||
| 112 | - : IotMessageDirectionEnum.DOWNSTREAM); | ||
| 113 | - | ||
| 114 | - // 解析具体的消息类型 | ||
| 115 | - if (topic.contains("/thing/service/")) { | ||
| 116 | - return parseServiceTopic(info, topic, parts); | ||
| 117 | - } else if (topic.contains("/thing/event/")) { | ||
| 118 | - return parseEventTopic(info, topic, parts); | ||
| 119 | - } | ||
| 120 | - | ||
| 121 | - return null; | ||
| 122 | - } | ||
| 123 | - | ||
| 124 | - /** | ||
| 125 | - * 解析服务相关主题 | ||
| 126 | - */ | ||
| 127 | - private static TopicInfo parseServiceTopic(TopicInfo info, String topic, String[] parts) { | ||
| 128 | - // 查找 service 关键字的位置 | ||
| 129 | - int serviceIndex = -1; | ||
| 130 | - for (int i = 0; i < parts.length; i++) { | ||
| 131 | - if ("service".equals(parts[i])) { | ||
| 132 | - serviceIndex = i; | ||
| 133 | - break; | ||
| 134 | - } | ||
| 135 | - } | ||
| 136 | - | ||
| 137 | - if (serviceIndex == -1 || serviceIndex + 1 >= parts.length) { | ||
| 138 | - return null; | ||
| 139 | - } | ||
| 140 | - | ||
| 141 | - String serviceType = parts[serviceIndex + 1]; | ||
| 142 | - | ||
| 143 | - // 根据服务类型确定消息类型 | ||
| 144 | - switch (serviceType) { | ||
| 145 | - case "property": | ||
| 146 | - if (serviceIndex + 2 < parts.length) { | ||
| 147 | - String operation = parts[serviceIndex + 2]; | ||
| 148 | - if ("set".equals(operation)) { | ||
| 149 | - info.setMessageType(IotMessageTypeEnum.PROPERTY_SET); | ||
| 150 | - } else if ("get".equals(operation)) { | ||
| 151 | - info.setMessageType(IotMessageTypeEnum.PROPERTY_GET); | ||
| 152 | - } | ||
| 153 | - } | ||
| 154 | - break; | ||
| 155 | - case "config": | ||
| 156 | - if (serviceIndex + 2 < parts.length && "set".equals(parts[serviceIndex + 2])) { | ||
| 157 | - info.setMessageType(IotMessageTypeEnum.CONFIG_SET); | ||
| 158 | - } | ||
| 159 | - break; | ||
| 160 | - case "ota": | ||
| 161 | - if (serviceIndex + 2 < parts.length && "upgrade".equals(parts[serviceIndex + 2])) { | ||
| 162 | - info.setMessageType(IotMessageTypeEnum.OTA_UPGRADE); | ||
| 163 | - } | ||
| 164 | - break; | ||
| 165 | - default: | ||
| 166 | - // 自定义服务 | ||
| 167 | - info.setMessageType(IotMessageTypeEnum.SERVICE_INVOKE); | ||
| 168 | - info.setServiceIdentifier(serviceType); | ||
| 169 | - break; | ||
| 170 | - } | ||
| 171 | - | ||
| 172 | - return info; | ||
| 173 | - } | ||
| 174 | - | ||
| 175 | - /** | ||
| 176 | - * 解析事件相关主题 | ||
| 177 | - */ | ||
| 178 | - private static TopicInfo parseEventTopic(TopicInfo info, String topic, String[] parts) { | ||
| 179 | - // 查找 event 关键字的位置 | ||
| 180 | - int eventIndex = -1; | ||
| 181 | - for (int i = 0; i < parts.length; i++) { | ||
| 182 | - if ("event".equals(parts[i])) { | ||
| 183 | - eventIndex = i; | ||
| 184 | - break; | ||
| 185 | - } | ||
| 186 | - } | ||
| 187 | - | ||
| 188 | - if (eventIndex == -1 || eventIndex + 1 >= parts.length) { | ||
| 189 | - return null; | ||
| 190 | - } | ||
| 191 | - | ||
| 192 | - String eventType = parts[eventIndex + 1]; | ||
| 193 | - | ||
| 194 | - if ("property".equals(eventType) && eventIndex + 2 < parts.length && "post".equals(parts[eventIndex + 2])) { | ||
| 195 | - info.setMessageType(IotMessageTypeEnum.PROPERTY_POST); | ||
| 196 | - } else { | ||
| 197 | - // 自定义事件 | ||
| 198 | - info.setMessageType(IotMessageTypeEnum.EVENT_POST); | ||
| 199 | - info.setEventIdentifier(eventType); | ||
| 200 | - } | ||
| 201 | - | ||
| 202 | - return info; | ||
| 203 | - } | ||
| 204 | - | ||
| 205 | - /** | ||
| 206 | - * 解析自定义主题 | ||
| 207 | - * 这里可以根据实际需求扩展自定义主题的解析逻辑 | ||
| 208 | - */ | ||
| 209 | - private static TopicInfo parseCustomTopic(TopicInfo info, String topic) { | ||
| 210 | - // TODO: 根据业务需要实现自定义主题解析逻辑 | ||
| 211 | - return info; | ||
| 212 | - } | ||
| 213 | - | ||
| 214 | - /** | ||
| 215 | - * 检查主题是否为有效的系统主题 | ||
| 216 | - * | ||
| 217 | - * @param topic 主题 | ||
| 218 | - * @return 如果是有效的系统主题返回 true,否则返回 false | ||
| 219 | - */ | ||
| 220 | - public static boolean isValidSystemTopic(String topic) { | ||
| 221 | - TopicInfo info = parse(topic); | ||
| 222 | - return info != null && | ||
| 223 | - StrUtil.isNotBlank(info.getProductKey()) && | ||
| 224 | - StrUtil.isNotBlank(info.getDeviceName()) && | ||
| 225 | - info.getMessageType() != null; | ||
| 226 | - } | ||
| 227 | - | ||
| 228 | - /** | ||
| 229 | - * 检查主题是否为响应主题 | ||
| 230 | - * | ||
| 231 | - * @param topic 主题 | ||
| 232 | - * @return 如果是响应主题返回 true,否则返回 false | ||
| 233 | - */ | ||
| 234 | - public static boolean isReplyTopic(String topic) { | ||
| 235 | - return topic != null && topic.endsWith(IotTopicConstants.REPLY_SUFFIX); | ||
| 236 | - } | ||
| 237 | -} | ||
| 238 | \ No newline at end of file | 0 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-protocol/src/main/java/cn/iocoder/yudao/module/iot/protocol/util/IotTopicUtils.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.protocol.util; | ||
| 2 | - | ||
| 3 | -import cn.hutool.core.util.StrUtil; | ||
| 4 | -import cn.iocoder.yudao.module.iot.protocol.constants.IotTopicConstants; | ||
| 5 | - | ||
| 6 | -/** | ||
| 7 | - * IoT 主题工具类 | ||
| 8 | - * <p> | ||
| 9 | - * 用于构建和解析设备主题 | ||
| 10 | - * | ||
| 11 | - * @author haohao | ||
| 12 | - */ | ||
| 13 | -public class IotTopicUtils { | ||
| 14 | - | ||
| 15 | - /** | ||
| 16 | - * 构建设备服务调用主题 | ||
| 17 | - * | ||
| 18 | - * @param productKey 产品Key | ||
| 19 | - * @param deviceName 设备名称 | ||
| 20 | - * @param serviceIdentifier 服务标识符 | ||
| 21 | - * @return 完整的主题路径 | ||
| 22 | - */ | ||
| 23 | - public static String buildServiceTopic(String productKey, String deviceName, String serviceIdentifier) { | ||
| 24 | - return buildDeviceBaseTopic(productKey, deviceName) + | ||
| 25 | - IotTopicConstants.SERVICE_TOPIC_PREFIX + serviceIdentifier; | ||
| 26 | - } | ||
| 27 | - | ||
| 28 | - /** | ||
| 29 | - * 构建设备属性设置主题 | ||
| 30 | - * | ||
| 31 | - * @param productKey 产品Key | ||
| 32 | - * @param deviceName 设备名称 | ||
| 33 | - * @return 完整的主题路径 | ||
| 34 | - */ | ||
| 35 | - public static String buildPropertySetTopic(String productKey, String deviceName) { | ||
| 36 | - return buildDeviceBaseTopic(productKey, deviceName) + IotTopicConstants.PROPERTY_SET_TOPIC; | ||
| 37 | - } | ||
| 38 | - | ||
| 39 | - /** | ||
| 40 | - * 构建设备属性获取主题 | ||
| 41 | - * | ||
| 42 | - * @param productKey 产品Key | ||
| 43 | - * @param deviceName 设备名称 | ||
| 44 | - * @return 完整的主题路径 | ||
| 45 | - */ | ||
| 46 | - public static String buildPropertyGetTopic(String productKey, String deviceName) { | ||
| 47 | - return buildDeviceBaseTopic(productKey, deviceName) + IotTopicConstants.PROPERTY_GET_TOPIC; | ||
| 48 | - } | ||
| 49 | - | ||
| 50 | - /** | ||
| 51 | - * 构建设备配置设置主题 | ||
| 52 | - * | ||
| 53 | - * @param productKey 产品Key | ||
| 54 | - * @param deviceName 设备名称 | ||
| 55 | - * @return 完整的主题路径 | ||
| 56 | - */ | ||
| 57 | - public static String buildConfigSetTopic(String productKey, String deviceName) { | ||
| 58 | - return buildDeviceBaseTopic(productKey, deviceName) + IotTopicConstants.CONFIG_SET_TOPIC; | ||
| 59 | - } | ||
| 60 | - | ||
| 61 | - /** | ||
| 62 | - * 构建设备 OTA 升级主题 | ||
| 63 | - * | ||
| 64 | - * @param productKey 产品Key | ||
| 65 | - * @param deviceName 设备名称 | ||
| 66 | - * @return 完整的主题路径 | ||
| 67 | - */ | ||
| 68 | - public static String buildOtaUpgradeTopic(String productKey, String deviceName) { | ||
| 69 | - return buildDeviceBaseTopic(productKey, deviceName) + IotTopicConstants.OTA_UPGRADE_TOPIC; | ||
| 70 | - } | ||
| 71 | - | ||
| 72 | - /** | ||
| 73 | - * 构建设备属性上报主题 | ||
| 74 | - * | ||
| 75 | - * @param productKey 产品Key | ||
| 76 | - * @param deviceName 设备名称 | ||
| 77 | - * @return 完整的主题路径 | ||
| 78 | - */ | ||
| 79 | - public static String buildPropertyPostTopic(String productKey, String deviceName) { | ||
| 80 | - return buildDeviceBaseTopic(productKey, deviceName) + IotTopicConstants.PROPERTY_POST_TOPIC; | ||
| 81 | - } | ||
| 82 | - | ||
| 83 | - /** | ||
| 84 | - * 构建设备事件上报主题 | ||
| 85 | - * | ||
| 86 | - * @param productKey 产品Key | ||
| 87 | - * @param deviceName 设备名称 | ||
| 88 | - * @param eventIdentifier 事件标识符 | ||
| 89 | - * @return 完整的主题路径 | ||
| 90 | - */ | ||
| 91 | - public static String buildEventPostTopic(String productKey, String deviceName, String eventIdentifier) { | ||
| 92 | - return buildDeviceBaseTopic(productKey, deviceName) + | ||
| 93 | - IotTopicConstants.EVENT_POST_TOPIC_PREFIX + eventIdentifier + IotTopicConstants.EVENT_POST_TOPIC_SUFFIX; | ||
| 94 | - } | ||
| 95 | - | ||
| 96 | - /** | ||
| 97 | - * 获取响应主题 | ||
| 98 | - * | ||
| 99 | - * @param requestTopic 请求主题 | ||
| 100 | - * @return 响应主题 | ||
| 101 | - */ | ||
| 102 | - public static String getReplyTopic(String requestTopic) { | ||
| 103 | - return requestTopic + IotTopicConstants.REPLY_SUFFIX; | ||
| 104 | - } | ||
| 105 | - | ||
| 106 | - /** | ||
| 107 | - * 构建设备基础主题 | ||
| 108 | - * 格式: /sys/${productKey}/${deviceName} | ||
| 109 | - * | ||
| 110 | - * @param productKey 产品Key | ||
| 111 | - * @param deviceName 设备名称 | ||
| 112 | - * @return 设备基础主题 | ||
| 113 | - */ | ||
| 114 | - public static String buildDeviceBaseTopic(String productKey, String deviceName) { | ||
| 115 | - return IotTopicConstants.SYS_TOPIC_PREFIX + productKey + "/" + deviceName; | ||
| 116 | - } | ||
| 117 | - | ||
| 118 | - /** | ||
| 119 | - * 从主题中解析产品Key | ||
| 120 | - * 格式: /sys/${productKey}/${deviceName}/... | ||
| 121 | - * | ||
| 122 | - * @param topic 主题 | ||
| 123 | - * @return 产品Key,如果无法解析则返回null | ||
| 124 | - */ | ||
| 125 | - public static String parseProductKeyFromTopic(String topic) { | ||
| 126 | - if (StrUtil.isBlank(topic) || !topic.startsWith(IotTopicConstants.SYS_TOPIC_PREFIX)) { | ||
| 127 | - return null; | ||
| 128 | - } | ||
| 129 | - | ||
| 130 | - String[] parts = topic.split("/"); | ||
| 131 | - if (parts.length < 4) { | ||
| 132 | - return null; | ||
| 133 | - } | ||
| 134 | - | ||
| 135 | - return parts[2]; | ||
| 136 | - } | ||
| 137 | - | ||
| 138 | - /** | ||
| 139 | - * 从主题中解析设备名称 | ||
| 140 | - * 格式: /sys/${productKey}/${deviceName}/... | ||
| 141 | - * | ||
| 142 | - * @param topic 主题 | ||
| 143 | - * @return 设备名称,如果无法解析则返回null | ||
| 144 | - */ | ||
| 145 | - public static String parseDeviceNameFromTopic(String topic) { | ||
| 146 | - if (StrUtil.isBlank(topic) || !topic.startsWith(IotTopicConstants.SYS_TOPIC_PREFIX)) { | ||
| 147 | - return null; | ||
| 148 | - } | ||
| 149 | - | ||
| 150 | - String[] parts = topic.split("/"); | ||
| 151 | - if (parts.length < 4) { | ||
| 152 | - return null; | ||
| 153 | - } | ||
| 154 | - | ||
| 155 | - return parts[3]; | ||
| 156 | - } | ||
| 157 | - | ||
| 158 | - /** | ||
| 159 | - * 从主题中解析方法名 | ||
| 160 | - * 例如:从 /sys/pk/dn/thing/service/property/set 解析出 property.set | ||
| 161 | - * | ||
| 162 | - * @param topic 主题 | ||
| 163 | - * @return 方法名,如果无法解析则返回null | ||
| 164 | - */ | ||
| 165 | - public static String parseMethodFromTopic(String topic) { | ||
| 166 | - if (StrUtil.isBlank(topic) || !topic.startsWith(IotTopicConstants.SYS_TOPIC_PREFIX)) { | ||
| 167 | - return null; | ||
| 168 | - } | ||
| 169 | - | ||
| 170 | - // 服务调用主题 | ||
| 171 | - if (topic.contains("/thing/service/")) { | ||
| 172 | - String servicePart = topic.substring(topic.indexOf("/thing/service/") + "/thing/service/".length()); | ||
| 173 | - return servicePart.replace("/", "."); | ||
| 174 | - } | ||
| 175 | - | ||
| 176 | - // 事件上报主题 | ||
| 177 | - if (topic.contains("/thing/event/")) { | ||
| 178 | - String eventPart = topic.substring(topic.indexOf("/thing/event/") + "/thing/event/".length()); | ||
| 179 | - return "event." + eventPart.replace("/", "."); | ||
| 180 | - } | ||
| 181 | - | ||
| 182 | - return null; | ||
| 183 | - } | ||
| 184 | -} | ||
| 185 | \ No newline at end of file | 0 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-protocol/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports deleted
yudao-module-iot/yudao-module-iot-protocol/src/test/java/cn/iocoder/yudao/module/iot/protocol/config/IotProtocolAutoConfigurationTest.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.protocol.config; | ||
| 2 | - | ||
| 3 | -import cn.iocoder.yudao.module.iot.protocol.convert.IotProtocolConverter; | ||
| 4 | -import cn.iocoder.yudao.module.iot.protocol.enums.IotProtocolTypeEnum; | ||
| 5 | -import cn.iocoder.yudao.module.iot.protocol.message.IotMessageParser; | ||
| 6 | -import cn.iocoder.yudao.module.iot.protocol.message.impl.IotHttpMessageParser; | ||
| 7 | -import cn.iocoder.yudao.module.iot.protocol.message.impl.IotMqttMessageParser; | ||
| 8 | -import org.junit.jupiter.api.BeforeEach; | ||
| 9 | -import org.junit.jupiter.api.Test; | ||
| 10 | - | ||
| 11 | -import static org.junit.jupiter.api.Assertions.*; | ||
| 12 | - | ||
| 13 | -/** | ||
| 14 | - * {@link IotProtocolAutoConfiguration} 单元测试 | ||
| 15 | - * | ||
| 16 | - * @author haohao | ||
| 17 | - */ | ||
| 18 | -class IotProtocolAutoConfigurationTest { | ||
| 19 | - | ||
| 20 | - private IotProtocolAutoConfiguration configuration; | ||
| 21 | - | ||
| 22 | - @BeforeEach | ||
| 23 | - void setUp() { | ||
| 24 | - configuration = new IotProtocolAutoConfiguration(); | ||
| 25 | - } | ||
| 26 | - | ||
| 27 | - @Test | ||
| 28 | - void testIotMqttMessageParser() { | ||
| 29 | - // 测试 MQTT 协议解析器 Bean 创建 | ||
| 30 | - IotMessageParser parser = configuration.iotMqttMessageParser(); | ||
| 31 | - | ||
| 32 | - assertNotNull(parser); | ||
| 33 | - assertInstanceOf(IotMqttMessageParser.class, parser); | ||
| 34 | - } | ||
| 35 | - | ||
| 36 | - @Test | ||
| 37 | - void testIotHttpMessageParser() { | ||
| 38 | - // 测试 HTTP 协议解析器 Bean 创建 | ||
| 39 | - IotMessageParser parser = configuration.iotHttpMessageParser(); | ||
| 40 | - | ||
| 41 | - assertNotNull(parser); | ||
| 42 | - assertInstanceOf(IotHttpMessageParser.class, parser); | ||
| 43 | - } | ||
| 44 | - | ||
| 45 | - @Test | ||
| 46 | - void testIotProtocolConverter() { | ||
| 47 | - // 创建解析器实例 | ||
| 48 | - IotMessageParser mqttParser = configuration.iotMqttMessageParser(); | ||
| 49 | - IotMessageParser httpParser = configuration.iotHttpMessageParser(); | ||
| 50 | - | ||
| 51 | - // 测试协议转换器 Bean 创建 | ||
| 52 | - IotProtocolConverter converter = configuration.iotProtocolConverter(mqttParser, httpParser); | ||
| 53 | - | ||
| 54 | - assertNotNull(converter); | ||
| 55 | - | ||
| 56 | - // 验证支持的协议 | ||
| 57 | - assertTrue(converter.supportsProtocol(IotProtocolTypeEnum.MQTT.getCode())); | ||
| 58 | - assertTrue(converter.supportsProtocol(IotProtocolTypeEnum.HTTP.getCode())); | ||
| 59 | - | ||
| 60 | - // 验证支持的协议数量 | ||
| 61 | - String[] supportedProtocols = converter.getSupportedProtocols(); | ||
| 62 | - assertEquals(2, supportedProtocols.length); | ||
| 63 | - } | ||
| 64 | - | ||
| 65 | - @Test | ||
| 66 | - void testBeanNameConstants() { | ||
| 67 | - // 测试 Bean 名称常量定义 | ||
| 68 | - assertEquals("iotMqttMessageParser", IotProtocolAutoConfiguration.IOT_MQTT_MESSAGE_PARSER_BEAN_NAME); | ||
| 69 | - assertEquals("iotHttpMessageParser", IotProtocolAutoConfiguration.IOT_HTTP_MESSAGE_PARSER_BEAN_NAME); | ||
| 70 | - } | ||
| 71 | -} | ||
| 72 | \ No newline at end of file | 0 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-protocol/src/test/java/cn/iocoder/yudao/module/iot/protocol/example/AliyunHttpProtocolExample.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.protocol.example; | ||
| 2 | - | ||
| 3 | -import cn.hutool.json.JSONObject; | ||
| 4 | -import cn.iocoder.yudao.module.iot.protocol.util.IotHttpTopicUtils; | ||
| 5 | - | ||
| 6 | -/** | ||
| 7 | - * 阿里云IoT平台HTTPS协议示例 | ||
| 8 | - * <p> | ||
| 9 | - * 参考阿里云IoT平台HTTPS连接通信标准,演示设备认证和数据上报的完整流程 | ||
| 10 | - * | ||
| 11 | - * @author haohao | ||
| 12 | - */ | ||
| 13 | -public class AliyunHttpProtocolExample { | ||
| 14 | - | ||
| 15 | - public static void main(String[] args) { | ||
| 16 | - System.out.println("=== 阿里云IoT平台HTTPS协议演示 ===\n"); | ||
| 17 | - | ||
| 18 | - // 演示设备认证 | ||
| 19 | - demonstrateDeviceAuth(); | ||
| 20 | - | ||
| 21 | - // 演示数据上报 | ||
| 22 | - demonstrateDataUpload(); | ||
| 23 | - | ||
| 24 | - // 演示路径构建 | ||
| 25 | - demonstratePathBuilding(); | ||
| 26 | - } | ||
| 27 | - | ||
| 28 | - /** | ||
| 29 | - * 演示设备认证流程 | ||
| 30 | - */ | ||
| 31 | - private static void demonstrateDeviceAuth() { | ||
| 32 | - System.out.println("1. 设备认证流程:"); | ||
| 33 | - System.out.println("认证路径: " + IotHttpTopicUtils.buildAuthPath()); | ||
| 34 | - | ||
| 35 | - // 构建认证请求消息 | ||
| 36 | - JSONObject authRequest = new JSONObject(); | ||
| 37 | - authRequest.set("productKey", "a1GFjLP****"); | ||
| 38 | - authRequest.set("deviceName", "device123"); | ||
| 39 | - authRequest.set("clientId", "device123_001"); | ||
| 40 | - authRequest.set("timestamp", String.valueOf(System.currentTimeMillis())); | ||
| 41 | - authRequest.set("sign", "4870141D4067227128CBB4377906C3731CAC221C"); | ||
| 42 | - authRequest.set("signmethod", "hmacsha1"); | ||
| 43 | - authRequest.set("version", "default"); | ||
| 44 | - | ||
| 45 | - System.out.println("认证请求消息:"); | ||
| 46 | - System.out.println(authRequest.toString()); | ||
| 47 | - | ||
| 48 | - // 模拟认证响应 | ||
| 49 | - JSONObject authResponse = new JSONObject(); | ||
| 50 | - authResponse.set("code", 0); | ||
| 51 | - authResponse.set("message", "success"); | ||
| 52 | - | ||
| 53 | - JSONObject info = new JSONObject(); | ||
| 54 | - info.set("token", "6944e5bfb92e4d4ea3918d1eda39****"); | ||
| 55 | - authResponse.set("info", info); | ||
| 56 | - | ||
| 57 | - System.out.println("认证响应:"); | ||
| 58 | - System.out.println(authResponse.toString()); | ||
| 59 | - System.out.println(); | ||
| 60 | - } | ||
| 61 | - | ||
| 62 | - /** | ||
| 63 | - * 演示数据上报流程 | ||
| 64 | - */ | ||
| 65 | - private static void demonstrateDataUpload() { | ||
| 66 | - System.out.println("2. 数据上报流程:"); | ||
| 67 | - | ||
| 68 | - String productKey = "a1GFjLP****"; | ||
| 69 | - String deviceName = "device123"; | ||
| 70 | - | ||
| 71 | - // 属性上报 | ||
| 72 | - String propertyPostPath = IotHttpTopicUtils.buildPropertyPostPath(productKey, deviceName); | ||
| 73 | - System.out.println("属性上报路径: " + propertyPostPath); | ||
| 74 | - | ||
| 75 | - // Alink格式的属性上报消息 | ||
| 76 | - JSONObject propertyMessage = new JSONObject(); | ||
| 77 | - propertyMessage.set("id", "123456"); | ||
| 78 | - propertyMessage.set("version", "1.0"); | ||
| 79 | - propertyMessage.set("method", "thing.event.property.post"); | ||
| 80 | - | ||
| 81 | - JSONObject propertyParams = new JSONObject(); | ||
| 82 | - JSONObject properties = new JSONObject(); | ||
| 83 | - properties.set("temperature", 25.6); | ||
| 84 | - properties.set("humidity", 60.3); | ||
| 85 | - propertyParams.set("properties", properties); | ||
| 86 | - propertyMessage.set("params", propertyParams); | ||
| 87 | - | ||
| 88 | - System.out.println("属性上报消息:"); | ||
| 89 | - System.out.println(propertyMessage.toString()); | ||
| 90 | - | ||
| 91 | - // 事件上报 | ||
| 92 | - String eventPostPath = IotHttpTopicUtils.buildEventPostPath(productKey, deviceName, "temperatureAlert"); | ||
| 93 | - System.out.println("\n事件上报路径: " + eventPostPath); | ||
| 94 | - | ||
| 95 | - JSONObject eventMessage = new JSONObject(); | ||
| 96 | - eventMessage.set("id", "123457"); | ||
| 97 | - eventMessage.set("version", "1.0"); | ||
| 98 | - eventMessage.set("method", "thing.event.temperatureAlert.post"); | ||
| 99 | - | ||
| 100 | - JSONObject eventParams = new JSONObject(); | ||
| 101 | - eventParams.set("value", new JSONObject().set("alertLevel", "high").set("currentTemp", 45.2)); | ||
| 102 | - eventParams.set("time", System.currentTimeMillis()); | ||
| 103 | - eventMessage.set("params", eventParams); | ||
| 104 | - | ||
| 105 | - System.out.println("事件上报消息:"); | ||
| 106 | - System.out.println(eventMessage.toString()); | ||
| 107 | - | ||
| 108 | - // 模拟数据上报响应 | ||
| 109 | - JSONObject uploadResponse = new JSONObject(); | ||
| 110 | - uploadResponse.set("code", 0); | ||
| 111 | - uploadResponse.set("message", "success"); | ||
| 112 | - | ||
| 113 | - JSONObject responseInfo = new JSONObject(); | ||
| 114 | - responseInfo.set("messageId", 892687470447040L); | ||
| 115 | - uploadResponse.set("info", responseInfo); | ||
| 116 | - | ||
| 117 | - System.out.println("\n数据上报响应:"); | ||
| 118 | - System.out.println(uploadResponse.toString()); | ||
| 119 | - System.out.println(); | ||
| 120 | - } | ||
| 121 | - | ||
| 122 | - /** | ||
| 123 | - * 演示路径构建功能 | ||
| 124 | - */ | ||
| 125 | - private static void demonstratePathBuilding() { | ||
| 126 | - System.out.println("3. 路径构建功能:"); | ||
| 127 | - | ||
| 128 | - String productKey = "smartProduct"; | ||
| 129 | - String deviceName = "sensor001"; | ||
| 130 | - | ||
| 131 | - // 系统主题路径 | ||
| 132 | - System.out.println("系统主题路径:"); | ||
| 133 | - System.out.println(" 属性设置: " + IotHttpTopicUtils.buildPropertySetPath(productKey, deviceName)); | ||
| 134 | - System.out.println(" 属性获取: " + IotHttpTopicUtils.buildPropertyGetPath(productKey, deviceName)); | ||
| 135 | - System.out.println(" 属性上报: " + IotHttpTopicUtils.buildPropertyPostPath(productKey, deviceName)); | ||
| 136 | - System.out.println(" 事件上报: " + IotHttpTopicUtils.buildEventPostPath(productKey, deviceName, "alarm")); | ||
| 137 | - System.out.println(" 服务调用: " + IotHttpTopicUtils.buildServiceInvokePath(productKey, deviceName, "reboot")); | ||
| 138 | - | ||
| 139 | - // 自定义主题路径 | ||
| 140 | - System.out.println("\n自定义主题路径:"); | ||
| 141 | - System.out.println(" 用户主题: " + IotHttpTopicUtils.buildCustomTopicPath(productKey, deviceName, "user/get")); | ||
| 142 | - | ||
| 143 | - // 响应路径 | ||
| 144 | - String requestPath = IotHttpTopicUtils.buildPropertySetPath(productKey, deviceName); | ||
| 145 | - String replyPath = IotHttpTopicUtils.buildReplyPath(requestPath); | ||
| 146 | - System.out.println("\n响应路径:"); | ||
| 147 | - System.out.println(" 请求路径: " + requestPath); | ||
| 148 | - System.out.println(" 响应路径: " + replyPath); | ||
| 149 | - | ||
| 150 | - // 路径解析 | ||
| 151 | - System.out.println("\n路径解析:"); | ||
| 152 | - String testPath = "/topic/sys/smartProduct/sensor001/thing/service/property/set"; | ||
| 153 | - String actualTopic = IotHttpTopicUtils.extractActualTopic(testPath); | ||
| 154 | - System.out.println(" HTTP路径: " + testPath); | ||
| 155 | - System.out.println(" 实际主题: " + actualTopic); | ||
| 156 | - System.out.println(" 产品Key: " + IotHttpTopicUtils.parseProductKeyFromTopic(actualTopic)); | ||
| 157 | - System.out.println(" 设备名称: " + IotHttpTopicUtils.parseDeviceNameFromTopic(actualTopic)); | ||
| 158 | - System.out.println(" 是否为系统主题: " + IotHttpTopicUtils.isSystemTopic(actualTopic)); | ||
| 159 | - | ||
| 160 | - // 路径类型检查 | ||
| 161 | - System.out.println("\n路径类型检查:"); | ||
| 162 | - System.out.println(" 认证路径检查: " + IotHttpTopicUtils.isAuthPath("/auth")); | ||
| 163 | - System.out.println(" 数据路径检查: " + IotHttpTopicUtils.isTopicPath("/topic/test")); | ||
| 164 | - System.out.println(" 有效路径检查: " + IotHttpTopicUtils.isValidHttpPath("/topic/sys/test/device/property")); | ||
| 165 | - } | ||
| 166 | -} | ||
| 167 | \ No newline at end of file | 0 | \ 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 deleted
| 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 | - * {@link IotHttpMessageParser} 单元测试 | ||
| 17 | - * <p> | ||
| 18 | - * 测试阿里云IoT平台HTTPS协议标准的消息解析功能 | ||
| 19 | - * | ||
| 20 | - * @author haohao | ||
| 21 | - */ | ||
| 22 | -class IotHttpMessageParserTest { | ||
| 23 | - | ||
| 24 | - private IotHttpMessageParser parser; | ||
| 25 | - | ||
| 26 | - @BeforeEach | ||
| 27 | - void setUp() { | ||
| 28 | - parser = new IotHttpMessageParser(); | ||
| 29 | - } | ||
| 30 | - | ||
| 31 | - @Test | ||
| 32 | - void testCanHandle() { | ||
| 33 | - // 测试能处理的路径 | ||
| 34 | - assertTrue(parser.canHandle("/auth")); | ||
| 35 | - assertTrue(parser.canHandle("/topic/sys/test/device1/thing/service/property/set")); | ||
| 36 | - assertTrue(parser.canHandle("/topic/test/device1/user/get")); | ||
| 37 | - | ||
| 38 | - // 测试不能处理的路径 | ||
| 39 | - assertFalse(parser.canHandle("/sys/test/device1/thing/service/property/set")); | ||
| 40 | - assertFalse(parser.canHandle("/unknown/path")); | ||
| 41 | - assertFalse(parser.canHandle(null)); | ||
| 42 | - assertFalse(parser.canHandle("")); | ||
| 43 | - } | ||
| 44 | - | ||
| 45 | - @Test | ||
| 46 | - void testParseAuthMessage() { | ||
| 47 | - // 构建认证消息 | ||
| 48 | - JSONObject authMessage = new JSONObject(); | ||
| 49 | - authMessage.set("productKey", "a1GFjLP****"); | ||
| 50 | - authMessage.set("deviceName", "device123"); | ||
| 51 | - authMessage.set("clientId", "device123_001"); | ||
| 52 | - authMessage.set("timestamp", "1501668289957"); | ||
| 53 | - authMessage.set("sign", "4870141D4067227128CBB4377906C3731CAC221C"); | ||
| 54 | - authMessage.set("signmethod", "hmacsha1"); | ||
| 55 | - authMessage.set("version", "default"); | ||
| 56 | - | ||
| 57 | - String topic = "/auth"; | ||
| 58 | - byte[] payload = authMessage.toString().getBytes(StandardCharsets.UTF_8); | ||
| 59 | - | ||
| 60 | - // 解析消息 | ||
| 61 | - IotMqttMessage result = parser.parse(topic, payload); | ||
| 62 | - | ||
| 63 | - // 验证结果 | ||
| 64 | - assertNotNull(result); | ||
| 65 | - assertNotNull(result.getId()); | ||
| 66 | - assertEquals("device.auth", result.getMethod()); | ||
| 67 | - assertEquals("default", result.getVersion()); | ||
| 68 | - assertNotNull(result.getParams()); | ||
| 69 | - | ||
| 70 | - Map<String, Object> params = result.getParams(); | ||
| 71 | - assertEquals("a1GFjLP****", params.get("productKey")); | ||
| 72 | - assertEquals("device123", params.get("deviceName")); | ||
| 73 | - assertEquals("device123_001", params.get("clientId")); | ||
| 74 | - assertEquals("1501668289957", params.get("timestamp")); | ||
| 75 | - assertEquals("4870141D4067227128CBB4377906C3731CAC221C", params.get("sign")); | ||
| 76 | - assertEquals("hmacsha1", params.get("signmethod")); | ||
| 77 | - } | ||
| 78 | - | ||
| 79 | - @Test | ||
| 80 | - void testParseAuthMessageWithMissingFields() { | ||
| 81 | - // 构建缺少必需字段的认证消息 | ||
| 82 | - JSONObject authMessage = new JSONObject(); | ||
| 83 | - authMessage.set("productKey", "a1GFjLP****"); | ||
| 84 | - authMessage.set("deviceName", "device123"); | ||
| 85 | - // 缺少 clientId 和 sign | ||
| 86 | - | ||
| 87 | - String topic = "/auth"; | ||
| 88 | - byte[] payload = authMessage.toString().getBytes(StandardCharsets.UTF_8); | ||
| 89 | - | ||
| 90 | - // 解析消息 | ||
| 91 | - IotMqttMessage result = parser.parse(topic, payload); | ||
| 92 | - | ||
| 93 | - // 验证结果 | ||
| 94 | - assertNull(result); | ||
| 95 | - } | ||
| 96 | - | ||
| 97 | - @Test | ||
| 98 | - void testParseJsonDataMessage() { | ||
| 99 | - // 构建JSON格式的数据消息 | ||
| 100 | - JSONObject dataMessage = new JSONObject(); | ||
| 101 | - dataMessage.set("id", "123456"); | ||
| 102 | - dataMessage.set("version", "1.0"); | ||
| 103 | - dataMessage.set("method", "thing.event.property.post"); | ||
| 104 | - | ||
| 105 | - JSONObject params = new JSONObject(); | ||
| 106 | - JSONObject properties = new JSONObject(); | ||
| 107 | - properties.set("temperature", 25.6); | ||
| 108 | - properties.set("humidity", 60.3); | ||
| 109 | - params.set("properties", properties); | ||
| 110 | - dataMessage.set("params", params); | ||
| 111 | - | ||
| 112 | - String topic = "/topic/sys/a1GFjLP****/device123/thing/event/property/post"; | ||
| 113 | - byte[] payload = dataMessage.toString().getBytes(StandardCharsets.UTF_8); | ||
| 114 | - | ||
| 115 | - // 解析消息 | ||
| 116 | - IotMqttMessage result = parser.parse(topic, payload); | ||
| 117 | - | ||
| 118 | - // 验证结果 | ||
| 119 | - assertNotNull(result); | ||
| 120 | - assertEquals("123456", result.getId()); | ||
| 121 | - assertEquals("thing.event.property.post", result.getMethod()); | ||
| 122 | - assertEquals("1.0", result.getVersion()); | ||
| 123 | - assertNotNull(result.getParams()); | ||
| 124 | - assertNotNull(result.getParams().get("properties")); | ||
| 125 | - } | ||
| 126 | - | ||
| 127 | - @Test | ||
| 128 | - void testParseRawDataMessage() { | ||
| 129 | - // 原始数据消息 | ||
| 130 | - String rawData = "temperature:25.6,humidity:60.3"; | ||
| 131 | - String topic = "/topic/sys/a1GFjLP****/device123/thing/event/property/post"; | ||
| 132 | - byte[] payload = rawData.getBytes(StandardCharsets.UTF_8); | ||
| 133 | - | ||
| 134 | - // 解析消息 | ||
| 135 | - IotMqttMessage result = parser.parse(topic, payload); | ||
| 136 | - | ||
| 137 | - // 验证结果 | ||
| 138 | - assertNotNull(result); | ||
| 139 | - assertNotNull(result.getId()); | ||
| 140 | - assertEquals("thing.event.property.post", result.getMethod()); | ||
| 141 | - assertEquals("1.0", result.getVersion()); | ||
| 142 | - assertNotNull(result.getParams()); | ||
| 143 | - assertEquals(rawData, result.getParams().get("data")); | ||
| 144 | - } | ||
| 145 | - | ||
| 146 | - @Test | ||
| 147 | - void testInferMethodFromTopic() { | ||
| 148 | - // 测试系统主题方法推断 | ||
| 149 | - testInferMethod("/sys/test/device/thing/service/property/set", "thing.service.property.set"); | ||
| 150 | - testInferMethod("/sys/test/device/thing/service/property/get", "thing.service.property.get"); | ||
| 151 | - testInferMethod("/sys/test/device/thing/event/property/post", "thing.event.property.post"); | ||
| 152 | - testInferMethod("/sys/test/device/thing/event/alarm/post", "thing.event.alarm.post"); | ||
| 153 | - testInferMethod("/sys/test/device/thing/service/reboot", "thing.service.reboot"); | ||
| 154 | - | ||
| 155 | - // 测试自定义主题 | ||
| 156 | - testInferMethod("/test/device/user/get", "custom.message"); | ||
| 157 | - } | ||
| 158 | - | ||
| 159 | - private void testInferMethod(String actualTopic, String expectedMethod) { | ||
| 160 | - String topic = "/topic" + actualTopic; | ||
| 161 | - String rawData = "test data"; | ||
| 162 | - byte[] payload = rawData.getBytes(StandardCharsets.UTF_8); | ||
| 163 | - | ||
| 164 | - IotMqttMessage result = parser.parse(topic, payload); | ||
| 165 | - assertNotNull(result); | ||
| 166 | - assertEquals(expectedMethod, result.getMethod()); | ||
| 167 | - } | ||
| 168 | - | ||
| 169 | - @Test | ||
| 170 | - void testFormatAuthResponse() { | ||
| 171 | - // 创建认证成功响应 | ||
| 172 | - Map<String, Object> data = new HashMap<>(); | ||
| 173 | - data.put("token", "6944e5bfb92e4d4ea3918d1eda39****"); | ||
| 174 | - | ||
| 175 | - IotStandardResponse response = IotStandardResponse.success("auth123", "device.auth", data); | ||
| 176 | - | ||
| 177 | - // 格式化响应 | ||
| 178 | - byte[] result = parser.formatResponse(response); | ||
| 179 | - | ||
| 180 | - // 验证结果 | ||
| 181 | - assertNotNull(result); | ||
| 182 | - assertTrue(result.length > 0); | ||
| 183 | - | ||
| 184 | - String responseStr = new String(result, StandardCharsets.UTF_8); | ||
| 185 | - JSONObject responseJson = new JSONObject(responseStr); | ||
| 186 | - | ||
| 187 | - assertEquals(200, responseJson.getInt("code")); | ||
| 188 | - assertEquals("success", responseJson.getStr("message")); | ||
| 189 | - assertNotNull(responseJson.get("info")); | ||
| 190 | - | ||
| 191 | - JSONObject info = responseJson.getJSONObject("info"); | ||
| 192 | - assertEquals("6944e5bfb92e4d4ea3918d1eda39****", info.getStr("token")); | ||
| 193 | - } | ||
| 194 | - | ||
| 195 | - @Test | ||
| 196 | - void testFormatDataResponse() { | ||
| 197 | - // 创建数据上报响应 | ||
| 198 | - IotStandardResponse response = IotStandardResponse.success("123456", "thing.event.property.post", null); | ||
| 199 | - | ||
| 200 | - // 格式化响应 | ||
| 201 | - byte[] result = parser.formatResponse(response); | ||
| 202 | - | ||
| 203 | - // 验证结果 | ||
| 204 | - assertNotNull(result); | ||
| 205 | - assertTrue(result.length > 0); | ||
| 206 | - | ||
| 207 | - String responseStr = new String(result, StandardCharsets.UTF_8); | ||
| 208 | - JSONObject responseJson = new JSONObject(responseStr); | ||
| 209 | - | ||
| 210 | - assertEquals(200, responseJson.getInt("code")); | ||
| 211 | - assertEquals("success", responseJson.getStr("message")); | ||
| 212 | - assertNotNull(responseJson.get("info")); | ||
| 213 | - | ||
| 214 | - JSONObject info = responseJson.getJSONObject("info"); | ||
| 215 | - assertEquals("123456", info.getStr("messageId")); | ||
| 216 | - } | ||
| 217 | - | ||
| 218 | - @Test | ||
| 219 | - void testParseInvalidMessage() { | ||
| 220 | - String topic = "/topic/sys/test/device/thing/service/property/set"; | ||
| 221 | - | ||
| 222 | - // 测试空消息 | ||
| 223 | - assertNull(parser.parse(topic, null)); | ||
| 224 | - assertNull(parser.parse(topic, new byte[0])); | ||
| 225 | - | ||
| 226 | - // 测试不支持的路径 | ||
| 227 | - byte[] validPayload = "test data".getBytes(StandardCharsets.UTF_8); | ||
| 228 | - assertNull(parser.parse("/unknown/path", validPayload)); | ||
| 229 | - } | ||
| 230 | - | ||
| 231 | - @Test | ||
| 232 | - void testParseDeviceKey() { | ||
| 233 | - // 测试有效的设备标识 | ||
| 234 | - String[] result1 = IotHttpMessageParser.parseDeviceKey("productKey/deviceName"); | ||
| 235 | - assertNotNull(result1); | ||
| 236 | - assertEquals(2, result1.length); | ||
| 237 | - assertEquals("productKey", result1[0]); | ||
| 238 | - assertEquals("deviceName", result1[1]); | ||
| 239 | - | ||
| 240 | - // 测试无效的设备标识 | ||
| 241 | - assertNull(IotHttpMessageParser.parseDeviceKey(null)); | ||
| 242 | - assertNull(IotHttpMessageParser.parseDeviceKey("")); | ||
| 243 | - assertNull(IotHttpMessageParser.parseDeviceKey("invalid")); | ||
| 244 | - assertNull(IotHttpMessageParser.parseDeviceKey("product/device/extra")); | ||
| 245 | - } | ||
| 246 | - | ||
| 247 | - @Test | ||
| 248 | - void testBuildDeviceKey() { | ||
| 249 | - // 测试构建设备标识 | ||
| 250 | - assertEquals("productKey/deviceName", | ||
| 251 | - IotHttpMessageParser.buildDeviceKey("productKey", "deviceName")); | ||
| 252 | - | ||
| 253 | - // 测试无效参数 | ||
| 254 | - assertNull(IotHttpMessageParser.buildDeviceKey(null, "deviceName")); | ||
| 255 | - assertNull(IotHttpMessageParser.buildDeviceKey("productKey", null)); | ||
| 256 | - assertNull(IotHttpMessageParser.buildDeviceKey("", "deviceName")); | ||
| 257 | - assertNull(IotHttpMessageParser.buildDeviceKey("productKey", "")); | ||
| 258 | - } | ||
| 259 | -} | ||
| 260 | \ No newline at end of file | 0 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-protocol/src/test/java/cn/iocoder/yudao/module/iot/protocol/message/impl/IotMqttMessageParserTest.java deleted
| 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 | -} | ||
| 191 | \ No newline at end of file | 0 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-protocol/src/test/java/cn/iocoder/yudao/module/iot/protocol/util/IotHttpTopicUtilsTest.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.protocol.util; | ||
| 2 | - | ||
| 3 | -import org.junit.jupiter.api.Test; | ||
| 4 | - | ||
| 5 | -import static org.junit.jupiter.api.Assertions.*; | ||
| 6 | - | ||
| 7 | -/** | ||
| 8 | - * {@link IotHttpTopicUtils} 单元测试 | ||
| 9 | - * | ||
| 10 | - * @author haohao | ||
| 11 | - */ | ||
| 12 | -class IotHttpTopicUtilsTest { | ||
| 13 | - | ||
| 14 | - @Test | ||
| 15 | - void testBuildAuthPath() { | ||
| 16 | - assertEquals("/auth", IotHttpTopicUtils.buildAuthPath()); | ||
| 17 | - } | ||
| 18 | - | ||
| 19 | - @Test | ||
| 20 | - void testBuildTopicPath() { | ||
| 21 | - // 测试正常路径 | ||
| 22 | - assertEquals("/topic/sys/test/device/thing/service/property/set", | ||
| 23 | - IotHttpTopicUtils.buildTopicPath("/sys/test/device/thing/service/property/set")); | ||
| 24 | - | ||
| 25 | - // 测试空路径 | ||
| 26 | - assertNull(IotHttpTopicUtils.buildTopicPath(null)); | ||
| 27 | - assertNull(IotHttpTopicUtils.buildTopicPath("")); | ||
| 28 | - } | ||
| 29 | - | ||
| 30 | - @Test | ||
| 31 | - void testBuildPropertySetPath() { | ||
| 32 | - String result = IotHttpTopicUtils.buildPropertySetPath("testProduct", "testDevice"); | ||
| 33 | - assertEquals("/topic/sys/testProduct/testDevice/thing/service/property/set", result); | ||
| 34 | - | ||
| 35 | - // 测试无效参数 | ||
| 36 | - assertNull(IotHttpTopicUtils.buildPropertySetPath(null, "testDevice")); | ||
| 37 | - assertNull(IotHttpTopicUtils.buildPropertySetPath("testProduct", null)); | ||
| 38 | - assertNull(IotHttpTopicUtils.buildPropertySetPath("", "testDevice")); | ||
| 39 | - assertNull(IotHttpTopicUtils.buildPropertySetPath("testProduct", "")); | ||
| 40 | - } | ||
| 41 | - | ||
| 42 | - @Test | ||
| 43 | - void testBuildPropertyGetPath() { | ||
| 44 | - String result = IotHttpTopicUtils.buildPropertyGetPath("testProduct", "testDevice"); | ||
| 45 | - assertEquals("/topic/sys/testProduct/testDevice/thing/service/property/get", result); | ||
| 46 | - } | ||
| 47 | - | ||
| 48 | - @Test | ||
| 49 | - void testBuildPropertyPostPath() { | ||
| 50 | - String result = IotHttpTopicUtils.buildPropertyPostPath("testProduct", "testDevice"); | ||
| 51 | - assertEquals("/topic/sys/testProduct/testDevice/thing/event/property/post", result); | ||
| 52 | - } | ||
| 53 | - | ||
| 54 | - @Test | ||
| 55 | - void testBuildEventPostPath() { | ||
| 56 | - String result = IotHttpTopicUtils.buildEventPostPath("testProduct", "testDevice", "alarm"); | ||
| 57 | - assertEquals("/topic/sys/testProduct/testDevice/thing/event/alarm/post", result); | ||
| 58 | - | ||
| 59 | - // 测试无效参数 | ||
| 60 | - assertNull(IotHttpTopicUtils.buildEventPostPath(null, "testDevice", "alarm")); | ||
| 61 | - assertNull(IotHttpTopicUtils.buildEventPostPath("testProduct", null, "alarm")); | ||
| 62 | - assertNull(IotHttpTopicUtils.buildEventPostPath("testProduct", "testDevice", null)); | ||
| 63 | - } | ||
| 64 | - | ||
| 65 | - @Test | ||
| 66 | - void testBuildServiceInvokePath() { | ||
| 67 | - String result = IotHttpTopicUtils.buildServiceInvokePath("testProduct", "testDevice", "reboot"); | ||
| 68 | - assertEquals("/topic/sys/testProduct/testDevice/thing/service/reboot", result); | ||
| 69 | - | ||
| 70 | - // 测试无效参数 | ||
| 71 | - assertNull(IotHttpTopicUtils.buildServiceInvokePath(null, "testDevice", "reboot")); | ||
| 72 | - assertNull(IotHttpTopicUtils.buildServiceInvokePath("testProduct", null, "reboot")); | ||
| 73 | - assertNull(IotHttpTopicUtils.buildServiceInvokePath("testProduct", "testDevice", null)); | ||
| 74 | - } | ||
| 75 | - | ||
| 76 | - @Test | ||
| 77 | - void testBuildCustomTopicPath() { | ||
| 78 | - String result = IotHttpTopicUtils.buildCustomTopicPath("testProduct", "testDevice", "user/get"); | ||
| 79 | - assertEquals("/topic/testProduct/testDevice/user/get", result); | ||
| 80 | - | ||
| 81 | - // 测试无效参数 | ||
| 82 | - assertNull(IotHttpTopicUtils.buildCustomTopicPath(null, "testDevice", "user/get")); | ||
| 83 | - assertNull(IotHttpTopicUtils.buildCustomTopicPath("testProduct", null, "user/get")); | ||
| 84 | - assertNull(IotHttpTopicUtils.buildCustomTopicPath("testProduct", "testDevice", null)); | ||
| 85 | - } | ||
| 86 | - | ||
| 87 | - @Test | ||
| 88 | - void testExtractActualTopic() { | ||
| 89 | - // 测试正常提取 | ||
| 90 | - String actualTopic = IotHttpTopicUtils | ||
| 91 | - .extractActualTopic("/topic/sys/testProduct/testDevice/thing/service/property/set"); | ||
| 92 | - assertEquals("/sys/testProduct/testDevice/thing/service/property/set", actualTopic); | ||
| 93 | - | ||
| 94 | - // 测试无效路径 | ||
| 95 | - assertNull(IotHttpTopicUtils.extractActualTopic("/auth")); | ||
| 96 | - assertNull(IotHttpTopicUtils.extractActualTopic("/unknown/path")); | ||
| 97 | - assertNull(IotHttpTopicUtils.extractActualTopic(null)); | ||
| 98 | - assertNull(IotHttpTopicUtils.extractActualTopic("")); | ||
| 99 | - } | ||
| 100 | - | ||
| 101 | - @Test | ||
| 102 | - void testParseProductKeyFromTopic() { | ||
| 103 | - // 测试系统主题 | ||
| 104 | - assertEquals("testProduct", | ||
| 105 | - IotHttpTopicUtils.parseProductKeyFromTopic("/sys/testProduct/testDevice/thing/service/property/set")); | ||
| 106 | - | ||
| 107 | - // 测试自定义主题 | ||
| 108 | - assertEquals("testProduct", IotHttpTopicUtils.parseProductKeyFromTopic("/testProduct/testDevice/user/get")); | ||
| 109 | - | ||
| 110 | - // 测试无效主题 | ||
| 111 | - assertNull(IotHttpTopicUtils.parseProductKeyFromTopic("/sys")); | ||
| 112 | - assertNull(IotHttpTopicUtils.parseProductKeyFromTopic("/single")); | ||
| 113 | - assertNull(IotHttpTopicUtils.parseProductKeyFromTopic("")); | ||
| 114 | - assertNull(IotHttpTopicUtils.parseProductKeyFromTopic(null)); | ||
| 115 | - } | ||
| 116 | - | ||
| 117 | - @Test | ||
| 118 | - void testParseDeviceNameFromTopic() { | ||
| 119 | - // 测试系统主题 | ||
| 120 | - assertEquals("testDevice", | ||
| 121 | - IotHttpTopicUtils.parseDeviceNameFromTopic("/sys/testProduct/testDevice/thing/service/property/set")); | ||
| 122 | - | ||
| 123 | - // 测试自定义主题 | ||
| 124 | - assertEquals("testDevice", IotHttpTopicUtils.parseDeviceNameFromTopic("/testProduct/testDevice/user/get")); | ||
| 125 | - | ||
| 126 | - // 测试无效主题 | ||
| 127 | - assertNull(IotHttpTopicUtils.parseDeviceNameFromTopic("/sys/testProduct")); | ||
| 128 | - assertNull(IotHttpTopicUtils.parseDeviceNameFromTopic("/testProduct")); | ||
| 129 | - assertNull(IotHttpTopicUtils.parseDeviceNameFromTopic("")); | ||
| 130 | - assertNull(IotHttpTopicUtils.parseDeviceNameFromTopic(null)); | ||
| 131 | - } | ||
| 132 | - | ||
| 133 | - @Test | ||
| 134 | - void testIsAuthPath() { | ||
| 135 | - assertTrue(IotHttpTopicUtils.isAuthPath("/auth")); | ||
| 136 | - assertFalse(IotHttpTopicUtils.isAuthPath("/topic/test")); | ||
| 137 | - assertFalse(IotHttpTopicUtils.isAuthPath("/unknown")); | ||
| 138 | - assertFalse(IotHttpTopicUtils.isAuthPath(null)); | ||
| 139 | - assertFalse(IotHttpTopicUtils.isAuthPath("")); | ||
| 140 | - } | ||
| 141 | - | ||
| 142 | - @Test | ||
| 143 | - void testIsTopicPath() { | ||
| 144 | - assertTrue(IotHttpTopicUtils.isTopicPath("/topic/sys/test/device/property")); | ||
| 145 | - assertTrue(IotHttpTopicUtils.isTopicPath("/topic/test")); | ||
| 146 | - assertFalse(IotHttpTopicUtils.isTopicPath("/auth")); | ||
| 147 | - assertFalse(IotHttpTopicUtils.isTopicPath("/unknown")); | ||
| 148 | - assertFalse(IotHttpTopicUtils.isTopicPath(null)); | ||
| 149 | - assertFalse(IotHttpTopicUtils.isTopicPath("")); | ||
| 150 | - } | ||
| 151 | - | ||
| 152 | - @Test | ||
| 153 | - void testIsValidHttpPath() { | ||
| 154 | - assertTrue(IotHttpTopicUtils.isValidHttpPath("/auth")); | ||
| 155 | - assertTrue(IotHttpTopicUtils.isValidHttpPath("/topic/test")); | ||
| 156 | - assertFalse(IotHttpTopicUtils.isValidHttpPath("/unknown")); | ||
| 157 | - assertFalse(IotHttpTopicUtils.isValidHttpPath(null)); | ||
| 158 | - assertFalse(IotHttpTopicUtils.isValidHttpPath("")); | ||
| 159 | - } | ||
| 160 | - | ||
| 161 | - @Test | ||
| 162 | - void testIsSystemTopic() { | ||
| 163 | - assertTrue(IotHttpTopicUtils.isSystemTopic("/sys/testProduct/testDevice/thing/service/property/set")); | ||
| 164 | - assertFalse(IotHttpTopicUtils.isSystemTopic("/testProduct/testDevice/user/get")); | ||
| 165 | - assertFalse(IotHttpTopicUtils.isSystemTopic("/unknown")); | ||
| 166 | - assertFalse(IotHttpTopicUtils.isSystemTopic(null)); | ||
| 167 | - assertFalse(IotHttpTopicUtils.isSystemTopic("")); | ||
| 168 | - } | ||
| 169 | - | ||
| 170 | - @Test | ||
| 171 | - void testBuildReplyPath() { | ||
| 172 | - // 测试系统主题响应路径 | ||
| 173 | - String requestPath = "/topic/sys/testProduct/testDevice/thing/service/property/set"; | ||
| 174 | - String replyPath = IotHttpTopicUtils.buildReplyPath(requestPath); | ||
| 175 | - assertEquals("/topic/sys/testProduct/testDevice/thing/service/property/set_reply", replyPath); | ||
| 176 | - | ||
| 177 | - // 测试非系统主题 | ||
| 178 | - String customPath = "/topic/testProduct/testDevice/user/get"; | ||
| 179 | - assertNull(IotHttpTopicUtils.buildReplyPath(customPath)); | ||
| 180 | - | ||
| 181 | - // 测试无效路径 | ||
| 182 | - assertNull(IotHttpTopicUtils.buildReplyPath("/auth")); | ||
| 183 | - assertNull(IotHttpTopicUtils.buildReplyPath("/unknown")); | ||
| 184 | - assertNull(IotHttpTopicUtils.buildReplyPath(null)); | ||
| 185 | - } | ||
| 186 | -} | ||
| 187 | \ No newline at end of file | 0 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-protocol/src/test/java/cn/iocoder/yudao/module/iot/protocol/util/IotTopicUtilsTest.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.protocol.util; | ||
| 2 | - | ||
| 3 | -import org.junit.jupiter.api.Test; | ||
| 4 | - | ||
| 5 | -import static org.junit.jupiter.api.Assertions.assertEquals; | ||
| 6 | -import static org.junit.jupiter.api.Assertions.assertNull; | ||
| 7 | - | ||
| 8 | -/** | ||
| 9 | - * {@link IotTopicUtils} 单元测试 | ||
| 10 | - * | ||
| 11 | - * @author haohao | ||
| 12 | - */ | ||
| 13 | -class IotTopicUtilsTest { | ||
| 14 | - | ||
| 15 | - @Test | ||
| 16 | - void testBuildPropertySetTopic() { | ||
| 17 | - String topic = IotTopicUtils.buildPropertySetTopic("testProduct", "testDevice"); | ||
| 18 | - assertEquals("/sys/testProduct/testDevice/thing/service/property/set", topic); | ||
| 19 | - } | ||
| 20 | - | ||
| 21 | - @Test | ||
| 22 | - void testBuildPropertyGetTopic() { | ||
| 23 | - String topic = IotTopicUtils.buildPropertyGetTopic("testProduct", "testDevice"); | ||
| 24 | - assertEquals("/sys/testProduct/testDevice/thing/service/property/get", topic); | ||
| 25 | - } | ||
| 26 | - | ||
| 27 | - @Test | ||
| 28 | - void testBuildEventPostTopic() { | ||
| 29 | - String topic = IotTopicUtils.buildEventPostTopic("testProduct", "testDevice", "temperature"); | ||
| 30 | - assertEquals("/sys/testProduct/testDevice/thing/event/temperature/post", topic); | ||
| 31 | - } | ||
| 32 | - | ||
| 33 | - @Test | ||
| 34 | - void testGetReplyTopic() { | ||
| 35 | - String requestTopic = "/sys/testProduct/testDevice/thing/service/property/set"; | ||
| 36 | - String replyTopic = IotTopicUtils.getReplyTopic(requestTopic); | ||
| 37 | - assertEquals("/sys/testProduct/testDevice/thing/service/property/set_reply", replyTopic); | ||
| 38 | - } | ||
| 39 | - | ||
| 40 | - @Test | ||
| 41 | - void testParseProductKeyFromTopic() { | ||
| 42 | - String topic = "/sys/testProduct/testDevice/thing/service/property/set"; | ||
| 43 | - String productKey = IotTopicUtils.parseProductKeyFromTopic(topic); | ||
| 44 | - assertEquals("testProduct", productKey); | ||
| 45 | - } | ||
| 46 | - | ||
| 47 | - @Test | ||
| 48 | - void testParseDeviceNameFromTopic() { | ||
| 49 | - String topic = "/sys/testProduct/testDevice/thing/service/property/set"; | ||
| 50 | - String deviceName = IotTopicUtils.parseDeviceNameFromTopic(topic); | ||
| 51 | - assertEquals("testDevice", deviceName); | ||
| 52 | - } | ||
| 53 | - | ||
| 54 | - @Test | ||
| 55 | - void testParseMethodFromTopic() { | ||
| 56 | - // 测试属性设置 | ||
| 57 | - String topic1 = "/sys/testProduct/testDevice/thing/service/property/set"; | ||
| 58 | - String method1 = IotTopicUtils.parseMethodFromTopic(topic1); | ||
| 59 | - assertEquals("property.set", method1); | ||
| 60 | - | ||
| 61 | - // 测试事件上报 | ||
| 62 | - String topic2 = "/sys/testProduct/testDevice/thing/event/temperature/post"; | ||
| 63 | - String method2 = IotTopicUtils.parseMethodFromTopic(topic2); | ||
| 64 | - assertEquals("event.temperature.post", method2); | ||
| 65 | - | ||
| 66 | - // 测试无效主题 | ||
| 67 | - String method3 = IotTopicUtils.parseMethodFromTopic("/invalid/topic"); | ||
| 68 | - assertNull(method3); | ||
| 69 | - } | ||
| 70 | - | ||
| 71 | - @Test | ||
| 72 | - void testParseInvalidTopic() { | ||
| 73 | - // 测试空主题 | ||
| 74 | - assertNull(IotTopicUtils.parseProductKeyFromTopic("")); | ||
| 75 | - assertNull(IotTopicUtils.parseProductKeyFromTopic(null)); | ||
| 76 | - | ||
| 77 | - // 测试格式错误的主题 | ||
| 78 | - assertNull(IotTopicUtils.parseProductKeyFromTopic("/invalid")); | ||
| 79 | - assertNull(IotTopicUtils.parseDeviceNameFromTopic("/sys/product")); | ||
| 80 | - } | ||
| 81 | -} | ||
| 82 | \ No newline at end of file | 0 | \ No newline at end of file |