【同步】BOOT 和 CLOUD 的功能
This commit is contained in:
@@ -4,6 +4,12 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
|
||||
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceGetReqDTO;
|
||||
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO;
|
||||
import cn.iocoder.yudao.module.iot.core.biz.dto.IotSubDeviceRegisterFullReqDTO;
|
||||
import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO;
|
||||
import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterRespDTO;
|
||||
import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterRespDTO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* IoT 设备通用 API
|
||||
@@ -28,4 +34,20 @@ public interface IotDeviceCommonApi {
|
||||
*/
|
||||
CommonResult<IotDeviceRespDTO> getDevice(IotDeviceGetReqDTO infoReqDTO);
|
||||
|
||||
/**
|
||||
* 直连/网关设备动态注册(一型一密)
|
||||
*
|
||||
* @param reqDTO 动态注册请求
|
||||
* @return 注册结果(包含 DeviceSecret)
|
||||
*/
|
||||
CommonResult<IotDeviceRegisterRespDTO> registerDevice(IotDeviceRegisterReqDTO reqDTO);
|
||||
|
||||
/**
|
||||
* 网关子设备动态注册(网关代理转发)
|
||||
*
|
||||
* @param reqDTO 子设备注册请求(包含网关标识和子设备列表)
|
||||
* @return 注册结果列表
|
||||
*/
|
||||
CommonResult<List<IotSubDeviceRegisterRespDTO>> registerSubDevices(IotSubDeviceRegisterFullReqDTO reqDTO);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package cn.iocoder.yudao.module.iot.core.biz.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* IoT 设备认证 Request DTO
|
||||
@@ -9,6 +11,8 @@ import lombok.Data;
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class IotDeviceAuthReqDTO {
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package cn.iocoder.yudao.module.iot.core.biz.dto;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterReqDTO;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* IoT 子设备动态注册 Request DTO
|
||||
* <p>
|
||||
* 额外包含了网关设备的标识信息
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
public class IotSubDeviceRegisterFullReqDTO {
|
||||
|
||||
/**
|
||||
* 网关设备 ProductKey
|
||||
*/
|
||||
@NotEmpty(message = "网关产品标识不能为空")
|
||||
private String gatewayProductKey;
|
||||
|
||||
/**
|
||||
* 网关设备 DeviceName
|
||||
*/
|
||||
@NotEmpty(message = "网关设备名称不能为空")
|
||||
private String gatewayDeviceName;
|
||||
|
||||
/**
|
||||
* 子设备注册列表
|
||||
*/
|
||||
@NotNull(message = "子设备注册列表不能为空")
|
||||
private List<IotSubDeviceRegisterReqDTO> subDevices;
|
||||
|
||||
}
|
||||
@@ -24,12 +24,28 @@ public enum IotDeviceMessageMethodEnum implements ArrayValuable<String> {
|
||||
|
||||
// TODO 芋艿:要不要加个 ping 消息;
|
||||
|
||||
// ========== 拓扑管理 ==========
|
||||
// 可参考:https://help.aliyun.com/zh/iot/user-guide/manage-topological-relationships
|
||||
|
||||
TOPO_ADD("thing.topo.add", "添加拓扑关系", true),
|
||||
TOPO_DELETE("thing.topo.delete", "删除拓扑关系", true),
|
||||
TOPO_GET("thing.topo.get", "获取拓扑关系", true),
|
||||
TOPO_CHANGE("thing.topo.change", "拓扑关系变更通知", false),
|
||||
|
||||
// ========== 设备注册 ==========
|
||||
// 可参考:https://help.aliyun.com/zh/iot/user-guide/unique-certificate-per-product-verification
|
||||
|
||||
DEVICE_REGISTER("thing.auth.register", "设备动态注册", true),
|
||||
SUB_DEVICE_REGISTER("thing.auth.register.sub", "子设备动态注册", true),
|
||||
|
||||
// ========== 设备属性 ==========
|
||||
// 可参考:https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services
|
||||
|
||||
PROPERTY_POST("thing.property.post", "属性上报", true),
|
||||
PROPERTY_SET("thing.property.set", "属性设置", false),
|
||||
|
||||
PROPERTY_PACK_POST("thing.event.property.pack.post", "批量上报(属性 + 事件 + 子设备)", true), // 网关独有
|
||||
|
||||
// ========== 设备事件 ==========
|
||||
// 可参考:https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services
|
||||
|
||||
@@ -50,6 +66,7 @@ public enum IotDeviceMessageMethodEnum implements ArrayValuable<String> {
|
||||
|
||||
OTA_UPGRADE("thing.ota.upgrade", "OTA 固定信息推送", false),
|
||||
OTA_PROGRESS("thing.ota.progress", "OTA 升级进度上报", true),
|
||||
|
||||
;
|
||||
|
||||
public static final String[] ARRAYS = Arrays.stream(values()).map(IotDeviceMessageMethodEnum::getMethod)
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.core.enums;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* IoT 设备消息类型枚举
|
||||
*/
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public enum IotDeviceMessageTypeEnum implements ArrayValuable<String> {
|
||||
|
||||
STATE("state"), // 设备状态
|
||||
// PROPERTY("property"), // 设备属性:可参考 https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services 设备属性、事件、服务
|
||||
EVENT("event"), // 设备事件:可参考 https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services 设备属性、事件、服务
|
||||
SERVICE("service"), // 设备服务:可参考 https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services 设备属性、事件、服务
|
||||
CONFIG("config"), // 设备配置:可参考 https://help.aliyun.com/zh/iot/user-guide/remote-configuration-1 远程配置
|
||||
OTA("ota"), // 设备 OTA:可参考 https://help.aliyun.com/zh/iot/user-guide/ota-update OTA 升级
|
||||
REGISTER("register"), // 设备注册:可参考 https://help.aliyun.com/zh/iot/user-guide/register-devices 设备身份注册
|
||||
TOPOLOGY("topology"),; // 设备拓扑:可参考 https://help.aliyun.com/zh/iot/user-guide/manage-topological-relationships 设备拓扑
|
||||
|
||||
public static final String[] ARRAYS = Arrays.stream(values()).map(IotDeviceMessageTypeEnum::getType).toArray(String[]::new);
|
||||
|
||||
/**
|
||||
* 属性
|
||||
*/
|
||||
private final String type;
|
||||
|
||||
@Override
|
||||
public String[] array() {
|
||||
return ARRAYS;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package cn.iocoder.yudao.module.iot.core.topic;
|
||||
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* IoT 设备标识
|
||||
*
|
||||
* 用于标识一个设备的基本信息(productKey + deviceName)
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class IotDeviceIdentity {
|
||||
|
||||
/**
|
||||
* 产品标识
|
||||
*/
|
||||
@NotEmpty(message = "产品标识不能为空")
|
||||
private String productKey;
|
||||
|
||||
/**
|
||||
* 设备名称
|
||||
*/
|
||||
@NotEmpty(message = "设备名称不能为空")
|
||||
private String deviceName;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package cn.iocoder.yudao.module.iot.core.topic.auth;
|
||||
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* IoT 设备动态注册 Request DTO
|
||||
* <p>
|
||||
* 用于直连设备/网关的一型一密动态注册:使用 productSecret 验证,返回 deviceSecret
|
||||
*
|
||||
* @author 芋道源码
|
||||
* @see <a href="https://help.aliyun.com/zh/iot/user-guide/unique-certificate-per-product-verification">阿里云 - 一型一密</a>
|
||||
*/
|
||||
@Data
|
||||
public class IotDeviceRegisterReqDTO {
|
||||
|
||||
/**
|
||||
* 产品标识
|
||||
*/
|
||||
@NotEmpty(message = "产品标识不能为空")
|
||||
private String productKey;
|
||||
|
||||
/**
|
||||
* 设备名称
|
||||
*/
|
||||
@NotEmpty(message = "设备名称不能为空")
|
||||
private String deviceName;
|
||||
|
||||
/**
|
||||
* 产品密钥
|
||||
*/
|
||||
@NotEmpty(message = "产品密钥不能为空")
|
||||
private String productSecret;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package cn.iocoder.yudao.module.iot.core.topic.auth;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* IoT 设备动态注册 Response DTO
|
||||
* <p>
|
||||
* 用于直连设备/网关的一型一密动态注册响应
|
||||
*
|
||||
* @author 芋道源码
|
||||
* @see <a href="https://help.aliyun.com/zh/iot/user-guide/unique-certificate-per-product-verification">阿里云 - 一型一密</a>
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class IotDeviceRegisterRespDTO {
|
||||
|
||||
/**
|
||||
* 产品标识
|
||||
*/
|
||||
private String productKey;
|
||||
|
||||
/**
|
||||
* 设备名称
|
||||
*/
|
||||
private String deviceName;
|
||||
|
||||
/**
|
||||
* 设备密钥
|
||||
*/
|
||||
private String deviceSecret;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package cn.iocoder.yudao.module.iot.core.topic.auth;
|
||||
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* IoT 子设备动态注册 Request DTO
|
||||
* <p>
|
||||
* 用于 thing.auth.register.sub 消息的 params 数组元素
|
||||
*
|
||||
* 特殊:网关子设备的动态注册,必须已经创建好该网关子设备(不然哪来的 {@link #deviceName} 字段)。更多的好处,是设备不用提前烧录 deviceSecret 密钥。
|
||||
*
|
||||
* @author 芋道源码
|
||||
* @see <a href="https://help.aliyun.com/zh/iot/user-guide/register-devices">阿里云 - 动态注册子设备</a>
|
||||
*/
|
||||
@Data
|
||||
public class IotSubDeviceRegisterReqDTO {
|
||||
|
||||
/**
|
||||
* 子设备 ProductKey
|
||||
*/
|
||||
@NotEmpty(message = "产品标识不能为空")
|
||||
private String productKey;
|
||||
|
||||
/**
|
||||
* 子设备 DeviceName
|
||||
*/
|
||||
@NotEmpty(message = "设备名称不能为空")
|
||||
private String deviceName;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package cn.iocoder.yudao.module.iot.core.topic.auth;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* IoT 子设备动态注册 Response DTO
|
||||
* <p>
|
||||
* 用于 thing.auth.register.sub 响应的设备信息
|
||||
*
|
||||
* @author 芋道源码
|
||||
* @see <a href="https://help.aliyun.com/zh/iot/user-guide/register-devices">阿里云 - 动态注册子设备</a>
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class IotSubDeviceRegisterRespDTO {
|
||||
|
||||
/**
|
||||
* 子设备 ProductKey
|
||||
*/
|
||||
private String productKey;
|
||||
|
||||
/**
|
||||
* 子设备 DeviceName
|
||||
*/
|
||||
private String deviceName;
|
||||
|
||||
/**
|
||||
* 分配的 DeviceSecret
|
||||
*/
|
||||
private String deviceSecret;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package cn.iocoder.yudao.module.iot.core.topic.event;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* IoT 设备事件上报 Request DTO
|
||||
* <p>
|
||||
* 用于 thing.event.post 消息的 params 参数
|
||||
*
|
||||
* @author 芋道源码
|
||||
* @see <a href="http://help.aliyun.com/zh/marketplace/device-reporting-events">阿里云 - 设备上报事件</a>
|
||||
*/
|
||||
@Data
|
||||
public class IotDeviceEventPostReqDTO {
|
||||
|
||||
/**
|
||||
* 事件标识符
|
||||
*/
|
||||
private String identifier;
|
||||
|
||||
/**
|
||||
* 事件输出参数
|
||||
*/
|
||||
private Object value;
|
||||
|
||||
/**
|
||||
* 上报时间(毫秒时间戳,可选)
|
||||
*/
|
||||
private Long time;
|
||||
|
||||
/**
|
||||
* 创建事件上报 DTO
|
||||
*
|
||||
* @param identifier 事件标识符
|
||||
* @param value 事件值
|
||||
* @return DTO 对象
|
||||
*/
|
||||
public static IotDeviceEventPostReqDTO of(String identifier, Object value) {
|
||||
return of(identifier, value, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建事件上报 DTO(带时间)
|
||||
*
|
||||
* @param identifier 事件标识符
|
||||
* @param value 事件值
|
||||
* @param time 上报时间
|
||||
* @return DTO 对象
|
||||
*/
|
||||
public static IotDeviceEventPostReqDTO of(String identifier, Object value, Long time) {
|
||||
return new IotDeviceEventPostReqDTO().setIdentifier(identifier).setValue(value).setTime(time);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* IoT Topic 消息体 DTO 定义
|
||||
* <p>
|
||||
* 定义设备与平台通信的消息体结构,遵循(参考)阿里云 Alink 协议规范
|
||||
*
|
||||
* @see <a href="https://help.aliyun.com/zh/iot/user-guide/alink-protocol-1">阿里云 Alink 协议</a>
|
||||
*/
|
||||
package cn.iocoder.yudao.module.iot.core.topic;
|
||||
@@ -0,0 +1,88 @@
|
||||
package cn.iocoder.yudao.module.iot.core.topic.property;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* IoT 设备属性批量上报 Request DTO
|
||||
* <p>
|
||||
* 用于 thing.event.property.pack.post 消息的 params 参数
|
||||
*
|
||||
* @author 芋道源码
|
||||
* @see <a href="http://help.aliyun.com/zh/marketplace/gateway-reports-data-in-batches">阿里云 - 网关批量上报数据</a>
|
||||
*/
|
||||
@Data
|
||||
public class IotDevicePropertyPackPostReqDTO {
|
||||
|
||||
/**
|
||||
* 网关自身属性
|
||||
* <p>
|
||||
* key: 属性标识符
|
||||
* value: 属性值
|
||||
*/
|
||||
private Map<String, Object> properties;
|
||||
|
||||
/**
|
||||
* 网关自身事件
|
||||
* <p>
|
||||
* key: 事件标识符
|
||||
* value: 事件值对象(包含 value 和 time)
|
||||
*/
|
||||
private Map<String, EventValue> events;
|
||||
|
||||
/**
|
||||
* 子设备数据列表
|
||||
*/
|
||||
private List<SubDeviceData> subDevices;
|
||||
|
||||
/**
|
||||
* 事件值对象
|
||||
*/
|
||||
@Data
|
||||
public static class EventValue {
|
||||
|
||||
/**
|
||||
* 事件参数
|
||||
*/
|
||||
private Object value;
|
||||
|
||||
/**
|
||||
* 上报时间(毫秒时间戳)
|
||||
*/
|
||||
private Long time;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 子设备数据
|
||||
*/
|
||||
@Data
|
||||
public static class SubDeviceData {
|
||||
|
||||
/**
|
||||
* 子设备标识
|
||||
*/
|
||||
private IotDeviceIdentity identity;
|
||||
|
||||
/**
|
||||
* 子设备属性
|
||||
* <p>
|
||||
* key: 属性标识符
|
||||
* value: 属性值
|
||||
*/
|
||||
private Map<String, Object> properties;
|
||||
|
||||
/**
|
||||
* 子设备事件
|
||||
* <p>
|
||||
* key: 事件标识符
|
||||
* value: 事件值对象(包含 value 和 time)
|
||||
*/
|
||||
private Map<String, EventValue> events;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package cn.iocoder.yudao.module.iot.core.topic.property;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* IoT 设备属性上报 Request DTO
|
||||
* <p>
|
||||
* 用于 thing.property.post 消息的 params 参数
|
||||
* <p>
|
||||
* 本质是一个 Map,key 为属性标识符,value 为属性值
|
||||
*
|
||||
* @author 芋道源码
|
||||
* @see <a href="http://help.aliyun.com/zh/marketplace/device-reporting-attributes">阿里云 - 设备上报属性</a>
|
||||
*/
|
||||
public class IotDevicePropertyPostReqDTO extends HashMap<String, Object> {
|
||||
|
||||
public IotDevicePropertyPostReqDTO() {
|
||||
super();
|
||||
}
|
||||
|
||||
public IotDevicePropertyPostReqDTO(Map<String, Object> properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建属性上报 DTO
|
||||
*
|
||||
* @param properties 属性数据
|
||||
* @return DTO 对象
|
||||
*/
|
||||
public static IotDevicePropertyPostReqDTO of(Map<String, Object> properties) {
|
||||
return new IotDevicePropertyPostReqDTO(properties);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package cn.iocoder.yudao.module.iot.core.topic.topo;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* IoT 设备拓扑添加 Request DTO
|
||||
* <p>
|
||||
* 用于 thing.topo.add 消息的 params 参数
|
||||
*
|
||||
* @author 芋道源码
|
||||
* @see <a href="http://help.aliyun.com/zh/marketplace/add-topological-relationship">阿里云 - 添加拓扑关系</a>
|
||||
*/
|
||||
@Data
|
||||
public class IotDeviceTopoAddReqDTO {
|
||||
|
||||
/**
|
||||
* 子设备认证信息列表
|
||||
* <p>
|
||||
* 复用 {@link IotDeviceAuthReqDTO},包含 clientId、username、password
|
||||
*/
|
||||
@NotEmpty(message = "子设备认证信息列表不能为空")
|
||||
private List<IotDeviceAuthReqDTO> subDevices;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package cn.iocoder.yudao.module.iot.core.topic.topo;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* IoT 设备拓扑关系变更通知 Request DTO
|
||||
* <p>
|
||||
* 用于 thing.topo.change 下行消息的 params 参数
|
||||
*
|
||||
* @author 芋道源码
|
||||
* @see <a href="https://help.aliyun.com/zh/marketplace/notify-gateway-topology-changes">阿里云 - 通知网关拓扑关系变化</a>
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class IotDeviceTopoChangeReqDTO {
|
||||
|
||||
public static final Integer STATUS_CREATE = 0;
|
||||
public static final Integer STATUS_DELETE = 1;
|
||||
|
||||
/**
|
||||
* 拓扑关系状态
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 子设备列表
|
||||
*/
|
||||
private List<IotDeviceIdentity> subList;
|
||||
|
||||
public static IotDeviceTopoChangeReqDTO ofCreate(List<IotDeviceIdentity> subList) {
|
||||
return new IotDeviceTopoChangeReqDTO(STATUS_CREATE, subList);
|
||||
}
|
||||
|
||||
public static IotDeviceTopoChangeReqDTO ofDelete(List<IotDeviceIdentity> subList) {
|
||||
return new IotDeviceTopoChangeReqDTO(STATUS_DELETE, subList);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package cn.iocoder.yudao.module.iot.core.topic.topo;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* IoT 设备拓扑删除 Request DTO
|
||||
* <p>
|
||||
* 用于 thing.topo.delete 消息的 params 参数
|
||||
*
|
||||
* @author 芋道源码
|
||||
* @see <a href="https://help.aliyun.com/zh/marketplace/delete-a-topological-relationship">阿里云 - 删除拓扑关系</a>
|
||||
*/
|
||||
@Data
|
||||
public class IotDeviceTopoDeleteReqDTO {
|
||||
|
||||
/**
|
||||
* 子设备标识列表
|
||||
*/
|
||||
@Valid
|
||||
@NotEmpty(message = "子设备标识列表不能为空")
|
||||
private List<IotDeviceIdentity> subDevices;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package cn.iocoder.yudao.module.iot.core.topic.topo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* IoT 设备拓扑关系获取 Request DTO
|
||||
* <p>
|
||||
* 用于 thing.topo.get 请求的 params 参数(目前为空,预留扩展)
|
||||
*
|
||||
* @author 芋道源码
|
||||
* @see <a href="https://help.aliyun.com/zh/marketplace/obtain-topological-relationship">阿里云 - 获取拓扑关系</a>
|
||||
*/
|
||||
@Data
|
||||
public class IotDeviceTopoGetReqDTO {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package cn.iocoder.yudao.module.iot.core.topic.topo;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* IoT 设备拓扑关系获取 Response DTO
|
||||
* <p>
|
||||
* 用于 thing.topo.get 响应
|
||||
*
|
||||
* @author 芋道源码
|
||||
* @see <a href="https://help.aliyun.com/zh/marketplace/obtain-topological-relationship">阿里云 - 获取拓扑关系</a>
|
||||
*/
|
||||
@Data
|
||||
public class IotDeviceTopoGetRespDTO {
|
||||
|
||||
/**
|
||||
* 子设备列表
|
||||
*/
|
||||
private List<IotDeviceIdentity> subDevices;
|
||||
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
package cn.iocoder.yudao.module.iot.core.util;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.crypto.digest.DigestUtil;
|
||||
import cn.hutool.crypto.digest.HmacAlgorithm;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
|
||||
import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity;
|
||||
|
||||
/**
|
||||
* IoT 设备【认证】的工具类,参考阿里云
|
||||
@@ -13,73 +13,40 @@ import lombok.NoArgsConstructor;
|
||||
*/
|
||||
public class IotDeviceAuthUtils {
|
||||
|
||||
/**
|
||||
* 认证信息
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class AuthInfo {
|
||||
|
||||
/**
|
||||
* 客户端 ID
|
||||
*/
|
||||
private String clientId;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
private String password;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 设备信息
|
||||
*/
|
||||
@Data
|
||||
public static class DeviceInfo {
|
||||
|
||||
private String productKey;
|
||||
|
||||
private String deviceName;
|
||||
|
||||
}
|
||||
|
||||
public static AuthInfo getAuthInfo(String productKey, String deviceName, String deviceSecret) {
|
||||
public static IotDeviceAuthReqDTO getAuthInfo(String productKey, String deviceName, String deviceSecret) {
|
||||
String clientId = buildClientId(productKey, deviceName);
|
||||
String username = buildUsername(productKey, deviceName);
|
||||
String content = "clientId" + clientId +
|
||||
"deviceName" + deviceName +
|
||||
"deviceSecret" + deviceSecret +
|
||||
"productKey" + productKey;
|
||||
String password = buildPassword(deviceSecret, content);
|
||||
return new AuthInfo(clientId, username, password);
|
||||
String password = buildPassword(deviceSecret,
|
||||
buildContent(clientId, productKey, deviceName, deviceSecret));
|
||||
return new IotDeviceAuthReqDTO(clientId, username, password);
|
||||
}
|
||||
|
||||
private static String buildClientId(String productKey, String deviceName) {
|
||||
public static String buildClientId(String productKey, String deviceName) {
|
||||
return String.format("%s.%s", productKey, deviceName);
|
||||
}
|
||||
|
||||
private static String buildUsername(String productKey, String deviceName) {
|
||||
public static String buildUsername(String productKey, String deviceName) {
|
||||
return String.format("%s&%s", deviceName, productKey);
|
||||
}
|
||||
|
||||
private static String buildPassword(String deviceSecret, String content) {
|
||||
return DigestUtil.hmac(HmacAlgorithm.HmacSHA256, deviceSecret.getBytes())
|
||||
public static String buildPassword(String deviceSecret, String content) {
|
||||
return DigestUtil.hmac(HmacAlgorithm.HmacSHA256, StrUtil.utf8Bytes(deviceSecret))
|
||||
.digestHex(content);
|
||||
}
|
||||
|
||||
public static DeviceInfo parseUsername(String username) {
|
||||
private static String buildContent(String clientId, String productKey, String deviceName, String deviceSecret) {
|
||||
return "clientId" + clientId +
|
||||
"deviceName" + deviceName +
|
||||
"deviceSecret" + deviceSecret +
|
||||
"productKey" + productKey;
|
||||
}
|
||||
|
||||
public static IotDeviceIdentity parseUsername(String username) {
|
||||
String[] usernameParts = username.split("&");
|
||||
if (usernameParts.length != 2) {
|
||||
return null;
|
||||
}
|
||||
return new DeviceInfo().setProductKey(usernameParts[1]).setDeviceName(usernameParts[0]);
|
||||
return new IotDeviceIdentity(usernameParts[1], usernameParts[0]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ public class IotDeviceMessageUtils {
|
||||
|
||||
/**
|
||||
* 判断消息中是否包含指定的标识符
|
||||
*
|
||||
* <p>
|
||||
* 对于不同消息类型的处理:
|
||||
* - EVENT_POST/SERVICE_INVOKE:检查 params.identifier 是否匹配
|
||||
* - STATE_UPDATE:检查 params.state 是否匹配
|
||||
@@ -99,6 +99,17 @@ public class IotDeviceMessageUtils {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断消息中是否不包含指定的标识符
|
||||
*
|
||||
* @param message 消息
|
||||
* @param identifier 要检查的标识符
|
||||
* @return 是否不包含
|
||||
*/
|
||||
public static boolean notContainsIdentifier(IotDeviceMessage message, String identifier) {
|
||||
return !containsIdentifier(message, identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 params 解析为 Map
|
||||
*
|
||||
@@ -144,20 +155,19 @@ public class IotDeviceMessageUtils {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 策略1:如果 params 不是 Map,直接返回该值(适用于简单的单属性消息)
|
||||
// 策略 1:如果 params 不是 Map,直接返回该值(适用于简单的单属性消息)
|
||||
if (!(params instanceof Map)) {
|
||||
return params;
|
||||
}
|
||||
|
||||
// 策略 2:直接通过标识符获取属性值
|
||||
Map<String, Object> paramsMap = (Map<String, Object>) params;
|
||||
|
||||
// 策略2:直接通过标识符获取属性值
|
||||
Object directValue = paramsMap.get(identifier);
|
||||
if (directValue != null) {
|
||||
return directValue;
|
||||
}
|
||||
|
||||
// 策略3:从 properties 字段中获取(适用于标准属性上报消息)
|
||||
// 策略 3:从 properties 字段中获取(适用于标准属性上报消息)
|
||||
Object properties = paramsMap.get("properties");
|
||||
if (properties instanceof Map) {
|
||||
Map<String, Object> propertiesMap = (Map<String, Object>) properties;
|
||||
@@ -167,7 +177,7 @@ public class IotDeviceMessageUtils {
|
||||
}
|
||||
}
|
||||
|
||||
// 策略4:从 data 字段中获取(适用于某些消息格式)
|
||||
// 策略 4:从 data 字段中获取(适用于某些消息格式)
|
||||
Object data = paramsMap.get("data");
|
||||
if (data instanceof Map) {
|
||||
Map<String, Object> dataMap = (Map<String, Object>) data;
|
||||
@@ -177,13 +187,13 @@ public class IotDeviceMessageUtils {
|
||||
}
|
||||
}
|
||||
|
||||
// 策略5:从 value 字段中获取(适用于单值消息)
|
||||
// 策略 5:从 value 字段中获取(适用于单值消息)
|
||||
Object value = paramsMap.get("value");
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
|
||||
// 策略6:如果 Map 只有两个字段且包含 identifier,返回另一个字段的值
|
||||
// 策略 6:如果 Map 只有两个字段且包含 identifier,返回另一个字段的值
|
||||
if (paramsMap.size() == 2 && paramsMap.containsKey("identifier")) {
|
||||
for (Map.Entry<String, Object> entry : paramsMap.entrySet()) {
|
||||
if (!"identifier".equals(entry.getKey())) {
|
||||
@@ -196,6 +206,43 @@ public class IotDeviceMessageUtils {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从服务调用消息中提取输入参数
|
||||
* <p>
|
||||
* 服务调用消息的 params 结构通常为:
|
||||
* {
|
||||
* "identifier": "serviceIdentifier",
|
||||
* "inputData": { ... } 或 "inputParams": { ... }
|
||||
* }
|
||||
*
|
||||
* @param message 设备消息
|
||||
* @return 输入参数 Map,如果未找到则返回 null
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Map<String, Object> extractServiceInputParams(IotDeviceMessage message) {
|
||||
// 1. 参数校验
|
||||
Object params = message.getParams();
|
||||
if (params == null) {
|
||||
return null;
|
||||
}
|
||||
if (!(params instanceof Map)) {
|
||||
return null;
|
||||
}
|
||||
Map<String, Object> paramsMap = (Map<String, Object>) params;
|
||||
|
||||
// 尝试从 inputData 字段获取
|
||||
Object inputData = paramsMap.get("inputData");
|
||||
if (inputData instanceof Map) {
|
||||
return (Map<String, Object>) inputData;
|
||||
}
|
||||
// 尝试从 inputParams 字段获取
|
||||
Object inputParams = paramsMap.get("inputParams");
|
||||
if (inputParams instanceof Map) {
|
||||
return (Map<String, Object>) inputParams;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// ========== Topic 相关 ==========
|
||||
|
||||
public static String buildMessageBusGatewayDeviceMessageTopic(String serverId) {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package cn.iocoder.yudao.module.iot.core.util;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* {@link IotDeviceMessageUtils} 的单元测试
|
||||
@@ -138,4 +138,72 @@ public class IotDeviceMessageUtilsTest {
|
||||
Object result = IotDeviceMessageUtils.extractPropertyValue(message, "temperature");
|
||||
assertEquals(25.5, result); // 应该返回直接标识符的值
|
||||
}
|
||||
|
||||
// ========== notContainsIdentifier 测试 ==========
|
||||
|
||||
/**
|
||||
* 测试 notContainsIdentifier 与 containsIdentifier 的互补性
|
||||
* **Property 2: notContainsIdentifier 与 containsIdentifier 互补性**
|
||||
* **Validates: Requirements 4.1**
|
||||
*/
|
||||
@Test
|
||||
public void testNotContainsIdentifier_complementary_whenContains() {
|
||||
// 准备参数:消息包含指定标识符
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
message.setMethod(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod());
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("temperature", 25);
|
||||
message.setParams(params);
|
||||
String identifier = "temperature";
|
||||
|
||||
// 调用 & 断言:验证互补性
|
||||
boolean containsResult = IotDeviceMessageUtils.containsIdentifier(message, identifier);
|
||||
boolean notContainsResult = IotDeviceMessageUtils.notContainsIdentifier(message, identifier);
|
||||
assertTrue(containsResult);
|
||||
assertFalse(notContainsResult);
|
||||
assertEquals(!containsResult, notContainsResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 notContainsIdentifier 与 containsIdentifier 的互补性
|
||||
* **Property 2: notContainsIdentifier 与 containsIdentifier 互补性**
|
||||
* **Validates: Requirements 4.1**
|
||||
*/
|
||||
@Test
|
||||
public void testNotContainsIdentifier_complementary_whenNotContains() {
|
||||
// 准备参数:消息不包含指定标识符
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
message.setMethod(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod());
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("temperature", 25);
|
||||
message.setParams(params);
|
||||
String identifier = "humidity";
|
||||
|
||||
// 调用 & 断言:验证互补性
|
||||
boolean containsResult = IotDeviceMessageUtils.containsIdentifier(message, identifier);
|
||||
boolean notContainsResult = IotDeviceMessageUtils.notContainsIdentifier(message, identifier);
|
||||
assertFalse(containsResult);
|
||||
assertTrue(notContainsResult);
|
||||
assertEquals(!containsResult, notContainsResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 notContainsIdentifier 与 containsIdentifier 的互补性 - 空参数场景
|
||||
* **Property 2: notContainsIdentifier 与 containsIdentifier 互补性**
|
||||
* **Validates: Requirements 4.1**
|
||||
*/
|
||||
@Test
|
||||
public void testNotContainsIdentifier_complementary_nullParams() {
|
||||
// 准备参数:params 为 null
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
message.setParams(null);
|
||||
String identifier = "temperature";
|
||||
|
||||
// 调用 & 断言:验证互补性
|
||||
boolean containsResult = IotDeviceMessageUtils.containsIdentifier(message, identifier);
|
||||
boolean notContainsResult = IotDeviceMessageUtils.notContainsIdentifier(message, identifier);
|
||||
assertFalse(containsResult);
|
||||
assertTrue(notContainsResult);
|
||||
assertEquals(!containsResult, notContainsResult);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user