feat(iot):【网关设备:60%】整体初步实现(优化部分代码)

This commit is contained in:
YunaiV
2026-01-24 09:15:01 +08:00
parent a2750693eb
commit 18ed7b50be
14 changed files with 155 additions and 216 deletions

View File

@@ -40,8 +40,6 @@ public interface ErrorCodeConstants {
ErrorCode DEVICE_TOPO_SUB_DEVICE_USERNAME_INVALID = new ErrorCode(1_050_003_101, "子设备用户名格式无效");
ErrorCode DEVICE_TOPO_SUB_DEVICE_AUTH_FAILED = new ErrorCode(1_050_003_102, "子设备认证失败");
ErrorCode DEVICE_TOPO_SUB_NOT_BINDTO_GATEWAY = new ErrorCode(1_050_003_103, "子设备【{}/{}】未绑定到该网关");
// TODO @AI这里的错误码校验要不要使用
ErrorCode DEVICE_TOPO_SUB_DEVICE_NOT_BOUND = new ErrorCode(1_050_003_104, "子设备【{}/{}】未绑定到任何网关");
// 子设备注册相关错误码 1-050-003-200
ErrorCode DEVICE_SUB_REGISTER_PARAMS_INVALID = new ErrorCode(1_050_003_200, "子设备注册参数无效");
ErrorCode DEVICE_SUB_REGISTER_PRODUCT_NOT_GATEWAY_SUB = new ErrorCode(1_050_003_201, "产品【{}】不是网关子设备类型");

View File

@@ -471,7 +471,7 @@ public class IotDeviceServiceImpl implements IotDeviceService {
// 3. 校验子设备拓扑关系:子设备必须先绑定到某网关才能认证上线
if (IotProductDeviceTypeEnum.isGatewaySub(device.getDeviceType())
&& device.getGatewayId() != null) {
&& device.getGatewayId() == null) {
log.warn("[authDevice][子设备({}/{}) 未绑定到任何网关,认证失败]", productKey, deviceName);
return false;
}

View File

@@ -1,8 +1,10 @@
package cn.iocoder.yudao.module.iot.service.device.message;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
@@ -183,10 +185,9 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService {
// 2. 记录消息
getSelf().createDeviceLogAsync(message);
// TODO @AI我在想是不是批量上传后还是得 reply 。因为打包上传的时候只是那条消息的回复。然后需要单独给每个子消息回复后续至于怎么使用是不是得看具体业务了例如说1批量上传属性默认回复是批量上传的消息然后每个属性、事件拆包消息单独回复后续网关设备按需回复给子设备。
// 3. 回复消息。前提:非 _reply 消息、非禁用回复的消息、非拆包消息
// 3. 回复消息。前提:非 _reply 消息、非禁用回复的消息
if (IotDeviceMessageUtils.isReplyMessage(message)
|| !message.needReply()
|| IotDeviceMessageMethodEnum.isReplyDisabled(message.getMethod())
|| StrUtil.isEmpty(message.getServerId())) {
return;
}
@@ -242,7 +243,6 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService {
if (Objects.equal(message.getMethod(), IotDeviceMessageMethodEnum.TOPO_DELETE.getMethod())) {
return handleTopoDelete(message, device);
}
// 获取拓扑关系
if (Objects.equal(message.getMethod(), IotDeviceMessageMethodEnum.TOPO_GET.getMethod())) {
return handleTopoGet(device);
@@ -267,57 +267,81 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService {
* @return 响应数据
*/
private Object handleTopoAdd(IotDeviceMessage message, IotDeviceDO gatewayDevice) {
// TODO @AI这里是不是 1.1 1.2 1.3 这样修改前我们确认下有没模块是这么写的iot 消息处理里;
// 1. 校验网关设备类型
// 1.1 校验网关设备类型
if (!IotProductDeviceTypeEnum.isGateway(gatewayDevice.getDeviceType())) {
throw exception(DEVICE_NOT_GATEWAY);
}
// 2. 解析参数
// 1.2 解析参数
// TODO @AI是不是 parseObject 增加一个方法,允许传入 object 类型,避免先转 jsonString 再 parseObject
IotDeviceTopoAddReqDTO params = JsonUtils.parseObject(JsonUtils.toJsonString(message.getParams()),
IotDeviceTopoAddReqDTO.class);
if (params == null) {
if (params == null || CollUtil.isEmpty(params.getSubDevices())) {
throw exception(DEVICE_TOPO_PARAMS_INVALID);
}
// 3. 解析子设备信息
IotDeviceAuthUtils.DeviceInfo subDeviceInfo = IotDeviceAuthUtils.parseUsername(params.getUsername());
// 2. 遍历处理每个子设备
// TODO @AIprocessTopoAddSubDevice 不要抽成小方法;
List<IotDeviceDO> addedSubDevices = new ArrayList<>(params.getSubDevices().size());
for (IotDeviceAuthReqDTO subDeviceAuth : params.getSubDevices()) {
try {
IotDeviceDO subDevice = processTopoAddSubDevice(subDeviceAuth, gatewayDevice);
addedSubDevices.add(subDevice);
} catch (Exception ex) {
log.warn("[handleTopoAdd][网关({}/{}) 添加子设备失败username={}]",
gatewayDevice.getProductKey(), gatewayDevice.getDeviceName(),
subDeviceAuth.getUsername(), ex);
}
}
// TODO @AIhttp://help.aliyun.com/zh/marketplace/add-topological-relationship 要回复的!
// 3. 发送拓扑变更通知
// TODO @AI这里不应该发它更多发生在管理后台改动后主动下发http://help.aliyun.com/zh/marketplace/notify-gateway-topology-changes
for (IotDeviceDO subDevice : addedSubDevices) {
sendTopoChangeNotify(gatewayDevice, "add", subDevice);
}
return null;
}
/**
* 处理单个子设备的拓扑添加
*
* @param subDeviceAuth 子设备认证信息
* @param gatewayDevice 网关设备
* @return 添加成功的子设备,失败返回 null
*/
private IotDeviceDO processTopoAddSubDevice(IotDeviceAuthReqDTO subDeviceAuth, IotDeviceDO gatewayDevice) {
// 1.1 解析子设备信息
IotDeviceAuthUtils.DeviceInfo subDeviceInfo = IotDeviceAuthUtils.parseUsername(subDeviceAuth.getUsername());
if (subDeviceInfo == null) {
throw exception(DEVICE_TOPO_SUB_DEVICE_USERNAME_INVALID);
}
// 4. 校验子设备认证信息
// TODO @AI链式调用
IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO();
authReqDTO.setClientId(params.getClientId());
authReqDTO.setUsername(params.getUsername());
authReqDTO.setPassword(params.getPassword());
if (!deviceService.authDevice(authReqDTO)) {
// 1.2 校验子设备认证信息
if (!deviceService.authDevice(subDeviceAuth)) {
throw exception(DEVICE_TOPO_SUB_DEVICE_AUTH_FAILED);
}
// 5. 获取子设备
// 1.3 获取子设备
IotDeviceDO subDevice = deviceService.getDeviceFromCache(subDeviceInfo.getProductKey(), subDeviceInfo.getDeviceName());
if (subDevice == null) {
throw exception(DEVICE_NOT_EXISTS);
}
// 6. 校验子设备类型
// 1.4 校验子设备类型
if (!IotProductDeviceTypeEnum.isGatewaySub(subDevice.getDeviceType())) {
throw exception(DEVICE_NOT_GATEWAY_SUB, subDevice.getProductKey(), subDevice.getDeviceName());
}
// 1.5 校验子设备是否已绑定到其他网关
if (subDevice.getGatewayId() != null
&& ObjectUtil.notEqual(subDevice.getGatewayId(), gatewayDevice.getId())) {
throw exception(DEVICE_GATEWAY_BINDTO_EXISTS, subDevice.getProductKey(), subDevice.getDeviceName());
}
// 7. 绑定拓扑关系
// TODO @AI这里要考虑,校验是不是老设备已经绑定到其他网关了?
// 2. 绑定拓扑关系
// TODO @AI上面的校验,貌似和 bindDeviceGateway 里的,有点重复;
deviceService.bindDeviceGateway(Collections.singletonList(subDevice.getId()), gatewayDevice.getId());
log.info("[handleTopoAdd][网关({}/{}) 绑定子设备({}/{})]",
log.info("[processTopoAddSubDevice][网关({}/{}) 绑定子设备({}/{})]",
gatewayDevice.getProductKey(), gatewayDevice.getDeviceName(),
subDevice.getProductKey(), subDevice.getDeviceName());
// 8. 发送拓扑变更通知
sendTopoChangeNotify(gatewayDevice, "add", subDevice);
return null;
return subDevice;
}
// TODO @AI是不是更适合在 deviceService 里面处理?
@@ -335,41 +359,70 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService {
throw exception(DEVICE_NOT_GATEWAY);
}
// 2. 解析参数
// 2. 解析参数(数组格式)
IotDeviceTopoDeleteReqDTO params = JsonUtils.parseObject(JsonUtils.toJsonString(message.getParams()),
IotDeviceTopoDeleteReqDTO.class);
if (params == null) {
if (params == null || params.getSubDevices() == null || params.getSubDevices().isEmpty()) {
throw exception(DEVICE_TOPO_PARAMS_INVALID);
}
// 3. 获取子设备
IotDeviceDO subDevice = deviceService.getDeviceFromCache(params.getProductKey(), params.getDeviceName());
// 3. 遍历处理每个子设备
List<IotDeviceDO> deletedSubDevices = new ArrayList<>();
for (IotDeviceTopoDeleteReqDTO.SubDevice subDeviceInfo : params.getSubDevices()) {
try {
IotDeviceDO subDevice = processTopoDeleteSubDevice(subDeviceInfo, gatewayDevice);
deletedSubDevices.add(subDevice);
} catch (Exception ex) {
log.warn("[handleTopoDelete][网关({}/{}) 删除子设备失败productKey={}, deviceName={}]",
gatewayDevice.getProductKey(), gatewayDevice.getDeviceName(),
subDeviceInfo.getProductKey(), subDeviceInfo.getDeviceName(), ex);
}
}
// 4. 发送拓扑变更通知
for (IotDeviceDO subDevice : deletedSubDevices) {
sendTopoChangeNotify(gatewayDevice, "delete", subDevice);
}
return null;
}
// TODO @AI是不是更适合在 deviceService 里面处理?
/**
* 处理单个子设备的拓扑删除
*
* @param subDeviceInfo 子设备标识
* @param gatewayDevice 网关设备
* @return 删除成功的子设备,失败返回 null
*/
private IotDeviceDO processTopoDeleteSubDevice(IotDeviceTopoDeleteReqDTO.SubDevice subDeviceInfo,
IotDeviceDO gatewayDevice) {
// 1. 获取子设备
IotDeviceDO subDevice = deviceService.getDeviceFromCache(subDeviceInfo.getProductKey(), subDeviceInfo.getDeviceName());
if (subDevice == null) {
throw exception(DEVICE_NOT_EXISTS);
}
// 4. 校验子设备是否绑定到该网关
// 2. 校验子设备是否绑定到该网关
if (!Objects.equal(subDevice.getGatewayId(), gatewayDevice.getId())) {
throw exception(DEVICE_TOPO_SUB_NOT_BINDTO_GATEWAY, params.getProductKey(), params.getDeviceName());
throw exception(DEVICE_TOPO_SUB_NOT_BINDTO_GATEWAY, subDeviceInfo.getProductKey(), subDeviceInfo.getDeviceName());
}
// 5. 解绑拓扑关系
// 3. 解绑拓扑关系
deviceService.unbindDeviceGateway(Collections.singletonList(subDevice.getId()));
log.info("[handleTopoDelete][网关({}/{}) 解绑子设备({}/{})]",
log.info("[processTopoDeleteSubDevice][网关({}/{}) 解绑子设备({}/{})]",
gatewayDevice.getProductKey(), gatewayDevice.getDeviceName(),
subDevice.getProductKey(), subDevice.getDeviceName());
// 6. 子设备下线
// 4. 子设备下线
if (Objects.equal(subDevice.getState(), IotDeviceStateEnum.ONLINE.getState())) {
deviceService.updateDeviceState(subDevice, IotDeviceStateEnum.OFFLINE.getState());
}
// 7. 发送拓扑变更通知
sendTopoChangeNotify(gatewayDevice, "delete", subDevice);
return null;
return subDevice;
}
// TODO @AI是不是更适合在 deviceService 里面处理?
/**
* 处理获取拓扑关系请求
*
@@ -391,6 +444,7 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService {
.setDeviceName(subDevice.getDeviceName()));
}
// TODO @AI是不是更适合在 deviceService 里面处理?
/**
* 发送拓扑变更通知
*
@@ -427,6 +481,7 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService {
// ========== 子设备注册处理方法 ==========
// TODO @AI是不是更适合在 deviceService 里面处理?
/**
* 处理子设备动态注册请求
*
@@ -469,6 +524,7 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService {
return results;
}
// TODO @AI是不是更适合在 deviceService 里面处理?
/**
* 注册单个子设备
*
@@ -539,11 +595,12 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService {
}
// 2. 处理网关自身属性
// TODO @AI是不是经过总线会更好
// TODO @AI是不是少处理了 event 事件?
if (params.getProperties() != null && !params.getProperties().isEmpty()) {
Map<String, Object> gatewayProperties = convertPackProperties(params.getProperties());
IotDeviceMessage gatewayMsg = IotDeviceMessage.builder()
.id(IotDeviceMessageUtils.generateMessageId())
.parentMessageId(packMessage.getId())
.deviceId(gatewayDevice.getId())
.tenantId(gatewayDevice.getTenantId())
.serverId(packMessage.getServerId())
@@ -596,7 +653,6 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService {
Map<String, Object> properties = convertPackProperties(subDeviceData.getProperties());
IotDeviceMessage subMsg = IotDeviceMessage.builder()
.id(IotDeviceMessageUtils.generateMessageId())
.parentMessageId(packMessage.getId())
.deviceId(subDevice.getId())
.tenantId(subDevice.getTenantId())
.serverId(packMessage.getServerId())
@@ -608,8 +664,8 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService {
getSelf().createDeviceLogAsync(subMsg);
}
// 3. 处理子设备事件(如果需要)
// TODO: 事件处理可以后续扩展
// 3. 处理子设备事件
// TODO @AI事件处理可以后续扩展
}
/**
@@ -633,7 +689,7 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService {
// ========== 网关下线联动处理 ==========
// TODO @AI:是不是写到 deviceService 里更合适?更解耦。
// TODO 芋艿:是不是写到 deviceService 里更合适?更解耦。
/**
* 处理网关下线,联动所有子设备下线
*

View File

@@ -43,7 +43,6 @@ public enum IotDeviceMessageMethodEnum implements ArrayValuable<String> {
PROPERTY_POST("thing.property.post", "属性上报", true),
PROPERTY_SET("thing.property.set", "属性设置", false),
// TODO @AI改成 thing.property.pack.post
PROPERTY_PACK_POST("thing.event.property.pack.post", "批量上报(属性 + 事件 + 子设备)", true), // 网关独有
// ========== 设备事件 ==========
@@ -77,8 +76,7 @@ public enum IotDeviceMessageMethodEnum implements ArrayValuable<String> {
*/
public static final Set<String> REPLY_DISABLED = SetUtils.asSet(
STATE_UPDATE.getMethod(),
OTA_PROGRESS.getMethod(), // 参考阿里云OTA 升级进度上报,不进行回复
TOPO_CHANGE.getMethod() // 拓扑变更通知,下行消息,不需要回复 TODO @AI看看阿里云的文档确认下是不是这样的
OTA_PROGRESS.getMethod() // 参考阿里云OTA 升级进度上报,不进行回复
);
private final String method;

View File

@@ -94,42 +94,6 @@ public class IotDeviceMessage {
*/
private String msg;
/**
* 父消息 ID
* <p>
* - null原始消息需要 reply
* - 非 null从父消息如 pack拆分而来不需要单独 reply
*/
private String parentMessageId;
// TODO @TODO @AI抽到工具类里具体哪个一起讨论下
/**
* 判断是否需要发送 reply
*
* @return 是否需要回复
*/
public boolean needReply() {
// 1. 来自拆包的消息,不单独 reply
if (parentMessageId != null) {
return false;
}
// 2. 某些方法本身不需要 reply如 STATE_UPDATE
if (IotDeviceMessageMethodEnum.isReplyDisabled(method)) {
return false;
}
return true;
}
// TODO @AI没用的字段删除删除
/**
* 获取原始消息 ID用于日志追踪
*
* @return 原始消息 ID
*/
public String getOriginMessageId() {
return parentMessageId != null ? parentMessageId : id;
}
// ========== 基础方法:只传递"codec编解码字段" ==========
public static IotDeviceMessage requestOf(String method) {

View File

@@ -0,0 +1,6 @@
package cn.iocoder.yudao.module.iot.core.topic;
// TODO @AI增加 productKey、DeviceName
// TODO @AI有更合适的类名么
public class IotDeviceIdentify {
}

View File

@@ -3,14 +3,13 @@ package cn.iocoder.yudao.module.iot.core.topic.auth;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
// TODO @AI不用带 device 关键字;
// TODO @AI挂个阿里云的链接https://help.aliyun.com/zh/iot/user-guide/register-devices 的「子设备的 MQTT 动态注册」小节
/**
* IoT 子设备动态注册 Request DTO
* <p>
* 用于 thing.sub.register 消息的 params 数组元素
*
* @author 芋道源码
* @see <a href="http://help.aliyun.com/zh/marketplace/dynamic-registration-of-sub-devices">阿里云 - 动态注册子设备</a>
*/
@Data
public class IotSubDeviceRegisterReqDTO {

View File

@@ -2,13 +2,13 @@ package cn.iocoder.yudao.module.iot.core.topic.auth;
import lombok.Data;
// TODO @AI修复建议参考 /Users/yunai/Java/ruoyi-vue-pro-jdk25/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/auth/IotSubDeviceRegisterReqDTO.java
/**
* IoT 子设备动态注册 Response DTO
* <p>
* 用于 thing.sub.register 响应的设备信息
*
* @author 芋道源码
* @see <a href="http://help.aliyun.com/zh/marketplace/dynamic-registration-of-sub-devices">阿里云 - 动态注册子设备</a>
*/
@Data
public class IotSubDeviceRegisterRespDTO {

View File

@@ -1,4 +1,8 @@
/**
* TODO @AI写下注释
* 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;
package cn.iocoder.yudao.module.iot.core.topic;

View File

@@ -6,41 +6,18 @@ import lombok.experimental.Accessors;
import java.util.List;
import java.util.Map;
// TODO @AI挂个阿里云的链接http://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services 的「设备批量上报属性、事件」小节
/**
* IoT 设备属性批量上报 Request DTO
* <p>
* 用于 thing.event.property.pack.post 消息的 params 参数
* 参考阿里云 Alink 协议
*
* @author 芋道源码
* @see <a href="http://help.aliyun.com/zh/marketplace/gateway-reports-data-in-batches">阿里云 - 网关批量上报数据</a>
*/
@Data
public class IotDevicePropertyPackPostReqDTO {
// TODO @AI去掉里面的 time直接平铺值可能就是直接的 map例如说 "Power"value 而不是 PropertyValue "properties": {
// "Power": [
// {
// "value": "on",
// "time": 1524448722000
// },
// {
// "value": "off",
// "time": 1524448722001
// }
// ],
// "WF": [
// {
// "value": 3,
// "time": 1524448722000
// },
// {
// "value": 4,
// "time": 1524448722009
// }
// ]
// }
// TODO @AI不用 PropertyValue直接使用 Object 接收就行!
/**
* 网关自身属性
* <p>
@@ -49,27 +26,6 @@ public class IotDevicePropertyPackPostReqDTO {
*/
private Map<String, PropertyValue> properties;
// TODO @AIEventValue {
//
// "method": "thing.event.post",
//
// "version": "1.0",
//
// "params": {
//
// "identifier": "eat",
//
// "params": {
//
// "rice": 100
//
// }
//
// }
//
//}
/**
* 网关自身事件
* <p>

View File

@@ -1,39 +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;
// TODO @AI得一起讨论下到底使用什么后缀合适1一方面要体现出请求、响应2一方面体现出上下行设备 to server还是 server to 设备),可以一起讨论?
// TODO @AI文档地址https://help.aliyun.com/zh/iot/user-guide/manage-topological-relationships?spm=a2c4g.11186623.help-menu-30520.d_2_2_7_3_2.2e983f47Z2iGbo&scm=20140722.H_89299._.OR_help-T_cn~zh-V_1#section-w33-vyg-12b
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 {
// TODO @AI是个数组
// TODO @AI有响应结果的
/**
* 子设备客户端 ID
* 子设备认证信息列表
* <p>
* 复用 {@link IotDeviceAuthReqDTO},包含 clientId、username、password
*/
@NotEmpty(message = "客户端 ID 不能为空")
private String clientId;
/**
* 子设备用户名
*/
@NotEmpty(message = "用户名不能为空")
private String username;
/**
* 子设备认证密码
*/
@NotEmpty(message = "密码不能为空")
private String password;
@NotEmpty(message = "子设备认证信息列表不能为空")
private List<IotDeviceAuthReqDTO> subDevices;
}

View File

@@ -3,9 +3,7 @@ package cn.iocoder.yudao.module.iot.core.topic.topo;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
// TODO @AI是个数组https://help.aliyun.com/zh/iot/user-guide/manage-topological-relationships?spm=a2c4g.11186623.help-menu-30520.d_2_2_7_3_2.540c390beZSDOG&scm=20140722.H_89299._.OR_help-T_cn~zh-V_1#section-rb1-wzw-y2b
// todo @AI删除完需要有个 reply 响应;
import java.util.List;
/**
* IoT 设备拓扑删除 Request DTO
@@ -13,20 +11,35 @@ import lombok.Data;
* 用于 thing.topo.delete 消息的 params 参数
*
* @author 芋道源码
* @see <a href="https://help.aliyun.com/zh/marketplace/delete-a-topological-relationship">阿里云 - 删除拓扑关系</a>
*/
@Data
public class IotDeviceTopoDeleteReqDTO {
// TODO @AI应该是数组IotDeviceIdentify
/**
* 子设备 ProductKey
* 子设备标识列表
*/
@NotEmpty(message = "产品标识不能为空")
private String productKey;
private List<SubDevice> subDevices;
/**
* 子设备 DeviceName
* 子设备标识
*/
@NotEmpty(message = "设备名称不能为空")
private String deviceName;
@Data
public static class SubDevice {
/**
* 子设备 ProductKey
*/
@NotEmpty(message = "产品标识不能为空")
private String productKey;
/**
* 子设备 DeviceName
*/
@NotEmpty(message = "设备名称不能为空")
private String deviceName;
}
}

View File

@@ -2,17 +2,19 @@ package cn.iocoder.yudao.module.iot.core.topic.topo;
import lombok.Data;
// TODO @AI是不是改成 IotDeviceTopoGetRespDTO
// TODO @AIIotDeviceTopoGetRespDTO
/**
* IoT 设备拓扑关系 Response DTO
* <p>
* 用于 thing.topo.get 响应的子设备信息
*
* @author 芋道源码
* @see <a href="https://help.aliyun.com/zh/marketplace/obtain-topological-relationship">阿里云 - 获取拓扑关系</a>
*/
@Data
public class IotDeviceTopoRespDTO {
// TODO @AI应该是数组IotDeviceIdentify
/**
* 子设备 ProductKey
*/

View File

@@ -63,50 +63,4 @@ public final class IotMqttTopicUtils {
return SYS_TOPIC_PREFIX + productKey + "/" + deviceName + "/" + topicSuffix;
}
/**
* 构建拓扑管理 Topic
* <p>
* 拓扑管理类 Topic 使用网关设备的 productKey/deviceName
*
* @param method 方法,如 thing.topo.add
* @param gatewayProductKey 网关 ProductKey
* @param gatewayDeviceName 网关 DeviceName
* @param isReply 是否为响应
* @return Topic
*/
public static String buildTopoTopic(String method, String gatewayProductKey,
String gatewayDeviceName, boolean isReply) {
return buildTopicByMethod(method, gatewayProductKey, gatewayDeviceName, isReply);
}
/**
* 判断是否为拓扑管理 Topic通过 method 判断)
*
* @param method 消息方法
* @return 是否为拓扑管理 Topic
*/
public static boolean isTopoMethod(String method) {
return method != null && method.startsWith("thing.topo.");
}
/**
* 判断是否为子设备注册 Topic
*
* @param method 消息方法
* @return 是否为子设备注册 Topic
*/
public static boolean isSubDeviceRegisterMethod(String method) {
return "thing.sub.register".equals(method);
}
/**
* 判断是否为批量上报 Topic
*
* @param method 消息方法
* @return 是否为批量上报 Topic
*/
public static boolean isPackPostMethod(String method) {
return "thing.event.property.pack.post".equals(method);
}
}