From 3798e52ee0f1e0817a26317a280a1b6484f80842 Mon Sep 17 00:00:00 2001 From: daiyongfei <974332738@qq.com> Date: Tue, 18 Nov 2025 15:34:46 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AF=BC=E5=85=A5=E8=AE=BE=E5=A4=87=E6=95=B0?= =?UTF-8?q?=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../device/AppDeviceXinghanController.java | 6 +- .../bjq/AppDeviceBJQ6075Controller.java | 2 +- .../device/DeviceBJQ6075BizService.java | 6 + .../impl/DeviceBJQ6075BizServiceImpl.java | 8 +- .../core/utils/Bitmap80x12Generator.java | 28 +- .../core/utils/file/ImageCompressUtil.java | 91 +++++ .../common/log/aspect/LogAspect.java | 1 + .../app/domain/bo/AppPersonnelInfoBo.java | 1 + .../controller/DeviceController.java | 137 ++++++- .../converter/IgnoreFailedImageConverter.java | 158 +++++--- .../equipment/domain/DeviceType.java | 2 +- .../domain/dto/DeviceExcelImportDTO.java | 80 ++-- .../dto/DeviceWithTypeExcelExportDTO.java | 98 +++++ .../equipment/domain/form/DeviceForm.java | 23 +- .../equipment/excel/HeadValidateListener.java | 54 +++ .../excel/UploadDeviceDataListener.java | 372 ++++++++++++++++-- .../equipment/handler/ImageWriteHandler.java | 133 ++++--- .../equipment/mapper/DeviceTypeMapper.java | 9 + .../equipment/service/DeviceTypeService.java | 9 + .../service/impl/DeviceExportService.java | 209 +++++++++- .../service/impl/DeviceServiceImpl.java | 91 ++++- .../service/impl/DeviceTypeServiceImpl.java | 15 + .../mapper/equipment/DeviceTypeMapper.xml | 10 + 23 files changed, 1351 insertions(+), 192 deletions(-) create mode 100644 fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/file/ImageCompressUtil.java create mode 100644 fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/DeviceWithTypeExcelExportDTO.java create mode 100644 fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/excel/HeadValidateListener.java diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/controller/device/AppDeviceXinghanController.java b/fys-admin/src/main/java/com/fuyuanshen/app/controller/device/AppDeviceXinghanController.java index f7a1d2d..47f552a 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/app/controller/device/AppDeviceXinghanController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/app/controller/device/AppDeviceXinghanController.java @@ -38,6 +38,8 @@ public class AppDeviceXinghanController extends BaseController { private final DeviceXinghanBizService appDeviceService; private final DeviceService deviceService; + + /** * 人员信息登记 */ @@ -67,7 +69,7 @@ public class AppDeviceXinghanController extends BaseController { public R upload(@Validated @ModelAttribute AppDeviceLogoUploadDto bo) { MultipartFile file = bo.getFile(); - if(file.getSize()>1024*1024*2){ + if (file.getSize() > 1024 * 1024 * 2) { return R.warn("图片不能大于2M"); } appDeviceService.uploadDeviceLogo(bo); @@ -125,7 +127,7 @@ public class AppDeviceXinghanController extends BaseController { @GetMapping(value = "/typeAll") @Operation(summary = "查询所有设备类型") - public R> queryDeviceTypes() { + public R> queryDeviceTypes() { List deviceTypes = appDeviceService.queryDeviceTypes(); return R.ok(deviceTypes); } diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/controller/device/bjq/AppDeviceBJQ6075Controller.java b/fys-admin/src/main/java/com/fuyuanshen/app/controller/device/bjq/AppDeviceBJQ6075Controller.java index 4f8839d..29e38cf 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/app/controller/device/bjq/AppDeviceBJQ6075Controller.java +++ b/fys-admin/src/main/java/com/fuyuanshen/app/controller/device/bjq/AppDeviceBJQ6075Controller.java @@ -148,7 +148,7 @@ public class AppDeviceBJQ6075Controller extends BaseController { */ @GetMapping("/getShareInfo/{id}") public R getShareInfo(@NotNull(message = "主键不能为空") - @PathVariable Long id) { + @PathVariable Long id) { return R.ok(appDeviceService6075.getInfo(id)); } diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceBJQ6075BizService.java b/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceBJQ6075BizService.java index c543b8b..61c03cf 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceBJQ6075BizService.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceBJQ6075BizService.java @@ -69,6 +69,12 @@ public interface DeviceBJQ6075BizService { */ public void recordDeviceLog(Long deviceId, String deviceName, String deviceAction, String content, Long operator); + /** + * 注册人员信息 + * + * @param bo 参数 + * @return 结果 + */ public boolean registerPersonInfo(AppPersonnelInfoBo bo); public void uploadDeviceLogo2(AppDeviceLogoUploadDto bo); diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/service/impl/DeviceBJQ6075BizServiceImpl.java b/fys-admin/src/main/java/com/fuyuanshen/web/service/impl/DeviceBJQ6075BizServiceImpl.java index 74541f7..56d18e1 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/service/impl/DeviceBJQ6075BizServiceImpl.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/service/impl/DeviceBJQ6075BizServiceImpl.java @@ -251,6 +251,12 @@ public class DeviceBJQ6075BizServiceImpl implements DeviceBJQ6075BizService { } + /** + * 注册人员信息 + * + * @param bo + * @return + */ @Override public boolean registerPersonInfo(AppPersonnelInfoBo bo) { Long deviceId = bo.getDeviceId(); @@ -264,7 +270,7 @@ public class DeviceBJQ6075BizServiceImpl implements DeviceBJQ6075BizService { QueryWrapper qw = new QueryWrapper() .eq("device_id", deviceId); List appPersonnelInfoVos = appPersonnelInfoMapper.selectVoList(qw); -// unitName,position,name,id + // 生成固定长度的点阵数据 byte[] unitName = generateFixedBitmapData(bo.getUnitName(), 120); byte[] position = generateFixedBitmapData(bo.getPosition(), 120); byte[] name = generateFixedBitmapData(bo.getName(), 120); diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/Bitmap80x12Generator.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/Bitmap80x12Generator.java index 5a678e5..bda99d7 100644 --- a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/Bitmap80x12Generator.java +++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/Bitmap80x12Generator.java @@ -27,14 +27,14 @@ public class Bitmap80x12Generator { BufferedImage image = convertByteArrayToImage(bytes, 12, 80); ImageIO.write(image, "PNG", new File("D:\\bitmap_preview.png")); System.out.println("成功生成预览图片: D:\\bitmap_preview.png"); - + // 打印十六进制数据 // System.out.println("生成的点阵数据2:"); // printHexData(bitmapData); // int[] ints = convertHexToDecimal(bitmapData); - System.out.println("打印十进制无符号:"+Arrays.toString(ints)); + System.out.println("打印十进制无符号:" + Arrays.toString(ints)); // printDecimalData(bitmapData); - + // 生成C文件 generateCFile(bitmapData, "bitmap_data.c", "chinese_text"); } @@ -125,7 +125,7 @@ public class Bitmap80x12Generator { System.out.println(); } - public static void buildArr(int[] data,List intData){ + public static void buildArr(int[] data, List intData) { for (int datum : data) { intData.add(datum); } @@ -133,8 +133,8 @@ public class Bitmap80x12Generator { /** * 生成固定长度的点阵数据 - * - * @param text 要转换的文本 + * + * @param text 要转换的文本 * @param fixedLength 固定长度(字节) * @return 固定长度的点阵数据 */ @@ -157,10 +157,11 @@ public class Bitmap80x12Generator { int copyLength = Math.min(rawData.length, fixedLength); System.arraycopy(rawData, 0, result, 0, copyLength); // 剩余部分自动初始化为0 - + return result; } + /** * 创建文本图像 */ @@ -185,14 +186,14 @@ public class Bitmap80x12Generator { // 获取字体度量 FontMetrics metrics = g.getFontMetrics(); - + // 计算文本绘制位置(居中) int textWidth = metrics.stringWidth(text); // int x = Math.max(0, (width - textWidth) / 2); // 水平居中 // 左对齐 int x = 0; int y = (height - metrics.getHeight()) / 2 + metrics.getAscent(); // 垂直居中 - + // 绘制文本 g.drawString(text, x, y); @@ -242,6 +243,7 @@ public class Bitmap80x12Generator { return byteListToArray(byteList); } + public static byte[] byteListToArray(List byteList) { byte[] result = new byte[byteList.size()]; for (int i = 0; i < byteList.size(); i++) { @@ -282,7 +284,7 @@ public class Bitmap80x12Generator { } } bitIndex++; - + // 如果已经处理完所有像素,则退出 if (bitIndex >= width * height) { return image; @@ -313,6 +315,7 @@ public class Bitmap80x12Generator { sb.append("\n};"); return sb.toString(); } + /** * 打印十六进制数据 */ @@ -320,7 +323,7 @@ public class Bitmap80x12Generator { for (int i = 0; i < data.length; i++) { int value = data[i] & 0xFF; System.out.printf("0x%02X", value); - + if (i < data.length - 1) { System.out.print(", "); if ((i + 1) % 12 == 0) System.out.println(); @@ -348,6 +351,7 @@ public class Bitmap80x12Generator { } } + private static void writeByteArray(FileWriter writer, byte[] data) throws IOException { for (int i = 0; i < data.length; i++) { int value = data[i] & 0xFF; @@ -359,4 +363,6 @@ public class Bitmap80x12Generator { } } } + + } diff --git a/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/file/ImageCompressUtil.java b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/file/ImageCompressUtil.java new file mode 100644 index 0000000..c72e3e3 --- /dev/null +++ b/fys-common/fys-common-core/src/main/java/com/fuyuanshen/common/core/utils/file/ImageCompressUtil.java @@ -0,0 +1,91 @@ +package com.fuyuanshen.common.core.utils.file; + +import lombok.extern.slf4j.Slf4j; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +/** + * 图片压缩工具类 + */ +@Slf4j +public class ImageCompressUtil { + + /** + * 压缩图片到指定大小以下 + * + * @param imageData 原始图片数据 + * @param maxSize 最大大小(字节) + * @return 压缩后的图片数据 + */ + public static byte[] compressImage(byte[] imageData, int maxSize) { + try { + // 如果图片本身小于等于最大大小,直接返回 + if (imageData.length <= maxSize) { + return imageData; + } + + // 读取原始图片 + BufferedImage originalImage = ImageIO.read(new ByteArrayInputStream(imageData)); + if (originalImage == null) { + log.warn("无法读取图片数据"); + return imageData; + } + + // 计算压缩比例 + double scale = Math.sqrt((double) maxSize / imageData.length); + // 确保至少压缩到一半大小,避免压缩效果不明显 + scale = Math.max(scale, 0.5); + + // 压缩图片 + byte[] compressedData = compressImageByScale(originalImage, scale); + + // 如果压缩后还是太大,继续压缩 + int attempts = 0; + while (compressedData.length > maxSize && attempts < 5) { + scale *= 0.8; // 每次缩小20% + compressedData = compressImageByScale(originalImage, scale); + attempts++; + } + + log.info("图片压缩完成,原始大小: {} bytes, 压缩后大小: {} bytes, 压缩比例: {}", + imageData.length, compressedData.length, String.format("%.2f", scale)); + + return compressedData; + } catch (Exception e) { + log.error("图片压缩失败: {}", e.getMessage(), e); + return imageData; // 压缩失败时返回原始数据 + } + } + + + /** + * 按比例缩放图片 + * + * @param originalImage 原始图片 + * @param scale 缩放比例 + * @return 缩放后的图片数据 + * @throws IOException IO异常 + */ + private static byte[] compressImageByScale(BufferedImage originalImage, double scale) throws IOException { + int width = (int) (originalImage.getWidth() * scale); + int height = (int) (originalImage.getHeight() * scale); + + // 创建缩放后的图片 + Image scaledImage = originalImage.getScaledInstance(width, height, Image.SCALE_SMOOTH); + BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + Graphics2D g2d = bufferedImage.createGraphics(); + g2d.drawImage(scaledImage, 0, 0, null); + g2d.dispose(); + + // 输出为JPEG格式 + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ImageIO.write(bufferedImage, "jpg", baos); + return baos.toByteArray(); + } + +} diff --git a/fys-common/fys-common-log/src/main/java/com/fuyuanshen/common/log/aspect/LogAspect.java b/fys-common/fys-common-log/src/main/java/com/fuyuanshen/common/log/aspect/LogAspect.java index 0772e93..6b3d921 100644 --- a/fys-common/fys-common-log/src/main/java/com/fuyuanshen/common/log/aspect/LogAspect.java +++ b/fys-common/fys-common-log/src/main/java/com/fuyuanshen/common/log/aspect/LogAspect.java @@ -216,4 +216,5 @@ public class LogAspect { return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse || o instanceof BindingResult; } + } diff --git a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/bo/AppPersonnelInfoBo.java b/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/bo/AppPersonnelInfoBo.java index 70a7918..12cbf96 100644 --- a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/bo/AppPersonnelInfoBo.java +++ b/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/bo/AppPersonnelInfoBo.java @@ -64,4 +64,5 @@ public class AppPersonnelInfoBo extends BaseEntity { * ID号 */ private String code; + } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/controller/DeviceController.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/controller/DeviceController.java index 030452a..bd6f0c1 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/controller/DeviceController.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/controller/DeviceController.java @@ -13,12 +13,14 @@ import com.fuyuanshen.common.satoken.utils.LoginHelper; import com.fuyuanshen.common.web.core.BaseController; import com.fuyuanshen.customer.mapper.CustomerMapper; import com.fuyuanshen.equipment.domain.Device; +import com.fuyuanshen.equipment.domain.DeviceType; import com.fuyuanshen.equipment.domain.dto.DeviceExcelImportDTO; import com.fuyuanshen.equipment.domain.dto.ImportResult; import com.fuyuanshen.equipment.domain.form.DeviceForm; import com.fuyuanshen.equipment.domain.query.DeviceQueryCriteria; import com.fuyuanshen.equipment.domain.vo.CustomerVo; import com.fuyuanshen.equipment.excel.DeviceImportParams; +import com.fuyuanshen.equipment.excel.HeadValidateListener; import com.fuyuanshen.equipment.excel.UploadDeviceDataListener; import com.fuyuanshen.equipment.mapper.DeviceMapper; import com.fuyuanshen.equipment.mapper.DeviceTypeMapper; @@ -39,7 +41,10 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; +import java.util.Arrays; +import java.util.HashSet; import java.util.List; +import java.util.Set; /** * @Description: @@ -163,7 +168,7 @@ public class DeviceController extends BaseController { @Operation(summary = "导出数据设备") - @GetMapping(value = "/download") + @GetMapping(value = "/download1") public R exportDevice(HttpServletResponse response, DeviceQueryCriteria criteria) throws IOException { List devices = deviceService.queryAll(criteria); exportService.export(devices, response); @@ -171,9 +176,36 @@ public class DeviceController extends BaseController { } + /** + * 导出设备数据(包含完整设备类型信息) + * + * @param response HttpServletResponse对象 + * @param criteria 查询条件 + * @return R + */ + @Operation(summary = "导出设备数据(包含完整设备类型信息)") + @GetMapping(value = "/download") + public R exportDeviceWithFullTypeInfo(HttpServletResponse response, DeviceQueryCriteria criteria) { + // 获取所有符合条件的设备 + List devices = deviceService.queryAll(criteria); + // 获取所有设备类型信息 + List deviceTypes = deviceTypeService.queryDeviceTypes(); + // 导出数据(包含完整设备类型信息) + exportService.exportWithTypeInfo(devices, deviceTypes, response); + return R.ok(); + } + + + /** + * 导入设备数据 + * + * @param file + * @return + * @throws BadRequestException + */ @Operation(summary = "导入设备数据") - @PostMapping(value = "/import", consumes = "multipart/form-data") - public R importData(@Parameter(name = "文件", required = true) @RequestPart("file") MultipartFile file) throws BadRequestException { + @PostMapping(value = "/import1", consumes = "multipart/form-data") + public R importData1(@Parameter(name = "文件", required = true) @RequestPart("file") MultipartFile file) throws BadRequestException { String suffix = FileUtil.getExtensionName(file.getOriginalFilename()); if (!("xlsx".equalsIgnoreCase(suffix))) { @@ -207,6 +239,105 @@ public class DeviceController extends BaseController { } } + + /** + * 导入设备数据 + * + * @param file 文件 + * @return R + */ + @Operation(summary = "导入设备数据") + @PostMapping(value = "/import", consumes = "multipart/form-data") + public R importData(@Parameter(name = "文件", required = true) @RequestPart("file") MultipartFile file) throws BadRequestException { + + String suffix = FileUtil.getExtensionName(file.getOriginalFilename()); + if (!("xlsx".equalsIgnoreCase(suffix))) { + throw new BadRequestException("只能上传Excel——xlsx格式文件"); + } + + // 检查文件大小,限制为100MB + if (file.getSize() > 100 * 10 * 1024 * 1024) { + throw new BadRequestException("文件大小不能超过100MB"); + } + + // 校验模板 + validateExcelTemplate(file); + + ImportResult result = new ImportResult(); + try { + LoginUser loginUser = LoginHelper.getLoginUser(); + DeviceImportParams params = DeviceImportParams.builder().ossService(ossService) + .deviceService(deviceService).tenantId(loginUser.getTenantId()) + .file(file).filePath("").deviceMapper(deviceMapper).deviceTypeService(deviceTypeService) + .deviceTypeMapper(deviceTypeMapper).userId(loginUser.getUserId()) + .build(); + // 创建监听器 + UploadDeviceDataListener listener = new UploadDeviceDataListener(params); + // 读取Excel + EasyExcel.read(file.getInputStream(), DeviceExcelImportDTO.class, listener).sheet().doRead(); + // 获取导入结果 + result = listener.getImportResult(); + // 设置响应消息 + String message = String.format("成功导入 %d 条数据,失败 %d 条", result.getSuccessCount(), result.getFailureCount()); + // 返回带有正确泛型的响应 + return R.ok(message, result); + } catch (Exception e) { + log.error("导入设备数据出错: {}", e.getMessage(), e); + // 在异常情况下,设置默认结果 + String errorMessage = String.format("导入失败: %s。成功 %d 条,失败 %d 条", e.getMessage(), result.getSuccessCount(), result.getFailureCount()); + // 使用新方法确保类型正确 + return R.fail(errorMessage, result); + } + } + + + /** + * 校验Excel模板是否正确 + * + * @param file MultipartFile对象 + * @throws BadRequestException 当模板不正确时抛出异常 + */ + private void validateExcelTemplate(MultipartFile file) throws BadRequestException { + try { + // 创建一个只读取表头的监听器 + HeadValidateListener headValidateListener = new HeadValidateListener(); + + // 使用EasyExcel读取表头 + EasyExcel.read(file.getInputStream(), headValidateListener) + .sheet() + .headRowNumber(0) + .doRead(); + + // 获取读取到的表头信息 + List actualHeaders = headValidateListener.getHeadNames(); + + // 定义必需的表头 + Set requiredHeaders = new HashSet<>(Arrays.asList( + "设备名称", "设备类型名称", "设备图片", "设备MAC", "蓝牙名称", "设备IMEI", + "备注", "是否支持蓝牙", "定位方式", "通讯方式", + "型号字典用于APP页面跳转", "型号字典用于PC页面跳转" + )); + + // 检查必需的表头是否都存在 + Set actualHeaderSet = new HashSet<>(actualHeaders); + if (!actualHeaderSet.containsAll(requiredHeaders)) { + requiredHeaders.removeAll(actualHeaderSet); + throw new BadRequestException("Excel模板缺少必需的列: " + String.join(", ", requiredHeaders)); + } + + // 检查第三列(索引为2)是否为"设备图片" + if (actualHeaders.size() > 2 && !"设备图片".equals(actualHeaders.get(2))) { + throw new BadRequestException("Excel模板不正确,第三列必须是'设备图片'列"); + } + } catch (BadRequestException e) { + throw e; // 直接重新抛出 + } catch (Exception e) { + log.error("校验Excel模板时发生错误: {}", e.getMessage(), e); + throw new BadRequestException("校验Excel模板时发生错误: " + e.getMessage()); + } + } + + } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/converter/IgnoreFailedImageConverter.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/converter/IgnoreFailedImageConverter.java index 6795efa..1023870 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/converter/IgnoreFailedImageConverter.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/converter/IgnoreFailedImageConverter.java @@ -20,17 +20,17 @@ import java.net.URLConnection; public class IgnoreFailedImageConverter implements Converter { private static final Logger logger = LoggerFactory.getLogger(IgnoreFailedImageConverter.class); + + // 重试次数 + private static final int MAX_RETRIES = 3; + // 指数退避初始延迟(毫秒) + private static final int INITIAL_DELAY = 1000; @Override public Class supportJavaTypeKey() { return URL.class; } - // @Override - // public CellDataTypeEnum supportExcelTypeKey() { - // return CellDataTypeEnum.STRING; - // } - @Override public WriteCellData convertToExcelData(URL value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { if (value == null) { @@ -38,60 +38,108 @@ public class IgnoreFailedImageConverter implements Converter { return new WriteCellData<>(new byte[0]); } - try { - logger.debug("开始加载图片: {}", value); - URLConnection conn = value.openConnection(); - // 增加连接和读取超时时间 - conn.setConnectTimeout(10000); // 10秒连接超时 - conn.setReadTimeout(30000); // 30秒读取超时 - - // 添加User-Agent避免被服务器拦截 - conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) ExcelExporter/1.0"); - - // 如果是HTTP连接,设置一些额外的属性 - if (conn instanceof HttpURLConnection) { - HttpURLConnection httpConn = (HttpURLConnection) conn; - httpConn.setRequestMethod("GET"); - // 不使用缓存 - httpConn.setUseCaches(false); - // 跟随重定向 - httpConn.setInstanceFollowRedirects(true); - } - - long contentLength = conn.getContentLengthLong(); - logger.debug("连接建立成功,图片大小: {} 字节", contentLength); - - // 检查内容长度是否有效 - if (contentLength == 0) { - logger.warn("图片文件为空: {}", value); - return new WriteCellData<>(new byte[0]); - } - - // 限制图片大小(防止过大文件导致内存问题) - if (contentLength > 10 * 1024 * 1024) { // 10MB限制 - logger.warn("图片文件过大 ({} bytes),跳过加载: {}", contentLength, value); - return new WriteCellData<>(new byte[0]); - } - - try (InputStream inputStream = conn.getInputStream()) { - // byte[] bytes = FileUtils.readInputStream(inputStream, value.toString()); - // 替代 FileUtils.readInputStream 的自定义方法 - byte[] bytes = readInputStream(inputStream); + // 尝试多次加载图片 + for (int attempt = 1; attempt <= MAX_RETRIES; attempt++) { + try { + logger.debug("开始加载图片: {}, 尝试次数: {}", value, attempt); + URLConnection conn = value.openConnection(); + // 增加连接和读取超时时间 + conn.setConnectTimeout(10000); // 10秒连接超时 + conn.setReadTimeout(30000); // 30秒读取超时 - // 检查读取到的数据是否为空 - if (bytes == null || bytes.length == 0) { - logger.warn("读取到空的图片数据: {}", value); - return new WriteCellData<>(new byte[0]); + // 添加User-Agent避免被服务器拦截 + conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 ExcelExporter/1.0"); + + // 如果是HTTP连接,设置一些额外的属性 + if (conn instanceof HttpURLConnection) { + HttpURLConnection httpConn = (HttpURLConnection) conn; + httpConn.setRequestMethod("GET"); + // 不使用缓存 + httpConn.setUseCaches(false); + // 跟随重定向 + httpConn.setInstanceFollowRedirects(true); + + // 检查响应码 + int responseCode = httpConn.getResponseCode(); + if (responseCode != HttpURLConnection.HTTP_OK) { + logger.warn("HTTP响应码异常: {}, URL: {}", responseCode, value); + if (attempt < MAX_RETRIES) { + // 等待后重试 + waitForRetry(attempt); + continue; + } else { + return new WriteCellData<>(new byte[0]); + } + } + } + + long contentLength = conn.getContentLengthLong(); + logger.debug("连接建立成功,图片大小: {} 字节", contentLength); + + // 检查内容长度是否有效 + if (contentLength == 0) { + logger.warn("图片文件为空: {}", value); + if (attempt < MAX_RETRIES) { + waitForRetry(attempt); + continue; + } else { + return new WriteCellData<>(new byte[0]); + } } - logger.debug("成功读取图片数据,大小: {} 字节", bytes.length); - return new WriteCellData<>(bytes); + // 限制图片大小(防止过大文件导致内存问题) + if (contentLength > 10 * 1024 * 1024) { // 10MB限制 + logger.warn("图片文件过大 ({} bytes),跳过加载: {}", contentLength, value); + return new WriteCellData<>(new byte[0]); + } + + try (InputStream inputStream = conn.getInputStream()) { + // byte[] bytes = FileUtils.readInputStream(inputStream, value.toString()); + // 替代 FileUtils.readInputStream 的自定义方法 + byte[] bytes = readInputStream(inputStream); + + // 检查读取到的数据是否为空 + if (bytes == null || bytes.length == 0) { + logger.warn("读取到空的图片数据: {}", value); + if (attempt < MAX_RETRIES) { + waitForRetry(attempt); + continue; + } else { + return new WriteCellData<>(new byte[0]); + } + } + + logger.debug("成功读取图片数据,大小: {} 字节", bytes.length); + return new WriteCellData<>(bytes); + } + } catch (Exception e) { + logger.warn("图片加载失败: {}, 尝试次数: {}, 原因: {}", value, attempt, e.getMessage(), e); + if (attempt < MAX_RETRIES) { + // 等待后重试 + waitForRetry(attempt); + } else { + // 最后一次尝试也失败了 + logger.error("图片加载最终失败,已重试 {} 次: {}", MAX_RETRIES, value, e); + return new WriteCellData<>(new byte[0]); // 返回空数组而不是 null + } } - } catch (Exception e) { - // 静默忽略错误,只记录日志 - logger.warn("图片加载失败: {}, 原因: {}", value, e.getMessage(), e); - // return null; // 返回null表示不写入图片 - return new WriteCellData<>(new byte[0]); // 返回空数组而不是 null + } + + // 所有尝试都失败了 + return new WriteCellData<>(new byte[0]); + } + + /** + * 等待重试,使用指数退避策略 + * @param attempt 当前尝试次数 + */ + private void waitForRetry(int attempt) { + try { + long delay = (long) INITIAL_DELAY * (1L << (attempt - 1)); // 指数退避 + logger.debug("等待 {} 毫秒后重试...", delay); + Thread.sleep(delay); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); } } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceType.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceType.java index bfef80b..f274023 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceType.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceType.java @@ -60,7 +60,7 @@ public class DeviceType extends TenantEntity { private String networkWay; @Schema(title = "通讯方式", example = "通讯方式 0:4G;1:蓝牙,2 4G&蓝牙") - private Integer communicationMode; + private String communicationMode; /** * 创建人名称 diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/DeviceExcelImportDTO.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/DeviceExcelImportDTO.java index 8c4204e..a059f3d 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/DeviceExcelImportDTO.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/DeviceExcelImportDTO.java @@ -1,24 +1,21 @@ package com.fuyuanshen.equipment.domain.dto; +import com.alibaba.excel.annotation.ExcelIgnore; import com.alibaba.excel.annotation.ExcelProperty; import com.alibaba.excel.annotation.write.style.ColumnWidth; import com.alibaba.excel.annotation.write.style.ContentRowHeight; import com.alibaba.excel.annotation.write.style.HeadRowHeight; -import com.alibaba.excel.converters.bytearray.ByteArrayImageConverter; import lombok.Data; /** * @author: 默苍璃 - * @date: 2025-06-0710:00 + * @date: 2025-06-07 10:00 */ @Data @HeadRowHeight(20) // 表头行高 @ContentRowHeight(100) // 内容行高 public class DeviceExcelImportDTO { - // @ExcelProperty("设备类型") - // private Long deviceType; - @ExcelProperty("设备名称") @ColumnWidth(20) private String deviceName; @@ -26,14 +23,23 @@ public class DeviceExcelImportDTO { @ExcelProperty("设备类型名称") private String typeName; - // @ExcelProperty(value = "设备图片", converter = ByteArrayImageConverter.class) - // @ColumnWidth(15) - // private byte[] devicePic; - // - // // 添加图片写入方法 - // public void setDevicePicFromBytes(byte[] bytes) { - // this.devicePic = bytes; - // } + /** + * 设备图片 + * 导入时的图片处理方式: + * 在导入过程中,图片不是通过Excel单元格中的文本数据(如URL)来处理的 + * 而是直接从Excel文件中提取嵌入的图片数据 + * 代码通过POI库直接读取Excel中的图片对象,然后上传到OSS并生成URL + */ + @ExcelProperty("设备图片") + @ColumnWidth(20) + private byte[] devicePic; + + /** + * 用于存储实际图片数据的字段 + * 使用@ExcelIgnore注解忽略该字段,避免创建额外的列 + */ + @ExcelIgnore + private byte[] imageData; @ExcelProperty("设备MAC") @ColumnWidth(20) @@ -46,18 +52,46 @@ public class DeviceExcelImportDTO { @ExcelProperty("蓝牙名称") private String bluetoothName; - // @ExcelProperty("设备SN") - // @ColumnWidth(20) - // private String deviceSn; - // - // @ExcelProperty("经度") - // private String longitude; - // - // @ExcelProperty("纬度") - // private String latitude; - @ExcelProperty("备注") @ColumnWidth(30) private String remark; + /* + *设备类型数据 + */ + @ExcelProperty("是否支持蓝牙") + @ColumnWidth(15) + private String isSupportBle; + + @ExcelProperty("定位方式") + @ColumnWidth(20) + private String locateMode; + + @ExcelProperty("通讯方式") + @ColumnWidth(20) + private String communicationMode; + + /** + * 型号字典用于APP页面跳转 + * app_model_dictionary + */ + @ExcelProperty("型号字典用于APP页面跳转") + @ColumnWidth(20) + private String appModelDictionary; + + /** + * 型号字典用于PC页面跳转 + * pc_model_dictionary + */ + @ExcelProperty("型号字典用于PC页面跳转") + @ColumnWidth(20) + private String pcModelDictionary; + + /** + * 错误原因 + */ + @ExcelProperty("错误原因") + @ColumnWidth(30) + private String errorMessage; + } \ No newline at end of file diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/DeviceWithTypeExcelExportDTO.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/DeviceWithTypeExcelExportDTO.java new file mode 100644 index 0000000..6ef7b5f --- /dev/null +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/DeviceWithTypeExcelExportDTO.java @@ -0,0 +1,98 @@ +package com.fuyuanshen.equipment.domain.dto; + +import com.alibaba.excel.annotation.ExcelProperty; +import com.alibaba.excel.annotation.write.style.ColumnWidth; +import com.alibaba.excel.annotation.write.style.ContentRowHeight; +import com.alibaba.excel.annotation.write.style.HeadRowHeight; +import lombok.Data; + +import java.net.URL; + +/** + * 设备及完整类型信息导出DTO + * + * @author: 默苍璃 + * @date: 2025-11-0416:25 + */ +@Data +@HeadRowHeight(20) // 表头行高 +@ContentRowHeight(100) // 内容行高 +public class DeviceWithTypeExcelExportDTO { + + @ExcelProperty("设备名称") + @ColumnWidth(20) + private String deviceName; + + @ExcelProperty("设备类型名称") + @ColumnWidth(20) + private String typeName; + + @ExcelProperty(value = "设备图片") + @ColumnWidth(30) // 设置图片列宽度 + private URL devicePic; // 使用URL类型 + + @ExcelProperty("设备MAC") + @ColumnWidth(20) + private String deviceMac; + + @ExcelProperty("蓝牙名称") + @ColumnWidth(20) + private String bluetoothName; + + @ExcelProperty("设备IMEI") + @ColumnWidth(20) + private String deviceImei; + + @ExcelProperty("备注") + @ColumnWidth(30) + private String remark; + + /** + * 绑定状态 + * 0 未绑定 + * 1 已绑定 + */ + @ExcelProperty("绑定状态") + @ColumnWidth(20) + private String bindingStatus; + + @ExcelProperty("创建时间") + @ColumnWidth(20) + private String createTime; + + @ExcelProperty("创建人") + @ColumnWidth(20) + private String createBy; + + /* + *设备类型数据 + */ + @ExcelProperty("是否支持蓝牙") + @ColumnWidth(15) + private String isSupportBle; + + @ExcelProperty("定位方式") + @ColumnWidth(20) + private String locateMode; + + @ExcelProperty("通讯方式") + @ColumnWidth(20) + private String communicationMode; + + /** + * 型号字典用于APP页面跳转 + * app_model_dictionary + */ + @ExcelProperty("型号字典用于APP页面跳转") + @ColumnWidth(20) + private String appModelDictionary; + + /** + * 型号字典用于PC页面跳转 + * pc_model_dictionary + */ + @ExcelProperty("型号字典用于PC页面跳转") + @ColumnWidth(20) + private String pcModelDictionary; + +} diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/form/DeviceForm.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/form/DeviceForm.java index 76e9398..def13d1 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/form/DeviceForm.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/form/DeviceForm.java @@ -26,8 +26,6 @@ public class DeviceForm { @Schema(title = "客户号") private Long customerId; - /*@Schema(value = "设备编号") - private String deviceNo;*/ @NotBlank(message = "设备名称不能为空") @Schema(title = "设备名称", required = true) @@ -56,4 +54,25 @@ public class DeviceForm { @Schema(title = "备注") private String remark; + + // 设备类型相关字段 + @Schema(title = "设备类型名称") + private String typeName; + + @Schema(title = "是否支持蓝牙") + private String isSupportBle; + + @Schema(title = "定位方式") + private String locateMode; + + @Schema(title = "通讯方式") + private String communicationMode; + + @Schema(title = "APP模型字典") + private String appModelDictionary; + + @Schema(title = "PC模型字典") + private String pcModelDictionary; + + } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/excel/HeadValidateListener.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/excel/HeadValidateListener.java new file mode 100644 index 0000000..2628a19 --- /dev/null +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/excel/HeadValidateListener.java @@ -0,0 +1,54 @@ +package com.fuyuanshen.equipment.excel; + +import com.alibaba.excel.context.AnalysisContext; +import com.alibaba.excel.event.AnalysisEventListener; +import com.alibaba.fastjson2.JSON; +import lombok.extern.slf4j.Slf4j; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * 读取表头 监听器 + * + * @author: 默苍璃 + * @date: 2025-11-1809:36 + */ +@Slf4j +public class HeadValidateListener extends AnalysisEventListener> { + + private List headNames = new ArrayList<>(); + + + /** + * When analysis one row trigger invoke function. + * + * @param headMap one row value. It is same as {@link AnalysisContext#readRowHolder()} + * @param context analysis context + */ + @Override + public void invoke(Map headMap, AnalysisContext context) { + log.info("解析到一条数据: {}", JSON.toJSONString(headMap)); + // 按顺序收集表头名称 + for (int i = 0; i < headMap.size(); i++) { + headNames.add(headMap.get(i)); + } + } + + /** + * if have something to do after all analysis + * + * @param context + */ + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + // 读取完成后不需要额外操作 + } + + + public List getHeadNames() { + return headNames; + } + +} diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/excel/UploadDeviceDataListener.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/excel/UploadDeviceDataListener.java index cdaf843..ba7e7b7 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/excel/UploadDeviceDataListener.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/excel/UploadDeviceDataListener.java @@ -5,8 +5,7 @@ import com.alibaba.excel.EasyExcel; import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.read.listener.ReadListener; import com.alibaba.fastjson2.JSON; -import com.fuyuanshen.common.core.domain.model.LoginUser; -import com.fuyuanshen.common.satoken.utils.LoginHelper; +import com.fuyuanshen.common.core.utils.file.ImageCompressUtil; import com.fuyuanshen.equipment.constants.DeviceConstants; import com.fuyuanshen.equipment.domain.Device; import com.fuyuanshen.equipment.domain.DeviceType; @@ -46,38 +45,41 @@ public class UploadDeviceDataListener implements ReadListener failedRecordsWithImages = new ArrayList<>(); - for (DeviceExcelImportDTO failedRecord : failedRecords) { + // 创建一个映射,用于存储失败记录行号与图片数据的关系 + Map errorReportImageMap = new HashMap<>(); + + for (int i = 0; i < failedRecords.size(); i++) { + DeviceExcelImportDTO failedRecord = failedRecords.get(i); + // 创建副本,避免修改原始数据 + DeviceExcelImportDTO recordWithImage = new DeviceExcelImportDTO(); + BeanUtil.copyProperties(failedRecord, recordWithImage); + // 设置图片占位符,让用户知道这里有图片 + // recordWithImage.setDevicePic("[图片]"); + failedRecordsWithImages.add(recordWithImage); + // 获取原始行号 - Integer rowIndex = null; + Integer originalRowIndex = null; for (Map.Entry entry : rowDtoMap.entrySet()) { if (entry.getValue() == failedRecord) { - rowIndex = entry.getKey(); + originalRowIndex = entry.getKey(); break; } } - if (rowIndex != null) { - // 创建副本,避免修改原始数据 - DeviceExcelImportDTO recordWithImage = new DeviceExcelImportDTO(); - BeanUtil.copyProperties(failedRecord, recordWithImage); - - // 设置图片数据 - byte[] imageData = rowImageMap.get(rowIndex); + // 保存图片数据用于错误报告 + if (originalRowIndex != null) { + byte[] imageData = rowImageMap.get(originalRowIndex); if (imageData != null) { - // recordWithImage.setDevicePicFromBytes(imageData); + errorReportImageMap.put(i, imageData); // 使用新的列表索引 } - - failedRecordsWithImages.add(recordWithImage); - } else { - failedRecordsWithImages.add(failedRecord); } } @@ -99,7 +101,8 @@ public class UploadDeviceDataListener implements ReadListener failedRecordsWithImages = new ArrayList<>(); + + for (int i = 0; i < failedRecords.size(); i++) { + DeviceExcelImportDTO failedRecord = failedRecords.get(i); + // 创建副本,避免修改原始数据 + DeviceExcelImportDTO recordWithImage = new DeviceExcelImportDTO(); + BeanUtil.copyProperties(failedRecord, recordWithImage); + + // 获取原始行号 + Integer originalRowIndex = null; + for (Map.Entry entry : rowDtoMap.entrySet()) { + if (entry.getValue() == failedRecord) { + originalRowIndex = entry.getKey(); + break; + } + } + + // 保存图片数据用于错误报告 + if (originalRowIndex != null) { + byte[] imageData = rowImageMap.get(originalRowIndex); + if (imageData != null) { + // recordWithImage.setImageData(imageData); + recordWithImage.setDevicePic(imageData); + } + } + + failedRecordsWithImages.add(recordWithImage); + } + + result.setFailedRecords(failedRecordsWithImages); + + // 生成错误报告 + if (!failedRecordsWithImages.isEmpty()) { + try { + // 生成唯一的文件名 + String fileName = "import_errors_" + System.currentTimeMillis() + ".xlsx"; + + // 创建错误报告目录 + String errorDirPath = params.getFilePath() + DeviceConstants.ERROR_REPORT_DIR; + File errorDir = new File(errorDirPath); + if (!errorDir.exists() && !errorDir.mkdirs()) { + log.error("无法创建错误报告目录: {}", errorDirPath); + return result; + } + + // 保存错误报告到文件(包含图片) + File errorFile = new File(errorDir, fileName); + EasyExcel.write(errorFile, DeviceExcelImportDTO.class) + .sheet("失败数据").doWrite(failedRecordsWithImages); + + // 生成访问URL + SysOssVo upload = params.getOssService().upload(errorFile); + String url = upload.getUrl(); + // 将http://替换为https://,但不影响已经是https://的URL + if (url.startsWith("http://")) { + url = "https://" + url.substring(7); + } + result.setErrorExcelUrl(url); + log.info("错误报告已保存: {}", errorFile.getAbsolutePath()); + log.info("错误报告已保存: {}", url); + } catch (Exception e) { + log.error("生成错误报告失败", e); + } + } + + return result; + } @Override public void invoke(DeviceExcelImportDTO data, AnalysisContext context) { @@ -142,14 +218,232 @@ public class UploadDeviceDataListener implements ReadListener typeNames = new HashSet<>(); + for (Integer rowIndex : rowIndexList) { + Device device = rowDeviceMap.get(rowIndex); + if (device != null && device.getTypeName() != null) { + typeNames.add(device.getTypeName()); + } + } + + // 批量查询所有设备类型 + Map deviceTypeMap = new HashMap<>(); + if (!typeNames.isEmpty()) { + List deviceTypes = params.getDeviceTypeService().queryByNames(new ArrayList<>(typeNames)); + for (DeviceType deviceType : deviceTypes) { + deviceTypeMap.put(deviceType.getTypeName(), deviceType); + } + } + + for (Integer rowIndex : rowIndexList) { + Device device = rowDeviceMap.get(rowIndex); + DeviceExcelImportDTO originalDto = rowDtoMap.get(rowIndex); + try { + // 从缓存中获取设备类型 + DeviceType deviceType = deviceTypeMap.get(device.getTypeName()); + + DeviceForm deviceForm = new DeviceForm(); + deviceForm.setDeviceName(device.getDeviceName()); + deviceForm.setDeviceType(deviceType != null ? deviceType.getId() : null); + deviceForm.setTypeName(device.getTypeName()); + deviceForm.setRemark(device.getRemark()); + deviceForm.setDeviceMac(device.getDeviceMac()); + deviceForm.setDeviceImei(device.getDeviceImei()); + deviceForm.setBluetoothName(device.getBluetoothName()); + deviceForm.setDevicePic(device.getDevicePic()); + + // 设置设备类型相关信息,供设备服务内部创建设备类型时使用 + if (deviceType == null) { + deviceForm.setIsSupportBle(originalDto.getIsSupportBle()); + deviceForm.setLocateMode(originalDto.getLocateMode()); + deviceForm.setCommunicationMode(originalDto.getCommunicationMode()); + deviceForm.setAppModelDictionary(originalDto.getAppModelDictionary()); + deviceForm.setPcModelDictionary(originalDto.getPcModelDictionary()); + } + + params.getDeviceService().addDevice(deviceForm); + successCount++; + log.info("行 {} 数据插入成功", rowIndex); + } catch (Exception e) { + failureCount++; + originalDto.setErrorMessage(e.getMessage()); + // originalDto.setErrorMessage("数据有误,请核对模板后,确认数据无误后,重新再试!!!"); + failedRecords.add(originalDto); + log.error("行 {} 数据插入失败: {}", rowIndex, e.getMessage()); + } + } + } + + /** + * 处理图片数据 + */ + private void processImages() { + // 如果没有数据行,直接返回 + if (rowIndexList.isEmpty()) { + return; + } + + // 检查是否有图片需要处理 + try { + // 只在确实有图片时才打开文件处理 + boolean hasImages = false; + try (OPCPackage opcPackage = OPCPackage.open(params.getFile().getInputStream())) { + ZipSecureFile.setMinInflateRatio(-1.0d); + XSSFWorkbook workbook = new XSSFWorkbook(opcPackage); + XSSFSheet sheet = workbook.getSheetAt(0); + + XSSFDrawing drawing = sheet.getDrawingPatriarch(); + if (drawing != null) { + for (XSSFShape shape : drawing.getShapes()) { + if (shape instanceof XSSFPicture) { + hasImages = true; + break; + } + } + } + } catch (Exception e) { + log.warn("检查图片时发生异常: {}", e.getMessage()); + // 如果检查图片失败,继续处理数据 + return; + } + + // 如果没有图片,直接返回 + if (!hasImages) { + log.info("未检测到图片,跳过图片处理"); + return; + } + + // 有图片时才进行处理 + try (OPCPackage opcPackage = OPCPackage.open(params.getFile().getInputStream())) { + ZipSecureFile.setMinInflateRatio(-1.0d); + XSSFWorkbook workbook = new XSSFWorkbook(opcPackage); + XSSFSheet sheet = workbook.getSheetAt(0); + + XSSFDrawing drawing = sheet.getDrawingPatriarch(); + if (drawing == null) return; + + for (XSSFShape shape : drawing.getShapes()) { + if (shape instanceof XSSFPicture) { + XSSFPicture picture = (XSSFPicture) shape; + XSSFClientAnchor anchor = picture.getPreferredSize(); + int rowIndex = anchor.getRow1(); + int colIndex = anchor.getCol1(); + + if (colIndex == 2) { + Device device = rowDeviceMap.get(rowIndex); + if (device != null) { + try { + byte[] imageData = picture.getPictureData().getData(); + + // 检查图片大小,如果超过5MB则拒绝上传 + if (imageData.length > 5 * 1024 * 1024) { + String errorMsg = "图片大小超过5MB限制,请压缩后重新上传"; + log.warn("行 {} 图片过大: {} bytes", rowIndex, imageData.length); + // 将错误信息添加到该行的DTO中 + DeviceExcelImportDTO dto = rowDtoMap.get(rowIndex); + if (dto != null) { + dto.setErrorMessage(errorMsg); + failedRecords.add(dto); + failureCount++; + } + device.setDevicePic(null); // 设置为空,让插入继续 + continue; // 跳过当前图片处理 + } + + // 表示Excel表格中的第3列(因为索引从0开始计算) + String extraValue = getCellValue(sheet, rowIndex, 2); + String imageUrl = uploadAndGenerateUrl(imageData, extraValue); + device.setDevicePic(imageUrl); + + // 2. 保存图片数据到DTO,用于错误报告 + rowImageMap.put(rowIndex, imageData); + } catch (Exception e) { + log.error("行 {} 图片处理失败: {}", rowIndex, e.getMessage()); + device.setDevicePic(null); // 设置为空,让插入继续 + } + } + } + } + } + } catch (Exception e) { + log.error("图片处理失败:{}", e.getMessage(), e); + } + } catch (Exception e) { + log.warn("图片处理过程中发生异常: {}", e.getMessage()); + } + } + + + private void processDataRowByRow1() { for (Integer rowIndex : rowIndexList) { Device device = rowDeviceMap.get(rowIndex); DeviceExcelImportDTO originalDto = rowDtoMap.get(rowIndex); try { // 设备类型 DeviceType deviceType = params.getDeviceTypeService().queryByName(device.getTypeName()); - // params.getDeviceService().save(device); + + // 如果设备类型不存在,则创建新的设备类型 + if (deviceType == null) { + DeviceType newDeviceType = new DeviceType(); + newDeviceType.setTypeName(device.getTypeName()); + newDeviceType.setIsSupportBle("是".equals(originalDto.getIsSupportBle()) || "1".equals(originalDto.getIsSupportBle())); + + // 设置定位方式 + if (originalDto.getLocateMode() != null) { + switch (originalDto.getLocateMode()) { + case "无": + newDeviceType.setLocateMode("0"); + break; + case "GPS": + newDeviceType.setLocateMode("1"); + break; + case "基站": + newDeviceType.setLocateMode("2"); + break; + case "wifi": + newDeviceType.setLocateMode("3"); + break; + case "北斗": + newDeviceType.setLocateMode("4"); + break; + default: + newDeviceType.setLocateMode(originalDto.getLocateMode()); + } + } + + // 设置通讯方式 + if (originalDto.getCommunicationMode() != null) { + switch (originalDto.getCommunicationMode()) { + case "4G": + newDeviceType.setCommunicationMode("0"); + break; + case "蓝牙": + newDeviceType.setCommunicationMode("1"); + break; + case "4G&蓝牙": + newDeviceType.setCommunicationMode("2"); + break; + default: + newDeviceType.setCommunicationMode(originalDto.getCommunicationMode()); + } + } + + newDeviceType.setAppModelDictionary(originalDto.getAppModelDictionary()); + newDeviceType.setPcModelDictionary(originalDto.getPcModelDictionary()); + + // 创建新的设备类型 + params.getDeviceTypeService().create(newDeviceType); + + // 重新查询确保获取到正确的ID + deviceType = params.getDeviceTypeService().queryByName(device.getTypeName()); + } + DeviceForm deviceForm = new DeviceForm(); deviceForm.setDeviceName(device.getDeviceName()); deviceForm.setDeviceType(deviceType.getId()); @@ -163,13 +457,19 @@ public class UploadDeviceDataListener implements ReadListener 5 * 1024 * 1024) { + String errorMsg = "图片大小超过5MB限制,请压缩后重新上传"; + log.warn("行 {} 图片过大: {} bytes", rowIndex, imageData.length); + // 将错误信息添加到该行的DTO中 + DeviceExcelImportDTO dto = rowDtoMap.get(rowIndex); + if (dto != null) { + dto.setErrorMessage(errorMsg); + failedRecords.add(dto); + failureCount++; + } + device.setDevicePic(null); // 设置为空,让插入继续 + continue; // 跳过当前图片处理 + } + // 表示Excel表格中的第3列(因为索引从0开始计算) String extraValue = getCellValue(sheet, rowIndex, 2); String imageUrl = uploadAndGenerateUrl(imageData, extraValue); @@ -224,9 +540,16 @@ public class UploadDeviceDataListener implements ReadListener 500 * 1024) { + log.info("检测到大图片 ({} bytes),正在进行压缩优化...", imageData.length); + imageData = ImageCompressUtil.compressImage(imageData, 500 * 1024); // 压缩到500KB以下 + } + String fileExtension = "jpg"; String newFileName = "PS_" + new Random(8) + "." + fileExtension; SysOssVo upload = params.getOssService().upload(imageData, newFileName); + log.info("图片保存成功,URL: {}", upload.getUrl()); return upload.getUrl(); } catch (Exception e) { log.error("保存图片失败", e); @@ -234,4 +557,5 @@ public class UploadDeviceDataListener implements ReadListener imageMap; + private int imageColIndex; + + public ImageWriteHandler(Map imageMap) { + this(imageMap, 2); + } + + public ImageWriteHandler(Map imageMap, int imageColIndex) { + this.imageMap = imageMap; + this.imageColIndex = imageColIndex; + } + + @Override + public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) { + if (imageMap == null || imageMap.isEmpty()) { + return; + } + + Workbook workbook = writeWorkbookHolder.getWorkbook(); + Sheet sheet = writeSheetHolder.getSheet(); + + // 设置图片列的宽度 + sheet.setColumnWidth(imageColIndex, 20 * 256); // 20个字符宽度 + + for (Map.Entry entry : imageMap.entrySet()) { + int dataRowIndex = entry.getKey(); + int rowIndex = dataRowIndex + 1; // 跳过标题行 + byte[] imageData = entry.getValue(); + + if (imageData != null && imageData.length > 0) { + insertImage(workbook, sheet, rowIndex, imageData); + } + } + } + + private void insertImage(Workbook workbook, Sheet sheet, int rowIndex, byte[] imageData) { + try { + // 获取或创建行(确保行存在) + Row row = sheet.getRow(rowIndex); + if (row == null) { + row = sheet.createRow(rowIndex); + } + + // 设置合适的行高(不要设置过大) + row.setHeightInPoints(60); // 60磅,足够显示小图 + + // 添加图片到工作簿 + int pictureIdx = workbook.addPicture(imageData, Workbook.PICTURE_TYPE_JPEG); + + // 创建绘图对象 + Drawing drawing = sheet.createDrawingPatriarch(); + + // 关键修改:使用MOVE_DONT_RESIZE锚点类型,避免影响行高 + ClientAnchor anchor = workbook.getCreationHelper().createClientAnchor(); + anchor.setAnchorType(ClientAnchor.AnchorType.MOVE_DONT_RESIZE); + anchor.setCol1(imageColIndex); + anchor.setRow1(rowIndex); + anchor.setCol2(imageColIndex + 1); + anchor.setRow2(rowIndex + 1); + + // 设置较小的偏移量,确保图片在单元格内 + anchor.setDx1(0); + anchor.setDy1(0); + anchor.setDx2(512 * 5); // 约5个字符宽度 + anchor.setDy2(256 * 4); // 约4行高度 + + // 插入图片 + Picture picture = drawing.createPicture(anchor, pictureIdx); + + // 不要调用resize(),避免自动调整影响行高 + // picture.resize(); + + } catch (Exception e) { + System.err.println("插入图片失败,行: " + rowIndex); + e.printStackTrace(); + } + } + @Override public void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) { // 不需要实现 } - - @Override - public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) { - Workbook workbook = writeWorkbookHolder.getWorkbook(); - Sheet sheet = writeSheetHolder.getSheet(); - - // 获取设备图片列索引(假设是第4列,索引3) - int imageColIndex = 3; - - // 遍历所有行 - for (int rowIndex = 1; rowIndex <= sheet.getLastRowNum(); rowIndex++) { // 从第2行开始(跳过标题) - Row row = sheet.getRow(rowIndex); - if (row == null) continue; - - Cell imageCell = row.getCell(imageColIndex); - if (imageCell == null) continue; - - // 获取图片数据 - byte[] imageData = null; - if (imageCell.getCellType() == CellType.STRING) { - // 处理Base64编码的图片(如果需要) - } - - if (imageData != null && imageData.length > 0) { - try { - // 添加图片到工作表 - int pictureIdx = workbook.addPicture(imageData, Workbook.PICTURE_TYPE_JPEG); - - // 创建绘图对象 - if (sheet instanceof XSSFSheet) { - XSSFSheet xssfSheet = (XSSFSheet) sheet; - XSSFDrawing drawing = xssfSheet.createDrawingPatriarch(); - - // 设置图片位置 - XSSFClientAnchor anchor = new XSSFClientAnchor(0, 0, 0, 0, imageColIndex, rowIndex, imageColIndex + 1, rowIndex + 1); - - // 创建图片 - drawing.createPicture(anchor, pictureIdx); - } - - // 清除单元格内容 - imageCell.setBlank(); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - } } \ No newline at end of file diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceTypeMapper.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceTypeMapper.java index 6500eaf..8bddea3 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceTypeMapper.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceTypeMapper.java @@ -51,4 +51,13 @@ public interface DeviceTypeMapper extends BaseMapper { */ DeviceType queryByName(@Param("criteria") DeviceTypeQueryCriteria criteria); + /** + * 根据名称列表查询设备类型 + * + * @param typeNames + * @return + */ + List selectByNames(@Param("typeNames") List typeNames); + + } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/DeviceTypeService.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/DeviceTypeService.java index 9dfcdca..2d75a0a 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/DeviceTypeService.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/DeviceTypeService.java @@ -49,6 +49,15 @@ public interface DeviceTypeService extends IService { */ DeviceType queryByName(String typeName); + /** + * 根据设备类型名称列表查询设备类型 + * + * @param typeNames 设备类型名称列表 + * @return List + */ + List queryByNames(List typeNames); + + /** * 新增设备类型 * diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceExportService.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceExportService.java index dc70220..4c51e7b 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceExportService.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceExportService.java @@ -3,7 +3,9 @@ package com.fuyuanshen.equipment.service.impl; import com.alibaba.excel.EasyExcel; import com.alibaba.excel.util.DateUtils; import com.fuyuanshen.equipment.domain.Device; +import com.fuyuanshen.equipment.domain.DeviceType; import com.fuyuanshen.equipment.domain.dto.DeviceExcelExportDTO; +import com.fuyuanshen.equipment.domain.dto.DeviceWithTypeExcelExportDTO; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -14,6 +16,7 @@ import java.net.URL; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.List; +import java.util.Map; import java.util.concurrent.Semaphore; import java.util.stream.Collectors; @@ -39,23 +42,16 @@ public class DeviceExportService { // 转换为DTO列表 List dtoList = devices.stream().map(device -> { DeviceExcelExportDTO dto = new DeviceExcelExportDTO(); - // dto.setId(device.getId()); - // dto.setDeviceType(device.getDeviceType()); - // dto.setCustomerName(device.getCustomerName()); dto.setDeviceName(device.getDeviceName()); dto.setDeviceMac(device.getDeviceMac()); // 设备IMEI dto.setDeviceImei(device.getDeviceImei()); // 蓝牙名称 dto.setBluetoothName(device.getBluetoothName()); - // dto.setLongitude(device.getLongitude()); - // dto.setLatitude(device.getLatitude()); dto.setRemark(device.getRemark()); dto.setTypeName(device.getTypeName()); dto.setCreateBy(device.getCreateByName()); - Integer deviceStatus = device.getDeviceStatus(); Integer bindingStatus = device.getBindingStatus(); - // dto.setDeviceStatus(deviceStatus == 1 ? "正常" : "失效"); dto.setBindingStatus(bindingStatus == 1 ? "已绑定" : "未绑定"); // 时间戳转换 dto.setCreateTime(DateUtils.format(device.getCreateTime(), "yyyy-MM-dd HH:mm:ss")); @@ -75,6 +71,158 @@ public class DeviceExportService { } + /** + * 导出设备数据(包含完整设备类型信息) + * + * @param devices + * @param deviceTypes + * @param response + */ + public void exportWithTypeInfo(List devices, List deviceTypes, HttpServletResponse response) { + try { + String fileName = "设备列表_含类型详情_" + System.currentTimeMillis() + ".xlsx"; + // 使用URLEncoder进行RFC 5987编码 + String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()).replaceAll("\\+", "%20"); + + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setCharacterEncoding("utf-8"); + // 使用RFC 5987标准编码文件名 + response.setHeader("Content-disposition", "attachment;filename*=UTF-8''" + encodedFileName); + + // 构建设备类型映射 + Map deviceTypeMap = deviceTypes.stream() + .collect(Collectors.toMap(DeviceType::getId, deviceType -> deviceType)); + + // 转换为DTO列表 + List dtoList = devices.stream().map(device -> { + DeviceWithTypeExcelExportDTO dto = new DeviceWithTypeExcelExportDTO(); + dto.setDeviceName(device.getDeviceName()); + dto.setDeviceMac(device.getDeviceMac()); + // 设备IMEI + dto.setDeviceImei(device.getDeviceImei()); + // 蓝牙名称 + dto.setBluetoothName(device.getBluetoothName()); + dto.setRemark(device.getRemark()); + dto.setTypeName(device.getTypeName()); + dto.setCreateBy(device.getCreateByName()); + Integer bindingStatus = device.getBindingStatus(); + dto.setBindingStatus(bindingStatus == 1 ? "已绑定" : "未绑定"); + // 时间戳转换 + dto.setCreateTime(DateUtils.format(device.getCreateTime(), "yyyy-MM-dd HH:mm:ss")); + + // 获取设备类型详细信息 + DeviceType deviceType = deviceTypeMap.get(device.getDeviceType()); + if (deviceType != null) { + // 处理是否支持蓝牙 + if (deviceType.getIsSupportBle() != null) { + dto.setIsSupportBle(deviceType.getIsSupportBle() ? "是" : "否"); + } else { + dto.setIsSupportBle("未知"); + } + + // 处理定位方式 + if (deviceType.getLocateMode() != null) { + dto.setLocateMode(convertLocateMode(deviceType.getLocateMode())); + } else { + dto.setLocateMode(""); + } + + // 处理通讯方式 + if (deviceType.getCommunicationMode() != null) { + dto.setCommunicationMode(convertCommunicationMode(deviceType.getCommunicationMode())); + } else { + dto.setCommunicationMode(""); + } + + // 处理APP页面跳转字典 + dto.setAppModelDictionary(deviceType.getAppModelDictionary() != null ? deviceType.getAppModelDictionary() : ""); + + // 处理PC页面跳转字典 + dto.setPcModelDictionary(deviceType.getPcModelDictionary() != null ? deviceType.getPcModelDictionary() : ""); + } else { + dto.setIsSupportBle("未知"); + dto.setLocateMode(""); + dto.setCommunicationMode(""); + dto.setAppModelDictionary(""); + dto.setPcModelDictionary(""); + } + + // 处理图片URL转换 + handleDevicePicForTypeExport(device, dto); + + return dto; + }).collect(Collectors.toList()); + + // 写入Excel + EasyExcel.write(response.getOutputStream(), DeviceWithTypeExcelExportDTO.class).sheet("设备数据含类型详情").doWrite(dtoList); + + } catch (IOException e) { + throw new RuntimeException("导出Excel失败", e); + } + } + + /** + * 转换定位方式代码为中文描述 + * + * @param locateMode 定位方式代码 (0:无;1:GPS;2:基站;3:wifi;4:北斗) + * @return 中文描述 + */ + private String convertLocateMode(String locateMode) { + switch (locateMode) { + case "0": + return "无"; + case "1": + return "GPS"; + case "2": + return "基站"; + case "3": + return "wifi"; + case "4": + return "北斗"; + default: + return locateMode; + } + } + + /** + * 转换联网方式代码为中文描述 + * + * @param networkWay 联网方式代码 (0:无;1:4G;2:WIFI) + * @return 中文描述 + */ + private String convertNetworkWay(String networkWay) { + switch (networkWay) { + case "0": + return "无"; + case "1": + return "4G"; + case "2": + return "WIFI"; + default: + return networkWay; + } + } + + /** + * 转换通讯方式代码为中文描述 + * + * @param communicationMode 通讯方式代码 (0:4G;1:蓝牙) + * @return 中文描述 + */ + private String convertCommunicationMode(String communicationMode) { + switch (communicationMode) { + case "0": + return "4G"; + case "1": + return "蓝牙"; + case "2": + return "4G&蓝牙"; + default: + return communicationMode; + } + } + + // 在DeviceExportService中添加并发控制 private static final Semaphore imageLoadSemaphore = new Semaphore(5); // 最多同时加载5张图片 @@ -113,4 +261,51 @@ public class DeviceExportService { } + + private void handleDevicePicForTypeExport(Device device, DeviceWithTypeExcelExportDTO dto) { + String picUrl = device.getDevicePic(); + log.debug("处理设备图片,设备ID: {}, 图片URL: {}", device.getId(), picUrl); + + if (picUrl != null && !picUrl.trim().isEmpty()) { + try { + // 获取加载图片的许可 + imageLoadSemaphore.acquire(); + + try { + picUrl = convertUrl(picUrl); + log.info("转换后的URL: {}", picUrl); + + // 尝试创建URL对象(会自动验证格式) + URL url = new URL(picUrl); + dto.setDevicePic(url); + log.debug("成功设置设备图片URL到DTO"); + } finally { + // 释放许可 + imageLoadSemaphore.release(); + } + } catch (Exception e) { + log.warn("设置设备图片失败,设备ID: {}, URL: {}, 错误: {}", device.getId(), picUrl, e.getMessage()); + dto.setDevicePic(null); + } + } else { + log.debug("设备没有设置图片,设备ID: {}", device.getId()); + dto.setDevicePic(null); + } + } + + /** + * 转换图片URL为HTTP + * 转回minio格式 + * + * @param originalUrl 原始URL + * @return 转换后的URL + */ + + public String convertUrl(String originalUrl) { + String result = originalUrl.replace("https://fuyuanshen.com", "http://120.79.224.186:9000"); + result = result.replace("http://fuyuanshen.com", "http://120.79.224.186:9000"); + return result; + } + + } \ No newline at end of file diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java index 2f37f95..6e9551c 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java @@ -192,15 +192,86 @@ public class DeviceServiceImpl extends ServiceImpl impleme throw new BadRequestException("设备IMEI已存在!!!"); } - DeviceTypeQueryCriteria queryCriteria = new DeviceTypeQueryCriteria(); - queryCriteria.setDeviceTypeId(deviceForm.getDeviceType()); - queryCriteria.setCustomerId(LoginHelper.getUserId()); - DeviceTypeGrants typeGrants = deviceTypeGrantsMapper.selectById(queryCriteria.getDeviceTypeId()); - if (typeGrants == null) { - throw new Exception("设备类型不存在!!!"); + // 检查设备类型是否存在,如果不存在则创建 + DeviceType deviceType = null; + if (deviceForm.getDeviceType() != null) { + deviceType = deviceTypeMapper.selectById(deviceForm.getDeviceType()); + } else if (deviceForm.getTypeName() != null) { + deviceType = deviceTypeMapper.selectOne(new QueryWrapper().eq("type_name", deviceForm.getTypeName())); } - DeviceType deviceTypes = deviceTypeMapper.selectById(typeGrants.getDeviceTypeId()); - if (deviceTypes == null) { + + if (deviceType == null && deviceForm.getTypeName() != null) { + // 创建新的设备类型 + DeviceType newDeviceType = new DeviceType(); + newDeviceType.setTypeName(deviceForm.getTypeName()); + newDeviceType.setIsSupportBle("是".equals(deviceForm.getIsSupportBle()) || "1".equals(deviceForm.getIsSupportBle())); + + // 设置定位方式 + if (deviceForm.getLocateMode() != null) { + switch (deviceForm.getLocateMode()) { + case "无": + newDeviceType.setLocateMode("0"); + break; + case "GPS": + newDeviceType.setLocateMode("1"); + break; + case "基站": + newDeviceType.setLocateMode("2"); + break; + case "wifi": + newDeviceType.setLocateMode("3"); + break; + case "北斗": + newDeviceType.setLocateMode("4"); + break; + default: + newDeviceType.setLocateMode(deviceForm.getLocateMode()); + } + } + + // 设置通讯方式 + if (deviceForm.getCommunicationMode() != null) { + switch (deviceForm.getCommunicationMode()) { + case "4G": + newDeviceType.setCommunicationMode("0"); + break; + case "蓝牙": + newDeviceType.setCommunicationMode("1"); + break; + case "4G&蓝牙": + newDeviceType.setCommunicationMode("2"); + break; + default: + newDeviceType.setCommunicationMode(deviceForm.getCommunicationMode()); + } + } + + newDeviceType.setAppModelDictionary(deviceForm.getAppModelDictionary()); + newDeviceType.setPcModelDictionary(deviceForm.getPcModelDictionary()); + + // 校验设备类型名称 + List existingTypes = deviceTypeMapper.selectList(new QueryWrapper().eq("type_name", newDeviceType.getTypeName())); + if (CollectionUtil.isNotEmpty(existingTypes)) { + throw new RuntimeException("设备类型名称已存在,无法新增!!!"); + } + + LoginUser loginUser = LoginHelper.getLoginUser(); + newDeviceType.setCreateByName(loginUser.getNickname()); + deviceTypeMapper.insert(newDeviceType); + + // 重新查询确保获取到正确的ID + deviceType = deviceTypeMapper.selectOne(new QueryWrapper().eq("type_name", deviceForm.getTypeName())); + + // 自动授权给自己 + DeviceTypeGrants deviceTypeGrants = new DeviceTypeGrants(); + deviceTypeGrants.setDeviceTypeId(deviceType.getId()); + deviceTypeGrants.setCustomerId(loginUser.getUserId()); + deviceTypeGrants.setGrantorCustomerId(loginUser.getUserId()); + deviceTypeGrants.setGrantedAt(new Date()); + deviceTypeGrantsMapper.insert(deviceTypeGrants); + } + + if (deviceType == null) { throw new Exception("设备类型不存在!!!"); } @@ -221,8 +292,8 @@ public class DeviceServiceImpl extends ServiceImpl impleme device.setCurrentOwnerId(loginUser.getUserId()); device.setOriginalOwnerId(loginUser.getUserId()); device.setCreateByName(loginUser.getNickname()); - device.setTypeName(deviceTypes.getTypeName()); - device.setDeviceType(deviceTypes.getId()); + device.setTypeName(deviceType.getTypeName()); + device.setDeviceType(deviceType.getId()); if (device.getDeviceImei() != null) { device.setPubTopic("A/" + device.getDeviceImei()); device.setSubTopic("B/" + device.getDeviceImei()); diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceTypeServiceImpl.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceTypeServiceImpl.java index 38eef64..d0f3815 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceTypeServiceImpl.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceTypeServiceImpl.java @@ -159,6 +159,21 @@ public class DeviceTypeServiceImpl extends ServiceImpl + */ + @Override + public List queryByNames(List typeNames) { + if (typeNames == null || typeNames.isEmpty()) { + return new ArrayList<>(); + } + return deviceTypeMapper.selectByNames(typeNames); + } + + /** * 新增设备类型 * diff --git a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceTypeMapper.xml b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceTypeMapper.xml index b1f70ad..3e608d0 100644 --- a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceTypeMapper.xml +++ b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceTypeMapper.xml @@ -58,4 +58,14 @@ + + + \ No newline at end of file