Java中实现html转pdf

[复制链接]
发表于 2025-9-26 12:22:54 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
目次

  • 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 引入依赖
  1. <dependencies>
  2.     <dependency>
  3.         <groupId>org.springframework.boot</groupId>
  4.         <artifactId>spring-boot-starter-web</artifactId>
  5.         <version>2.6.0</version>
  6.     </dependency>
  7.    
  8.     <dependency>
  9.         <groupId>org.freemarker</groupId>
  10.         <artifactId>freemarker</artifactId>
  11.         <version>2.3.30</version>
  12.     </dependency>
  13.    
  14.     <dependency>
  15.         <groupId>com.openhtmltopdf</groupId>
  16.         <artifactId>openhtmltopdf-pdfbox</artifactId>
  17.         <version>1.0.10</version>
  18.     </dependency>
  19.     <dependency>
  20.         <groupId>org.projectlombok</groupId>
  21.         <artifactId>lombok</artifactId>
  22.         <version>1.18.36</version>
  23.     </dependency>
  24. </dependencies>
复制代码
4.1.2 编写Freemarker工具类

加载步伐中src/main/resources/templates/ftls目次下的模板文件,然后渲染成html内容。
  1. package com.huan.pdf.utils;
  2. import freemarker.cache.ClassTemplateLoader;
  3. import freemarker.template.Configuration;
  4. import freemarker.template.Template;
  5. import freemarker.template.TemplateExceptionHandler;
  6. import lombok.extern.slf4j.Slf4j;
  7. import java.io.StringWriter;
  8. import java.util.Map;
  9. /**
  10. * freemarker 工具类
  11. *
  12. * @author admin
  13. */
  14. @Slf4j
  15. public class FreemarkerUtils {
  16.     /**
  17.      * 模板文件夹路径
  18.      */
  19.     private static final String TEMPLATE_DIR = "/templates/ftls";
  20.     private static final Configuration CONFIGURATION;
  21.     static {
  22.         CONFIGURATION = new Configuration(Configuration.VERSION_2_3_30);
  23.         CONFIGURATION.setTemplateLoader(new ClassTemplateLoader(FreemarkerUtils.class, TEMPLATE_DIR));
  24.         CONFIGURATION.setDefaultEncoding("UTF-8");
  25.         CONFIGURATION.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
  26.         CONFIGURATION.setLogTemplateExceptions(false);
  27.         CONFIGURATION.setWrapUncheckedExceptions(true);
  28.     }
  29.     /**
  30.      * 根据模板名称和数据模型生成字符串
  31.      *
  32.      * @param templateName 模板名称
  33.      * @param dataModel    数据模型
  34.      * @return 生成的字符串
  35.      */
  36.     public static String processTemplate(String templateName, Map<String, Object> dataModel) {
  37.         try {
  38.             Template template = CONFIGURATION.getTemplate(templateName);
  39.             StringWriter writer = new StringWriter();
  40.             template.process(dataModel, writer);
  41.             return writer.toString();
  42.         } catch (Exception e) {
  43.             log.error("解析模板出现问题", e);
  44.         }
  45.         return "";
  46.     }
  47. }
复制代码
4.1.3 编写pdf工具类

编写pdf工具类,用于将html内容渲染成pdf文件,此处只是简朴实现,后期该类还必要修改
  1. package com.huan.pdf.utils;
  2. import com.openhtmltopdf.pdfboxout.PdfRendererBuilder;
  3. import lombok.extern.slf4j.Slf4j;
  4. import javax.servlet.http.HttpServletResponse;
  5. import java.io.IOException;
  6. import java.io.OutputStream;
  7. import java.util.UUID;
  8. /**
  9. * pdf工具类
  10. *
  11. * @author admin
  12. */
  13. @Slf4j
  14. public class PdfUtils {
  15.     /**
  16.      * 生成pdf文件
  17.      *
  18.      * @param pdfTemplate pdf模板
  19.      * @param response    http response
  20.      */
  21.     public static void generatePdf(String pdfTemplate, HttpServletResponse response) {
  22.         // 设置响应头
  23.         String fileName = UUID.randomUUID() + ".pdf";
  24.         response.setContentType("application/pdf");
  25.         response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
  26.         try (OutputStream os = response.getOutputStream()) {
  27.             PdfRendererBuilder builder = new PdfRendererBuilder();
  28.             builder.withHtmlContent(pdfTemplate, null);
  29.             builder.toStream(os);
  30.             builder.run();
  31.         } catch (IOException e) {
  32.             log.error("生成pdf文件失败", e);
  33.             throw new RuntimeException("生成pdf文件失败", e);
  34.         }
  35.     }
  36. }
复制代码
4.1.4 增长一个模板
  1. <!DOCTYPE html>
  2. <html lang="en">
  3.     <head>
  4.         <meta charset="UTF-8" />
  5.         <title>生成pdf</title>
  6.         
  7.     </head>
  8.     <body>
  9.         ${mainTitle}
  10.     </body>
  11. </html>
复制代码
该模板中存在变量mainTitle,这个变量的值通事背景来赋值
4.1.5 增长一个控制层
  1. package com.huan.pdf.controller;
  2. import com.huan.pdf.utils.FreemarkerUtils;
  3. import com.huan.pdf.utils.PdfUtils;
  4. import org.springframework.web.bind.annotation.GetMapping;
  5. import org.springframework.web.bind.annotation.RestController;
  6. import javax.servlet.http.HttpServletResponse;
  7. import java.time.LocalDateTime;
  8. import java.time.format.DateTimeFormatter;
  9. import java.util.HashMap;
  10. import java.util.Map;
  11. /**
  12. * pdf控制器
  13. *
  14. * @author admin
  15. */
  16. @RestController
  17. public class PdfController {
  18.     @GetMapping("pdf")
  19.     public void pdf(HttpServletResponse response) {
  20.         Map<String, Object> params = new HashMap<>(16);
  21.         params.put("mainTitle", "这是一个标题 - " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
  22.         // 渲染模板
  23.         String htmlContent = FreemarkerUtils.processTemplate("pdf.ftl", params);
  24.         // 生成pdf
  25.         PdfUtils.generatePdf(htmlContent, response);
  26.     }
  27. }
复制代码
注意:此处的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工具类中增长利用中笔墨体
  1. builder.useFont(() -> PdfUtils.class.getClassLoader().getResourceAsStream("fonts/simsun.ttf"), "SimSun");
复制代码

4.2.1.3 freemarker模板中利用中笔墨体
  1. [/code][align=center][/align]
  2. [size=3]4.2.1.4 运行[/size]
  3. [align=center][/align]
  4. 从上图中可以看到,如今已经可以展示中文了。
  5. [size=4]4.2.2 天生的pdf支持简朴的样式[/size]
  6. 此处实现将天生的pdf中的 这是一个标题-时间 这句话的字体修改成赤色。
  7. [size=3]4.2.2.1 freemarker模板中利用css样式[/size]
  8. [code].main-title { text-align: center; font-size:25px; color:#FF0000; }
复制代码

4.2.2.2 运行


通过上图可知,样式已经收效了。
4.2.3 表格的某一行不要出现跨页

4.2.3.1 freemarker模板中增长一个表格
  1. <table>
  2.     <tr><td>序号</td></tr>
  3.     <tr><td>1</td></tr>
  4.     <tr><td>2</td></tr>
  5.     <tr><td>3</td></tr>
  6.     <tr><td>4</td></tr>
  7.     <tr><td>5</td></tr>
  8. </table>
复制代码

4.2.3.2 检察结果


从上图可以看到,天生的pdf,内容跨了2页,那么怎样办理这个标题呢?通过css样式办理
4.3.2.3 css办理
  1. table { border-collapse: collapse; page-break-inside: auto;}
  2. 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样式指定页面规则
  1. @page{ size:A3 landscape; }
复制代码
4.2.5.2 检察结果


从上图中可知 恰恰是A3横向
4.2.6 pdf 加密

实现思绪:通过pdfbox天生加密的暗码,此处给默认暗码a0nin13s
4.2.6.1 修改pdf天生的工具类
  1. /**
  2. * 生成带密码的 PDF 文件(用户密码 a0min13s)
  3. *
  4. * @param pdfTemplate HTML 模板字符串
  5. * @param response    HTTP 响应
  6. */
  7. public static void generatePdf(String pdfTemplate, HttpServletResponse response) {
  8.     String fileName = UUID.randomUUID() + ".pdf";
  9.     response.setContentType("application/pdf");
  10.     response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
  11.     // 1. 先用 openhtmltopdf 生成未加密 PDF(内存)
  12.     ByteArrayOutputStream temp = new ByteArrayOutputStream();
  13.     try {
  14.         PdfRendererBuilder builder = new PdfRendererBuilder();
  15.         builder.useFont(() -> PdfUtils.class.getClassLoader().getResourceAsStream("fonts/simsun.ttf"), "SimSun");
  16.         builder.withHtmlContent(pdfTemplate, null);
  17.         builder.toStream(temp);
  18.         // 完成渲染
  19.         builder.run();
  20.     } catch (IOException e) {
  21.         log.error("生成PDF失败", e);
  22.         throw new RuntimeException("生成PDF失败");
  23.     }
  24.     // 用 PDFBox 加载并加密
  25.     try (PDDocument doc = PDDocument.load(temp.toByteArray());
  26.          OutputStream os = response.getOutputStream()) {
  27.         AccessPermission ap = new AccessPermission();
  28.         // 可选:禁止打印、复制等
  29.         ap.setCanPrint(false);
  30.         ap.setCanExtractContent(false);
  31.         // 用户密码,所有者密码一样即可(也可设不同)
  32.         StandardProtectionPolicy policy =
  33.                 // ownerPwd  userPwd
  34.                 new StandardProtectionPolicy("a0min13s", "a0min13s", ap);
  35.         // 128 位 AES
  36.         policy.setEncryptionKeyLength(128);
  37.         policy.setPermissions(ap);
  38.         // 执行加密
  39.         doc.protect(policy);
  40.         // 写给浏览器
  41.         doc.save(os);
  42.         // 确保全部送出
  43.         os.flush();
  44.     } catch (IOException e) {
  45.         log.error("PDF加密输出失败", e);
  46.         throw new RuntimeException("PDF加密输出失败");
  47.     }
  48. }
复制代码
4.2.6.2 检察结果


5、完备代码

https://gitee.com/huan1993/spring-cloud-parent/tree/master/pdf/openhtmltopdf
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表