【功能新增】IoT:设备管理,增加批量导入
This commit is contained in:
parent
dea8883f82
commit
92c2717d46
|
@ -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, "产品分类不存在");
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -69,6 +69,14 @@ public interface IotDeviceGroupService {
|
|||
*/
|
||||
IotDeviceGroupDO getDeviceGroup(Long id);
|
||||
|
||||
/**
|
||||
* 获得设备分组
|
||||
*
|
||||
* @param name 名称
|
||||
* @return 设备分组
|
||||
*/
|
||||
IotDeviceGroupDO getDeviceGroupByName(String name);
|
||||
|
||||
/**
|
||||
* 获得设备分组分页
|
||||
*
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
/**
|
||||
* 获得产品分页
|
||||
*
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue