mirror of
https://gitee.com/yudaocode/yudao-boot-mini.git
synced 2026-03-22 05:27:15 +08:00
feat(iot):【网关设备:70%】动态注册的初步实现(未测试),基于 stateful-sauteeing-pillow.md 规划
This commit is contained in:
@@ -7,6 +7,8 @@ import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
|
||||
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
|
||||
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceGetReqDTO;
|
||||
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO;
|
||||
import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO;
|
||||
import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterRespDTO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
|
||||
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
|
||||
@@ -57,4 +59,11 @@ public class IoTDeviceApiImpl implements IotDeviceCommonApi {
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
@PostMapping(RpcConstants.RPC_API_PREFIX + "/iot/device/register")
|
||||
@PermitAll
|
||||
public CommonResult<IotDeviceRegisterRespDTO> registerDevice(@RequestBody IotDeviceRegisterReqDTO reqDTO) {
|
||||
return success(deviceService.registerDevice(reqDTO));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -27,6 +27,12 @@ public class IotProductRespVO {
|
||||
@ExcelProperty("产品标识")
|
||||
private String productKey;
|
||||
|
||||
@Schema(description = "产品密钥", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String productSecret;
|
||||
|
||||
@Schema(description = "是否开启动态注册", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
|
||||
private Boolean registerEnabled;
|
||||
|
||||
@Schema(description = "产品分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Long categoryId;
|
||||
|
||||
|
||||
@@ -48,4 +48,8 @@ public class IotProductSaveReqVO {
|
||||
@NotEmpty(message = "数据格式不能为空")
|
||||
private String codecType;
|
||||
|
||||
@Schema(description = "是否开启动态注册", example = "false")
|
||||
@NotEmpty(message = "是否开启动态注册不能为空")
|
||||
private Boolean registerEnabled;
|
||||
|
||||
}
|
||||
@@ -32,6 +32,14 @@ public class IotProductDO extends TenantBaseDO {
|
||||
* 产品标识
|
||||
*/
|
||||
private String productKey;
|
||||
/**
|
||||
* 产品密钥,用于一型一密动态注册
|
||||
*/
|
||||
private String productSecret;
|
||||
/**
|
||||
* 是否开启动态注册
|
||||
*/
|
||||
private Boolean registerEnabled;
|
||||
/**
|
||||
* 产品分类编号
|
||||
* <p>
|
||||
|
||||
@@ -40,9 +40,12 @@ public interface ErrorCodeConstants {
|
||||
ErrorCode DEVICE_TOPO_SUB_DEVICE_USERNAME_INVALID = new ErrorCode(1_050_003_101, "子设备用户名格式无效");
|
||||
ErrorCode DEVICE_TOPO_SUB_DEVICE_AUTH_FAILED = new ErrorCode(1_050_003_102, "子设备认证失败");
|
||||
ErrorCode DEVICE_TOPO_SUB_NOT_BINDTO_GATEWAY = new ErrorCode(1_050_003_103, "子设备【{}/{}】未绑定到该网关");
|
||||
// 子设备注册相关错误码 1-050-003-200
|
||||
// 设备注册相关错误码 1-050-003-200
|
||||
ErrorCode DEVICE_SUB_REGISTER_PARAMS_INVALID = new ErrorCode(1_050_003_200, "子设备注册参数无效");
|
||||
ErrorCode DEVICE_SUB_REGISTER_PRODUCT_NOT_GATEWAY_SUB = new ErrorCode(1_050_003_201, "产品【{}】不是网关子设备类型");
|
||||
ErrorCode DEVICE_REGISTER_DISABLED = new ErrorCode(1_050_003_210, "该产品未开启动态注册功能");
|
||||
ErrorCode DEVICE_REGISTER_SIGN_INVALID = new ErrorCode(1_050_003_211, "动态注册签名验证失败");
|
||||
ErrorCode DEVICE_ALREADY_ACTIVATED = new ErrorCode(1_050_003_212, "设备已激活,不允许重复注册");
|
||||
|
||||
// ========== 产品分类 1-050-004-000 ==========
|
||||
ErrorCode PRODUCT_CATEGORY_NOT_EXISTS = new ErrorCode(1_050_004_000, "产品分类不存在");
|
||||
|
||||
@@ -6,6 +6,8 @@ import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
|
||||
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity;
|
||||
import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO;
|
||||
import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterRespDTO;
|
||||
import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterRespDTO;
|
||||
import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoGetRespDTO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
|
||||
@@ -292,7 +294,7 @@ public interface IotDeviceService {
|
||||
*/
|
||||
List<IotDeviceDO> getDeviceListByHasLocation();
|
||||
|
||||
// ========== 网关-子设备绑定相关 ==========
|
||||
// ========== 网关-拓扑管理(后台操作) ==========
|
||||
|
||||
/**
|
||||
* 绑定子设备到网关
|
||||
@@ -354,6 +356,8 @@ public interface IotDeviceService {
|
||||
*/
|
||||
IotDeviceTopoGetRespDTO handleTopoGetMessage(IotDeviceDO gatewayDevice);
|
||||
|
||||
// ========== 设备动态注册 ==========
|
||||
|
||||
/**
|
||||
* 处理子设备动态注册消息(网关设备上报)
|
||||
*
|
||||
@@ -363,4 +367,12 @@ public interface IotDeviceService {
|
||||
*/
|
||||
List<IotSubDeviceRegisterRespDTO> handleSubDeviceRegisterMessage(IotDeviceMessage message, IotDeviceDO gatewayDevice);
|
||||
|
||||
/**
|
||||
* 设备动态注册(直连设备/网关)
|
||||
*
|
||||
* @param reqDTO 动态注册请求
|
||||
* @return 注册结果(包含 DeviceSecret)
|
||||
*/
|
||||
IotDeviceRegisterRespDTO registerDevice(@Valid IotDeviceRegisterReqDTO reqDTO);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package cn.iocoder.yudao.module.iot.service.device;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.BooleanUtil;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
@@ -18,6 +18,8 @@ import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
|
||||
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity;
|
||||
import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO;
|
||||
import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterRespDTO;
|
||||
import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterReqDTO;
|
||||
import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterRespDTO;
|
||||
import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoAddReqDTO;
|
||||
@@ -77,6 +79,10 @@ public class IotDeviceServiceImpl implements IotDeviceService {
|
||||
@Lazy // 延迟加载,解决循环依赖
|
||||
private IotDeviceMessageService deviceMessageService;
|
||||
|
||||
private IotDeviceServiceImpl getSelf() {
|
||||
return SpringUtil.getBean(getClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long createDevice(IotDeviceSaveReqVO createReqVO) {
|
||||
return createDevice0(createReqVO).getId();
|
||||
@@ -138,7 +144,7 @@ public class IotDeviceServiceImpl implements IotDeviceService {
|
||||
device.setProductId(product.getId()).setProductKey(product.getProductKey())
|
||||
.setDeviceType(product.getDeviceType());
|
||||
// 生成密钥
|
||||
device.setDeviceSecret(generateDeviceSecret());
|
||||
device.setDeviceSecret(IotDeviceAuthUtils.generateDeviceSecret());
|
||||
// 设置设备状态为未激活
|
||||
device.setState(IotDeviceStateEnum.INACTIVE.getState());
|
||||
}
|
||||
@@ -368,15 +374,6 @@ public class IotDeviceServiceImpl implements IotDeviceService {
|
||||
return deviceMapper.selectCountByGroupId(groupId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 deviceSecret
|
||||
*
|
||||
* @return 生成的 deviceSecret
|
||||
*/
|
||||
private String generateDeviceSecret() {
|
||||
return IdUtil.fastSimpleUUID();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class) // 添加事务,异常则回滚所有导入
|
||||
public IotDeviceImportRespVO importDevice(List<IotDeviceImportExcelVO> importDevices, boolean updateSupport) {
|
||||
@@ -566,7 +563,7 @@ public class IotDeviceServiceImpl implements IotDeviceService {
|
||||
return deviceMapper.selectListByHasLocation();
|
||||
}
|
||||
|
||||
// ========== 网关-子设备绑定相关 ==========
|
||||
// ========== 网关-拓扑管理(后台操作) ==========
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@@ -766,77 +763,6 @@ public class IotDeviceServiceImpl implements IotDeviceService {
|
||||
return new IotDeviceTopoGetRespDTO().setSubDevices(subDeviceIdentities);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IotSubDeviceRegisterRespDTO> handleSubDeviceRegisterMessage(IotDeviceMessage message, IotDeviceDO gatewayDevice) {
|
||||
// 1.1 校验网关设备类型
|
||||
if (!IotProductDeviceTypeEnum.isGateway(gatewayDevice.getDeviceType())) {
|
||||
throw exception(DEVICE_NOT_GATEWAY);
|
||||
}
|
||||
// 1.2 解析参数
|
||||
if (!(message.getParams() instanceof List)) {
|
||||
throw exception(DEVICE_SUB_REGISTER_PARAMS_INVALID);
|
||||
}
|
||||
List<IotSubDeviceRegisterReqDTO> paramsList = JsonUtils.convertList(message.getParams(),
|
||||
IotSubDeviceRegisterReqDTO.class);
|
||||
if (CollUtil.isEmpty(paramsList)) {
|
||||
throw exception(DEVICE_SUB_REGISTER_PARAMS_INVALID);
|
||||
}
|
||||
|
||||
// 2. 遍历注册每个子设备
|
||||
List<IotSubDeviceRegisterRespDTO> results = new ArrayList<>(paramsList.size());
|
||||
for (IotSubDeviceRegisterReqDTO params : paramsList) {
|
||||
try {
|
||||
IotDeviceDO device = registerSubDevice(gatewayDevice, params);
|
||||
results.add(new IotSubDeviceRegisterRespDTO(
|
||||
params.getProductKey(), params.getDeviceName(), device.getDeviceSecret()));
|
||||
} catch (Exception ex) {
|
||||
log.error("[handleSubDeviceRegisterMessage][子设备({}/{}) 注册失败]",
|
||||
params.getProductKey(), params.getDeviceName(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 返回响应数据(包含成功注册的子设备列表)
|
||||
return results;
|
||||
}
|
||||
|
||||
private IotDeviceDO registerSubDevice(IotDeviceDO gatewayDevice, IotSubDeviceRegisterReqDTO params) {
|
||||
// 1.1 校验产品
|
||||
IotProductDO product = productService.getProductByProductKey(params.getProductKey());
|
||||
if (product == null) {
|
||||
throw exception(PRODUCT_NOT_EXISTS);
|
||||
}
|
||||
// 1.2 校验产品是否为网关子设备类型
|
||||
if (!IotProductDeviceTypeEnum.isGatewaySub(product.getDeviceType())) {
|
||||
throw exception(DEVICE_SUB_REGISTER_PRODUCT_NOT_GATEWAY_SUB, params.getProductKey());
|
||||
}
|
||||
// 1.3 查找设备是否已存在
|
||||
IotDeviceDO existDevice = getSelf().getDeviceFromCache(params.getProductKey(), params.getDeviceName());
|
||||
if (existDevice != null) {
|
||||
// 校验是否绑定到当前网关
|
||||
if (ObjUtil.notEqual(existDevice.getGatewayId(), gatewayDevice.getId())) {
|
||||
throw exception(DEVICE_GATEWAY_BINDTO_EXISTS,
|
||||
existDevice.getProductKey(), existDevice.getDeviceName());
|
||||
}
|
||||
// 已存在则返回设备信息
|
||||
return existDevice;
|
||||
}
|
||||
|
||||
// 2. 创建新设备
|
||||
IotDeviceSaveReqVO createReqVO = new IotDeviceSaveReqVO()
|
||||
.setDeviceName(params.getDeviceName())
|
||||
.setProductId(product.getId())
|
||||
.setGatewayId(gatewayDevice.getId());
|
||||
IotDeviceDO newDevice = createDevice0(createReqVO);
|
||||
log.info("[registerSubDevice][网关({}/{}) 注册子设备({}/{})]",
|
||||
gatewayDevice.getProductKey(), gatewayDevice.getDeviceName(),
|
||||
newDevice.getProductKey(), newDevice.getDeviceName());
|
||||
return newDevice;
|
||||
}
|
||||
|
||||
private IotDeviceServiceImpl getSelf() {
|
||||
return SpringUtil.getBean(getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送拓扑变更通知给网关设备
|
||||
*
|
||||
@@ -875,4 +801,107 @@ public class IotDeviceServiceImpl implements IotDeviceService {
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 设备动态注册 ==========
|
||||
|
||||
@Override
|
||||
public IotDeviceRegisterRespDTO registerDevice(IotDeviceRegisterReqDTO reqDTO) {
|
||||
// 1.1 校验产品
|
||||
IotProductDO product = productService.getProductByProductKey(reqDTO.getProductKey());
|
||||
if (product == null) {
|
||||
throw exception(PRODUCT_NOT_EXISTS);
|
||||
}
|
||||
// 1.2 校验产品是否开启动态注册
|
||||
if (BooleanUtil.isFalse(product.getRegisterEnabled())) {
|
||||
throw exception(DEVICE_REGISTER_DISABLED);
|
||||
}
|
||||
// 1.3 验证签名
|
||||
if (!IotDeviceAuthUtils.verifyRegisterSign(product.getProductSecret(),
|
||||
reqDTO.getProductKey(), reqDTO.getDeviceName(), reqDTO.getRandom(), reqDTO.getSign())) {
|
||||
throw exception(DEVICE_REGISTER_SIGN_INVALID);
|
||||
}
|
||||
|
||||
// 4. 查找设备(预注册模式:设备必须已存在)
|
||||
// TODO @AI:设备不用提前有,这个有问题!
|
||||
IotDeviceDO device = getSelf().getDeviceFromCache(reqDTO.getProductKey(), reqDTO.getDeviceName());
|
||||
if (device == null) {
|
||||
throw exception(DEVICE_NOT_EXISTS);
|
||||
}
|
||||
|
||||
// 5. 校验设备是否已激活(已激活的设备不允许重复注册)
|
||||
if (!Objects.equals(device.getState(), IotDeviceStateEnum.INACTIVE.getState())) {
|
||||
throw exception(DEVICE_ALREADY_ACTIVATED);
|
||||
}
|
||||
|
||||
// 6. 返回设备密钥
|
||||
return new IotDeviceRegisterRespDTO(device.getProductKey(), device.getDeviceName(), device.getDeviceSecret());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IotSubDeviceRegisterRespDTO> handleSubDeviceRegisterMessage(IotDeviceMessage message, IotDeviceDO gatewayDevice) {
|
||||
// 1.1 校验网关设备类型
|
||||
if (!IotProductDeviceTypeEnum.isGateway(gatewayDevice.getDeviceType())) {
|
||||
throw exception(DEVICE_NOT_GATEWAY);
|
||||
}
|
||||
// 1.2 解析参数
|
||||
if (!(message.getParams() instanceof List)) {
|
||||
throw exception(DEVICE_SUB_REGISTER_PARAMS_INVALID);
|
||||
}
|
||||
List<IotSubDeviceRegisterReqDTO> paramsList = JsonUtils.convertList(message.getParams(),
|
||||
IotSubDeviceRegisterReqDTO.class);
|
||||
if (CollUtil.isEmpty(paramsList)) {
|
||||
throw exception(DEVICE_SUB_REGISTER_PARAMS_INVALID);
|
||||
}
|
||||
|
||||
// 2. 遍历注册每个子设备
|
||||
List<IotSubDeviceRegisterRespDTO> results = new ArrayList<>(paramsList.size());
|
||||
for (IotSubDeviceRegisterReqDTO params : paramsList) {
|
||||
try {
|
||||
IotDeviceDO device = registerSubDevice(gatewayDevice, params);
|
||||
results.add(new IotSubDeviceRegisterRespDTO(
|
||||
params.getProductKey(), params.getDeviceName(), device.getDeviceSecret()));
|
||||
} catch (Exception ex) {
|
||||
log.error("[handleSubDeviceRegisterMessage][子设备({}/{}) 注册失败]",
|
||||
params.getProductKey(), params.getDeviceName(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 返回响应数据(包含成功注册的子设备列表)
|
||||
return results;
|
||||
}
|
||||
|
||||
// TODO @AI:阿里云的,设备必须存在;
|
||||
private IotDeviceDO registerSubDevice(IotDeviceDO gatewayDevice, IotSubDeviceRegisterReqDTO params) {
|
||||
// 1.1 校验产品
|
||||
IotProductDO product = productService.getProductByProductKey(params.getProductKey());
|
||||
if (product == null) {
|
||||
throw exception(PRODUCT_NOT_EXISTS);
|
||||
}
|
||||
// 1.2 校验产品是否为网关子设备类型
|
||||
if (!IotProductDeviceTypeEnum.isGatewaySub(product.getDeviceType())) {
|
||||
throw exception(DEVICE_SUB_REGISTER_PRODUCT_NOT_GATEWAY_SUB, params.getProductKey());
|
||||
}
|
||||
// 1.3 查找设备是否已存在
|
||||
IotDeviceDO existDevice = getSelf().getDeviceFromCache(params.getProductKey(), params.getDeviceName());
|
||||
if (existDevice != null) {
|
||||
// 校验是否绑定到当前网关
|
||||
if (ObjUtil.notEqual(existDevice.getGatewayId(), gatewayDevice.getId())) {
|
||||
throw exception(DEVICE_GATEWAY_BINDTO_EXISTS,
|
||||
existDevice.getProductKey(), existDevice.getDeviceName());
|
||||
}
|
||||
// 已存在则返回设备信息
|
||||
return existDevice;
|
||||
}
|
||||
|
||||
// 2. 创建新设备
|
||||
IotDeviceSaveReqVO createReqVO = new IotDeviceSaveReqVO()
|
||||
.setDeviceName(params.getDeviceName())
|
||||
.setProductId(product.getId())
|
||||
.setGatewayId(gatewayDevice.getId());
|
||||
IotDeviceDO newDevice = createDevice0(createReqVO);
|
||||
log.info("[registerSubDevice][网关({}/{}) 注册子设备({}/{})]",
|
||||
gatewayDevice.getProductKey(), gatewayDevice.getDeviceName(),
|
||||
newDevice.getProductKey(), newDevice.getDeviceName());
|
||||
return newDevice;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.iot.service.product;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
|
||||
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.product.vo.product.IotProductPageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.product.vo.product.IotProductSaveReqVO;
|
||||
@@ -53,7 +54,8 @@ public class IotProductServiceImpl implements IotProductService {
|
||||
|
||||
// 2. 插入
|
||||
IotProductDO product = BeanUtils.toBean(createReqVO, IotProductDO.class)
|
||||
.setStatus(IotProductStatusEnum.UNPUBLISHED.getStatus());
|
||||
.setStatus(IotProductStatusEnum.UNPUBLISHED.getStatus())
|
||||
.setProductSecret(IotDeviceAuthUtils.generateProductSecret());
|
||||
productMapper.insert(product);
|
||||
return product.getId();
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
|
||||
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceGetReqDTO;
|
||||
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO;
|
||||
import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO;
|
||||
import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterRespDTO;
|
||||
|
||||
/**
|
||||
* IoT 设备通用 API
|
||||
@@ -28,4 +30,12 @@ public interface IotDeviceCommonApi {
|
||||
*/
|
||||
CommonResult<IotDeviceRespDTO> getDevice(IotDeviceGetReqDTO infoReqDTO);
|
||||
|
||||
/**
|
||||
* 设备动态注册(一型一密)
|
||||
*
|
||||
* @param reqDTO 动态注册请求
|
||||
* @return 注册结果(包含 DeviceSecret)
|
||||
*/
|
||||
CommonResult<IotDeviceRegisterRespDTO> registerDevice(IotDeviceRegisterReqDTO reqDTO);
|
||||
|
||||
}
|
||||
|
||||
@@ -33,9 +33,10 @@ public enum IotDeviceMessageMethodEnum implements ArrayValuable<String> {
|
||||
TOPO_CHANGE("thing.topo.change", "拓扑关系变更通知", false),
|
||||
|
||||
// ========== 设备注册 ==========
|
||||
// 可参考:https://help.aliyun.com/zh/iot/user-guide/register-devices
|
||||
// 可参考:https://help.aliyun.com/zh/iot/user-guide/unique-certificate-per-product-verification
|
||||
|
||||
SUB_DEVICE_REGISTER("thing.sub.register", "子设备动态注册", true),
|
||||
DEVICE_REGISTER("thing.auth.register", "设备动态注册", true),
|
||||
SUB_DEVICE_REGISTER("thing.auth.register.sub", "子设备动态注册", true),
|
||||
|
||||
// ========== 设备属性 ==========
|
||||
// 可参考:https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package cn.iocoder.yudao.module.iot.core.topic.auth;
|
||||
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* IoT 设备动态注册 Request DTO
|
||||
* <p>
|
||||
* 用于直连设备/网关的一型一密动态注册:使用 ProductSecret 验证签名,返回 DeviceSecret
|
||||
*
|
||||
* @author 芋道源码
|
||||
* @see <a href="https://help.aliyun.com/zh/iot/user-guide/unique-certificate-per-product-verification">阿里云 - 一型一密</a>
|
||||
*/
|
||||
@Data
|
||||
public class IotDeviceRegisterReqDTO {
|
||||
|
||||
/**
|
||||
* 产品标识
|
||||
*/
|
||||
@NotEmpty(message = "产品标识不能为空")
|
||||
private String productKey;
|
||||
|
||||
/**
|
||||
* 设备名称
|
||||
*/
|
||||
@NotEmpty(message = "设备名称不能为空")
|
||||
private String deviceName;
|
||||
|
||||
// TODO @AI:可以去掉 random 字段;
|
||||
/**
|
||||
* 随机数,用于签名
|
||||
*/
|
||||
@NotEmpty(message = "随机数不能为空")
|
||||
private String random;
|
||||
|
||||
// TODO @AI:看起来,是直接带 productSecret 阿里云上,你在检查下!
|
||||
/**
|
||||
* 签名
|
||||
*/
|
||||
@NotEmpty(message = "签名不能为空")
|
||||
private String sign;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package cn.iocoder.yudao.module.iot.core.topic.auth;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* IoT 设备动态注册 Response DTO
|
||||
* <p>
|
||||
* 用于直连设备/网关的一型一密动态注册响应
|
||||
*
|
||||
* @author 芋道源码
|
||||
* @see <a href="https://help.aliyun.com/zh/iot/user-guide/unique-certificate-per-product-verification">阿里云 - 一型一密</a>
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class IotDeviceRegisterRespDTO {
|
||||
|
||||
/**
|
||||
* 产品标识
|
||||
*/
|
||||
private String productKey;
|
||||
|
||||
/**
|
||||
* 设备名称
|
||||
*/
|
||||
private String deviceName;
|
||||
|
||||
/**
|
||||
* 设备密钥
|
||||
*/
|
||||
private String deviceSecret;
|
||||
|
||||
}
|
||||
@@ -6,10 +6,12 @@ import lombok.Data;
|
||||
/**
|
||||
* IoT 子设备动态注册 Request DTO
|
||||
* <p>
|
||||
* 用于 thing.sub.register 消息的 params 数组元素
|
||||
* 用于 thing.auth.register.sub 消息的 params 数组元素
|
||||
*
|
||||
* 特殊:网关子设备的动态注册,必须已经创建好该网关子设备(不然哪来的 {@link #deviceName} 字段)。更多的好处,是设备不用提前烧录 deviceSecret 密钥。
|
||||
*
|
||||
* @author 芋道源码
|
||||
* @see <a href="http://help.aliyun.com/zh/marketplace/dynamic-registration-of-sub-devices">阿里云 - 动态注册子设备</a>
|
||||
* @see <a href="https://help.aliyun.com/zh/iot/user-guide/register-devices">阿里云 - 动态注册子设备</a>
|
||||
*/
|
||||
@Data
|
||||
public class IotSubDeviceRegisterReqDTO {
|
||||
|
||||
@@ -7,10 +7,10 @@ import lombok.NoArgsConstructor;
|
||||
/**
|
||||
* IoT 子设备动态注册 Response DTO
|
||||
* <p>
|
||||
* 用于 thing.sub.register 响应的设备信息
|
||||
* 用于 thing.auth.register.sub 响应的设备信息
|
||||
*
|
||||
* @author 芋道源码
|
||||
* @see <a href="http://help.aliyun.com/zh/marketplace/dynamic-registration-of-sub-devices">阿里云 - 动态注册子设备</a>
|
||||
* @see <a href="https://help.aliyun.com/zh/iot/user-guide/register-devices">阿里云 - 动态注册子设备</a>
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package cn.iocoder.yudao.module.iot.core.util;
|
||||
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.crypto.digest.DigestUtil;
|
||||
import cn.hutool.crypto.digest.HmacAlgorithm;
|
||||
@@ -87,4 +88,62 @@ public class IotDeviceAuthUtils {
|
||||
return new DeviceInfo().setProductKey(usernameParts[1]).setDeviceName(usernameParts[0]);
|
||||
}
|
||||
|
||||
// ========== 动态注册相关方法 ==========
|
||||
|
||||
// TODO @AI:想了下,还是放回到对应的 productService、deviceService 更合适;
|
||||
|
||||
/**
|
||||
* 生成产品密钥
|
||||
*
|
||||
* @return 产品密钥(UUID)
|
||||
*/
|
||||
public static String generateProductSecret() {
|
||||
return IdUtil.fastSimpleUUID();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成设备密钥
|
||||
*
|
||||
* @return 设备密钥(UUID)
|
||||
*/
|
||||
public static String generateDeviceSecret() {
|
||||
return IdUtil.fastSimpleUUID();
|
||||
}
|
||||
|
||||
// TODO @AI:去掉 random;
|
||||
/**
|
||||
* 计算动态注册签名
|
||||
* <p>
|
||||
* 参考阿里云规范,参数按字典序排列拼接
|
||||
*
|
||||
* @param productSecret 产品密钥
|
||||
* @param productKey 产品标识
|
||||
* @param deviceName 设备名称
|
||||
* @param random 随机数
|
||||
* @return 签名
|
||||
* @see <a href="https://help.aliyun.com/zh/iot/user-guide/unique-certificate-per-product-verification">一型一密</a>
|
||||
*/
|
||||
public static String buildRegisterSign(String productSecret, String productKey, String deviceName, String random) {
|
||||
String content = "deviceName" + deviceName + "productKey" + productKey + "random" + random;
|
||||
return DigestUtil.hmac(HmacAlgorithm.HmacSHA256, StrUtil.utf8Bytes(productSecret))
|
||||
.digestHex(content);
|
||||
}
|
||||
|
||||
// TODO @AI:是不是调用方自己验证就好了,不要这里面抽;
|
||||
/**
|
||||
* 验证动态注册签名
|
||||
*
|
||||
* @param productSecret 产品密钥
|
||||
* @param productKey 产品标识
|
||||
* @param deviceName 设备名称
|
||||
* @param random 随机数
|
||||
* @param sign 待验证的签名
|
||||
* @return 是否验证通过
|
||||
*/
|
||||
public static boolean verifyRegisterSign(String productSecret, String productKey,
|
||||
String deviceName, String random, String sign) {
|
||||
String expectedSign = buildRegisterSign(productSecret, productKey, deviceName, random);
|
||||
return expectedSign.equals(sign);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.http;
|
||||
import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
|
||||
import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties;
|
||||
import cn.iocoder.yudao.module.iot.gateway.protocol.http.router.IotHttpAuthHandler;
|
||||
import cn.iocoder.yudao.module.iot.gateway.protocol.http.router.IotHttpRegisterHandler;
|
||||
import cn.iocoder.yudao.module.iot.gateway.protocol.http.router.IotHttpRegisterSubHandler;
|
||||
import cn.iocoder.yudao.module.iot.gateway.protocol.http.router.IotHttpUpstreamHandler;
|
||||
import io.vertx.core.AbstractVerticle;
|
||||
import io.vertx.core.Vertx;
|
||||
@@ -47,6 +49,10 @@ public class IotHttpUpstreamProtocol extends AbstractVerticle {
|
||||
// 创建处理器,添加路由处理器
|
||||
IotHttpAuthHandler authHandler = new IotHttpAuthHandler(this);
|
||||
router.post(IotHttpAuthHandler.PATH).handler(authHandler);
|
||||
IotHttpRegisterHandler registerHandler = new IotHttpRegisterHandler();
|
||||
router.post(IotHttpRegisterHandler.PATH).handler(registerHandler);
|
||||
IotHttpRegisterSubHandler registerSubHandler = new IotHttpRegisterSubHandler(this);
|
||||
router.post(IotHttpRegisterSubHandler.PATH).handler(registerSubHandler);
|
||||
IotHttpUpstreamHandler upstreamHandler = new IotHttpUpstreamHandler(this);
|
||||
router.post(IotHttpUpstreamHandler.PATH).handler(upstreamHandler);
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import cn.hutool.extra.spring.SpringUtil;
|
||||
import cn.iocoder.yudao.framework.common.exception.ServiceException;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
|
||||
import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
|
||||
import cn.iocoder.yudao.module.iot.gateway.service.auth.IotDeviceTokenService;
|
||||
import io.vertx.core.Handler;
|
||||
@@ -54,7 +55,7 @@ public abstract class IotHttpAbstractHandler implements Handler<RoutingContext>
|
||||
private void beforeHandle(RoutingContext context) {
|
||||
// 如果不需要认证,则不走前置处理
|
||||
String path = context.request().path();
|
||||
if (ObjUtil.equal(path, IotHttpAuthHandler.PATH)) {
|
||||
if (ObjectUtils.equalsAny(path, IotHttpAuthHandler.PATH, IotHttpRegisterHandler.PATH)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
package cn.iocoder.yudao.module.iot.gateway.protocol.http.router;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
|
||||
import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO;
|
||||
import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterRespDTO;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import io.vertx.ext.web.RoutingContext;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.invalidParamException;
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
|
||||
/**
|
||||
* IoT 网关 HTTP 协议的【设备动态注册】处理器
|
||||
* <p>
|
||||
* 用于直连设备/网关的一型一密动态注册,不需要认证
|
||||
*
|
||||
* @author 芋道源码
|
||||
* @see <a href="https://help.aliyun.com/zh/iot/user-guide/unique-certificate-per-product-verification">阿里云 - 一型一密</a>
|
||||
*/
|
||||
public class IotHttpRegisterHandler extends IotHttpAbstractHandler {
|
||||
|
||||
public static final String PATH = "/auth/register/device";
|
||||
|
||||
private final IotDeviceCommonApi deviceApi;
|
||||
|
||||
public IotHttpRegisterHandler() {
|
||||
this.deviceApi = SpringUtil.getBean(IotDeviceCommonApi.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommonResult<Object> handle0(RoutingContext context) {
|
||||
// 1. 解析参数
|
||||
// TODO @AI:参数不太对,看看我写的建议
|
||||
JsonObject body = context.body().asJsonObject();
|
||||
String productKey = body.getString("productKey");
|
||||
if (StrUtil.isEmpty(productKey)) {
|
||||
throw invalidParamException("productKey 不能为空");
|
||||
}
|
||||
String deviceName = body.getString("deviceName");
|
||||
if (StrUtil.isEmpty(deviceName)) {
|
||||
throw invalidParamException("deviceName 不能为空");
|
||||
}
|
||||
String random = body.getString("random");
|
||||
if (StrUtil.isEmpty(random)) {
|
||||
throw invalidParamException("random 不能为空");
|
||||
}
|
||||
String sign = body.getString("sign");
|
||||
if (StrUtil.isEmpty(sign)) {
|
||||
throw invalidParamException("sign 不能为空");
|
||||
}
|
||||
|
||||
// 2. 调用动态注册
|
||||
IotDeviceRegisterReqDTO reqDTO = new IotDeviceRegisterReqDTO()
|
||||
.setProductKey(productKey)
|
||||
.setDeviceName(deviceName)
|
||||
.setRandom(random)
|
||||
.setSign(sign);
|
||||
CommonResult<IotDeviceRegisterRespDTO> result = deviceApi.registerDevice(reqDTO);
|
||||
result.checkError();
|
||||
|
||||
// 3. 返回结果
|
||||
return success(result.getData());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package cn.iocoder.yudao.module.iot.gateway.protocol.http.router;
|
||||
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpUpstreamProtocol;
|
||||
import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
|
||||
import io.vertx.ext.web.RoutingContext;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
|
||||
/**
|
||||
* IoT 网关 HTTP 协议的【子设备动态注册】处理器
|
||||
* <p>
|
||||
* 用于子设备的动态注册,需要网关认证
|
||||
*
|
||||
* @author 芋道源码
|
||||
* @see <a href="https://help.aliyun.com/zh/iot/user-guide/register-devices">阿里云 - 动态注册子设备</a>
|
||||
*/
|
||||
public class IotHttpRegisterSubHandler extends IotHttpAbstractHandler {
|
||||
|
||||
/**
|
||||
* 路径:/auth/register/sub-device/:productKey/:deviceName
|
||||
* <p>
|
||||
* productKey 和 deviceName 是网关设备的标识
|
||||
*/
|
||||
public static final String PATH = "/auth/register/sub-device/:productKey/:deviceName";
|
||||
|
||||
private final IotHttpUpstreamProtocol protocol;
|
||||
|
||||
private final IotDeviceMessageService deviceMessageService;
|
||||
|
||||
public IotHttpRegisterSubHandler(IotHttpUpstreamProtocol protocol) {
|
||||
this.protocol = protocol;
|
||||
this.deviceMessageService = SpringUtil.getBean(IotDeviceMessageService.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommonResult<Object> handle0(RoutingContext context) {
|
||||
// 1. 解析通用参数
|
||||
String productKey = context.pathParam("productKey");
|
||||
String deviceName = context.pathParam("deviceName");
|
||||
|
||||
// 2.1 解析消息
|
||||
byte[] bytes = context.body().buffer().getBytes();
|
||||
IotDeviceMessage message = deviceMessageService.decodeDeviceMessage(bytes, productKey, deviceName);
|
||||
// 2.2 设置方法
|
||||
message.setMethod(IotDeviceMessageMethodEnum.SUB_DEVICE_REGISTER.getMethod());
|
||||
|
||||
// TODO @AI:可能还是需要一个新的 deviceApi 接口。因为 register sub 子设备不太一行;
|
||||
// 2.3 发送消息
|
||||
Object responseData = deviceMessageService.sendDeviceMessage(message, productKey, deviceName, protocol.getServerId());
|
||||
|
||||
// 3. 返回结果
|
||||
return success(responseData);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,6 +6,8 @@ import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
|
||||
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
|
||||
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceGetReqDTO;
|
||||
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO;
|
||||
import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO;
|
||||
import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterRespDTO;
|
||||
import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.Resource;
|
||||
@@ -54,6 +56,11 @@ public class IotDeviceApiImpl implements IotDeviceCommonApi {
|
||||
return doPost("/get", getReqDTO, new ParameterizedTypeReference<>() { });
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommonResult<IotDeviceRegisterRespDTO> registerDevice(IotDeviceRegisterReqDTO reqDTO) {
|
||||
return doPost("/register", reqDTO, new ParameterizedTypeReference<>() { });
|
||||
}
|
||||
|
||||
private <T, R> CommonResult<R> doPost(String url, T body,
|
||||
ParameterizedTypeReference<CommonResult<R>> responseType) {
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
package cn.iocoder.yudao.module.iot.gateway.protocol.http;
|
||||
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.http.HttpResponse;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
|
||||
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
|
||||
import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO;
|
||||
import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterReqDTO;
|
||||
import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
// TODO @AI:合并到 IotDirectDeviceHttpProtocolIntegrationTest 里呀,没必要拆开;只搞一个直连设备的注册就好了;
|
||||
/**
|
||||
* IoT 设备动态注册 HTTP 协议集成测试(手动测试)
|
||||
*
|
||||
* <p>测试场景:一型一密(One Type One Secret)动态注册机制
|
||||
*
|
||||
* <p><b>前置条件:</b>
|
||||
* <ol>
|
||||
* <li>产品已开启动态注册(registerEnabled = true)</li>
|
||||
* <li>设备已预先创建(预注册模式)</li>
|
||||
* <li>设备 deviceSecret 为空(未激活状态)</li>
|
||||
* </ol>
|
||||
*
|
||||
* <p>使用步骤:
|
||||
* <ol>
|
||||
* <li>启动 yudao-module-iot-gateway 服务(HTTP 端口 8092)</li>
|
||||
* <li>运行 {@link #testDeviceRegister()} 测试直连设备/网关动态注册</li>
|
||||
* <li>运行 {@link #testSubDeviceRegister()} 测试子设备动态注册(需要先获取网关 Token)</li>
|
||||
* </ol>
|
||||
*
|
||||
* @author 芋道源码
|
||||
* @see <a href="https://help.aliyun.com/zh/iot/user-guide/unique-certificate-per-product-verification">阿里云 - 一型一密</a>
|
||||
*/
|
||||
@Slf4j
|
||||
@SuppressWarnings("HttpUrlsUsage")
|
||||
public class IotDeviceRegisterHttpProtocolIntegrationTest {
|
||||
|
||||
private static final String SERVER_HOST = "127.0.0.1";
|
||||
private static final int SERVER_PORT = 8092;
|
||||
|
||||
// ===================== 直连设备/网关动态注册配置(根据实际情况修改) =====================
|
||||
/**
|
||||
* 产品 Key(需要开启动态注册)
|
||||
*/
|
||||
private static final String PRODUCT_KEY = "4aymZgOTOOCrDKRT";
|
||||
/**
|
||||
* 产品密钥(从 iot_product 表的 product_secret 字段获取)
|
||||
*/
|
||||
private static final String PRODUCT_SECRET = "your_product_secret";
|
||||
/**
|
||||
* 设备名称(需要预先创建,deviceSecret 为空)
|
||||
*/
|
||||
private static final String DEVICE_NAME = "test-register-device";
|
||||
|
||||
// ===================== 网关设备信息(用于子设备动态注册) =====================
|
||||
private static final String GATEWAY_PRODUCT_KEY = "m6XcS1ZJ3TW8eC0v";
|
||||
private static final String GATEWAY_DEVICE_NAME = "sub-ddd";
|
||||
private static final String GATEWAY_DEVICE_SECRET = "b3d62c70f8a4495487ed1d35d61ac2b3";
|
||||
|
||||
/**
|
||||
* 网关设备 Token:从网关认证获取后,粘贴到这里
|
||||
*/
|
||||
private static final String GATEWAY_TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwcm9kdWN0S2V5IjoibTZYY1MxWkozVFc4ZUMwdiIsImV4cCI6MTc2OTg2NjY3OCwiZGV2aWNlTmFtZSI6InN1Yi1kZGQifQ.nCLSAfHEjXLtTDRXARjOoFqpuo5WfArjFWweUAzrjKU";
|
||||
|
||||
// ===================== 子设备信息(用于子设备动态注册) =====================
|
||||
private static final String SUB_DEVICE_PRODUCT_KEY = "jAufEMTF1W6wnPhn";
|
||||
private static final String SUB_DEVICE_NAME = "test-sub-register-device";
|
||||
|
||||
// ===================== 直连设备/网关动态注册测试 =====================
|
||||
|
||||
/**
|
||||
* 直连设备/网关动态注册测试
|
||||
* <p>
|
||||
* 使用产品密钥(productSecret)进行签名验证,成功后返回设备密钥(deviceSecret)
|
||||
* <p>
|
||||
* 注意:此接口不需要 Token 认证
|
||||
*/
|
||||
@Test
|
||||
public void testDeviceRegister() {
|
||||
// 1.1 构建请求
|
||||
String url = String.format("http://%s:%d/auth/register/device", SERVER_HOST, SERVER_PORT);
|
||||
// 1.2 生成签名
|
||||
String random = IdUtil.fastSimpleUUID();
|
||||
String sign = IotDeviceAuthUtils.buildRegisterSign(PRODUCT_SECRET, PRODUCT_KEY, DEVICE_NAME, random);
|
||||
// 1.3 构建请求参数
|
||||
IotDeviceRegisterReqDTO reqDTO = new IotDeviceRegisterReqDTO();
|
||||
reqDTO.setProductKey(PRODUCT_KEY);
|
||||
reqDTO.setDeviceName(DEVICE_NAME);
|
||||
reqDTO.setRandom(random);
|
||||
reqDTO.setSign(sign);
|
||||
String payload = JsonUtils.toJsonString(reqDTO);
|
||||
// 1.4 输出请求
|
||||
log.info("[testDeviceRegister][请求 URL: {}]", url);
|
||||
log.info("[testDeviceRegister][请求体: {}]", payload);
|
||||
|
||||
// 2.1 发送请求
|
||||
String response = HttpUtil.post(url, payload);
|
||||
// 2.2 输出结果
|
||||
log.info("[testDeviceRegister][响应体: {}]", response);
|
||||
log.info("[testDeviceRegister][成功后可使用返回的 deviceSecret 进行一机一密认证]");
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试动态注册后使用 deviceSecret 进行认证
|
||||
* <p>
|
||||
* 此测试需要先执行 testDeviceRegister 获取 deviceSecret
|
||||
*/
|
||||
@Test
|
||||
public void testAuthAfterRegister() {
|
||||
// 1.1 构建请求
|
||||
String url = String.format("http://%s:%d/auth", SERVER_HOST, SERVER_PORT);
|
||||
// TODO 将 testDeviceRegister 返回的 deviceSecret 填入此处
|
||||
String deviceSecret = "返回的deviceSecret";
|
||||
IotDeviceAuthUtils.AuthInfo authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, deviceSecret);
|
||||
IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO()
|
||||
.setClientId(authInfo.getClientId())
|
||||
.setUsername(authInfo.getUsername())
|
||||
.setPassword(authInfo.getPassword());
|
||||
String payload = JsonUtils.toJsonString(authReqDTO);
|
||||
// 1.2 输出请求
|
||||
log.info("[testAuthAfterRegister][请求 URL: {}]", url);
|
||||
log.info("[testAuthAfterRegister][请求体: {}]", payload);
|
||||
|
||||
// 2.1 发送请求
|
||||
String response = HttpUtil.post(url, payload);
|
||||
// 2.2 输出结果
|
||||
log.info("[testAuthAfterRegister][响应体: {}]", response);
|
||||
}
|
||||
|
||||
// ===================== 网关认证测试 =====================
|
||||
|
||||
/**
|
||||
* 网关设备认证测试:获取网关设备 Token(用于后续子设备动态注册)
|
||||
*/
|
||||
@Test
|
||||
public void testGatewayAuth() {
|
||||
// 1.1 构建请求
|
||||
String url = String.format("http://%s:%d/auth", SERVER_HOST, SERVER_PORT);
|
||||
IotDeviceAuthUtils.AuthInfo authInfo = IotDeviceAuthUtils.getAuthInfo(
|
||||
GATEWAY_PRODUCT_KEY, GATEWAY_DEVICE_NAME, GATEWAY_DEVICE_SECRET);
|
||||
IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO()
|
||||
.setClientId(authInfo.getClientId())
|
||||
.setUsername(authInfo.getUsername())
|
||||
.setPassword(authInfo.getPassword());
|
||||
String payload = JsonUtils.toJsonString(authReqDTO);
|
||||
// 1.2 输出请求
|
||||
log.info("[testGatewayAuth][请求 URL: {}]", url);
|
||||
log.info("[testGatewayAuth][请求体: {}]", payload);
|
||||
|
||||
// 2.1 发送请求
|
||||
String response = HttpUtil.post(url, payload);
|
||||
// 2.2 输出结果
|
||||
log.info("[testGatewayAuth][响应体: {}]", response);
|
||||
log.info("[testGatewayAuth][请将返回的 token 复制到 GATEWAY_TOKEN 常量中]");
|
||||
}
|
||||
|
||||
// ===================== 子设备动态注册测试 =====================
|
||||
|
||||
/**
|
||||
* 子设备动态注册测试
|
||||
* <p>
|
||||
* 网关设备代理子设备进行动态注册,平台返回子设备的 deviceSecret
|
||||
* <p>
|
||||
* 注意:此接口需要网关 Token 认证
|
||||
*/
|
||||
@Test
|
||||
public void testSubDeviceRegister() {
|
||||
// 1.1 构建请求(需要网关认证)
|
||||
String url = String.format("http://%s:%d/auth/register/sub-device/%s/%s",
|
||||
SERVER_HOST, SERVER_PORT, GATEWAY_PRODUCT_KEY, GATEWAY_DEVICE_NAME);
|
||||
// 1.2 构建请求参数
|
||||
IotSubDeviceRegisterReqDTO subDevice = new IotSubDeviceRegisterReqDTO();
|
||||
subDevice.setProductKey(SUB_DEVICE_PRODUCT_KEY);
|
||||
subDevice.setDeviceName(SUB_DEVICE_NAME);
|
||||
String payload = JsonUtils.toJsonString(MapUtil.builder()
|
||||
.put("id", IdUtil.fastSimpleUUID())
|
||||
.put("method", IotDeviceMessageMethodEnum.SUB_DEVICE_REGISTER.getMethod())
|
||||
.put("version", "1.0")
|
||||
.put("params", Collections.singletonList(subDevice))
|
||||
.build());
|
||||
// 1.3 输出请求
|
||||
log.info("[testSubDeviceRegister][请求 URL: {}]", url);
|
||||
log.info("[testSubDeviceRegister][请求体: {}]", payload);
|
||||
|
||||
// 2.1 发送请求(需要网关 Token)
|
||||
try (HttpResponse httpResponse = HttpUtil.createPost(url)
|
||||
.header("Authorization", GATEWAY_TOKEN)
|
||||
.body(payload)
|
||||
.execute()) {
|
||||
// 2.2 输出结果
|
||||
log.info("[testSubDeviceRegister][响应体: {}]", httpResponse.body());
|
||||
log.info("[testSubDeviceRegister][成功后可使用返回的 deviceSecret 进行子设备认证]");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -211,11 +211,13 @@ public class IotGatewayDeviceHttpProtocolIntegrationTest {
|
||||
* 子设备动态注册测试
|
||||
* <p>
|
||||
* 网关设备代理子设备进行动态注册,平台返回子设备的 deviceSecret
|
||||
* <p>
|
||||
* 注意:此接口需要网关 Token 认证
|
||||
*/
|
||||
@Test
|
||||
public void testSubDeviceRegister() {
|
||||
// 1.1 构建请求
|
||||
String url = String.format("http://%s:%d/topic/sys/%s/%s/thing/sub/register",
|
||||
String url = String.format("http://%s:%d/auth/register/sub-device/%s/%s",
|
||||
SERVER_HOST, SERVER_PORT, GATEWAY_PRODUCT_KEY, GATEWAY_DEVICE_NAME);
|
||||
// 1.2 构建请求参数
|
||||
IotSubDeviceRegisterReqDTO subDevice = new IotSubDeviceRegisterReqDTO();
|
||||
|
||||
Reference in New Issue
Block a user