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

This commit is contained in:
YunaiV 2024-12-15 10:46:33 +08:00
parent dea8883f82
commit 92c2717d46
13 changed files with 239 additions and 15 deletions

View File

@ -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, "产品分类不存在");

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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);
}
}

View File

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

View File

@ -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);

View File

@ -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);
/**
* 得设备分页
* <EFBFBD><EFBFBD>得设备分页
*
* @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);
}

View File

@ -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;
}
}

View File

@ -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);
/**
* 获得产品分页
*

View File

@ -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 iotProductDO;
return product;
}
private void validateProductStatus(IotProductDO iotProductDO) {
if (Objects.equals(iotProductDO.getStatus(), IotProductStatusEnum.PUBLISHED.getStatus())) {
@Override
public IotProductDO validateProductExists(String productKey) {
IotProductDO product = productMapper.selectByProductKey(productKey);
if (product == null) {
throw exception(PRODUCT_NOT_EXISTS);
}
return product;
}
private void validateProductStatus(IotProductDO product) {
if (Objects.equals(product.getStatus(), IotProductStatusEnum.PUBLISHED.getStatus())) {
throw exception(PRODUCT_STATUS_NOT_DELETE);
}
}