【功能新增】IoT:设备注册 sub register 逻辑

This commit is contained in:
YunaiV 2025-02-08 20:56:16 +08:00
parent 5f7bb8041f
commit 4254c06c37
11 changed files with 176 additions and 48 deletions

View File

@ -53,6 +53,15 @@ public interface IotDeviceUpstreamApi {
@PostMapping(PREFIX + "/register")
CommonResult<Boolean> registerDevice(@Valid @RequestBody IotDeviceRegisterReqDTO registerReqDTO);
// TODO @芋艿这个需要 plugins 接入下
/**
* 注册子设备
*
* @param registerReqDTO 注册子设备 DTO
*/
@PostMapping(PREFIX + "/register-sub")
CommonResult<Boolean> registerSubDevice(@Valid @RequestBody IotDeviceRegisterSubReqDTO registerReqDTO);
// ========== 插件相关 ==========
/**

View File

@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.iot.api.device.dto.control.upstream;
import lombok.Data;
/**
* IoT 设备注册注册 Request DTO
* IoT 设备注册自己 Request DTO
*
* @author 芋道源码
*/

View File

@ -0,0 +1,42 @@
package cn.iocoder.yudao.module.iot.api.device.dto.control.upstream;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import java.util.List;
/**
* IoT 设备注册子设备 Request DTO
*
* @author 芋道源码
*/
@Data
public class IotDeviceRegisterSubReqDTO extends IotDeviceUpstreamAbstractReqDTO {
/**
* 子设备数组
*/
@NotEmpty(message = "子设备不能为空")
private List<Device> params;
/**
* 设备信息
*/
@Data
public static class Device {
/**
* 产品标识
*/
@NotEmpty(message = "产品标识不能为空")
private String productKey;
/**
* 设备名称
*/
@NotEmpty(message = "设备名称不能为空")
private String deviceName;
}
}

View File

@ -29,8 +29,8 @@ public enum IotDeviceMessageIdentifierEnum {
OTA_REPORT("report"), // 上行
REGISTER_REGISTER("register"), // 上行
REGISTER_SUB_REGISTER("sub_register"), // 上行
REGISTER_SUB_UNREGISTER("sub_unregister"),; // 下行
REGISTER_REGISTER_SUB("register_sub"), // 上行
REGISTER_UNREGISTER_SUB("unregister_sub"),; // 下行
/**
* 标志符

View File

@ -46,4 +46,14 @@ public enum IotProductDeviceTypeEnum implements ArrayValuable<Integer> {
return GATEWAY.getType().equals(type);
}
/**
* 判断是否是网关子设备
*
* @param type 类型
* @return 是否是网关子设备
*/
public static boolean isGatewaySub(Integer type) {
return GATEWAY_SUB.getType().equals(type);
}
}

View File

@ -49,6 +49,12 @@ public class IoTDeviceUpstreamApiImpl implements IotDeviceUpstreamApi {
return success(true);
}
@Override
public CommonResult<Boolean> registerSubDevice(IotDeviceRegisterSubReqDTO registerReqDTO) {
deviceUpstreamService.registerSubDevice(registerReqDTO);
return success(true);
}
// ========== 插件相关 ==========
@Override

View File

@ -1,7 +1,6 @@
package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Size;
import lombok.Data;
@ -18,8 +17,7 @@ public class IotDeviceSaveReqVO {
@Size(max = 50, message = "设备编号长度不能超过 50 个字符")
private String deviceKey;
@Schema(description = "设备名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
@NotEmpty(message = "设备名称不能为空")
@Schema(description = "设备名称", requiredMode = Schema.RequiredMode.AUTO, example = "王五")
private String deviceName;
@Schema(description = "备注名称", example = "张三")

View File

@ -30,10 +30,12 @@ public interface IotDeviceService {
*
* @param productKey 产品标识
* @param deviceName 设备名称
* @param gatewayId 网关设备 ID
* @return 设备
*/
IotDeviceDO createDevice(@NotEmpty(message = "产品标识不能为空") String productKey,
@NotEmpty(message = "设备名称不能为空") String deviceName);
@NotEmpty(message = "设备名称不能为空") String deviceName,
Long gatewayId);
/**
* 更新设备
@ -42,6 +44,17 @@ public interface IotDeviceService {
*/
void updateDevice(@Valid IotDeviceSaveReqVO updateReqVO);
// TODO @芋艿先这么实现未来看情况要不要自己实现
/**
* 更新设备的所属网关
*
* @param id 编号
* @param gatewayId 网关设备 ID
*/
default void updateDeviceGateway(Long id, Long gatewayId) {
updateDevice(new IotDeviceSaveReqVO().setId(id).setGatewayId(gatewayId));
}
/**
* 更新设备状态
*

View File

@ -64,22 +64,10 @@ public class IotDeviceServiceImpl implements IotDeviceService {
if (product == null) {
throw exception(PRODUCT_NOT_EXISTS);
}
// 1.2 校验设备标识是否唯一
TenantUtils.executeIgnore(() -> {
if (deviceMapper.selectByDeviceKey(createReqVO.getDeviceKey()) != null) {
throw exception(PRODUCT_KEY_EXISTS);
}
});
// 1.3 校验设备名称在同一产品下是否唯一
if (deviceMapper.selectByProductKeyAndDeviceName(product.getProductKey(), createReqVO.getDeviceName()) != null) {
throw exception(DEVICE_NAME_EXISTS);
}
// 1.4 校验父设备是否为合法网关
if (IotProductDeviceTypeEnum.isGateway(product.getDeviceType())
&& createReqVO.getGatewayId() != null) {
validateGatewayDeviceExists(createReqVO.getGatewayId());
}
// 1.5 校验分组存在
// 1.2 统一校验
validateCreateDeviceParam(product.getProductKey(), createReqVO.getDeviceName(), createReqVO.getDeviceKey(),
createReqVO.getGatewayId(), product);
// 1.3 校验分组存在
deviceGroupService.validateDeviceGroupExists(createReqVO.getGroupIds());
// 2. 插入到数据库
@ -90,34 +78,47 @@ public class IotDeviceServiceImpl implements IotDeviceService {
}
@Override
public IotDeviceDO createDevice(String productKey, String deviceName) {
public IotDeviceDO createDevice(String productKey, String deviceName, Long gatewayId) {
String deviceKey = generateDeviceKey();
// 1.1 校验产品是否存在
IotProductDO product = TenantUtils.executeIgnore(() ->
productService.getProductByProductKey(productKey));
if (product == null) {
throw exception(PRODUCT_NOT_EXISTS);
}
// 1.2 校验设备标识是否唯一
String deviceKey = generateDeviceKey();
TenantUtils.executeIgnore(() -> {
if (deviceMapper.selectByDeviceKey(deviceKey) != null) {
throw exception(PRODUCT_KEY_EXISTS);
}
});
return TenantUtils.execute(product.getTenantId(), () -> {
// 1.3 校验设备名称在同一产品下是否唯一
if (deviceMapper.selectByProductKeyAndDeviceName(product.getProductKey(), deviceName) != null) {
throw exception(DEVICE_NAME_EXISTS);
}
// 1.2 校验设备名称在同一产品下是否唯一
validateCreateDeviceParam(productKey, deviceName, deviceKey, gatewayId, product);
// 2. 插入到数据库
IotDeviceDO device = new IotDeviceDO().setDeviceName(deviceName).setDeviceKey(deviceKey);
IotDeviceDO device = new IotDeviceDO().setDeviceName(deviceName).setDeviceKey(deviceKey)
.setGatewayId(gatewayId);
initDevice(device, product);
deviceMapper.insert(device);
return device;
});
}
private void validateCreateDeviceParam(String productKey, String deviceName, String deviceKey,
Long gatewayId, IotProductDO product) {
TenantUtils.executeIgnore(() -> {
// 校验设备名称在同一产品下是否唯一
if (deviceMapper.selectByProductKeyAndDeviceName(productKey, deviceName) != null) {
throw exception(DEVICE_NAME_EXISTS);
}
// 校验设备标识是否唯一
if (deviceMapper.selectByDeviceKey(deviceKey) != null) {
throw exception(DEVICE_KEY_EXISTS);
}
});
// 校验父设备是否为合法网关
if (IotProductDeviceTypeEnum.isGatewaySub(product.getDeviceType())
&& gatewayId != null) {
validateGatewayDeviceExists(gatewayId);
}
}
private void initDevice(IotDeviceDO device, IotProductDO product) {
device.setProductId(product.getId()).setProductKey(product.getProductKey()).setDeviceType(product.getDeviceType());
// 生成并设置必要的字段
@ -136,7 +137,7 @@ public class IotDeviceServiceImpl implements IotDeviceService {
// 1.1 校验存在
IotDeviceDO device = validateDeviceExists(updateReqVO.getId());
// 1.2 校验父设备是否为合法网关
if (IotProductDeviceTypeEnum.isGateway(device.getDeviceType())
if (IotProductDeviceTypeEnum.isGatewaySub(device.getDeviceType())
&& updateReqVO.getGatewayId() != null) {
validateGatewayDeviceExists(updateReqVO.getGatewayId());
}

View File

@ -1,9 +1,6 @@
package cn.iocoder.yudao.module.iot.service.device.control;
import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceEventReportReqDTO;
import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDevicePropertyReportReqDTO;
import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceRegisterReqDTO;
import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceStateUpdateReqDTO;
import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.*;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.control.IotDeviceUpstreamReqVO;
import jakarta.validation.Valid;
@ -51,4 +48,11 @@ public interface IotDeviceUpstreamService {
*/
void registerDevice(IotDeviceRegisterReqDTO registerReqDTO);
/**
* 注册子设备
*
* @param registerReqDTO 注册子设备 DTO
*/
void registerSubDevice(IotDeviceRegisterSubReqDTO registerReqDTO);
}

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.iot.service.device.control;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjUtil;
@ -13,6 +14,7 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.enums.device.IotDeviceMessageIdentifierEnum;
import cn.iocoder.yudao.module.iot.enums.device.IotDeviceMessageTypeEnum;
import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStateEnum;
import cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum;
import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.mq.producer.device.IotDeviceProducer;
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
@ -164,20 +166,30 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService {
@Override
public void registerDevice(IotDeviceRegisterReqDTO registerReqDTO) {
// 1.1 注册设备
log.info("[registerDevice][注册设备: {}]", registerReqDTO);
IotDeviceDO device = deviceService.getDeviceByProductKeyAndDeviceNameFromCache(
registerReqDTO.getProductKey(), registerReqDTO.getDeviceName());
boolean register = device == null;
registerDevice0(registerReqDTO.getProductKey(), registerReqDTO.getDeviceName(), null, registerReqDTO);
}
private void registerDevice0(String productKey, String deviceName, Long gatewayId,
IotDeviceUpstreamAbstractReqDTO registerReqDTO) {
// 1.1 注册设备
IotDeviceDO device = deviceService.getDeviceByProductKeyAndDeviceNameFromCache(productKey, deviceName);
boolean registerNew = device == null;
if (device == null) {
device = deviceService.createDevice(registerReqDTO.getProductKey(), registerReqDTO.getDeviceName());
log.info("[registerDevice][请求({}) 成功注册设备({})]", registerReqDTO, device);
device = deviceService.createDevice(productKey, deviceName, gatewayId);
log.info("[registerDevice0][消息({}) 设备({}/{}) 成功注册]", registerReqDTO, productKey, device);
} else if (gatewayId != null && ObjUtil.notEqual(device.getGatewayId(), gatewayId)) {
Long deviceId = device.getId();
TenantUtils.execute(device.getTenantId(),
() -> deviceService.updateDeviceGateway(deviceId, gatewayId));
log.info("[registerDevice0][消息({}) 设备({}/{}) 更新网关设备编号({})]",
registerReqDTO, productKey, device, gatewayId);
}
// 1.2 记录设备的最后时间
updateDeviceLastTime(device, registerReqDTO);
// 2. 发送设备消息
if (register) {
if (registerNew) {
IotDeviceMessage message = BeanUtils.toBean(registerReqDTO, IotDeviceMessage.class)
.setType(IotDeviceMessageTypeEnum.REGISTER.getType())
.setIdentifier(IotDeviceMessageIdentifierEnum.REGISTER_REGISTER.getIdentifier());
@ -185,6 +197,39 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService {
}
}
@Override
public void registerSubDevice(IotDeviceRegisterSubReqDTO registerReqDTO) {
// 1.1 注册子设备
log.info("[registerSubDevice][注册子设备: {}]", registerReqDTO);
IotDeviceDO device = deviceService.getDeviceByProductKeyAndDeviceNameFromCache(
registerReqDTO.getProductKey(), registerReqDTO.getDeviceName());
if (device == null) {
log.error("[registerSubDevice][设备({}/{}) 不存在]",
registerReqDTO.getProductKey(), registerReqDTO.getDeviceName());
return;
}
if (!IotProductDeviceTypeEnum.isGateway(device.getDeviceType())) {
log.error("[registerSubDevice][设备({}/{}) 不是网关设备({}),无法进行注册]",
registerReqDTO.getProductKey(), registerReqDTO.getDeviceName(), device);
return;
}
// 1.2 记录设备的最后时间
updateDeviceLastTime(device, registerReqDTO);
// 2. 处理子设备
if (CollUtil.isNotEmpty(registerReqDTO.getParams())) {
registerReqDTO.getParams().forEach(subDevice -> registerDevice0(
subDevice.getProductKey(), subDevice.getDeviceName(), device.getId(), registerReqDTO));
}
// 3. 发送设备消息
IotDeviceMessage message = BeanUtils.toBean(registerReqDTO, IotDeviceMessage.class)
.setType(IotDeviceMessageTypeEnum.REGISTER.getType())
.setIdentifier(IotDeviceMessageIdentifierEnum.REGISTER_REGISTER_SUB.getIdentifier())
.setData(registerReqDTO.getParams());
sendDeviceMessage(message, device);
}
private void updateDeviceLastTime(IotDeviceDO device, IotDeviceUpstreamAbstractReqDTO reqDTO) {
// 1. 异步记录设备与插件实例的映射
pluginInstanceService.updateDevicePluginInstanceProcessIdAsync(device.getDeviceKey(), reqDTO.getProcessId());