Commit b4ce72ea7d4caec62b29ed4dcbf6f366a6266009

Authored by YunaiV
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&lt;RoutingContext&gt;
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&lt;NetSocket&gt; {
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())
... ...