PDF导出功能开发

This commit is contained in:
2026-01-04 17:10:11 +08:00
parent a76e2752cb
commit 0273ce0a9a
7 changed files with 372 additions and 1 deletions

View File

@@ -150,6 +150,27 @@
<artifactId>swagger-models</artifactId>
<version>1.6.3</version>
</dependency>
<!-- 导出PDF使用带图表 -->
<!-- JFreeChart图表生成核心 -->
<dependency>
<groupId>org.jfree</groupId>
<artifactId>jfreechart</artifactId>
<version>1.5.4</version>
</dependency>
<!-- iText 7PDF 生成核心) -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext7-core</artifactId>
<version>7.2.5</version>
<type>pom</type>
</dependency>
<!-- iText 中文字体支持 -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>font-asian</artifactId>
<version>7.2.5</version>
</dependency>
</dependencies>
</project>

View File

@@ -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中文字体关键解决图表中文方块乱码
// 选择系统存在的中文字体WindowsSimHei/黑体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;
}
}

View File

@@ -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<VideoIdentification> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper
.between(VideoIdentification::getTakeTime, DateUtil.offset(DateUtil.date(), DateField.SECOND, 330), DateUtil.date())
.orderByAsc(VideoIdentification::getTakeTime);
List<VideoIdentification> list = videoIdentificationService.list(queryWrapper);
// 调用工具类,写入响应
PdfExportUtil.writePdfToResponse(response, list, title);
}
}

View File

@@ -235,4 +235,8 @@ public class DsVideo extends Model<DsVideo> {
@ApiModelProperty(value = "是否开启可视范围1-是0-否")
@TableField(value = "whether_to_turn_on_the_view")
private Integer whetherToTurnOnTheView;
@ApiModelProperty(value = "轮询顺序,页面轮询使用")
@TableField(value = "play_index")
private Integer playIndex;
}

View File

@@ -349,7 +349,7 @@ public class DsVideoServiceImpl extends ServiceImpl<DsVideoMapper, DsVideo> impl
@Override
public DsVideo getVideoInfo(DsVideo dsVideo) {
DsVideo dbDsVideo = dsVideoService.getById(dsVideo.getId());
DsVideo dbDsVideo = dsVideoService.getById(Long.parseLong(dsVideo.getId()));
return getDsVideoPTZ(dbDsVideo);
}

View File

@@ -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<VideoIdentification> list, String fileName) throws IOException {
public static byte[] generatePdfBytes(List<VideoIdentification> 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<VideoIdentification> 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();
}
}

View File

@@ -41,6 +41,7 @@
<result property="beBayonet" column="be_bayonet"/>
<result property="beLock" column="be_lock"/>
<result property="whetherToTurnOnTheView" column="whether_to_turn_on_the_view"/>
<result property="playIndex" column="play_index"/>
</resultMap>
</mapper>