feat(iot):【网关设备:30%】增加网关设备绑定能力(优化代码),基于 optimized-pondering-dragon.md 规划

This commit is contained in:
YunaiV
2026-01-22 09:52:52 +08:00
parent 4b1dfad063
commit b4013e9a6c
9 changed files with 73 additions and 91 deletions

View File

@@ -96,35 +96,20 @@ public class IotDeviceController {
}));
}
// TODO @AI希望改成“未绑定的”。需要剔除已经绑定包括自己的
// TODO @AI不需要传递 gatewayId
// TODO @AI需要分页
@GetMapping("/bindable-sub-device-list")
@Operation(summary = "获取可绑定到网关的子设备列表")
@Parameter(name = "gatewayId", description = "网关设备编号(可选)", example = "1")
@GetMapping("/unbound-sub-device-page")
@Operation(summary = "获取未绑定网关的子设备分页")
@PreAuthorize("@ss.hasPermission('iot:device:query')")
public CommonResult<List<IotDeviceRespVO>> getBindableSubDeviceList(
@RequestParam(value = "gatewayId", required = false) Long gatewayId) {
List<IotDeviceDO> list = deviceService.getBindableSubDeviceList(gatewayId);
if (CollUtil.isEmpty(list)) {
return success(Collections.emptyList());
public CommonResult<PageResult<IotDeviceRespVO>> getUnboundSubDevicePage(@Valid IotDevicePageReqVO pageReqVO) {
PageResult<IotDeviceDO> pageResult = deviceService.getUnboundSubDevicePage(pageReqVO);
if (CollUtil.isEmpty(pageResult.getList())) {
return success(PageResult.empty());
}
// 补充产品名称
Map<Long, IotProductDO> productMap = convertMap(productService.getProductList(), IotProductDO::getId);
return success(convertList(list, device -> {
// TODO @AI可以 beanutils 转换么?
IotDeviceRespVO respVO = new IotDeviceRespVO()
.setId(device.getId())
.setDeviceName(device.getDeviceName())
.setNickname(device.getNickname())
.setProductId(device.getProductId())
.setState(device.getState())
.setGatewayId(device.getGatewayId());
MapUtils.findAndThen(productMap, device.getProductId(),
product -> respVO.setProductName(product.getName()));
return respVO;
}));
PageResult<IotDeviceRespVO> result = BeanUtils.toBean(pageResult, IotDeviceRespVO.class, device ->
MapUtils.findAndThen(productMap, device.getProductId(), product -> device.setProductName(product.getName())));
return success(result);
}
@PutMapping("/update-group")

View File

@@ -143,11 +143,13 @@ public class IotProductController {
@GetMapping("/simple-list")
@Operation(summary = "获取产品的精简信息列表", description = "主要用于前端的下拉选项")
public CommonResult<List<IotProductRespVO>> getProductSimpleList() {
List<IotProductDO> list = productService.getProductList();
return success(convertList(list, product -> // 只返回 id、name 字段
@Parameter(name = "deviceType", description = "设备类型", example = "1")
public CommonResult<List<IotProductRespVO>> getProductSimpleList(
@RequestParam(value = "deviceType", required = false) Integer deviceType) {
List<IotProductDO> list = productService.getProductList(deviceType);
return success(convertList(list, product -> // 只返回 id、name、productKey 字段
new IotProductRespVO().setId(product.getId()).setName(product.getName()).setStatus(product.getStatus())
.setDeviceType(product.getDeviceType())));
.setDeviceType(product.getDeviceType()).setProductKey(product.getProductKey())));
}
}

View File

@@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDevicePageReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import jakarta.annotation.Nullable;
import org.apache.ibatis.annotations.Mapper;
@@ -142,20 +143,20 @@ public interface IotDeviceMapper extends BaseMapperX<IotDeviceDO> {
}
/**
* 查询绑定网关的子设备列表
* <p>
* 条件:设备类型为 GATEWAY_SUB 且未绑定任何网关,或已绑定到指定网关
* 分页查询绑定网关的子设备
*
* @param gatewayId 网关设备编号(可选,用于包含已绑定到该网关的设备)
* @return 子设备列表
* @param reqVO 分页查询参数
* @return 子设备分页
*/
default List<IotDeviceDO> selectBindableSubDeviceList(@Nullable Long gatewayId) {
return selectList(new LambdaQueryWrapperX<IotDeviceDO>()
.eq(IotDeviceDO::getDeviceType, cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum.GATEWAY_SUB.getType())
.and(wrapper -> wrapper
.isNull(IotDeviceDO::getGatewayId)));
// .or()
// .eqIfPresent(IotDeviceDO::getGatewayId, gatewayId)))
default PageResult<IotDeviceDO> selectUnboundSubDevicePage(IotDevicePageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<IotDeviceDO>()
.likeIfPresent(IotDeviceDO::getDeviceName, reqVO.getDeviceName())
.likeIfPresent(IotDeviceDO::getNickname, reqVO.getNickname())
.eqIfPresent(IotDeviceDO::getProductId, reqVO.getProductId())
// 仅查询子设备 + 未绑定网关
.eq(IotDeviceDO::getDeviceType, IotProductDeviceTypeEnum.GATEWAY_SUB.getType())
.isNull(IotDeviceDO::getGatewayId)
.orderByDesc(IotDeviceDO::getId));
}
}

View File

@@ -27,6 +27,12 @@ public interface IotProductMapper extends BaseMapperX<IotProductDO> {
.orderByDesc(IotProductDO::getId));
}
default List<IotProductDO> selectList(Integer deviceType) {
return selectList(new LambdaQueryWrapperX<IotProductDO>()
.eqIfPresent(IotProductDO::getDeviceType, deviceType)
.orderByDesc(IotProductDO::getId));
}
default IotProductDO selectByProductKey(String productKey) {
return selectOne(new LambdaQueryWrapper<IotProductDO>()
.apply("LOWER(product_key) = {0}", productKey.toLowerCase()));
@@ -37,5 +43,4 @@ public interface IotProductMapper extends BaseMapperX<IotProductDO> {
.geIfPresent(IotProductDO::getCreateTime, createTime));
}
}

View File

@@ -26,19 +26,15 @@ public interface ErrorCodeConstants {
// ========== 设备 1-050-003-000 ============
ErrorCode DEVICE_NOT_EXISTS = new ErrorCode(1_050_003_000, "设备不存在");
ErrorCode DEVICE_NAME_EXISTS = new ErrorCode(1_050_003_001, "设备名称在同一产品下必须唯一");
ErrorCode DEVICE_HAS_CHILDREN = new ErrorCode(1_050_003_002, "子设备,不允许删除");
ErrorCode DEVICE_GATEWAY_HAS_SUB = new ErrorCode(1_050_003_002, "网关设备存在已绑定的子设备,不允许删除");
ErrorCode DEVICE_KEY_EXISTS = new ErrorCode(1_050_003_003, "设备标识已经存在");
ErrorCode DEVICE_GATEWAY_NOT_EXISTS = new ErrorCode(1_050_003_004, "网关设备不存在");
ErrorCode DEVICE_NOT_GATEWAY = new ErrorCode(1_050_003_005, "设备不是网关设备");
ErrorCode DEVICE_IMPORT_LIST_IS_EMPTY = new ErrorCode(1_050_003_006, "导入设备数据不能为空!");
ErrorCode DEVICE_DOWNSTREAM_FAILED_SERVER_ID_NULL = new ErrorCode(1_050_003_007, "下行设备消息失败,原因:设备未连接网关");
ErrorCode DEVICE_SERIAL_NUMBER_EXISTS = new ErrorCode(1_050_003_008, "设备序列号已存在,序列号必须全局唯一");
// TODO @AI1_050_003_009 需要提示具体的哪个设备。产品/设备,标识下
ErrorCode DEVICE_NOT_GATEWAY_SUB = new ErrorCode(1_050_003_009, "设备不是网关子设备类型,无法绑定到网关");
// TODO @AI1_050_003_009 需要提示具体的哪个设备。产品/设备,标识下
ErrorCode DEVICE_GATEWAY_BINDTO_EXISTS = new ErrorCode(1_050_003_010, "设备已绑定到其他网关,请先解绑");
// TODO @AI是不是可以删除DEVICE_GATEWAY_BINDTO_NOT_EXISTS
ErrorCode DEVICE_GATEWAY_BINDTO_NOT_EXISTS = new ErrorCode(1_050_003_011, "设备未绑定到任何网关");
ErrorCode DEVICE_NOT_GATEWAY_SUB = new ErrorCode(1_050_003_009, "设备【{}/{}】不是网关子设备类型,无法绑定到网关");
ErrorCode DEVICE_GATEWAY_BINDTO_EXISTS = new ErrorCode(1_050_003_010, "设备【{}/{}】已绑定到其他网关,请先解绑");
// ========== 产品分类 1-050-004-000 ==========
ErrorCode PRODUCT_CATEGORY_NOT_EXISTS = new ErrorCode(1_050_004_000, "产品分类不存在");

View File

@@ -73,7 +73,6 @@ public interface IotDeviceService {
*/
void updateDeviceGroup(@Valid IotDeviceUpdateGroupReqVO updateReqVO);
// TODO @AI网关设备被删除时需要查看是否有子设备绑定。如果有则不允许删除。
/**
* 删除单个设备
*
@@ -307,14 +306,12 @@ public interface IotDeviceService {
void unbindDeviceGateway(Collection<Long> ids);
/**
* 获取绑定网关的子设备列表
* <p>
* 条件:设备类型为 GATEWAY_SUB 且未绑定任何网关
* 获取绑定网关的子设备分页
*
* @param gatewayId 网关设备编号(可选,用于包含已绑定到该网关的设备
* @return 子设备列表
* @param pageReqVO 分页查询参数(仅使用 productId、deviceName、nickname
* @return 子设备分页
*/
List<IotDeviceDO> getBindableSubDeviceList(@Nullable Long gatewayId);
PageResult<IotDeviceDO> getUnboundSubDevicePage(IotDevicePageReqVO pageReqVO);
/**
* 根据网关编号获取子设备列表
@@ -324,13 +321,4 @@ public interface IotDeviceService {
*/
List<IotDeviceDO> getDeviceListByGatewayId(Long gatewayId);
// TODO @AI暂时用不到可以删除。
/**
* 根据网关编号获取子设备数量
*
* @param gatewayId 网关设备编号
* @return 子设备数量
*/
Long getDeviceCountByGatewayId(Long gatewayId);
}

View File

@@ -170,9 +170,10 @@ public class IotDeviceServiceImpl implements IotDeviceService {
public void deleteDevice(Long id) {
// 1.1 校验存在
IotDeviceDO device = validateDeviceExists(id);
// 1.2 如果是网关设备,检查是否有子设备
if (device.getGatewayId() != null && deviceMapper.selectCountByGatewayId(id) > 0) {
throw exception(DEVICE_HAS_CHILDREN);
// 1.2 如果是网关设备,检查是否有子设备绑定
if (IotProductDeviceTypeEnum.isGateway(device.getDeviceType())
&& deviceMapper.selectCountByGatewayId(id) > 0) {
throw exception(DEVICE_GATEWAY_HAS_SUB);
}
// 2. 删除设备
@@ -193,10 +194,11 @@ public class IotDeviceServiceImpl implements IotDeviceService {
if (CollUtil.isEmpty(devices)) {
return;
}
// 1.2 校验网关设备是否存在
// 1.2 如果是网关设备,检查是否有子设备绑定
for (IotDeviceDO device : devices) {
if (device.getGatewayId() != null && deviceMapper.selectCountByGatewayId(device.getId()) > 0) {
throw exception(DEVICE_HAS_CHILDREN);
if (IotProductDeviceTypeEnum.isGateway(device.getDeviceType())
&& deviceMapper.selectCountByGatewayId(device.getId()) > 0) {
throw exception(DEVICE_GATEWAY_HAS_SUB);
}
}
@@ -522,34 +524,29 @@ public class IotDeviceServiceImpl implements IotDeviceService {
if (CollUtil.isEmpty(ids)) {
return;
}
// TODO @AI校验应该是 1.1、1.2 统一风格;
// 1. 校验网关设备存在且类型正确
// 1.1 校验网关设备存在且类型正确
validateGatewayDeviceExists(gatewayId);
// 2. 校验并绑定每个子设备
// 1.2 校验子设备存在
List<IotDeviceDO> devices = deviceMapper.selectByIds(ids);
if (devices.size() != ids.size()) {
throw exception(DEVICE_NOT_EXISTS);
}
List<IotDeviceDO> updateList = new ArrayList<>();
// 1.3 校验每个设备是否可绑定
for (IotDeviceDO device : devices) {
// 2.1 校验是否为子设备类型
if (!IotProductDeviceTypeEnum.isGatewaySub(device.getDeviceType())) {
throw exception(DEVICE_NOT_GATEWAY_SUB);
throw exception(DEVICE_NOT_GATEWAY_SUB, device.getProductKey(), device.getDeviceName());
}
// 2.2 校验是否已绑定其他网关
if (device.getGatewayId() != null && !device.getGatewayId().equals(gatewayId)) {
throw exception(DEVICE_GATEWAY_BINDTO_EXISTS);
throw exception(DEVICE_GATEWAY_BINDTO_EXISTS, device.getProductKey(), device.getDeviceName());
}
updateList.add(new IotDeviceDO().setId(device.getId()).setGatewayId(gatewayId));
}
// 3. 批量更新数据库
// TODO @AIList<IotDeviceDO> updateList 直接 convertList不用上面 for 里面搞;校验是校验,插入是插入;
// 2. 批量更新数据库
List<IotDeviceDO> updateList = convertList(devices, device ->
new IotDeviceDO().setId(device.getId()).setGatewayId(gatewayId));
deviceMapper.updateBatch(updateList);
// 4. 清空对应缓存
// 3. 清空对应缓存
deleteDeviceCache(devices);
}
@@ -579,8 +576,8 @@ public class IotDeviceServiceImpl implements IotDeviceService {
}
@Override
public List<IotDeviceDO> getBindableSubDeviceList(@Nullable Long gatewayId) {
return deviceMapper.selectBindableSubDeviceList(gatewayId);
public PageResult<IotDeviceDO> getUnboundSubDevicePage(IotDevicePageReqVO pageReqVO) {
return deviceMapper.selectUnboundSubDevicePage(pageReqVO);
}
@Override
@@ -588,11 +585,6 @@ public class IotDeviceServiceImpl implements IotDeviceService {
return deviceMapper.selectListByGatewayId(gatewayId);
}
@Override
public Long getDeviceCountByGatewayId(Long gatewayId) {
return deviceMapper.selectCountByGatewayId(gatewayId);
}
private IotDeviceServiceImpl getSelf() {
return SpringUtil.getBean(getClass());
}

View File

@@ -105,6 +105,14 @@ public interface IotProductService {
*/
List<IotProductDO> getProductList();
/**
* 根据设备类型获得产品列表
*
* @param deviceType 设备类型(可选)
* @return 产品列表
*/
List<IotProductDO> getProductList(@Nullable Integer deviceType);
/**
* 获得产品数量
*

View File

@@ -157,6 +157,11 @@ public class IotProductServiceImpl implements IotProductService {
return productMapper.selectList();
}
@Override
public List<IotProductDO> getProductList(Integer deviceType) {
return productMapper.selectList(deviceType);
}
@Override
public Long getProductCount(LocalDateTime createTime) {
return productMapper.selectCountByCreateTime(createTime);