Commit b4ce72ea7d4caec62b29ed4dcbf6f366a6266009
1 parent
38a21ad5
feat(iot):【网关设备:72%】动态注册的初步实现(未测试、额外优化代码),基于 stateful-sauteeing-pillow.md 规划
Showing
29 changed files
with
313 additions
and
457 deletions
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/IoTDeviceApiImpl.java
| ... | ... | @@ -7,8 +7,10 @@ import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi; |
| 7 | 7 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; |
| 8 | 8 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceGetReqDTO; |
| 9 | 9 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO; |
| 10 | +import cn.iocoder.yudao.module.iot.core.biz.dto.IotSubDeviceRegisterFullReqDTO; | |
| 10 | 11 | import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO; |
| 11 | 12 | import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterRespDTO; |
| 13 | +import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterRespDTO; | |
| 12 | 14 | import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; |
| 13 | 15 | import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; |
| 14 | 16 | import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; |
| ... | ... | @@ -21,6 +23,8 @@ import org.springframework.web.bind.annotation.PostMapping; |
| 21 | 23 | import org.springframework.web.bind.annotation.RequestBody; |
| 22 | 24 | import org.springframework.web.bind.annotation.RestController; |
| 23 | 25 | |
| 26 | +import java.util.List; | |
| 27 | + | |
| 24 | 28 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| 25 | 29 | |
| 26 | 30 | /** |
| ... | ... | @@ -66,4 +70,11 @@ public class IoTDeviceApiImpl implements IotDeviceCommonApi { |
| 66 | 70 | return success(deviceService.registerDevice(reqDTO)); |
| 67 | 71 | } |
| 68 | 72 | |
| 73 | + @Override | |
| 74 | + @PostMapping(RpcConstants.RPC_API_PREFIX + "/iot/device/register-sub") | |
| 75 | + @PermitAll | |
| 76 | + public CommonResult<List<IotSubDeviceRegisterRespDTO>> registerSubDevices(@RequestBody IotSubDeviceRegisterFullReqDTO reqDTO) { | |
| 77 | + return success(deviceService.registerSubDevices(reqDTO)); | |
| 78 | + } | |
| 79 | + | |
| 69 | 80 | } |
| 70 | 81 | \ 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/IotProductSaveReqVO.java
| ... | ... | @@ -49,7 +49,7 @@ public class IotProductSaveReqVO { |
| 49 | 49 | private String codecType; |
| 50 | 50 | |
| 51 | 51 | @Schema(description = "是否开启动态注册", example = "false") |
| 52 | - @NotEmpty(message = "是否开启动态注册不能为空") | |
| 52 | + @NotNull(message = "是否开启动态注册不能为空") | |
| 53 | 53 | private Boolean registerEnabled; |
| 54 | 54 | |
| 55 | 55 | } |
| 56 | 56 | \ No newline at end of file | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java
| ... | ... | @@ -44,8 +44,8 @@ public interface ErrorCodeConstants { |
| 44 | 44 | ErrorCode DEVICE_SUB_REGISTER_PARAMS_INVALID = new ErrorCode(1_050_003_200, "子设备注册参数无效"); |
| 45 | 45 | ErrorCode DEVICE_SUB_REGISTER_PRODUCT_NOT_GATEWAY_SUB = new ErrorCode(1_050_003_201, "产品【{}】不是网关子设备类型"); |
| 46 | 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, "设备已激活,不允许重复注册"); | |
| 47 | + ErrorCode DEVICE_REGISTER_SECRET_INVALID = new ErrorCode(1_050_003_211, "产品密钥验证失败"); | |
| 48 | + ErrorCode DEVICE_REGISTER_ALREADY_EXISTS = new ErrorCode(1_050_003_212, "设备已存在,不允许重复注册"); | |
| 49 | 49 | |
| 50 | 50 | // ========== 产品分类 1-050-004-000 ========== |
| 51 | 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
| ... | ... | @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.iot.service.device; |
| 3 | 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | 4 | import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.*; |
| 5 | 5 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; |
| 6 | +import cn.iocoder.yudao.module.iot.core.biz.dto.IotSubDeviceRegisterFullReqDTO; | |
| 6 | 7 | import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum; |
| 7 | 8 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 8 | 9 | import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity; |
| ... | ... | @@ -44,18 +45,6 @@ public interface IotDeviceService { |
| 44 | 45 | */ |
| 45 | 46 | void updateDevice(@Valid IotDeviceSaveReqVO updateReqVO); |
| 46 | 47 | |
| 47 | - // TODO @芋艿:先这么实现。未来看情况,要不要自己实现 | |
| 48 | - | |
| 49 | - /** | |
| 50 | - * 更新设备的所属网关 | |
| 51 | - * | |
| 52 | - * @param id 编号 | |
| 53 | - * @param gatewayId 网关设备 ID | |
| 54 | - */ | |
| 55 | - default void updateDeviceGateway(Long id, Long gatewayId) { | |
| 56 | - updateDevice(new IotDeviceSaveReqVO().setId(id).setGatewayId(gatewayId)); | |
| 57 | - } | |
| 58 | - | |
| 59 | 48 | /** |
| 60 | 49 | * 更新设备状态 |
| 61 | 50 | * |
| ... | ... | @@ -359,20 +348,31 @@ public interface IotDeviceService { |
| 359 | 348 | // ========== 设备动态注册 ========== |
| 360 | 349 | |
| 361 | 350 | /** |
| 362 | - * 处理子设备动态注册消息(网关设备上报) | |
| 351 | + * 直连/网关设备动态注册 | |
| 363 | 352 | * |
| 364 | - * @param message 消息 | |
| 365 | - * @param gatewayDevice 网关设备 | |
| 353 | + * @param reqDTO 动态注册请求 | |
| 354 | + * @return 注册结果(包含 DeviceSecret) | |
| 355 | + */ | |
| 356 | + IotDeviceRegisterRespDTO registerDevice(@Valid IotDeviceRegisterReqDTO reqDTO); | |
| 357 | + | |
| 358 | + /** | |
| 359 | + * 网关子设备动态注册 | |
| 360 | + * <p> | |
| 361 | + * 与 {@link #handleSubDeviceRegisterMessage} 方法的区别: | |
| 362 | + * 该方法网关设备信息通过 reqDTO 参数传入,而 {@link #handleSubDeviceRegisterMessage} 方法通过 gatewayDevice 参数传入 | |
| 363 | + * | |
| 364 | + * @param reqDTO 子设备注册请求(包含网关设备信息) | |
| 366 | 365 | * @return 注册结果列表 |
| 367 | 366 | */ |
| 368 | - List<IotSubDeviceRegisterRespDTO> handleSubDeviceRegisterMessage(IotDeviceMessage message, IotDeviceDO gatewayDevice); | |
| 367 | + List<IotSubDeviceRegisterRespDTO> registerSubDevices(@Valid IotSubDeviceRegisterFullReqDTO reqDTO); | |
| 369 | 368 | |
| 370 | 369 | /** |
| 371 | - * 设备动态注册(直连设备/网关) | |
| 370 | + * 处理子设备动态注册消息(网关设备上报) | |
| 372 | 371 | * |
| 373 | - * @param reqDTO 动态注册请求 | |
| 374 | - * @return 注册结果(包含 DeviceSecret) | |
| 372 | + * @param message 消息 | |
| 373 | + * @param gatewayDevice 网关设备 | |
| 374 | + * @return 注册结果列表 | |
| 375 | 375 | */ |
| 376 | - IotDeviceRegisterRespDTO registerDevice(@Valid IotDeviceRegisterReqDTO reqDTO); | |
| 376 | + List<IotSubDeviceRegisterRespDTO> handleSubDeviceRegisterMessage(IotDeviceMessage message, IotDeviceDO gatewayDevice); | |
| 377 | 377 | |
| 378 | 378 | } | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java
| ... | ... | @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.iot.service.device; |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | 4 | import cn.hutool.core.util.BooleanUtil; |
| 5 | +import cn.hutool.core.util.IdUtil; | |
| 5 | 6 | import cn.hutool.core.util.ObjUtil; |
| 6 | 7 | import cn.hutool.core.util.StrUtil; |
| 7 | 8 | import cn.hutool.extra.spring.SpringUtil; |
| ... | ... | @@ -14,6 +15,7 @@ import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; |
| 14 | 15 | import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; |
| 15 | 16 | import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.*; |
| 16 | 17 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; |
| 18 | +import cn.iocoder.yudao.module.iot.core.biz.dto.IotSubDeviceRegisterFullReqDTO; | |
| 17 | 19 | import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; |
| 18 | 20 | import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum; |
| 19 | 21 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| ... | ... | @@ -142,11 +144,13 @@ public class IotDeviceServiceImpl implements IotDeviceService { |
| 142 | 144 | |
| 143 | 145 | private void initDevice(IotDeviceDO device, IotProductDO product) { |
| 144 | 146 | device.setProductId(product.getId()).setProductKey(product.getProductKey()) |
| 145 | - .setDeviceType(product.getDeviceType()); | |
| 146 | - // 生成密钥 | |
| 147 | - device.setDeviceSecret(IotDeviceAuthUtils.generateDeviceSecret()); | |
| 148 | - // 设置设备状态为未激活 | |
| 149 | - device.setState(IotDeviceStateEnum.INACTIVE.getState()); | |
| 147 | + .setDeviceType(product.getDeviceType()) | |
| 148 | + .setDeviceSecret(generateDeviceSecret()) // 生成密钥 | |
| 149 | + .setState(IotDeviceStateEnum.INACTIVE.getState()); // 默认未激活 | |
| 150 | + } | |
| 151 | + | |
| 152 | + private String generateDeviceSecret() { | |
| 153 | + return IdUtil.fastSimpleUUID(); | |
| 150 | 154 | } |
| 151 | 155 | |
| 152 | 156 | @Override |
| ... | ... | @@ -448,7 +452,7 @@ public class IotDeviceServiceImpl implements IotDeviceService { |
| 448 | 452 | public IotDeviceAuthInfoRespVO getDeviceAuthInfo(Long id) { |
| 449 | 453 | IotDeviceDO device = validateDeviceExists(id); |
| 450 | 454 | // 使用 IotDeviceAuthUtils 生成认证信息 |
| 451 | - IotDeviceAuthUtils.AuthInfo authInfo = IotDeviceAuthUtils.getAuthInfo( | |
| 455 | + IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo( | |
| 452 | 456 | device.getProductKey(), device.getDeviceName(), device.getDeviceSecret()); |
| 453 | 457 | return BeanUtils.toBean(authInfo, IotDeviceAuthInfoRespVO.class); |
| 454 | 458 | } |
| ... | ... | @@ -496,7 +500,7 @@ public class IotDeviceServiceImpl implements IotDeviceService { |
| 496 | 500 | @Override |
| 497 | 501 | public boolean authDevice(IotDeviceAuthReqDTO authReqDTO) { |
| 498 | 502 | // 1. 校验设备是否存在 |
| 499 | - IotDeviceAuthUtils.DeviceInfo deviceInfo = IotDeviceAuthUtils.parseUsername(authReqDTO.getUsername()); | |
| 503 | + IotDeviceIdentity deviceInfo = IotDeviceAuthUtils.parseUsername(authReqDTO.getUsername()); | |
| 500 | 504 | if (deviceInfo == null) { |
| 501 | 505 | log.error("[authDevice][认证失败,username({}) 格式不正确]", authReqDTO.getUsername()); |
| 502 | 506 | return false; |
| ... | ... | @@ -510,7 +514,7 @@ public class IotDeviceServiceImpl implements IotDeviceService { |
| 510 | 514 | } |
| 511 | 515 | |
| 512 | 516 | // 2. 校验密码 |
| 513 | - IotDeviceAuthUtils.AuthInfo authInfo = IotDeviceAuthUtils.getAuthInfo(productKey, deviceName, device.getDeviceSecret()); | |
| 517 | + IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(productKey, deviceName, device.getDeviceSecret()); | |
| 514 | 518 | if (ObjUtil.notEqual(authInfo.getPassword(), authReqDTO.getPassword())) { |
| 515 | 519 | log.error("[authDevice][设备({}/{}) 密码不正确]", productKey, deviceName); |
| 516 | 520 | return false; |
| ... | ... | @@ -666,7 +670,7 @@ public class IotDeviceServiceImpl implements IotDeviceService { |
| 666 | 670 | |
| 667 | 671 | private IotDeviceDO addDeviceTopo(IotDeviceDO gatewayDevice, IotDeviceAuthReqDTO subDeviceAuth) { |
| 668 | 672 | // 1.1 解析子设备信息 |
| 669 | - IotDeviceAuthUtils.DeviceInfo subDeviceInfo = IotDeviceAuthUtils.parseUsername(subDeviceAuth.getUsername()); | |
| 673 | + IotDeviceIdentity subDeviceInfo = IotDeviceAuthUtils.parseUsername(subDeviceAuth.getUsername()); | |
| 670 | 674 | if (subDeviceInfo == null) { |
| 671 | 675 | throw exception(DEVICE_TOPO_SUB_DEVICE_USERNAME_INVALID); |
| 672 | 676 | } |
| ... | ... | @@ -814,63 +818,78 @@ public class IotDeviceServiceImpl implements IotDeviceService { |
| 814 | 818 | if (BooleanUtil.isFalse(product.getRegisterEnabled())) { |
| 815 | 819 | throw exception(DEVICE_REGISTER_DISABLED); |
| 816 | 820 | } |
| 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 | + // 1.3 验证 productSecret | |
| 822 | + if (ObjUtil.notEqual(product.getProductSecret(), reqDTO.getProductSecret())) { | |
| 823 | + throw exception(DEVICE_REGISTER_SECRET_INVALID); | |
| 821 | 824 | } |
| 822 | - | |
| 823 | - // 4. 查找设备(预注册模式:设备必须已存在) | |
| 824 | - // TODO @AI:设备不用提前有,这个有问题! | |
| 825 | + // 1.4 校验设备是否已存在(已存在则不允许重复注册) | |
| 825 | 826 | IotDeviceDO device = getSelf().getDeviceFromCache(reqDTO.getProductKey(), reqDTO.getDeviceName()); |
| 826 | - if (device == null) { | |
| 827 | - throw exception(DEVICE_NOT_EXISTS); | |
| 827 | + if (device != null) { | |
| 828 | + throw exception(DEVICE_REGISTER_ALREADY_EXISTS); | |
| 828 | 829 | } |
| 829 | 830 | |
| 830 | - // 5. 校验设备是否已激活(已激活的设备不允许重复注册) | |
| 831 | - if (!Objects.equals(device.getState(), IotDeviceStateEnum.INACTIVE.getState())) { | |
| 832 | - throw exception(DEVICE_ALREADY_ACTIVATED); | |
| 833 | - } | |
| 834 | - | |
| 835 | - // 6. 返回设备密钥 | |
| 831 | + // 2.1 自动创建设备 | |
| 832 | + IotDeviceSaveReqVO createReqVO = new IotDeviceSaveReqVO() | |
| 833 | + .setDeviceName(reqDTO.getDeviceName()) | |
| 834 | + .setProductId(product.getId()); | |
| 835 | + device = createDevice0(createReqVO); | |
| 836 | + log.info("[registerDevice][产品({}) 自动创建设备({})]", | |
| 837 | + reqDTO.getProductKey(), reqDTO.getDeviceName()); | |
| 838 | + // 2.2 返回设备密钥 | |
| 836 | 839 | return new IotDeviceRegisterRespDTO(device.getProductKey(), device.getDeviceName(), device.getDeviceSecret()); |
| 837 | 840 | } |
| 838 | 841 | |
| 839 | 842 | @Override |
| 843 | + public List<IotSubDeviceRegisterRespDTO> registerSubDevices(IotSubDeviceRegisterFullReqDTO reqDTO) { | |
| 844 | + // 1. 校验网关设备 | |
| 845 | + IotDeviceDO gatewayDevice = getSelf().getDeviceFromCache(reqDTO.getGatewayProductKey(), reqDTO.getGatewayDeviceName()); | |
| 846 | + | |
| 847 | + // 2. 遍历注册每个子设备 | |
| 848 | + return registerSubDevices0(gatewayDevice, reqDTO.getSubDevices()); | |
| 849 | + } | |
| 850 | + | |
| 851 | + @Override | |
| 840 | 852 | public List<IotSubDeviceRegisterRespDTO> handleSubDeviceRegisterMessage(IotDeviceMessage message, IotDeviceDO gatewayDevice) { |
| 841 | - // 1.1 校验网关设备类型 | |
| 842 | - if (!IotProductDeviceTypeEnum.isGateway(gatewayDevice.getDeviceType())) { | |
| 843 | - throw exception(DEVICE_NOT_GATEWAY); | |
| 844 | - } | |
| 845 | - // 1.2 解析参数 | |
| 853 | + // 1. 解析参数 | |
| 846 | 854 | if (!(message.getParams() instanceof List)) { |
| 847 | 855 | throw exception(DEVICE_SUB_REGISTER_PARAMS_INVALID); |
| 848 | 856 | } |
| 849 | - List<IotSubDeviceRegisterReqDTO> paramsList = JsonUtils.convertList(message.getParams(), | |
| 850 | - IotSubDeviceRegisterReqDTO.class); | |
| 851 | - if (CollUtil.isEmpty(paramsList)) { | |
| 857 | + List<IotSubDeviceRegisterReqDTO> subDevices = JsonUtils.convertList(message.getParams(), IotSubDeviceRegisterReqDTO.class); | |
| 858 | + | |
| 859 | + // 2. 遍历注册每个子设备 | |
| 860 | + return registerSubDevices0(gatewayDevice, subDevices); | |
| 861 | + } | |
| 862 | + | |
| 863 | + private List<IotSubDeviceRegisterRespDTO> registerSubDevices0(IotDeviceDO gatewayDevice, | |
| 864 | + List<IotSubDeviceRegisterReqDTO> subDevices) { | |
| 865 | + // 1.1 校验网关设备 | |
| 866 | + if (gatewayDevice == null) { | |
| 867 | + throw exception(DEVICE_NOT_EXISTS); | |
| 868 | + } | |
| 869 | + if (!IotProductDeviceTypeEnum.isGateway(gatewayDevice.getDeviceType())) { | |
| 870 | + throw exception(DEVICE_NOT_GATEWAY); | |
| 871 | + } | |
| 872 | + // 1.2 注册设备不能为空 | |
| 873 | + if (CollUtil.isEmpty(subDevices)) { | |
| 852 | 874 | throw exception(DEVICE_SUB_REGISTER_PARAMS_INVALID); |
| 853 | 875 | } |
| 854 | 876 | |
| 855 | 877 | // 2. 遍历注册每个子设备 |
| 856 | - List<IotSubDeviceRegisterRespDTO> results = new ArrayList<>(paramsList.size()); | |
| 857 | - for (IotSubDeviceRegisterReqDTO params : paramsList) { | |
| 878 | + List<IotSubDeviceRegisterRespDTO> results = new ArrayList<>(subDevices.size()); | |
| 879 | + for (IotSubDeviceRegisterReqDTO subDevice : subDevices) { | |
| 858 | 880 | try { |
| 859 | - IotDeviceDO device = registerSubDevice(gatewayDevice, params); | |
| 881 | + IotDeviceDO device = registerSubDevice0(gatewayDevice, subDevice); | |
| 860 | 882 | results.add(new IotSubDeviceRegisterRespDTO( |
| 861 | - params.getProductKey(), params.getDeviceName(), device.getDeviceSecret())); | |
| 883 | + subDevice.getProductKey(), subDevice.getDeviceName(), device.getDeviceSecret())); | |
| 862 | 884 | } catch (Exception ex) { |
| 863 | - log.error("[handleSubDeviceRegisterMessage][子设备({}/{}) 注册失败]", | |
| 864 | - params.getProductKey(), params.getDeviceName(), ex); | |
| 885 | + log.error("[registerSubDevices0][子设备({}/{}) 注册失败]", | |
| 886 | + subDevice.getProductKey(), subDevice.getDeviceName(), ex); | |
| 865 | 887 | } |
| 866 | 888 | } |
| 867 | - | |
| 868 | - // 3. 返回响应数据(包含成功注册的子设备列表) | |
| 869 | 889 | return results; |
| 870 | 890 | } |
| 871 | 891 | |
| 872 | - // TODO @AI:阿里云的,设备必须存在; | |
| 873 | - private IotDeviceDO registerSubDevice(IotDeviceDO gatewayDevice, IotSubDeviceRegisterReqDTO params) { | |
| 892 | + private IotDeviceDO registerSubDevice0(IotDeviceDO gatewayDevice, IotSubDeviceRegisterReqDTO params) { | |
| 874 | 893 | // 1.1 校验产品 |
| 875 | 894 | IotProductDO product = productService.getProductByProductKey(params.getProductKey()); |
| 876 | 895 | if (product == null) { |
| ... | ... | @@ -880,28 +899,28 @@ public class IotDeviceServiceImpl implements IotDeviceService { |
| 880 | 899 | if (!IotProductDeviceTypeEnum.isGatewaySub(product.getDeviceType())) { |
| 881 | 900 | throw exception(DEVICE_SUB_REGISTER_PRODUCT_NOT_GATEWAY_SUB, params.getProductKey()); |
| 882 | 901 | } |
| 883 | - // 1.3 查找设备是否已存在 | |
| 902 | + // 1.3 校验设备是否已存在(子设备动态注册:设备必须已预注册) | |
| 884 | 903 | IotDeviceDO existDevice = getSelf().getDeviceFromCache(params.getProductKey(), params.getDeviceName()); |
| 885 | - if (existDevice != null) { | |
| 886 | - // 校验是否绑定到当前网关 | |
| 887 | - if (ObjUtil.notEqual(existDevice.getGatewayId(), gatewayDevice.getId())) { | |
| 888 | - throw exception(DEVICE_GATEWAY_BINDTO_EXISTS, | |
| 889 | - existDevice.getProductKey(), existDevice.getDeviceName()); | |
| 890 | - } | |
| 891 | - // 已存在则返回设备信息 | |
| 892 | - return existDevice; | |
| 904 | + if (existDevice == null) { | |
| 905 | + throw exception(DEVICE_NOT_EXISTS); | |
| 906 | + } | |
| 907 | + // 1.4 校验是否绑定到其他网关 | |
| 908 | + if (existDevice.getGatewayId() != null && ObjUtil.notEqual(existDevice.getGatewayId(), gatewayDevice.getId())) { | |
| 909 | + throw exception(DEVICE_GATEWAY_BINDTO_EXISTS, | |
| 910 | + existDevice.getProductKey(), existDevice.getDeviceName()); | |
| 893 | 911 | } |
| 894 | 912 | |
| 895 | - // 2. 创建新设备 | |
| 896 | - IotDeviceSaveReqVO createReqVO = new IotDeviceSaveReqVO() | |
| 897 | - .setDeviceName(params.getDeviceName()) | |
| 898 | - .setProductId(product.getId()) | |
| 899 | - .setGatewayId(gatewayDevice.getId()); | |
| 900 | - IotDeviceDO newDevice = createDevice0(createReqVO); | |
| 901 | - log.info("[registerSubDevice][网关({}/{}) 注册子设备({}/{})]", | |
| 902 | - gatewayDevice.getProductKey(), gatewayDevice.getDeviceName(), | |
| 903 | - newDevice.getProductKey(), newDevice.getDeviceName()); | |
| 904 | - return newDevice; | |
| 913 | + // 2. 绑定到网关(如果尚未绑定) | |
| 914 | + if (existDevice.getGatewayId() == null) { | |
| 915 | + // 2.1 更新数据库 | |
| 916 | + deviceMapper.updateById(new IotDeviceDO().setId(existDevice.getId()).setGatewayId(gatewayDevice.getId())); | |
| 917 | + // 2.2 清空对应缓存 | |
| 918 | + deleteDeviceCache(existDevice); | |
| 919 | + log.info("[registerSubDevice][网关({}/{}) 绑定子设备({}/{})]", | |
| 920 | + gatewayDevice.getProductKey(), gatewayDevice.getDeviceName(), | |
| 921 | + existDevice.getProductKey(), existDevice.getDeviceName()); | |
| 922 | + } | |
| 923 | + return existDevice; | |
| 905 | 924 | } |
| 906 | 925 | |
| 907 | 926 | } | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductServiceImpl.java
| ... | ... | @@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.iot.service.product; |
| 3 | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | 4 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 5 | 5 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 6 | -import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; | |
| 6 | +import cn.hutool.core.util.IdUtil; | |
| 7 | 7 | import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; |
| 8 | 8 | import cn.iocoder.yudao.module.iot.controller.admin.product.vo.product.IotProductPageReqVO; |
| 9 | 9 | import cn.iocoder.yudao.module.iot.controller.admin.product.vo.product.IotProductSaveReqVO; |
| ... | ... | @@ -55,11 +55,15 @@ public class IotProductServiceImpl implements IotProductService { |
| 55 | 55 | // 2. 插入 |
| 56 | 56 | IotProductDO product = BeanUtils.toBean(createReqVO, IotProductDO.class) |
| 57 | 57 | .setStatus(IotProductStatusEnum.UNPUBLISHED.getStatus()) |
| 58 | - .setProductSecret(IotDeviceAuthUtils.generateProductSecret()); | |
| 58 | + .setProductSecret(generateProductSecret()); | |
| 59 | 59 | productMapper.insert(product); |
| 60 | 60 | return product.getId(); |
| 61 | 61 | } |
| 62 | 62 | |
| 63 | + private String generateProductSecret() { | |
| 64 | + return IdUtil.fastSimpleUUID(); | |
| 65 | + } | |
| 66 | + | |
| 63 | 67 | @Override |
| 64 | 68 | @CacheEvict(value = RedisKeyConstants.PRODUCT, key = "#updateReqVO.id") |
| 65 | 69 | public void updateProduct(IotProductSaveReqVO updateReqVO) { | ... | ... |
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/IotDeviceCommonApi.java
| ... | ... | @@ -4,8 +4,12 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 4 | 4 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; |
| 5 | 5 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceGetReqDTO; |
| 6 | 6 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO; |
| 7 | +import cn.iocoder.yudao.module.iot.core.biz.dto.IotSubDeviceRegisterFullReqDTO; | |
| 7 | 8 | import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO; |
| 8 | 9 | import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterRespDTO; |
| 10 | +import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterRespDTO; | |
| 11 | + | |
| 12 | +import java.util.List; | |
| 9 | 13 | |
| 10 | 14 | /** |
| 11 | 15 | * IoT 设备通用 API |
| ... | ... | @@ -31,11 +35,19 @@ public interface IotDeviceCommonApi { |
| 31 | 35 | CommonResult<IotDeviceRespDTO> getDevice(IotDeviceGetReqDTO infoReqDTO); |
| 32 | 36 | |
| 33 | 37 | /** |
| 34 | - * 设备动态注册(一型一密) | |
| 38 | + * 直连/网关设备动态注册(一型一密) | |
| 35 | 39 | * |
| 36 | 40 | * @param reqDTO 动态注册请求 |
| 37 | 41 | * @return 注册结果(包含 DeviceSecret) |
| 38 | 42 | */ |
| 39 | 43 | CommonResult<IotDeviceRegisterRespDTO> registerDevice(IotDeviceRegisterReqDTO reqDTO); |
| 40 | 44 | |
| 45 | + /** | |
| 46 | + * 网关子设备动态注册(网关代理转发) | |
| 47 | + * | |
| 48 | + * @param reqDTO 子设备注册请求(包含网关标识和子设备列表) | |
| 49 | + * @return 注册结果列表 | |
| 50 | + */ | |
| 51 | + CommonResult<List<IotSubDeviceRegisterRespDTO>> registerSubDevices(IotSubDeviceRegisterFullReqDTO reqDTO); | |
| 52 | + | |
| 41 | 53 | } | ... | ... |
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/dto/IotDeviceAuthReqDTO.java
| 1 | 1 | package cn.iocoder.yudao.module.iot.core.biz.dto; |
| 2 | 2 | |
| 3 | 3 | import jakarta.validation.constraints.NotEmpty; |
| 4 | +import lombok.AllArgsConstructor; | |
| 4 | 5 | import lombok.Data; |
| 6 | +import lombok.NoArgsConstructor; | |
| 5 | 7 | |
| 6 | 8 | /** |
| 7 | 9 | * IoT 设备认证 Request DTO |
| ... | ... | @@ -9,6 +11,8 @@ import lombok.Data; |
| 9 | 11 | * @author 芋道源码 |
| 10 | 12 | */ |
| 11 | 13 | @Data |
| 14 | +@NoArgsConstructor | |
| 15 | +@AllArgsConstructor | |
| 12 | 16 | public class IotDeviceAuthReqDTO { |
| 13 | 17 | |
| 14 | 18 | /** | ... | ... |
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/dto/IotSubDeviceRegisterFullReqDTO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.core.biz.dto; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterReqDTO; | |
| 4 | +import jakarta.validation.constraints.NotEmpty; | |
| 5 | +import jakarta.validation.constraints.NotNull; | |
| 6 | +import lombok.Data; | |
| 7 | + | |
| 8 | +import java.util.List; | |
| 9 | + | |
| 10 | +/** | |
| 11 | + * IoT 子设备动态注册 Request DTO | |
| 12 | + * <p> | |
| 13 | + * 额外包含了网关设备的标识信息 | |
| 14 | + * | |
| 15 | + * @author 芋道源码 | |
| 16 | + */ | |
| 17 | +@Data | |
| 18 | +public class IotSubDeviceRegisterFullReqDTO { | |
| 19 | + | |
| 20 | + /** | |
| 21 | + * 网关设备 ProductKey | |
| 22 | + */ | |
| 23 | + @NotEmpty(message = "网关产品标识不能为空") | |
| 24 | + private String gatewayProductKey; | |
| 25 | + | |
| 26 | + /** | |
| 27 | + * 网关设备 DeviceName | |
| 28 | + */ | |
| 29 | + @NotEmpty(message = "网关设备名称不能为空") | |
| 30 | + private String gatewayDeviceName; | |
| 31 | + | |
| 32 | + /** | |
| 33 | + * 子设备注册列表 | |
| 34 | + */ | |
| 35 | + @NotNull(message = "子设备注册列表不能为空") | |
| 36 | + private List<IotSubDeviceRegisterReqDTO> subDevices; | |
| 37 | + | |
| 38 | +} | ... | ... |
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/auth/IotDeviceRegisterReqDTO.java
| ... | ... | @@ -6,7 +6,7 @@ import lombok.Data; |
| 6 | 6 | /** |
| 7 | 7 | * IoT 设备动态注册 Request DTO |
| 8 | 8 | * <p> |
| 9 | - * 用于直连设备/网关的一型一密动态注册:使用 ProductSecret 验证签名,返回 DeviceSecret | |
| 9 | + * 用于直连设备/网关的一型一密动态注册:使用 productSecret 验证,返回 deviceSecret | |
| 10 | 10 | * |
| 11 | 11 | * @author 芋道源码 |
| 12 | 12 | * @see <a href="https://help.aliyun.com/zh/iot/user-guide/unique-certificate-per-product-verification">阿里云 - 一型一密</a> |
| ... | ... | @@ -26,18 +26,10 @@ public class IotDeviceRegisterReqDTO { |
| 26 | 26 | @NotEmpty(message = "设备名称不能为空") |
| 27 | 27 | private String deviceName; |
| 28 | 28 | |
| 29 | - // TODO @AI:可以去掉 random 字段; | |
| 30 | 29 | /** |
| 31 | - * 随机数,用于签名 | |
| 30 | + * 产品密钥 | |
| 32 | 31 | */ |
| 33 | - @NotEmpty(message = "随机数不能为空") | |
| 34 | - private String random; | |
| 35 | - | |
| 36 | - // TODO @AI:看起来,是直接带 productSecret 阿里云上,你在检查下! | |
| 37 | - /** | |
| 38 | - * 签名 | |
| 39 | - */ | |
| 40 | - @NotEmpty(message = "签名不能为空") | |
| 41 | - private String sign; | |
| 32 | + @NotEmpty(message = "产品密钥不能为空") | |
| 33 | + private String productSecret; | |
| 42 | 34 | |
| 43 | 35 | } | ... | ... |
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/event/IotDeviceEventPostReqDTO.java
| ... | ... | @@ -8,7 +8,7 @@ import lombok.Data; |
| 8 | 8 | * 用于 thing.event.post 消息的 params 参数 |
| 9 | 9 | * |
| 10 | 10 | * @author 芋道源码 |
| 11 | - * @see <a href="https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services">阿里云 - 设备上报事件</a> | |
| 11 | + * @see <a href="http://help.aliyun.com/zh/marketplace/device-reporting-events">阿里云 - 设备上报事件</a> | |
| 12 | 12 | */ |
| 13 | 13 | @Data |
| 14 | 14 | public class IotDeviceEventPostReqDTO { | ... | ... |
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/property/IotDevicePropertyPostReqDTO.java
| ... | ... | @@ -11,7 +11,7 @@ import java.util.Map; |
| 11 | 11 | * 本质是一个 Map,key 为属性标识符,value 为属性值 |
| 12 | 12 | * |
| 13 | 13 | * @author 芋道源码 |
| 14 | - * @see <a href="https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services">阿里云 - 设备上报属性</a> | |
| 14 | + * @see <a href="http://help.aliyun.com/zh/marketplace/device-reporting-attributes">阿里云 - 设备上报属性</a> | |
| 15 | 15 | */ |
| 16 | 16 | public class IotDevicePropertyPostReqDTO extends HashMap<String, Object> { |
| 17 | 17 | ... | ... |
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/util/IotDeviceAuthUtils.java
| 1 | 1 | package cn.iocoder.yudao.module.iot.core.util; |
| 2 | 2 | |
| 3 | -import cn.hutool.core.util.IdUtil; | |
| 4 | 3 | import cn.hutool.core.util.StrUtil; |
| 5 | 4 | import cn.hutool.crypto.digest.DigestUtil; |
| 6 | 5 | import cn.hutool.crypto.digest.HmacAlgorithm; |
| 7 | -import lombok.AllArgsConstructor; | |
| 8 | -import lombok.Data; | |
| 9 | -import lombok.NoArgsConstructor; | |
| 6 | +import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; | |
| 7 | +import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity; | |
| 10 | 8 | |
| 11 | 9 | /** |
| 12 | 10 | * IoT 设备【认证】的工具类,参考阿里云 |
| ... | ... | @@ -15,49 +13,12 @@ import lombok.NoArgsConstructor; |
| 15 | 13 | */ |
| 16 | 14 | public class IotDeviceAuthUtils { |
| 17 | 15 | |
| 18 | - /** | |
| 19 | - * 认证信息 | |
| 20 | - */ | |
| 21 | - @Data | |
| 22 | - @NoArgsConstructor | |
| 23 | - @AllArgsConstructor | |
| 24 | - public static class AuthInfo { | |
| 25 | - | |
| 26 | - /** | |
| 27 | - * 客户端 ID | |
| 28 | - */ | |
| 29 | - private String clientId; | |
| 30 | - | |
| 31 | - /** | |
| 32 | - * 用户名 | |
| 33 | - */ | |
| 34 | - private String username; | |
| 35 | - | |
| 36 | - /** | |
| 37 | - * 密码 | |
| 38 | - */ | |
| 39 | - private String password; | |
| 40 | - | |
| 41 | - } | |
| 42 | - | |
| 43 | - /** | |
| 44 | - * 设备信息 | |
| 45 | - */ | |
| 46 | - @Data | |
| 47 | - public static class DeviceInfo { | |
| 48 | - | |
| 49 | - private String productKey; | |
| 50 | - | |
| 51 | - private String deviceName; | |
| 52 | - | |
| 53 | - } | |
| 54 | - | |
| 55 | - public static AuthInfo getAuthInfo(String productKey, String deviceName, String deviceSecret) { | |
| 16 | + public static IotDeviceAuthReqDTO getAuthInfo(String productKey, String deviceName, String deviceSecret) { | |
| 56 | 17 | String clientId = buildClientId(productKey, deviceName); |
| 57 | 18 | String username = buildUsername(productKey, deviceName); |
| 58 | 19 | String password = buildPassword(deviceSecret, |
| 59 | 20 | buildContent(clientId, productKey, deviceName, deviceSecret)); |
| 60 | - return new AuthInfo(clientId, username, password); | |
| 21 | + return new IotDeviceAuthReqDTO(clientId, username, password); | |
| 61 | 22 | } |
| 62 | 23 | |
| 63 | 24 | public static String buildClientId(String productKey, String deviceName) { |
| ... | ... | @@ -80,70 +41,12 @@ public class IotDeviceAuthUtils { |
| 80 | 41 | "productKey" + productKey; |
| 81 | 42 | } |
| 82 | 43 | |
| 83 | - public static DeviceInfo parseUsername(String username) { | |
| 44 | + public static IotDeviceIdentity parseUsername(String username) { | |
| 84 | 45 | String[] usernameParts = username.split("&"); |
| 85 | 46 | if (usernameParts.length != 2) { |
| 86 | 47 | return null; |
| 87 | 48 | } |
| 88 | - return new DeviceInfo().setProductKey(usernameParts[1]).setDeviceName(usernameParts[0]); | |
| 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); | |
| 49 | + return new IotDeviceIdentity(usernameParts[1], usernameParts[0]); | |
| 147 | 50 | } |
| 148 | 51 | |
| 149 | 52 | } | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/emqx/router/IotEmqxAuthEventHandler.java
| ... | ... | @@ -7,6 +7,7 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 7 | 7 | import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi; |
| 8 | 8 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; |
| 9 | 9 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 10 | +import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity; | |
| 10 | 11 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; |
| 11 | 12 | import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; |
| 12 | 13 | import io.vertx.core.json.JsonObject; |
| ... | ... | @@ -201,7 +202,7 @@ public class IotEmqxAuthEventHandler { |
| 201 | 202 | */ |
| 202 | 203 | private void handleDeviceStateChange(String username, boolean online) { |
| 203 | 204 | // 1. 解析设备信息 |
| 204 | - IotDeviceAuthUtils.DeviceInfo deviceInfo = IotDeviceAuthUtils.parseUsername(username); | |
| 205 | + IotDeviceIdentity deviceInfo = IotDeviceAuthUtils.parseUsername(username); | |
| 205 | 206 | if (deviceInfo == null) { |
| 206 | 207 | log.debug("[handleDeviceStateChange][跳过非设备({})连接]", username); |
| 207 | 208 | return; | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/IotHttpUpstreamProtocol.java
| ... | ... | @@ -51,7 +51,7 @@ public class IotHttpUpstreamProtocol extends AbstractVerticle { |
| 51 | 51 | router.post(IotHttpAuthHandler.PATH).handler(authHandler); |
| 52 | 52 | IotHttpRegisterHandler registerHandler = new IotHttpRegisterHandler(); |
| 53 | 53 | router.post(IotHttpRegisterHandler.PATH).handler(registerHandler); |
| 54 | - IotHttpRegisterSubHandler registerSubHandler = new IotHttpRegisterSubHandler(this); | |
| 54 | + IotHttpRegisterSubHandler registerSubHandler = new IotHttpRegisterSubHandler(); | |
| 55 | 55 | router.post(IotHttpRegisterSubHandler.PATH).handler(registerSubHandler); |
| 56 | 56 | IotHttpUpstreamHandler upstreamHandler = new IotHttpUpstreamHandler(this); |
| 57 | 57 | router.post(IotHttpUpstreamHandler.PATH).handler(upstreamHandler); | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/router/IotHttpAbstractHandler.java
| ... | ... | @@ -8,7 +8,7 @@ import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| 8 | 8 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 9 | 9 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; |
| 10 | 10 | import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; |
| 11 | -import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; | |
| 11 | +import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity; | |
| 12 | 12 | import cn.iocoder.yudao.module.iot.gateway.service.auth.IotDeviceTokenService; |
| 13 | 13 | import io.vertx.core.Handler; |
| 14 | 14 | import io.vertx.core.http.HttpHeaders; |
| ... | ... | @@ -74,7 +74,7 @@ public abstract class IotHttpAbstractHandler implements Handler<RoutingContext> |
| 74 | 74 | } |
| 75 | 75 | |
| 76 | 76 | // 校验 token |
| 77 | - IotDeviceAuthUtils.DeviceInfo deviceInfo = deviceTokenService.verifyToken(token); | |
| 77 | + IotDeviceIdentity deviceInfo = deviceTokenService.verifyToken(token); | |
| 78 | 78 | Assert.notNull(deviceInfo, "设备信息不能为空"); |
| 79 | 79 | // 校验设备信息是否匹配 |
| 80 | 80 | if (ObjUtil.notEqual(productKey, deviceInfo.getProductKey()) | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/router/IotHttpAuthHandler.java
| ... | ... | @@ -9,7 +9,7 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 9 | 9 | import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi; |
| 10 | 10 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; |
| 11 | 11 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 12 | -import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; | |
| 12 | +import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity; | |
| 13 | 13 | import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpUpstreamProtocol; |
| 14 | 14 | import cn.iocoder.yudao.module.iot.gateway.service.auth.IotDeviceTokenService; |
| 15 | 15 | import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; |
| ... | ... | @@ -72,7 +72,7 @@ public class IotHttpAuthHandler extends IotHttpAbstractHandler { |
| 72 | 72 | throw exception(DEVICE_AUTH_FAIL); |
| 73 | 73 | } |
| 74 | 74 | // 2.2 生成 Token |
| 75 | - IotDeviceAuthUtils.DeviceInfo deviceInfo = deviceTokenService.parseUsername(username); | |
| 75 | + IotDeviceIdentity deviceInfo = deviceTokenService.parseUsername(username); | |
| 76 | 76 | Assert.notNull(deviceInfo, "设备信息不能为空"); |
| 77 | 77 | String token = deviceTokenService.createToken(deviceInfo.getProductKey(), deviceInfo.getDeviceName()); |
| 78 | 78 | Assert.notBlank(token, "生成 token 不能为空位"); | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/router/IotHttpRegisterHandler.java
| ... | ... | @@ -33,7 +33,6 @@ public class IotHttpRegisterHandler extends IotHttpAbstractHandler { |
| 33 | 33 | @Override |
| 34 | 34 | public CommonResult<Object> handle0(RoutingContext context) { |
| 35 | 35 | // 1. 解析参数 |
| 36 | - // TODO @AI:参数不太对,看看我写的建议 | |
| 37 | 36 | JsonObject body = context.body().asJsonObject(); |
| 38 | 37 | String productKey = body.getString("productKey"); |
| 39 | 38 | if (StrUtil.isEmpty(productKey)) { |
| ... | ... | @@ -43,21 +42,14 @@ public class IotHttpRegisterHandler extends IotHttpAbstractHandler { |
| 43 | 42 | if (StrUtil.isEmpty(deviceName)) { |
| 44 | 43 | throw invalidParamException("deviceName 不能为空"); |
| 45 | 44 | } |
| 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 不能为空"); | |
| 45 | + String productSecret = body.getString("productSecret"); | |
| 46 | + if (StrUtil.isEmpty(productSecret)) { | |
| 47 | + throw invalidParamException("productSecret 不能为空"); | |
| 53 | 48 | } |
| 54 | 49 | |
| 55 | 50 | // 2. 调用动态注册 |
| 56 | 51 | IotDeviceRegisterReqDTO reqDTO = new IotDeviceRegisterReqDTO() |
| 57 | - .setProductKey(productKey) | |
| 58 | - .setDeviceName(deviceName) | |
| 59 | - .setRandom(random) | |
| 60 | - .setSign(sign); | |
| 52 | + .setProductKey(productKey).setDeviceName(deviceName).setProductSecret(productSecret); | |
| 61 | 53 | CommonResult<IotDeviceRegisterRespDTO> result = deviceApi.registerDevice(reqDTO); |
| 62 | 54 | result.checkError(); |
| 63 | 55 | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/router/IotHttpRegisterSubHandler.java
| ... | ... | @@ -2,12 +2,15 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.http.router; |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.extra.spring.SpringUtil; |
| 4 | 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; | |
| 5 | +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | |
| 6 | +import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi; | |
| 7 | +import cn.iocoder.yudao.module.iot.core.biz.dto.IotSubDeviceRegisterFullReqDTO; | |
| 8 | +import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterRespDTO; | |
| 9 | +import io.vertx.core.json.JsonObject; | |
| 9 | 10 | import io.vertx.ext.web.RoutingContext; |
| 10 | 11 | |
| 12 | +import java.util.List; | |
| 13 | + | |
| 11 | 14 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| 12 | 15 | |
| 13 | 16 | /** |
| ... | ... | @@ -27,13 +30,10 @@ public class IotHttpRegisterSubHandler extends IotHttpAbstractHandler { |
| 27 | 30 | */ |
| 28 | 31 | public static final String PATH = "/auth/register/sub-device/:productKey/:deviceName"; |
| 29 | 32 | |
| 30 | - private final IotHttpUpstreamProtocol protocol; | |
| 31 | - | |
| 32 | - private final IotDeviceMessageService deviceMessageService; | |
| 33 | + private final IotDeviceCommonApi deviceApi; | |
| 33 | 34 | |
| 34 | - public IotHttpRegisterSubHandler(IotHttpUpstreamProtocol protocol) { | |
| 35 | - this.protocol = protocol; | |
| 36 | - this.deviceMessageService = SpringUtil.getBean(IotDeviceMessageService.class); | |
| 35 | + public IotHttpRegisterSubHandler() { | |
| 36 | + this.deviceApi = SpringUtil.getBean(IotDeviceCommonApi.class); | |
| 37 | 37 | } |
| 38 | 38 | |
| 39 | 39 | @Override |
| ... | ... | @@ -42,18 +42,19 @@ public class IotHttpRegisterSubHandler extends IotHttpAbstractHandler { |
| 42 | 42 | String productKey = context.pathParam("productKey"); |
| 43 | 43 | String deviceName = context.pathParam("deviceName"); |
| 44 | 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()); | |
| 45 | + // 2. 解析子设备列表 | |
| 46 | + JsonObject body = context.body().asJsonObject(); | |
| 47 | + List<cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterReqDTO> subDevices = JsonUtils.parseArray( | |
| 48 | + body.getJsonArray("params").toString(), cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterReqDTO.class); | |
| 50 | 49 | |
| 51 | - // TODO @AI:可能还是需要一个新的 deviceApi 接口。因为 register sub 子设备不太一行; | |
| 52 | - // 2.3 发送消息 | |
| 53 | - Object responseData = deviceMessageService.sendDeviceMessage(message, productKey, deviceName, protocol.getServerId()); | |
| 50 | + // 3. 调用子设备动态注册 | |
| 51 | + IotSubDeviceRegisterFullReqDTO reqDTO = new IotSubDeviceRegisterFullReqDTO() | |
| 52 | + .setGatewayProductKey(productKey).setGatewayDeviceName(deviceName).setSubDevices(subDevices); | |
| 53 | + CommonResult<List<IotSubDeviceRegisterRespDTO>> result = deviceApi.registerSubDevices(reqDTO); | |
| 54 | + result.checkError(); | |
| 54 | 55 | |
| 55 | - // 3. 返回结果 | |
| 56 | - return success(responseData); | |
| 56 | + // 4. 返回结果 | |
| 57 | + return success(result.getData()); | |
| 57 | 58 | } |
| 58 | 59 | |
| 59 | 60 | } | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/router/IotMqttUpstreamHandler.java
| ... | ... | @@ -9,6 +9,7 @@ import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; |
| 9 | 9 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceGetReqDTO; |
| 10 | 10 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO; |
| 11 | 11 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 12 | +import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity; | |
| 12 | 13 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; |
| 13 | 14 | import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttUpstreamProtocol; |
| 14 | 15 | import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager.IotMqttConnectionManager; |
| ... | ... | @@ -214,7 +215,7 @@ public class IotMqttUpstreamHandler { |
| 214 | 215 | } |
| 215 | 216 | |
| 216 | 217 | // 4. 获取设备信息 |
| 217 | - IotDeviceAuthUtils.DeviceInfo deviceInfo = IotDeviceAuthUtils.parseUsername(username); | |
| 218 | + IotDeviceIdentity deviceInfo = IotDeviceAuthUtils.parseUsername(username); | |
| 218 | 219 | if (deviceInfo == null) { |
| 219 | 220 | log.warn("[authenticateDevice][用户名格式不正确,客户端 ID: {},用户名: {}]", clientId, username); |
| 220 | 221 | return false; | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqttws/router/IotMqttWsUpstreamHandler.java
| ... | ... | @@ -10,6 +10,7 @@ import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; |
| 10 | 10 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceGetReqDTO; |
| 11 | 11 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO; |
| 12 | 12 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 13 | +import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity; | |
| 13 | 14 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; |
| 14 | 15 | import cn.iocoder.yudao.module.iot.gateway.protocol.mqttws.IotMqttWsUpstreamProtocol; |
| 15 | 16 | import cn.iocoder.yudao.module.iot.gateway.protocol.mqttws.manager.IotMqttWsConnectionManager; |
| ... | ... | @@ -521,7 +522,7 @@ public class IotMqttWsUpstreamHandler { |
| 521 | 522 | } |
| 522 | 523 | |
| 523 | 524 | // 3. 获取设备信息 |
| 524 | - IotDeviceAuthUtils.DeviceInfo deviceInfo = IotDeviceAuthUtils.parseUsername(username); | |
| 525 | + IotDeviceIdentity deviceInfo = IotDeviceAuthUtils.parseUsername(username); | |
| 525 | 526 | if (deviceInfo == null) { |
| 526 | 527 | log.warn("[authenticateDevice][用户名格式不正确,username: {}]", username); |
| 527 | 528 | return null; | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/router/IotTcpUpstreamHandler.java
| ... | ... | @@ -11,6 +11,7 @@ import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi; |
| 11 | 11 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; |
| 12 | 12 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO; |
| 13 | 13 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 14 | +import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity; | |
| 14 | 15 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; |
| 15 | 16 | import cn.iocoder.yudao.module.iot.gateway.codec.tcp.IotTcpBinaryDeviceMessageCodec; |
| 16 | 17 | import cn.iocoder.yudao.module.iot.gateway.codec.tcp.IotTcpJsonDeviceMessageCodec; |
| ... | ... | @@ -162,7 +163,7 @@ public class IotTcpUpstreamHandler implements Handler<NetSocket> { |
| 162 | 163 | } |
| 163 | 164 | |
| 164 | 165 | // 2.1 解析设备信息 |
| 165 | - IotDeviceAuthUtils.DeviceInfo deviceInfo = IotDeviceAuthUtils.parseUsername(authParams.getUsername()); | |
| 166 | + IotDeviceIdentity deviceInfo = IotDeviceAuthUtils.parseUsername(authParams.getUsername()); | |
| 166 | 167 | if (deviceInfo == null) { |
| 167 | 168 | sendErrorResponse(socket, message.getRequestId(), "解析设备信息失败", codecType); |
| 168 | 169 | return; | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/service/auth/IotDeviceTokenService.java
| 1 | 1 | package cn.iocoder.yudao.module.iot.gateway.service.auth; |
| 2 | 2 | |
| 3 | -import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; | |
| 3 | +import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity; | |
| 4 | 4 | |
| 5 | 5 | /** |
| 6 | 6 | * IoT 设备 Token Service 接口 |
| ... | ... | @@ -24,7 +24,7 @@ public interface IotDeviceTokenService { |
| 24 | 24 | * @param token 设备 Token |
| 25 | 25 | * @return 设备信息 |
| 26 | 26 | */ |
| 27 | - IotDeviceAuthUtils.DeviceInfo verifyToken(String token); | |
| 27 | + IotDeviceIdentity verifyToken(String token); | |
| 28 | 28 | |
| 29 | 29 | /** |
| 30 | 30 | * 解析用户名 |
| ... | ... | @@ -32,6 +32,6 @@ public interface IotDeviceTokenService { |
| 32 | 32 | * @param username 用户名 |
| 33 | 33 | * @return 设备信息 |
| 34 | 34 | */ |
| 35 | - IotDeviceAuthUtils.DeviceInfo parseUsername(String username); | |
| 35 | + IotDeviceIdentity parseUsername(String username); | |
| 36 | 36 | |
| 37 | 37 | } | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/service/auth/IotDeviceTokenServiceImpl.java
| ... | ... | @@ -5,6 +5,7 @@ import cn.hutool.json.JSONObject; |
| 5 | 5 | import cn.hutool.jwt.JWT; |
| 6 | 6 | import cn.hutool.jwt.JWTUtil; |
| 7 | 7 | import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; |
| 8 | +import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity; | |
| 8 | 9 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; |
| 9 | 10 | import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties; |
| 10 | 11 | import jakarta.annotation.Resource; |
| ... | ... | @@ -48,7 +49,7 @@ public class IotDeviceTokenServiceImpl implements IotDeviceTokenService { |
| 48 | 49 | } |
| 49 | 50 | |
| 50 | 51 | @Override |
| 51 | - public IotDeviceAuthUtils.DeviceInfo verifyToken(String token) { | |
| 52 | + public IotDeviceIdentity verifyToken(String token) { | |
| 52 | 53 | Assert.notBlank(token, "token 不能为空"); |
| 53 | 54 | // 校验 JWT Token |
| 54 | 55 | boolean verify = JWTUtil.verify(token, gatewayProperties.getToken().getSecret().getBytes()); |
| ... | ... | @@ -68,11 +69,11 @@ public class IotDeviceTokenServiceImpl implements IotDeviceTokenService { |
| 68 | 69 | String deviceName = payload.getStr("deviceName"); |
| 69 | 70 | Assert.notBlank(productKey, "productKey 不能为空"); |
| 70 | 71 | Assert.notBlank(deviceName, "deviceName 不能为空"); |
| 71 | - return new IotDeviceAuthUtils.DeviceInfo().setProductKey(productKey).setDeviceName(deviceName); | |
| 72 | + return new IotDeviceIdentity(productKey, deviceName); | |
| 72 | 73 | } |
| 73 | 74 | |
| 74 | 75 | @Override |
| 75 | - public IotDeviceAuthUtils.DeviceInfo parseUsername(String username) { | |
| 76 | + public IotDeviceIdentity parseUsername(String username) { | |
| 76 | 77 | return IotDeviceAuthUtils.parseUsername(username); |
| 77 | 78 | } |
| 78 | 79 | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/service/device/remote/IotDeviceApiImpl.java
| ... | ... | @@ -6,9 +6,13 @@ import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi; |
| 6 | 6 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; |
| 7 | 7 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceGetReqDTO; |
| 8 | 8 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO; |
| 9 | +import cn.iocoder.yudao.module.iot.core.biz.dto.IotSubDeviceRegisterFullReqDTO; | |
| 9 | 10 | import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO; |
| 10 | 11 | import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterRespDTO; |
| 12 | +import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterRespDTO; | |
| 11 | 13 | import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties; |
| 14 | + | |
| 15 | +import java.util.List; | |
| 12 | 16 | import jakarta.annotation.PostConstruct; |
| 13 | 17 | import jakarta.annotation.Resource; |
| 14 | 18 | import lombok.extern.slf4j.Slf4j; |
| ... | ... | @@ -61,6 +65,11 @@ public class IotDeviceApiImpl implements IotDeviceCommonApi { |
| 61 | 65 | return doPost("/register", reqDTO, new ParameterizedTypeReference<>() { }); |
| 62 | 66 | } |
| 63 | 67 | |
| 68 | + @Override | |
| 69 | + public CommonResult<List<IotSubDeviceRegisterRespDTO>> registerSubDevices(IotSubDeviceRegisterFullReqDTO reqDTO) { | |
| 70 | + return doPost("/register-sub", reqDTO, new ParameterizedTypeReference<>() { }); | |
| 71 | + } | |
| 72 | + | |
| 64 | 73 | private <T, R> CommonResult<R> doPost(String url, T body, |
| 65 | 74 | ParameterizedTypeReference<CommonResult<R>> responseType) { |
| 66 | 75 | try { | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/IotDeviceRegisterHttpProtocolIntegrationTest.java deleted
| 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/IotDirectDeviceHttpProtocolIntegrationTest.java
| ... | ... | @@ -7,6 +7,7 @@ import cn.hutool.http.HttpUtil; |
| 7 | 7 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; |
| 8 | 8 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; |
| 9 | 9 | import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; |
| 10 | +import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO; | |
| 10 | 11 | import cn.iocoder.yudao.module.iot.core.topic.event.IotDeviceEventPostReqDTO; |
| 11 | 12 | import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPostReqDTO; |
| 12 | 13 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; |
| ... | ... | @@ -22,6 +23,7 @@ import org.junit.jupiter.api.Test; |
| 22 | 23 | * <p>使用步骤: |
| 23 | 24 | * <ol> |
| 24 | 25 | * <li>启动 yudao-module-iot-gateway 服务(HTTP 端口 8092)</li> |
| 26 | + * <li>运行 {@link #testDeviceRegister()} 测试直连设备动态注册(一型一密)</li> | |
| 25 | 27 | * <li>运行 {@link #testAuth()} 获取设备 token,将返回的 token 粘贴到 {@link #TOKEN} 常量</li> |
| 26 | 28 | * <li>运行以下测试方法: |
| 27 | 29 | * <ul> |
| ... | ... | @@ -46,10 +48,77 @@ public class IotDirectDeviceHttpProtocolIntegrationTest { |
| 46 | 48 | private static final String DEVICE_SECRET = "0baa4c2ecc104ae1a26b4070c218bdf3"; |
| 47 | 49 | |
| 48 | 50 | /** |
| 51 | + * 产品密钥(从 iot_product 表的 product_secret 字段获取),用于动态注册 | |
| 52 | + */ | |
| 53 | + private static final String PRODUCT_SECRET = "your_product_secret"; | |
| 54 | + | |
| 55 | + /** | |
| 56 | + * 动态注册的设备名称 | |
| 57 | + */ | |
| 58 | + private static final String REGISTER_DEVICE_NAME = "test-register-device"; | |
| 59 | + | |
| 60 | + /** | |
| 49 | 61 | * 直连设备 Token:从 {@link #testAuth()} 方法获取后,粘贴到这里 |
| 50 | 62 | */ |
| 51 | 63 | private static final String TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwcm9kdWN0S2V5IjoiNGF5bVpnT1RPT0NyREtSVCIsImV4cCI6MTc2OTMwNTA1NSwiZGV2aWNlTmFtZSI6InNtYWxsIn0.mf3MEATCn5bp6cXgULunZjs8d00RGUxj96JEz0hMS7k"; |
| 52 | 64 | |
| 65 | + // ===================== 动态注册测试 ===================== | |
| 66 | + | |
| 67 | + /** | |
| 68 | + * 直连设备动态注册测试(一型一密) | |
| 69 | + * <p> | |
| 70 | + * 使用产品密钥(productSecret)验证身份,成功后返回设备密钥(deviceSecret) | |
| 71 | + * <p> | |
| 72 | + * 注意:此接口不需要 Token 认证 | |
| 73 | + */ | |
| 74 | + @Test | |
| 75 | + public void testDeviceRegister() { | |
| 76 | + // 1.1 构建请求 | |
| 77 | + String url = String.format("http://%s:%d/auth/register/device", SERVER_HOST, SERVER_PORT); | |
| 78 | + // 1.2 构建请求参数 | |
| 79 | + IotDeviceRegisterReqDTO reqDTO = new IotDeviceRegisterReqDTO(); | |
| 80 | + reqDTO.setProductKey(PRODUCT_KEY); | |
| 81 | + reqDTO.setDeviceName(REGISTER_DEVICE_NAME); | |
| 82 | + reqDTO.setProductSecret(PRODUCT_SECRET); | |
| 83 | + String payload = JsonUtils.toJsonString(reqDTO); | |
| 84 | + // 1.3 输出请求 | |
| 85 | + log.info("[testDeviceRegister][请求 URL: {}]", url); | |
| 86 | + log.info("[testDeviceRegister][请求体: {}]", payload); | |
| 87 | + | |
| 88 | + // 2.1 发送请求 | |
| 89 | + String response = HttpUtil.post(url, payload); | |
| 90 | + // 2.2 输出结果 | |
| 91 | + log.info("[testDeviceRegister][响应体: {}]", response); | |
| 92 | + log.info("[testDeviceRegister][成功后可使用返回的 deviceSecret 进行一机一密认证]"); | |
| 93 | + } | |
| 94 | + | |
| 95 | + /** | |
| 96 | + * 测试动态注册后使用 deviceSecret 进行认证 | |
| 97 | + * <p> | |
| 98 | + * 此测试需要先执行 testDeviceRegister 获取 deviceSecret | |
| 99 | + */ | |
| 100 | + @Test | |
| 101 | + public void testAuthAfterRegister() { | |
| 102 | + // 1.1 构建请求 | |
| 103 | + String url = String.format("http://%s:%d/auth", SERVER_HOST, SERVER_PORT); | |
| 104 | + // TODO 将 testDeviceRegister 返回的 deviceSecret 填入此处 | |
| 105 | + String deviceSecret = "返回的deviceSecret"; | |
| 106 | + IotDeviceAuthUtils.AuthInfo authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, REGISTER_DEVICE_NAME, deviceSecret); | |
| 107 | + IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO() | |
| 108 | + .setClientId(authInfo.getClientId()) | |
| 109 | + .setUsername(authInfo.getUsername()) | |
| 110 | + .setPassword(authInfo.getPassword()); | |
| 111 | + String payload = JsonUtils.toJsonString(authReqDTO); | |
| 112 | + // 1.2 输出请求 | |
| 113 | + log.info("[testAuthAfterRegister][请求 URL: {}]", url); | |
| 114 | + log.info("[testAuthAfterRegister][请求体: {}]", payload); | |
| 115 | + | |
| 116 | + // 2.1 发送请求 | |
| 117 | + String response = HttpUtil.post(url, payload); | |
| 118 | + // 2.2 输出结果 | |
| 119 | + log.info("[testAuthAfterRegister][响应体: {}]", response); | |
| 120 | + } | |
| 121 | + | |
| 53 | 122 | // ===================== 认证测试 ===================== |
| 54 | 123 | |
| 55 | 124 | /** | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/IotGatewayDeviceHttpProtocolIntegrationTest.java
| ... | ... | @@ -75,7 +75,7 @@ public class IotGatewayDeviceHttpProtocolIntegrationTest { |
| 75 | 75 | public void testAuth() { |
| 76 | 76 | // 1.1 构建请求 |
| 77 | 77 | String url = String.format("http://%s:%d/auth", SERVER_HOST, SERVER_PORT); |
| 78 | - IotDeviceAuthUtils.AuthInfo authInfo = IotDeviceAuthUtils.getAuthInfo( | |
| 78 | + IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo( | |
| 79 | 79 | GATEWAY_PRODUCT_KEY, GATEWAY_DEVICE_NAME, GATEWAY_DEVICE_SECRET); |
| 80 | 80 | IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO() |
| 81 | 81 | .setClientId(authInfo.getClientId()) |
| ... | ... | @@ -108,7 +108,7 @@ public class IotGatewayDeviceHttpProtocolIntegrationTest { |
| 108 | 108 | String url = String.format("http://%s:%d/topic/sys/%s/%s/thing/topo/add", |
| 109 | 109 | SERVER_HOST, SERVER_PORT, GATEWAY_PRODUCT_KEY, GATEWAY_DEVICE_NAME); |
| 110 | 110 | // 1.2 构建子设备认证信息 |
| 111 | - IotDeviceAuthUtils.AuthInfo subAuthInfo = IotDeviceAuthUtils.getAuthInfo( | |
| 111 | + IotDeviceAuthReqDTO subAuthInfo = IotDeviceAuthUtils.getAuthInfo( | |
| 112 | 112 | SUB_DEVICE_PRODUCT_KEY, SUB_DEVICE_NAME, SUB_DEVICE_SECRET); |
| 113 | 113 | IotDeviceAuthReqDTO subDeviceAuth = new IotDeviceAuthReqDTO() |
| 114 | 114 | .setClientId(subAuthInfo.getClientId()) | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/IotGatewaySubDeviceHttpProtocolIntegrationTest.java
| ... | ... | @@ -63,7 +63,7 @@ public class IotGatewaySubDeviceHttpProtocolIntegrationTest { |
| 63 | 63 | public void testAuth() { |
| 64 | 64 | // 1.1 构建请求 |
| 65 | 65 | String url = String.format("http://%s:%d/auth", SERVER_HOST, SERVER_PORT); |
| 66 | - IotDeviceAuthUtils.AuthInfo authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET); | |
| 66 | + IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET); | |
| 67 | 67 | IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO() |
| 68 | 68 | .setClientId(authInfo.getClientId()) |
| 69 | 69 | .setUsername(authInfo.getUsername()) | ... | ... |