|
@@ -12,6 +12,7 @@ import cn.hutool.core.bean.BeanUtil;
|
|
|
import cn.hutool.core.collection.CollUtil;
|
|
|
import cn.hutool.core.date.StopWatch;
|
|
|
import cn.hutool.core.lang.Assert;
|
|
|
+import cn.hutool.core.util.ObjectUtil;
|
|
|
import cn.hutool.core.util.StrUtil;
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
@@ -19,6 +20,8 @@ import com.github.jfcloud.influxdb.flux.AggregationWindow;
|
|
|
import com.google.common.collect.Lists;
|
|
|
import com.itextpdf.io.image.ImageData;
|
|
|
import com.itextpdf.io.image.ImageDataFactory;
|
|
|
+import com.itextpdf.kernel.pdf.PdfObject;
|
|
|
+import com.itextpdf.kernel.pdf.WriterProperties;
|
|
|
import com.itextpdf.layout.element.Image;
|
|
|
import com.itextpdf.kernel.colors.ColorConstants;
|
|
|
import com.itextpdf.kernel.font.PdfFont;
|
|
@@ -53,15 +56,24 @@ import vip.xiaonuo.common.enums.CommonDeleteFlagEnum;
|
|
|
import vip.xiaonuo.common.exception.CommonException;
|
|
|
|
|
|
import java.io.*;
|
|
|
+import java.math.RoundingMode;
|
|
|
import java.net.URLEncoder;
|
|
|
import java.nio.charset.StandardCharsets;
|
|
|
+import java.text.DecimalFormat;
|
|
|
+import java.text.ParseException;
|
|
|
import java.text.SimpleDateFormat;
|
|
|
import java.util.*;
|
|
|
import java.util.List;
|
|
|
+import java.util.concurrent.CompletableFuture;
|
|
|
+import java.util.concurrent.ConcurrentHashMap;
|
|
|
+import java.util.concurrent.ExecutionException;
|
|
|
import java.util.concurrent.TimeUnit;
|
|
|
import java.util.function.Consumer;
|
|
|
import java.util.function.Supplier;
|
|
|
+import java.util.regex.Matcher;
|
|
|
+import java.util.regex.Pattern;
|
|
|
import java.util.stream.Collectors;
|
|
|
+import java.util.stream.IntStream;
|
|
|
|
|
|
@Slf4j
|
|
|
@Service
|
|
@@ -516,281 +528,161 @@ public class AppDeviceService {
|
|
|
return monitorTargetRegionService.updateById(monitorTargetRegion);
|
|
|
}
|
|
|
|
|
|
- /*public void export(HttpServletResponse response, AppTrendParam trendParam) {
|
|
|
- StopWatch stopWatch = new StopWatch("传感器数据导出");
|
|
|
+ private static final DecimalFormat TEMP_FORMAT = new DecimalFormat("0.0");
|
|
|
|
|
|
- stopWatch.start("查询设备信息");
|
|
|
- MonitorTargetRegion monitorTargetRegion = monitorTargetRegionService.findOneByDeviceCodeAndSensorNo(trendParam.getSensorCode(), trendParam.getRoads());
|
|
|
- stopWatch.stop();
|
|
|
- if (Objects.isNull(monitorTargetRegion)) {
|
|
|
- throw new CommonException("未找到设备编号 [{}] 和传感器编号 [{}] 对应的监控目标区域。", trendParam.getSensorCode(), trendParam.getRoads());
|
|
|
- }
|
|
|
-
|
|
|
- stopWatch.start("查询传感器数据");
|
|
|
- AppDeviceQueryParams appDeviceQueryParams = BeanUtil.copyProperties(trendParam, AppDeviceQueryParams.class);
|
|
|
- appDeviceQueryParams.setSensorRoute(trendParam.getRoads());
|
|
|
- SensorEchartDataResult sensorEchartDataResult = queryDataByDeviceIdAndRoads(appDeviceQueryParams);
|
|
|
- stopWatch.stop();
|
|
|
-
|
|
|
- // 计算统计信息
|
|
|
- stopWatch.start("计算统计信息");
|
|
|
- List<Float> temperatureData = sensorEchartDataResult.getTemperature().getY();
|
|
|
- float maxTemp = Collections.max(temperatureData);
|
|
|
- float minTemp = Collections.min(temperatureData);
|
|
|
- float avgTemp = (float) temperatureData.stream().mapToDouble(f -> f).average().orElse(0.0);
|
|
|
- stopWatch.stop();
|
|
|
-
|
|
|
- stopWatch.start("导出数据");
|
|
|
- List<ExportParam> exportParamList = new ArrayList<>();
|
|
|
- ExportParam exportParam = new ExportParam();
|
|
|
- exportParam.setRegionName(monitorTargetRegion.getName());
|
|
|
- exportParam.setDeviceCode(trendParam.getSensorCode());
|
|
|
- exportParam.setSensorRoad(trendParam.getRoads());
|
|
|
- String sensorType = monitorTargetRegion.getSensorType().toUpperCase();
|
|
|
-
|
|
|
- // 仅处理温度数据('W'类型)
|
|
|
- if (sensorType.contains("W")) {
|
|
|
- exportParam.setDataType("温度");
|
|
|
- LinkedHashSet<String> x = sensorEchartDataResult.getTemperature().getX();
|
|
|
- List<Float> y = sensorEchartDataResult.getTemperature().getY();
|
|
|
- int i = 0;
|
|
|
- for (String time : x) {
|
|
|
- ExportParam exportParamOut = BeanUtil.copyProperties(exportParam, ExportParam.class);
|
|
|
- exportParamOut.setTime(time);
|
|
|
- exportParamOut.setData(y.get(i));
|
|
|
- exportParamList.add(exportParamOut);
|
|
|
- i++;
|
|
|
+ public void export(HttpServletResponse response, AppTrendParam trendParam, MultipartFile file) {
|
|
|
+ // 初始化配置和计时器
|
|
|
+ TEMP_FORMAT.setRoundingMode(RoundingMode.HALF_UP);
|
|
|
+ try {
|
|
|
+ // 并行执行设备查询和数据查询
|
|
|
+ CompletableFuture<MonitorTargetRegion> regionFuture = CompletableFuture.supplyAsync(() ->
|
|
|
+ monitorTargetRegionService.findOneByDeviceCodeAndSensorNo(trendParam.getSensorCode(), trendParam.getRoads()));
|
|
|
+
|
|
|
+ AppDeviceQueryParams appDeviceQueryParams = BeanUtil.copyProperties(trendParam, AppDeviceQueryParams.class);
|
|
|
+ appDeviceQueryParams.setSensorRoute(trendParam.getRoads());
|
|
|
+ CompletableFuture<SensorEchartDataResult> dataFuture = CompletableFuture.supplyAsync(() ->
|
|
|
+ queryDataByDeviceIdAndRoads(appDeviceQueryParams));
|
|
|
+
|
|
|
+ // 等待查询完成并获取结果
|
|
|
+ MonitorTargetRegion monitorTargetRegion = regionFuture.get();
|
|
|
+ if (Objects.isNull(monitorTargetRegion)) {
|
|
|
+ throw new CommonException("未找到设备编号 [{}] 和传感器编号 [{}] 对应的监控目标区域。",
|
|
|
+ trendParam.getSensorCode(), trendParam.getRoads());
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- String fileName = "传感器数据报告.pdf";
|
|
|
- response.setContentType("application/pdf");
|
|
|
- response.setCharacterEncoding("utf-8");
|
|
|
- response.setHeader("Content-disposition", "attachment;filename*=utf-8''" +
|
|
|
- URLEncoder.encode(fileName, StandardCharsets.UTF_8));
|
|
|
-
|
|
|
- try (OutputStream outputStream = response.getOutputStream()) {
|
|
|
- PdfFont chineseFont = PdfFontFactory.createFont("STSong-Light", "UniGB-UCS2-H");
|
|
|
- PdfWriter writer = new PdfWriter(outputStream);
|
|
|
- PdfDocument pdfDoc = new PdfDocument(writer);
|
|
|
- Document document = new Document(pdfDoc, PageSize.A4);
|
|
|
- document.setMargins(15, 20, 10, 20);
|
|
|
|
|
|
- // 报告标题
|
|
|
- Paragraph title = new Paragraph("传感器数据报告")
|
|
|
- .setFont(chineseFont)
|
|
|
- .setTextAlignment(TextAlignment.CENTER)
|
|
|
- .setFontSize(20)
|
|
|
- .setBold()
|
|
|
- .setMarginBottom(10);
|
|
|
- document.add(title);
|
|
|
+ SensorEchartDataResult sensorEchartDataResult = dataFuture.get();
|
|
|
+
|
|
|
+ // 并行计算统计信息
|
|
|
+ Map<String, Float[]> statsMap = calculateAllStatsParallel(sensorEchartDataResult);
|
|
|
+ Float maxTemp = statsMap.get("temperature")[0];
|
|
|
+ Float minTemp = statsMap.get("temperature")[1];
|
|
|
+ Float avgTemp = statsMap.get("temperature")[2];
|
|
|
+ Float maxHum = statsMap.get("humidity")[0];
|
|
|
+ Float minHum = statsMap.get("humidity")[1];
|
|
|
+ Float avgHum = statsMap.get("humidity")[2];
|
|
|
+ Float maxCo2 = statsMap.get("co2")[0];
|
|
|
+ Float minCo2 = statsMap.get("co2")[1];
|
|
|
+ Float avgCo2 = statsMap.get("co2")[2];
|
|
|
+
|
|
|
+ // 并行构建导出数据列表
|
|
|
+ Map<String, List<ExportParam>> exportDataMap = buildExportDataParallel(
|
|
|
+ monitorTargetRegion, trendParam, sensorEchartDataResult);
|
|
|
+ List<ExportParam> tempExportParamList = exportDataMap.get("temperature");
|
|
|
+ List<ExportParam> humExportParamList = exportDataMap.get("humidity");
|
|
|
+ List<ExportParam> co2ExportParamList = exportDataMap.get("co2");
|
|
|
+
|
|
|
+ // 生成PDF报告
|
|
|
+ generatePdfReport(response, file, monitorTargetRegion, trendParam,
|
|
|
+ sensorEchartDataResult, statsMap, exportDataMap);
|
|
|
+ } catch (Exception e) {
|
|
|
+ throw new RuntimeException("PDF生成失败", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- // 报告下载时间
|
|
|
- SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
|
|
|
- Paragraph downloadTime = new Paragraph("报告下载时间: " + sdf.format(new Date()))
|
|
|
- .setFont(chineseFont)
|
|
|
- .setFontSize(10)
|
|
|
- .setTextAlignment(TextAlignment.LEFT);
|
|
|
- document.add(downloadTime);
|
|
|
+ // 并行计算所有统计信息
|
|
|
+ private Map<String, Float[]> calculateAllStatsParallel(SensorEchartDataResult sensorEchartDataResult) {
|
|
|
+ CompletableFuture<Float[]> tempStatsFuture = CompletableFuture.supplyAsync(() ->
|
|
|
+ calculateStats(sensorEchartDataResult.getTemperature()));
|
|
|
+ CompletableFuture<Float[]> humStatsFuture = CompletableFuture.supplyAsync(() ->
|
|
|
+ calculateStats(sensorEchartDataResult.getHumidity()));
|
|
|
+ CompletableFuture<Float[]> co2StatsFuture = CompletableFuture.supplyAsync(() ->
|
|
|
+ calculateStats(sensorEchartDataResult.getCo2()));
|
|
|
|
|
|
- Paragraph deviceInfo = new Paragraph("设备信息")
|
|
|
- .setFont(chineseFont)
|
|
|
- .setTextAlignment(TextAlignment.CENTER)
|
|
|
- .setFontSize(15)
|
|
|
- .setBold()
|
|
|
- .setMarginBottom(2);
|
|
|
- document.add(deviceInfo);
|
|
|
-
|
|
|
- // 设备信息表格
|
|
|
- Table deviceTable = new Table(new float[]{2, 3, 2, 3}).useAllAvailableWidth();
|
|
|
- addDeviceTableRow(deviceTable, "设备名称:", monitorTargetRegion.getName().split("-")[0], "温度上限:", monitorTargetRegion.getTemperatureUp() + "°C");
|
|
|
- addDeviceTableRow(deviceTable, "点位名称:", monitorTargetRegion.getName(), "温度下限:", monitorTargetRegion.getTemperatureDown() + "");
|
|
|
- addDeviceTableRow(deviceTable, "冷链设备编号:", trendParam.getSensorCode(), "传感器路数:", trendParam.getRoads() + "路");
|
|
|
- deviceTable.setMarginBottom(10);
|
|
|
- document.add(deviceTable);
|
|
|
+ CompletableFuture.allOf(tempStatsFuture, humStatsFuture, co2StatsFuture).join();
|
|
|
|
|
|
- Paragraph data = new Paragraph("记录信息")
|
|
|
- .setFont(chineseFont)
|
|
|
- .setTextAlignment(TextAlignment.CENTER)
|
|
|
- .setFontSize(15)
|
|
|
- .setBold()
|
|
|
- .setMarginBottom(2);
|
|
|
- document.add(data);
|
|
|
-
|
|
|
- // 记录信息表格
|
|
|
- Table recordTable = new Table(new float[]{2, 3, 2, 3}).useAllAvailableWidth();
|
|
|
- addRecordTableRow(recordTable, "开始时间:", trendParam.getStartTime(), "最高温度:", String.format("%.1f°C", maxTemp));
|
|
|
- addRecordTableRow(recordTable, "结束时间:", trendParam.getEndTime(), "最低温度:", String.format("%.1f°C", minTemp));
|
|
|
- addRecordTableRow(recordTable, "", "", "平均温度:", String.format("%.1f°C", avgTemp));
|
|
|
- recordTable.setMarginBottom(10);
|
|
|
- document.add(recordTable);
|
|
|
+ Map<String, Float[]> statsMap = new ConcurrentHashMap<>();
|
|
|
+ try {
|
|
|
+ statsMap.put("temperature", tempStatsFuture.get());
|
|
|
+ statsMap.put("humidity", humStatsFuture.get());
|
|
|
+ statsMap.put("co2", co2StatsFuture.get());
|
|
|
+ } catch (Exception e) {
|
|
|
+ throw new RuntimeException("计算统计数据失败", e);
|
|
|
+ }
|
|
|
|
|
|
- Paragraph curve = new Paragraph("温度曲线")
|
|
|
- .setFont(chineseFont)
|
|
|
- .setTextAlignment(TextAlignment.CENTER)
|
|
|
- .setFontSize(15)
|
|
|
- .setBold();
|
|
|
- document.add(curve);
|
|
|
+ return statsMap;
|
|
|
+ }
|
|
|
|
|
|
- try {
|
|
|
- // 1. 创建适用于String类型X轴的数据集)
|
|
|
- DefaultCategoryDataset dataset = new DefaultCategoryDataset();
|
|
|
- List<Float> yValues = sensorEchartDataResult.getTemperature().getY();
|
|
|
- List<String> xLabels = new ArrayList<>(sensorEchartDataResult.getTemperature().getX());
|
|
|
-
|
|
|
- // 填充分类数据集(行键,数值,列键)
|
|
|
- for (int i = 0; i < xLabels.size(); i++) {
|
|
|
- dataset.addValue(yValues.get(i), "温度", xLabels.get(i)); // 列键使用时间字符串
|
|
|
- }
|
|
|
+ // 计算单个数据集的统计信息
|
|
|
+ private Float[] calculateStats(SensorEchartData data) {
|
|
|
+ if (Objects.isNull(data) || data.getY() == null || data.getY().isEmpty()) {
|
|
|
+ return new Float[]{null, null, null};
|
|
|
+ }
|
|
|
|
|
|
- // 2. 创建分类折线图(替换核心方法)[1,3](@ref)
|
|
|
- JFreeChart chart = ChartFactory.createLineChart(
|
|
|
- null, // 隐藏标题
|
|
|
- "时间", // X轴标签(原"时间序列"改为更直观的"时间")
|
|
|
- "温度(℃)",
|
|
|
- dataset,
|
|
|
- PlotOrientation.VERTICAL,
|
|
|
- false, // 不显示图例
|
|
|
- false,
|
|
|
- false
|
|
|
- );
|
|
|
-
|
|
|
- // 3. 样式优化(需调整为CategoryPlot)[5,7](@ref)
|
|
|
- CategoryPlot plot = chart.getCategoryPlot();
|
|
|
- plot.setBackgroundPaint(Color.WHITE);
|
|
|
-
|
|
|
- // 设置X/Y轴字体(解决中文显示问题)[3,8](@ref)
|
|
|
- Font songFont = new Font("宋体", Font.PLAIN, 11);
|
|
|
- CategoryAxis domainAxis = plot.getDomainAxis();
|
|
|
- domainAxis.setLabelFont(songFont);
|
|
|
- domainAxis.setTickLabelFont(songFont);
|
|
|
- domainAxis.setCategoryLabelPositions(CategoryLabelPositions.UP_45); // 标签倾斜45度防重叠
|
|
|
-
|
|
|
- ValueAxis rangeAxis = plot.getRangeAxis();
|
|
|
- rangeAxis.setLabelFont(songFont);
|
|
|
- double minValue;
|
|
|
- double maxValue;
|
|
|
- if (Collections.min(yValues) > 0) {
|
|
|
- minValue = Collections.min(yValues) * 0.95; // 下边界留5%余量
|
|
|
- } else {
|
|
|
- minValue = Collections.min(yValues) * 1.05;
|
|
|
- }
|
|
|
- if (Collections.max(yValues) < 0) {
|
|
|
- maxValue = Collections.max(yValues) * 0.95; // 下边界留5%余量
|
|
|
- } else {
|
|
|
- maxValue = Collections.max(yValues) * 1.05; // 上边界留5%余量
|
|
|
- }
|
|
|
- rangeAxis.setRange(minValue, maxValue);
|
|
|
+ DoubleSummaryStatistics stats = data.getY().parallelStream()
|
|
|
+ .mapToDouble(f -> f)
|
|
|
+ .summaryStatistics();
|
|
|
+ return new Float[]{(float) stats.getMax(), (float) stats.getMin(), (float) stats.getAverage()};
|
|
|
+ }
|
|
|
|
|
|
- // 设置刻度密度(根据数据范围动态调整)
|
|
|
- if ((maxValue - minValue) < 50) { // 温差较小时缩小刻度间隔
|
|
|
- rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
|
|
|
- }
|
|
|
+ // 并行构建所有导出数据列表
|
|
|
+ private Map<String, List<ExportParam>> buildExportDataParallel(
|
|
|
+ MonitorTargetRegion monitorTargetRegion, AppTrendParam trendParam,
|
|
|
+ SensorEchartDataResult sensorEchartDataResult) {
|
|
|
|
|
|
- // 4. 自定义折线样式(使用CategoryItemRenderer)[5](@ref)
|
|
|
- LineAndShapeRenderer renderer = new LineAndShapeRenderer();
|
|
|
- renderer.setSeriesPaint(0, new Color(79, 129, 189));
|
|
|
- renderer.setSeriesStroke(0, new BasicStroke(1.8f));
|
|
|
- renderer.setSeriesShapesVisible(0, true); // 显示数据点
|
|
|
- plot.setRenderer(renderer);
|
|
|
+ ExportParam template = new ExportParam();
|
|
|
+ template.setRegionName(monitorTargetRegion.getName());
|
|
|
+ template.setDeviceCode(trendParam.getSensorCode());
|
|
|
+ template.setSensorRoad(trendParam.getRoads());
|
|
|
+ String sensorType = monitorTargetRegion.getSensorType().toUpperCase();
|
|
|
|
|
|
+ List<CompletableFuture<Map.Entry<String, List<ExportParam>>>> futures = new ArrayList<>();
|
|
|
|
|
|
- // 5. 处理时间标签过长问题(动态调整间隔)[6,7](@ref)
|
|
|
- if(xLabels.size() > 20) { // 当数据点超过20个时启用标签间隔
|
|
|
- domainAxis.setTickLabelsVisible(true);
|
|
|
- domainAxis.setTickMarksVisible(true);
|
|
|
-// domainAxis.setMaximumCategoryLabelLines(3); // 允许换行显示
|
|
|
- domainAxis.setCategoryLabelPositions(CategoryLabelPositions.UP_90); // 垂直显示
|
|
|
- }
|
|
|
+ if (sensorType.contains("W")) {
|
|
|
+ futures.add(CompletableFuture.supplyAsync(() ->
|
|
|
+ Map.entry("temperature", buildExportParamList(template, sensorEchartDataResult.getTemperature(), "温度"))));
|
|
|
+ }
|
|
|
+ if (sensorType.contains("S")) {
|
|
|
+ futures.add(CompletableFuture.supplyAsync(() ->
|
|
|
+ Map.entry("humidity", buildExportParamList(template, sensorEchartDataResult.getHumidity(), "湿度"))));
|
|
|
+ }
|
|
|
+ if (sensorType.contains("C")) {
|
|
|
+ futures.add(CompletableFuture.supplyAsync(() ->
|
|
|
+ Map.entry("co2", buildExportParamList(template, sensorEchartDataResult.getCo2(), "二氧化碳"))));
|
|
|
+ }
|
|
|
|
|
|
- // 6. 生成图像并插入PDF(保持原有逻辑)
|
|
|
- BufferedImage chartImage = chart.createBufferedImage(600, 450); // 加宽画布
|
|
|
- ImageData imageData = ImageDataFactory.create(chartImage, null);
|
|
|
- com.itextpdf.layout.element.Image pdfImage = new com.itextpdf.layout.element.Image(imageData)
|
|
|
- .setHorizontalAlignment(HorizontalAlignment.CENTER);
|
|
|
- document.add(pdfImage);
|
|
|
+ CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
|
|
|
|
|
|
+ Map<String, List<ExportParam>> exportDataMap = new ConcurrentHashMap<>();
|
|
|
+ futures.forEach(future -> {
|
|
|
+ try {
|
|
|
+ Map.Entry<String, List<ExportParam>> entry = future.get();
|
|
|
+ exportDataMap.put(entry.getKey(), entry.getValue());
|
|
|
} catch (Exception e) {
|
|
|
- log.error("折线图生成失败: {}", e.getMessage());
|
|
|
- throw new RuntimeException("图表生成异常", e);
|
|
|
+ throw new RuntimeException("构建导出数据失败", e);
|
|
|
}
|
|
|
+ });
|
|
|
|
|
|
- document.add(new AreaBreak());
|
|
|
-
|
|
|
- // 温度数据表格
|
|
|
- Paragraph dataTitle = new Paragraph("温度数据")
|
|
|
- .setFont(chineseFont)
|
|
|
- .setTextAlignment(TextAlignment.CENTER)
|
|
|
- .setFontSize(15)
|
|
|
- .setBold()
|
|
|
- .setMarginBottom(2);
|
|
|
- document.add(dataTitle);
|
|
|
-
|
|
|
- Table dataTable = new Table(2).useAllAvailableWidth();
|
|
|
- dataTable.addHeaderCell(createCell("时间", true));
|
|
|
- dataTable.addHeaderCell(createCell("°C", true));
|
|
|
-
|
|
|
- exportParamList.forEach(param -> {
|
|
|
- dataTable.addCell(createCell(param.getTime(), false));
|
|
|
- dataTable.addCell(createCell(String.format("%.1f", param.getData()), false));
|
|
|
- });
|
|
|
- document.add(dataTable);
|
|
|
+ return exportDataMap;
|
|
|
+ }
|
|
|
|
|
|
- document.close();
|
|
|
- } catch (Exception e) {
|
|
|
- throw new RuntimeException("PDF生成失败", e);
|
|
|
- } finally {
|
|
|
- stopWatch.stop();
|
|
|
- log.info("PDF导出耗时:{}", stopWatch.prettyPrint(TimeUnit.MILLISECONDS));
|
|
|
+ // 构建单个数据类型的导出参数列表
|
|
|
+ private List<ExportParam> buildExportParamList(ExportParam template, SensorEchartData data, String dataType) {
|
|
|
+ if (data == null || data.getX() == null || data.getY() == null) {
|
|
|
+ return new ArrayList<>();
|
|
|
}
|
|
|
- }*/
|
|
|
|
|
|
- public void export(HttpServletResponse response, AppTrendParam trendParam, MultipartFile file) {
|
|
|
- StopWatch stopWatch = new StopWatch("传感器数据导出");
|
|
|
+ List<ExportParam> list = new ArrayList<>(data.getX().size());
|
|
|
+ ExportParam param = BeanUtil.copyProperties(template, ExportParam.class);
|
|
|
+ param.setDataType(dataType);
|
|
|
|
|
|
- stopWatch.start("查询设备信息");
|
|
|
- MonitorTargetRegion monitorTargetRegion = monitorTargetRegionService.findOneByDeviceCodeAndSensorNo(trendParam.getSensorCode(), trendParam.getRoads());
|
|
|
- stopWatch.stop();
|
|
|
- if (Objects.isNull(monitorTargetRegion)) {
|
|
|
- throw new CommonException("未找到设备编号 [{}] 和传感器编号 [{}] 对应的监控目标区域。", trendParam.getSensorCode(), trendParam.getRoads());
|
|
|
+ Iterator<String> xIter = data.getX().iterator();
|
|
|
+ Iterator<Float> yIter = data.getY().iterator();
|
|
|
+
|
|
|
+ while (xIter.hasNext() && yIter.hasNext()) {
|
|
|
+ ExportParam copy = BeanUtil.copyProperties(param, ExportParam.class);
|
|
|
+ copy.setTime(xIter.next());
|
|
|
+ copy.setData(yIter.next());
|
|
|
+ list.add(copy);
|
|
|
}
|
|
|
|
|
|
- stopWatch.start("查询传感器数据");
|
|
|
- AppDeviceQueryParams appDeviceQueryParams = BeanUtil.copyProperties(trendParam, AppDeviceQueryParams.class);
|
|
|
- appDeviceQueryParams.setSensorRoute(trendParam.getRoads());
|
|
|
- SensorEchartDataResult sensorEchartDataResult = queryDataByDeviceIdAndRoads(appDeviceQueryParams);
|
|
|
- stopWatch.stop();
|
|
|
-
|
|
|
- // 计算统计信息
|
|
|
- stopWatch.start("计算统计信息");
|
|
|
- List<Float> temperatureData = sensorEchartDataResult.getTemperature().getY();
|
|
|
- float maxTemp = Collections.max(temperatureData);
|
|
|
- float minTemp = Collections.min(temperatureData);
|
|
|
- float avgTemp = (float) temperatureData.stream().mapToDouble(f -> f).average().orElse(0.0);
|
|
|
- stopWatch.stop();
|
|
|
-
|
|
|
- stopWatch.start("导出数据");
|
|
|
- List<ExportParam> exportParamList = new ArrayList<>();
|
|
|
- ExportParam exportParam = new ExportParam();
|
|
|
- exportParam.setRegionName(monitorTargetRegion.getName());
|
|
|
- exportParam.setDeviceCode(trendParam.getSensorCode());
|
|
|
- exportParam.setSensorRoad(trendParam.getRoads());
|
|
|
- String sensorType = monitorTargetRegion.getSensorType().toUpperCase();
|
|
|
+ return list;
|
|
|
+ }
|
|
|
|
|
|
- // 仅处理温度数据('W'类型)
|
|
|
- if (sensorType.contains("W")) {
|
|
|
- exportParam.setDataType("温度");
|
|
|
- LinkedHashSet<String> x = sensorEchartDataResult.getTemperature().getX();
|
|
|
- List<Float> y = sensorEchartDataResult.getTemperature().getY();
|
|
|
- int i = 0;
|
|
|
- for (String time : x) {
|
|
|
- ExportParam exportParamOut = BeanUtil.copyProperties(exportParam, ExportParam.class);
|
|
|
- exportParamOut.setTime(time);
|
|
|
- exportParamOut.setData(y.get(i));
|
|
|
- exportParamList.add(exportParamOut);
|
|
|
- i++;
|
|
|
- }
|
|
|
- }
|
|
|
+ // 生成PDF报告
|
|
|
+ private void generatePdfReport(HttpServletResponse response, MultipartFile file,
|
|
|
+ MonitorTargetRegion monitorTargetRegion, AppTrendParam trendParam,
|
|
|
+ SensorEchartDataResult sensorEchartDataResult, Map<String, Float[]> statsMap,
|
|
|
+ Map<String, List<ExportParam>> exportDataMap) throws IOException {
|
|
|
|
|
|
String fileName = "传感器数据报告.pdf";
|
|
|
response.setContentType("application/pdf");
|
|
@@ -800,180 +692,297 @@ public class AppDeviceService {
|
|
|
|
|
|
try (OutputStream outputStream = response.getOutputStream()) {
|
|
|
PdfFont chineseFont = PdfFontFactory.createFont("STSong-Light", "UniGB-UCS2-H");
|
|
|
- PdfWriter writer = new PdfWriter(outputStream);
|
|
|
+ WriterProperties writerProperties = new WriterProperties()
|
|
|
+ .setFullCompressionMode(true)
|
|
|
+ .useSmartMode();
|
|
|
+ PdfWriter writer = new PdfWriter(outputStream, writerProperties);
|
|
|
PdfDocument pdfDoc = new PdfDocument(writer);
|
|
|
Document document = new Document(pdfDoc, PageSize.A4);
|
|
|
document.setMargins(15, 20, 10, 20);
|
|
|
|
|
|
- // 报告标题
|
|
|
- Paragraph title = new Paragraph("传感器数据报告")
|
|
|
+ // 预构建所有PDF元素
|
|
|
+ List<IElement> elements = new ArrayList<>();
|
|
|
+
|
|
|
+ // 添加报告标题
|
|
|
+ elements.add(new Paragraph("传感器数据报告")
|
|
|
.setFont(chineseFont)
|
|
|
.setTextAlignment(TextAlignment.CENTER)
|
|
|
.setFontSize(20)
|
|
|
.setBold()
|
|
|
- .setMarginBottom(10);
|
|
|
- document.add(title);
|
|
|
+ .setMarginBottom(10));
|
|
|
|
|
|
- // 报告下载时间
|
|
|
- SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
|
|
|
- Paragraph downloadTime = new Paragraph("报告下载时间: " + sdf.format(new Date()))
|
|
|
+ // 添加报告下载时间
|
|
|
+ elements.add(new Paragraph("报告下载时间: " + new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(new Date()))
|
|
|
.setFont(chineseFont)
|
|
|
.setFontSize(10)
|
|
|
- .setTextAlignment(TextAlignment.LEFT);
|
|
|
- document.add(downloadTime);
|
|
|
+ .setTextAlignment(TextAlignment.LEFT));
|
|
|
|
|
|
- Paragraph deviceInfo = new Paragraph("设备信息")
|
|
|
+ // 添加设备信息标题
|
|
|
+ elements.add(new Paragraph("设备信息")
|
|
|
.setFont(chineseFont)
|
|
|
.setTextAlignment(TextAlignment.CENTER)
|
|
|
.setFontSize(15)
|
|
|
.setBold()
|
|
|
- .setMarginBottom(2);
|
|
|
- document.add(deviceInfo);
|
|
|
+ .setMarginBottom(2));
|
|
|
|
|
|
+ // 构建设备信息表格
|
|
|
float[] columnWidths = {2, 4, 2, 3};
|
|
|
-
|
|
|
- // 设备信息表格
|
|
|
Table deviceTable = new Table(UnitValue.createPercentArray(columnWidths))
|
|
|
.useAllAvailableWidth()
|
|
|
- .setHorizontalAlignment(HorizontalAlignment.CENTER);;
|
|
|
- addDeviceTableRow(deviceTable, "设备名称:", monitorTargetRegion.getName().split("-")[0], "温度上限:", monitorTargetRegion.getTemperatureUp() + "°C");
|
|
|
- addDeviceTableRow(deviceTable, "点位名称:", monitorTargetRegion.getName(), "温度下限:", monitorTargetRegion.getTemperatureDown() + "");
|
|
|
- addDeviceTableRow(deviceTable, "冷链设备编号:", trendParam.getSensorCode(), "传感器路数:", trendParam.getRoads() + "路");
|
|
|
+ .setHorizontalAlignment(HorizontalAlignment.CENTER);
|
|
|
+
|
|
|
+ String sensorType = monitorTargetRegion.getSensorType().toUpperCase();
|
|
|
+ addTableRow(deviceTable, "设备名称:", monitorTargetRegion.getName().split("-")[0],
|
|
|
+ "点位名称:", monitorTargetRegion.getName());
|
|
|
+
|
|
|
+ if (sensorType.contains("W")) {
|
|
|
+ addTableRow(deviceTable, "温度上限:", monitorTargetRegion.getTemperatureUp() + "°C",
|
|
|
+ "温度下限:", monitorTargetRegion.getTemperatureDown() + "°C");
|
|
|
+ }
|
|
|
+ if (sensorType.contains("S")) {
|
|
|
+ addTableRow(deviceTable, "湿度上限:", monitorTargetRegion.getHumidityUp() + "%",
|
|
|
+ "湿度下限:", monitorTargetRegion.getHumidityDown() + "%");
|
|
|
+ }
|
|
|
+ if (sensorType.contains("C")) {
|
|
|
+ addTableRow(deviceTable, "二氧化碳上限:", monitorTargetRegion.getCo2Up() + "ppm",
|
|
|
+ "二氧化碳下限:", monitorTargetRegion.getCo2Down() + "ppm");
|
|
|
+ }
|
|
|
+
|
|
|
+ addTableRow(deviceTable, "冷链设备编号:", trendParam.getSensorCode(),
|
|
|
+ "传感器路数:", trendParam.getRoads() + "路");
|
|
|
deviceTable.setMarginBottom(10);
|
|
|
- document.add(deviceTable);
|
|
|
+ elements.add(deviceTable);
|
|
|
|
|
|
- Paragraph data = new Paragraph("记录信息")
|
|
|
+ // 添加记录信息标题
|
|
|
+ elements.add(new Paragraph("记录信息")
|
|
|
.setFont(chineseFont)
|
|
|
.setTextAlignment(TextAlignment.CENTER)
|
|
|
.setFontSize(15)
|
|
|
.setBold()
|
|
|
- .setMarginBottom(2);
|
|
|
- document.add(data);
|
|
|
+ .setMarginBottom(2));
|
|
|
|
|
|
- // 记录信息表格
|
|
|
+ // 构建记录信息表格
|
|
|
Table recordTable = new Table(UnitValue.createPercentArray(columnWidths))
|
|
|
.useAllAvailableWidth()
|
|
|
.setHorizontalAlignment(HorizontalAlignment.CENTER);
|
|
|
- addRecordTableRow(recordTable, "开始时间:", trendParam.getStartTime(), "最高温度:", String.format("%.1f°C", maxTemp));
|
|
|
- addRecordTableRow(recordTable, "结束时间:", trendParam.getEndTime(), "最低温度:", String.format("%.1f°C", minTemp));
|
|
|
- addRecordTableRow(recordTable, "", "", "平均温度:", String.format("%.1f°C", avgTemp));
|
|
|
- recordTable.setMarginBottom(10);
|
|
|
- document.add(recordTable);
|
|
|
|
|
|
- if (!file.isEmpty()) {
|
|
|
- Paragraph curve = new Paragraph("温度曲线")
|
|
|
- .setFont(chineseFont)
|
|
|
- .setTextAlignment(TextAlignment.CENTER)
|
|
|
- .setFontSize(15)
|
|
|
- .setBold();
|
|
|
- document.add(curve);
|
|
|
+ Float[] tempStats = statsMap.get("temperature");
|
|
|
+ Float[] humStats = statsMap.get("humidity");
|
|
|
+ Float[] co2Stats = statsMap.get("co2");
|
|
|
+
|
|
|
+ if (sensorType.contains("W")) {
|
|
|
+ addTableRow(recordTable, "开始时间:", trendParam.getStartTime(),
|
|
|
+ "最高温度:", TEMP_FORMAT.format(tempStats[0]) + "°C");
|
|
|
+ addTableRow(recordTable, "结束时间:", trendParam.getEndTime(),
|
|
|
+ "最低温度:", TEMP_FORMAT.format(tempStats[1]) + "°C");
|
|
|
+ addTableRow(recordTable, "", "",
|
|
|
+ "平均温度:", TEMP_FORMAT.format(tempStats[2]) + "°C");
|
|
|
+ }
|
|
|
+ if (sensorType.contains("S")) {
|
|
|
+ addTableRow(recordTable, "开始时间:", trendParam.getStartTime(),
|
|
|
+ "最高湿度:", TEMP_FORMAT.format(humStats[0]) + "%");
|
|
|
+ addTableRow(recordTable, "结束时间:", trendParam.getEndTime(),
|
|
|
+ "最低湿度:", TEMP_FORMAT.format(humStats[1]) + "%");
|
|
|
+ addTableRow(recordTable, "", "",
|
|
|
+ "平均湿度:", TEMP_FORMAT.format(humStats[2]) + "%");
|
|
|
+ }
|
|
|
+ if (sensorType.contains("C")) {
|
|
|
+ addTableRow(recordTable, "开始时间:", trendParam.getStartTime(),
|
|
|
+ "最高Co2浓度:", TEMP_FORMAT.format(co2Stats[0]) + "ppm");
|
|
|
+ addTableRow(recordTable, "结束时间:", trendParam.getEndTime(),
|
|
|
+ "最低Co2浓度:", TEMP_FORMAT.format(co2Stats[1]) + "ppm");
|
|
|
+ addTableRow(recordTable, "", "",
|
|
|
+ "平均Co2浓度:", TEMP_FORMAT.format(co2Stats[2]) + "ppm");
|
|
|
+ }
|
|
|
+ recordTable.setMarginBottom(10);
|
|
|
+ elements.add(recordTable);
|
|
|
+
|
|
|
+ // 添加图表图片
|
|
|
+ if (ObjectUtil.isNotNull(file)) {
|
|
|
+ if (sensorType.contains("W")) {
|
|
|
+ elements.add(new Paragraph("温度曲线")
|
|
|
+ .setFont(chineseFont)
|
|
|
+ .setTextAlignment(TextAlignment.CENTER)
|
|
|
+ .setFontSize(15)
|
|
|
+ .setBold());
|
|
|
+ }
|
|
|
+ if (sensorType.contains("S")) {
|
|
|
+ elements.add(new Paragraph("湿度曲线")
|
|
|
+ .setFont(chineseFont)
|
|
|
+ .setTextAlignment(TextAlignment.CENTER)
|
|
|
+ .setFontSize(15)
|
|
|
+ .setBold());
|
|
|
+ }
|
|
|
+ if (sensorType.contains("C")) {
|
|
|
+ elements.add(new Paragraph("二氧化碳曲线")
|
|
|
+ .setFont(chineseFont)
|
|
|
+ .setTextAlignment(TextAlignment.CENTER)
|
|
|
+ .setFontSize(15)
|
|
|
+ .setBold());
|
|
|
+ }
|
|
|
|
|
|
try {
|
|
|
- // 获取图片字节数据
|
|
|
- byte[] imageBytes = file.getBytes();
|
|
|
-
|
|
|
+ byte[] imageBytes = file.getBytes(); // 一次性读取
|
|
|
ImageData imageData = ImageDataFactory.create(imageBytes);
|
|
|
Image image = new Image(imageData);
|
|
|
|
|
|
- float pageWidth = PageSize.A4.getWidth();
|
|
|
- float scaledWidth = pageWidth * 0.8f;
|
|
|
+ // 获取页面实际可用宽度(考虑页边距)
|
|
|
+ float pageWidth = PageSize.A4.getWidth() - document.getLeftMargin() - document.getRightMargin();
|
|
|
|
|
|
- image.scaleToFit(scaledWidth, image.getImageHeight());
|
|
|
- image.setHorizontalAlignment(HorizontalAlignment.CENTER);
|
|
|
- document.add(image);
|
|
|
+ // 计算缩放比例,使图片宽度等于页面可用宽度
|
|
|
+ float scaleRatio = pageWidth / image.getImageWidth();
|
|
|
+ image.scale(scaleRatio, scaleRatio); // 等比例缩放
|
|
|
|
|
|
+ // 设置对齐方式
|
|
|
+ image.setHorizontalAlignment(HorizontalAlignment.CENTER);
|
|
|
+ elements.add(image);
|
|
|
} catch (IOException e) {
|
|
|
throw new RuntimeException("图片处理失败: " + e.getMessage());
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // 温度数据表格
|
|
|
- Paragraph dataTitle = new Paragraph("温度数据")
|
|
|
- .setFont(chineseFont)
|
|
|
- .setTextAlignment(TextAlignment.CENTER)
|
|
|
- .setFontSize(15)
|
|
|
- .setBold()
|
|
|
- .setMarginBottom(2);
|
|
|
- document.add(dataTitle);
|
|
|
-
|
|
|
- Table dataTable = new Table(new float[]{2,1,2,1,2,1,2,1}).useAllAvailableWidth();
|
|
|
- for (int i = 0; i < 4; i++) {
|
|
|
- dataTable.addHeaderCell(createCell("时间", true));
|
|
|
- dataTable.addHeaderCell(createCell("°C", true));
|
|
|
+ // 批量添加所有元素到文档
|
|
|
+ for (IElement element : elements) {
|
|
|
+ if (element instanceof IBlockElement) {
|
|
|
+ document.add((IBlockElement) element);
|
|
|
+ } else if (element instanceof Image) {
|
|
|
+ document.add((Image) element);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- // 数据分组逻辑(每组4条记录)
|
|
|
- List<List<ExportParam>> groups = new ArrayList<>();
|
|
|
- List<ExportParam> currentGroup = new ArrayList<>();
|
|
|
- for (ExportParam param : exportParamList) {
|
|
|
- currentGroup.add(param);
|
|
|
- if (currentGroup.size() == 4) { // 每组4条记录(每行显示4个时间+温度)
|
|
|
- groups.add(currentGroup);
|
|
|
- currentGroup = new ArrayList<>();
|
|
|
- }
|
|
|
+ // 添加数据表格 - 调整顺序使CO2数据最后显示
|
|
|
+ if (sensorType.contains("W")) {
|
|
|
+ createChart(document, chineseFont, exportDataMap.get("temperature"), "W");
|
|
|
}
|
|
|
- // 处理剩余数据
|
|
|
- if (!currentGroup.isEmpty()) {
|
|
|
- groups.add(currentGroup);
|
|
|
+ if (sensorType.contains("S")) {
|
|
|
+ createChart(document, chineseFont, exportDataMap.get("humidity"), "S");
|
|
|
}
|
|
|
-
|
|
|
- // 填充表格数据
|
|
|
- for (List<ExportParam> group : groups) {
|
|
|
- // 添加每组数据
|
|
|
- for (ExportParam param : group) {
|
|
|
- dataTable.addCell(createCell(param.getTime(), false));
|
|
|
- dataTable.addCell(createCell(String.format("%.1f", param.getData()), false));
|
|
|
- }
|
|
|
- // 补全空单元格(每组不足4条时)
|
|
|
- int missing = 4 - group.size();
|
|
|
- for (int i = 0; i < missing; i++) {
|
|
|
- dataTable.addCell(createCell("", false));
|
|
|
- dataTable.addCell(createCell("", false));
|
|
|
- }
|
|
|
+ if (sensorType.contains("C")) {
|
|
|
+ createChart(document, chineseFont, exportDataMap.get("co2"), "C");
|
|
|
}
|
|
|
- document.add(dataTable);
|
|
|
|
|
|
document.close();
|
|
|
- } catch (Exception e) {
|
|
|
- throw new RuntimeException("PDF生成失败", e);
|
|
|
- } finally {
|
|
|
- stopWatch.stop();
|
|
|
- log.info("PDF导出耗时:{}", stopWatch.prettyPrint(TimeUnit.MILLISECONDS));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // 辅助方法:添加设备信息行
|
|
|
- private void addDeviceTableRow(Table table, String label1, String value1, String label2, String value2) {
|
|
|
- table.addCell(createCell(label1, true));
|
|
|
- table.addCell(createCell(value1, false));
|
|
|
- table.addCell(createCell(label2, true));
|
|
|
- table.addCell(createCell(value2, false));
|
|
|
+ // 添加设备表格行
|
|
|
+ private void addTableRow(Table table, String label1, String value1, String label2, String value2) throws IOException {
|
|
|
+ PdfFont CHINESE_FONT = PdfFontFactory.createFont("STSong-Light", "UniGB-UCS2-H");
|
|
|
+ table.addCell(new Cell().add(new Paragraph(label1).setFont(CHINESE_FONT)));
|
|
|
+ table.addCell(new Cell().add(new Paragraph(value1).setFont(CHINESE_FONT)));
|
|
|
+ table.addCell(new Cell().add(new Paragraph(label2).setFont(CHINESE_FONT)));
|
|
|
+ table.addCell(new Cell().add(new Paragraph(value2).setFont(CHINESE_FONT)));
|
|
|
}
|
|
|
|
|
|
- // 辅助方法:添加记录信息行
|
|
|
- private void addRecordTableRow(Table table, String label1, String value1, String label2, String value2) {
|
|
|
- table.addCell(createCell(label1, true));
|
|
|
- table.addCell(createCell(value1, false));
|
|
|
- table.addCell(createCell(label2, true));
|
|
|
- table.addCell(createCell(value2, false));
|
|
|
- }
|
|
|
+// 添加记录表格行
|
|
|
+/* private void addTableRow(Table table, String label1, String value1, String label2, String value2) {
|
|
|
+ table.addCell(new Cell().add(new Paragraph(label1).setFont(CHINESE_FONT)));
|
|
|
+ table.addCell(new Cell().add(new Paragraph(value1).setFont(CHINESE_FONT)));
|
|
|
+ table.addCell(new Cell().add(new Paragraph(label2).setFont(CHINESE_FONT)));
|
|
|
+ table.addCell(new Cell().add(new Paragraph(value2).setFont(CHINESE_FONT)));
|
|
|
+ }*/
|
|
|
|
|
|
- // 辅助方法:创建带样式的单元格
|
|
|
- private Cell createCell(String content, boolean isHeader) {
|
|
|
- Cell cell = null;
|
|
|
- try {
|
|
|
- cell = new Cell().add(new Paragraph(content))
|
|
|
- .setFont(PdfFontFactory.createFont("STSong-Light", "UniGB-UCS2-H"))
|
|
|
- .setPadding(5);
|
|
|
- } catch (IOException e) {
|
|
|
- throw new RuntimeException(e);
|
|
|
+ // 创建数据图表
|
|
|
+ private void createChart(Document document, PdfFont font, List<ExportParam> dataList, String type) {
|
|
|
+ if (dataList == null || dataList.isEmpty()) return;
|
|
|
+
|
|
|
+ Paragraph chartTitle = new Paragraph();
|
|
|
+ switch (type) {
|
|
|
+ case "W":
|
|
|
+ chartTitle = new Paragraph("温度数据").setFont(font).setBold();
|
|
|
+ break;
|
|
|
+ case "S":
|
|
|
+ chartTitle = new Paragraph("湿度数据").setFont(font).setBold();
|
|
|
+ break;
|
|
|
+ case "C":
|
|
|
+ chartTitle = new Paragraph("二氧化碳数据").setFont(font).setBold();
|
|
|
+ break;
|
|
|
}
|
|
|
- if (isHeader) {
|
|
|
- cell.setBackgroundColor(ColorConstants.LIGHT_GRAY)
|
|
|
- .setTextAlignment(TextAlignment.CENTER);
|
|
|
- } else {
|
|
|
- cell.setTextAlignment(TextAlignment.LEFT);
|
|
|
+
|
|
|
+ document.add(chartTitle.setTextAlignment(TextAlignment.CENTER).setFontSize(15));
|
|
|
+
|
|
|
+ // 创建8列表格,一宽一窄交替(宽列3倍于窄列)
|
|
|
+ float[] columnWidths = {3, 1, 3, 1, 3, 1}; // 宽:窄 = 3:1
|
|
|
+ Table dataTable = new Table(UnitValue.createPercentArray(columnWidths))
|
|
|
+ .useAllAvailableWidth()
|
|
|
+ .setHorizontalAlignment(HorizontalAlignment.CENTER);
|
|
|
+
|
|
|
+ // 添加表头 - 8列,交替显示"时间"和单位
|
|
|
+ for (int i = 0; i < 3; i++) {
|
|
|
+ dataTable.addHeaderCell(new Cell().add(new Paragraph("时间").setFont(font)));
|
|
|
+ dataTable.addHeaderCell(new Cell().add(new Paragraph(type.equals("C") ? "浓度" :
|
|
|
+ (type.equals("W") ? "温度" : "湿度")).setFont(font)));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加数据行,每行4组数据(8列)
|
|
|
+ for (int i = 0; i < dataList.size(); i += 4) {
|
|
|
+ // 处理每4个数据点为一组(对应8列)
|
|
|
+ for (int j = 0; j < 4 && (i + j) < dataList.size(); j++) {
|
|
|
+ ExportParam param = dataList.get(i + j);
|
|
|
+
|
|
|
+ // 宽列:时间
|
|
|
+ dataTable.addCell(new Cell().add(new Paragraph(formatTime(param.getTime())).setFont(font)));
|
|
|
+
|
|
|
+ // 窄列:数值
|
|
|
+ String value = TEMP_FORMAT.format(param.getData());
|
|
|
+ if (type.equals("W")) {
|
|
|
+ value += "°C";
|
|
|
+ } else if (type.equals("S")) {
|
|
|
+ value += "%";
|
|
|
+ } else {
|
|
|
+ value += "ppm";
|
|
|
+ }
|
|
|
+ dataTable.addCell(new Cell().add(new Paragraph(value).setFont(font)));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果最后一组不足4个,用空单元格填充
|
|
|
+ int remaining = 4 - (dataList.size() - i) % 4;
|
|
|
+ if (remaining > 0 && remaining < 4 && i + 4 >= dataList.size()) {
|
|
|
+ for (int k = 0; k < remaining * 2; k++) {
|
|
|
+ dataTable.addCell(new Cell().add(new Paragraph("").setFont(font)));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ document.add(dataTable.setMarginBottom(20));
|
|
|
+ }
|
|
|
+
|
|
|
+ private String formatTime(String time) {
|
|
|
+ String[] split = time.split(" ");
|
|
|
+ String regex = "[\u4e00-\u9fa5]";
|
|
|
+ Pattern pattern = Pattern.compile(regex);
|
|
|
+ Matcher matcher = pattern.matcher(split[0]);
|
|
|
+ String s = String.join("-", matcher.replaceAll("-").split("-"));
|
|
|
+ if (split.length != 2) {
|
|
|
+ return time;
|
|
|
}
|
|
|
- return cell;
|
|
|
+ return switch (split[1]) {
|
|
|
+ case "零点" -> s + " 00:00";
|
|
|
+ case "一点" -> s + " 01:00";
|
|
|
+ case "两点" -> s + " 02:00";
|
|
|
+ case "三点" -> s + " 03:00";
|
|
|
+ case "四点" -> s + " 04:00";
|
|
|
+ case "五点" -> s + " 05:00";
|
|
|
+ case "六点" -> s + " 06:00";
|
|
|
+ case "七点" -> s + " 07:00";
|
|
|
+ case "八点" -> s + " 08:00";
|
|
|
+ case "九点" -> s + " 09:00";
|
|
|
+ case "十点" -> s + " 10:00";
|
|
|
+ case "十一点" -> s + " 11:00";
|
|
|
+ case "十二点" -> s + " 12:00";
|
|
|
+ case "十三点" -> s + " 13:00";
|
|
|
+ case "十四点" -> s + " 14:00";
|
|
|
+ case "十五点" -> s + " 15:00";
|
|
|
+ case "十六点" -> s + " 16:00";
|
|
|
+ case "十七点" -> s + " 17:00";
|
|
|
+ case "十八点" -> s + " 18:00";
|
|
|
+ case "十九点" -> s + " 19:00";
|
|
|
+ case "二十点" -> s + " 20:00";
|
|
|
+ case "二十一点" -> s + " 21:00";
|
|
|
+ case "二十二点" -> s + " 22:00";
|
|
|
+ case "二十三点" -> s + " 23:00";
|
|
|
+ case "二十四点" -> s + " 24:00";
|
|
|
+ default -> time;
|
|
|
+ };
|
|
|
}
|
|
|
}
|