Commit b4013e9a6c057f18dd42cc5ef867af3db730ffa4

Authored by YunaiV
1 parent 4b1dfad0

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

yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.java
... ... @@ -96,35 +96,20 @@ public class IotDeviceController {
96 96 }));
97 97 }
98 98  
99   - // TODO @AI:希望改成“未绑定的”。需要剔除已经绑定,包括自己的;
100   - // TODO @AI:不需要传递 gatewayId;
101   - // TODO @AI:需要分页;
102   - @GetMapping("/bindable-sub-device-list")
103   - @Operation(summary = "获取可绑定到网关的子设备列表")
104   - @Parameter(name = "gatewayId", description = "网关设备编号(可选)", example = "1")
  99 + @GetMapping("/unbound-sub-device-page")
  100 + @Operation(summary = "获取未绑定网关的子设备分页")
105 101 @PreAuthorize("@ss.hasPermission('iot:device:query')")
106   - public CommonResult<List<IotDeviceRespVO>> getBindableSubDeviceList(
107   - @RequestParam(value = "gatewayId", required = false) Long gatewayId) {
108   - List<IotDeviceDO> list = deviceService.getBindableSubDeviceList(gatewayId);
109   - if (CollUtil.isEmpty(list)) {
110   - return success(Collections.emptyList());
  102 + public CommonResult<PageResult<IotDeviceRespVO>> getUnboundSubDevicePage(@Valid IotDevicePageReqVO pageReqVO) {
  103 + PageResult<IotDeviceDO> pageResult = deviceService.getUnboundSubDevicePage(pageReqVO);
  104 + if (CollUtil.isEmpty(pageResult.getList())) {
  105 + return success(PageResult.empty());
111 106 }
112 107  
113 108 // 补充产品名称
114 109 Map<Long, IotProductDO> productMap = convertMap(productService.getProductList(), IotProductDO::getId);
115   - return success(convertList(list, device -> {
116   - // TODO @AI:可以 beanutils 转换么?
117   - IotDeviceRespVO respVO = new IotDeviceRespVO()
118   - .setId(device.getId())
119   - .setDeviceName(device.getDeviceName())
120   - .setNickname(device.getNickname())
121   - .setProductId(device.getProductId())
122   - .setState(device.getState())
123   - .setGatewayId(device.getGatewayId());
124   - MapUtils.findAndThen(productMap, device.getProductId(),
125   - product -> respVO.setProductName(product.getName()));
126   - return respVO;
127   - }));
  110 + PageResult<IotDeviceRespVO> result = BeanUtils.toBean(pageResult, IotDeviceRespVO.class, device ->
  111 + MapUtils.findAndThen(productMap, device.getProductId(), product -> device.setProductName(product.getName())));
  112 + return success(result);
128 113 }
129 114  
130 115 @PutMapping("/update-group")
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/IotProductController.java
... ... @@ -143,11 +143,13 @@ public class IotProductController {
143 143  
144 144 @GetMapping("/simple-list")
145 145 @Operation(summary = "获取产品的精简信息列表", description = "主要用于前端的下拉选项")
146   - public CommonResult<List<IotProductRespVO>> getProductSimpleList() {
147   - List<IotProductDO> list = productService.getProductList();
148   - return success(convertList(list, product -> // 只返回 id、name 字段
  146 + @Parameter(name = "deviceType", description = "设备类型", example = "1")
  147 + public CommonResult<List<IotProductRespVO>> getProductSimpleList(
  148 + @RequestParam(value = "deviceType", required = false) Integer deviceType) {
  149 + List<IotProductDO> list = productService.getProductList(deviceType);
  150 + return success(convertList(list, product -> // 只返回 id、name、productKey 字段
149 151 new IotProductRespVO().setId(product.getId()).setName(product.getName()).setStatus(product.getStatus())
150   - .setDeviceType(product.getDeviceType())));
  152 + .setDeviceType(product.getDeviceType()).setProductKey(product.getProductKey())));
151 153 }
152 154  
153 155 }
154 156 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceMapper.java
... ... @@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
6 6 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
7 7 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDevicePageReqVO;
8 8 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
  9 +import cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum;
9 10 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
10 11 import jakarta.annotation.Nullable;
11 12 import org.apache.ibatis.annotations.Mapper;
... ... @@ -142,20 +143,20 @@ public interface IotDeviceMapper extends BaseMapperX&lt;IotDeviceDO&gt; {
142 143 }
143 144  
144 145 /**
145   - * 查询可绑定到网关的子设备列表
146   - * <p>
147   - * 条件:设备类型为 GATEWAY_SUB 且未绑定任何网关,或已绑定到指定网关
  146 + * 分页查询未绑定网关的子设备
148 147 *
149   - * @param gatewayId 网关设备编号(可选,用于包含已绑定到该网关的设备)
150   - * @return 子设备列表
  148 + * @param reqVO 分页查询参数
  149 + * @return 子设备分页
151 150 */
152   - default List<IotDeviceDO> selectBindableSubDeviceList(@Nullable Long gatewayId) {
153   - return selectList(new LambdaQueryWrapperX<IotDeviceDO>()
154   - .eq(IotDeviceDO::getDeviceType, cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum.GATEWAY_SUB.getType())
155   - .and(wrapper -> wrapper
156   - .isNull(IotDeviceDO::getGatewayId)));
157   -// .or()
158   -// .eqIfPresent(IotDeviceDO::getGatewayId, gatewayId)))
  151 + default PageResult<IotDeviceDO> selectUnboundSubDevicePage(IotDevicePageReqVO reqVO) {
  152 + return selectPage(reqVO, new LambdaQueryWrapperX<IotDeviceDO>()
  153 + .likeIfPresent(IotDeviceDO::getDeviceName, reqVO.getDeviceName())
  154 + .likeIfPresent(IotDeviceDO::getNickname, reqVO.getNickname())
  155 + .eqIfPresent(IotDeviceDO::getProductId, reqVO.getProductId())
  156 + // 仅查询子设备 + 未绑定网关
  157 + .eq(IotDeviceDO::getDeviceType, IotProductDeviceTypeEnum.GATEWAY_SUB.getType())
  158 + .isNull(IotDeviceDO::getGatewayId)
  159 + .orderByDesc(IotDeviceDO::getId));
159 160 }
160 161  
161 162 }
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/product/IotProductMapper.java
... ... @@ -27,6 +27,12 @@ public interface IotProductMapper extends BaseMapperX&lt;IotProductDO&gt; {
27 27 .orderByDesc(IotProductDO::getId));
28 28 }
29 29  
  30 + default List<IotProductDO> selectList(Integer deviceType) {
  31 + return selectList(new LambdaQueryWrapperX<IotProductDO>()
  32 + .eqIfPresent(IotProductDO::getDeviceType, deviceType)
  33 + .orderByDesc(IotProductDO::getId));
  34 + }
  35 +
30 36 default IotProductDO selectByProductKey(String productKey) {
31 37 return selectOne(new LambdaQueryWrapper<IotProductDO>()
32 38 .apply("LOWER(product_key) = {0}", productKey.toLowerCase()));
... ... @@ -37,5 +43,4 @@ public interface IotProductMapper extends BaseMapperX&lt;IotProductDO&gt; {
37 43 .geIfPresent(IotProductDO::getCreateTime, createTime));
38 44 }
39 45  
40   -
41 46 }
42 47 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java
... ... @@ -26,19 +26,15 @@ public interface ErrorCodeConstants {
26 26 // ========== 设备 1-050-003-000 ============
27 27 ErrorCode DEVICE_NOT_EXISTS = new ErrorCode(1_050_003_000, "设备不存在");
28 28 ErrorCode DEVICE_NAME_EXISTS = new ErrorCode(1_050_003_001, "设备名称在同一产品下必须唯一");
29   - ErrorCode DEVICE_HAS_CHILDREN = new ErrorCode(1_050_003_002, "有子设备,不允许删除");
  29 + ErrorCode DEVICE_GATEWAY_HAS_SUB = new ErrorCode(1_050_003_002, "网关设备存在已绑定的子设备,不允许删除");
30 30 ErrorCode DEVICE_KEY_EXISTS = new ErrorCode(1_050_003_003, "设备标识已经存在");
31 31 ErrorCode DEVICE_GATEWAY_NOT_EXISTS = new ErrorCode(1_050_003_004, "网关设备不存在");
32 32 ErrorCode DEVICE_NOT_GATEWAY = new ErrorCode(1_050_003_005, "设备不是网关设备");
33 33 ErrorCode DEVICE_IMPORT_LIST_IS_EMPTY = new ErrorCode(1_050_003_006, "导入设备数据不能为空!");
34 34 ErrorCode DEVICE_DOWNSTREAM_FAILED_SERVER_ID_NULL = new ErrorCode(1_050_003_007, "下行设备消息失败,原因:设备未连接网关");
35 35 ErrorCode DEVICE_SERIAL_NUMBER_EXISTS = new ErrorCode(1_050_003_008, "设备序列号已存在,序列号必须全局唯一");
36   - // TODO @AI:1_050_003_009 需要提示具体的哪个设备。产品/设备,标识下
37   - ErrorCode DEVICE_NOT_GATEWAY_SUB = new ErrorCode(1_050_003_009, "设备不是网关子设备类型,无法绑定到网关");
38   - // TODO @AI:1_050_003_009 需要提示具体的哪个设备。产品/设备,标识下
39   - ErrorCode DEVICE_GATEWAY_BINDTO_EXISTS = new ErrorCode(1_050_003_010, "设备已绑定到其他网关,请先解绑");
40   - // TODO @AI:是不是可以删除,DEVICE_GATEWAY_BINDTO_NOT_EXISTS
41   - ErrorCode DEVICE_GATEWAY_BINDTO_NOT_EXISTS = new ErrorCode(1_050_003_011, "设备未绑定到任何网关");
  36 + ErrorCode DEVICE_NOT_GATEWAY_SUB = new ErrorCode(1_050_003_009, "设备【{}/{}】不是网关子设备类型,无法绑定到网关");
  37 + ErrorCode DEVICE_GATEWAY_BINDTO_EXISTS = new ErrorCode(1_050_003_010, "设备【{}/{}】已绑定到其他网关,请先解绑");
42 38  
43 39 // ========== 产品分类 1-050-004-000 ==========
44 40 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
... ... @@ -73,7 +73,6 @@ public interface IotDeviceService {
73 73 */
74 74 void updateDeviceGroup(@Valid IotDeviceUpdateGroupReqVO updateReqVO);
75 75  
76   - // TODO @AI:网关设备被删除时,需要查看是否有子设备绑定。如果有,则不允许删除。
77 76 /**
78 77 * 删除单个设备
79 78 *
... ... @@ -307,14 +306,12 @@ public interface IotDeviceService {
307 306 void unbindDeviceGateway(Collection<Long> ids);
308 307  
309 308 /**
310   - * 获取可绑定到网关的子设备列表
311   - * <p>
312   - * 条件:设备类型为 GATEWAY_SUB 且未绑定任何网关
  309 + * 获取未绑定网关的子设备分页
313 310 *
314   - * @param gatewayId 网关设备编号(可选,用于包含已绑定到该网关的设备)
315   - * @return 子设备列表
  311 + * @param pageReqVO 分页查询参数(仅使用 productId、deviceName、nickname)
  312 + * @return 子设备分页
316 313 */
317   - List<IotDeviceDO> getBindableSubDeviceList(@Nullable Long gatewayId);
  314 + PageResult<IotDeviceDO> getUnboundSubDevicePage(IotDevicePageReqVO pageReqVO);
318 315  
319 316 /**
320 317 * 根据网关编号获取子设备列表
... ... @@ -324,13 +321,4 @@ public interface IotDeviceService {
324 321 */
325 322 List<IotDeviceDO> getDeviceListByGatewayId(Long gatewayId);
326 323  
327   - // TODO @AI:暂时用不到,可以删除。
328   - /**
329   - * 根据网关编号获取子设备数量
330   - *
331   - * @param gatewayId 网关设备编号
332   - * @return 子设备数量
333   - */
334   - Long getDeviceCountByGatewayId(Long gatewayId);
335   -
336 324 }
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java
... ... @@ -170,9 +170,10 @@ public class IotDeviceServiceImpl implements IotDeviceService {
170 170 public void deleteDevice(Long id) {
171 171 // 1.1 校验存在
172 172 IotDeviceDO device = validateDeviceExists(id);
173   - // 1.2 如果是网关设备,检查是否有子设备
174   - if (device.getGatewayId() != null && deviceMapper.selectCountByGatewayId(id) > 0) {
175   - throw exception(DEVICE_HAS_CHILDREN);
  173 + // 1.2 如果是网关设备,检查是否有子设备绑定
  174 + if (IotProductDeviceTypeEnum.isGateway(device.getDeviceType())
  175 + && deviceMapper.selectCountByGatewayId(id) > 0) {
  176 + throw exception(DEVICE_GATEWAY_HAS_SUB);
176 177 }
177 178  
178 179 // 2. 删除设备
... ... @@ -193,10 +194,11 @@ public class IotDeviceServiceImpl implements IotDeviceService {
193 194 if (CollUtil.isEmpty(devices)) {
194 195 return;
195 196 }
196   - // 1.2 校验网关设备是否存在
  197 + // 1.2 如果是网关设备,检查是否有子设备绑定
197 198 for (IotDeviceDO device : devices) {
198   - if (device.getGatewayId() != null && deviceMapper.selectCountByGatewayId(device.getId()) > 0) {
199   - throw exception(DEVICE_HAS_CHILDREN);
  199 + if (IotProductDeviceTypeEnum.isGateway(device.getDeviceType())
  200 + && deviceMapper.selectCountByGatewayId(device.getId()) > 0) {
  201 + throw exception(DEVICE_GATEWAY_HAS_SUB);
200 202 }
201 203 }
202 204  
... ... @@ -522,34 +524,29 @@ public class IotDeviceServiceImpl implements IotDeviceService {
522 524 if (CollUtil.isEmpty(ids)) {
523 525 return;
524 526 }
525   - // TODO @AI:校验应该是 1.1、1.2 统一风格;
526   - // 1. 校验网关设备存在且类型正确
  527 + // 1.1 校验网关设备存在且类型正确
527 528 validateGatewayDeviceExists(gatewayId);
528   -
529   - // 2. 校验并绑定每个子设备
  529 + // 1.2 校验子设备存在
530 530 List<IotDeviceDO> devices = deviceMapper.selectByIds(ids);
531 531 if (devices.size() != ids.size()) {
532 532 throw exception(DEVICE_NOT_EXISTS);
533 533 }
534   -
535   - List<IotDeviceDO> updateList = new ArrayList<>();
  534 + // 1.3 校验每个设备是否可绑定
536 535 for (IotDeviceDO device : devices) {
537   - // 2.1 校验是否为子设备类型
538 536 if (!IotProductDeviceTypeEnum.isGatewaySub(device.getDeviceType())) {
539   - throw exception(DEVICE_NOT_GATEWAY_SUB);
  537 + throw exception(DEVICE_NOT_GATEWAY_SUB, device.getProductKey(), device.getDeviceName());
540 538 }
541   - // 2.2 校验是否已绑定其他网关
542 539 if (device.getGatewayId() != null && !device.getGatewayId().equals(gatewayId)) {
543   - throw exception(DEVICE_GATEWAY_BINDTO_EXISTS);
  540 + throw exception(DEVICE_GATEWAY_BINDTO_EXISTS, device.getProductKey(), device.getDeviceName());
544 541 }
545   - updateList.add(new IotDeviceDO().setId(device.getId()).setGatewayId(gatewayId));
546 542 }
547 543  
548   - // 3. 批量更新数据库
549   - // TODO @AI:List<IotDeviceDO> updateList 直接 convertList,不用上面 for 里面搞;校验是校验,插入是插入;
  544 + // 2. 批量更新数据库
  545 + List<IotDeviceDO> updateList = convertList(devices, device ->
  546 + new IotDeviceDO().setId(device.getId()).setGatewayId(gatewayId));
550 547 deviceMapper.updateBatch(updateList);
551 548  
552   - // 4. 清空对应缓存
  549 + // 3. 清空对应缓存
553 550 deleteDeviceCache(devices);
554 551 }
555 552  
... ... @@ -579,8 +576,8 @@ public class IotDeviceServiceImpl implements IotDeviceService {
579 576 }
580 577  
581 578 @Override
582   - public List<IotDeviceDO> getBindableSubDeviceList(@Nullable Long gatewayId) {
583   - return deviceMapper.selectBindableSubDeviceList(gatewayId);
  579 + public PageResult<IotDeviceDO> getUnboundSubDevicePage(IotDevicePageReqVO pageReqVO) {
  580 + return deviceMapper.selectUnboundSubDevicePage(pageReqVO);
584 581 }
585 582  
586 583 @Override
... ... @@ -588,11 +585,6 @@ public class IotDeviceServiceImpl implements IotDeviceService {
588 585 return deviceMapper.selectListByGatewayId(gatewayId);
589 586 }
590 587  
591   - @Override
592   - public Long getDeviceCountByGatewayId(Long gatewayId) {
593   - return deviceMapper.selectCountByGatewayId(gatewayId);
594   - }
595   -
596 588 private IotDeviceServiceImpl getSelf() {
597 589 return SpringUtil.getBean(getClass());
598 590 }
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductService.java
... ... @@ -106,6 +106,14 @@ public interface IotProductService {
106 106 List<IotProductDO> getProductList();
107 107  
108 108 /**
  109 + * 根据设备类型获得产品列表
  110 + *
  111 + * @param deviceType 设备类型(可选)
  112 + * @return 产品列表
  113 + */
  114 + List<IotProductDO> getProductList(@Nullable Integer deviceType);
  115 +
  116 + /**
109 117 * 获得产品数量
110 118 *
111 119 * @param createTime 创建时间,如果为空,则统计所有产品数量
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductServiceImpl.java
... ... @@ -158,6 +158,11 @@ public class IotProductServiceImpl implements IotProductService {
158 158 }
159 159  
160 160 @Override
  161 + public List<IotProductDO> getProductList(Integer deviceType) {
  162 + return productMapper.selectList(deviceType);
  163 + }
  164 +
  165 + @Override
161 166 public Long getProductCount(LocalDateTime createTime) {
162 167 return productMapper.selectCountByCreateTime(createTime);
163 168 }
... ...