目录
- 1、背景
- 2、需求
- 3、思路
- 4、实现步骤
- 4.1 搭建一个简单的工程
- 4.1.1 引入依赖
- 4.1.2 编写Freemarker工具类
- 4.1.3 编写pdf工具类
- 4.1.4 增加一个模板
- 4.1.5 增加一个控制层
- 4.1.6 运行
- 4.2 功能完善
- 4.2.1 生成的pdf需要支持中文
- 4.2.1.1 程序中引入宋体
- 4.2.1.2 pdf工具类中增加使用中文字体
- 4.2.1.3 freemarker模板中使用中文字体
- 4.2.1.4 运行
- 4.2.2 生成的pdf支持简单的样式
- 4.2.2.1 freemarker模板中使用css样式
- 4.2.2.2 运行
- 4.2.3 表格的某一行不要出现跨页
- 4.2.3.1 freemarker模板中增加一个表格
- 4.2.3.2 查看效果
- 4.3.2.3 css解决
- 4.3.2.4 查看效果
- 4.2.4 单独开启一页pdf
- 4.2.4.1 freemarker模板修改
- 4.2.4.2 查看效果
- 4.2.5 指定pdf页面的规格
- 4.2.5.1 css样式指定页面规则
- 4.2.5.2 查看效果
- 4.2.6 pdf 加密
- 4.2.6.1 修改pdf生成的工具类
- 4.2.6.2 查看效果
- 5、完整代码
1、背景
最近项目中需要生成日报文件,日报文件的格式为pdf,且日报的样式相对而言比较复杂,存在多段文字,存在多个表格,且存在样式。目前想到的解决办法是
先生成html文件,让后将html文件转换成pdf文件。通过网上搜索,发现openhtmltopdf可以实现我们的需求,此处记录一下。
2、需求
- 生成的pdf需要支持中文。
- 生成的pdf支持简单的样式。(此处可以使用css样式来解决,但不是所有的css样式都支持)
- 生成的pdf存在表格,每行应完整地出现在同一页,不要一半在上一页、一半在下一页。
- 生成的pdf可以自己指定到分页,比如某个表格的数据渲染完之后,需要单独开启一页。
- 生成的pdf支持密码加密。
- 生成的pdf可以支持纸张规格,比如是A3还是A4,并且还可设置横向还是纵向。
3、思路
1、html的生成,我们可以通过freemarker来实现。
2、html转pdf,通过openhtmltopdf来实现。
4、实现步骤
4.1 搭建一个简单的工程
首先搭建一个简单的可运行的程序,可实现Freemarker渲染模板,然后生成pdf文件
4.1.1 引入依赖
- <dependencies>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- spring-boot-starter-web</artifactId>
- <version>2.6.0</version>
- </dependency>
-
- <dependency>
- <groupId>org.freemarker</groupId>
- freemarker</artifactId>
- <version>2.3.30</version>
- </dependency>
-
- <dependency>
- <groupId>com.openhtmltopdf</groupId>
- openhtmltopdf-pdfbox</artifactId>
- <version>1.0.10</version>
- </dependency>
- <dependency>
- <groupId>org.projectlombok</groupId>
- lombok</artifactId>
- <version>1.18.36</version>
- </dependency>
- </dependencies>
复制代码 4.1.2 编写Freemarker工具类
加载程序中src/main/resources/templates/ftls目录下的模板文件,然后渲染成html内容。- package com.huan.pdf.utils;
- import freemarker.cache.ClassTemplateLoader;
- import freemarker.template.Configuration;
- import freemarker.template.Template;
- import freemarker.template.TemplateExceptionHandler;
- import lombok.extern.slf4j.Slf4j;
- import java.io.StringWriter;
- import java.util.Map;
- /**
- * freemarker 工具类
- *
- * @author admin
- */
- @Slf4j
- public class FreemarkerUtils {
- /**
- * 模板文件夹路径
- */
- private static final String TEMPLATE_DIR = "/templates/ftls";
- private static final Configuration CONFIGURATION;
- static {
- CONFIGURATION = new Configuration(Configuration.VERSION_2_3_30);
- CONFIGURATION.setTemplateLoader(new ClassTemplateLoader(FreemarkerUtils.class, TEMPLATE_DIR));
- CONFIGURATION.setDefaultEncoding("UTF-8");
- CONFIGURATION.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
- CONFIGURATION.setLogTemplateExceptions(false);
- CONFIGURATION.setWrapUncheckedExceptions(true);
- }
- /**
- * 根据模板名称和数据模型生成字符串
- *
- * @param templateName 模板名称
- * @param dataModel 数据模型
- * @return 生成的字符串
- */
- public static String processTemplate(String templateName, Map<String, Object> dataModel) {
- try {
- Template template = CONFIGURATION.getTemplate(templateName);
- StringWriter writer = new StringWriter();
- template.process(dataModel, writer);
- return writer.toString();
- } catch (Exception e) {
- log.error("解析模板出现问题", e);
- }
- return "";
- }
- }
复制代码 4.1.3 编写pdf工具类
编写pdf工具类,用于将html内容渲染成pdf文件,此处只是简单实现,后期该类还需要修改- package com.huan.pdf.utils;
- import com.openhtmltopdf.pdfboxout.PdfRendererBuilder;
- import lombok.extern.slf4j.Slf4j;
- import javax.servlet.http.HttpServletResponse;
- import java.io.IOException;
- import java.io.OutputStream;
- import java.util.UUID;
- /**
- * pdf工具类
- *
- * @author admin
- */
- @Slf4j
- public class PdfUtils {
- /**
- * 生成pdf文件
- *
- * @param pdfTemplate pdf模板
- * @param response http response
- */
- public static void generatePdf(String pdfTemplate, HttpServletResponse response) {
- // 设置响应头
- String fileName = UUID.randomUUID() + ".pdf";
- response.setContentType("application/pdf");
- response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
- try (OutputStream os = response.getOutputStream()) {
- PdfRendererBuilder builder = new PdfRendererBuilder();
- builder.withHtmlContent(pdfTemplate, null);
- builder.toStream(os);
- builder.run();
- } catch (IOException e) {
- log.error("生成pdf文件失败", e);
- throw new RuntimeException("生成pdf文件失败", e);
- }
- }
- }
复制代码 4.1.4 增加一个模板
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8" />
- <title>生成pdf</title>
-
- </head>
- <body>
- ${mainTitle}
- </body>
- </html>
复制代码 该模板中存在变量mainTitle,这个变量的值通过后台来赋值
4.1.5 增加一个控制层
- package com.huan.pdf.controller;
- import com.huan.pdf.utils.FreemarkerUtils;
- import com.huan.pdf.utils.PdfUtils;
- import org.springframework.web.bind.annotation.GetMapping;
- import org.springframework.web.bind.annotation.RestController;
- import javax.servlet.http.HttpServletResponse;
- import java.time.LocalDateTime;
- import java.time.format.DateTimeFormatter;
- import java.util.HashMap;
- import java.util.Map;
- /**
- * pdf控制器
- *
- * @author admin
- */
- @RestController
- public class PdfController {
- @GetMapping("pdf")
- public void pdf(HttpServletResponse response) {
- Map<String, Object> params = new HashMap<>(16);
- params.put("mainTitle", "这是一个标题 - " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
- // 渲染模板
- String htmlContent = FreemarkerUtils.processTemplate("pdf.ftl", params);
- // 生成pdf
- PdfUtils.generatePdf(htmlContent, response);
- }
- }
复制代码 注意:此处的mainTitle中存在中文,生产的Pdf会乱码待会儿在处理
4.1.6 运行
可以看到可以正常的生成pdf了,但是中文乱码了。 至此我们一个简单的程序就搭建完成了,下面让我们来完善功能。
4.2 功能完善
4.2.1 生成的pdf需要支持中文
默认情况下生成的pdf,中文是乱码的,若需要解决这个问题,就需要引入中文字体。此处我们使用宋体。
4.2.1.1 程序中引入宋体
在程序的src/main/resources/fonts目录下,引入宋体(simsun.ttf)
4.2.1.2 pdf工具类中增加使用中文字体
- builder.useFont(() -> PdfUtils.class.getClassLoader().getResourceAsStream("fonts/simsun.ttf"), "SimSun");
复制代码
4.2.1.3 freemarker模板中使用中文字体
- [/code][align=center][img]https://p0-xtjj-private.juejin.cn/tos-cn-i-73owjymdk6/c193391475694f9698f7cc8fb2b9443b~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAgaHVhbjE5OTM=:q75.awebp?policy=eyJ2bSI6MywidWlkIjoiNDQ4MjU2NDc3NTAyMjk1In0%3D&rk3s=e9ecf3d6&x-orig-authkey=f32326d3454f2ac7e96d3d06cdbb035152127018&x-orig-expires=1758946856&x-orig-sign=aGuG3%2B10KqU0Zks7O1dUEGGkbiQ%3D[/img][/align]
- [size=3]4.2.1.4 运行[/size]
- [align=center][img]https://p0-xtjj-private.juejin.cn/tos-cn-i-73owjymdk6/eaca0fd3755e4030b8b25184e559a569~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAgaHVhbjE5OTM=:q75.awebp?policy=eyJ2bSI6MywidWlkIjoiNDQ4MjU2NDc3NTAyMjk1In0%3D&rk3s=e9ecf3d6&x-orig-authkey=f32326d3454f2ac7e96d3d06cdbb035152127018&x-orig-expires=1758946856&x-orig-sign=E%2FrP2agyvcyC5cRWrdvWtvoJGtQ%3D[/img][/align]
- 从上图中可以看到,现在已经可以展示中文了。
- [size=4]4.2.2 生成的pdf支持简单的样式[/size]
- 此处实现将生成的pdf中的 这是一个标题-时间 这句话的字体修改成红色。
- [size=3]4.2.2.1 freemarker模板中使用css样式[/size]
- [code].main-title { text-align: center; font-size:25px; color:#FF0000; }
复制代码
4.2.2.2 运行
通过上图可知,样式已经生效了。
4.2.3 表格的某一行不要出现跨页
4.2.3.1 freemarker模板中增加一个表格
- <table>
- <tr><td>序号</td></tr>
- <tr><td>1</td></tr>
- <tr><td>2</td></tr>
- <tr><td>3</td></tr>
- <tr><td>4</td></tr>
- <tr><td>5</td></tr>
- </table>
复制代码
4.2.3.2 查看效果
从上图可以看到,生成的pdf,内容跨了2页,那么如何解决这个问题呢?通过css样式解决
4.3.2.3 css解决
- table { border-collapse: collapse; page-break-inside: auto;}
- tr { page-break-inside: avoid;}
复制代码 4.3.2.4 查看效果
4.2.4 单独开启一页pdf
4.2.4.1 freemarker模板修改
通过css样式page-break-before:always开启新的一页pdf。
4.2.4.2 查看效果
4.2.5 指定pdf页面的规格
默认情况是A4 纵向,现在我想修改成A3 横向。这个指定对所有的页面都生效,不可只对某一个页面生效,若想对某一个页面生效,可以生成多个pdf文件,然后进行pdf文件的合并操作。
4.2.5.1 css样式指定页面规则
- @page{ size:A3 landscape; }
复制代码 4.2.5.2 查看效果
从上图中可知 正好是A3横向
4.2.6 pdf 加密
实现思路:通过pdfbox生成加密的密码,此处给默认密码a0nin13s
4.2.6.1 修改pdf生成的工具类
- /**
- * 生成带密码的 PDF 文件(用户密码 a0min13s)
- *
- * @param pdfTemplate HTML 模板字符串
- * @param response HTTP 响应
- */
- public static void generatePdf(String pdfTemplate, HttpServletResponse response) {
- String fileName = UUID.randomUUID() + ".pdf";
- response.setContentType("application/pdf");
- response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
- // 1. 先用 openhtmltopdf 生成未加密 PDF(内存)
- ByteArrayOutputStream temp = new ByteArrayOutputStream();
- try {
- PdfRendererBuilder builder = new PdfRendererBuilder();
- builder.useFont(() -> PdfUtils.class.getClassLoader().getResourceAsStream("fonts/simsun.ttf"), "SimSun");
- builder.withHtmlContent(pdfTemplate, null);
- builder.toStream(temp);
- // 完成渲染
- builder.run();
- } catch (IOException e) {
- log.error("生成PDF失败", e);
- throw new RuntimeException("生成PDF失败");
- }
- // 用 PDFBox 加载并加密
- try (PDDocument doc = PDDocument.load(temp.toByteArray());
- OutputStream os = response.getOutputStream()) {
- AccessPermission ap = new AccessPermission();
- // 可选:禁止打印、复制等
- ap.setCanPrint(false);
- ap.setCanExtractContent(false);
- // 用户密码,所有者密码一样即可(也可设不同)
- StandardProtectionPolicy policy =
- // ownerPwd userPwd
- new StandardProtectionPolicy("a0min13s", "a0min13s", ap);
- // 128 位 AES
- policy.setEncryptionKeyLength(128);
- policy.setPermissions(ap);
- // 执行加密
- doc.protect(policy);
- // 写给浏览器
- doc.save(os);
- // 确保全部送出
- os.flush();
- } catch (IOException e) {
- log.error("PDF加密输出失败", e);
- throw new RuntimeException("PDF加密输出失败");
- }
- }
复制代码 4.2.6.2 查看效果
5、完整代码
https://gitee.com/huan1993/spring-cloud-parent/tree/master/pdf/openhtmltopdf
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |