优化设备导出

This commit is contained in:
2025-11-19 10:55:44 +08:00
parent 3798e52ee0
commit a145c372b8
3 changed files with 47 additions and 27 deletions

View File

@ -69,9 +69,9 @@ spring:
servlet: servlet:
multipart: multipart:
# 单个文件大小 # 单个文件大小
max-file-size: 10MB max-file-size: 100MB
# 设置总上传的文件大小 # 设置总上传的文件大小
max-request-size: 20MB max-request-size: 200MB
mvc: mvc:
# 设置静态资源路径 防止所有请求都去查静态资源 # 设置静态资源路径 防止所有请求都去查静态资源
static-path-pattern: /static/** static-path-pattern: /static/**

View File

@ -4,6 +4,7 @@ import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth; import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.ContentRowHeight; import com.alibaba.excel.annotation.write.style.ContentRowHeight;
import com.alibaba.excel.annotation.write.style.HeadRowHeight; import com.alibaba.excel.annotation.write.style.HeadRowHeight;
import com.fuyuanshen.equipment.converter.IgnoreFailedImageConverter;
import lombok.Data; import lombok.Data;
import java.net.URL; import java.net.URL;
@ -27,7 +28,8 @@ public class DeviceWithTypeExcelExportDTO {
@ColumnWidth(20) @ColumnWidth(20)
private String typeName; private String typeName;
@ExcelProperty(value = "设备图片") @ExcelProperty(value = "设备图片", converter = IgnoreFailedImageConverter.class)
// @ExcelProperty(value = "设备图片")
@ColumnWidth(30) // 设置图片列宽度 @ColumnWidth(30) // 设置图片列宽度
private URL devicePic; // 使用URL类型 private URL devicePic; // 使用URL类型

View File

@ -11,13 +11,12 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.io.IOException; import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.Semaphore; import java.util.concurrent.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
@ -29,6 +28,9 @@ import java.util.stream.Collectors;
public class DeviceExportService { public class DeviceExportService {
public void export(List<Device> devices, HttpServletResponse response) { public void export(List<Device> devices, HttpServletResponse response) {
long startTime = System.currentTimeMillis();
log.info("开始导出设备列表,设备数量: {}", devices.size());
try { try {
String fileName = "设备列表_" + System.currentTimeMillis() + ".xlsx"; String fileName = "设备列表_" + System.currentTimeMillis() + ".xlsx";
// 使用URLEncoder进行RFC 5987编码 // 使用URLEncoder进行RFC 5987编码
@ -39,8 +41,9 @@ public class DeviceExportService {
// 使用RFC 5987标准编码文件名 // 使用RFC 5987标准编码文件名
response.setHeader("Content-disposition", "attachment;filename*=UTF-8''" + encodedFileName); response.setHeader("Content-disposition", "attachment;filename*=UTF-8''" + encodedFileName);
// 转换为DTO列表 // 转换为DTO列表,使用并行流加速处理
List<DeviceExcelExportDTO> dtoList = devices.stream().map(device -> { List<DeviceExcelExportDTO> dtoList = devices.parallelStream().map(device -> {
long deviceProcessStartTime = System.currentTimeMillis();
DeviceExcelExportDTO dto = new DeviceExcelExportDTO(); DeviceExcelExportDTO dto = new DeviceExcelExportDTO();
dto.setDeviceName(device.getDeviceName()); dto.setDeviceName(device.getDeviceName());
dto.setDeviceMac(device.getDeviceMac()); dto.setDeviceMac(device.getDeviceMac());
@ -59,13 +62,22 @@ public class DeviceExportService {
// 处理图片URL转换 // 处理图片URL转换
handleDevicePic(device, dto); handleDevicePic(device, dto);
long deviceProcessEndTime = System.currentTimeMillis();
log.info("单个设备处理耗时: {} ms, 设备ID: {}", (deviceProcessEndTime - deviceProcessStartTime), device.getId());
return dto; return dto;
}).collect(Collectors.toList()); }).collect(Collectors.toList());
// 写入Excel // 写入Excel
long excelWriteStartTime = System.currentTimeMillis();
EasyExcel.write(response.getOutputStream(), DeviceExcelExportDTO.class).sheet("设备数据").doWrite(dtoList); EasyExcel.write(response.getOutputStream(), DeviceExcelExportDTO.class).sheet("设备数据").doWrite(dtoList);
long excelWriteEndTime = System.currentTimeMillis();
long endTime = System.currentTimeMillis();
log.info("设备列表导出完成,总耗时: {} ms设备数量: {}Excel写入耗时: {} ms",
(endTime - startTime), devices.size(), (excelWriteEndTime - excelWriteStartTime));
} catch (IOException e) { } catch (IOException e) {
log.error("导出Excel失败", e);
throw new RuntimeException("导出Excel失败", e); throw new RuntimeException("导出Excel失败", e);
} }
} }
@ -79,6 +91,9 @@ public class DeviceExportService {
* @param response * @param response
*/ */
public void exportWithTypeInfo(List<Device> devices, List<DeviceType> deviceTypes, HttpServletResponse response) { public void exportWithTypeInfo(List<Device> devices, List<DeviceType> deviceTypes, HttpServletResponse response) {
long startTime = System.currentTimeMillis();
log.info("开始导出设备列表(含类型详情),设备数量: {}", devices.size());
try { try {
String fileName = "设备列表_含类型详情_" + System.currentTimeMillis() + ".xlsx"; String fileName = "设备列表_含类型详情_" + System.currentTimeMillis() + ".xlsx";
// 使用URLEncoder进行RFC 5987编码 // 使用URLEncoder进行RFC 5987编码
@ -93,8 +108,9 @@ public class DeviceExportService {
Map<Long, DeviceType> deviceTypeMap = deviceTypes.stream() Map<Long, DeviceType> deviceTypeMap = deviceTypes.stream()
.collect(Collectors.toMap(DeviceType::getId, deviceType -> deviceType)); .collect(Collectors.toMap(DeviceType::getId, deviceType -> deviceType));
// 转换为DTO列表 // 转换为DTO列表,使用并行流加速处理
List<DeviceWithTypeExcelExportDTO> dtoList = devices.stream().map(device -> { List<DeviceWithTypeExcelExportDTO> dtoList = devices.parallelStream().map(device -> {
long deviceProcessStartTime = System.currentTimeMillis();
DeviceWithTypeExcelExportDTO dto = new DeviceWithTypeExcelExportDTO(); DeviceWithTypeExcelExportDTO dto = new DeviceWithTypeExcelExportDTO();
dto.setDeviceName(device.getDeviceName()); dto.setDeviceName(device.getDeviceName());
dto.setDeviceMac(device.getDeviceMac()); dto.setDeviceMac(device.getDeviceMac());
@ -149,14 +165,20 @@ public class DeviceExportService {
// 处理图片URL转换 // 处理图片URL转换
handleDevicePicForTypeExport(device, dto); handleDevicePicForTypeExport(device, dto);
return dto; return dto;
}).collect(Collectors.toList()); }).collect(Collectors.toList());
// 写入Excel // 写入Excel
long excelWriteStartTime = System.currentTimeMillis();
EasyExcel.write(response.getOutputStream(), DeviceWithTypeExcelExportDTO.class).sheet("设备数据含类型详情").doWrite(dtoList); EasyExcel.write(response.getOutputStream(), DeviceWithTypeExcelExportDTO.class).sheet("设备数据含类型详情").doWrite(dtoList);
long excelWriteEndTime = System.currentTimeMillis();
long endTime = System.currentTimeMillis();
log.info("设备列表(含类型详情)导出完成,总耗时: {} ms设备数量: {}Excel写入耗时: {} ms",
(endTime - startTime), devices.size(), (excelWriteEndTime - excelWriteStartTime));
} catch (IOException e) { } catch (IOException e) {
log.error("导出Excel失败", e);
throw new RuntimeException("导出Excel失败", e); throw new RuntimeException("导出Excel失败", e);
} }
} }
@ -224,71 +246,67 @@ public class DeviceExportService {
// 在DeviceExportService中添加并发控制 // 在DeviceExportService中添加并发控制
private static final Semaphore imageLoadSemaphore = new Semaphore(5); // 最多同时加载5张图片 private static final Semaphore imageLoadSemaphore = new Semaphore(10); // 增加到最多同时加载10张图片
private void handleDevicePic(Device device, DeviceExcelExportDTO dto) { private void handleDevicePic(Device device, DeviceExcelExportDTO dto) {
String picUrl = device.getDevicePic(); String picUrl = device.getDevicePic();
log.debug("处理设备图片设备ID: {}, 图片URL: {}", device.getId(), picUrl);
if (picUrl != null && !picUrl.trim().isEmpty()) { if (picUrl != null && !picUrl.trim().isEmpty()) {
try { try {
// 获取加载图片的许可 // 获取加载图片的许可,带超时控制
imageLoadSemaphore.acquire(); if (!imageLoadSemaphore.tryAcquire(5, TimeUnit.SECONDS)) {
dto.setDevicePic(null);
return;
}
try { try {
// 自动将HTTP转换为HTTPS以避免重定向问题 // 自动将HTTP转换为HTTPS以避免重定向问题
if (picUrl.startsWith("http://")) { if (picUrl.startsWith("http://")) {
picUrl = "https://" + picUrl.substring(7); picUrl = "https://" + picUrl.substring(7);
log.debug("自动将HTTP转换为HTTPS: {}", picUrl);
} }
// 尝试创建URL对象会自动验证格式 // 尝试创建URL对象会自动验证格式
URL url = new URL(picUrl); URL url = new URL(picUrl);
dto.setDevicePic(url); dto.setDevicePic(url);
log.debug("成功设置设备图片URL到DTO");
} finally { } finally {
// 释放许可 // 释放许可
imageLoadSemaphore.release(); imageLoadSemaphore.release();
} }
} catch (Exception e) { } catch (Exception e) {
log.warn("设置设备图片失败设备ID: {}, URL: {}, 错误: {}", device.getId(), picUrl, e.getMessage());
dto.setDevicePic(null); dto.setDevicePic(null);
} }
} else { } else {
log.debug("设备没有设置图片设备ID: {}", device.getId());
dto.setDevicePic(null); dto.setDevicePic(null);
} }
} }
private void handleDevicePicForTypeExport(Device device, DeviceWithTypeExcelExportDTO dto) { private void handleDevicePicForTypeExport(Device device, DeviceWithTypeExcelExportDTO dto) {
String picUrl = device.getDevicePic(); String picUrl = device.getDevicePic();
log.debug("处理设备图片设备ID: {}, 图片URL: {}", device.getId(), picUrl);
if (picUrl != null && !picUrl.trim().isEmpty()) { if (picUrl != null && !picUrl.trim().isEmpty()) {
try { try {
// 获取加载图片的许可 // 获取加载图片的许可,带超时控制
imageLoadSemaphore.acquire(); if (!imageLoadSemaphore.tryAcquire(5, TimeUnit.SECONDS)) {
dto.setDevicePic(null);
return;
}
try { try {
picUrl = convertUrl(picUrl); picUrl = convertUrl(picUrl);
log.info("转换后的URL: {}", picUrl);
// 尝试创建URL对象会自动验证格式 // 尝试创建URL对象会自动验证格式
URL url = new URL(picUrl); URL url = new URL(picUrl);
dto.setDevicePic(url); dto.setDevicePic(url);
log.debug("成功设置设备图片URL到DTO");
} finally { } finally {
// 释放许可 // 释放许可
imageLoadSemaphore.release(); imageLoadSemaphore.release();
} }
} catch (Exception e) { } catch (Exception e) {
log.warn("设置设备图片失败设备ID: {}, URL: {}, 错误: {}", device.getId(), picUrl, e.getMessage());
dto.setDevicePic(null); dto.setDevicePic(null);
} }
} else { } else {
log.debug("设备没有设置图片设备ID: {}", device.getId());
dto.setDevicePic(null); dto.setDevicePic(null);
} }
} }