Commit 38a21ad59c513c957079c97df99cc596859fd61c
1 parent
1309be39
feat(iot):【网关设备:70%】动态注册的初步实现(未测试),基于 stateful-sauteeing-pillow.md 规划
Showing
22 changed files
with
634 additions
and
65 deletions
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/IoTDeviceApiImpl.java
| @@ -7,6 +7,8 @@ import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi; | @@ -7,6 +7,8 @@ import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi; | ||
| 7 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; | 7 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; |
| 8 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceGetReqDTO; | 8 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceGetReqDTO; |
| 9 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO; | 9 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO; |
| 10 | +import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO; | ||
| 11 | +import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterRespDTO; | ||
| 10 | import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; | 12 | import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; |
| 11 | import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; | 13 | import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; |
| 12 | import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; | 14 | import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; |
| @@ -57,4 +59,11 @@ public class IoTDeviceApiImpl implements IotDeviceCommonApi { | @@ -57,4 +59,11 @@ public class IoTDeviceApiImpl implements IotDeviceCommonApi { | ||
| 57 | })); | 59 | })); |
| 58 | } | 60 | } |
| 59 | 61 | ||
| 62 | + @Override | ||
| 63 | + @PostMapping(RpcConstants.RPC_API_PREFIX + "/iot/device/register") | ||
| 64 | + @PermitAll | ||
| 65 | + public CommonResult<IotDeviceRegisterRespDTO> registerDevice(@RequestBody IotDeviceRegisterReqDTO reqDTO) { | ||
| 66 | + return success(deviceService.registerDevice(reqDTO)); | ||
| 67 | + } | ||
| 68 | + | ||
| 60 | } | 69 | } |
| 61 | \ No newline at end of file | 70 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/product/IotProductRespVO.java
| @@ -27,6 +27,12 @@ public class IotProductRespVO { | @@ -27,6 +27,12 @@ public class IotProductRespVO { | ||
| 27 | @ExcelProperty("产品标识") | 27 | @ExcelProperty("产品标识") |
| 28 | private String productKey; | 28 | private String productKey; |
| 29 | 29 | ||
| 30 | + @Schema(description = "产品密钥", requiredMode = Schema.RequiredMode.REQUIRED) | ||
| 31 | + private String productSecret; | ||
| 32 | + | ||
| 33 | + @Schema(description = "是否开启动态注册", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") | ||
| 34 | + private Boolean registerEnabled; | ||
| 35 | + | ||
| 30 | @Schema(description = "产品分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | 36 | @Schema(description = "产品分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| 31 | private Long categoryId; | 37 | private Long categoryId; |
| 32 | 38 |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/product/IotProductSaveReqVO.java
| @@ -48,4 +48,8 @@ public class IotProductSaveReqVO { | @@ -48,4 +48,8 @@ public class IotProductSaveReqVO { | ||
| 48 | @NotEmpty(message = "数据格式不能为空") | 48 | @NotEmpty(message = "数据格式不能为空") |
| 49 | private String codecType; | 49 | private String codecType; |
| 50 | 50 | ||
| 51 | + @Schema(description = "是否开启动态注册", example = "false") | ||
| 52 | + @NotEmpty(message = "是否开启动态注册不能为空") | ||
| 53 | + private Boolean registerEnabled; | ||
| 54 | + | ||
| 51 | } | 55 | } |
| 52 | \ No newline at end of file | 56 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/product/IotProductDO.java
| @@ -33,6 +33,14 @@ public class IotProductDO extends TenantBaseDO { | @@ -33,6 +33,14 @@ public class IotProductDO extends TenantBaseDO { | ||
| 33 | */ | 33 | */ |
| 34 | private String productKey; | 34 | private String productKey; |
| 35 | /** | 35 | /** |
| 36 | + * 产品密钥,用于一型一密动态注册 | ||
| 37 | + */ | ||
| 38 | + private String productSecret; | ||
| 39 | + /** | ||
| 40 | + * 是否开启动态注册 | ||
| 41 | + */ | ||
| 42 | + private Boolean registerEnabled; | ||
| 43 | + /** | ||
| 36 | * 产品分类编号 | 44 | * 产品分类编号 |
| 37 | * <p> | 45 | * <p> |
| 38 | * 关联 {@link IotProductCategoryDO#getId()} | 46 | * 关联 {@link IotProductCategoryDO#getId()} |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java
| @@ -40,9 +40,12 @@ public interface ErrorCodeConstants { | @@ -40,9 +40,12 @@ public interface ErrorCodeConstants { | ||
| 40 | ErrorCode DEVICE_TOPO_SUB_DEVICE_USERNAME_INVALID = new ErrorCode(1_050_003_101, "子设备用户名格式无效"); | 40 | ErrorCode DEVICE_TOPO_SUB_DEVICE_USERNAME_INVALID = new ErrorCode(1_050_003_101, "子设备用户名格式无效"); |
| 41 | ErrorCode DEVICE_TOPO_SUB_DEVICE_AUTH_FAILED = new ErrorCode(1_050_003_102, "子设备认证失败"); | 41 | ErrorCode DEVICE_TOPO_SUB_DEVICE_AUTH_FAILED = new ErrorCode(1_050_003_102, "子设备认证失败"); |
| 42 | ErrorCode DEVICE_TOPO_SUB_NOT_BINDTO_GATEWAY = new ErrorCode(1_050_003_103, "子设备【{}/{}】未绑定到该网关"); | 42 | ErrorCode DEVICE_TOPO_SUB_NOT_BINDTO_GATEWAY = new ErrorCode(1_050_003_103, "子设备【{}/{}】未绑定到该网关"); |
| 43 | - // 子设备注册相关错误码 1-050-003-200 | 43 | + // 设备注册相关错误码 1-050-003-200 |
| 44 | ErrorCode DEVICE_SUB_REGISTER_PARAMS_INVALID = new ErrorCode(1_050_003_200, "子设备注册参数无效"); | 44 | ErrorCode DEVICE_SUB_REGISTER_PARAMS_INVALID = new ErrorCode(1_050_003_200, "子设备注册参数无效"); |
| 45 | ErrorCode DEVICE_SUB_REGISTER_PRODUCT_NOT_GATEWAY_SUB = new ErrorCode(1_050_003_201, "产品【{}】不是网关子设备类型"); | 45 | ErrorCode DEVICE_SUB_REGISTER_PRODUCT_NOT_GATEWAY_SUB = new ErrorCode(1_050_003_201, "产品【{}】不是网关子设备类型"); |
| 46 | + ErrorCode DEVICE_REGISTER_DISABLED = new ErrorCode(1_050_003_210, "该产品未开启动态注册功能"); | ||
| 47 | + ErrorCode DEVICE_REGISTER_SIGN_INVALID = new ErrorCode(1_050_003_211, "动态注册签名验证失败"); | ||
| 48 | + ErrorCode DEVICE_ALREADY_ACTIVATED = new ErrorCode(1_050_003_212, "设备已激活,不允许重复注册"); | ||
| 46 | 49 | ||
| 47 | // ========== 产品分类 1-050-004-000 ========== | 50 | // ========== 产品分类 1-050-004-000 ========== |
| 48 | ErrorCode PRODUCT_CATEGORY_NOT_EXISTS = new ErrorCode(1_050_004_000, "产品分类不存在"); | 51 | ErrorCode PRODUCT_CATEGORY_NOT_EXISTS = new ErrorCode(1_050_004_000, "产品分类不存在"); |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java
| @@ -6,6 +6,8 @@ import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; | @@ -6,6 +6,8 @@ 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; | 7 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 8 | import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity; | 8 | import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity; |
| 9 | +import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO; | ||
| 10 | +import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterRespDTO; | ||
| 9 | import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterRespDTO; | 11 | import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterRespDTO; |
| 10 | import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoGetRespDTO; | 12 | import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoGetRespDTO; |
| 11 | import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; | 13 | import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; |
| @@ -292,7 +294,7 @@ public interface IotDeviceService { | @@ -292,7 +294,7 @@ public interface IotDeviceService { | ||
| 292 | */ | 294 | */ |
| 293 | List<IotDeviceDO> getDeviceListByHasLocation(); | 295 | List<IotDeviceDO> getDeviceListByHasLocation(); |
| 294 | 296 | ||
| 295 | - // ========== 网关-子设备绑定相关 ========== | 297 | + // ========== 网关-拓扑管理(后台操作) ========== |
| 296 | 298 | ||
| 297 | /** | 299 | /** |
| 298 | * 绑定子设备到网关 | 300 | * 绑定子设备到网关 |
| @@ -354,6 +356,8 @@ public interface IotDeviceService { | @@ -354,6 +356,8 @@ public interface IotDeviceService { | ||
| 354 | */ | 356 | */ |
| 355 | IotDeviceTopoGetRespDTO handleTopoGetMessage(IotDeviceDO gatewayDevice); | 357 | IotDeviceTopoGetRespDTO handleTopoGetMessage(IotDeviceDO gatewayDevice); |
| 356 | 358 | ||
| 359 | + // ========== 设备动态注册 ========== | ||
| 360 | + | ||
| 357 | /** | 361 | /** |
| 358 | * 处理子设备动态注册消息(网关设备上报) | 362 | * 处理子设备动态注册消息(网关设备上报) |
| 359 | * | 363 | * |
| @@ -363,4 +367,12 @@ public interface IotDeviceService { | @@ -363,4 +367,12 @@ public interface IotDeviceService { | ||
| 363 | */ | 367 | */ |
| 364 | List<IotSubDeviceRegisterRespDTO> handleSubDeviceRegisterMessage(IotDeviceMessage message, IotDeviceDO gatewayDevice); | 368 | List<IotSubDeviceRegisterRespDTO> handleSubDeviceRegisterMessage(IotDeviceMessage message, IotDeviceDO gatewayDevice); |
| 365 | 369 | ||
| 370 | + /** | ||
| 371 | + * 设备动态注册(直连设备/网关) | ||
| 372 | + * | ||
| 373 | + * @param reqDTO 动态注册请求 | ||
| 374 | + * @return 注册结果(包含 DeviceSecret) | ||
| 375 | + */ | ||
| 376 | + IotDeviceRegisterRespDTO registerDevice(@Valid IotDeviceRegisterReqDTO reqDTO); | ||
| 377 | + | ||
| 366 | } | 378 | } |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java
| 1 | package cn.iocoder.yudao.module.iot.service.device; | 1 | package cn.iocoder.yudao.module.iot.service.device; |
| 2 | 2 | ||
| 3 | import cn.hutool.core.collection.CollUtil; | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | -import cn.hutool.core.util.IdUtil; | 4 | +import cn.hutool.core.util.BooleanUtil; |
| 5 | import cn.hutool.core.util.ObjUtil; | 5 | import cn.hutool.core.util.ObjUtil; |
| 6 | import cn.hutool.core.util.StrUtil; | 6 | import cn.hutool.core.util.StrUtil; |
| 7 | import cn.hutool.extra.spring.SpringUtil; | 7 | import cn.hutool.extra.spring.SpringUtil; |
| @@ -18,6 +18,8 @@ import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; | @@ -18,6 +18,8 @@ import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; | ||
| 18 | import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum; | 18 | import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum; |
| 19 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | 19 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 20 | import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity; | 20 | import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity; |
| 21 | +import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO; | ||
| 22 | +import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterRespDTO; | ||
| 21 | import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterReqDTO; | 23 | import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterReqDTO; |
| 22 | import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterRespDTO; | 24 | import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterRespDTO; |
| 23 | import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoAddReqDTO; | 25 | import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoAddReqDTO; |
| @@ -77,6 +79,10 @@ public class IotDeviceServiceImpl implements IotDeviceService { | @@ -77,6 +79,10 @@ public class IotDeviceServiceImpl implements IotDeviceService { | ||
| 77 | @Lazy // 延迟加载,解决循环依赖 | 79 | @Lazy // 延迟加载,解决循环依赖 |
| 78 | private IotDeviceMessageService deviceMessageService; | 80 | private IotDeviceMessageService deviceMessageService; |
| 79 | 81 | ||
| 82 | + private IotDeviceServiceImpl getSelf() { | ||
| 83 | + return SpringUtil.getBean(getClass()); | ||
| 84 | + } | ||
| 85 | + | ||
| 80 | @Override | 86 | @Override |
| 81 | public Long createDevice(IotDeviceSaveReqVO createReqVO) { | 87 | public Long createDevice(IotDeviceSaveReqVO createReqVO) { |
| 82 | return createDevice0(createReqVO).getId(); | 88 | return createDevice0(createReqVO).getId(); |
| @@ -138,7 +144,7 @@ public class IotDeviceServiceImpl implements IotDeviceService { | @@ -138,7 +144,7 @@ public class IotDeviceServiceImpl implements IotDeviceService { | ||
| 138 | device.setProductId(product.getId()).setProductKey(product.getProductKey()) | 144 | device.setProductId(product.getId()).setProductKey(product.getProductKey()) |
| 139 | .setDeviceType(product.getDeviceType()); | 145 | .setDeviceType(product.getDeviceType()); |
| 140 | // 生成密钥 | 146 | // 生成密钥 |
| 141 | - device.setDeviceSecret(generateDeviceSecret()); | 147 | + device.setDeviceSecret(IotDeviceAuthUtils.generateDeviceSecret()); |
| 142 | // 设置设备状态为未激活 | 148 | // 设置设备状态为未激活 |
| 143 | device.setState(IotDeviceStateEnum.INACTIVE.getState()); | 149 | device.setState(IotDeviceStateEnum.INACTIVE.getState()); |
| 144 | } | 150 | } |
| @@ -368,15 +374,6 @@ public class IotDeviceServiceImpl implements IotDeviceService { | @@ -368,15 +374,6 @@ public class IotDeviceServiceImpl implements IotDeviceService { | ||
| 368 | return deviceMapper.selectCountByGroupId(groupId); | 374 | return deviceMapper.selectCountByGroupId(groupId); |
| 369 | } | 375 | } |
| 370 | 376 | ||
| 371 | - /** | ||
| 372 | - * 生成 deviceSecret | ||
| 373 | - * | ||
| 374 | - * @return 生成的 deviceSecret | ||
| 375 | - */ | ||
| 376 | - private String generateDeviceSecret() { | ||
| 377 | - return IdUtil.fastSimpleUUID(); | ||
| 378 | - } | ||
| 379 | - | ||
| 380 | @Override | 377 | @Override |
| 381 | @Transactional(rollbackFor = Exception.class) // 添加事务,异常则回滚所有导入 | 378 | @Transactional(rollbackFor = Exception.class) // 添加事务,异常则回滚所有导入 |
| 382 | public IotDeviceImportRespVO importDevice(List<IotDeviceImportExcelVO> importDevices, boolean updateSupport) { | 379 | public IotDeviceImportRespVO importDevice(List<IotDeviceImportExcelVO> importDevices, boolean updateSupport) { |
| @@ -566,7 +563,7 @@ public class IotDeviceServiceImpl implements IotDeviceService { | @@ -566,7 +563,7 @@ public class IotDeviceServiceImpl implements IotDeviceService { | ||
| 566 | return deviceMapper.selectListByHasLocation(); | 563 | return deviceMapper.selectListByHasLocation(); |
| 567 | } | 564 | } |
| 568 | 565 | ||
| 569 | - // ========== 网关-子设备绑定相关 ========== | 566 | + // ========== 网关-拓扑管理(后台操作) ========== |
| 570 | 567 | ||
| 571 | @Override | 568 | @Override |
| 572 | @Transactional(rollbackFor = Exception.class) | 569 | @Transactional(rollbackFor = Exception.class) |
| @@ -766,6 +763,79 @@ public class IotDeviceServiceImpl implements IotDeviceService { | @@ -766,6 +763,79 @@ public class IotDeviceServiceImpl implements IotDeviceService { | ||
| 766 | return new IotDeviceTopoGetRespDTO().setSubDevices(subDeviceIdentities); | 763 | return new IotDeviceTopoGetRespDTO().setSubDevices(subDeviceIdentities); |
| 767 | } | 764 | } |
| 768 | 765 | ||
| 766 | + /** | ||
| 767 | + * 发送拓扑变更通知给网关设备 | ||
| 768 | + * | ||
| 769 | + * @param gatewayId 网关设备编号 | ||
| 770 | + * @param status 变更状态(0-创建, 1-删除) | ||
| 771 | + * @param subDevices 子设备列表 | ||
| 772 | + * @see <a href="https://help.aliyun.com/zh/marketplace/notify-gateway-topology-changes">阿里云 - 通知网关拓扑关系变化</a> | ||
| 773 | + */ | ||
| 774 | + private void sendTopoChangeNotify(Long gatewayId, Integer status, List<IotDeviceDO> subDevices) { | ||
| 775 | + if (CollUtil.isEmpty(subDevices)) { | ||
| 776 | + return; | ||
| 777 | + } | ||
| 778 | + // 1. 获取网关设备 | ||
| 779 | + IotDeviceDO gatewayDevice = deviceMapper.selectById(gatewayId); | ||
| 780 | + if (gatewayDevice == null) { | ||
| 781 | + log.warn("[sendTopoChangeNotify][网关设备({}) 不存在,无法发送拓扑变更通知]", gatewayId); | ||
| 782 | + return; | ||
| 783 | + } | ||
| 784 | + | ||
| 785 | + try { | ||
| 786 | + // 2.1 构建拓扑变更通知消息 | ||
| 787 | + List<IotDeviceIdentity> subList = convertList(subDevices, subDevice -> | ||
| 788 | + new IotDeviceIdentity(subDevice.getProductKey(), subDevice.getDeviceName())); | ||
| 789 | + IotDeviceTopoChangeReqDTO params = new IotDeviceTopoChangeReqDTO(status, subList); | ||
| 790 | + IotDeviceMessage notifyMessage = IotDeviceMessage.requestOf( | ||
| 791 | + IotDeviceMessageMethodEnum.TOPO_CHANGE.getMethod(), params); | ||
| 792 | + | ||
| 793 | + // 2.2 发送消息 | ||
| 794 | + deviceMessageService.sendDeviceMessage(notifyMessage, gatewayDevice); | ||
| 795 | + log.info("[sendTopoChangeNotify][网关({}/{}) 发送拓扑变更通知成功,status={}, subDevices={}]", | ||
| 796 | + gatewayDevice.getProductKey(), gatewayDevice.getDeviceName(), | ||
| 797 | + status, subList); | ||
| 798 | + } catch (Exception ex) { | ||
| 799 | + log.error("[sendTopoChangeNotify][网关({}/{}) 发送拓扑变更通知失败,status={}]", | ||
| 800 | + gatewayDevice.getProductKey(), gatewayDevice.getDeviceName(), status, ex); | ||
| 801 | + } | ||
| 802 | + } | ||
| 803 | + | ||
| 804 | + // ========== 设备动态注册 ========== | ||
| 805 | + | ||
| 806 | + @Override | ||
| 807 | + public IotDeviceRegisterRespDTO registerDevice(IotDeviceRegisterReqDTO reqDTO) { | ||
| 808 | + // 1.1 校验产品 | ||
| 809 | + IotProductDO product = productService.getProductByProductKey(reqDTO.getProductKey()); | ||
| 810 | + if (product == null) { | ||
| 811 | + throw exception(PRODUCT_NOT_EXISTS); | ||
| 812 | + } | ||
| 813 | + // 1.2 校验产品是否开启动态注册 | ||
| 814 | + if (BooleanUtil.isFalse(product.getRegisterEnabled())) { | ||
| 815 | + throw exception(DEVICE_REGISTER_DISABLED); | ||
| 816 | + } | ||
| 817 | + // 1.3 验证签名 | ||
| 818 | + if (!IotDeviceAuthUtils.verifyRegisterSign(product.getProductSecret(), | ||
| 819 | + reqDTO.getProductKey(), reqDTO.getDeviceName(), reqDTO.getRandom(), reqDTO.getSign())) { | ||
| 820 | + throw exception(DEVICE_REGISTER_SIGN_INVALID); | ||
| 821 | + } | ||
| 822 | + | ||
| 823 | + // 4. 查找设备(预注册模式:设备必须已存在) | ||
| 824 | + // TODO @AI:设备不用提前有,这个有问题! | ||
| 825 | + IotDeviceDO device = getSelf().getDeviceFromCache(reqDTO.getProductKey(), reqDTO.getDeviceName()); | ||
| 826 | + if (device == null) { | ||
| 827 | + throw exception(DEVICE_NOT_EXISTS); | ||
| 828 | + } | ||
| 829 | + | ||
| 830 | + // 5. 校验设备是否已激活(已激活的设备不允许重复注册) | ||
| 831 | + if (!Objects.equals(device.getState(), IotDeviceStateEnum.INACTIVE.getState())) { | ||
| 832 | + throw exception(DEVICE_ALREADY_ACTIVATED); | ||
| 833 | + } | ||
| 834 | + | ||
| 835 | + // 6. 返回设备密钥 | ||
| 836 | + return new IotDeviceRegisterRespDTO(device.getProductKey(), device.getDeviceName(), device.getDeviceSecret()); | ||
| 837 | + } | ||
| 838 | + | ||
| 769 | @Override | 839 | @Override |
| 770 | public List<IotSubDeviceRegisterRespDTO> handleSubDeviceRegisterMessage(IotDeviceMessage message, IotDeviceDO gatewayDevice) { | 840 | public List<IotSubDeviceRegisterRespDTO> handleSubDeviceRegisterMessage(IotDeviceMessage message, IotDeviceDO gatewayDevice) { |
| 771 | // 1.1 校验网关设备类型 | 841 | // 1.1 校验网关设备类型 |
| @@ -799,6 +869,7 @@ public class IotDeviceServiceImpl implements IotDeviceService { | @@ -799,6 +869,7 @@ public class IotDeviceServiceImpl implements IotDeviceService { | ||
| 799 | return results; | 869 | return results; |
| 800 | } | 870 | } |
| 801 | 871 | ||
| 872 | + // TODO @AI:阿里云的,设备必须存在; | ||
| 802 | private IotDeviceDO registerSubDevice(IotDeviceDO gatewayDevice, IotSubDeviceRegisterReqDTO params) { | 873 | private IotDeviceDO registerSubDevice(IotDeviceDO gatewayDevice, IotSubDeviceRegisterReqDTO params) { |
| 803 | // 1.1 校验产品 | 874 | // 1.1 校验产品 |
| 804 | IotProductDO product = productService.getProductByProductKey(params.getProductKey()); | 875 | IotProductDO product = productService.getProductByProductKey(params.getProductKey()); |
| @@ -833,46 +904,4 @@ public class IotDeviceServiceImpl implements IotDeviceService { | @@ -833,46 +904,4 @@ public class IotDeviceServiceImpl implements IotDeviceService { | ||
| 833 | return newDevice; | 904 | return newDevice; |
| 834 | } | 905 | } |
| 835 | 906 | ||
| 836 | - private IotDeviceServiceImpl getSelf() { | ||
| 837 | - return SpringUtil.getBean(getClass()); | ||
| 838 | - } | ||
| 839 | - | ||
| 840 | - /** | ||
| 841 | - * 发送拓扑变更通知给网关设备 | ||
| 842 | - * | ||
| 843 | - * @param gatewayId 网关设备编号 | ||
| 844 | - * @param status 变更状态(0-创建, 1-删除) | ||
| 845 | - * @param subDevices 子设备列表 | ||
| 846 | - * @see <a href="https://help.aliyun.com/zh/marketplace/notify-gateway-topology-changes">阿里云 - 通知网关拓扑关系变化</a> | ||
| 847 | - */ | ||
| 848 | - private void sendTopoChangeNotify(Long gatewayId, Integer status, List<IotDeviceDO> subDevices) { | ||
| 849 | - if (CollUtil.isEmpty(subDevices)) { | ||
| 850 | - return; | ||
| 851 | - } | ||
| 852 | - // 1. 获取网关设备 | ||
| 853 | - IotDeviceDO gatewayDevice = deviceMapper.selectById(gatewayId); | ||
| 854 | - if (gatewayDevice == null) { | ||
| 855 | - log.warn("[sendTopoChangeNotify][网关设备({}) 不存在,无法发送拓扑变更通知]", gatewayId); | ||
| 856 | - return; | ||
| 857 | - } | ||
| 858 | - | ||
| 859 | - try { | ||
| 860 | - // 2.1 构建拓扑变更通知消息 | ||
| 861 | - List<IotDeviceIdentity> subList = convertList(subDevices, subDevice -> | ||
| 862 | - new IotDeviceIdentity(subDevice.getProductKey(), subDevice.getDeviceName())); | ||
| 863 | - IotDeviceTopoChangeReqDTO params = new IotDeviceTopoChangeReqDTO(status, subList); | ||
| 864 | - IotDeviceMessage notifyMessage = IotDeviceMessage.requestOf( | ||
| 865 | - IotDeviceMessageMethodEnum.TOPO_CHANGE.getMethod(), params); | ||
| 866 | - | ||
| 867 | - // 2.2 发送消息 | ||
| 868 | - deviceMessageService.sendDeviceMessage(notifyMessage, gatewayDevice); | ||
| 869 | - log.info("[sendTopoChangeNotify][网关({}/{}) 发送拓扑变更通知成功,status={}, subDevices={}]", | ||
| 870 | - gatewayDevice.getProductKey(), gatewayDevice.getDeviceName(), | ||
| 871 | - status, subList); | ||
| 872 | - } catch (Exception ex) { | ||
| 873 | - log.error("[sendTopoChangeNotify][网关({}/{}) 发送拓扑变更通知失败,status={}]", | ||
| 874 | - gatewayDevice.getProductKey(), gatewayDevice.getDeviceName(), status, ex); | ||
| 875 | - } | ||
| 876 | - } | ||
| 877 | - | ||
| 878 | } | 907 | } |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductServiceImpl.java
| @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.iot.service.product; | @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.iot.service.product; | ||
| 3 | import cn.hutool.core.collection.CollUtil; | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 4 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 5 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | 5 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 6 | +import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; | ||
| 6 | import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; | 7 | import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; |
| 7 | import cn.iocoder.yudao.module.iot.controller.admin.product.vo.product.IotProductPageReqVO; | 8 | import cn.iocoder.yudao.module.iot.controller.admin.product.vo.product.IotProductPageReqVO; |
| 8 | import cn.iocoder.yudao.module.iot.controller.admin.product.vo.product.IotProductSaveReqVO; | 9 | import cn.iocoder.yudao.module.iot.controller.admin.product.vo.product.IotProductSaveReqVO; |
| @@ -53,7 +54,8 @@ public class IotProductServiceImpl implements IotProductService { | @@ -53,7 +54,8 @@ public class IotProductServiceImpl implements IotProductService { | ||
| 53 | 54 | ||
| 54 | // 2. 插入 | 55 | // 2. 插入 |
| 55 | IotProductDO product = BeanUtils.toBean(createReqVO, IotProductDO.class) | 56 | IotProductDO product = BeanUtils.toBean(createReqVO, IotProductDO.class) |
| 56 | - .setStatus(IotProductStatusEnum.UNPUBLISHED.getStatus()); | 57 | + .setStatus(IotProductStatusEnum.UNPUBLISHED.getStatus()) |
| 58 | + .setProductSecret(IotDeviceAuthUtils.generateProductSecret()); | ||
| 57 | productMapper.insert(product); | 59 | productMapper.insert(product); |
| 58 | return product.getId(); | 60 | return product.getId(); |
| 59 | } | 61 | } |
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/IotDeviceCommonApi.java
| @@ -4,6 +4,8 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult; | @@ -4,6 +4,8 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult; | ||
| 4 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; | 4 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; |
| 5 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceGetReqDTO; | 5 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceGetReqDTO; |
| 6 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO; | 6 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO; |
| 7 | +import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO; | ||
| 8 | +import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterRespDTO; | ||
| 7 | 9 | ||
| 8 | /** | 10 | /** |
| 9 | * IoT 设备通用 API | 11 | * IoT 设备通用 API |
| @@ -28,4 +30,12 @@ public interface IotDeviceCommonApi { | @@ -28,4 +30,12 @@ public interface IotDeviceCommonApi { | ||
| 28 | */ | 30 | */ |
| 29 | CommonResult<IotDeviceRespDTO> getDevice(IotDeviceGetReqDTO infoReqDTO); | 31 | CommonResult<IotDeviceRespDTO> getDevice(IotDeviceGetReqDTO infoReqDTO); |
| 30 | 32 | ||
| 33 | + /** | ||
| 34 | + * 设备动态注册(一型一密) | ||
| 35 | + * | ||
| 36 | + * @param reqDTO 动态注册请求 | ||
| 37 | + * @return 注册结果(包含 DeviceSecret) | ||
| 38 | + */ | ||
| 39 | + CommonResult<IotDeviceRegisterRespDTO> registerDevice(IotDeviceRegisterReqDTO reqDTO); | ||
| 40 | + | ||
| 31 | } | 41 | } |
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotDeviceMessageMethodEnum.java
| @@ -33,9 +33,10 @@ public enum IotDeviceMessageMethodEnum implements ArrayValuable<String> { | @@ -33,9 +33,10 @@ public enum IotDeviceMessageMethodEnum implements ArrayValuable<String> { | ||
| 33 | TOPO_CHANGE("thing.topo.change", "拓扑关系变更通知", false), | 33 | TOPO_CHANGE("thing.topo.change", "拓扑关系变更通知", false), |
| 34 | 34 | ||
| 35 | // ========== 设备注册 ========== | 35 | // ========== 设备注册 ========== |
| 36 | - // 可参考:https://help.aliyun.com/zh/iot/user-guide/register-devices | 36 | + // 可参考:https://help.aliyun.com/zh/iot/user-guide/unique-certificate-per-product-verification |
| 37 | 37 | ||
| 38 | - SUB_DEVICE_REGISTER("thing.sub.register", "子设备动态注册", true), | 38 | + DEVICE_REGISTER("thing.auth.register", "设备动态注册", true), |
| 39 | + SUB_DEVICE_REGISTER("thing.auth.register.sub", "子设备动态注册", true), | ||
| 39 | 40 | ||
| 40 | // ========== 设备属性 ========== | 41 | // ========== 设备属性 ========== |
| 41 | // 可参考:https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services | 42 | // 可参考:https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services |
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/auth/IotDeviceRegisterReqDTO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.core.topic.auth; | ||
| 2 | + | ||
| 3 | +import jakarta.validation.constraints.NotEmpty; | ||
| 4 | +import lombok.Data; | ||
| 5 | + | ||
| 6 | +/** | ||
| 7 | + * IoT 设备动态注册 Request DTO | ||
| 8 | + * <p> | ||
| 9 | + * 用于直连设备/网关的一型一密动态注册:使用 ProductSecret 验证签名,返回 DeviceSecret | ||
| 10 | + * | ||
| 11 | + * @author 芋道源码 | ||
| 12 | + * @see <a href="https://help.aliyun.com/zh/iot/user-guide/unique-certificate-per-product-verification">阿里云 - 一型一密</a> | ||
| 13 | + */ | ||
| 14 | +@Data | ||
| 15 | +public class IotDeviceRegisterReqDTO { | ||
| 16 | + | ||
| 17 | + /** | ||
| 18 | + * 产品标识 | ||
| 19 | + */ | ||
| 20 | + @NotEmpty(message = "产品标识不能为空") | ||
| 21 | + private String productKey; | ||
| 22 | + | ||
| 23 | + /** | ||
| 24 | + * 设备名称 | ||
| 25 | + */ | ||
| 26 | + @NotEmpty(message = "设备名称不能为空") | ||
| 27 | + private String deviceName; | ||
| 28 | + | ||
| 29 | + // TODO @AI:可以去掉 random 字段; | ||
| 30 | + /** | ||
| 31 | + * 随机数,用于签名 | ||
| 32 | + */ | ||
| 33 | + @NotEmpty(message = "随机数不能为空") | ||
| 34 | + private String random; | ||
| 35 | + | ||
| 36 | + // TODO @AI:看起来,是直接带 productSecret 阿里云上,你在检查下! | ||
| 37 | + /** | ||
| 38 | + * 签名 | ||
| 39 | + */ | ||
| 40 | + @NotEmpty(message = "签名不能为空") | ||
| 41 | + private String sign; | ||
| 42 | + | ||
| 43 | +} |
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/auth/IotDeviceRegisterRespDTO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.core.topic.auth; | ||
| 2 | + | ||
| 3 | +import lombok.AllArgsConstructor; | ||
| 4 | +import lombok.Data; | ||
| 5 | +import lombok.NoArgsConstructor; | ||
| 6 | + | ||
| 7 | +/** | ||
| 8 | + * IoT 设备动态注册 Response DTO | ||
| 9 | + * <p> | ||
| 10 | + * 用于直连设备/网关的一型一密动态注册响应 | ||
| 11 | + * | ||
| 12 | + * @author 芋道源码 | ||
| 13 | + * @see <a href="https://help.aliyun.com/zh/iot/user-guide/unique-certificate-per-product-verification">阿里云 - 一型一密</a> | ||
| 14 | + */ | ||
| 15 | +@Data | ||
| 16 | +@NoArgsConstructor | ||
| 17 | +@AllArgsConstructor | ||
| 18 | +public class IotDeviceRegisterRespDTO { | ||
| 19 | + | ||
| 20 | + /** | ||
| 21 | + * 产品标识 | ||
| 22 | + */ | ||
| 23 | + private String productKey; | ||
| 24 | + | ||
| 25 | + /** | ||
| 26 | + * 设备名称 | ||
| 27 | + */ | ||
| 28 | + private String deviceName; | ||
| 29 | + | ||
| 30 | + /** | ||
| 31 | + * 设备密钥 | ||
| 32 | + */ | ||
| 33 | + private String deviceSecret; | ||
| 34 | + | ||
| 35 | +} |
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/auth/IotSubDeviceRegisterReqDTO.java
| @@ -6,10 +6,12 @@ import lombok.Data; | @@ -6,10 +6,12 @@ import lombok.Data; | ||
| 6 | /** | 6 | /** |
| 7 | * IoT 子设备动态注册 Request DTO | 7 | * IoT 子设备动态注册 Request DTO |
| 8 | * <p> | 8 | * <p> |
| 9 | - * 用于 thing.sub.register 消息的 params 数组元素 | 9 | + * 用于 thing.auth.register.sub 消息的 params 数组元素 |
| 10 | + * | ||
| 11 | + * 特殊:网关子设备的动态注册,必须已经创建好该网关子设备(不然哪来的 {@link #deviceName} 字段)。更多的好处,是设备不用提前烧录 deviceSecret 密钥。 | ||
| 10 | * | 12 | * |
| 11 | * @author 芋道源码 | 13 | * @author 芋道源码 |
| 12 | - * @see <a href="http://help.aliyun.com/zh/marketplace/dynamic-registration-of-sub-devices">阿里云 - 动态注册子设备</a> | 14 | + * @see <a href="https://help.aliyun.com/zh/iot/user-guide/register-devices">阿里云 - 动态注册子设备</a> |
| 13 | */ | 15 | */ |
| 14 | @Data | 16 | @Data |
| 15 | public class IotSubDeviceRegisterReqDTO { | 17 | public class IotSubDeviceRegisterReqDTO { |
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/auth/IotSubDeviceRegisterRespDTO.java
| @@ -7,10 +7,10 @@ import lombok.NoArgsConstructor; | @@ -7,10 +7,10 @@ import lombok.NoArgsConstructor; | ||
| 7 | /** | 7 | /** |
| 8 | * IoT 子设备动态注册 Response DTO | 8 | * IoT 子设备动态注册 Response DTO |
| 9 | * <p> | 9 | * <p> |
| 10 | - * 用于 thing.sub.register 响应的设备信息 | 10 | + * 用于 thing.auth.register.sub 响应的设备信息 |
| 11 | * | 11 | * |
| 12 | * @author 芋道源码 | 12 | * @author 芋道源码 |
| 13 | - * @see <a href="http://help.aliyun.com/zh/marketplace/dynamic-registration-of-sub-devices">阿里云 - 动态注册子设备</a> | 13 | + * @see <a href="https://help.aliyun.com/zh/iot/user-guide/register-devices">阿里云 - 动态注册子设备</a> |
| 14 | */ | 14 | */ |
| 15 | @Data | 15 | @Data |
| 16 | @NoArgsConstructor | 16 | @NoArgsConstructor |
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/util/IotDeviceAuthUtils.java
| 1 | package cn.iocoder.yudao.module.iot.core.util; | 1 | package cn.iocoder.yudao.module.iot.core.util; |
| 2 | 2 | ||
| 3 | +import cn.hutool.core.util.IdUtil; | ||
| 3 | import cn.hutool.core.util.StrUtil; | 4 | import cn.hutool.core.util.StrUtil; |
| 4 | import cn.hutool.crypto.digest.DigestUtil; | 5 | import cn.hutool.crypto.digest.DigestUtil; |
| 5 | import cn.hutool.crypto.digest.HmacAlgorithm; | 6 | import cn.hutool.crypto.digest.HmacAlgorithm; |
| @@ -87,4 +88,62 @@ public class IotDeviceAuthUtils { | @@ -87,4 +88,62 @@ public class IotDeviceAuthUtils { | ||
| 87 | return new DeviceInfo().setProductKey(usernameParts[1]).setDeviceName(usernameParts[0]); | 88 | return new DeviceInfo().setProductKey(usernameParts[1]).setDeviceName(usernameParts[0]); |
| 88 | } | 89 | } |
| 89 | 90 | ||
| 91 | + // ========== 动态注册相关方法 ========== | ||
| 92 | + | ||
| 93 | + // TODO @AI:想了下,还是放回到对应的 productService、deviceService 更合适; | ||
| 94 | + | ||
| 95 | + /** | ||
| 96 | + * 生成产品密钥 | ||
| 97 | + * | ||
| 98 | + * @return 产品密钥(UUID) | ||
| 99 | + */ | ||
| 100 | + public static String generateProductSecret() { | ||
| 101 | + return IdUtil.fastSimpleUUID(); | ||
| 102 | + } | ||
| 103 | + | ||
| 104 | + /** | ||
| 105 | + * 生成设备密钥 | ||
| 106 | + * | ||
| 107 | + * @return 设备密钥(UUID) | ||
| 108 | + */ | ||
| 109 | + public static String generateDeviceSecret() { | ||
| 110 | + return IdUtil.fastSimpleUUID(); | ||
| 111 | + } | ||
| 112 | + | ||
| 113 | + // TODO @AI:去掉 random; | ||
| 114 | + /** | ||
| 115 | + * 计算动态注册签名 | ||
| 116 | + * <p> | ||
| 117 | + * 参考阿里云规范,参数按字典序排列拼接 | ||
| 118 | + * | ||
| 119 | + * @param productSecret 产品密钥 | ||
| 120 | + * @param productKey 产品标识 | ||
| 121 | + * @param deviceName 设备名称 | ||
| 122 | + * @param random 随机数 | ||
| 123 | + * @return 签名 | ||
| 124 | + * @see <a href="https://help.aliyun.com/zh/iot/user-guide/unique-certificate-per-product-verification">一型一密</a> | ||
| 125 | + */ | ||
| 126 | + public static String buildRegisterSign(String productSecret, String productKey, String deviceName, String random) { | ||
| 127 | + String content = "deviceName" + deviceName + "productKey" + productKey + "random" + random; | ||
| 128 | + return DigestUtil.hmac(HmacAlgorithm.HmacSHA256, StrUtil.utf8Bytes(productSecret)) | ||
| 129 | + .digestHex(content); | ||
| 130 | + } | ||
| 131 | + | ||
| 132 | + // TODO @AI:是不是调用方自己验证就好了,不要这里面抽; | ||
| 133 | + /** | ||
| 134 | + * 验证动态注册签名 | ||
| 135 | + * | ||
| 136 | + * @param productSecret 产品密钥 | ||
| 137 | + * @param productKey 产品标识 | ||
| 138 | + * @param deviceName 设备名称 | ||
| 139 | + * @param random 随机数 | ||
| 140 | + * @param sign 待验证的签名 | ||
| 141 | + * @return 是否验证通过 | ||
| 142 | + */ | ||
| 143 | + public static boolean verifyRegisterSign(String productSecret, String productKey, | ||
| 144 | + String deviceName, String random, String sign) { | ||
| 145 | + String expectedSign = buildRegisterSign(productSecret, productKey, deviceName, random); | ||
| 146 | + return expectedSign.equals(sign); | ||
| 147 | + } | ||
| 148 | + | ||
| 90 | } | 149 | } |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/IotHttpUpstreamProtocol.java
| @@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.http; | @@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.http; | ||
| 3 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; | 3 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; |
| 4 | import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties; | 4 | import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties; |
| 5 | import cn.iocoder.yudao.module.iot.gateway.protocol.http.router.IotHttpAuthHandler; | 5 | import cn.iocoder.yudao.module.iot.gateway.protocol.http.router.IotHttpAuthHandler; |
| 6 | +import cn.iocoder.yudao.module.iot.gateway.protocol.http.router.IotHttpRegisterHandler; | ||
| 7 | +import cn.iocoder.yudao.module.iot.gateway.protocol.http.router.IotHttpRegisterSubHandler; | ||
| 6 | import cn.iocoder.yudao.module.iot.gateway.protocol.http.router.IotHttpUpstreamHandler; | 8 | import cn.iocoder.yudao.module.iot.gateway.protocol.http.router.IotHttpUpstreamHandler; |
| 7 | import io.vertx.core.AbstractVerticle; | 9 | import io.vertx.core.AbstractVerticle; |
| 8 | import io.vertx.core.Vertx; | 10 | import io.vertx.core.Vertx; |
| @@ -47,6 +49,10 @@ public class IotHttpUpstreamProtocol extends AbstractVerticle { | @@ -47,6 +49,10 @@ public class IotHttpUpstreamProtocol extends AbstractVerticle { | ||
| 47 | // 创建处理器,添加路由处理器 | 49 | // 创建处理器,添加路由处理器 |
| 48 | IotHttpAuthHandler authHandler = new IotHttpAuthHandler(this); | 50 | IotHttpAuthHandler authHandler = new IotHttpAuthHandler(this); |
| 49 | router.post(IotHttpAuthHandler.PATH).handler(authHandler); | 51 | router.post(IotHttpAuthHandler.PATH).handler(authHandler); |
| 52 | + IotHttpRegisterHandler registerHandler = new IotHttpRegisterHandler(); | ||
| 53 | + router.post(IotHttpRegisterHandler.PATH).handler(registerHandler); | ||
| 54 | + IotHttpRegisterSubHandler registerSubHandler = new IotHttpRegisterSubHandler(this); | ||
| 55 | + router.post(IotHttpRegisterSubHandler.PATH).handler(registerSubHandler); | ||
| 50 | IotHttpUpstreamHandler upstreamHandler = new IotHttpUpstreamHandler(this); | 56 | IotHttpUpstreamHandler upstreamHandler = new IotHttpUpstreamHandler(this); |
| 51 | router.post(IotHttpUpstreamHandler.PATH).handler(upstreamHandler); | 57 | router.post(IotHttpUpstreamHandler.PATH).handler(upstreamHandler); |
| 52 | 58 |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/router/IotHttpAbstractHandler.java
| @@ -7,6 +7,7 @@ import cn.hutool.extra.spring.SpringUtil; | @@ -7,6 +7,7 @@ import cn.hutool.extra.spring.SpringUtil; | ||
| 7 | import cn.iocoder.yudao.framework.common.exception.ServiceException; | 7 | import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| 8 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; | 8 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 9 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | 9 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; |
| 10 | +import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; | ||
| 10 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; | 11 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; |
| 11 | import cn.iocoder.yudao.module.iot.gateway.service.auth.IotDeviceTokenService; | 12 | import cn.iocoder.yudao.module.iot.gateway.service.auth.IotDeviceTokenService; |
| 12 | import io.vertx.core.Handler; | 13 | import io.vertx.core.Handler; |
| @@ -54,7 +55,7 @@ public abstract class IotHttpAbstractHandler implements Handler<RoutingContext> | @@ -54,7 +55,7 @@ public abstract class IotHttpAbstractHandler implements Handler<RoutingContext> | ||
| 54 | private void beforeHandle(RoutingContext context) { | 55 | private void beforeHandle(RoutingContext context) { |
| 55 | // 如果不需要认证,则不走前置处理 | 56 | // 如果不需要认证,则不走前置处理 |
| 56 | String path = context.request().path(); | 57 | String path = context.request().path(); |
| 57 | - if (ObjUtil.equal(path, IotHttpAuthHandler.PATH)) { | 58 | + if (ObjectUtils.equalsAny(path, IotHttpAuthHandler.PATH, IotHttpRegisterHandler.PATH)) { |
| 58 | return; | 59 | return; |
| 59 | } | 60 | } |
| 60 | 61 |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/router/IotHttpRegisterHandler.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.gateway.protocol.http.router; | ||
| 2 | + | ||
| 3 | +import cn.hutool.core.util.StrUtil; | ||
| 4 | +import cn.hutool.extra.spring.SpringUtil; | ||
| 5 | +import cn.iocoder.yudao.framework.common.pojo.CommonResult; | ||
| 6 | +import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi; | ||
| 7 | +import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO; | ||
| 8 | +import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterRespDTO; | ||
| 9 | +import io.vertx.core.json.JsonObject; | ||
| 10 | +import io.vertx.ext.web.RoutingContext; | ||
| 11 | + | ||
| 12 | +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.invalidParamException; | ||
| 13 | +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; | ||
| 14 | + | ||
| 15 | +/** | ||
| 16 | + * IoT 网关 HTTP 协议的【设备动态注册】处理器 | ||
| 17 | + * <p> | ||
| 18 | + * 用于直连设备/网关的一型一密动态注册,不需要认证 | ||
| 19 | + * | ||
| 20 | + * @author 芋道源码 | ||
| 21 | + * @see <a href="https://help.aliyun.com/zh/iot/user-guide/unique-certificate-per-product-verification">阿里云 - 一型一密</a> | ||
| 22 | + */ | ||
| 23 | +public class IotHttpRegisterHandler extends IotHttpAbstractHandler { | ||
| 24 | + | ||
| 25 | + public static final String PATH = "/auth/register/device"; | ||
| 26 | + | ||
| 27 | + private final IotDeviceCommonApi deviceApi; | ||
| 28 | + | ||
| 29 | + public IotHttpRegisterHandler() { | ||
| 30 | + this.deviceApi = SpringUtil.getBean(IotDeviceCommonApi.class); | ||
| 31 | + } | ||
| 32 | + | ||
| 33 | + @Override | ||
| 34 | + public CommonResult<Object> handle0(RoutingContext context) { | ||
| 35 | + // 1. 解析参数 | ||
| 36 | + // TODO @AI:参数不太对,看看我写的建议 | ||
| 37 | + JsonObject body = context.body().asJsonObject(); | ||
| 38 | + String productKey = body.getString("productKey"); | ||
| 39 | + if (StrUtil.isEmpty(productKey)) { | ||
| 40 | + throw invalidParamException("productKey 不能为空"); | ||
| 41 | + } | ||
| 42 | + String deviceName = body.getString("deviceName"); | ||
| 43 | + if (StrUtil.isEmpty(deviceName)) { | ||
| 44 | + throw invalidParamException("deviceName 不能为空"); | ||
| 45 | + } | ||
| 46 | + String random = body.getString("random"); | ||
| 47 | + if (StrUtil.isEmpty(random)) { | ||
| 48 | + throw invalidParamException("random 不能为空"); | ||
| 49 | + } | ||
| 50 | + String sign = body.getString("sign"); | ||
| 51 | + if (StrUtil.isEmpty(sign)) { | ||
| 52 | + throw invalidParamException("sign 不能为空"); | ||
| 53 | + } | ||
| 54 | + | ||
| 55 | + // 2. 调用动态注册 | ||
| 56 | + IotDeviceRegisterReqDTO reqDTO = new IotDeviceRegisterReqDTO() | ||
| 57 | + .setProductKey(productKey) | ||
| 58 | + .setDeviceName(deviceName) | ||
| 59 | + .setRandom(random) | ||
| 60 | + .setSign(sign); | ||
| 61 | + CommonResult<IotDeviceRegisterRespDTO> result = deviceApi.registerDevice(reqDTO); | ||
| 62 | + result.checkError(); | ||
| 63 | + | ||
| 64 | + // 3. 返回结果 | ||
| 65 | + return success(result.getData()); | ||
| 66 | + } | ||
| 67 | + | ||
| 68 | +} |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/router/IotHttpRegisterSubHandler.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.gateway.protocol.http.router; | ||
| 2 | + | ||
| 3 | +import cn.hutool.extra.spring.SpringUtil; | ||
| 4 | +import cn.iocoder.yudao.framework.common.pojo.CommonResult; | ||
| 5 | +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; | ||
| 6 | +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | ||
| 7 | +import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpUpstreamProtocol; | ||
| 8 | +import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; | ||
| 9 | +import io.vertx.ext.web.RoutingContext; | ||
| 10 | + | ||
| 11 | +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; | ||
| 12 | + | ||
| 13 | +/** | ||
| 14 | + * IoT 网关 HTTP 协议的【子设备动态注册】处理器 | ||
| 15 | + * <p> | ||
| 16 | + * 用于子设备的动态注册,需要网关认证 | ||
| 17 | + * | ||
| 18 | + * @author 芋道源码 | ||
| 19 | + * @see <a href="https://help.aliyun.com/zh/iot/user-guide/register-devices">阿里云 - 动态注册子设备</a> | ||
| 20 | + */ | ||
| 21 | +public class IotHttpRegisterSubHandler extends IotHttpAbstractHandler { | ||
| 22 | + | ||
| 23 | + /** | ||
| 24 | + * 路径:/auth/register/sub-device/:productKey/:deviceName | ||
| 25 | + * <p> | ||
| 26 | + * productKey 和 deviceName 是网关设备的标识 | ||
| 27 | + */ | ||
| 28 | + public static final String PATH = "/auth/register/sub-device/:productKey/:deviceName"; | ||
| 29 | + | ||
| 30 | + private final IotHttpUpstreamProtocol protocol; | ||
| 31 | + | ||
| 32 | + private final IotDeviceMessageService deviceMessageService; | ||
| 33 | + | ||
| 34 | + public IotHttpRegisterSubHandler(IotHttpUpstreamProtocol protocol) { | ||
| 35 | + this.protocol = protocol; | ||
| 36 | + this.deviceMessageService = SpringUtil.getBean(IotDeviceMessageService.class); | ||
| 37 | + } | ||
| 38 | + | ||
| 39 | + @Override | ||
| 40 | + public CommonResult<Object> handle0(RoutingContext context) { | ||
| 41 | + // 1. 解析通用参数 | ||
| 42 | + String productKey = context.pathParam("productKey"); | ||
| 43 | + String deviceName = context.pathParam("deviceName"); | ||
| 44 | + | ||
| 45 | + // 2.1 解析消息 | ||
| 46 | + byte[] bytes = context.body().buffer().getBytes(); | ||
| 47 | + IotDeviceMessage message = deviceMessageService.decodeDeviceMessage(bytes, productKey, deviceName); | ||
| 48 | + // 2.2 设置方法 | ||
| 49 | + message.setMethod(IotDeviceMessageMethodEnum.SUB_DEVICE_REGISTER.getMethod()); | ||
| 50 | + | ||
| 51 | + // TODO @AI:可能还是需要一个新的 deviceApi 接口。因为 register sub 子设备不太一行; | ||
| 52 | + // 2.3 发送消息 | ||
| 53 | + Object responseData = deviceMessageService.sendDeviceMessage(message, productKey, deviceName, protocol.getServerId()); | ||
| 54 | + | ||
| 55 | + // 3. 返回结果 | ||
| 56 | + return success(responseData); | ||
| 57 | + } | ||
| 58 | + | ||
| 59 | +} |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/service/device/remote/IotDeviceApiImpl.java
| @@ -6,6 +6,8 @@ import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi; | @@ -6,6 +6,8 @@ import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi; | ||
| 6 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; | 6 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; |
| 7 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceGetReqDTO; | 7 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceGetReqDTO; |
| 8 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO; | 8 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO; |
| 9 | +import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO; | ||
| 10 | +import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterRespDTO; | ||
| 9 | import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties; | 11 | import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties; |
| 10 | import jakarta.annotation.PostConstruct; | 12 | import jakarta.annotation.PostConstruct; |
| 11 | import jakarta.annotation.Resource; | 13 | import jakarta.annotation.Resource; |
| @@ -54,6 +56,11 @@ public class IotDeviceApiImpl implements IotDeviceCommonApi { | @@ -54,6 +56,11 @@ public class IotDeviceApiImpl implements IotDeviceCommonApi { | ||
| 54 | return doPost("/get", getReqDTO, new ParameterizedTypeReference<>() { }); | 56 | return doPost("/get", getReqDTO, new ParameterizedTypeReference<>() { }); |
| 55 | } | 57 | } |
| 56 | 58 | ||
| 59 | + @Override | ||
| 60 | + public CommonResult<IotDeviceRegisterRespDTO> registerDevice(IotDeviceRegisterReqDTO reqDTO) { | ||
| 61 | + return doPost("/register", reqDTO, new ParameterizedTypeReference<>() { }); | ||
| 62 | + } | ||
| 63 | + | ||
| 57 | private <T, R> CommonResult<R> doPost(String url, T body, | 64 | private <T, R> CommonResult<R> doPost(String url, T body, |
| 58 | ParameterizedTypeReference<CommonResult<R>> responseType) { | 65 | ParameterizedTypeReference<CommonResult<R>> responseType) { |
| 59 | try { | 66 | try { |
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/IotDeviceRegisterHttpProtocolIntegrationTest.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.gateway.protocol.http; | ||
| 2 | + | ||
| 3 | +import cn.hutool.core.map.MapUtil; | ||
| 4 | +import cn.hutool.core.util.IdUtil; | ||
| 5 | +import cn.hutool.http.HttpResponse; | ||
| 6 | +import cn.hutool.http.HttpUtil; | ||
| 7 | +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | ||
| 8 | +import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; | ||
| 9 | +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; | ||
| 10 | +import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO; | ||
| 11 | +import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterReqDTO; | ||
| 12 | +import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; | ||
| 13 | +import lombok.extern.slf4j.Slf4j; | ||
| 14 | +import org.junit.jupiter.api.Test; | ||
| 15 | + | ||
| 16 | +import java.util.Collections; | ||
| 17 | + | ||
| 18 | +// TODO @AI:合并到 IotDirectDeviceHttpProtocolIntegrationTest 里呀,没必要拆开;只搞一个直连设备的注册就好了; | ||
| 19 | +/** | ||
| 20 | + * IoT 设备动态注册 HTTP 协议集成测试(手动测试) | ||
| 21 | + * | ||
| 22 | + * <p>测试场景:一型一密(One Type One Secret)动态注册机制 | ||
| 23 | + * | ||
| 24 | + * <p><b>前置条件:</b> | ||
| 25 | + * <ol> | ||
| 26 | + * <li>产品已开启动态注册(registerEnabled = true)</li> | ||
| 27 | + * <li>设备已预先创建(预注册模式)</li> | ||
| 28 | + * <li>设备 deviceSecret 为空(未激活状态)</li> | ||
| 29 | + * </ol> | ||
| 30 | + * | ||
| 31 | + * <p>使用步骤: | ||
| 32 | + * <ol> | ||
| 33 | + * <li>启动 yudao-module-iot-gateway 服务(HTTP 端口 8092)</li> | ||
| 34 | + * <li>运行 {@link #testDeviceRegister()} 测试直连设备/网关动态注册</li> | ||
| 35 | + * <li>运行 {@link #testSubDeviceRegister()} 测试子设备动态注册(需要先获取网关 Token)</li> | ||
| 36 | + * </ol> | ||
| 37 | + * | ||
| 38 | + * @author 芋道源码 | ||
| 39 | + * @see <a href="https://help.aliyun.com/zh/iot/user-guide/unique-certificate-per-product-verification">阿里云 - 一型一密</a> | ||
| 40 | + */ | ||
| 41 | +@Slf4j | ||
| 42 | +@SuppressWarnings("HttpUrlsUsage") | ||
| 43 | +public class IotDeviceRegisterHttpProtocolIntegrationTest { | ||
| 44 | + | ||
| 45 | + private static final String SERVER_HOST = "127.0.0.1"; | ||
| 46 | + private static final int SERVER_PORT = 8092; | ||
| 47 | + | ||
| 48 | + // ===================== 直连设备/网关动态注册配置(根据实际情况修改) ===================== | ||
| 49 | + /** | ||
| 50 | + * 产品 Key(需要开启动态注册) | ||
| 51 | + */ | ||
| 52 | + private static final String PRODUCT_KEY = "4aymZgOTOOCrDKRT"; | ||
| 53 | + /** | ||
| 54 | + * 产品密钥(从 iot_product 表的 product_secret 字段获取) | ||
| 55 | + */ | ||
| 56 | + private static final String PRODUCT_SECRET = "your_product_secret"; | ||
| 57 | + /** | ||
| 58 | + * 设备名称(需要预先创建,deviceSecret 为空) | ||
| 59 | + */ | ||
| 60 | + private static final String DEVICE_NAME = "test-register-device"; | ||
| 61 | + | ||
| 62 | + // ===================== 网关设备信息(用于子设备动态注册) ===================== | ||
| 63 | + private static final String GATEWAY_PRODUCT_KEY = "m6XcS1ZJ3TW8eC0v"; | ||
| 64 | + private static final String GATEWAY_DEVICE_NAME = "sub-ddd"; | ||
| 65 | + private static final String GATEWAY_DEVICE_SECRET = "b3d62c70f8a4495487ed1d35d61ac2b3"; | ||
| 66 | + | ||
| 67 | + /** | ||
| 68 | + * 网关设备 Token:从网关认证获取后,粘贴到这里 | ||
| 69 | + */ | ||
| 70 | + private static final String GATEWAY_TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwcm9kdWN0S2V5IjoibTZYY1MxWkozVFc4ZUMwdiIsImV4cCI6MTc2OTg2NjY3OCwiZGV2aWNlTmFtZSI6InN1Yi1kZGQifQ.nCLSAfHEjXLtTDRXARjOoFqpuo5WfArjFWweUAzrjKU"; | ||
| 71 | + | ||
| 72 | + // ===================== 子设备信息(用于子设备动态注册) ===================== | ||
| 73 | + private static final String SUB_DEVICE_PRODUCT_KEY = "jAufEMTF1W6wnPhn"; | ||
| 74 | + private static final String SUB_DEVICE_NAME = "test-sub-register-device"; | ||
| 75 | + | ||
| 76 | + // ===================== 直连设备/网关动态注册测试 ===================== | ||
| 77 | + | ||
| 78 | + /** | ||
| 79 | + * 直连设备/网关动态注册测试 | ||
| 80 | + * <p> | ||
| 81 | + * 使用产品密钥(productSecret)进行签名验证,成功后返回设备密钥(deviceSecret) | ||
| 82 | + * <p> | ||
| 83 | + * 注意:此接口不需要 Token 认证 | ||
| 84 | + */ | ||
| 85 | + @Test | ||
| 86 | + public void testDeviceRegister() { | ||
| 87 | + // 1.1 构建请求 | ||
| 88 | + String url = String.format("http://%s:%d/auth/register/device", SERVER_HOST, SERVER_PORT); | ||
| 89 | + // 1.2 生成签名 | ||
| 90 | + String random = IdUtil.fastSimpleUUID(); | ||
| 91 | + String sign = IotDeviceAuthUtils.buildRegisterSign(PRODUCT_SECRET, PRODUCT_KEY, DEVICE_NAME, random); | ||
| 92 | + // 1.3 构建请求参数 | ||
| 93 | + IotDeviceRegisterReqDTO reqDTO = new IotDeviceRegisterReqDTO(); | ||
| 94 | + reqDTO.setProductKey(PRODUCT_KEY); | ||
| 95 | + reqDTO.setDeviceName(DEVICE_NAME); | ||
| 96 | + reqDTO.setRandom(random); | ||
| 97 | + reqDTO.setSign(sign); | ||
| 98 | + String payload = JsonUtils.toJsonString(reqDTO); | ||
| 99 | + // 1.4 输出请求 | ||
| 100 | + log.info("[testDeviceRegister][请求 URL: {}]", url); | ||
| 101 | + log.info("[testDeviceRegister][请求体: {}]", payload); | ||
| 102 | + | ||
| 103 | + // 2.1 发送请求 | ||
| 104 | + String response = HttpUtil.post(url, payload); | ||
| 105 | + // 2.2 输出结果 | ||
| 106 | + log.info("[testDeviceRegister][响应体: {}]", response); | ||
| 107 | + log.info("[testDeviceRegister][成功后可使用返回的 deviceSecret 进行一机一密认证]"); | ||
| 108 | + } | ||
| 109 | + | ||
| 110 | + /** | ||
| 111 | + * 测试动态注册后使用 deviceSecret 进行认证 | ||
| 112 | + * <p> | ||
| 113 | + * 此测试需要先执行 testDeviceRegister 获取 deviceSecret | ||
| 114 | + */ | ||
| 115 | + @Test | ||
| 116 | + public void testAuthAfterRegister() { | ||
| 117 | + // 1.1 构建请求 | ||
| 118 | + String url = String.format("http://%s:%d/auth", SERVER_HOST, SERVER_PORT); | ||
| 119 | + // TODO 将 testDeviceRegister 返回的 deviceSecret 填入此处 | ||
| 120 | + String deviceSecret = "返回的deviceSecret"; | ||
| 121 | + IotDeviceAuthUtils.AuthInfo authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, deviceSecret); | ||
| 122 | + IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO() | ||
| 123 | + .setClientId(authInfo.getClientId()) | ||
| 124 | + .setUsername(authInfo.getUsername()) | ||
| 125 | + .setPassword(authInfo.getPassword()); | ||
| 126 | + String payload = JsonUtils.toJsonString(authReqDTO); | ||
| 127 | + // 1.2 输出请求 | ||
| 128 | + log.info("[testAuthAfterRegister][请求 URL: {}]", url); | ||
| 129 | + log.info("[testAuthAfterRegister][请求体: {}]", payload); | ||
| 130 | + | ||
| 131 | + // 2.1 发送请求 | ||
| 132 | + String response = HttpUtil.post(url, payload); | ||
| 133 | + // 2.2 输出结果 | ||
| 134 | + log.info("[testAuthAfterRegister][响应体: {}]", response); | ||
| 135 | + } | ||
| 136 | + | ||
| 137 | + // ===================== 网关认证测试 ===================== | ||
| 138 | + | ||
| 139 | + /** | ||
| 140 | + * 网关设备认证测试:获取网关设备 Token(用于后续子设备动态注册) | ||
| 141 | + */ | ||
| 142 | + @Test | ||
| 143 | + public void testGatewayAuth() { | ||
| 144 | + // 1.1 构建请求 | ||
| 145 | + String url = String.format("http://%s:%d/auth", SERVER_HOST, SERVER_PORT); | ||
| 146 | + IotDeviceAuthUtils.AuthInfo authInfo = IotDeviceAuthUtils.getAuthInfo( | ||
| 147 | + GATEWAY_PRODUCT_KEY, GATEWAY_DEVICE_NAME, GATEWAY_DEVICE_SECRET); | ||
| 148 | + IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO() | ||
| 149 | + .setClientId(authInfo.getClientId()) | ||
| 150 | + .setUsername(authInfo.getUsername()) | ||
| 151 | + .setPassword(authInfo.getPassword()); | ||
| 152 | + String payload = JsonUtils.toJsonString(authReqDTO); | ||
| 153 | + // 1.2 输出请求 | ||
| 154 | + log.info("[testGatewayAuth][请求 URL: {}]", url); | ||
| 155 | + log.info("[testGatewayAuth][请求体: {}]", payload); | ||
| 156 | + | ||
| 157 | + // 2.1 发送请求 | ||
| 158 | + String response = HttpUtil.post(url, payload); | ||
| 159 | + // 2.2 输出结果 | ||
| 160 | + log.info("[testGatewayAuth][响应体: {}]", response); | ||
| 161 | + log.info("[testGatewayAuth][请将返回的 token 复制到 GATEWAY_TOKEN 常量中]"); | ||
| 162 | + } | ||
| 163 | + | ||
| 164 | + // ===================== 子设备动态注册测试 ===================== | ||
| 165 | + | ||
| 166 | + /** | ||
| 167 | + * 子设备动态注册测试 | ||
| 168 | + * <p> | ||
| 169 | + * 网关设备代理子设备进行动态注册,平台返回子设备的 deviceSecret | ||
| 170 | + * <p> | ||
| 171 | + * 注意:此接口需要网关 Token 认证 | ||
| 172 | + */ | ||
| 173 | + @Test | ||
| 174 | + public void testSubDeviceRegister() { | ||
| 175 | + // 1.1 构建请求(需要网关认证) | ||
| 176 | + String url = String.format("http://%s:%d/auth/register/sub-device/%s/%s", | ||
| 177 | + SERVER_HOST, SERVER_PORT, GATEWAY_PRODUCT_KEY, GATEWAY_DEVICE_NAME); | ||
| 178 | + // 1.2 构建请求参数 | ||
| 179 | + IotSubDeviceRegisterReqDTO subDevice = new IotSubDeviceRegisterReqDTO(); | ||
| 180 | + subDevice.setProductKey(SUB_DEVICE_PRODUCT_KEY); | ||
| 181 | + subDevice.setDeviceName(SUB_DEVICE_NAME); | ||
| 182 | + String payload = JsonUtils.toJsonString(MapUtil.builder() | ||
| 183 | + .put("id", IdUtil.fastSimpleUUID()) | ||
| 184 | + .put("method", IotDeviceMessageMethodEnum.SUB_DEVICE_REGISTER.getMethod()) | ||
| 185 | + .put("version", "1.0") | ||
| 186 | + .put("params", Collections.singletonList(subDevice)) | ||
| 187 | + .build()); | ||
| 188 | + // 1.3 输出请求 | ||
| 189 | + log.info("[testSubDeviceRegister][请求 URL: {}]", url); | ||
| 190 | + log.info("[testSubDeviceRegister][请求体: {}]", payload); | ||
| 191 | + | ||
| 192 | + // 2.1 发送请求(需要网关 Token) | ||
| 193 | + try (HttpResponse httpResponse = HttpUtil.createPost(url) | ||
| 194 | + .header("Authorization", GATEWAY_TOKEN) | ||
| 195 | + .body(payload) | ||
| 196 | + .execute()) { | ||
| 197 | + // 2.2 输出结果 | ||
| 198 | + log.info("[testSubDeviceRegister][响应体: {}]", httpResponse.body()); | ||
| 199 | + log.info("[testSubDeviceRegister][成功后可使用返回的 deviceSecret 进行子设备认证]"); | ||
| 200 | + } | ||
| 201 | + } | ||
| 202 | + | ||
| 203 | +} |
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/IotGatewayDeviceHttpProtocolIntegrationTest.java
| @@ -211,11 +211,13 @@ public class IotGatewayDeviceHttpProtocolIntegrationTest { | @@ -211,11 +211,13 @@ public class IotGatewayDeviceHttpProtocolIntegrationTest { | ||
| 211 | * 子设备动态注册测试 | 211 | * 子设备动态注册测试 |
| 212 | * <p> | 212 | * <p> |
| 213 | * 网关设备代理子设备进行动态注册,平台返回子设备的 deviceSecret | 213 | * 网关设备代理子设备进行动态注册,平台返回子设备的 deviceSecret |
| 214 | + * <p> | ||
| 215 | + * 注意:此接口需要网关 Token 认证 | ||
| 214 | */ | 216 | */ |
| 215 | @Test | 217 | @Test |
| 216 | public void testSubDeviceRegister() { | 218 | public void testSubDeviceRegister() { |
| 217 | // 1.1 构建请求 | 219 | // 1.1 构建请求 |
| 218 | - String url = String.format("http://%s:%d/topic/sys/%s/%s/thing/sub/register", | 220 | + String url = String.format("http://%s:%d/auth/register/sub-device/%s/%s", |
| 219 | SERVER_HOST, SERVER_PORT, GATEWAY_PRODUCT_KEY, GATEWAY_DEVICE_NAME); | 221 | SERVER_HOST, SERVER_PORT, GATEWAY_PRODUCT_KEY, GATEWAY_DEVICE_NAME); |
| 220 | // 1.2 构建请求参数 | 222 | // 1.2 构建请求参数 |
| 221 | IotSubDeviceRegisterReqDTO subDevice = new IotSubDeviceRegisterReqDTO(); | 223 | IotSubDeviceRegisterReqDTO subDevice = new IotSubDeviceRegisterReqDTO(); |