0


Spring Boot集成OpenPDF和Freemarker实现PDF导出功能并附水印

在这里插入图片描述

😄 19年之后由于某些原因断更了三年,23年重新扬帆起航,推出更多优质博文,希望大家多多支持~
🌷 古之立大事者,不惟有超世之才,亦必有坚忍不拔之志
🎐 个人CSND主页——Micro麦可乐的博客
🐥《Docker实操教程》专栏以最新的Centos版本为基础进行Docker实操教程,入门到实战
🌺《RabbitMQ》专栏主要介绍使用JAVA开发RabbitMQ的系列教程,从基础知识到项目实战
🌸《设计模式》专栏以实际的生活场景为案例进行讲解,让大家对设计模式有一个更清晰的理解
💕《Jenkins实战》专栏主要介绍Jenkins+Docker的实战教程,让你快速掌握项目CI/CD,是2024年最新的实战教程
🌞《Spring Boot》专栏主要介绍我们日常工作项目中经常应用到的功能以及技巧,代码样例完整
如果文章能够给大家带来一定的帮助!欢迎关注、评论互动~

Spring Boot 集成 OpenPDF 和 Freemarker 实现 PDF 导出功能

前言

本文对应代码下载地址:https://download.csdn.net/download/lhmyy521125/89590079 无需积分!无需积分!

在我们日常开发中,生成

PDF

文件是一项常见的需求。无论是生成单据、报表、发票还是其他文档,

PDF

格式因其便捷的打印和跨平台支持而被广泛使用。本文将介绍如何在

Spring Boot

项目中使用

flying-saucer-pdf

Freemarker

来实现 HTML 模板到 PDF 的导出功能

**

flying-saucer-pdf + html

输出的单据效果:**

在这里插入图片描述
**

OpenPDF

后端编码形式输出的单据效果:**

在这里插入图片描述


概述

Flying Saucere介绍
项目地址:https://github.com/flyingsaucerproject/flyingsaucer

Flying Saucer

是一个纯Java库,用于使用

CSS 2.1 / CSS 3

呈现任意格式良好的XML(或XHTML),用于布局和格式化,输出到Swing面板,PDF和图像

使用文档:https://flyingsaucerproject.github.io/flyingsaucer/r8/guide/users-guide-R8.html

OpenPDF介绍
项目地址:https://github.com/LibrePDF/OpenPDF

OpenPDF

是一个用于创建和编辑PDF文件的Java库,具有LGPL和MPL开源许可证。OpenPDF是iText的LGPL/MPL开源继承者,基于iText 4 svn标签的一些分支

不同版本的OpenPDF,它们需要不同版本的Java

  • 2.0.x分支需要Java 17或更高版本。
  • 1.4.x分支需要Java 11或更高版本。
  • 1.3.x分支需要Java 8或更高版本。

为什么要把这两个放在一起说?

如果大家有看了

Flying Saucere

GitHub

上的介绍,你会发现

flying-saucer-pdf 

实际上是依赖于

OpenPDF

在这里插入图片描述
也就是说无论我们是要基于HTML模版来生成,还是采用后端编码的形式生成,我们都只需要引入

flying-saucer-pdf

依赖即可,比如博主文章开始的效果截图


实战开始

❶ 项目初始化

首先,创建一个新的 Spring Boot 项目,在在

pom.xml

文件中添加相关依赖

<dependencies><!-- Spring Boot Starter Web --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- Spring Boot Starter Freemarker --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-freemarker</artifactId></dependency><!-- 实际上 flying-saucer-pdf 使用OpenPDF实现 --><dependency><groupId>org.xhtmlrenderer</groupId><artifactId>flying-saucer-pdf</artifactId><version>9.9.0</version></dependency></dependencies>

❷ 配置 Freemarker

application.yml

文件中添加

Freemarker

的基本配置

# freemarker配置 实际上也可以直接默认Springboot装配配置# 更多是只需要修改模版后缀 和 模版路径spring:freemarker:suffix: .ftl
        charset: utf-8template-loader-path: classpath:/templates/
        expose-request-attributes:trueexpose-session-attributes:trueexpose-spring-macro-helpers:true

❸ 创建 HTML 模板

src/main/resources/templates

目录下创建一个

Freemarker

模板文件

template.ftl
<!DOCTYPEhtml><html><head><metacharset="utf-8"/><title>测试导出单据模版</title><linkhref="https://demo.ruoyi.vip/css/bootstrap.min.css?v=3.3.7"rel="stylesheet"type="text/css"/><linkhref="https://demo.ruoyi.vip/css/style.min.css?v=20210831"rel="stylesheet"/><linkhref="https://demo.ruoyi.vip/ruoyi/css/ry-ui.css?v=4.7.9"rel="stylesheet"/><style>@page{size: 210mm 297mm;/*设置纸张大小:A4(210mm 297mm)、A3(297mm 420mm) 横向则反过来*/margin: 0.5in;@bottom-center{content:"版权所有";font-family: SimSun;font-size: 12px;color:red;};@top-center{content:element(header)};@bottom-right{content:"第"counter(page)"页  共 "counter(pages)"页";font-family: SimSun;font-size: 12px;color:#000;};}body{font-family: SimSun;}img{width: 50px;}</style></head><bodyclass="gray-bg"><div><divclass="row"><divclass="col-sm-12"><divclass="ibox-content"><divclass="row"><divclass="col-sm-6 text-right"><h4>单据编号:</h4><h4class="text-navy">H+-000567F7-00</h4><address><strong>${companyName}</strong><br/>
              ${address}<br/><abbrtitle="Phone">总机:</abbr> ${tel}
            </address><p><span><strong>日期:</strong> 2014-11-11</span></p></div></div><divclass="table-responsive m-t"><tableclass="invoice-table"style="width: 100%;line-height: 60px"><thead><tr><th>图片</th><th>清单</th><th>数量</th><th>单价</th><th>总价</th></tr></thead><tbody><#ifproducts??&&(products?size> 0)>
              <#listproductsasp><tr><td><imgsrc="${p.productImg}"/></td><td><strong>${p.productName}</strong></td><td>${p.quantity}</td><td>&yen;${p.price}</td><td>&yen;${p.total}</td></tr></#list></#if></tbody></table></div><!-- /table-responsive --><tableclass="invoice-total"style="width: 100%;line-height: 30px"><tbody><tr><td><strong>总价:</strong></td><td>&yen;${total}</td></tr><tr><td><strong>税:</strong></td><td>&yen;${tax}</td></tr><tr><td><strong>总计</strong></td><td>&yen;${aggregate}</td></tr></tbody></table><divclass="well m-t"><strong>注意:</strong> 请保存好单据</div></div></div></div></div></body></html>

❹ 基于HTML模版 PDF 生成逻辑

创建一个

PdfService

类,用于生成 PDF 文件

packagecom.toher.project.openpdf;importcom.lowagie.text.*;importcom.lowagie.text.Font;importcom.lowagie.text.pdf.*;importfreemarker.cache.ClassTemplateLoader;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Service;importorg.springframework.ui.freemarker.FreeMarkerTemplateUtils;importorg.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;importorg.xhtmlrenderer.pdf.ITextFontResolver;importorg.xhtmlrenderer.pdf.ITextRenderer;importjava.awt.*;importjava.io.ByteArrayOutputStream;importjava.util.Map;@ServicepublicclassPdfService{@AutowiredprivateFreeMarkerConfigurer freeMarkerConfigurer;publicbyte[]generatePdf(Map<String,Object> data)throwsException{// 生成HTMLString html =FreeMarkerTemplateUtils.processTemplateIntoString(
                freeMarkerConfigurer.getConfiguration().getTemplate("template.ftl"), data);ByteArrayOutputStream out =newByteArrayOutputStream();ITextRenderer renderer =newITextRenderer();//加载/resource/static/font的字体ClassTemplateLoader classTemplateLoader =newClassTemplateLoader(PdfService.class,"/static/font");ITextFontResolver fontResolver =(ITextFontResolver)renderer.getSharedContext().getFontResolver();String fontPath = classTemplateLoader.getBasePackagePath()+"simsun.ttc";
        fontResolver.addFont(fontPath,BaseFont.IDENTITY_H,BaseFont.NOT_EMBEDDED);

        renderer.setDocumentFromString(html);
        renderer.layout();
        renderer.createPDF(out,false);PdfWriter writer = renderer.getWriter();//设置水印BaseFont bfChinese =BaseFont.createFont("STSong-Light","UniGB-UCS2-H",BaseFont.NOT_EMBEDDED);Font docFont =newFont(bfChinese,10,Font.UNDEFINED,Color.BLACK);
        writer.setPageEvent(newPdfPageEventHelper(){@OverridepublicvoidonEndPage(PdfWriter writer,Document document){PdfContentByte waterMar = writer.getDirectContentUnder();String text ="Micro麦可乐";addTextFullWaterMark(waterMar, text, bfChinese);}});
        renderer.finishPDF();return out.toByteArray();}publicstaticvoidaddTextFullWaterMark(PdfContentByte waterMar,String text,BaseFont bfChinese){
        waterMar.beginText();PdfGState gs =newPdfGState();// 设置填充字体不透明度为0.2f
        gs.setFillOpacity(0.1f);
        waterMar.setFontAndSize(bfChinese,40);// 设置透明度
        waterMar.setGState(gs);// 设置水印对齐方式 水印内容 X坐标 Y坐标 旋转角度for(int x =0; x <=700; x +=200){for(int y =0; y <=800; y +=200){
                waterMar.showTextAligned(Element.ALIGN_RIGHT, text, x, y,35);}}// 设置水印颜色
        waterMar.setColorFill(Color.GRAY);//结束设置
        waterMar.endText();
        waterMar.stroke();}}

❺ 基于后端编码形式生成

有些项目不一定是采用html模版形式生成

PDF

,这里博主就简单演示一下,使用

OpenPDF

后端编码形式生成

PDF
packagecom.toher.project.openpdf;importcom.lowagie.text.Font;importcom.lowagie.text.*;importcom.lowagie.text.Image;importcom.lowagie.text.alignment.HorizontalAlignment;importcom.lowagie.text.alignment.VerticalAlignment;importcom.lowagie.text.html.simpleparser.HTMLWorker;importcom.lowagie.text.pdf.*;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Service;importorg.springframework.ui.freemarker.FreeMarkerTemplateUtils;importorg.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;importorg.xhtmlrenderer.pdf.ITextRenderer;importjava.awt.*;importjava.io.ByteArrayOutputStream;importjava.io.IOException;importjava.io.InputStream;importjava.io.StringReader;importjava.util.Map;@ServicepublicclassOpenPdfService{@AutowiredprivateFreeMarkerConfigurer freeMarkerConfigurer;publicbyte[]generatePdf(Map<String,Object> data)throwsException{ByteArrayOutputStream out =newByteArrayOutputStream();// 创建PDF文档Document document =newDocument();PdfWriter writer =PdfWriter.getInstance(document, out);//如果需要定义字体,将自己的字体放在 resources/fonts目录下//BaseFont font = BaseFont.createFont("fonts/Viaoda_Libre/ViaodaLibre-Regular.ttf", BaseFont.IDENTITY_H, false);BaseFont bfChinese =BaseFont.createFont("STSong-Light","UniGB-UCS2-H",BaseFont.NOT_EMBEDDED);Font docFont =newFont(bfChinese,10,Font.UNDEFINED,Color.BLACK);//设置水印
        writer.setPageEvent(newPdfPageEventHelper(){@OverridepublicvoidonEndPage(PdfWriter writer,Document document){PdfContentByte waterMar = writer.getDirectContentUnder();String text ="Micro麦可乐";addTextFullWaterMark(waterMar, text, bfChinese);}});// 设置边距
        document.setMargins(20,20,20,20);// 打开文档
        document.open();/**
         * 01 表格演示
         */String[] tableTitle =newString[]{"清单","数量","单价","总价"};Table table =newTable(tableTitle.length);
        table.setWidths(newfloat[]{70,10,10,10});// 设置表格前的间距
        table.setSpacing(0);// 设置表格在页面中所占的宽度百分比
        table.setWidth(100);
        table.setBorder(0);//模拟5行表格数据for(int row =0; row <5; row++){for(int i =0; i < tableTitle.length; i++){Chunk chunk;if(row ==0){
                    chunk =newChunk(tableTitle[i], docFont);}else{
                    chunk =newChunk(row +"行 模拟数据"+ i, docFont);}// 建立单元格Cell cell =newCell(chunk);// 设置水平对齐
                cell.setHorizontalAlignment(HorizontalAlignment.CENTER);// 设置垂直对齐
                cell.setVerticalAlignment(VerticalAlignment.CENTER);
                table.addCell(cell);}}
        document.add(table);/**
         * 02 写入图片
         */byte[] byteArray =newbyte[0];InputStream inputStream =this.getClass().getResourceAsStream("/static/img/test.png");if(inputStream !=null){
            byteArray =newbyte[inputStream.available()];
            inputStream.read(byteArray);}Image image =Image.getInstance(byteArray);// 图片进行缩放
        image.scaleAbsolute(200,200);
        document.add(image);/**
         * 03 写入html内容
         */HTMLWorker htmlWorker =newHTMLWorker(document);String html ="<p style='color: crimson'>Hello, micro</p>";
        htmlWorker.parse(newStringReader(html);// 关闭文档
        document.close();return out.toByteArray();}publicstaticvoidaddTextFullWaterMark(PdfContentByte waterMar,String text,BaseFont bfChinese){
        waterMar.beginText();PdfGState gs =newPdfGState();// 设置填充字体不透明度为0.2f
        gs.setFillOpacity(0.2f);
        waterMar.setFontAndSize(bfChinese,40);// 设置透明度
        waterMar.setGState(gs);// 设置水印对齐方式 水印内容 X坐标 Y坐标 旋转角度for(int x =0; x <=700; x +=200){for(int y =0; y <=800; y +=200){
                waterMar.showTextAligned(Element.ALIGN_RIGHT, text, x, y,35);}}// 设置水印颜色
        waterMar.setColorFill(Color.GRAY);//结束设置
        waterMar.endText();
        waterMar.stroke();}}

❻ 创建 Controller

创建一个

PdfController

类,用于处理生成

PDF

的请求

packagecom.toher.project.openpdf;importcom.lowagie.text.DocumentException;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.http.HttpHeaders;importorg.springframework.http.MediaType;importorg.springframework.http.ResponseEntity;importorg.springframework.web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.RequestParam;importorg.springframework.web.bind.annotation.RestController;importjava.io.IOException;importjava.math.BigDecimal;importjava.util.ArrayList;importjava.util.HashMap;importjava.util.List;importjava.util.Map;@RestControllerpublicclassPdfController{@AutowiredprivatePdfService pdfService;@AutowiredprivateOpenPdfService openPdfService;/**
     * 采用flying-saucer-pdf html转pdf
     * @return
     */@GetMapping("/generate-pdf")publicResponseEntity<byte[]>generatePdf(){// 模拟数据库查询结果Map<String,Object> data =newHashMap<>();
        data.put("img","https://demo.ruoyi.vip/img/profile.jpg");

        data.put("companyName","阿里巴巴集团");
        data.put("address","中国杭州市华星路99号东部软件园创业大厦6层(310099)");
        data.put("tel","(+86) 571-8502-2088");
        data.put("creatTime","2024-07-27");

        data.put("total",1026.00);
        data.put("tax",235.98);
        data.put("aggregate",1261.98);List<ProductVo> products =newArrayList<>();ProductVo productVo =newProductVo();
        productVo.setProductImg("https://demo.ruoyi.vip/img/profile.jpg");
        productVo.setProductName("尚都比拉2013冬装新款女装 韩版修身呢子大衣 秋冬气质羊毛呢外套");
        productVo.setQuantity(1);
        productVo.setPrice(newBigDecimal("26"));
        productVo.setTotal(productVo.getPrice().multiply(productVo.getPrice()));
        products.add(productVo);ProductVo productVo1 =newProductVo();
        productVo1.setProductImg("https://demo.ruoyi.vip/img/profile.jpg");
        productVo1.setProductName("11*11夏娜 新款斗篷毛呢外套 女秋冬呢子大衣 韩版大码宽松呢大衣");
        productVo1.setQuantity(2);
        productVo1.setPrice(newBigDecimal("80"));
        productVo1.setTotal(productVo.getPrice().multiply(productVo.getPrice()));
        products.add(productVo1);ProductVo productVo2 =newProductVo();
        productVo2.setProductImg("https://demo.ruoyi.vip/img/profile.jpg");
        productVo2.setProductName("2013秋装 新款女装韩版学生秋冬加厚加绒保暖开衫卫衣 百搭女外套");
        productVo2.setQuantity(3);
        productVo2.setPrice(newBigDecimal("280"));
        productVo2.setTotal(productVo.getPrice().multiply(productVo.getPrice()));
        products.add(productVo2);

        data.put("products", products);byte[] pdfBytes =null;try{
            pdfBytes = pdfService.generatePdf(data);}catch(Exception e){
            e.printStackTrace();}HttpHeaders headers =newHttpHeaders();
        headers.setContentType(MediaType.APPLICATION_PDF);
        headers.setContentDispositionFormData("attachment","example.pdf");returnResponseEntity.ok().headers(headers).body(pdfBytes);}/**
     * 采用openpdf 生成pdf
     * @return
     */@GetMapping("/generate-openpdf")publicResponseEntity<byte[]>generateOpenPdf(){byte[] pdfBytes =null;try{
            pdfBytes = openPdfService.generatePdf();}catch(Exception e){
            e.printStackTrace();}HttpHeaders headers =newHttpHeaders();
        headers.setContentType(MediaType.APPLICATION_PDF);
        headers.setContentDispositionFormData("attachment","example.pdf");returnResponseEntity.ok().headers(headers).body(pdfBytes);}}

❼ 测试和运行

启动 Spring Boot 应用程序,然后在浏览器中访问以下 URL:

#Html模版形式生成
http://localhost:8080/generate-pdf 

#后端编码形式生成
http://localhost:8080/generate-openpdf

浏览器将会下载生成的 PDF 文件

example.pdf

,其中包含动态生成的内容,并且附加了水印

一点点建议

博主的代码中仅仅是为了让大家能快速熟悉,一些细节问题还需要大家在实际项目中进行优化调整

  • 模板设计:在设计 Freemarker 模板时,可以使用 CSS 来控制 PDF 的样式,使生成的 PDF 更加美观。
  • 水印设置:通过 CSS 设置水印样式,可以根据需求调整水印的位置、透明度、大小等属性。
  • 错误处理:在实际项目中,需增加错误处理和日志记录,确保在生成 PDF 过程中出现问题时能够及时发现并处理。
  • 性能优化:对于大批量生成 PDF 的场景,可以考虑使用异步处理或批处理机制,提高系统的处理能力。

总结

本文介绍了如何在

Spring Boot

项目中使用

Flying Saucer

Freemarker

实现

PDF

导出功能,并附加水印,并也演示了直接在后端编码形式生成

PDF


通过

Freemarker

模板引擎生成

HTML

,再使用

Flying Saucer

HTML

转换为

PDF

,此方法灵活且易于扩展,可以根据业务需求生成复杂的

PDF

文档

如果本文对您有所帮助,希望 一键三连 给博主一点点鼓励,如果您有任何疑问或建议,请随时留言讨论!


在这里插入图片描述

标签: spring boot pdf 后端

本文转载自: https://blog.csdn.net/lhmyy521125/article/details/140743558
版权归原作者 Micro麦可乐 所有, 如有侵权,请联系我们删除。

“Spring Boot集成OpenPDF和Freemarker实现PDF导出功能并附水印”的评论:

还没有评论