ソースを参照

【功能新增】IoT:设备管理,增加批量导入

YunaiV 8 ヶ月 前
コミット
92c2717d46
13 ファイル変更239 行追加15 行削除
  1. 1 0
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java
  2. 26 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.java
  3. 37 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceImportExcelVO.java
  4. 23 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceImportRespVO.java
  5. 4 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceGroupMapper.java
  6. 4 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceMapper.java
  7. 1 1
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/product/IotProductMapper.java
  8. 8 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceGroupService.java
  9. 5 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceGroupServiceImpl.java
  10. 12 1
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java
  11. 86 7
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java
  12. 16 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductService.java
  13. 16 6
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductServiceImpl.java

+ 1 - 0
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java

@@ -29,6 +29,7 @@ public interface ErrorCodeConstants {
     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, "导入设备数据不能为空!");
 
     // ========== 产品分类 1-050-004-000 ==========
     ErrorCode PRODUCT_CATEGORY_NOT_EXISTS = new ErrorCode(1_050_004_000, "产品分类不存在");

+ 26 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.java

@@ -18,8 +18,10 @@ import jakarta.validation.Valid;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
 
 import java.io.IOException;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
 
@@ -133,4 +135,28 @@ public class IotDeviceController {
         new IotDeviceRespVO().setId(device.getId()).setDeviceName(device.getDeviceName())));
     }
 
+    @PostMapping("/import")
+    @Operation(summary = "导入设备")
+    @PreAuthorize("@ss.hasPermission('iot:device:import')")
+    public CommonResult<IotDeviceImportRespVO> importDevice(
+            @RequestParam("file") MultipartFile file,
+            @RequestParam(value = "updateSupport", required = false, defaultValue = "false") Boolean updateSupport)
+            throws Exception {
+        List<IotDeviceImportExcelVO> list = ExcelUtils.read(file, IotDeviceImportExcelVO.class);
+        return success(deviceService.importDevice(list, updateSupport));
+    }
+
+    @GetMapping("/get-import-template")
+    @Operation(summary = "获得导入设备模板")
+    public void importTemplate(HttpServletResponse response) throws IOException {
+        // 手动创建导出 demo
+        List<IotDeviceImportExcelVO> list = Arrays.asList(
+                IotDeviceImportExcelVO.builder().deviceName("温度传感器001").parentDeviceName("gateway110")
+                        .productKey("1de24640dfe").groupNames("灰度分组,生产分组").build(),
+                IotDeviceImportExcelVO.builder().deviceName("biubiu")
+                        .productKey("YzvHxd4r67sT4s2B").groupNames("").build());
+        // 输出
+        ExcelUtils.write(response, "设备导入模板.xls", "数据", IotDeviceImportExcelVO.class, list);
+    }
+
 }

+ 37 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceImportExcelVO.java

@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
+
+import com.alibaba.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+
+/**
+ * 设备 Excel 导入 VO
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@Accessors(chain = false) // 设置 chain = false,避免设备导入有问题
+public class IotDeviceImportExcelVO {
+
+    @ExcelProperty("设备名称")
+    @NotEmpty(message = "设备名称不能为空")
+    private String deviceName;
+
+    @ExcelProperty("父设备名称")
+    @Schema(description = "父设备名称", example = "网关001")
+    private String parentDeviceName;
+
+    @ExcelProperty("产品标识")
+    @NotEmpty(message = "产品标识不能为空")
+    private String productKey;
+
+    @ExcelProperty("设备分组")
+    private String groupNames;
+
+}

+ 23 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceImportRespVO.java

@@ -0,0 +1,23 @@
+package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Builder;
+import lombok.Data;
+
+import java.util.List;
+import java.util.Map;
+
+@Schema(description = "管理后台 - IoT 设备导入 Response VO")
+@Data
+@Builder
+public class IotDeviceImportRespVO {
+
+    @Schema(description = "创建成功的设备名称数组", requiredMode = Schema.RequiredMode.REQUIRED)
+    private List<String> createDeviceNames;
+
+    @Schema(description = "更新成功的设备名称数组", requiredMode = Schema.RequiredMode.REQUIRED)
+    private List<String> updateDeviceNames;
+
+    @Schema(description = "导入失败的设备集合,key为设备名称,value为失败原因", requiredMode = Schema.RequiredMode.REQUIRED)
+    private Map<String, String> failureDeviceNames;
+} 

+ 4 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceGroupMapper.java

@@ -28,4 +28,8 @@ public interface IotDeviceGroupMapper extends BaseMapperX<IotDeviceGroupDO> {
         return selectList(IotDeviceGroupDO::getStatus, status);
     }
 
+    default IotDeviceGroupDO selectByName(String name) {
+        return selectOne(IotDeviceGroupDO::getName, name);
+    }
+
 }

+ 4 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceMapper.java

@@ -29,6 +29,10 @@ public interface IotDeviceMapper extends BaseMapperX<IotDeviceDO> {
                 .orderByDesc(IotDeviceDO::getId));
     }
 
+    default IotDeviceDO selectByDeviceName(String deviceName) {
+        return selectOne(IotDeviceDO::getDeviceName, deviceName);
+    }
+
     default IotDeviceDO selectByProductKeyAndDeviceName(String productKey, String deviceName) {
         return selectOne(IotDeviceDO::getProductKey, productKey,
                 IotDeviceDO::getDeviceName, deviceName);

+ 1 - 1
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/product/IotProductMapper.java

@@ -23,7 +23,7 @@ public interface IotProductMapper extends BaseMapperX<IotProductDO> {
     }
 
     default IotProductDO selectByProductKey(String productKey) {
-        return selectOne(new LambdaQueryWrapperX<IotProductDO>().eq(IotProductDO::getProductKey, productKey));
+        return selectOne(IotProductDO::getProductKey, productKey);
     }
 
 }

+ 8 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceGroupService.java

@@ -69,6 +69,14 @@ public interface IotDeviceGroupService {
      */
     IotDeviceGroupDO getDeviceGroup(Long id);
 
+    /**
+     * 获得设备分组
+     *
+     * @param name 名称
+     * @return 设备分组
+     */
+    IotDeviceGroupDO getDeviceGroupByName(String name);
+
     /**
      * 获得设备分组分页
      *

+ 5 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceGroupServiceImpl.java

@@ -76,6 +76,11 @@ public class IotDeviceGroupServiceImpl implements IotDeviceGroupService {
         return deviceGroupMapper.selectById(id);
     }
 
+    @Override
+    public IotDeviceGroupDO getDeviceGroupByName(String name) {
+        return deviceGroupMapper.selectByName(name);
+    }
+
     @Override
     public PageResult<IotDeviceGroupDO> getDeviceGroupPage(IotDeviceGroupPageReqVO pageReqVO) {
         return deviceGroupMapper.selectPage(pageReqVO);

+ 12 - 1
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java

@@ -6,6 +6,8 @@ import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceSa
 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceStatusUpdateReqVO;
 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceUpdateGroupReqVO;
 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
+import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceImportRespVO;
+import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceImportExcelVO;
 import jakarta.validation.Valid;
 
 import javax.annotation.Nullable;
@@ -71,7 +73,7 @@ public interface IotDeviceService {
     IotDeviceDO getDevice(Long id);
 
     /**
-     * 得设备分页
+     * ��得设备分页
      *
      * @param pageReqVO 分页查询
      * @return IoT 设备分页
@@ -111,4 +113,13 @@ public interface IotDeviceService {
      */
     IotDeviceDO getDeviceByProductKeyAndDeviceName(String productKey, String deviceName);
 
+    /**
+     * 导入设备
+     *
+     * @param importDevices 导入设备列表
+     * @param updateSupport 是否支持更新
+     * @return 导入结果
+     */
+    IotDeviceImportRespVO importDevice(List<IotDeviceImportExcelVO> importDevices, boolean updateSupport);
+
 }

+ 86 - 7
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java

@@ -3,20 +3,22 @@ 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.RandomUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.exception.ServiceException;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
 import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
-import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDevicePageReqVO;
-import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceSaveReqVO;
-import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceStatusUpdateReqVO;
-import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceUpdateGroupReqVO;
+import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.*;
 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceGroupDO;
 import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
 import cn.iocoder.yudao.module.iot.dal.mysql.device.IotDeviceMapper;
 import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStatusEnum;
 import cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum;
 import cn.iocoder.yudao.module.iot.service.product.IotProductService;
 import jakarta.annotation.Resource;
+import jakarta.validation.ConstraintViolationException;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
@@ -25,9 +27,7 @@ import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.Nullable;
 import java.time.LocalDateTime;
-import java.util.Collection;
-import java.util.List;
-import java.util.Objects;
+import java.util.*;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
@@ -244,6 +244,15 @@ public class IotDeviceServiceImpl implements IotDeviceService {
         return deviceMapper.selectByProductKeyAndDeviceName(productKey, deviceName);
     }
 
+    /**
+     * 生成 deviceKey
+     *
+     * @return 生成的 deviceKey
+     */
+    private String generateDeviceKey() {
+        return RandomUtil.randomString(16);
+    }
+
     /**
      * 生成 deviceSecret
      *
@@ -282,4 +291,74 @@ public class IotDeviceServiceImpl implements IotDeviceService {
         return RandomUtil.randomString(32);
     }
 
+    @Override
+    @Transactional(rollbackFor = Exception.class) // 添加事务,异常则回滚所有导入
+    public IotDeviceImportRespVO importDevice(List<IotDeviceImportExcelVO> importDevices, boolean updateSupport) {
+        // 1. 参数校验
+        if (CollUtil.isEmpty(importDevices)) {
+            throw exception(DEVICE_IMPORT_LIST_IS_EMPTY);
+        }
+
+        // 2. 遍历,逐个创建 or 更新
+        IotDeviceImportRespVO respVO = IotDeviceImportRespVO.builder().createDeviceNames(new ArrayList<>())
+                .updateDeviceNames(new ArrayList<>()).failureDeviceNames(new LinkedHashMap<>()).build();
+        importDevices.forEach(importDevice -> {
+            try {
+                // 2.1.1 校验字段是否符合要求
+                try {
+                    ValidationUtils.validate(importDevice);
+                } catch (ConstraintViolationException ex){
+                    respVO.getFailureDeviceNames().put(importDevice.getDeviceName(), ex.getMessage());
+                    return;
+                }
+                // 2.1.2 校验产品是否存在
+                IotProductDO product = productService.validateProductExists(importDevice.getProductKey());
+                // 2.1.3 校验父设备是否存在
+                Long gatewayId = null;
+                if (StrUtil.isNotEmpty(importDevice.getParentDeviceName())) {
+                    IotDeviceDO gatewayDevice = deviceMapper.selectByDeviceName(importDevice.getParentDeviceName());
+                    if (gatewayDevice == null) {
+                        throw exception(DEVICE_GATEWAY_NOT_EXISTS);
+                    }
+                    if (!IotProductDeviceTypeEnum.isGateway(gatewayDevice.getDeviceType())) {
+                        throw exception(DEVICE_NOT_GATEWAY);
+                    }
+                    gatewayId = gatewayDevice.getId();
+                }
+                // 2.1.4 校验设备分组是否存在
+                Set<Long> groupIds = new HashSet<>();
+                if (StrUtil.isNotEmpty(importDevice.getGroupNames())) {
+                    String[] groupNames = importDevice.getGroupNames().split(",");
+                    for (String groupName : groupNames) {
+                        IotDeviceGroupDO group = deviceGroupService.getDeviceGroupByName(groupName);
+                        if (group == null) {
+                            throw exception(DEVICE_GROUP_NOT_EXISTS);
+                        }
+                        groupIds.add(group.getId());
+                    }
+                }
+
+                // 2.2.1 判断如果不存在,在进行插入
+                IotDeviceDO existDevice = deviceMapper.selectByDeviceName(importDevice.getDeviceName());
+                if (existDevice == null) {
+                    createDevice(new IotDeviceSaveReqVO()
+                            .setDeviceName(importDevice.getDeviceName()).setDeviceKey(generateDeviceKey())
+                            .setProductId(product.getId()).setGatewayId(gatewayId).setGroupIds(groupIds));
+                    respVO.getCreateDeviceNames().add(importDevice.getDeviceName());
+                    return;
+                }
+                // 2.2.2 如果存在,判断是否允许更新
+                if (updateSupport) {
+                    throw exception(DEVICE_KEY_EXISTS);
+                }
+                updateDevice(new IotDeviceSaveReqVO().setId(existDevice.getId())
+                        .setGatewayId(gatewayId).setGroupIds(groupIds));
+                respVO.getUpdateDeviceNames().add(importDevice.getDeviceName());
+            } catch (ServiceException ex) {
+                respVO.getFailureDeviceNames().put(importDevice.getDeviceName(), ex.getMessage());
+            }
+        });
+        return respVO;
+    }
+
 }

+ 16 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductService.java

@@ -45,6 +45,22 @@ public interface IotProductService {
      */
     IotProductDO getProduct(Long id);
 
+    /**
+     * 校验产品存在
+     *
+     * @param id 编号
+     * @return 产品
+     */
+    IotProductDO validateProductExists(Long id);
+
+    /**
+     * 校验产品存在
+     *
+     * @param productKey 产品 key
+     * @return 产品
+     */
+    IotProductDO validateProductExists(String productKey);
+
     /**
      * 获得产品分页
      *

+ 16 - 6
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductServiceImpl.java

@@ -71,16 +71,26 @@ public class IotProductServiceImpl implements IotProductService {
         productMapper.deleteById(id);
     }
 
-    private IotProductDO validateProductExists(Long id) {
-        IotProductDO iotProductDO = productMapper.selectById(id);
-        if (iotProductDO == null) {
+    @Override
+    public IotProductDO validateProductExists(Long id) {
+        IotProductDO product = productMapper.selectById(id);
+        if (product == null) {
+            throw exception(PRODUCT_NOT_EXISTS);
+        }
+        return product;
+    }
+
+    @Override
+    public IotProductDO validateProductExists(String productKey) {
+        IotProductDO product = productMapper.selectByProductKey(productKey);
+        if (product == null) {
             throw exception(PRODUCT_NOT_EXISTS);
         }
-        return iotProductDO;
+        return product;
     }
 
-    private void validateProductStatus(IotProductDO iotProductDO) {
-        if (Objects.equals(iotProductDO.getStatus(), IotProductStatusEnum.PUBLISHED.getStatus())) {
+    private void validateProductStatus(IotProductDO product) {
+        if (Objects.equals(product.getStatus(), IotProductStatusEnum.PUBLISHED.getStatus())) {
             throw exception(PRODUCT_STATUS_NOT_DELETE);
         }
     }