Java后台生成pdf文件

简介: Java后台生成pdf文件

前段时间因为相关业务需求需要后台生成pdf文件,对于一直crud的程序员来说,这无疑是需要一定时间来做技术预研的。下面根据我的实践经验总结一下我是如何使用java生成pdf文件的。

根据spring mvc的设计模式,理论上来说,我们可以把pdf文件视作一个View视图,那么整个mvc模型如下图:

832f05d31a3a4d60b3ee309d8ba2f282_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png

如果按照上图所示,那么我们要编写一个pdf视图解析器,这无疑是一个有难度的事情。但是把思路转换一下,我们可以先把model转换成html,再通过html转换成pdf是不是会更容易一点?

454f46428ffa4f54a9a01b19ff190cdf_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png

1.如何把model转换成html?

这个问题spring mvc已经替我们解决了,thymeleaf的实现无非就是一个活生生的model转换成html的例子。

2.html如何转换成pdf?

基于IText 基于FlyingSaucer 基于WKHtmlToPdf 基于pd4ml
跨平台性 跨平台 跨平台 跨平台 跨平台
是否安装软件 需安装WKHtmlToPdf
是否收费 免费 免费 免费 收费
转换Html效率 速度快 未测 速度慢。相比URL来说,效率较慢。能忽略一些html语法或资源是否存在问题。 速度快。部分CSS样式不支持。
效果 存在样式失真问题。对html语法有一定要求 存在样式失真问题。对html语法有较高要求。 失真情况较小,大部分网页能按Chome浏览器显示的页面转换 部分CSS样式有问题。
转换URL效率 未测 未测 效率不是特别高 未测
效果 未测 未测 部分网页由于其限制,或将出现html网页不完整。 未测
优点 不需安装软件、转换速度快 不需安装软件、转换速度快 生成PDF质量高 不需要安装软件、转换速度快
缺点 对html标签严格,少一个结束标签就会报错;服务器需要安装字体 对html标签严格,少一个结束标签就会报错;服务器需要安装字体 需要安装软件、时间效率不高 对部分CSS样式不支持。
分页 图片 表格 链接 中文 特殊字符 整体样式 速度
IText 支持 支持 支持 支持 支持 支持 失真问题
FlyingSaucer 未知 未知 未知 未知 未知 未知 未知
WKHtmlToPdf 支持 支持 支持 支持 支持 支持 很好
pd4ml 支持 支持 支持 支持 支持 支持 失真问题

对比以上各类实现:

1.WKHtmlToPdf因为转换速度慢、需要安装软件的缺点被暂时排除在外;pd4ml因为是收费的,并且同样存在一些常见的样式失真问题,直接排除;

2.剩下的就是在IText和FlyingSaucer的实现方案中做选择,对比之下,选择IText作为我们的最终实现方案

【相关依赖】

<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itextpdf</artifactId>
    <version>5.5.13.2</version>
</dependency>
<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itext-asian</artifactId>
    <version>5.2.0</version>
</dependency>
<dependency>
    <groupId>com.itextpdf.tool</groupId>
    <artifactId>xmlworker</artifactId>
    <version>5.5.13.2</version>
</dependency>
<dependency>
    <groupId>org.xhtmlrenderer</groupId>
    <artifactId>flying-saucer-pdf-itext5</artifactId>
    <version>9.1.22</version>
</dependency>
复制代码

【代码实现】

import com.itextpdf.text.pdf.BaseFont;
import com.zx.silverfox.common.exception.GlobalException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.xhtmlrenderer.pdf.ITextFontResolver;
import org.xhtmlrenderer.pdf.ITextRenderer;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
@Slf4j
public final class HtmlUtil {
    private HtmlUtil() {
    }
    // 字体路径,放在资源目录下
    private static final String FONT_PATH = "classpath:simsun.ttc";
    public static void file2Pdf(File htmlFile, String pdfFile) throws GlobalException {
        try (OutputStream os = new FileOutputStream(pdfFile)) {
            String url = htmlFile.toURI().toURL().toString();
            ITextRenderer renderer = new ITextRenderer();
            renderer.setDocument(url);
            // 解决中文支持
            ITextFontResolver fontResolver = renderer.getFontResolver();
            // 获取字体绝对路径,ApplicationContextUtil是我自己写的类
            String fontPath = ApplicationContextUtil.classpath(FONT_PATH);
            fontResolver.addFont(fontPath, BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
            renderer.layout();
            renderer.createPDF(os);
        } catch (Exception e) {
            // 抛出自定义异常
            throw GlobalException.newInstance(e);
        }
    }
    public static void html2Pdf(String html, String pdfFile) throws GlobalException {
        String pdfDir = StringUtils.substringBeforeLast(pdfFile, "/");
        File file = new File(pdfDir);
        if (!file.exists()) {
            file.mkdirs();
        }
        try (OutputStream os = new FileOutputStream(pdfFile)) {
            ITextRenderer renderer = new ITextRenderer();
            renderer.setDocumentFromString(html);
            // 解决中文支持
            ITextFontResolver fontResolver = renderer.getFontResolver();
            // 获取字体绝对路径,ApplicationContextUtil是我自己写的类
            String fontPath = ApplicationContextUtil.classpath(FONT_PATH);
            fontResolver.addFont(fontPath, BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
            renderer.layout();
            renderer.createPDF(os);
        } catch (Exception e) {
            // 抛出自定义异常
            throw GlobalException.newInstance(e);
        }
    }
}
复制代码

【字体文件】

simsun.tcc   密码:rzw4

以上实现就完成了html转换成pdf的功能,后续就是model转html:

因为我使用的是springboot,所以直接使用以下依赖。小伙伴可以根据自身项目具体情况使用对应的依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
复制代码

【代码实现】

import com.google.common.collect.Maps;
import com.zx.silverfox.common.exception.GlobalException;
import com.zx.silverfox.common.util.HtmlUtil;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import java.util.Map;
public abstract class AbstractTemplate {
    // 使用thymeleaf模版引擎
    private TemplateEngine engine;
    // 模版名称
    private String templateName;
    private AbstractTemplate() {}
    public AbstractTemplate(TemplateEngine engine,String templateName) {
        this.engine = engine;
        this.templateName=templateName;
    }
    /**
     * 模版名称
     *
     * @return
     */
    protected String templateName(){
        return this.templateName;
    }
    /**
     * 所有的参数数据
     *
     * @return
     */
    private Map<String, Object> variables(){
        // Maps是使用到了guava依赖
        Map<String, Object> variables = Maps.newHashMap();
        // 对应html模版中的template变量,取值的时候就按照“${template.字段名}”格式,可自行修改
        variables.put("template", this);
        return variables;
    };
    /**
     * 解析模版,生成html
     *
     * @return
     */
    public String process() {
        Context ctx = new Context();
        // 设置model
        ctx.setVariables(variables());
        // 根据model解析成html字符串
        return engine.process(templateName(), ctx);
    }
    public void parse2Pdf(String targetPdfFilePath) throws GlobalException {
        String html = process();
        // 通过html转换成pdf
        HtmlUtil.html2Pdf(html, targetPdfFilePath);
    }
}
复制代码

创建模版引擎

@Configuration
public class TemplateEngineConfig {
    // 注入TemplateEngine模版引擎
    @Bean
    public TemplateEngine templateEngine(){
        ClassLoaderTemplateResolver resolver = new ClassLoaderTemplateResolver();
        // 设置模版前缀,相当于需要在资源文件夹中创建一个html2pdfTemplate文件夹,所有的模版都放在这个文件夹中
        resolver.setPrefix("/html2pdfTemplate/");
        // 设置模版后缀
        resolver.setSuffix(".html");
        resolver.setCharacterEncoding("UTF-8");
        // 设置模版模型为HTML
        resolver.setTemplateMode("HTML");
        TemplateEngine engine = new TemplateEngine();
        engine.setTemplateResolver(resolver);
        return engine;
    }
}
复制代码

因为我们的依赖是基于springboot的,所以为了不让spring-boot-starter-thymeleaf自动配置,我们需要排除相关的配置类。不想这样做的小伙伴可使用thymeleaf其他依赖,原理上都一样。

@SpringBootApplication(exclude = ThymeleafAutoConfiguration.class)
复制代码

至此,所有的技术准备都做好了,如何使用我们编写好的代码实现model转换pdf文件呢?

【示例】

import lombok.Data;
import org.thymeleaf.TemplateEngine;
import java.util.List;
@Data
public class Model extends AbstractTemplate {
    // 构造函数
    public Model(TemplateEngine engine, String templateName) {
        super(engine, templateName);
    }
    // 名称
    private String name;
    // 保险记录
    private List<InsuranceInfo> insuranceInfos; 
}
@Data
public class InsuranceInfo{
    /** 出险日期 */
    private String expirationDate;
    /** 描述 */
    private String description;
}
复制代码

【报告模版.html】

<!DOCTYPE html
        PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>报告模版</title>
    <style>
      <!-- 编写css   -->
    </style>
</head>
 <!--  引入字体  -->
<body style="font-family: SimSun;">
<div class="main">
    报告模版
</div>
<div class="main2">
    <span class="heng" th:text="${template.name}">template.name</span>
    <table  class="tabletype">
      <thead>
        <tr class="recordhead">
          <th class="leaf" style="width: 80px;">出险日期</th>
          <th class="leaf" style="width: 80px;">描述</th>
        </tr>
      </thead>
      <tbody th:if="${template.insuranceInfos}">
        <tr  th:each="m,var : ${template.insuranceInfos}">
          <th class="leaf" th:text="${m.expirationDate}"></th>
          <th class="leaf" th:text="${m.description}"></th>
        </tr>
      </tbody>
  </table>
</div>
</body>
</html>
复制代码

【测试代码】

@Autowired private TemplateEngine engine;
    public void test() throws Exception {
        // 创建model,需要指定模版引擎和具体的模版,“报告模版”指的是资源目录下/html2pdfTemplate/报告模版.html文件。如果是springboot项目,那么就是在resources文件夹下面
        Model model = new Model(engine,"报告模版");
        model.setName("名称");
        List<InsuranceInfo> insuranceInfos = new ArrayList<>();
        InsuranceInfo record1 = new InsuranceInfo();
        record1.setExpirationDate("2021-01-19");
        record1.setDescription("刹车失灵");
        insuranceInfos.add(record1);
        InsuranceInfo record2 = new InsuranceInfo();
        record2.setExpirationDate("2021-03-06");
        record2.setDescription("挡风玻璃破裂");
        insuranceInfos.add(record2);
        model.setInsuranceInfos(insuranceInfos);
        //生成pdf,指定目标文件路径
        model.parse2Pdf("/home/dev/桌面/test.pdf");
    }
复制代码

根据以上理论和实践,我们已经达到了我们的目标,最终完成了数据转换成PDF文件的需求



相关文章
|
1天前
|
Java 开发者
Java一分钟之-Java IO流:文件读写基础
【5月更文挑战第10天】本文介绍了Java IO流在文件读写中的应用,包括`FileInputStream`和`FileOutputStream`用于字节流操作,`BufferedReader`和`PrintWriter`用于字符流。通过代码示例展示了如何读取和写入文件,强调了常见问题如未关闭流、文件路径、编码、权限和异常处理,并提供了追加写入与读取的示例。理解这些基础知识和注意事项能帮助开发者编写更可靠的程序。
5 0
|
2天前
|
Java
JDK环境下利用记事本对java文件进行运行编译
JDK环境下利用记事本对java文件进行运行编译
9 0
|
4天前
|
Arthas 安全 Java
java服务报错 FileNotFoundException:打开的文件过多
java服务报错 FileNotFoundException:打开的文件过多
13 0
|
7天前
|
Oracle Java 关系型数据库
windows 下 win11 JDK17安装与环境变量的配置(配置简单详细,包含IJ中java文件如何使用命令运行)
本文介绍了Windows 11中安装JDK 17的步骤,包括从官方网站下载JDK、配置环境变量以及验证安装是否成功。首先,下载JDK 17的安装文件,如果没有Oracle账户,可以直接解压缩文件到指定目录。接着,配置系统环境变量,新建`JAVA_HOME`变量指向JDK安装路径,并在`Path`变量中添加。然后,通过命令行(cmd)验证安装,分别输入`java -version`和`javac -version`检查版本信息。最后,作者分享了如何在任意位置运行Java代码,包括在IntelliJ IDEA(IJ)中创建的Java文件,只需去掉包声明,就可以通过命令行直接运行。
|
9天前
|
存储 监控 Java
如何在Java中实现等待文件修改后再读取数据的功能?
如何在Java中实现等待文件修改后再读取数据的功能?
17 0
|
9天前
|
存储 Java 数据格式
Java实战:轻松掌握文件重命名与路径提取技巧
Java实战:轻松掌握文件重命名与路径提取技巧
16 0
|
9天前
|
Java
如何解决使用若依前后端分离打包部署到服务器上后主包无法找到从包中的文件的问题?如何在 Java 代码中访问 jar 包中的资源文件?
如何解决使用若依前后端分离打包部署到服务器上后主包无法找到从包中的文件的问题?如何在 Java 代码中访问 jar 包中的资源文件?
46 0
|
29天前
|
Web App开发 Windows
【Windows】 chrome 如何下载网站在线预览PDF文件,保存到本地
【Windows】 chrome 如何下载网站在线预览PDF文件,保存到本地
146 0
|
1月前
|
前端开发
开发过程中遇到过的docx、pptx、xlsx、pdf文件预览多种方式
开发过程中遇到过的docx、pptx、xlsx、pdf文件预览多种方式
17 0
|
2月前
|
数据挖掘 数据安全/隐私保护 开发者
使用Spire.PDF for Python插件从PDF文件提取文字和图片信息
使用Spire.PDF for Python插件从PDF文件提取文字和图片信息
125 0