From 0273ce0a9aab42b3e43936930a9a9b55f7e8de76 Mon Sep 17 00:00:00 2001 From: qichengbin Date: Sun, 4 Jan 2026 17:10:11 +0800 Subject: [PATCH] =?UTF-8?q?PDF=E5=AF=BC=E5=87=BA=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E5=BC=80=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- smartFishingPort-common/pom.xml | 21 +++ .../controller/HutoolChartPdfDemo.java | 137 ++++++++++++++++ .../controller/PollExportController.java | 56 +++++++ .../domain/entity/DsVideo.java | 4 + .../service/impl/DsVideoServiceImpl.java | 2 +- .../util/pdf/PdfExportUtil.java | 152 ++++++++++++++++++ .../main/resources/mapper/DsVideoMapper.xml | 1 + 7 files changed, 372 insertions(+), 1 deletion(-) create mode 100644 smartFishingPort-kechuang/src/main/java/com/ltgk/smartFishingPort/controller/HutoolChartPdfDemo.java create mode 100644 smartFishingPort-kechuang/src/main/java/com/ltgk/smartFishingPort/controller/PollExportController.java create mode 100644 smartFishingPort-kechuang/src/main/java/com/ltgk/smartFishingPort/util/pdf/PdfExportUtil.java diff --git a/smartFishingPort-common/pom.xml b/smartFishingPort-common/pom.xml index 1605c08..c01e11d 100644 --- a/smartFishingPort-common/pom.xml +++ b/smartFishingPort-common/pom.xml @@ -150,6 +150,27 @@ swagger-models 1.6.3 + + + + + org.jfree + jfreechart + 1.5.4 + + + + com.itextpdf + itext7-core + 7.2.5 + pom + + + + com.itextpdf + font-asian + 7.2.5 + \ No newline at end of file diff --git a/smartFishingPort-kechuang/src/main/java/com/ltgk/smartFishingPort/controller/HutoolChartPdfDemo.java b/smartFishingPort-kechuang/src/main/java/com/ltgk/smartFishingPort/controller/HutoolChartPdfDemo.java new file mode 100644 index 0000000..1f4e381 --- /dev/null +++ b/smartFishingPort-kechuang/src/main/java/com/ltgk/smartFishingPort/controller/HutoolChartPdfDemo.java @@ -0,0 +1,137 @@ +package com.ltgk.smartFishingPort.controller; + +import com.itextpdf.io.image.ImageData; +import com.itextpdf.io.image.ImageDataFactory; +import com.itextpdf.kernel.font.PdfFont; +import com.itextpdf.kernel.font.PdfFontFactory; +import com.itextpdf.kernel.pdf.PdfDocument; +import com.itextpdf.kernel.pdf.PdfWriter; +import com.itextpdf.layout.Document; +import com.itextpdf.layout.element.Image; +import com.itextpdf.layout.element.Paragraph; +import com.itextpdf.layout.element.Text; +import com.itextpdf.layout.properties.TextAlignment; +import org.jfree.chart.ChartFactory; +import org.jfree.chart.ChartUtils; +import org.jfree.chart.JFreeChart; +import org.jfree.chart.plot.PiePlot; +import org.jfree.data.general.DefaultPieDataset; + +import java.awt.*; +import java.io.ByteArrayOutputStream; +import java.io.File; + +public class HutoolChartPdfDemo { + // 全局iText中文字体(解决PDF文本中文乱码) + private static final PdfFont ITEXT_CHINESE_FONT; + + static { + try { + // 方案1:使用iText自带中文字体(需引入font-asian依赖,推荐) + ITEXT_CHINESE_FONT = PdfFontFactory.createFont( + "STSongStd-Light", + "UniGB-UCS2-H", + PdfFontFactory.EmbeddingStrategy.PREFER_EMBEDDED + ); + + // 方案2:本地字体(Windows备用,注释掉方案1可启用) + // ITEXT_CHINESE_FONT = PdfFontFactory.createFont( + // "C:/Windows/Fonts/simsun.ttc,0", + // PdfEncodings.IDENTITY_H, + // PdfFontFactory.EmbeddingStrategy.PREFER_EMBEDDED + // ); + } catch (Exception e) { + throw new RuntimeException("iText加载中文字体失败", e); + } + } + + public static void main(String[] args) { + // 1. 定义PDF输出文件 + File pdfFile = new File("pie_chart.pdf"); + + try ( + // 2. 初始化PDF写入器和文档(try-with-resources自动关闭) + PdfWriter writer = new PdfWriter(pdfFile); + PdfDocument pdfDoc = new PdfDocument(writer); + Document document = new Document(pdfDoc); + // 3. 字节流存储图表(避免本地文件) + ByteArrayOutputStream chartStream = new ByteArrayOutputStream() + ) { +// // ========== 核心1:生成带中文字体的饼图 ========== +// JFreeChart pieChart = createPieChartWithChineseFont(); +// // 将饼图写入字节流(600x400高清尺寸) +// ChartUtils.writeChartAsPNG(chartStream, pieChart, 600, 400); +// +// // ========== 核心2:修复ImageDataFactory.create入参问题 ========== +// // 提取字节数组(替代InputStream,兼容所有iText版本) +// byte[] chartBytes = chartStream.toByteArray(); +// // 关键:用byte[]入参创建ImageData,无任何重载歧义 +// ImageData imageData = ImageDataFactory.create(chartBytes); +// Image chartImage = new Image(imageData); +// +// // ========== 配置图片样式 ========== +// // 自适应PDF页面宽度(左右留20px边距) +// float pageWidth = document.getPageEffectiveArea(pdfDoc.getDefaultPageSize()).getWidth(); +// chartImage.setWidth(pageWidth - 40); +// chartImage.setTextAlignment(TextAlignment.CENTER); // 图片居中 + + // ========== 配置PDF文本(中文正常显示) ========== + Paragraph title = new Paragraph("产品销量占比") + .setFont(ITEXT_CHINESE_FONT) // 强制中文字体 + .setFontSize(16) + .setBold() + .setMarginBottom(10) + .setTextAlignment(TextAlignment.CENTER); // 标题居中 + + // ========== 组装PDF内容 ========== + document.add(title); +// document.add(chartImage); + Paragraph title2 = new Paragraph("翻身的机会发射点发射点季后赛得分翻身的机会发射点发射点季后赛得分翻身的机会发射点发射点季后赛得分翻身的机会发射点发射点季后赛得分翻身的机会发射点发射点季后赛得分翻身的机会发射点发射点季后赛得分翻身的机会发射点发射点季后赛得分翻身的机会发射点发射点季后赛得分翻身的机会发射点发射点季后赛得分") + .setFont(ITEXT_CHINESE_FONT) + .setFontSize(14) + .setBold() + .setMarginBottom(10) + .setTextAlignment(TextAlignment.LEFT) + .setFirstLineIndent(28); // 两个中文字符的宽度 + + document.add(title2); + + System.out.println("PDF生成成功:" + pdfFile.getAbsolutePath()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 生成带中文字体的饼图(解决JFreeChart中文乱码) + */ + private static JFreeChart createPieChartWithChineseFont() { + // 1. 构建饼图数据集 + DefaultPieDataset dataset = new DefaultPieDataset(); + dataset.setValue("产品A", 35); + dataset.setValue("产品B", 25); + dataset.setValue("产品C", 40); + + // 2. 创建饼图 + JFreeChart pieChart = ChartFactory.createPieChart( + "2025年Q1产品销量占比", // 图表标题 + dataset, // 数据集 + true, // 显示图例 + true, // 显示工具提示 + false // 不生成URL + ); + + // 3. 配置JFreeChart中文字体(关键:解决图表中文方块乱码) + // 选择系统存在的中文字体(Windows:SimHei/黑体;Linux:需安装simhei.ttf) + Font chineseFont = new Font("SimHei", Font.PLAIN, 12); + // 图表标题字体 + pieChart.getTitle().setFont(new Font("SimHei", Font.BOLD, 14)); + // 图例字体 + pieChart.getLegend().setItemFont(chineseFont); + // 饼图标签字体 + PiePlot plot = (PiePlot) pieChart.getPlot(); + plot.setLabelFont(chineseFont); + + return pieChart; + } +} \ No newline at end of file diff --git a/smartFishingPort-kechuang/src/main/java/com/ltgk/smartFishingPort/controller/PollExportController.java b/smartFishingPort-kechuang/src/main/java/com/ltgk/smartFishingPort/controller/PollExportController.java new file mode 100644 index 0000000..e182493 --- /dev/null +++ b/smartFishingPort-kechuang/src/main/java/com/ltgk/smartFishingPort/controller/PollExportController.java @@ -0,0 +1,56 @@ +package com.ltgk.smartFishingPort.controller; + + +import cn.hutool.core.date.DateField; +import cn.hutool.core.date.DateUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.ltgk.smartFishingPort.common.core.controller.BaseController; +import com.ltgk.smartFishingPort.domain.entity.VideoIdentification; +import com.ltgk.smartFishingPort.service.IVideoIdentificationService; +import com.ltgk.smartFishingPort.util.pdf.PdfExportUtil; +import io.swagger.annotations.Api; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.List; + +/** + * 轮询巡检报告导出 + * + * @author Qi ChengBin + * @date 2025/12/30 + */ +@Api(tags = {"轮询巡检报告导出"}) +@RestController +@RequestMapping("/pollExport") +public class PollExportController extends BaseController { + + @Resource + private IVideoIdentificationService videoIdentificationService; + + /** + * CCTV 轮询 巡检报告 + * + * @param response: + * @author Qi ChengBin + * @date 2025/12/31 + */ + @PostMapping("/cctvExport") + public void cctvExport(HttpServletResponse response) throws IOException { + String time = DateUtil.format(DateUtil.date(), "yyyy年MM月dd号HH时mm分"); + String title = time + "-CCTV轮询巡检报告"; + + // 按照拍照时间倒叙,查出最后10条数据 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper + .between(VideoIdentification::getTakeTime, DateUtil.offset(DateUtil.date(), DateField.SECOND, 330), DateUtil.date()) + .orderByAsc(VideoIdentification::getTakeTime); + List list = videoIdentificationService.list(queryWrapper); + // 调用工具类,写入响应 + PdfExportUtil.writePdfToResponse(response, list, title); + } +} diff --git a/smartFishingPort-kechuang/src/main/java/com/ltgk/smartFishingPort/domain/entity/DsVideo.java b/smartFishingPort-kechuang/src/main/java/com/ltgk/smartFishingPort/domain/entity/DsVideo.java index 8b46429..935f334 100644 --- a/smartFishingPort-kechuang/src/main/java/com/ltgk/smartFishingPort/domain/entity/DsVideo.java +++ b/smartFishingPort-kechuang/src/main/java/com/ltgk/smartFishingPort/domain/entity/DsVideo.java @@ -235,4 +235,8 @@ public class DsVideo extends Model { @ApiModelProperty(value = "是否开启可视范围;1-是,0-否") @TableField(value = "whether_to_turn_on_the_view") private Integer whetherToTurnOnTheView; + + @ApiModelProperty(value = "轮询顺序,页面轮询使用") + @TableField(value = "play_index") + private Integer playIndex; } diff --git a/smartFishingPort-kechuang/src/main/java/com/ltgk/smartFishingPort/service/impl/DsVideoServiceImpl.java b/smartFishingPort-kechuang/src/main/java/com/ltgk/smartFishingPort/service/impl/DsVideoServiceImpl.java index c228dec..d422ebb 100644 --- a/smartFishingPort-kechuang/src/main/java/com/ltgk/smartFishingPort/service/impl/DsVideoServiceImpl.java +++ b/smartFishingPort-kechuang/src/main/java/com/ltgk/smartFishingPort/service/impl/DsVideoServiceImpl.java @@ -349,7 +349,7 @@ public class DsVideoServiceImpl extends ServiceImpl impl @Override public DsVideo getVideoInfo(DsVideo dsVideo) { - DsVideo dbDsVideo = dsVideoService.getById(dsVideo.getId()); + DsVideo dbDsVideo = dsVideoService.getById(Long.parseLong(dsVideo.getId())); return getDsVideoPTZ(dbDsVideo); } diff --git a/smartFishingPort-kechuang/src/main/java/com/ltgk/smartFishingPort/util/pdf/PdfExportUtil.java b/smartFishingPort-kechuang/src/main/java/com/ltgk/smartFishingPort/util/pdf/PdfExportUtil.java new file mode 100644 index 0000000..9a58d5c --- /dev/null +++ b/smartFishingPort-kechuang/src/main/java/com/ltgk/smartFishingPort/util/pdf/PdfExportUtil.java @@ -0,0 +1,152 @@ +package com.ltgk.smartFishingPort.util.pdf; + +import cn.hutool.core.date.DateUtil; +import com.itextpdf.kernel.font.PdfFont; +import com.itextpdf.kernel.font.PdfFontFactory; +import com.itextpdf.kernel.pdf.PdfDocument; +import com.itextpdf.kernel.pdf.PdfWriter; +import com.itextpdf.layout.Document; +import com.itextpdf.layout.element.Paragraph; +import com.itextpdf.layout.properties.TextAlignment; +import com.ltgk.smartFishingPort.domain.entity.VideoIdentification; +import org.jfree.chart.ChartFactory; +import org.jfree.chart.JFreeChart; +import org.jfree.chart.plot.PiePlot; +import org.jfree.data.general.DefaultPieDataset; +import org.springframework.util.CollectionUtils; + +import javax.servlet.http.HttpServletResponse; +import java.awt.*; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.List; + +/** + * PDF导出工具类 + * + * @author Qi ChengBin + * @date 2025/12/31 + */ +public class PdfExportUtil { + + /** + * 获取中文字体(每次调用创建新实例) + * + * @return com.itextpdf.kernel.font.PdfFont + * @author Qi ChengBin + * @date 2026/1/4 + */ + private static PdfFont getChineseFont() throws IOException { + return PdfFontFactory.createFont( + "STSongStd-Light", + "UniGB-UCS2-H", + PdfFontFactory.EmbeddingStrategy.PREFER_EMBEDDED + ); + } + + /** + * 生成PDF字节流(核心逻辑) + */ +// public synchronized static byte[] generatePdfBytes(List list, String fileName) throws IOException { + public static byte[] generatePdfBytes(List list, String fileName) throws IOException { + // 字节流存储PDF(替代本地文件) + ByteArrayOutputStream pdfOut = new ByteArrayOutputStream(); + try ( + PdfWriter writer = new PdfWriter(pdfOut); + PdfDocument pdfDoc = new PdfDocument(writer); + Document document = new Document(pdfDoc); +// ByteArrayOutputStream chartStream = new ByteArrayOutputStream(); + ) { + + // 获取当前PDF文档专用的字体实例 + PdfFont chineseFont = getChineseFont(); + + // ========== 2. 生成PDF文本内容 ========== + Paragraph title = new Paragraph(fileName) + .setFont(chineseFont).setFontSize(20).setBold() + .setMarginBottom(10).setTextAlignment(TextAlignment.CENTER) + .setFontColor(com.itextpdf.kernel.colors.ColorConstants.RED); + + // 添加分隔符 + Paragraph separator = new Paragraph("========================================================================") + .setFont(chineseFont).setFontSize(12) + .setMarginTop(10).setMarginBottom(10).setTextAlignment(TextAlignment.CENTER) + .setFontColor(com.itextpdf.kernel.colors.ColorConstants.RED); + + document.add(title); + document.add(separator); + if (CollectionUtils.isEmpty(list)) { + Paragraph content = new Paragraph("暂无告警信息!") + .setFont(chineseFont).setFontSize(14) + .setMarginBottom(10).setTextAlignment(TextAlignment.CENTER) + .setFirstLineIndent(28); // 首行缩进2个中文字符 + // ========== 3. 组装PDF内容 ========== + document.add(content); + } else { + for (VideoIdentification item : list) { + Paragraph content = new Paragraph( + "船名为:" + item.getBoatName() + ";船型为:" + item.getShipType() + ";在 " + DateUtil.format(item.getTakeTime(), "yyyy-MM-dd HH:mm:ss") + " 时出现" + item.getIllegalType() + "违章【需人工核查!】" + ) + .setFont(chineseFont).setFontSize(14) + .setMarginBottom(10).setTextAlignment(TextAlignment.LEFT) + .setFirstLineIndent(28); // 首行缩进2个中文字符 + // ========== 3. 组装PDF内容 ========== + document.add(content); + } + } + + // 关闭文档,确保内容写入字节流 + document.close(); + pdfDoc.close(); + + return pdfOut.toByteArray(); + } catch (Exception e) { + throw new IOException("PDF生成失败", e); + } + } + + /** + * 生成带中文字体的饼图(解决JFreeChart中文乱码) + */ + private static JFreeChart createPieChartWithChinese() { + // 1. 准备饼图数据 + DefaultPieDataset dataset = new DefaultPieDataset(); + dataset.setValue("产品A", 30); + dataset.setValue("产品B", 50); + dataset.setValue("产品C", 20); + + // 2. 创建饼图 + JFreeChart chart = ChartFactory.createPieChart( + "产品销量占比", // 标题 + dataset, true, true, false // 图例、提示、URL + ); + + // 3. 设置中文字体 + Font font = new Font("宋体", Font.PLAIN, 12); + chart.getTitle().setFont(new Font("宋体", Font.BOLD, 16)); // 标题字体 + chart.getLegend().setItemFont(font); // 图例字体 + ((PiePlot) chart.getPlot()).setLabelFont(font); // 饼图标签字体 + + return chart; + } + + /** + * 将PDF写入响应,触发前端下载 + */ + public static void writePdfToResponse(HttpServletResponse response, List list, String fileName) throws IOException { + byte[] pdfBytes = generatePdfBytes(list, fileName); + + // 设置响应头(解决下载乱码+指定PDF类型) + response.setContentType("application/pdf"); + response.setCharacterEncoding("UTF-8"); + String encodeFileName = URLEncoder.encode(fileName, String.valueOf(StandardCharsets.UTF_8)).replaceAll("\\+", "%20"); + response.setHeader("Content-Disposition", "attachment; filename=\"" + encodeFileName + ".pdf\""); + response.setContentLength(pdfBytes.length); + + // 写入响应流 + response.getOutputStream().write(pdfBytes); + response.getOutputStream().flush(); + } +} \ No newline at end of file diff --git a/smartFishingPort-kechuang/src/main/resources/mapper/DsVideoMapper.xml b/smartFishingPort-kechuang/src/main/resources/mapper/DsVideoMapper.xml index 6b20c95..2f71003 100644 --- a/smartFishingPort-kechuang/src/main/resources/mapper/DsVideoMapper.xml +++ b/smartFishingPort-kechuang/src/main/resources/mapper/DsVideoMapper.xml @@ -41,6 +41,7 @@ +