【功能新增】IoT:设备拓扑图的添加

This commit is contained in:
YunaiV 2025-02-08 21:44:49 +08:00
parent 4254c06c37
commit bc9b3715b1
17 changed files with 161 additions and 18 deletions

View File

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

View File

@ -7,6 +7,8 @@ import java.util.Map;
/** /**
* IoT 设备事件上报 Request DTO * IoT 设备事件上报 Request DTO
*
* @author 芋道源码
*/ */
@Data @Data
public class IotDeviceEventReportReqDTO extends IotDeviceUpstreamAbstractReqDTO { public class IotDeviceEventReportReqDTO extends IotDeviceUpstreamAbstractReqDTO {

View File

@ -7,6 +7,8 @@ import java.util.Map;
/** /**
* IoT 设备属性上报 Request DTO * IoT 设备属性上报 Request DTO
*
* @author 芋道源码
*/ */
@Data @Data
public class IotDevicePropertyReportReqDTO extends IotDeviceUpstreamAbstractReqDTO { public class IotDevicePropertyReportReqDTO extends IotDeviceUpstreamAbstractReqDTO {

View File

@ -13,6 +13,7 @@ import java.util.List;
@Data @Data
public class IotDeviceRegisterSubReqDTO extends IotDeviceUpstreamAbstractReqDTO { public class IotDeviceRegisterSubReqDTO extends IotDeviceUpstreamAbstractReqDTO {
// TODO @芋艿看看要不要优化命名
/** /**
* 子设备数组 * 子设备数组
*/ */

View File

@ -7,6 +7,8 @@ import lombok.Data;
/** /**
* IoT 设备状态更新 Request DTO * IoT 设备状态更新 Request DTO
*
* @author 芋道源码
*/ */
@Data @Data
public class IotDeviceStateUpdateReqDTO extends IotDeviceUpstreamAbstractReqDTO { public class IotDeviceStateUpdateReqDTO extends IotDeviceUpstreamAbstractReqDTO {

View File

@ -0,0 +1,43 @@
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
*/
@Data
public class IotDeviceTopologyAddReqDTO extends IotDeviceUpstreamAbstractReqDTO {
// TODO @芋艿看看要不要优化命名
/**
* 子设备数组
*/
@NotEmpty(message = "子设备不能为空")
private List<IotDeviceRegisterSubReqDTO.Device> params;
/**
* 设备信息
*/
@Data
public static class Device {
/**
* 产品标识
*/
@NotEmpty(message = "产品标识不能为空")
private String productKey;
/**
* 设备名称
*/
@NotEmpty(message = "设备名称不能为空")
private String deviceName;
// TODO @芋艿阿里云还有 sign 签名
}
}

View File

@ -30,7 +30,10 @@ public enum IotDeviceMessageIdentifierEnum {
REGISTER_REGISTER("register"), // 上行 REGISTER_REGISTER("register"), // 上行
REGISTER_REGISTER_SUB("register_sub"), // 上行 REGISTER_REGISTER_SUB("register_sub"), // 上行
REGISTER_UNREGISTER_SUB("unregister_sub"),; // 下行 REGISTER_UNREGISTER_SUB("unregister_sub"), // 下行
TOPOLOGY_ADD("topology_add"), // 下行;
;
/** /**
* 标志符 * 标志符

View File

@ -19,7 +19,8 @@ public enum IotDeviceMessageTypeEnum implements ArrayValuable<String> {
SERVICE("service"), // 设备服务可参考 https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services 设备属性事件服务 SERVICE("service"), // 设备服务可参考 https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services 设备属性事件服务
CONFIG("config"), // 设备配置可参考 https://help.aliyun.com/zh/iot/user-guide/remote-configuration-1 远程配置 CONFIG("config"), // 设备配置可参考 https://help.aliyun.com/zh/iot/user-guide/remote-configuration-1 远程配置
OTA("ota"), // 设备 OTA可参考 https://help.aliyun.com/zh/iot/user-guide/ota-update OTA 升级 OTA("ota"), // 设备 OTA可参考 https://help.aliyun.com/zh/iot/user-guide/ota-update OTA 升级
REGISTER("register"),; // 设备注册可参考 https://help.aliyun.com/zh/iot/user-guide/register-devices 设备身份注册 REGISTER("register"), // 设备注册可参考 https://help.aliyun.com/zh/iot/user-guide/register-devices 设备身份注册
TOPOLOGY("topology"),; // 设备拓扑可参考 https://help.aliyun.com/zh/iot/user-guide/manage-topological-relationships 设备拓扑
public static final String[] ARRAYS = Arrays.stream(values()).map(IotDeviceMessageTypeEnum::getType).toArray(String[]::new); public static final String[] ARRAYS = Arrays.stream(values()).map(IotDeviceMessageTypeEnum::getType).toArray(String[]::new);

View File

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

View File

@ -61,6 +61,8 @@ public class IotDeviceController {
return success(true); return success(true);
} }
// TODO @芋艿参考阿里云1绑定网关2解绑网关
@PutMapping("/update-group") @PutMapping("/update-group")
@Operation(summary = "更新设备分组") @Operation(summary = "更新设备分组")
@PreAuthorize("@ss.hasPermission('iot:device:update')") @PreAuthorize("@ss.hasPermission('iot:device:update')")

View File

@ -9,8 +9,9 @@ import lombok.NoArgsConstructor;
import java.time.LocalDateTime; import java.time.LocalDateTime;
// TODO @芋艿参考阿里云的物模型优化 IoT 上下行消息的设计尽量保持一致渐进式不要一口气
/** /**
* 设备消息 * IoT 设备消息
*/ */
@Data @Data
@NoArgsConstructor @NoArgsConstructor

View File

@ -81,8 +81,7 @@ public class IotDeviceServiceImpl implements IotDeviceService {
public IotDeviceDO createDevice(String productKey, String deviceName, Long gatewayId) { public IotDeviceDO createDevice(String productKey, String deviceName, Long gatewayId) {
String deviceKey = generateDeviceKey(); String deviceKey = generateDeviceKey();
// 1.1 校验产品是否存在 // 1.1 校验产品是否存在
IotProductDO product = TenantUtils.executeIgnore(() -> IotProductDO product = TenantUtils.executeIgnore(() -> productService.getProductByProductKey(productKey));
productService.getProductByProductKey(productKey));
if (product == null) { if (product == null) {
throw exception(PRODUCT_NOT_EXISTS); throw exception(PRODUCT_NOT_EXISTS);
} }
@ -120,7 +119,8 @@ public class IotDeviceServiceImpl implements IotDeviceService {
} }
private void initDevice(IotDeviceDO device, IotProductDO product) { private void initDevice(IotDeviceDO device, IotProductDO product) {
device.setProductId(product.getId()).setProductKey(product.getProductKey()).setDeviceType(product.getDeviceType()); device.setProductId(product.getId()).setProductKey(product.getProductKey())
.setDeviceType(product.getDeviceType());
// 生成并设置必要的字段 // 生成并设置必要的字段
// TODO @芋艿各种 mqtt 是不是可以简化 // TODO @芋艿各种 mqtt 是不是可以简化
device.setDeviceSecret(generateDeviceSecret()) device.setDeviceSecret(generateDeviceSecret())
@ -363,7 +363,7 @@ public class IotDeviceServiceImpl implements IotDeviceService {
// 2.1.1 校验字段是否符合要求 // 2.1.1 校验字段是否符合要求
try { try {
ValidationUtils.validate(importDevice); ValidationUtils.validate(importDevice);
} catch (ConstraintViolationException ex){ } catch (ConstraintViolationException ex) {
respVO.getFailureDeviceNames().put(importDevice.getDeviceName(), ex.getMessage()); respVO.getFailureDeviceNames().put(importDevice.getDeviceName(), ex.getMessage());
return; return;
} }
@ -427,7 +427,8 @@ public class IotDeviceServiceImpl implements IotDeviceService {
} }
@CacheEvict(value = RedisKeyConstants.DEVICE, key = "#device.productKey + '_' + #device.deviceName") @CacheEvict(value = RedisKeyConstants.DEVICE, key = "#device.productKey + '_' + #device.deviceName")
public void deleteDeviceCache0(IotDeviceDO device) {} public void deleteDeviceCache0(IotDeviceDO device) {
}
private IotDeviceServiceImpl getSelf() { private IotDeviceServiceImpl getSelf() {
return SpringUtil.getBean(getClass()); return SpringUtil.getBean(getClass());

View File

@ -88,6 +88,7 @@ public class IotDeviceDownstreamServiceImpl implements IotDeviceDownstreamServic
if (Objects.equals(downstreamReqVO.getType(), IotDeviceMessageTypeEnum.OTA.getType())) { if (Objects.equals(downstreamReqVO.getType(), IotDeviceMessageTypeEnum.OTA.getType())) {
return otaUpgrade(downstreamReqVO, device, parentDevice); return otaUpgrade(downstreamReqVO, device, parentDevice);
} }
// TODO @芋艿取消设备的网关的时要不要下发 REGISTER_UNREGISTER_SUB
throw new IllegalArgumentException("不支持的下行消息类型:" + downstreamReqVO); throw new IllegalArgumentException("不支持的下行消息类型:" + downstreamReqVO);
} }

View File

@ -55,4 +55,11 @@ public interface IotDeviceUpstreamService {
*/ */
void registerSubDevice(IotDeviceRegisterSubReqDTO registerReqDTO); void registerSubDevice(IotDeviceRegisterSubReqDTO registerReqDTO);
/**
* 添加设备拓扑
*
* @param addReqDTO 添加设备拓扑 DTO
*/
void addDeviceTopology(IotDeviceTopologyAddReqDTO addReqDTO);
} }

View File

@ -220,6 +220,7 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService {
if (CollUtil.isNotEmpty(registerReqDTO.getParams())) { if (CollUtil.isNotEmpty(registerReqDTO.getParams())) {
registerReqDTO.getParams().forEach(subDevice -> registerDevice0( registerReqDTO.getParams().forEach(subDevice -> registerDevice0(
subDevice.getProductKey(), subDevice.getDeviceName(), device.getId(), registerReqDTO)); subDevice.getProductKey(), subDevice.getDeviceName(), device.getId(), registerReqDTO));
// TODO @芋艿后续要处理每个设备是否成功
} }
// 3. 发送设备消息 // 3. 发送设备消息
@ -230,6 +231,52 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService {
sendDeviceMessage(message, device); sendDeviceMessage(message, device);
} }
@Override
public void addDeviceTopology(IotDeviceTopologyAddReqDTO addReqDTO) {
// 1.1 获得设备
log.info("[addDeviceTopology][添加设备拓扑: {}]", addReqDTO);
IotDeviceDO device = deviceService.getDeviceByProductKeyAndDeviceNameFromCache(
addReqDTO.getProductKey(), addReqDTO.getDeviceName());
if (device == null) {
log.error("[addDeviceTopology][设备({}/{}) 不存在]",
addReqDTO.getProductKey(), addReqDTO.getDeviceName());
return;
}
if (!IotProductDeviceTypeEnum.isGateway(device.getDeviceType())) {
log.error("[addDeviceTopology][设备({}/{}) 不是网关设备({}),无法进行拓扑添加]",
addReqDTO.getProductKey(), addReqDTO.getDeviceName(), device);
return;
}
// 1.2 记录设备的最后时间
updateDeviceLastTime(device, addReqDTO);
// 2. 处理拓扑
if (CollUtil.isNotEmpty(addReqDTO.getParams())) {
TenantUtils.execute(device.getTenantId(), () -> {
addReqDTO.getParams().forEach(subDevice -> {
IotDeviceDO subDeviceDO = deviceService.getDeviceByProductKeyAndDeviceNameFromCache(
subDevice.getProductKey(), subDevice.getDeviceName());
// TODO @芋艿后续要处理每个设备是否成功
if (subDeviceDO == null) {
log.error("[addDeviceTopology][子设备({}/{}) 不存在]",
subDevice.getProductKey(), subDevice.getDeviceName());
return;
}
deviceService.updateDeviceGateway(subDeviceDO.getId(), device.getId());
log.info("[addDeviceTopology][子设备({}/{}) 添加到网关设备({}) 成功]",
subDevice.getProductKey(), subDevice.getDeviceName(), device);
});
});
}
// 3. 发送设备消息
IotDeviceMessage message = BeanUtils.toBean(addReqDTO, IotDeviceMessage.class)
.setType(IotDeviceMessageTypeEnum.TOPOLOGY.getType())
.setIdentifier(IotDeviceMessageIdentifierEnum.TOPOLOGY_ADD.getIdentifier())
.setData(addReqDTO.getParams());
sendDeviceMessage(message, device);
}
private void updateDeviceLastTime(IotDeviceDO device, IotDeviceUpstreamAbstractReqDTO reqDTO) { private void updateDeviceLastTime(IotDeviceDO device, IotDeviceUpstreamAbstractReqDTO reqDTO) {
// 1. 异步记录设备与插件实例的映射 // 1. 异步记录设备与插件实例的映射
pluginInstanceService.updateDevicePluginInstanceProcessIdAsync(device.getDeviceKey(), reqDTO.getProcessId()); pluginInstanceService.updateDevicePluginInstanceProcessIdAsync(device.getDeviceKey(), reqDTO.getProcessId());

View File

@ -2,10 +2,7 @@ package cn.iocoder.yudao.module.iot.plugin.common.upstream;
import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi; import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceEventReportReqDTO; import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.*;
import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDevicePropertyReportReqDTO;
import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceStateUpdateReqDTO;
import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotPluginInstanceHeartbeatReqDTO;
import cn.iocoder.yudao.module.iot.plugin.common.config.IotPluginCommonProperties; import cn.iocoder.yudao.module.iot.plugin.common.config.IotPluginCommonProperties;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -42,6 +39,24 @@ public class IotDeviceUpstreamClient implements IotDeviceUpstreamApi {
return doPost(url, reportReqDTO); return doPost(url, reportReqDTO);
} }
// TODO @芋艿待实现
@Override
public CommonResult<Boolean> registerDevice(IotDeviceRegisterReqDTO registerReqDTO) {
return null;
}
// TODO @芋艿待实现
@Override
public CommonResult<Boolean> registerSubDevice(IotDeviceRegisterSubReqDTO registerReqDTO) {
return null;
}
// TODO @芋艿待实现
@Override
public CommonResult<Boolean> addDeviceTopology(IotDeviceTopologyAddReqDTO addReqDTO) {
return null;
}
@Override @Override
public CommonResult<Boolean> reportDeviceProperty(IotDevicePropertyReportReqDTO reportReqDTO) { public CommonResult<Boolean> reportDeviceProperty(IotDevicePropertyReportReqDTO reportReqDTO) {
String url = properties.getUpstreamUrl() + URL_PREFIX + "/report-property"; String url = properties.getUpstreamUrl() + URL_PREFIX + "/report-property";