Commit bfbc352a1ca80c7ddedd5dfa535b4083218ebe86
1 parent
18ed7b50
feat(iot):【网关设备:65%】整体初步实现(优化部分代码)
Showing
14 changed files
with
579 additions
and
556 deletions
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/JsonUtils.java
| @@ -229,4 +229,37 @@ public class JsonUtils { | @@ -229,4 +229,37 @@ public class JsonUtils { | ||
| 229 | return JSONUtil.isTypeJSONObject(str); | 229 | return JSONUtil.isTypeJSONObject(str); |
| 230 | } | 230 | } |
| 231 | 231 | ||
| 232 | + /** | ||
| 233 | + * 将 Object 转换为目标类型 | ||
| 234 | + * <p> | ||
| 235 | + * 避免先转 jsonString 再 parseObject 的性能损耗 | ||
| 236 | + * | ||
| 237 | + * @param obj 源对象(可以是 Map、POJO 等) | ||
| 238 | + * @param clazz 目标类型 | ||
| 239 | + * @return 转换后的对象 | ||
| 240 | + */ | ||
| 241 | + public static <T> T convertObject(Object obj, Class<T> clazz) { | ||
| 242 | + if (obj == null) { | ||
| 243 | + return null; | ||
| 244 | + } | ||
| 245 | + if (clazz.isInstance(obj)) { | ||
| 246 | + return clazz.cast(obj); | ||
| 247 | + } | ||
| 248 | + return objectMapper.convertValue(obj, clazz); | ||
| 249 | + } | ||
| 250 | + | ||
| 251 | + /** | ||
| 252 | + * 将 Object 转换为目标类型(支持泛型) | ||
| 253 | + * | ||
| 254 | + * @param obj 源对象 | ||
| 255 | + * @param typeReference 目标类型引用 | ||
| 256 | + * @return 转换后的对象 | ||
| 257 | + */ | ||
| 258 | + public static <T> T convertObject(Object obj, TypeReference<T> typeReference) { | ||
| 259 | + if (obj == null) { | ||
| 260 | + return null; | ||
| 261 | + } | ||
| 262 | + return objectMapper.convertValue(obj, typeReference); | ||
| 263 | + } | ||
| 264 | + | ||
| 232 | } | 265 | } |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java
| @@ -4,6 +4,10 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; | @@ -4,6 +4,10 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||
| 4 | import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.*; | 4 | import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.*; |
| 5 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; | 5 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; |
| 6 | import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum; | 6 | import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum; |
| 7 | +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | ||
| 8 | +import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity; | ||
| 9 | +import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterRespDTO; | ||
| 10 | +import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoGetRespDTO; | ||
| 7 | import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; | 11 | import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; |
| 8 | import jakarta.validation.Valid; | 12 | import jakarta.validation.Valid; |
| 9 | 13 | ||
| @@ -321,4 +325,41 @@ public interface IotDeviceService { | @@ -321,4 +325,41 @@ public interface IotDeviceService { | ||
| 321 | */ | 325 | */ |
| 322 | List<IotDeviceDO> getDeviceListByGatewayId(Long gatewayId); | 326 | List<IotDeviceDO> getDeviceListByGatewayId(Long gatewayId); |
| 323 | 327 | ||
| 328 | + // ========== 网关-拓扑管理(设备上报) ========== | ||
| 329 | + | ||
| 330 | + /** | ||
| 331 | + * 处理添加拓扑关系消息(网关设备上报) | ||
| 332 | + * | ||
| 333 | + * @param message 消息 | ||
| 334 | + * @param gatewayDevice 网关设备 | ||
| 335 | + * @return 成功添加的子设备列表 | ||
| 336 | + */ | ||
| 337 | + List<IotDeviceIdentity> handleTopoAddMessage(IotDeviceMessage message, IotDeviceDO gatewayDevice); | ||
| 338 | + | ||
| 339 | + /** | ||
| 340 | + * 处理删除拓扑关系消息(网关设备上报) | ||
| 341 | + * | ||
| 342 | + * @param message 消息 | ||
| 343 | + * @param gatewayDevice 网关设备 | ||
| 344 | + * @return 成功删除的子设备列表 | ||
| 345 | + */ | ||
| 346 | + List<IotDeviceIdentity> handleTopoDeleteMessage(IotDeviceMessage message, IotDeviceDO gatewayDevice); | ||
| 347 | + | ||
| 348 | + /** | ||
| 349 | + * 处理获取拓扑关系消息(网关设备上报) | ||
| 350 | + * | ||
| 351 | + * @param gatewayDevice 网关设备 | ||
| 352 | + * @return 拓扑关系响应 | ||
| 353 | + */ | ||
| 354 | + IotDeviceTopoGetRespDTO handleTopoGetMessage(IotDeviceDO gatewayDevice); | ||
| 355 | + | ||
| 356 | + /** | ||
| 357 | + * 处理子设备动态注册消息(网关设备上报) | ||
| 358 | + * | ||
| 359 | + * @param message 消息 | ||
| 360 | + * @param gatewayDevice 网关设备 | ||
| 361 | + * @return 注册结果列表 | ||
| 362 | + */ | ||
| 363 | + List<IotSubDeviceRegisterRespDTO> handleSubDeviceRegisterMessage(IotDeviceMessage message, IotDeviceDO gatewayDevice); | ||
| 364 | + | ||
| 324 | } | 365 | } |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java
| @@ -7,6 +7,7 @@ import cn.hutool.core.util.StrUtil; | @@ -7,6 +7,7 @@ import cn.hutool.core.util.StrUtil; | ||
| 7 | import cn.hutool.extra.spring.SpringUtil; | 7 | import cn.hutool.extra.spring.SpringUtil; |
| 8 | import cn.iocoder.yudao.framework.common.exception.ServiceException; | 8 | import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| 9 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 9 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 10 | +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | ||
| 10 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | 11 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 11 | import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; | 12 | import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; |
| 12 | import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; | 13 | import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; |
| @@ -14,6 +15,13 @@ import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; | @@ -14,6 +15,13 @@ import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; | ||
| 14 | import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.*; | 15 | import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.*; |
| 15 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; | 16 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; |
| 16 | import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum; | 17 | import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum; |
| 18 | +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | ||
| 19 | +import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity; | ||
| 20 | +import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterReqDTO; | ||
| 21 | +import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterRespDTO; | ||
| 22 | +import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoAddReqDTO; | ||
| 23 | +import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoDeleteReqDTO; | ||
| 24 | +import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoGetRespDTO; | ||
| 17 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; | 25 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; |
| 18 | import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; | 26 | import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; |
| 19 | import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceGroupDO; | 27 | import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceGroupDO; |
| @@ -64,6 +72,10 @@ public class IotDeviceServiceImpl implements IotDeviceService { | @@ -64,6 +72,10 @@ public class IotDeviceServiceImpl implements IotDeviceService { | ||
| 64 | 72 | ||
| 65 | @Override | 73 | @Override |
| 66 | public Long createDevice(IotDeviceSaveReqVO createReqVO) { | 74 | public Long createDevice(IotDeviceSaveReqVO createReqVO) { |
| 75 | + return createDevice0(createReqVO).getId(); | ||
| 76 | + } | ||
| 77 | + | ||
| 78 | + private IotDeviceDO createDevice0(IotDeviceSaveReqVO createReqVO) { | ||
| 67 | // 1.1 校验产品是否存在 | 79 | // 1.1 校验产品是否存在 |
| 68 | IotProductDO product = productService.getProduct(createReqVO.getProductId()); | 80 | IotProductDO product = productService.getProduct(createReqVO.getProductId()); |
| 69 | if (product == null) { | 81 | if (product == null) { |
| @@ -81,7 +93,7 @@ public class IotDeviceServiceImpl implements IotDeviceService { | @@ -81,7 +93,7 @@ public class IotDeviceServiceImpl implements IotDeviceService { | ||
| 81 | IotDeviceDO device = BeanUtils.toBean(createReqVO, IotDeviceDO.class); | 93 | IotDeviceDO device = BeanUtils.toBean(createReqVO, IotDeviceDO.class); |
| 82 | initDevice(device, product); | 94 | initDevice(device, product); |
| 83 | deviceMapper.insert(device); | 95 | deviceMapper.insert(device); |
| 84 | - return device.getId(); | 96 | + return device; |
| 85 | } | 97 | } |
| 86 | 98 | ||
| 87 | private void validateCreateDeviceParam(String productKey, String deviceName, | 99 | private void validateCreateDeviceParam(String productKey, String deviceName, |
| @@ -298,6 +310,37 @@ public class IotDeviceServiceImpl implements IotDeviceService { | @@ -298,6 +310,37 @@ public class IotDeviceServiceImpl implements IotDeviceService { | ||
| 298 | 310 | ||
| 299 | // 2. 清空对应缓存 | 311 | // 2. 清空对应缓存 |
| 300 | deleteDeviceCache(device); | 312 | deleteDeviceCache(device); |
| 313 | + | ||
| 314 | + // 3. 网关设备下线时,联动所有子设备下线 | ||
| 315 | + if (Objects.equals(state, IotDeviceStateEnum.OFFLINE.getState()) | ||
| 316 | + && IotProductDeviceTypeEnum.isGateway(device.getDeviceType())) { | ||
| 317 | + handleGatewayOffline(device); | ||
| 318 | + } | ||
| 319 | + } | ||
| 320 | + | ||
| 321 | + /** | ||
| 322 | + * 处理网关下线,联动所有子设备下线 | ||
| 323 | + * | ||
| 324 | + * @param gatewayDevice 网关设备 | ||
| 325 | + */ | ||
| 326 | + private void handleGatewayOffline(IotDeviceDO gatewayDevice) { | ||
| 327 | + List<IotDeviceDO> subDevices = deviceMapper.selectListByGatewayId(gatewayDevice.getId()); | ||
| 328 | + if (CollUtil.isEmpty(subDevices)) { | ||
| 329 | + return; | ||
| 330 | + } | ||
| 331 | + for (IotDeviceDO subDevice : subDevices) { | ||
| 332 | + if (Objects.equals(subDevice.getState(), IotDeviceStateEnum.ONLINE.getState())) { | ||
| 333 | + try { | ||
| 334 | + updateDeviceState(subDevice, IotDeviceStateEnum.OFFLINE.getState()); | ||
| 335 | + log.info("[handleGatewayOffline][网关({}/{}) 下线,子设备({}/{}) 联动下线]", | ||
| 336 | + gatewayDevice.getProductKey(), gatewayDevice.getDeviceName(), | ||
| 337 | + subDevice.getProductKey(), subDevice.getDeviceName()); | ||
| 338 | + } catch (Exception ex) { | ||
| 339 | + log.error("[handleGatewayOffline][子设备({}/{}) 下线失败]", | ||
| 340 | + subDevice.getProductKey(), subDevice.getDeviceName(), ex); | ||
| 341 | + } | ||
| 342 | + } | ||
| 343 | + } | ||
| 301 | } | 344 | } |
| 302 | 345 | ||
| 303 | @Override | 346 | @Override |
| @@ -533,19 +576,10 @@ public class IotDeviceServiceImpl implements IotDeviceService { | @@ -533,19 +576,10 @@ public class IotDeviceServiceImpl implements IotDeviceService { | ||
| 533 | } | 576 | } |
| 534 | // 1.1 校验网关设备存在且类型正确 | 577 | // 1.1 校验网关设备存在且类型正确 |
| 535 | validateGatewayDeviceExists(gatewayId); | 578 | validateGatewayDeviceExists(gatewayId); |
| 536 | - // 1.2 校验子设备存在 | 579 | + // 1.2 校验每个设备是否可绑定 |
| 537 | List<IotDeviceDO> devices = deviceMapper.selectByIds(ids); | 580 | List<IotDeviceDO> devices = deviceMapper.selectByIds(ids); |
| 538 | - if (devices.size() != ids.size()) { | ||
| 539 | - throw exception(DEVICE_NOT_EXISTS); | ||
| 540 | - } | ||
| 541 | - // 1.3 校验每个设备是否可绑定 | ||
| 542 | for (IotDeviceDO device : devices) { | 581 | for (IotDeviceDO device : devices) { |
| 543 | - if (!IotProductDeviceTypeEnum.isGatewaySub(device.getDeviceType())) { | ||
| 544 | - throw exception(DEVICE_NOT_GATEWAY_SUB, device.getProductKey(), device.getDeviceName()); | ||
| 545 | - } | ||
| 546 | - if (device.getGatewayId() != null && !device.getGatewayId().equals(gatewayId)) { | ||
| 547 | - throw exception(DEVICE_GATEWAY_BINDTO_EXISTS, device.getProductKey(), device.getDeviceName()); | ||
| 548 | - } | 582 | + checkSubDeviceCanBind(device, gatewayId); |
| 549 | } | 583 | } |
| 550 | 584 | ||
| 551 | // 2. 批量更新数据库 | 585 | // 2. 批量更新数据库 |
| @@ -555,21 +589,33 @@ public class IotDeviceServiceImpl implements IotDeviceService { | @@ -555,21 +589,33 @@ public class IotDeviceServiceImpl implements IotDeviceService { | ||
| 555 | 589 | ||
| 556 | // 3. 清空对应缓存 | 590 | // 3. 清空对应缓存 |
| 557 | deleteDeviceCache(devices); | 591 | deleteDeviceCache(devices); |
| 592 | + | ||
| 593 | + // TODO @AI:需要下发网关设备,让其建立拓扑关系吗?(增加) | ||
| 594 | + } | ||
| 595 | + | ||
| 596 | + private void checkSubDeviceCanBind(IotDeviceDO device, Long gatewayId) { | ||
| 597 | + if (!IotProductDeviceTypeEnum.isGatewaySub(device.getDeviceType())) { | ||
| 598 | + throw exception(DEVICE_NOT_GATEWAY_SUB, device.getProductKey(), device.getDeviceName()); | ||
| 599 | + } | ||
| 600 | + if (ObjUtil.equals(device.getGatewayId(), gatewayId)) { | ||
| 601 | + throw exception(DEVICE_GATEWAY_BINDTO_EXISTS, device.getProductKey(), device.getDeviceName()); | ||
| 602 | + } | ||
| 558 | } | 603 | } |
| 559 | 604 | ||
| 560 | @Override | 605 | @Override |
| 561 | @Transactional(rollbackFor = Exception.class) | 606 | @Transactional(rollbackFor = Exception.class) |
| 562 | public void unbindDeviceGateway(Collection<Long> ids) { | 607 | public void unbindDeviceGateway(Collection<Long> ids) { |
| 608 | + // 1. 校验设备存在 | ||
| 563 | if (CollUtil.isEmpty(ids)) { | 609 | if (CollUtil.isEmpty(ids)) { |
| 564 | return; | 610 | return; |
| 565 | } | 611 | } |
| 566 | - // 1. 校验设备存在 | ||
| 567 | List<IotDeviceDO> devices = deviceMapper.selectByIds(ids); | 612 | List<IotDeviceDO> devices = deviceMapper.selectByIds(ids); |
| 568 | - if (devices.size() != ids.size()) { | ||
| 569 | - throw exception(DEVICE_NOT_EXISTS); | 613 | + if (CollUtil.isNotEmpty(devices)) { |
| 614 | + return; | ||
| 570 | } | 615 | } |
| 571 | 616 | ||
| 572 | // 2. 批量更新数据库(将 gatewayId 设置为 null) | 617 | // 2. 批量更新数据库(将 gatewayId 设置为 null) |
| 618 | + // TODO @AI:需要搞个方法,专门批量更新某个字段为 null。 | ||
| 573 | List<IotDeviceDO> updateList = devices.stream() | 619 | List<IotDeviceDO> updateList = devices.stream() |
| 574 | .filter(device -> device.getGatewayId() != null) | 620 | .filter(device -> device.getGatewayId() != null) |
| 575 | .map(device -> new IotDeviceDO().setId(device.getId()).setGatewayId(null)) | 621 | .map(device -> new IotDeviceDO().setId(device.getId()).setGatewayId(null)) |
| @@ -580,6 +626,8 @@ public class IotDeviceServiceImpl implements IotDeviceService { | @@ -580,6 +626,8 @@ public class IotDeviceServiceImpl implements IotDeviceService { | ||
| 580 | 626 | ||
| 581 | // 3. 清空对应缓存 | 627 | // 3. 清空对应缓存 |
| 582 | deleteDeviceCache(devices); | 628 | deleteDeviceCache(devices); |
| 629 | + | ||
| 630 | + // TODO @AI:需要下发网关设备,让其建立拓扑关系吗?(减少) | ||
| 583 | } | 631 | } |
| 584 | 632 | ||
| 585 | @Override | 633 | @Override |
| @@ -592,6 +640,202 @@ public class IotDeviceServiceImpl implements IotDeviceService { | @@ -592,6 +640,202 @@ public class IotDeviceServiceImpl implements IotDeviceService { | ||
| 592 | return deviceMapper.selectListByGatewayId(gatewayId); | 640 | return deviceMapper.selectListByGatewayId(gatewayId); |
| 593 | } | 641 | } |
| 594 | 642 | ||
| 643 | + // ========== 网关-拓扑管理(设备上报) ========== | ||
| 644 | + | ||
| 645 | + @Override | ||
| 646 | + public List<IotDeviceIdentity> handleTopoAddMessage(IotDeviceMessage message, IotDeviceDO gatewayDevice) { | ||
| 647 | + // 1.1 校验网关设备类型 | ||
| 648 | + if (!IotProductDeviceTypeEnum.isGateway(gatewayDevice.getDeviceType())) { | ||
| 649 | + throw exception(DEVICE_NOT_GATEWAY); | ||
| 650 | + } | ||
| 651 | + // 1.2 解析参数 | ||
| 652 | + IotDeviceTopoAddReqDTO params = JsonUtils.parseObject(JsonUtils.toJsonString(message.getParams()), | ||
| 653 | + IotDeviceTopoAddReqDTO.class); | ||
| 654 | + if (params == null || CollUtil.isEmpty(params.getSubDevices())) { | ||
| 655 | + throw exception(DEVICE_TOPO_PARAMS_INVALID); | ||
| 656 | + } | ||
| 657 | + | ||
| 658 | + // 2. 遍历处理每个子设备 | ||
| 659 | + List<IotDeviceIdentity> addedSubDevices = new ArrayList<>(); | ||
| 660 | + for (IotDeviceAuthReqDTO subDeviceAuth : params.getSubDevices()) { | ||
| 661 | + try { | ||
| 662 | + IotDeviceDO subDevice = addDeviceTopo(gatewayDevice, subDeviceAuth); | ||
| 663 | + addedSubDevices.add(new IotDeviceIdentity(subDevice.getProductKey(), subDevice.getDeviceName())); | ||
| 664 | + } catch (Exception ex) { | ||
| 665 | + log.warn("[handleTopoAddMessage][网关({}/{}) 添加子设备失败,message={}]", | ||
| 666 | + gatewayDevice.getProductKey(), gatewayDevice.getDeviceName(), message, ex); | ||
| 667 | + } | ||
| 668 | + } | ||
| 669 | + | ||
| 670 | + // 3. 返回响应数据(包含成功添加的子设备列表) | ||
| 671 | + return addedSubDevices; | ||
| 672 | + } | ||
| 673 | + | ||
| 674 | + private IotDeviceDO addDeviceTopo(IotDeviceDO gatewayDevice, IotDeviceAuthReqDTO subDeviceAuth) { | ||
| 675 | + // 1.1 解析子设备信息 | ||
| 676 | + IotDeviceAuthUtils.DeviceInfo subDeviceInfo = IotDeviceAuthUtils.parseUsername(subDeviceAuth.getUsername()); | ||
| 677 | + if (subDeviceInfo == null) { | ||
| 678 | + throw exception(DEVICE_TOPO_SUB_DEVICE_USERNAME_INVALID); | ||
| 679 | + } | ||
| 680 | + // 1.2 校验子设备认证信息 | ||
| 681 | + if (!authDevice(subDeviceAuth)) { | ||
| 682 | + throw exception(DEVICE_TOPO_SUB_DEVICE_AUTH_FAILED); | ||
| 683 | + } | ||
| 684 | + // 1.3 获取子设备 | ||
| 685 | + IotDeviceDO subDevice = getSelf().getDeviceFromCache(subDeviceInfo.getProductKey(), subDeviceInfo.getDeviceName()); | ||
| 686 | + if (subDevice == null) { | ||
| 687 | + throw exception(DEVICE_NOT_EXISTS); | ||
| 688 | + } | ||
| 689 | + // 1.4 校验子设备类型 | ||
| 690 | + checkSubDeviceCanBind(subDevice, gatewayDevice.getId()); | ||
| 691 | + | ||
| 692 | + // 2. 更新数据库 | ||
| 693 | + deviceMapper.updateById(new IotDeviceDO().setId(subDevice.getId()).setGatewayId(subDevice.getGatewayId())); | ||
| 694 | + log.info("[addDeviceTopo][网关({}/{}) 绑定子设备({}/{})]", | ||
| 695 | + gatewayDevice.getProductKey(), gatewayDevice.getDeviceName(), | ||
| 696 | + subDevice.getProductKey(), subDevice.getDeviceName()); | ||
| 697 | + | ||
| 698 | + // 3. 清空对应缓存 | ||
| 699 | + deleteDeviceCache(subDevice); | ||
| 700 | + return subDevice; | ||
| 701 | + } | ||
| 702 | + | ||
| 703 | + @Override | ||
| 704 | + public List<IotDeviceIdentity> handleTopoDeleteMessage(IotDeviceMessage message, IotDeviceDO gatewayDevice) { | ||
| 705 | + // 1.1 校验网关设备类型 | ||
| 706 | + if (!IotProductDeviceTypeEnum.isGateway(gatewayDevice.getDeviceType())) { | ||
| 707 | + throw exception(DEVICE_NOT_GATEWAY); | ||
| 708 | + } | ||
| 709 | + // 1.2 解析参数 | ||
| 710 | + IotDeviceTopoDeleteReqDTO params = JsonUtils.parseObject(JsonUtils.toJsonString(message.getParams()), | ||
| 711 | + IotDeviceTopoDeleteReqDTO.class); | ||
| 712 | + if (params == null || CollUtil.isEmpty(params.getSubDevices())) { | ||
| 713 | + throw exception(DEVICE_TOPO_PARAMS_INVALID); | ||
| 714 | + } | ||
| 715 | + | ||
| 716 | + // 2. 遍历处理每个子设备 | ||
| 717 | + List<IotDeviceIdentity> deletedSubDevices = new ArrayList<>(); | ||
| 718 | + for (IotDeviceIdentity subDeviceIdentity : params.getSubDevices()) { | ||
| 719 | + try { | ||
| 720 | + deleteDeviceTopo(gatewayDevice, subDeviceIdentity); | ||
| 721 | + deletedSubDevices.add(subDeviceIdentity); | ||
| 722 | + } catch (Exception ex) { | ||
| 723 | + log.warn("[handleTopoDeleteMessage][网关({}/{}) 删除子设备失败,productKey={}, deviceName={}]", | ||
| 724 | + gatewayDevice.getProductKey(), gatewayDevice.getDeviceName(), | ||
| 725 | + subDeviceIdentity.getProductKey(), subDeviceIdentity.getDeviceName(), ex); | ||
| 726 | + } | ||
| 727 | + } | ||
| 728 | + | ||
| 729 | + // 3. 返回响应数据(包含成功删除的子设备列表) | ||
| 730 | + return deletedSubDevices; | ||
| 731 | + } | ||
| 732 | + | ||
| 733 | + private void deleteDeviceTopo(IotDeviceDO gatewayDevice, IotDeviceIdentity subDeviceIdentity) { | ||
| 734 | + // 1.1 获取子设备 | ||
| 735 | + IotDeviceDO subDevice = getSelf().getDeviceFromCache(subDeviceIdentity.getProductKey(), subDeviceIdentity.getDeviceName()); | ||
| 736 | + if (subDevice == null) { | ||
| 737 | + throw exception(DEVICE_NOT_EXISTS); | ||
| 738 | + } | ||
| 739 | + // 1.2 校验子设备是否绑定到该网关 | ||
| 740 | + if (ObjUtil.notEqual(subDevice.getGatewayId(), gatewayDevice.getId())) { | ||
| 741 | + throw exception(DEVICE_TOPO_SUB_NOT_BINDTO_GATEWAY, | ||
| 742 | + subDeviceIdentity.getProductKey(), subDeviceIdentity.getDeviceName()); | ||
| 743 | + } | ||
| 744 | + | ||
| 745 | + // 2. 更新数据库 | ||
| 746 | + // TODO @AI:直接调用更新方法; | ||
| 747 | +// unbindDeviceGateway(Collections.singletonList(subDevice.getId())); | ||
| 748 | + log.info("[deleteDeviceTopo][网关({}/{}) 解绑子设备({}/{})]", | ||
| 749 | + gatewayDevice.getProductKey(), gatewayDevice.getDeviceName(), | ||
| 750 | + subDevice.getProductKey(), subDevice.getDeviceName()); | ||
| 751 | + | ||
| 752 | + // 3. 清空对应缓存 | ||
| 753 | + deleteDeviceCache(subDevice); | ||
| 754 | + | ||
| 755 | + // 4. 子设备下线 | ||
| 756 | + if (Objects.equals(subDevice.getState(), IotDeviceStateEnum.ONLINE.getState())) { | ||
| 757 | + updateDeviceState(subDevice, IotDeviceStateEnum.OFFLINE.getState()); | ||
| 758 | + } | ||
| 759 | + } | ||
| 760 | + | ||
| 761 | + @Override | ||
| 762 | + public IotDeviceTopoGetRespDTO handleTopoGetMessage(IotDeviceDO gatewayDevice) { | ||
| 763 | + // 1. 校验网关设备类型 | ||
| 764 | + if (!IotProductDeviceTypeEnum.isGateway(gatewayDevice.getDeviceType())) { | ||
| 765 | + throw exception(DEVICE_NOT_GATEWAY); | ||
| 766 | + } | ||
| 767 | + | ||
| 768 | + // 2. 获取子设备列表并转换 | ||
| 769 | + List<IotDeviceDO> subDevices = deviceMapper.selectListByGatewayId(gatewayDevice.getId()); | ||
| 770 | + List<IotDeviceIdentity> subDeviceIdentities = convertList(subDevices, subDevice -> | ||
| 771 | + new IotDeviceIdentity(subDevice.getProductKey(), subDevice.getDeviceName())); | ||
| 772 | + return new IotDeviceTopoGetRespDTO().setSubDevices(subDeviceIdentities); | ||
| 773 | + } | ||
| 774 | + | ||
| 775 | + @Override | ||
| 776 | + public List<IotSubDeviceRegisterRespDTO> handleSubDeviceRegisterMessage(IotDeviceMessage message, IotDeviceDO gatewayDevice) { | ||
| 777 | + // 1.1 校验网关设备类型 | ||
| 778 | + if (!IotProductDeviceTypeEnum.isGateway(gatewayDevice.getDeviceType())) { | ||
| 779 | + throw exception(DEVICE_NOT_GATEWAY); | ||
| 780 | + } | ||
| 781 | + // 1.2 解析参数 | ||
| 782 | + if (!(message.getParams() instanceof List)) { | ||
| 783 | + throw exception(DEVICE_SUB_REGISTER_PARAMS_INVALID); | ||
| 784 | + } | ||
| 785 | + // TODO @AI:这个要不也弄到 JsonUtils 里面去?感觉类似 convertObject 呀。 | ||
| 786 | + List<IotSubDeviceRegisterReqDTO> paramsList = JsonUtils.parseArray(JsonUtils.toJsonString(message.getParams()), | ||
| 787 | + IotSubDeviceRegisterReqDTO.class); | ||
| 788 | + if (CollUtil.isEmpty(paramsList)) { | ||
| 789 | + throw exception(DEVICE_SUB_REGISTER_PARAMS_INVALID); | ||
| 790 | + } | ||
| 791 | + | ||
| 792 | + // 2. 遍历注册每个子设备 | ||
| 793 | + List<IotSubDeviceRegisterRespDTO> results = new ArrayList<>(paramsList.size()); | ||
| 794 | + for (IotSubDeviceRegisterReqDTO params : paramsList) { | ||
| 795 | + try { | ||
| 796 | + IotDeviceDO device = registerSubDevice(gatewayDevice, params); | ||
| 797 | + results.add(new IotSubDeviceRegisterRespDTO( | ||
| 798 | + params.getProductKey(), params.getDeviceName(), device.getDeviceSecret())); | ||
| 799 | + } catch (Exception ex) { | ||
| 800 | + log.error("[handleSubDeviceRegisterMessage][子设备({}/{}) 注册失败]", | ||
| 801 | + params.getProductKey(), params.getDeviceName(), ex); | ||
| 802 | + } | ||
| 803 | + } | ||
| 804 | + | ||
| 805 | + // 3. 返回响应数据(包含成功注册的子设备列表) | ||
| 806 | + return results; | ||
| 807 | + } | ||
| 808 | + | ||
| 809 | + private IotDeviceDO registerSubDevice(IotDeviceDO gatewayDevice, IotSubDeviceRegisterReqDTO params) { | ||
| 810 | + // 1.1 校验产品 | ||
| 811 | + IotProductDO product = productService.getProductByProductKey(params.getProductKey()); | ||
| 812 | + if (product == null) { | ||
| 813 | + throw exception(PRODUCT_NOT_EXISTS); | ||
| 814 | + } | ||
| 815 | + // 1.2 校验产品是否为网关子设备类型 | ||
| 816 | + if (!IotProductDeviceTypeEnum.isGatewaySub(product.getDeviceType())) { | ||
| 817 | + throw exception(DEVICE_SUB_REGISTER_PRODUCT_NOT_GATEWAY_SUB, params.getProductKey()); | ||
| 818 | + } | ||
| 819 | + // 1.3 查找设备是否已存在 | ||
| 820 | + // TODO @AI:存在的时候,必须父设备是自己,才返回,否则抛出业务异常; | ||
| 821 | + IotDeviceDO existDevice = getSelf().getDeviceFromCache(params.getProductKey(), params.getDeviceName()); | ||
| 822 | + if (existDevice != null) { | ||
| 823 | + // 已存在则返回设备信息 | ||
| 824 | + return existDevice; | ||
| 825 | + } | ||
| 826 | + | ||
| 827 | + // 2. 创建新设备 | ||
| 828 | + IotDeviceSaveReqVO createReqVO = new IotDeviceSaveReqVO() | ||
| 829 | + .setDeviceName(params.getDeviceName()) | ||
| 830 | + .setProductId(product.getId()) | ||
| 831 | + .setGatewayId(gatewayDevice.getId()); | ||
| 832 | + IotDeviceDO newDevice = createDevice0(createReqVO); | ||
| 833 | + log.info("[registerSubDevice][网关({}/{}) 注册子设备({}/{})]", | ||
| 834 | + gatewayDevice.getProductKey(), gatewayDevice.getDeviceName(), | ||
| 835 | + newDevice.getProductKey(), newDevice.getDeviceName()); | ||
| 836 | + return newDevice; | ||
| 837 | + } | ||
| 838 | + | ||
| 595 | private IotDeviceServiceImpl getSelf() { | 839 | private IotDeviceServiceImpl getSelf() { |
| 596 | return SpringUtil.getBean(getClass()); | 840 | return SpringUtil.getBean(getClass()); |
| 597 | } | 841 | } |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/message/IotDeviceMessageServiceImpl.java
| @@ -4,7 +4,6 @@ import cn.hutool.core.collection.CollUtil; | @@ -4,7 +4,6 @@ import cn.hutool.core.collection.CollUtil; | ||
| 4 | import cn.hutool.core.date.LocalDateTimeUtil; | 4 | import cn.hutool.core.date.LocalDateTimeUtil; |
| 5 | import cn.hutool.core.lang.Assert; | 5 | import cn.hutool.core.lang.Assert; |
| 6 | import cn.hutool.core.map.MapUtil; | 6 | import cn.hutool.core.map.MapUtil; |
| 7 | -import cn.hutool.core.util.ObjectUtil; | ||
| 8 | import cn.hutool.core.util.StrUtil; | 7 | import cn.hutool.core.util.StrUtil; |
| 9 | import cn.hutool.extra.spring.SpringUtil; | 8 | import cn.hutool.extra.spring.SpringUtil; |
| 10 | import cn.iocoder.yudao.framework.common.exception.ServiceException; | 9 | import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| @@ -12,30 +11,21 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; | @@ -12,30 +11,21 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||
| 12 | import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; | 11 | import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; |
| 13 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | 12 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; |
| 14 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | 13 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 15 | -import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceSaveReqVO; | ||
| 16 | import cn.iocoder.yudao.module.iot.controller.admin.device.vo.message.IotDeviceMessagePageReqVO; | 14 | import cn.iocoder.yudao.module.iot.controller.admin.device.vo.message.IotDeviceMessagePageReqVO; |
| 17 | import cn.iocoder.yudao.module.iot.controller.admin.statistics.vo.IotStatisticsDeviceMessageReqVO; | 15 | import cn.iocoder.yudao.module.iot.controller.admin.statistics.vo.IotStatisticsDeviceMessageReqVO; |
| 18 | import cn.iocoder.yudao.module.iot.controller.admin.statistics.vo.IotStatisticsDeviceMessageSummaryByDateRespVO; | 16 | import cn.iocoder.yudao.module.iot.controller.admin.statistics.vo.IotStatisticsDeviceMessageSummaryByDateRespVO; |
| 19 | -import cn.iocoder.yudao.module.iot.core.biz.dto.*; | ||
| 20 | import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; | 17 | import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; |
| 21 | -import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum; | ||
| 22 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | 18 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 23 | -import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPackPostReqDTO; | ||
| 24 | -import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterReqDTO; | ||
| 25 | -import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterRespDTO; | ||
| 26 | -import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoAddReqDTO; | ||
| 27 | -import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoDeleteReqDTO; | ||
| 28 | -import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoRespDTO; | ||
| 29 | -import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; | ||
| 30 | import cn.iocoder.yudao.module.iot.core.mq.producer.IotDeviceMessageProducer; | 19 | import cn.iocoder.yudao.module.iot.core.mq.producer.IotDeviceMessageProducer; |
| 20 | +import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity; | ||
| 21 | +import cn.iocoder.yudao.module.iot.core.topic.event.IotDeviceEventPostReqDTO; | ||
| 22 | +import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPackPostReqDTO; | ||
| 23 | +import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPostReqDTO; | ||
| 31 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; | 24 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; |
| 32 | import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; | 25 | import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; |
| 33 | import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceMessageDO; | 26 | import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceMessageDO; |
| 34 | -import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; | ||
| 35 | import cn.iocoder.yudao.module.iot.dal.tdengine.IotDeviceMessageMapper; | 27 | import cn.iocoder.yudao.module.iot.dal.tdengine.IotDeviceMessageMapper; |
| 36 | -import cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum; | ||
| 37 | import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; | 28 | import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; |
| 38 | -import cn.iocoder.yudao.module.iot.service.product.IotProductService; | ||
| 39 | import cn.iocoder.yudao.module.iot.service.device.property.IotDevicePropertyService; | 29 | import cn.iocoder.yudao.module.iot.service.device.property.IotDevicePropertyService; |
| 40 | import cn.iocoder.yudao.module.iot.service.ota.IotOtaTaskRecordService; | 30 | import cn.iocoder.yudao.module.iot.service.ota.IotOtaTaskRecordService; |
| 41 | import com.baomidou.mybatisplus.core.metadata.IPage; | 31 | import com.baomidou.mybatisplus.core.metadata.IPage; |
| @@ -50,11 +40,12 @@ import org.springframework.validation.annotation.Validated; | @@ -50,11 +40,12 @@ import org.springframework.validation.annotation.Validated; | ||
| 50 | 40 | ||
| 51 | import java.sql.Timestamp; | 41 | import java.sql.Timestamp; |
| 52 | import java.time.LocalDateTime; | 42 | import java.time.LocalDateTime; |
| 53 | -import java.util.*; | 43 | +import java.util.List; |
| 44 | +import java.util.Map; | ||
| 54 | 45 | ||
| 55 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | 46 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 56 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; | 47 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; |
| 57 | -import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*; | 48 | +import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.DEVICE_DOWNSTREAM_FAILED_SERVER_ID_NULL; |
| 58 | 49 | ||
| 59 | /** | 50 | /** |
| 60 | * IoT 设备消息 Service 实现类 | 51 | * IoT 设备消息 Service 实现类 |
| @@ -73,9 +64,6 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService { | @@ -73,9 +64,6 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService { | ||
| 73 | @Resource | 64 | @Resource |
| 74 | @Lazy // 延迟加载,避免循环依赖 | 65 | @Lazy // 延迟加载,避免循环依赖 |
| 75 | private IotOtaTaskRecordService otaTaskRecordService; | 66 | private IotOtaTaskRecordService otaTaskRecordService; |
| 76 | - @Resource | ||
| 77 | - @Lazy // 延迟加载,避免循环依赖 | ||
| 78 | - private IotProductService productService; | ||
| 79 | 67 | ||
| 80 | @Resource | 68 | @Resource |
| 81 | private IotDeviceMessageMapper deviceMessageMapper; | 69 | private IotDeviceMessageMapper deviceMessageMapper; |
| @@ -210,11 +198,6 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService { | @@ -210,11 +198,6 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService { | ||
| 210 | Assert.notEmpty(stateStr, "设备状态不能为空"); | 198 | Assert.notEmpty(stateStr, "设备状态不能为空"); |
| 211 | Integer state = Integer.valueOf(stateStr); | 199 | Integer state = Integer.valueOf(stateStr); |
| 212 | deviceService.updateDeviceState(device, state); | 200 | deviceService.updateDeviceState(device, state); |
| 213 | - // 特殊:网关设备下线时,网关子设备联动下线 | ||
| 214 | - if (Objects.equal(state, IotDeviceStateEnum.OFFLINE.getState()) | ||
| 215 | - && IotProductDeviceTypeEnum.isGateway(device.getDeviceType())) { | ||
| 216 | - handleGatewayOffline(device, message.getServerId()); | ||
| 217 | - } | ||
| 218 | return null; | 201 | return null; |
| 219 | } | 202 | } |
| 220 | 203 | ||
| @@ -237,349 +220,31 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService { | @@ -237,349 +220,31 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService { | ||
| 237 | 220 | ||
| 238 | // 添加拓扑关系 | 221 | // 添加拓扑关系 |
| 239 | if (Objects.equal(message.getMethod(), IotDeviceMessageMethodEnum.TOPO_ADD.getMethod())) { | 222 | if (Objects.equal(message.getMethod(), IotDeviceMessageMethodEnum.TOPO_ADD.getMethod())) { |
| 240 | - return handleTopoAdd(message, device); | 223 | + return deviceService.handleTopoAddMessage(message, device); |
| 241 | } | 224 | } |
| 242 | // 删除拓扑关系 | 225 | // 删除拓扑关系 |
| 243 | if (Objects.equal(message.getMethod(), IotDeviceMessageMethodEnum.TOPO_DELETE.getMethod())) { | 226 | if (Objects.equal(message.getMethod(), IotDeviceMessageMethodEnum.TOPO_DELETE.getMethod())) { |
| 244 | - return handleTopoDelete(message, device); | 227 | + return deviceService.handleTopoDeleteMessage(message, device); |
| 245 | } | 228 | } |
| 246 | // 获取拓扑关系 | 229 | // 获取拓扑关系 |
| 247 | if (Objects.equal(message.getMethod(), IotDeviceMessageMethodEnum.TOPO_GET.getMethod())) { | 230 | if (Objects.equal(message.getMethod(), IotDeviceMessageMethodEnum.TOPO_GET.getMethod())) { |
| 248 | - return handleTopoGet(device); | 231 | + return deviceService.handleTopoGetMessage(device); |
| 249 | } | 232 | } |
| 250 | 233 | ||
| 251 | // 子设备动态注册 | 234 | // 子设备动态注册 |
| 252 | if (Objects.equal(message.getMethod(), IotDeviceMessageMethodEnum.SUB_DEVICE_REGISTER.getMethod())) { | 235 | if (Objects.equal(message.getMethod(), IotDeviceMessageMethodEnum.SUB_DEVICE_REGISTER.getMethod())) { |
| 253 | - return handleSubDeviceRegister(message, device); | ||
| 254 | - } | ||
| 255 | - | ||
| 256 | - return null; | ||
| 257 | - } | ||
| 258 | - | ||
| 259 | - // ========== 拓扑管理处理方法 ========== | ||
| 260 | - | ||
| 261 | - // TODO @AI:是不是更适合在 deviceService 里面处理? | ||
| 262 | - /** | ||
| 263 | - * 处理添加拓扑关系请求 | ||
| 264 | - * | ||
| 265 | - * @param message 消息 | ||
| 266 | - * @param gatewayDevice 网关设备 | ||
| 267 | - * @return 响应数据 | ||
| 268 | - */ | ||
| 269 | - private Object handleTopoAdd(IotDeviceMessage message, IotDeviceDO gatewayDevice) { | ||
| 270 | - // 1.1 校验网关设备类型 | ||
| 271 | - if (!IotProductDeviceTypeEnum.isGateway(gatewayDevice.getDeviceType())) { | ||
| 272 | - throw exception(DEVICE_NOT_GATEWAY); | ||
| 273 | - } | ||
| 274 | - // 1.2 解析参数 | ||
| 275 | - // TODO @AI:是不是 parseObject 增加一个方法,允许传入 object 类型,避免先转 jsonString 再 parseObject ; | ||
| 276 | - IotDeviceTopoAddReqDTO params = JsonUtils.parseObject(JsonUtils.toJsonString(message.getParams()), | ||
| 277 | - IotDeviceTopoAddReqDTO.class); | ||
| 278 | - if (params == null || CollUtil.isEmpty(params.getSubDevices())) { | ||
| 279 | - throw exception(DEVICE_TOPO_PARAMS_INVALID); | ||
| 280 | - } | ||
| 281 | - | ||
| 282 | - // 2. 遍历处理每个子设备 | ||
| 283 | - // TODO @AI:processTopoAddSubDevice 不要抽成小方法; | ||
| 284 | - List<IotDeviceDO> addedSubDevices = new ArrayList<>(params.getSubDevices().size()); | ||
| 285 | - for (IotDeviceAuthReqDTO subDeviceAuth : params.getSubDevices()) { | ||
| 286 | - try { | ||
| 287 | - IotDeviceDO subDevice = processTopoAddSubDevice(subDeviceAuth, gatewayDevice); | ||
| 288 | - addedSubDevices.add(subDevice); | ||
| 289 | - } catch (Exception ex) { | ||
| 290 | - log.warn("[handleTopoAdd][网关({}/{}) 添加子设备失败,username={}]", | ||
| 291 | - gatewayDevice.getProductKey(), gatewayDevice.getDeviceName(), | ||
| 292 | - subDeviceAuth.getUsername(), ex); | ||
| 293 | - } | ||
| 294 | - } | ||
| 295 | - // TODO @AI:http://help.aliyun.com/zh/marketplace/add-topological-relationship 要回复的! | ||
| 296 | - | ||
| 297 | - // 3. 发送拓扑变更通知 | ||
| 298 | - // TODO @AI:这里不应该发,它更多发生在,管理后台改动后,主动下发;http://help.aliyun.com/zh/marketplace/notify-gateway-topology-changes | ||
| 299 | - for (IotDeviceDO subDevice : addedSubDevices) { | ||
| 300 | - sendTopoChangeNotify(gatewayDevice, "add", subDevice); | ||
| 301 | - } | ||
| 302 | - return null; | ||
| 303 | - } | ||
| 304 | - | ||
| 305 | - /** | ||
| 306 | - * 处理单个子设备的拓扑添加 | ||
| 307 | - * | ||
| 308 | - * @param subDeviceAuth 子设备认证信息 | ||
| 309 | - * @param gatewayDevice 网关设备 | ||
| 310 | - * @return 添加成功的子设备,失败返回 null | ||
| 311 | - */ | ||
| 312 | - private IotDeviceDO processTopoAddSubDevice(IotDeviceAuthReqDTO subDeviceAuth, IotDeviceDO gatewayDevice) { | ||
| 313 | - // 1.1 解析子设备信息 | ||
| 314 | - IotDeviceAuthUtils.DeviceInfo subDeviceInfo = IotDeviceAuthUtils.parseUsername(subDeviceAuth.getUsername()); | ||
| 315 | - if (subDeviceInfo == null) { | ||
| 316 | - throw exception(DEVICE_TOPO_SUB_DEVICE_USERNAME_INVALID); | ||
| 317 | - } | ||
| 318 | - // 1.2 校验子设备认证信息 | ||
| 319 | - if (!deviceService.authDevice(subDeviceAuth)) { | ||
| 320 | - throw exception(DEVICE_TOPO_SUB_DEVICE_AUTH_FAILED); | ||
| 321 | - } | ||
| 322 | - | ||
| 323 | - // 1.3 获取子设备 | ||
| 324 | - IotDeviceDO subDevice = deviceService.getDeviceFromCache(subDeviceInfo.getProductKey(), subDeviceInfo.getDeviceName()); | ||
| 325 | - if (subDevice == null) { | ||
| 326 | - throw exception(DEVICE_NOT_EXISTS); | ||
| 327 | - } | ||
| 328 | - // 1.4 校验子设备类型 | ||
| 329 | - if (!IotProductDeviceTypeEnum.isGatewaySub(subDevice.getDeviceType())) { | ||
| 330 | - throw exception(DEVICE_NOT_GATEWAY_SUB, subDevice.getProductKey(), subDevice.getDeviceName()); | ||
| 331 | - } | ||
| 332 | - // 1.5 校验子设备是否已绑定到其他网关 | ||
| 333 | - if (subDevice.getGatewayId() != null | ||
| 334 | - && ObjectUtil.notEqual(subDevice.getGatewayId(), gatewayDevice.getId())) { | ||
| 335 | - throw exception(DEVICE_GATEWAY_BINDTO_EXISTS, subDevice.getProductKey(), subDevice.getDeviceName()); | ||
| 336 | - } | ||
| 337 | - | ||
| 338 | - // 2. 绑定拓扑关系 | ||
| 339 | - // TODO @AI:上面的校验,貌似和 bindDeviceGateway 里的,有点重复; | ||
| 340 | - deviceService.bindDeviceGateway(Collections.singletonList(subDevice.getId()), gatewayDevice.getId()); | ||
| 341 | - log.info("[processTopoAddSubDevice][网关({}/{}) 绑定子设备({}/{})]", | ||
| 342 | - gatewayDevice.getProductKey(), gatewayDevice.getDeviceName(), | ||
| 343 | - subDevice.getProductKey(), subDevice.getDeviceName()); | ||
| 344 | - return subDevice; | ||
| 345 | - } | ||
| 346 | - | ||
| 347 | - // TODO @AI:是不是更适合在 deviceService 里面处理? | ||
| 348 | - | ||
| 349 | - /** | ||
| 350 | - * 处理删除拓扑关系请求 | ||
| 351 | - * | ||
| 352 | - * @param message 消息 | ||
| 353 | - * @param gatewayDevice 网关设备 | ||
| 354 | - * @return 响应数据 | ||
| 355 | - */ | ||
| 356 | - private Object handleTopoDelete(IotDeviceMessage message, IotDeviceDO gatewayDevice) { | ||
| 357 | - // 1. 校验网关设备类型 | ||
| 358 | - if (!IotProductDeviceTypeEnum.isGateway(gatewayDevice.getDeviceType())) { | ||
| 359 | - throw exception(DEVICE_NOT_GATEWAY); | ||
| 360 | - } | ||
| 361 | - | ||
| 362 | - // 2. 解析参数(数组格式) | ||
| 363 | - IotDeviceTopoDeleteReqDTO params = JsonUtils.parseObject(JsonUtils.toJsonString(message.getParams()), | ||
| 364 | - IotDeviceTopoDeleteReqDTO.class); | ||
| 365 | - if (params == null || params.getSubDevices() == null || params.getSubDevices().isEmpty()) { | ||
| 366 | - throw exception(DEVICE_TOPO_PARAMS_INVALID); | 236 | + return deviceService.handleSubDeviceRegisterMessage(message, device); |
| 367 | } | 237 | } |
| 368 | 238 | ||
| 369 | - // 3. 遍历处理每个子设备 | ||
| 370 | - List<IotDeviceDO> deletedSubDevices = new ArrayList<>(); | ||
| 371 | - for (IotDeviceTopoDeleteReqDTO.SubDevice subDeviceInfo : params.getSubDevices()) { | ||
| 372 | - try { | ||
| 373 | - IotDeviceDO subDevice = processTopoDeleteSubDevice(subDeviceInfo, gatewayDevice); | ||
| 374 | - deletedSubDevices.add(subDevice); | ||
| 375 | - } catch (Exception ex) { | ||
| 376 | - log.warn("[handleTopoDelete][网关({}/{}) 删除子设备失败,productKey={}, deviceName={}]", | ||
| 377 | - gatewayDevice.getProductKey(), gatewayDevice.getDeviceName(), | ||
| 378 | - subDeviceInfo.getProductKey(), subDeviceInfo.getDeviceName(), ex); | ||
| 379 | - } | ||
| 380 | - } | ||
| 381 | - | ||
| 382 | - // 4. 发送拓扑变更通知 | ||
| 383 | - for (IotDeviceDO subDevice : deletedSubDevices) { | ||
| 384 | - sendTopoChangeNotify(gatewayDevice, "delete", subDevice); | ||
| 385 | - } | ||
| 386 | return null; | 239 | return null; |
| 387 | } | 240 | } |
| 388 | 241 | ||
| 389 | - // TODO @AI:是不是更适合在 deviceService 里面处理? | ||
| 390 | - | ||
| 391 | - /** | ||
| 392 | - * 处理单个子设备的拓扑删除 | ||
| 393 | - * | ||
| 394 | - * @param subDeviceInfo 子设备标识 | ||
| 395 | - * @param gatewayDevice 网关设备 | ||
| 396 | - * @return 删除成功的子设备,失败返回 null | ||
| 397 | - */ | ||
| 398 | - private IotDeviceDO processTopoDeleteSubDevice(IotDeviceTopoDeleteReqDTO.SubDevice subDeviceInfo, | ||
| 399 | - IotDeviceDO gatewayDevice) { | ||
| 400 | - // 1. 获取子设备 | ||
| 401 | - IotDeviceDO subDevice = deviceService.getDeviceFromCache(subDeviceInfo.getProductKey(), subDeviceInfo.getDeviceName()); | ||
| 402 | - if (subDevice == null) { | ||
| 403 | - throw exception(DEVICE_NOT_EXISTS); | ||
| 404 | - } | ||
| 405 | - | ||
| 406 | - // 2. 校验子设备是否绑定到该网关 | ||
| 407 | - if (!Objects.equal(subDevice.getGatewayId(), gatewayDevice.getId())) { | ||
| 408 | - throw exception(DEVICE_TOPO_SUB_NOT_BINDTO_GATEWAY, subDeviceInfo.getProductKey(), subDeviceInfo.getDeviceName()); | ||
| 409 | - } | ||
| 410 | - | ||
| 411 | - // 3. 解绑拓扑关系 | ||
| 412 | - deviceService.unbindDeviceGateway(Collections.singletonList(subDevice.getId())); | ||
| 413 | - log.info("[processTopoDeleteSubDevice][网关({}/{}) 解绑子设备({}/{})]", | ||
| 414 | - gatewayDevice.getProductKey(), gatewayDevice.getDeviceName(), | ||
| 415 | - subDevice.getProductKey(), subDevice.getDeviceName()); | ||
| 416 | - | ||
| 417 | - // 4. 子设备下线 | ||
| 418 | - if (Objects.equal(subDevice.getState(), IotDeviceStateEnum.ONLINE.getState())) { | ||
| 419 | - deviceService.updateDeviceState(subDevice, IotDeviceStateEnum.OFFLINE.getState()); | ||
| 420 | - } | ||
| 421 | - | ||
| 422 | - return subDevice; | ||
| 423 | - } | ||
| 424 | - | ||
| 425 | - // TODO @AI:是不是更适合在 deviceService 里面处理? | ||
| 426 | - /** | ||
| 427 | - * 处理获取拓扑关系请求 | ||
| 428 | - * | ||
| 429 | - * @param gatewayDevice 网关设备 | ||
| 430 | - * @return 子设备列表 | ||
| 431 | - */ | ||
| 432 | - private Object handleTopoGet(IotDeviceDO gatewayDevice) { | ||
| 433 | - // 1. 校验网关设备类型 | ||
| 434 | - if (!IotProductDeviceTypeEnum.isGateway(gatewayDevice.getDeviceType())) { | ||
| 435 | - throw exception(DEVICE_NOT_GATEWAY); | ||
| 436 | - } | ||
| 437 | - | ||
| 438 | - // 2. 获取子设备列表 | ||
| 439 | - List<IotDeviceDO> subDevices = deviceService.getDeviceListByGatewayId(gatewayDevice.getId()); | ||
| 440 | - | ||
| 441 | - // 3. 转换为响应格式 | ||
| 442 | - return convertList(subDevices, subDevice -> new IotDeviceTopoRespDTO() | ||
| 443 | - .setProductKey(subDevice.getProductKey()) | ||
| 444 | - .setDeviceName(subDevice.getDeviceName())); | ||
| 445 | - } | ||
| 446 | - | ||
| 447 | - // TODO @AI:是不是更适合在 deviceService 里面处理? | ||
| 448 | - /** | ||
| 449 | - * 发送拓扑变更通知 | ||
| 450 | - * | ||
| 451 | - * @param gatewayDevice 网关设备 | ||
| 452 | - * @param changeType 变更类型:add/delete | ||
| 453 | - * @param subDevice 子设备 | ||
| 454 | - */ | ||
| 455 | - private void sendTopoChangeNotify(IotDeviceDO gatewayDevice, String changeType, IotDeviceDO subDevice) { | ||
| 456 | - try { | ||
| 457 | - String serverId = devicePropertyService.getDeviceServerId(gatewayDevice.getId()); | ||
| 458 | - if (StrUtil.isEmpty(serverId)) { | ||
| 459 | - log.warn("[sendTopoChangeNotify][网关({}/{}) serverId 为空,无法发送拓扑变更通知]", | ||
| 460 | - gatewayDevice.getProductKey(), gatewayDevice.getDeviceName()); | ||
| 461 | - return; | ||
| 462 | - } | ||
| 463 | - | ||
| 464 | - Map<String, Object> params = MapUtil.builder(new HashMap<String, Object>()) | ||
| 465 | - .put("changeType", changeType) | ||
| 466 | - .put("subDevice", MapUtil.builder(new HashMap<String, Object>()) | ||
| 467 | - .put("productKey", subDevice.getProductKey()) | ||
| 468 | - .put("deviceName", subDevice.getDeviceName()) | ||
| 469 | - .build()) | ||
| 470 | - .build(); | ||
| 471 | - | ||
| 472 | - IotDeviceMessage notifyMessage = IotDeviceMessage.requestOf( | ||
| 473 | - IotDeviceMessageMethodEnum.TOPO_CHANGE.getMethod(), params); | ||
| 474 | - sendDeviceMessage(notifyMessage, gatewayDevice, serverId); | ||
| 475 | - } catch (Exception ex) { | ||
| 476 | - log.error("[sendTopoChangeNotify][发送拓扑变更通知失败,网关({}/{}), 子设备({}/{})]", | ||
| 477 | - gatewayDevice.getProductKey(), gatewayDevice.getDeviceName(), | ||
| 478 | - subDevice.getProductKey(), subDevice.getDeviceName(), ex); | ||
| 479 | - } | ||
| 480 | - } | ||
| 481 | - | ||
| 482 | - // ========== 子设备注册处理方法 ========== | ||
| 483 | - | ||
| 484 | - // TODO @AI:是不是更适合在 deviceService 里面处理? | ||
| 485 | - /** | ||
| 486 | - * 处理子设备动态注册请求 | ||
| 487 | - * | ||
| 488 | - * @param message 消息 | ||
| 489 | - * @param gatewayDevice 网关设备 | ||
| 490 | - * @return 注册结果列表 | ||
| 491 | - */ | ||
| 492 | - private Object handleSubDeviceRegister(IotDeviceMessage message, IotDeviceDO gatewayDevice) { | ||
| 493 | - // 1. 校验网关设备类型 | ||
| 494 | - if (!IotProductDeviceTypeEnum.isGateway(gatewayDevice.getDeviceType())) { | ||
| 495 | - throw exception(DEVICE_NOT_GATEWAY); | ||
| 496 | - } | ||
| 497 | - | ||
| 498 | - // 2. 解析参数(数组) | ||
| 499 | - List<IotSubDeviceRegisterReqDTO> paramsList; | ||
| 500 | - if (message.getParams() instanceof List) { | ||
| 501 | - paramsList = JsonUtils.parseArray(JsonUtils.toJsonString(message.getParams()), | ||
| 502 | - IotSubDeviceRegisterReqDTO.class); | ||
| 503 | - } else { | ||
| 504 | - throw exception(DEVICE_SUB_REGISTER_PARAMS_INVALID); | ||
| 505 | - } | ||
| 506 | - | ||
| 507 | - if (paramsList == null || paramsList.isEmpty()) { | ||
| 508 | - throw exception(DEVICE_SUB_REGISTER_PARAMS_INVALID); | ||
| 509 | - } | ||
| 510 | - | ||
| 511 | - // 3. 遍历注册每个子设备 | ||
| 512 | - List<IotSubDeviceRegisterRespDTO> results = new ArrayList<>(); | ||
| 513 | - for (IotSubDeviceRegisterReqDTO params : paramsList) { | ||
| 514 | - try { | ||
| 515 | - IotSubDeviceRegisterRespDTO result = registerSubDevice(params, gatewayDevice); | ||
| 516 | - results.add(result); | ||
| 517 | - } catch (Exception ex) { | ||
| 518 | - log.error("[handleSubDeviceRegister][子设备({}/{}) 注册失败]", | ||
| 519 | - params.getProductKey(), params.getDeviceName(), ex); | ||
| 520 | - // 继续处理其他子设备,不影响整体流程 | ||
| 521 | - } | ||
| 522 | - } | ||
| 523 | - | ||
| 524 | - return results; | ||
| 525 | - } | ||
| 526 | - | ||
| 527 | - // TODO @AI:是不是更适合在 deviceService 里面处理? | ||
| 528 | - /** | ||
| 529 | - * 注册单个子设备 | ||
| 530 | - * | ||
| 531 | - * @param params 注册参数 | ||
| 532 | - * @param gatewayDevice 网关设备 | ||
| 533 | - * @return 注册结果 | ||
| 534 | - */ | ||
| 535 | - private IotSubDeviceRegisterRespDTO registerSubDevice(IotSubDeviceRegisterReqDTO params, | ||
| 536 | - IotDeviceDO gatewayDevice) { | ||
| 537 | - // 1. 查找产品 | ||
| 538 | - IotProductDO product = productService.getProductByProductKey(params.getProductKey()); | ||
| 539 | - if (product == null) { | ||
| 540 | - throw exception(PRODUCT_NOT_EXISTS); | ||
| 541 | - } | ||
| 542 | - | ||
| 543 | - // 2. 校验产品是否为网关子设备类型 | ||
| 544 | - if (!IotProductDeviceTypeEnum.isGatewaySub(product.getDeviceType())) { | ||
| 545 | - throw exception(DEVICE_SUB_REGISTER_PRODUCT_NOT_GATEWAY_SUB, params.getProductKey()); | ||
| 546 | - } | ||
| 547 | - | ||
| 548 | - // 3. 查找设备是否已存在 | ||
| 549 | - IotDeviceDO existDevice = deviceService.getDeviceFromCache(params.getProductKey(), params.getDeviceName()); | ||
| 550 | - if (existDevice != null) { | ||
| 551 | - // 已存在则返回设备信息 | ||
| 552 | - return new IotSubDeviceRegisterRespDTO() | ||
| 553 | - .setProductKey(existDevice.getProductKey()) | ||
| 554 | - .setDeviceName(existDevice.getDeviceName()) | ||
| 555 | - .setDeviceSecret(existDevice.getDeviceSecret()); | ||
| 556 | - } | ||
| 557 | - | ||
| 558 | - // 4. 创建新设备 | ||
| 559 | - IotDeviceSaveReqVO createReqVO = new IotDeviceSaveReqVO() | ||
| 560 | - .setDeviceName(params.getDeviceName()) | ||
| 561 | - .setProductId(product.getId()) | ||
| 562 | - .setGatewayId(gatewayDevice.getId()); | ||
| 563 | - Long deviceId = deviceService.createDevice(createReqVO); | ||
| 564 | - | ||
| 565 | - // 5. 获取新创建的设备信息 | ||
| 566 | - IotDeviceDO newDevice = deviceService.getDevice(deviceId); | ||
| 567 | - log.info("[registerSubDevice][网关({}/{}) 注册子设备({}/{})]", | ||
| 568 | - gatewayDevice.getProductKey(), gatewayDevice.getDeviceName(), | ||
| 569 | - newDevice.getProductKey(), newDevice.getDeviceName()); | ||
| 570 | - | ||
| 571 | - return new IotSubDeviceRegisterRespDTO() | ||
| 572 | - .setProductKey(newDevice.getProductKey()) | ||
| 573 | - .setDeviceName(newDevice.getDeviceName()) | ||
| 574 | - .setDeviceSecret(newDevice.getDeviceSecret()); | ||
| 575 | - } | ||
| 576 | - | ||
| 577 | // ========== 批量上报处理方法 ========== | 242 | // ========== 批量上报处理方法 ========== |
| 578 | 243 | ||
| 579 | /** | 244 | /** |
| 580 | * 处理批量上报消息 | 245 | * 处理批量上报消息 |
| 581 | * <p> | 246 | * <p> |
| 582 | - * 将 pack 消息拆分成多条标准消息,递归处理 | 247 | + * 将 pack 消息拆分成多条标准消息,发送到 MQ 让规则引擎处理 |
| 583 | * | 248 | * |
| 584 | * @param packMessage 批量消息 | 249 | * @param packMessage 批量消息 |
| 585 | * @param gatewayDevice 网关设备 | 250 | * @param gatewayDevice 网关设备 |
| @@ -590,135 +255,72 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService { | @@ -590,135 +255,72 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService { | ||
| 590 | JsonUtils.toJsonString(packMessage.getParams()), | 255 | JsonUtils.toJsonString(packMessage.getParams()), |
| 591 | IotDevicePropertyPackPostReqDTO.class); | 256 | IotDevicePropertyPackPostReqDTO.class); |
| 592 | if (params == null) { | 257 | if (params == null) { |
| 593 | - log.warn("[handlePackMessage][消息({}) 参数解析失败]", packMessage.getId()); | 258 | + log.warn("[handlePackMessage][消息({}) 参数解析失败]", packMessage); |
| 594 | return; | 259 | return; |
| 595 | } | 260 | } |
| 596 | 261 | ||
| 597 | - // 2. 处理网关自身属性 | ||
| 598 | - // TODO @AI:是不是经过总线会更好: | ||
| 599 | - // TODO @AI:是不是少处理了 event 事件? | ||
| 600 | - if (params.getProperties() != null && !params.getProperties().isEmpty()) { | ||
| 601 | - Map<String, Object> gatewayProperties = convertPackProperties(params.getProperties()); | ||
| 602 | - IotDeviceMessage gatewayMsg = IotDeviceMessage.builder() | ||
| 603 | - .id(IotDeviceMessageUtils.generateMessageId()) | ||
| 604 | - .deviceId(gatewayDevice.getId()) | ||
| 605 | - .tenantId(gatewayDevice.getTenantId()) | ||
| 606 | - .serverId(packMessage.getServerId()) | ||
| 607 | - .method(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod()) | ||
| 608 | - .params(gatewayProperties) | ||
| 609 | - .reportTime(LocalDateTime.now()) | ||
| 610 | - .build(); | ||
| 611 | - // 直接调用处理,不通过消息总线 | ||
| 612 | - try { | ||
| 613 | - devicePropertyService.saveDeviceProperty(gatewayDevice, gatewayMsg); | ||
| 614 | - getSelf().createDeviceLogAsync(gatewayMsg); | ||
| 615 | - } catch (Exception ex) { | ||
| 616 | - log.error("[handlePackMessage][网关({}) 属性处理失败]", gatewayDevice.getId(), ex); | ||
| 617 | - } | ||
| 618 | - } | 262 | + // 2. 处理网关设备(自身)的数据 |
| 263 | + sendDevicePackData(gatewayDevice, packMessage.getServerId(), params.getProperties(), params.getEvents()); | ||
| 619 | 264 | ||
| 620 | - // 3. 处理子设备数据 | ||
| 621 | - if (params.getSubDevices() != null) { | ||
| 622 | - for (IotDevicePropertyPackPostReqDTO.SubDeviceData subDeviceData : params.getSubDevices()) { | ||
| 623 | - try { | ||
| 624 | - handleSubDevicePackData(packMessage, subDeviceData); | ||
| 625 | - } catch (Exception ex) { | ||
| 626 | - log.error("[handlePackMessage][子设备({}/{}) 数据处理失败]", | ||
| 627 | - subDeviceData.getIdentity().getProductKey(), | ||
| 628 | - subDeviceData.getIdentity().getDeviceName(), ex); | ||
| 629 | - } | ||
| 630 | - } | ||
| 631 | - } | ||
| 632 | - } | ||
| 633 | - | ||
| 634 | - /** | ||
| 635 | - * 处理子设备的 pack 数据 | ||
| 636 | - * | ||
| 637 | - * @param packMessage 原始 pack 消息 | ||
| 638 | - * @param subDeviceData 子设备数据 | ||
| 639 | - */ | ||
| 640 | - private void handleSubDevicePackData(IotDeviceMessage packMessage, | ||
| 641 | - IotDevicePropertyPackPostReqDTO.SubDeviceData subDeviceData) { | ||
| 642 | - // 1. 获取子设备 | ||
| 643 | - IotDevicePropertyPackPostReqDTO.DeviceIdentity identity = subDeviceData.getIdentity(); | ||
| 644 | - IotDeviceDO subDevice = deviceService.getDeviceFromCache(identity.getProductKey(), identity.getDeviceName()); | ||
| 645 | - if (subDevice == null) { | ||
| 646 | - log.warn("[handleSubDevicePackData][子设备({}/{}) 不存在]", | ||
| 647 | - identity.getProductKey(), identity.getDeviceName()); | 265 | + // 3. 处理子设备的数据 |
| 266 | + if (CollUtil.isEmpty(params.getSubDevices())) { | ||
| 648 | return; | 267 | return; |
| 649 | } | 268 | } |
| 650 | - | ||
| 651 | - // 2. 处理子设备属性 | ||
| 652 | - if (subDeviceData.getProperties() != null && !subDeviceData.getProperties().isEmpty()) { | ||
| 653 | - Map<String, Object> properties = convertPackProperties(subDeviceData.getProperties()); | ||
| 654 | - IotDeviceMessage subMsg = IotDeviceMessage.builder() | ||
| 655 | - .id(IotDeviceMessageUtils.generateMessageId()) | ||
| 656 | - .deviceId(subDevice.getId()) | ||
| 657 | - .tenantId(subDevice.getTenantId()) | ||
| 658 | - .serverId(packMessage.getServerId()) | ||
| 659 | - .method(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod()) | ||
| 660 | - .params(properties) | ||
| 661 | - .reportTime(LocalDateTime.now()) | ||
| 662 | - .build(); | ||
| 663 | - devicePropertyService.saveDeviceProperty(subDevice, subMsg); | ||
| 664 | - getSelf().createDeviceLogAsync(subMsg); | ||
| 665 | - } | ||
| 666 | - | ||
| 667 | - // 3. 处理子设备事件 | ||
| 668 | - // TODO @AI:事件处理可以后续扩展 | ||
| 669 | - } | ||
| 670 | - | ||
| 671 | - /** | ||
| 672 | - * 转换 pack 属性格式为标准属性格式 | ||
| 673 | - * <p> | ||
| 674 | - * pack 格式:{"temperature": {"value": 25.5, "time": 1524448722000}} | ||
| 675 | - * 标准格式:{"temperature": 25.5} | ||
| 676 | - * | ||
| 677 | - * @param packProperties pack 属性 | ||
| 678 | - * @return 标准属性 | ||
| 679 | - */ | ||
| 680 | - private Map<String, Object> convertPackProperties(Map<String, IotDevicePropertyPackPostReqDTO.PropertyValue> packProperties) { | ||
| 681 | - Map<String, Object> result = new HashMap<>(); | ||
| 682 | - for (Map.Entry<String, IotDevicePropertyPackPostReqDTO.PropertyValue> entry : packProperties.entrySet()) { | ||
| 683 | - if (entry.getValue() != null) { | ||
| 684 | - result.put(entry.getKey(), entry.getValue().getValue()); | 269 | + for (IotDevicePropertyPackPostReqDTO.SubDeviceData subDeviceData : params.getSubDevices()) { |
| 270 | + try { | ||
| 271 | + IotDeviceIdentity identity = subDeviceData.getIdentity(); | ||
| 272 | + IotDeviceDO subDevice = deviceService.getDeviceFromCache(identity.getProductKey(), identity.getDeviceName()); | ||
| 273 | + if (subDevice == null) { | ||
| 274 | + log.warn("[handlePackMessage][子设备({}/{}) 不存在]", identity.getProductKey(), identity.getDeviceName()); | ||
| 275 | + return; | ||
| 276 | + } | ||
| 277 | + sendDevicePackData(subDevice, packMessage.getServerId(), subDeviceData.getProperties(), subDeviceData.getEvents()); | ||
| 278 | + } catch (Exception ex) { | ||
| 279 | + log.error("[handlePackMessage][子设备({}/{}) 数据处理失败]", subDeviceData.getIdentity().getProductKey(), | ||
| 280 | + subDeviceData.getIdentity().getDeviceName(), ex); | ||
| 685 | } | 281 | } |
| 686 | } | 282 | } |
| 687 | - return result; | ||
| 688 | } | 283 | } |
| 689 | 284 | ||
| 690 | - // ========== 网关下线联动处理 ========== | ||
| 691 | - | ||
| 692 | - // TODO 芋艿:是不是写到 deviceService 里更合适?更解耦。 | ||
| 693 | /** | 285 | /** |
| 694 | - * 处理网关下线,联动所有子设备下线 | 286 | + * 发送设备 pack 数据到 MQ(属性 + 事件) |
| 695 | * | 287 | * |
| 696 | - * @param gatewayDevice 网关设备 | ||
| 697 | - * @param serverId 服务标识 | 288 | + * @param device 设备 |
| 289 | + * @param serverId 服务标识 | ||
| 290 | + * @param properties 属性数据 | ||
| 291 | + * @param events 事件数据 | ||
| 698 | */ | 292 | */ |
| 699 | - private void handleGatewayOffline(IotDeviceDO gatewayDevice, String serverId) { | ||
| 700 | - // 1. 获取网关下所有子设备 | ||
| 701 | - List<IotDeviceDO> subDevices = deviceService.getDeviceListByGatewayId(gatewayDevice.getId()); | ||
| 702 | - if (subDevices == null || subDevices.isEmpty()) { | ||
| 703 | - return; | ||
| 704 | - } | ||
| 705 | - | ||
| 706 | - // 2. 将在线的子设备设置为下线 | ||
| 707 | - for (IotDeviceDO subDevice : subDevices) { | ||
| 708 | - if (Objects.equal(subDevice.getState(), IotDeviceStateEnum.ONLINE.getState())) { | ||
| 709 | - try { | ||
| 710 | - deviceService.updateDeviceState(subDevice, IotDeviceStateEnum.OFFLINE.getState()); | ||
| 711 | - log.info("[handleGatewayOffline][网关({}/{}) 下线,子设备({}/{}) 联动下线]", | ||
| 712 | - gatewayDevice.getProductKey(), gatewayDevice.getDeviceName(), | ||
| 713 | - subDevice.getProductKey(), subDevice.getDeviceName()); | ||
| 714 | - } catch (Exception ex) { | ||
| 715 | - log.error("[handleGatewayOffline][子设备({}/{}) 下线失败]", | ||
| 716 | - subDevice.getProductKey(), subDevice.getDeviceName(), ex); | 293 | + private void sendDevicePackData(IotDeviceDO device, String serverId, |
| 294 | + Map<String, Object> properties, | ||
| 295 | + Map<String, IotDevicePropertyPackPostReqDTO.EventValue> events) { | ||
| 296 | + // 1. 发送属性消息 | ||
| 297 | + if (MapUtil.isNotEmpty(properties)) { | ||
| 298 | + IotDeviceMessage propertyMsg = IotDeviceMessage.requestOf( | ||
| 299 | + device.getId(), device.getTenantId(), serverId, | ||
| 300 | + IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod(), | ||
| 301 | + IotDevicePropertyPostReqDTO.of(properties)); | ||
| 302 | + deviceMessageProducer.sendDeviceMessage(propertyMsg); | ||
| 303 | + } | ||
| 304 | + | ||
| 305 | + // 2. 发送事件消息 | ||
| 306 | + if (MapUtil.isNotEmpty(events)) { | ||
| 307 | + for (Map.Entry<String, IotDevicePropertyPackPostReqDTO.EventValue> eventEntry : events.entrySet()) { | ||
| 308 | + String eventId = eventEntry.getKey(); | ||
| 309 | + IotDevicePropertyPackPostReqDTO.EventValue eventValue = eventEntry.getValue(); | ||
| 310 | + if (eventValue == null) { | ||
| 311 | + continue; | ||
| 717 | } | 312 | } |
| 313 | + IotDeviceMessage eventMsg = IotDeviceMessage.requestOf( | ||
| 314 | + device.getId(), device.getTenantId(), serverId, | ||
| 315 | + IotDeviceMessageMethodEnum.EVENT_POST.getMethod(), | ||
| 316 | + IotDeviceEventPostReqDTO.of(eventId, eventValue.getValue(), eventValue.getTime())); | ||
| 317 | + deviceMessageProducer.sendDeviceMessage(eventMsg); | ||
| 718 | } | 318 | } |
| 719 | } | 319 | } |
| 720 | } | 320 | } |
| 721 | 321 | ||
| 322 | + // ========= 设备消息查询 ========== | ||
| 323 | + | ||
| 722 | @Override | 324 | @Override |
| 723 | public PageResult<IotDeviceMessageDO> getDeviceMessagePage(IotDeviceMessagePageReqVO pageReqVO) { | 325 | public PageResult<IotDeviceMessageDO> getDeviceMessagePage(IotDeviceMessagePageReqVO pageReqVO) { |
| 724 | try { | 326 | try { |
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/mq/message/IotDeviceMessage.java
| @@ -108,6 +108,23 @@ public class IotDeviceMessage { | @@ -108,6 +108,23 @@ public class IotDeviceMessage { | ||
| 108 | return of(requestId, method, params, null, null, null); | 108 | return of(requestId, method, params, null, null, null); |
| 109 | } | 109 | } |
| 110 | 110 | ||
| 111 | + /** | ||
| 112 | + * 创建设备请求消息(包含设备信息) | ||
| 113 | + * | ||
| 114 | + * @param deviceId 设备编号 | ||
| 115 | + * @param tenantId 租户编号 | ||
| 116 | + * @param serverId 服务标识 | ||
| 117 | + * @param method 消息方法 | ||
| 118 | + * @param params 消息参数 | ||
| 119 | + * @return 消息对象 | ||
| 120 | + */ | ||
| 121 | + public static IotDeviceMessage requestOf(Long deviceId, Long tenantId, String serverId, | ||
| 122 | + String method, Object params) { | ||
| 123 | + IotDeviceMessage message = of(null, method, params, null, null, null); | ||
| 124 | + return message.setId(IotDeviceMessageUtils.generateMessageId()) | ||
| 125 | + .setDeviceId(deviceId).setTenantId(tenantId).setServerId(serverId); | ||
| 126 | + } | ||
| 127 | + | ||
| 111 | public static IotDeviceMessage replyOf(String requestId, String method, | 128 | public static IotDeviceMessage replyOf(String requestId, String method, |
| 112 | Object data, Integer code, String msg) { | 129 | Object data, Integer code, String msg) { |
| 113 | if (code == null) { | 130 | if (code == null) { |
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/IotDeviceIdentify.java deleted
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/IotDeviceIdentity.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.core.topic; | ||
| 2 | + | ||
| 3 | +import jakarta.validation.constraints.NotEmpty; | ||
| 4 | +import lombok.AllArgsConstructor; | ||
| 5 | +import lombok.Data; | ||
| 6 | +import lombok.NoArgsConstructor; | ||
| 7 | + | ||
| 8 | +/** | ||
| 9 | + * IoT 设备标识 | ||
| 10 | + * | ||
| 11 | + * 用于标识一个设备的基本信息(productKey + deviceName) | ||
| 12 | + * | ||
| 13 | + * @author 芋道源码 | ||
| 14 | + */ | ||
| 15 | +@Data | ||
| 16 | +@NoArgsConstructor | ||
| 17 | +@AllArgsConstructor | ||
| 18 | +public class IotDeviceIdentity { | ||
| 19 | + | ||
| 20 | + /** | ||
| 21 | + * 产品标识 | ||
| 22 | + */ | ||
| 23 | + @NotEmpty(message = "产品标识不能为空") | ||
| 24 | + private String productKey; | ||
| 25 | + | ||
| 26 | + /** | ||
| 27 | + * 设备名称 | ||
| 28 | + */ | ||
| 29 | + @NotEmpty(message = "设备名称不能为空") | ||
| 30 | + private String deviceName; | ||
| 31 | + | ||
| 32 | +} |
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/auth/IotSubDeviceRegisterRespDTO.java
| 1 | package cn.iocoder.yudao.module.iot.core.topic.auth; | 1 | package cn.iocoder.yudao.module.iot.core.topic.auth; |
| 2 | 2 | ||
| 3 | +import lombok.AllArgsConstructor; | ||
| 3 | import lombok.Data; | 4 | import lombok.Data; |
| 5 | +import lombok.NoArgsConstructor; | ||
| 4 | 6 | ||
| 5 | /** | 7 | /** |
| 6 | * IoT 子设备动态注册 Response DTO | 8 | * IoT 子设备动态注册 Response DTO |
| @@ -11,6 +13,8 @@ import lombok.Data; | @@ -11,6 +13,8 @@ import lombok.Data; | ||
| 11 | * @see <a href="http://help.aliyun.com/zh/marketplace/dynamic-registration-of-sub-devices">阿里云 - 动态注册子设备</a> | 13 | * @see <a href="http://help.aliyun.com/zh/marketplace/dynamic-registration-of-sub-devices">阿里云 - 动态注册子设备</a> |
| 12 | */ | 14 | */ |
| 13 | @Data | 15 | @Data |
| 16 | +@NoArgsConstructor | ||
| 17 | +@AllArgsConstructor | ||
| 14 | public class IotSubDeviceRegisterRespDTO { | 18 | public class IotSubDeviceRegisterRespDTO { |
| 15 | 19 | ||
| 16 | /** | 20 | /** |
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/event/IotDeviceEventPostReqDTO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.core.topic.event; | ||
| 2 | + | ||
| 3 | +import lombok.Data; | ||
| 4 | + | ||
| 5 | +/** | ||
| 6 | + * IoT 设备事件上报 Request DTO | ||
| 7 | + * <p> | ||
| 8 | + * 用于 thing.event.{eventId}.post 消息的 params 参数 | ||
| 9 | + * | ||
| 10 | + * @author 芋道源码 | ||
| 11 | + * @see <a href="https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services">阿里云 - 设备上报事件</a> | ||
| 12 | + */ | ||
| 13 | +@Data | ||
| 14 | +public class IotDeviceEventPostReqDTO { | ||
| 15 | + | ||
| 16 | + /** | ||
| 17 | + * 事件标识符 | ||
| 18 | + */ | ||
| 19 | + private String eventId; | ||
| 20 | + | ||
| 21 | + /** | ||
| 22 | + * 事件输出参数 | ||
| 23 | + */ | ||
| 24 | + private Object value; | ||
| 25 | + | ||
| 26 | + /** | ||
| 27 | + * 上报时间(毫秒时间戳,可选) | ||
| 28 | + */ | ||
| 29 | + private Long time; | ||
| 30 | + | ||
| 31 | + /** | ||
| 32 | + * 创建事件上报 DTO | ||
| 33 | + * | ||
| 34 | + * @param eventId 事件标识符 | ||
| 35 | + * @param value 事件值 | ||
| 36 | + * @return DTO 对象 | ||
| 37 | + */ | ||
| 38 | + public static IotDeviceEventPostReqDTO of(String eventId, Object value) { | ||
| 39 | + return of(eventId, value, null); | ||
| 40 | + } | ||
| 41 | + | ||
| 42 | + /** | ||
| 43 | + * 创建事件上报 DTO(带时间) | ||
| 44 | + * | ||
| 45 | + * @param eventId 事件标识符 | ||
| 46 | + * @param value 事件值 | ||
| 47 | + * @param time 上报时间 | ||
| 48 | + * @return DTO 对象 | ||
| 49 | + */ | ||
| 50 | + public static IotDeviceEventPostReqDTO of(String eventId, Object value, Long time) { | ||
| 51 | + return new IotDeviceEventPostReqDTO() | ||
| 52 | + .setEventId(eventId) | ||
| 53 | + .setValue(value) | ||
| 54 | + .setTime(time); | ||
| 55 | + } | ||
| 56 | + | ||
| 57 | +} |
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/property/IotDevicePropertyPackPostReqDTO.java
| 1 | package cn.iocoder.yudao.module.iot.core.topic.property; | 1 | package cn.iocoder.yudao.module.iot.core.topic.property; |
| 2 | 2 | ||
| 3 | +import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity; | ||
| 3 | import lombok.Data; | 4 | import lombok.Data; |
| 4 | -import lombok.experimental.Accessors; | ||
| 5 | 5 | ||
| 6 | import java.util.List; | 6 | import java.util.List; |
| 7 | import java.util.Map; | 7 | import java.util.Map; |
| @@ -17,14 +17,13 @@ import java.util.Map; | @@ -17,14 +17,13 @@ import java.util.Map; | ||
| 17 | @Data | 17 | @Data |
| 18 | public class IotDevicePropertyPackPostReqDTO { | 18 | public class IotDevicePropertyPackPostReqDTO { |
| 19 | 19 | ||
| 20 | - // TODO @AI:不用 PropertyValue,直接使用 Object 接收就行! | ||
| 21 | /** | 20 | /** |
| 22 | * 网关自身属性 | 21 | * 网关自身属性 |
| 23 | * <p> | 22 | * <p> |
| 24 | * key: 属性标识符 | 23 | * key: 属性标识符 |
| 25 | - * value: 属性值对象(包含 value 和 time) | 24 | + * value: 属性值 |
| 26 | */ | 25 | */ |
| 27 | - private Map<String, PropertyValue> properties; | 26 | + private Map<String, Object> properties; |
| 28 | 27 | ||
| 29 | /** | 28 | /** |
| 30 | * 网关自身事件 | 29 | * 网关自身事件 |
| @@ -40,24 +39,6 @@ public class IotDevicePropertyPackPostReqDTO { | @@ -40,24 +39,6 @@ public class IotDevicePropertyPackPostReqDTO { | ||
| 40 | private List<SubDeviceData> subDevices; | 39 | private List<SubDeviceData> subDevices; |
| 41 | 40 | ||
| 42 | /** | 41 | /** |
| 43 | - * 属性值对象 | ||
| 44 | - */ | ||
| 45 | - @Data | ||
| 46 | - public static class PropertyValue { | ||
| 47 | - | ||
| 48 | - /** | ||
| 49 | - * 属性值 | ||
| 50 | - */ | ||
| 51 | - private Object value; | ||
| 52 | - | ||
| 53 | - /** | ||
| 54 | - * 上报时间(毫秒时间戳) | ||
| 55 | - */ | ||
| 56 | - private Long time; | ||
| 57 | - | ||
| 58 | - } | ||
| 59 | - | ||
| 60 | - /** | ||
| 61 | * 事件值对象 | 42 | * 事件值对象 |
| 62 | */ | 43 | */ |
| 63 | @Data | 44 | @Data |
| @@ -84,37 +65,24 @@ public class IotDevicePropertyPackPostReqDTO { | @@ -84,37 +65,24 @@ public class IotDevicePropertyPackPostReqDTO { | ||
| 84 | /** | 65 | /** |
| 85 | * 子设备标识 | 66 | * 子设备标识 |
| 86 | */ | 67 | */ |
| 87 | - private DeviceIdentity identity; | 68 | + private IotDeviceIdentity identity; |
| 88 | 69 | ||
| 89 | /** | 70 | /** |
| 90 | * 子设备属性 | 71 | * 子设备属性 |
| 72 | + * <p> | ||
| 73 | + * key: 属性标识符 | ||
| 74 | + * value: 属性值 | ||
| 91 | */ | 75 | */ |
| 92 | - private Map<String, PropertyValue> properties; | 76 | + private Map<String, Object> properties; |
| 93 | 77 | ||
| 94 | /** | 78 | /** |
| 95 | * 子设备事件 | 79 | * 子设备事件 |
| 80 | + * <p> | ||
| 81 | + * key: 事件标识符 | ||
| 82 | + * value: 事件值对象(包含 value 和 time) | ||
| 96 | */ | 83 | */ |
| 97 | private Map<String, EventValue> events; | 84 | private Map<String, EventValue> events; |
| 98 | 85 | ||
| 99 | } | 86 | } |
| 100 | 87 | ||
| 101 | - /** | ||
| 102 | - * 设备标识 | ||
| 103 | - */ | ||
| 104 | - @Data | ||
| 105 | - @Accessors(chain = true) | ||
| 106 | - public static class DeviceIdentity { | ||
| 107 | - | ||
| 108 | - /** | ||
| 109 | - * 产品标识 | ||
| 110 | - */ | ||
| 111 | - private String productKey; | ||
| 112 | - | ||
| 113 | - /** | ||
| 114 | - * 设备名称 | ||
| 115 | - */ | ||
| 116 | - private String deviceName; | ||
| 117 | - | ||
| 118 | - } | ||
| 119 | - | ||
| 120 | } | 88 | } |
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/property/IotDevicePropertyPostReqDTO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.core.topic.property; | ||
| 2 | + | ||
| 3 | +import java.util.HashMap; | ||
| 4 | +import java.util.Map; | ||
| 5 | + | ||
| 6 | +/** | ||
| 7 | + * IoT 设备属性上报 Request DTO | ||
| 8 | + * <p> | ||
| 9 | + * 用于 thing.property.post 消息的 params 参数 | ||
| 10 | + * <p> | ||
| 11 | + * 本质是一个 Map,key 为属性标识符,value 为属性值 | ||
| 12 | + * | ||
| 13 | + * @author 芋道源码 | ||
| 14 | + * @see <a href="https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services">阿里云 - 设备上报属性</a> | ||
| 15 | + */ | ||
| 16 | +public class IotDevicePropertyPostReqDTO extends HashMap<String, Object> { | ||
| 17 | + | ||
| 18 | + public IotDevicePropertyPostReqDTO() { | ||
| 19 | + super(); | ||
| 20 | + } | ||
| 21 | + | ||
| 22 | + public IotDevicePropertyPostReqDTO(Map<String, Object> properties) { | ||
| 23 | + super(properties); | ||
| 24 | + } | ||
| 25 | + | ||
| 26 | + /** | ||
| 27 | + * 创建属性上报 DTO | ||
| 28 | + * | ||
| 29 | + * @param properties 属性数据 | ||
| 30 | + * @return DTO 对象 | ||
| 31 | + */ | ||
| 32 | + public static IotDevicePropertyPostReqDTO of(Map<String, Object> properties) { | ||
| 33 | + return new IotDevicePropertyPostReqDTO(properties); | ||
| 34 | + } | ||
| 35 | + | ||
| 36 | +} |
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/topo/IotDeviceTopoDeleteReqDTO.java
| 1 | package cn.iocoder.yudao.module.iot.core.topic.topo; | 1 | package cn.iocoder.yudao.module.iot.core.topic.topo; |
| 2 | 2 | ||
| 3 | +import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity; | ||
| 4 | +import jakarta.validation.Valid; | ||
| 3 | import jakarta.validation.constraints.NotEmpty; | 5 | import jakarta.validation.constraints.NotEmpty; |
| 4 | import lombok.Data; | 6 | import lombok.Data; |
| 5 | 7 | ||
| @@ -16,30 +18,11 @@ import java.util.List; | @@ -16,30 +18,11 @@ import java.util.List; | ||
| 16 | @Data | 18 | @Data |
| 17 | public class IotDeviceTopoDeleteReqDTO { | 19 | public class IotDeviceTopoDeleteReqDTO { |
| 18 | 20 | ||
| 19 | - // TODO @AI:应该是数组;IotDeviceIdentify | ||
| 20 | /** | 21 | /** |
| 21 | * 子设备标识列表 | 22 | * 子设备标识列表 |
| 22 | */ | 23 | */ |
| 23 | - private List<SubDevice> subDevices; | ||
| 24 | - | ||
| 25 | - /** | ||
| 26 | - * 子设备标识 | ||
| 27 | - */ | ||
| 28 | - @Data | ||
| 29 | - public static class SubDevice { | ||
| 30 | - | ||
| 31 | - /** | ||
| 32 | - * 子设备 ProductKey | ||
| 33 | - */ | ||
| 34 | - @NotEmpty(message = "产品标识不能为空") | ||
| 35 | - private String productKey; | ||
| 36 | - | ||
| 37 | - /** | ||
| 38 | - * 子设备 DeviceName | ||
| 39 | - */ | ||
| 40 | - @NotEmpty(message = "设备名称不能为空") | ||
| 41 | - private String deviceName; | ||
| 42 | - | ||
| 43 | - } | 24 | + @Valid |
| 25 | + @NotEmpty(message = "子设备标识列表不能为空") | ||
| 26 | + private List<IotDeviceIdentity> subDevices; | ||
| 44 | 27 | ||
| 45 | } | 28 | } |
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/topo/IotDeviceTopoGetReqDTO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.core.topic.topo; | ||
| 2 | + | ||
| 3 | +import lombok.Data; | ||
| 4 | + | ||
| 5 | +/** | ||
| 6 | + * IoT 设备拓扑关系获取 Request DTO | ||
| 7 | + * <p> | ||
| 8 | + * 用于 thing.topo.get 请求的 params 参数(目前为空,预留扩展) | ||
| 9 | + * | ||
| 10 | + * @author 芋道源码 | ||
| 11 | + * @see <a href="https://help.aliyun.com/zh/marketplace/obtain-topological-relationship">阿里云 - 获取拓扑关系</a> | ||
| 12 | + */ | ||
| 13 | +@Data | ||
| 14 | +public class IotDeviceTopoGetReqDTO { | ||
| 15 | + | ||
| 16 | +} |
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/topo/IotDeviceTopoRespDTO.java renamed to yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/topo/IotDeviceTopoGetRespDTO.java
| 1 | package cn.iocoder.yudao.module.iot.core.topic.topo; | 1 | package cn.iocoder.yudao.module.iot.core.topic.topo; |
| 2 | 2 | ||
| 3 | +import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity; | ||
| 3 | import lombok.Data; | 4 | import lombok.Data; |
| 4 | 5 | ||
| 5 | -// TODO @AI:IotDeviceTopoGetRespDTO | 6 | +import java.util.List; |
| 7 | + | ||
| 6 | /** | 8 | /** |
| 7 | - * IoT 设备拓扑关系 Response DTO | 9 | + * IoT 设备拓扑关系获取 Response DTO |
| 8 | * <p> | 10 | * <p> |
| 9 | - * 用于 thing.topo.get 响应的子设备信息 | 11 | + * 用于 thing.topo.get 响应 |
| 10 | * | 12 | * |
| 11 | * @author 芋道源码 | 13 | * @author 芋道源码 |
| 12 | * @see <a href="https://help.aliyun.com/zh/marketplace/obtain-topological-relationship">阿里云 - 获取拓扑关系</a> | 14 | * @see <a href="https://help.aliyun.com/zh/marketplace/obtain-topological-relationship">阿里云 - 获取拓扑关系</a> |
| 13 | */ | 15 | */ |
| 14 | @Data | 16 | @Data |
| 15 | -public class IotDeviceTopoRespDTO { | ||
| 16 | - | ||
| 17 | - // TODO @AI:应该是数组;IotDeviceIdentify | ||
| 18 | - /** | ||
| 19 | - * 子设备 ProductKey | ||
| 20 | - */ | ||
| 21 | - private String productKey; | 17 | +public class IotDeviceTopoGetRespDTO { |
| 22 | 18 | ||
| 23 | /** | 19 | /** |
| 24 | - * 子设备 DeviceName | 20 | + * 子设备列表 |
| 25 | */ | 21 | */ |
| 26 | - private String deviceName; | 22 | + private List<IotDeviceIdentity> subDevices; |
| 27 | 23 | ||
| 28 | } | 24 | } |