Commit 92c2717d461cc117ce0d2c595d3705606a58c287

Authored by YunaiV
1 parent dea8883f

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

Showing 13 changed files with 239 additions and 15 deletions
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 {
29 29 ErrorCode DEVICE_KEY_EXISTS = new ErrorCode(1_050_003_003, "设备标识已经存在");
30 30 ErrorCode DEVICE_GATEWAY_NOT_EXISTS = new ErrorCode(1_050_003_004, "网关设备不存在");
31 31 ErrorCode DEVICE_NOT_GATEWAY = new ErrorCode(1_050_003_005, "设备不是网关设备");
  32 + ErrorCode DEVICE_IMPORT_LIST_IS_EMPTY = new ErrorCode(1_050_003_006, "导入设备数据不能为空!");
32 33  
33 34 // ========== 产品分类 1-050-004-000 ==========
34 35 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/controller/admin/device/IotDeviceController.java
... ... @@ -18,8 +18,10 @@ import jakarta.validation.Valid;
18 18 import org.springframework.security.access.prepost.PreAuthorize;
19 19 import org.springframework.validation.annotation.Validated;
20 20 import org.springframework.web.bind.annotation.*;
  21 +import org.springframework.web.multipart.MultipartFile;
21 22  
22 23 import java.io.IOException;
  24 +import java.util.Arrays;
23 25 import java.util.Collection;
24 26 import java.util.List;
25 27  
... ... @@ -133,4 +135,28 @@ public class IotDeviceController {
133 135 new IotDeviceRespVO().setId(device.getId()).setDeviceName(device.getDeviceName())));
134 136 }
135 137  
  138 + @PostMapping("/import")
  139 + @Operation(summary = "导入设备")
  140 + @PreAuthorize("@ss.hasPermission('iot:device:import')")
  141 + public CommonResult<IotDeviceImportRespVO> importDevice(
  142 + @RequestParam("file") MultipartFile file,
  143 + @RequestParam(value = "updateSupport", required = false, defaultValue = "false") Boolean updateSupport)
  144 + throws Exception {
  145 + List<IotDeviceImportExcelVO> list = ExcelUtils.read(file, IotDeviceImportExcelVO.class);
  146 + return success(deviceService.importDevice(list, updateSupport));
  147 + }
  148 +
  149 + @GetMapping("/get-import-template")
  150 + @Operation(summary = "获得导入设备模板")
  151 + public void importTemplate(HttpServletResponse response) throws IOException {
  152 + // 手动创建导出 demo
  153 + List<IotDeviceImportExcelVO> list = Arrays.asList(
  154 + IotDeviceImportExcelVO.builder().deviceName("温度传感器001").parentDeviceName("gateway110")
  155 + .productKey("1de24640dfe").groupNames("灰度分组,生产分组").build(),
  156 + IotDeviceImportExcelVO.builder().deviceName("biubiu")
  157 + .productKey("YzvHxd4r67sT4s2B").groupNames("").build());
  158 + // 输出
  159 + ExcelUtils.write(response, "设备导入模板.xls", "数据", IotDeviceImportExcelVO.class, list);
  160 + }
  161 +
136 162 }
137 163 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceImportExcelVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
  2 +
  3 +import com.alibaba.excel.annotation.ExcelProperty;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import jakarta.validation.constraints.NotEmpty;
  6 +import lombok.AllArgsConstructor;
  7 +import lombok.Builder;
  8 +import lombok.Data;
  9 +import lombok.NoArgsConstructor;
  10 +import lombok.experimental.Accessors;
  11 +
  12 +/**
  13 + * 设备 Excel 导入 VO
  14 + */
  15 +@Data
  16 +@Builder
  17 +@AllArgsConstructor
  18 +@NoArgsConstructor
  19 +@Accessors(chain = false) // 设置 chain = false,避免设备导入有问题
  20 +public class IotDeviceImportExcelVO {
  21 +
  22 + @ExcelProperty("设备名称")
  23 + @NotEmpty(message = "设备名称不能为空")
  24 + private String deviceName;
  25 +
  26 + @ExcelProperty("父设备名称")
  27 + @Schema(description = "父设备名称", example = "网关001")
  28 + private String parentDeviceName;
  29 +
  30 + @ExcelProperty("产品标识")
  31 + @NotEmpty(message = "产品标识不能为空")
  32 + private String productKey;
  33 +
  34 + @ExcelProperty("设备分组")
  35 + private String groupNames;
  36 +
  37 +}
0 38 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceImportRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Builder;
  5 +import lombok.Data;
  6 +
  7 +import java.util.List;
  8 +import java.util.Map;
  9 +
  10 +@Schema(description = "管理后台 - IoT 设备导入 Response VO")
  11 +@Data
  12 +@Builder
  13 +public class IotDeviceImportRespVO {
  14 +
  15 + @Schema(description = "创建成功的设备名称数组", requiredMode = Schema.RequiredMode.REQUIRED)
  16 + private List<String> createDeviceNames;
  17 +
  18 + @Schema(description = "更新成功的设备名称数组", requiredMode = Schema.RequiredMode.REQUIRED)
  19 + private List<String> updateDeviceNames;
  20 +
  21 + @Schema(description = "导入失败的设备集合,key为设备名称,value为失败原因", requiredMode = Schema.RequiredMode.REQUIRED)
  22 + private Map<String, String> failureDeviceNames;
  23 +}
0 24 \ No newline at end of file
... ...
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&lt;IotDeviceGroupDO&gt; {
28 28 return selectList(IotDeviceGroupDO::getStatus, status);
29 29 }
30 30  
  31 + default IotDeviceGroupDO selectByName(String name) {
  32 + return selectOne(IotDeviceGroupDO::getName, name);
  33 + }
  34 +
31 35 }
32 36 \ 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
... ... @@ -29,6 +29,10 @@ public interface IotDeviceMapper extends BaseMapperX&lt;IotDeviceDO&gt; {
29 29 .orderByDesc(IotDeviceDO::getId));
30 30 }
31 31  
  32 + default IotDeviceDO selectByDeviceName(String deviceName) {
  33 + return selectOne(IotDeviceDO::getDeviceName, deviceName);
  34 + }
  35 +
32 36 default IotDeviceDO selectByProductKeyAndDeviceName(String productKey, String deviceName) {
33 37 return selectOne(IotDeviceDO::getProductKey, productKey,
34 38 IotDeviceDO::getDeviceName, deviceName);
... ...
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&lt;IotProductDO&gt; {
23 23 }
24 24  
25 25 default IotProductDO selectByProductKey(String productKey) {
26   - return selectOne(new LambdaQueryWrapperX<IotProductDO>().eq(IotProductDO::getProductKey, productKey));
  26 + return selectOne(IotProductDO::getProductKey, productKey);
27 27 }
28 28  
29 29 }
30 30 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceGroupService.java
... ... @@ -70,6 +70,14 @@ public interface IotDeviceGroupService {
70 70 IotDeviceGroupDO getDeviceGroup(Long id);
71 71  
72 72 /**
  73 + * 获得设备分组
  74 + *
  75 + * @param name 名称
  76 + * @return 设备分组
  77 + */
  78 + IotDeviceGroupDO getDeviceGroupByName(String name);
  79 +
  80 + /**
73 81 * 获得设备分组分页
74 82 *
75 83 * @param pageReqVO 分页查询
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceGroupServiceImpl.java
... ... @@ -77,6 +77,11 @@ public class IotDeviceGroupServiceImpl implements IotDeviceGroupService {
77 77 }
78 78  
79 79 @Override
  80 + public IotDeviceGroupDO getDeviceGroupByName(String name) {
  81 + return deviceGroupMapper.selectByName(name);
  82 + }
  83 +
  84 + @Override
80 85 public PageResult<IotDeviceGroupDO> getDeviceGroupPage(IotDeviceGroupPageReqVO pageReqVO) {
81 86 return deviceGroupMapper.selectPage(pageReqVO);
82 87 }
... ...
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
6 6 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceStatusUpdateReqVO;
7 7 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceUpdateGroupReqVO;
8 8 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
  9 +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceImportRespVO;
  10 +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceImportExcelVO;
9 11 import jakarta.validation.Valid;
10 12  
11 13 import javax.annotation.Nullable;
... ... @@ -71,7 +73,7 @@ public interface IotDeviceService {
71 73 IotDeviceDO getDevice(Long id);
72 74  
73 75 /**
74   - * 获得设备分页
  76 + * ��得设备分页
75 77 *
76 78 * @param pageReqVO 分页查询
77 79 * @return IoT 设备分页
... ... @@ -111,4 +113,13 @@ public interface IotDeviceService {
111 113 */
112 114 IotDeviceDO getDeviceByProductKeyAndDeviceName(String productKey, String deviceName);
113 115  
  116 + /**
  117 + * 导入设备
  118 + *
  119 + * @param importDevices 导入设备列表
  120 + * @param updateSupport 是否支持更新
  121 + * @return 导入结果
  122 + */
  123 + IotDeviceImportRespVO importDevice(List<IotDeviceImportExcelVO> importDevices, boolean updateSupport);
  124 +
114 125 }
115 126 \ No newline at end of file
... ...
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;
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.util.IdUtil;
5 5 import cn.hutool.core.util.RandomUtil;
  6 +import cn.hutool.core.util.StrUtil;
  7 +import cn.iocoder.yudao.framework.common.exception.ServiceException;
6 8 import cn.iocoder.yudao.framework.common.pojo.PageResult;
7 9 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  10 +import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
8 11 import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
9   -import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDevicePageReqVO;
10   -import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceSaveReqVO;
11   -import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceStatusUpdateReqVO;
12   -import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceUpdateGroupReqVO;
  12 +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.*;
13 13 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
  14 +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceGroupDO;
14 15 import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
15 16 import cn.iocoder.yudao.module.iot.dal.mysql.device.IotDeviceMapper;
16 17 import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStatusEnum;
17 18 import cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum;
18 19 import cn.iocoder.yudao.module.iot.service.product.IotProductService;
19 20 import jakarta.annotation.Resource;
  21 +import jakarta.validation.ConstraintViolationException;
20 22 import lombok.extern.slf4j.Slf4j;
21 23 import org.springframework.context.annotation.Lazy;
22 24 import org.springframework.stereotype.Service;
... ... @@ -25,9 +27,7 @@ import org.springframework.validation.annotation.Validated;
25 27  
26 28 import javax.annotation.Nullable;
27 29 import java.time.LocalDateTime;
28   -import java.util.Collection;
29   -import java.util.List;
30   -import java.util.Objects;
  30 +import java.util.*;
31 31  
32 32 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
33 33 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
... ... @@ -245,6 +245,15 @@ public class IotDeviceServiceImpl implements IotDeviceService {
245 245 }
246 246  
247 247 /**
  248 + * 生成 deviceKey
  249 + *
  250 + * @return 生成的 deviceKey
  251 + */
  252 + private String generateDeviceKey() {
  253 + return RandomUtil.randomString(16);
  254 + }
  255 +
  256 + /**
248 257 * 生成 deviceSecret
249 258 *
250 259 * @return 生成的 deviceSecret
... ... @@ -282,4 +291,74 @@ public class IotDeviceServiceImpl implements IotDeviceService {
282 291 return RandomUtil.randomString(32);
283 292 }
284 293  
  294 + @Override
  295 + @Transactional(rollbackFor = Exception.class) // 添加事务,异常则回滚所有导入
  296 + public IotDeviceImportRespVO importDevice(List<IotDeviceImportExcelVO> importDevices, boolean updateSupport) {
  297 + // 1. 参数校验
  298 + if (CollUtil.isEmpty(importDevices)) {
  299 + throw exception(DEVICE_IMPORT_LIST_IS_EMPTY);
  300 + }
  301 +
  302 + // 2. 遍历,逐个创建 or 更新
  303 + IotDeviceImportRespVO respVO = IotDeviceImportRespVO.builder().createDeviceNames(new ArrayList<>())
  304 + .updateDeviceNames(new ArrayList<>()).failureDeviceNames(new LinkedHashMap<>()).build();
  305 + importDevices.forEach(importDevice -> {
  306 + try {
  307 + // 2.1.1 校验字段是否符合要求
  308 + try {
  309 + ValidationUtils.validate(importDevice);
  310 + } catch (ConstraintViolationException ex){
  311 + respVO.getFailureDeviceNames().put(importDevice.getDeviceName(), ex.getMessage());
  312 + return;
  313 + }
  314 + // 2.1.2 校验产品是否存在
  315 + IotProductDO product = productService.validateProductExists(importDevice.getProductKey());
  316 + // 2.1.3 校验父设备是否存在
  317 + Long gatewayId = null;
  318 + if (StrUtil.isNotEmpty(importDevice.getParentDeviceName())) {
  319 + IotDeviceDO gatewayDevice = deviceMapper.selectByDeviceName(importDevice.getParentDeviceName());
  320 + if (gatewayDevice == null) {
  321 + throw exception(DEVICE_GATEWAY_NOT_EXISTS);
  322 + }
  323 + if (!IotProductDeviceTypeEnum.isGateway(gatewayDevice.getDeviceType())) {
  324 + throw exception(DEVICE_NOT_GATEWAY);
  325 + }
  326 + gatewayId = gatewayDevice.getId();
  327 + }
  328 + // 2.1.4 校验设备分组是否存在
  329 + Set<Long> groupIds = new HashSet<>();
  330 + if (StrUtil.isNotEmpty(importDevice.getGroupNames())) {
  331 + String[] groupNames = importDevice.getGroupNames().split(",");
  332 + for (String groupName : groupNames) {
  333 + IotDeviceGroupDO group = deviceGroupService.getDeviceGroupByName(groupName);
  334 + if (group == null) {
  335 + throw exception(DEVICE_GROUP_NOT_EXISTS);
  336 + }
  337 + groupIds.add(group.getId());
  338 + }
  339 + }
  340 +
  341 + // 2.2.1 判断如果不存在,在进行插入
  342 + IotDeviceDO existDevice = deviceMapper.selectByDeviceName(importDevice.getDeviceName());
  343 + if (existDevice == null) {
  344 + createDevice(new IotDeviceSaveReqVO()
  345 + .setDeviceName(importDevice.getDeviceName()).setDeviceKey(generateDeviceKey())
  346 + .setProductId(product.getId()).setGatewayId(gatewayId).setGroupIds(groupIds));
  347 + respVO.getCreateDeviceNames().add(importDevice.getDeviceName());
  348 + return;
  349 + }
  350 + // 2.2.2 如果存在,判断是否允许更新
  351 + if (updateSupport) {
  352 + throw exception(DEVICE_KEY_EXISTS);
  353 + }
  354 + updateDevice(new IotDeviceSaveReqVO().setId(existDevice.getId())
  355 + .setGatewayId(gatewayId).setGroupIds(groupIds));
  356 + respVO.getUpdateDeviceNames().add(importDevice.getDeviceName());
  357 + } catch (ServiceException ex) {
  358 + respVO.getFailureDeviceNames().put(importDevice.getDeviceName(), ex.getMessage());
  359 + }
  360 + });
  361 + return respVO;
  362 + }
  363 +
285 364 }
286 365 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductService.java
... ... @@ -46,6 +46,22 @@ public interface IotProductService {
46 46 IotProductDO getProduct(Long id);
47 47  
48 48 /**
  49 + * 校验产品存在
  50 + *
  51 + * @param id 编号
  52 + * @return 产品
  53 + */
  54 + IotProductDO validateProductExists(Long id);
  55 +
  56 + /**
  57 + * 校验产品存在
  58 + *
  59 + * @param productKey 产品 key
  60 + * @return 产品
  61 + */
  62 + IotProductDO validateProductExists(String productKey);
  63 +
  64 + /**
49 65 * 获得产品分页
50 66 *
51 67 * @param pageReqVO 分页查询
... ...
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 {
71 71 productMapper.deleteById(id);
72 72 }
73 73  
74   - private IotProductDO validateProductExists(Long id) {
75   - IotProductDO iotProductDO = productMapper.selectById(id);
76   - if (iotProductDO == null) {
  74 + @Override
  75 + public IotProductDO validateProductExists(Long id) {
  76 + IotProductDO product = productMapper.selectById(id);
  77 + if (product == null) {
  78 + throw exception(PRODUCT_NOT_EXISTS);
  79 + }
  80 + return product;
  81 + }
  82 +
  83 + @Override
  84 + public IotProductDO validateProductExists(String productKey) {
  85 + IotProductDO product = productMapper.selectByProductKey(productKey);
  86 + if (product == null) {
77 87 throw exception(PRODUCT_NOT_EXISTS);
78 88 }
79   - return iotProductDO;
  89 + return product;
80 90 }
81 91  
82   - private void validateProductStatus(IotProductDO iotProductDO) {
83   - if (Objects.equals(iotProductDO.getStatus(), IotProductStatusEnum.PUBLISHED.getStatus())) {
  92 + private void validateProductStatus(IotProductDO product) {
  93 + if (Objects.equals(product.getStatus(), IotProductStatusEnum.PUBLISHED.getStatus())) {
84 94 throw exception(PRODUCT_STATUS_NOT_DELETE);
85 95 }
86 96 }
... ...