From 18ed7b50beedd93331ee42bec0ccb4db7ca248c0 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 24 Jan 2026 09:15:01 +0800 Subject: [PATCH] feat(iot):【网关设备:60%】整体初步实现(优化部分代码) --- yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java | 2 -- yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java | 2 +- yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/message/IotDeviceMessageServiceImpl.java | 150 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------------------------- yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotDeviceMessageMethodEnum.java | 4 +--- yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/mq/message/IotDeviceMessage.java | 36 ------------------------------------ yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/IotDeviceIdentify.java | 6 ++++++ yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/auth/IotSubDeviceRegisterReqDTO.java | 3 +-- yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/auth/IotSubDeviceRegisterRespDTO.java | 2 +- yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/package-info.java | 8 ++++++-- yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/property/IotDevicePropertyPackPostReqDTO.java | 48 ++---------------------------------------------- yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/topo/IotDeviceTopoAddReqDTO.java | 29 +++++++++-------------------- yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/topo/IotDeviceTopoDeleteReqDTO.java | 31 ++++++++++++++++++++++--------- yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/topo/IotDeviceTopoRespDTO.java | 4 +++- yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/util/IotMqttTopicUtils.java | 46 ---------------------------------------------- 14 files changed, 155 insertions(+), 216 deletions(-) create mode 100644 yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/IotDeviceIdentify.java diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java index 202e2d0..2e74ebc 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java @@ -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, "产品【{}】不是网关子设备类型"); diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java index 41ea380..569085f 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java @@ -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; } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/message/IotDeviceMessageServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/message/IotDeviceMessageServiceImpl.java index ff34921..302b4bf 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/message/IotDeviceMessageServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/message/IotDeviceMessageServiceImpl.java @@ -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 @AI:processTopoAddSubDevice 不要抽成小方法; + List 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 @AI:http://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 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 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 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 里更合适?更解耦。 /** * 处理网关下线,联动所有子设备下线 * diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotDeviceMessageMethodEnum.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotDeviceMessageMethodEnum.java index 7b3d693..c65c6f4 100644 --- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotDeviceMessageMethodEnum.java +++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotDeviceMessageMethodEnum.java @@ -43,7 +43,6 @@ public enum IotDeviceMessageMethodEnum implements ArrayValuable { 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 { */ public static final Set REPLY_DISABLED = SetUtils.asSet( STATE_UPDATE.getMethod(), - OTA_PROGRESS.getMethod(), // 参考阿里云,OTA 升级进度上报,不进行回复 - TOPO_CHANGE.getMethod() // 拓扑变更通知,下行消息,不需要回复 TODO @AI:看看阿里云的文档,确认下是不是这样的 + OTA_PROGRESS.getMethod() // 参考阿里云,OTA 升级进度上报,不进行回复 ); private final String method; diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/mq/message/IotDeviceMessage.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/mq/message/IotDeviceMessage.java index b158c8c..6821c0d 100644 --- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/mq/message/IotDeviceMessage.java +++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/mq/message/IotDeviceMessage.java @@ -94,42 +94,6 @@ public class IotDeviceMessage { */ private String msg; - /** - * 父消息 ID - *

- * - 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) { diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/IotDeviceIdentify.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/IotDeviceIdentify.java new file mode 100644 index 0000000..98396bc --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/IotDeviceIdentify.java @@ -0,0 +1,6 @@ +package cn.iocoder.yudao.module.iot.core.topic; + +// TODO @AI:增加 productKey、DeviceName +// TODO @AI:有更合适的类名么??? +public class IotDeviceIdentify { +} diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/auth/IotSubDeviceRegisterReqDTO.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/auth/IotSubDeviceRegisterReqDTO.java index 5bf7ee6..765780a 100644 --- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/auth/IotSubDeviceRegisterReqDTO.java +++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/auth/IotSubDeviceRegisterReqDTO.java @@ -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 *

* 用于 thing.sub.register 消息的 params 数组元素 * * @author 芋道源码 + * @see 阿里云 - 动态注册子设备 */ @Data public class IotSubDeviceRegisterReqDTO { diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/auth/IotSubDeviceRegisterRespDTO.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/auth/IotSubDeviceRegisterRespDTO.java index 935cea4..cbf3289 100644 --- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/auth/IotSubDeviceRegisterRespDTO.java +++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/auth/IotSubDeviceRegisterRespDTO.java @@ -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 *

* 用于 thing.sub.register 响应的设备信息 * * @author 芋道源码 + * @see 阿里云 - 动态注册子设备 */ @Data public class IotSubDeviceRegisterRespDTO { diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/package-info.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/package-info.java index c531199..bc97dd9 100644 --- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/package-info.java +++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/package-info.java @@ -1,4 +1,8 @@ /** - * TODO @AI:写下注释 + * IoT Topic 消息体 DTO 定义 + *

+ * 定义设备与平台通信的消息体结构,遵循(参考)阿里云 Alink 协议规范 + * + * @see 阿里云 Alink 协议 */ -package cn.iocoder.yudao.module.iot.core.topic; \ No newline at end of file +package cn.iocoder.yudao.module.iot.core.topic; diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/property/IotDevicePropertyPackPostReqDTO.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/property/IotDevicePropertyPackPostReqDTO.java index bb56911..bfd214e 100644 --- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/property/IotDevicePropertyPackPostReqDTO.java +++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/property/IotDevicePropertyPackPostReqDTO.java @@ -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 *

* 用于 thing.event.property.pack.post 消息的 params 参数 - * 参考阿里云 Alink 协议 * * @author 芋道源码 + * @see 阿里云 - 网关批量上报数据 */ @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 接收就行! /** * 网关自身属性 *

@@ -49,27 +26,6 @@ public class IotDevicePropertyPackPostReqDTO { */ private Map properties; - // TODO @AI:EventValue { - // - // "method": "thing.event.post", - // - // "version": "1.0", - // - // "params": { - // - // "identifier": "eat", - // - // "params": { - // - // "rice": 100 - // - // } - // - // } - // - //} - - /** * 网关自身事件 *

diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/topo/IotDeviceTopoAddReqDTO.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/topo/IotDeviceTopoAddReqDTO.java index 4049ded..97ec332 100644 --- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/topo/IotDeviceTopoAddReqDTO.java +++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/topo/IotDeviceTopoAddReqDTO.java @@ -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 *

* 用于 thing.topo.add 消息的 params 参数 * * @author 芋道源码 + * @see 阿里云 - 添加拓扑关系 */ @Data public class IotDeviceTopoAddReqDTO { - // TODO @AI:是个数组; - // TODO @AI:有响应结果的; - - /** - * 子设备客户端 ID - */ - @NotEmpty(message = "客户端 ID 不能为空") - private String clientId; - - /** - * 子设备用户名 - */ - @NotEmpty(message = "用户名不能为空") - private String username; - /** - * 子设备认证密码 + * 子设备认证信息列表 + *

+ * 复用 {@link IotDeviceAuthReqDTO},包含 clientId、username、password */ - @NotEmpty(message = "密码不能为空") - private String password; + @NotEmpty(message = "子设备认证信息列表不能为空") + private List subDevices; } diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/topo/IotDeviceTopoDeleteReqDTO.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/topo/IotDeviceTopoDeleteReqDTO.java index e16360c..11de07f 100644 --- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/topo/IotDeviceTopoDeleteReqDTO.java +++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/topo/IotDeviceTopoDeleteReqDTO.java @@ -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 阿里云 - 删除拓扑关系 */ @Data public class IotDeviceTopoDeleteReqDTO { + // TODO @AI:应该是数组;IotDeviceIdentify /** - * 子设备 ProductKey + * 子设备标识列表 */ - @NotEmpty(message = "产品标识不能为空") - private String productKey; + private List subDevices; /** - * 子设备 DeviceName + * 子设备标识 */ - @NotEmpty(message = "设备名称不能为空") - private String deviceName; + @Data + public static class SubDevice { + + /** + * 子设备 ProductKey + */ + @NotEmpty(message = "产品标识不能为空") + private String productKey; + + /** + * 子设备 DeviceName + */ + @NotEmpty(message = "设备名称不能为空") + private String deviceName; + + } } diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/topo/IotDeviceTopoRespDTO.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/topo/IotDeviceTopoRespDTO.java index c672323..79302b6 100644 --- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/topo/IotDeviceTopoRespDTO.java +++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/topo/IotDeviceTopoRespDTO.java @@ -2,17 +2,19 @@ package cn.iocoder.yudao.module.iot.core.topic.topo; import lombok.Data; -// TODO @AI:是不是改成 IotDeviceTopoGetRespDTO +// TODO @AI:IotDeviceTopoGetRespDTO /** * IoT 设备拓扑关系 Response DTO *

* 用于 thing.topo.get 响应的子设备信息 * * @author 芋道源码 + * @see 阿里云 - 获取拓扑关系 */ @Data public class IotDeviceTopoRespDTO { + // TODO @AI:应该是数组;IotDeviceIdentify /** * 子设备 ProductKey */ diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/util/IotMqttTopicUtils.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/util/IotMqttTopicUtils.java index c9cf1df..7f72937 100644 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/util/IotMqttTopicUtils.java +++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/util/IotMqttTopicUtils.java @@ -63,50 +63,4 @@ public final class IotMqttTopicUtils { return SYS_TOPIC_PREFIX + productKey + "/" + deviceName + "/" + topicSuffix; } - /** - * 构建拓扑管理 Topic - *

- * 拓扑管理类 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); - } - } \ No newline at end of file -- libgit2 0.21.4