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文件的需求



相关文章
|
2月前
|
Java
java小工具util系列5:java文件相关操作工具,包括读取服务器路径下文件,删除文件及子文件,删除文件夹等方法
java小工具util系列5:java文件相关操作工具,包括读取服务器路径下文件,删除文件及子文件,删除文件夹等方法
93 9
|
2月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
104 2
|
16天前
|
人工智能 自然语言处理 Java
FastExcel:开源的 JAVA 解析 Excel 工具,集成 AI 通过自然语言处理 Excel 文件,完全兼容 EasyExcel
FastExcel 是一款基于 Java 的高性能 Excel 处理工具,专注于优化大规模数据处理,提供简洁易用的 API 和流式操作能力,支持从 EasyExcel 无缝迁移。
81 9
FastExcel:开源的 JAVA 解析 Excel 工具,集成 AI 通过自然语言处理 Excel 文件,完全兼容 EasyExcel
|
28天前
|
人工智能 文字识别 数据挖掘
MarkItDown:微软开源的多格式转Markdown工具,支持将PDF、Word、图像和音频等文件转换为Markdown格式
MarkItDown 是微软开源的多功能文档转换工具,支持将 PDF、PPT、Word、Excel、图像、音频等多种格式的文件转换为 Markdown 格式,具备 OCR 文字识别、语音转文字和元数据提取等功能。
185 9
MarkItDown:微软开源的多格式转Markdown工具,支持将PDF、Word、图像和音频等文件转换为Markdown格式
|
1月前
|
Java
java实现从HDFS上下载文件及文件夹的功能,以流形式输出,便于用户自定义保存任何路径下
java实现从HDFS上下载文件及文件夹的功能,以流形式输出,便于用户自定义保存任何路径下
97 34
|
1月前
|
JavaScript
jquery图片和pdf文件预览插件
EZView.js是一款jquery图片和pdf文件预览插件。EZView.js可以为图片和pdf格式文件生成在线预览效果。支持的文件格式有pdf、jpg、 png、jpeg、gif。
49 16
|
2月前
|
消息中间件 存储 Java
RocketMQ文件刷盘机制深度解析与Java模拟实现
【11月更文挑战第22天】在现代分布式系统中,消息队列(Message Queue, MQ)作为一种重要的中间件,扮演着连接不同服务、实现异步通信和消息解耦的关键角色。Apache RocketMQ作为一款高性能的分布式消息中间件,广泛应用于实时数据流处理、日志流处理等场景。为了保证消息的可靠性,RocketMQ引入了一种称为“刷盘”的机制,将消息从内存写入到磁盘中,确保消息持久化。本文将从底层原理、业务场景、概念、功能点等方面深入解析RocketMQ的文件刷盘机制,并使用Java模拟实现类似的功能。
50 3
|
2月前
|
Java 测试技术 Maven
Maven clean 提示文件 java.io.IOException
在使用Maven进行项目打包时,遇到了`Failed to delete`错误,尝试手动删除目标文件也失败,提示`java.io.IOException`。经过分析,发现问题是由于`sys-info.log`文件被其他进程占用。解决方法是关闭IDEA和相关Java进程,清理隐藏的Java进程后重新尝试Maven clean操作。最终问题得以解决。总结:遇到此类问题时,可以通过任务管理器清理相关进程或重启电脑来解决。
|
Java 大数据 Apache

热门文章

最新文章