Redian新闻
>
服务端自定义生成PDF的几种方案

服务端自定义生成PDF的几种方案

公众号新闻

点击上方“芋道源码”,选择“设为星标

管她前浪,还是后浪?

能浪的浪,才是好浪!

每天 10:33 更新文章,每天掉亿点点头发...

源码精品专栏

 
来源:Java知音

这个文档太复杂了,还要导出pdf?

废话不多说直接进入正题,首先分析生成pdf场景及生成内容,考虑复用性和维护难度是我们当前开发工作的第一要务!

下面是调研的几个主要方案:

一、itext 表单填充

使用方式:

  • itext表单填充方案是以pdf作为基础模板,通过在pdf中嵌入表单元素组件的方式(需要使用pdf编辑工具),最后由程序进行数据填充并另存为pdf结果。

方案优缺点:

  • 优点:代码优雅,生成后格式变化影响极小。
  • 缺点:原始模板变化需要重新生成pdf,重新编辑表单元素;不支持列表填充数据。

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://gitee.com/zhijiantianya/ruoyi-vue-pro
  • 视频教程:https://doc.iocoder.cn/video/

二、freemarker + doc4J 基于Word 生成 PDF

使用方式:

  • 首先将调整好格式的原始 word 导出为 XML 格式,编辑 XML 模板中需要填充元素的位置,最后由程序处理先由freemarker模板工具替换元素内容,再使用doc4J进行pdf导出。

方案优缺点:

  • 优点:通用性强,基于模板引擎功能强大。
  • 缺点:XML 格式的word真的有够复杂,想要在此模板上调整样式真的难上加难;由于系统不支持的原因需要导入中文字体库;doc4J 部分 doc 元素不支持(例如直线),导出格式差异较大。

这可能是由于doc4J迭代问题无法保证新元素的支持,导出结果比较奔放。。。

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://gitee.com/zhijiantianya/yudao-cloud
  • 视频教程:https://doc.iocoder.cn/video/

三、freemarker + aspose-words 导出PDF

使用方式:

  • 类似于 freemarker+doc4J 方式,同样需要编辑XML,导出格式相较doc4J而言有极大提升。

方案优缺点:

  • 优点:通用性强,基于模板引擎功能强大,无需手工管理字体(macOS),代码简单,导出格式与模板基本无差异。
  • 缺点:需要编辑 XML 模板;该方案不是免费版(当然有大神)。

受限于调试前期需要的修修改改,模板能给人整吐了,所以才有了下一个方案。

四、html + freemarker + itextpdf(html2pdf)

使用方式:

  • 翻译 word 为 html 页面(当然就是手写啦,还原度很重要!),html中模板元素插入(文字填充、列表循环 freemarker 支持的全都能写),最后由程序处理先由freemarker模板工具替换元素内容,再使用html2pdf进行pdf导出。

方案优缺点:

  • 优点:可维护性相较与上面方案都有极大提升(调试可见性,动态替换生效);通用性强,基于模板引擎功能强大;导出格式可控性较强;
  • 缺点:需要中文字体库。

这个方案是综合以上多次踩坑的结果,结果是显而易见的。

浅浅来一点代码,省的大家到处找

 <dependencies>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>html2pdf</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>org.xhtmlrenderer</groupId>
<artifactId>flying-saucer-pdf-itext5</artifactId>
<version>9.0.3</version>
</dependency>
<dependency>
<groupId>binarta.oss</groupId>
<artifactId>groovy-template-enginex-freemarker</artifactId>
<version>0.1.3</version>
</dependency>
<dependencies>

个人以为自己的代码会自解释,就不贴太多注释了

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.map.MapUtil;
import com.google.common.collect.Lists;
import com.itextpdf.text.pdf.BaseFont;
import freemarker.cache.ByteArrayTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import lombok.SneakyThrows;
import org.xhtmlrenderer.pdf.ITextFontResolver;
import org.xhtmlrenderer.pdf.ITextRenderer;

import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.StringWriter;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;

public class HtmlToPdfUtil {

    private static final ByteArrayTemplateLoader TEMPLATE_LOADER = new ByteArrayTemplateLoader();

    // 导入需要字体库的位置哦;simsun 为 宋体
    public static final String FRONT_PATH = "/usr/share/fonts/simsun.ttc";

    /**
     * 看明白的话只用这个方法就够
     */

    public static ByteArrayOutputStream htmlToPdf(String templateName, Supplier<byte[]> loadTemplateSupplier, Map<String, Object> modeViewMap) {

        String html = xmlFormat(templateName, loadTemplateSupplier, modeViewMap);

        return htmlToPdf(html);
    }

    @SneakyThrows
    public static ByteArrayOutputStream htmlToPdf(String htmlStr) {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ITextRenderer renderer = new ITextRenderer();
        renderer.setDocumentFromString(htmlStr);
        ITextFontResolver resolver = renderer.getFontResolver();
        //添加字体,解决中文不显示的问题
        resolver.addFont(FRONT_PATH, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
        renderer.layout();
        renderer.createPDF(outputStream);
        return outputStream;
    }


    public static String xmlFormat(String templateName, Supplier<byte[]> loadTemplateSupplier, Map<String, Object> modeViewMap) {
        if (Objects.isNull(TEMPLATE_LOADER.findTemplateSource(templateName))) {
            synchronized (TEMPLATE_LOADER) {
                if (Objects.isNull(TEMPLATE_LOADER.findTemplateSource(templateName))) {
                    TEMPLATE_LOADER.putTemplate(templateName, loadTemplateSupplier.get());
                }
            }
        }
        return xmlFormat(templateName, modeViewMap);
    }

    @SneakyThrows
    public static String xmlFormat(String templateName, Map<String, Object> modeViewMap) {
        Configuration cfg = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
        // 指定FreeMarker模板文件的位置
        cfg.setTemplateLoader(TEMPLATE_LOADER);
        // 设置模板的编码格式
        cfg.setEncoding(Locale.CHINA, Charset.defaultCharset().name());
        // 获取模板文件 template
        Template template = cfg.getTemplate(templateName, Charset.defaultCharset().name());
        StringWriter stringWriter = new StringWriter();

        BufferedWriter writer = new BufferedWriter(stringWriter);
        template.process(modeViewMap, writer);
        return stringWriter.toString();
    }

}

解决这个问题的核心思路方案其实一直没变,变化的只是工具,一定要思路清晰!



欢迎加入我的知识星球,一起探讨架构,交流源码。加入方式,长按下方二维码噢

已在知识星球更新源码解析如下:

最近更新《芋道 SpringBoot 2.X 入门》系列,已经 101 余篇,覆盖了 MyBatis、Redis、MongoDB、ES、分库分表、读写分离、SpringMVC、Webflux、权限、WebSocket、Dubbo、RabbitMQ、RocketMQ、Kafka、性能测试等等内容。

提供近 3W 行代码的 SpringBoot 示例,以及超 4W 行代码的电商微服务项目。

获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。

文章有帮助的话,在看,转发吧。

谢谢支持哟 (*^__^*)

微信扫码关注该文公众号作者

戳这里提交新闻线索和高质量文章给我们。
相关阅读
AI自动生成prompt媲美人类,网友:工程师刚被聘用,又要淘汰了SpringBoot+Prometheus+Grafana 实现自定义监控持有美国B类签证入境,怎么转换身份?最佳的几种转换身份介绍!分享5篇医疗数据挖掘论文(附pdf下载)美国今年出版总收入下降,桑德斯将出版《对资本主义生气是应该的》|文化周报周作宇|未来关怀:时空互渗与意义生成图学习如何可解释?首篇《图反事实解释:定义、方法、评价》综述,46页pdf165篇文献全面概述图反事实解释进展分享5篇自动驾驶分割领域论文(附pdf下载)抢个小屋一一厨房装修〈一〉“街区改造”还是“街区毁掉”?(附PDF/CAD/SU下载)一定提升自我的几种好习惯一个人的徒步,900公里法国之路+世界尽头:D43~途经圣地亚哥5 年工作经验,Docker 的几种网络模式都说不清,你敢信?松耦合式的权限控制设计,自定义权限表达式58岁PDF发明人离世!他给男友订了一架直升机,从此相伴13年龙卷风健康快递 208PD-L1/PD-1研究2022丨诺奖加持,论文和基金均火箭速度增长;成果及转化正在其时!大数据分析及19篇论文帮你理清思路​“一晚1700,可无套” 男友曝光女学生包养价目表,私密照pdf多达92页!男人越来越爱你的几种表现,女人遇到了就很幸福!大规模GNN如何学习?北邮最新《分布式图神经网络训练》综述,35页pdf阐述分布式GNN训练算法和系统终身不婚的我告别世界的几种方式限时领丨稀缺资源《国家地理Look》7级教材全套[PDF+MP3+视频课]极简主义生活方式 :了解自己的真实欲望Excel如何快速自定义填充表格?遗产(4)在中东赚大钱的老板夫人如何生成「好」的图?面向图生成的深度生成模型系统综述|TPAMI2022UBS前财富管理总裁官宣: 商科生暑期必读的8本书(完整版+PDF)炸裂!PDF转Word彻底告别收费时代,这个OCR开源项目要逆天!机会欲望干货速领!高盛2023全球展望报告原件.PDF高血压的几种常见症状,哪种情况下必须服药治疗?持有美国旅游签证入境,最佳的几种转换身份介绍!建筑师不得不看的建筑细部神书!(附电子版PDF下载)《扫黑行动》:扫黑故事的几种叙述可能2022年品牌抖音电商增长新打法秘籍.pdf
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。