Java基于itextPDF实现pdf动态导出

news2024/12/23 0:12:00

Java基于itextPDF实现pdf动态导出

  • 1、制作PDF导出模板
  • 2 、集成itextpdf
  • 3 、编写实体
  • 4 、编写主要代码
  • 5、编写controller并测试
    • 补充:踩坑记录

现在的业务越来越复杂了,有些业务场景已经不能满足与EXCEL导出和WORD导出了,例如准考证打印,电子证书等等,这些都是动态数据导出的PDF。接下来我们就看一下怎么实现PDF的动态导出吧。

1、制作PDF导出模板

第一步,我们需要制作一个PDF模板,可以先使用WORD去制作,制作完成以后再转为PDF。
在这里插入图片描述在这里插入图片描述
当转为PDF以后,我们就需要去给PDF设置表单域了,表单域的名称和你要填充的数据名称需要一一对应。
在这里插入图片描述

这里推荐几个可以编辑表单域的软件:Adobe Acrobat 、 万兴PDF、PDFill、Nitro
我这里懒省事用的万兴PDF(免费版有水印),具体哪个更好用一点请大家自行判断。

2 、集成itextpdf

接下来第二步则是在项目中集成itextpdf,项目中使用的是SpringBoot 2.7 , 同时还集成了lombok.

<dependency>
   <groupId>com.itextpdf</groupId>
   <artifactId>itextpdf</artifactId>
   <version>5.5.13</version>
</dependency>

3 、编写实体

编写导出PDF需要用到的实体,这里注意,实体中的属性名需要和表单域名一一对应。
同时为了方便测试,在无参构造中初始化了一些默认数据。

package com.vinci.pdf.entity;

import lombok.Data;


/**
 * @package: com.vinci.pdf.entity
 * @className: Person
 * @author: Vinci
 * @description: 测试用实体
 * @date: 2023/11/13 9:56
 */
@Data
public class Person {


    /**
     * @description: 姓名
     **/
    private String name;

    /**
     * @description: 国籍
     **/
    private String nationality;

    /**
     * @description: 居住地
     **/
    private String address;

    /**
     * @description: 民族
     **/
    private String nation;

    /**
     * @description: 户籍地
     **/
    private String registeredResidence;

    /**
     * @description: 身高 / 体重
     **/
    private String heightAndWeight;

    /**
     * @description: 婚姻状况
     **/
    private String maritalStatus;

    /**
     * @description: 年龄
     **/
    private Integer age;

    /**
     * @description: 照片
     **/
    private String largeHeadPhoto;


    /**
     * @description: 这里为了方便测试,在无参构造直接初始化数据来模拟持久化数据。
     **/
    public Person() {
        this.name = "vinci";
        this.nationality = "中国";
        this.address = "江苏南京";
        this.nation = "汉族";
        this.registeredResidence = "河南漯河";
        this.heightAndWeight = "178cm / 65Kg";
        this.maritalStatus = "未婚";
        this.age = 24;
        this.largeHeadPhoto = Thread.currentThread().getContextClassLoader().getResource("static/header1.jpg").getFile();
    }

}

4 、编写主要代码

在Service实现类中编写主要功能,将数据填充到PDF中并实现导出。

package com.vinci.pdf.service.impl;

import com.itextpdf.text.Document;
import com.itextpdf.text.Image;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.*;
import com.vinci.pdf.entity.Person;
import com.vinci.pdf.service.api.PdfGenerateTestService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Objects;

/**
 * @package: com.vinci.pdf.service.impl
 * @className: PdfGenerateTestServiceImpl
 * @author: Vinci
 * @description: pdf生成测试接口实现
 * @date: 2023/11/13 10:15
 */
@Service
public class PdfGenerateTestServiceImpl implements PdfGenerateTestService {


    /**
     * @description: 日志服务
     **/
    private static final Logger log = LoggerFactory.getLogger(PdfGenerateTestServiceImpl.class);


    /**
     * @description: pdf生成
     * @author: Vinci
     * @date: 2023/11/13 10:25
     **/
    @Override
    public void pdfGenerate(HttpServletResponse response) throws UnsupportedEncodingException {
        // 模板地址
        URL resource = Thread.currentThread().getContextClassLoader()
                .getResource("templates/aipuu-y1mhx.pdf");
        if(resource == null){
            throw new RuntimeException("没有找到模板");
        }

        String path = resource.getPath();

        // PDF的文件名称 及响应头
        String fileName = "test.pdf";
        fileName = URLEncoder.encode(fileName, "UTF-8");
        response.setContentType("application/force-download");
        //如果想要下载文件的话,这里的inline可以替换为 attachment
        response.setHeader("Content-Disposition",
                "fileName=" + fileName);


        OutputStream ops = null;
        ByteArrayOutputStream bos = null;
        PdfStamper pdfStamper = null;
        PdfReader pdfReader = null;

        try {
            ops = response.getOutputStream();
            pdfReader = new PdfReader(path);
            bos = new ByteArrayOutputStream();

            // 根据模板生成新的PDF
            pdfStamper = new PdfStamper(pdfReader, bos);
            AcroFields form = pdfStamper.getAcroFields();

            // 设置字体
            BaseFont font = BaseFont.createFont(
                    "C:/WINDOWS/Fonts/SIMSUN.TTC,1",
                    BaseFont.IDENTITY_H,
                    BaseFont.EMBEDDED
            );
            form.addSubstitutionFont(font);

            // 获取数据(这里在无参构造中生成了一些数据,实际开发中可用持久化数据来代替)
            Person person = new Person();

            // 通过反射遍历来给PDF中的表单生成数据
            Field[] fields = person.getClass().getDeclaredFields();
            for (Field field : fields) {
                field.setAccessible(true);
                String key = field.getName();
                Object value = field.get(person);
                if(!Objects.equals(key,"largeHeadPhoto")){
                    //处理文本数据
                    form.setField(key, value.toString());
                }else{
                    // 通过表单域名获取所在页和坐标,左下角为起点
                    int pageNo = form.getFieldPositions(key).get(0).page;
                    Rectangle signRect = form.getFieldPositions(key).get(0).position;
                    float x = signRect.getLeft();
                    float y = signRect.getBottom();
                    // 读图片
                    Image image = Image.getInstance(value.toString());
                    // 获取操作的页面
                    PdfContentByte under = pdfStamper.getOverContent(pageNo);
                    // 根据域的大小缩放图片
                    image.scaleToFit(signRect.getWidth(), signRect.getHeight());
                    // 添加图片
                    image.setAbsolutePosition(x, y);
                    under.addImage(image);
                }
            }

            // 设置PDF为只读
            pdfStamper.setFormFlattening(true);

            // 关闭资源
            pdfStamper.close();

            Document doc = new Document();
            PdfCopy copy = new PdfCopy(doc, ops);
            doc.open();
            PdfImportedPage importPage = copy.getImportedPage(new PdfReader(bos.toByteArray()), 1);
            copy.addPage(importPage);
            doc.close();

        }catch (Exception e){
            log.error("发现异常",e);
        }finally {
            try {
                if (ops != null) {
                    ops.flush();
                    ops.close();
                }
                if (pdfReader != null) {
                    pdfReader.close();
                }
            }catch (Exception e){
                log.error("发现异常",e);
            }
        }
    }


}

5、编写controller并测试

编写Controller来方便我们通过浏览器的请求的方式去测试

package com.vinci.pdf.controller;

import com.vinci.pdf.service.api.PdfGenerateTestService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;


/**
 * @package: com.vinci.pdf.controller
 * @className: PdfGenerateTestController
 * @author: Vinci
 * @description:pdf生成测试controller
 * @date: 2023/11/13 10:16
 */
@RestController
@RequestMapping("/pdf")
public class PdfGenerateTestController {

    /**
     * @description: 日志打印
     **/
    private static final Logger log = LoggerFactory.getLogger(PdfGenerateTestController.class);

    /**
     * @description: 业务接口
     **/
    @Resource
    private PdfGenerateTestService pdfGenerateTestService;

    /**
     * @description: 测试pdf生成
     * @author: Vinci
     * @date: 2023/11/13 10:17
     **/
    @GetMapping(value = "/generate")
    public void pdfGenerate(HttpServletResponse response){
        try{
            pdfGenerateTestService.pdfGenerate(response);
        }catch (Exception e){
            log.error("发现异常",e);
        }
    }
}

这里我们打开浏览器访问 http://localhost:8080/pdf/generate 发现PDF已经在下载了
在这里插入图片描述
下载成功后我们打开,发现里面已经有数据了。
在这里插入图片描述

补充:踩坑记录

使用万兴PDF编辑图片类型的表单域时一定要注意,去掉背景色,否则导出后你会看不到图片
在这里插入图片描述

本文代码下载地址:https://gitee.com/vinci99/springboot-pdf-generate.git

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1207465.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MATLAB|科研绘图|山脊图

效果图 山脊图介绍 山脊图&#xff08;Ridge Plot&#xff09;&#xff0c;也被称为Joy Plot&#xff0c;是一种用于可视化数据分布的图表&#xff0c;特别是用于显示多个组的分布情况。在这种图表中&#xff0c;每个组的数据分布都通过平滑的密度曲线来表示&#xff0c;这些曲…

JTS: 20 InteriorPoint 内部中心点

文章目录 版本代码 版本 org.locationtech.jts:jts-core:1.19.0 链接: github 代码 package pers.stu.algorithm;import org.locationtech.jts.algorithm.InteriorPoint; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.GeometryFactory; i…

FusionDiff:第一个基于扩散模型实现的多聚焦图像融合的论文

文章目录 1. 论文介绍2. 研究动机3. 模型结构3.1 网络架构3.2 前向扩散过程3.3 逆向扩散过程3.4 训练和推理过程 4. 小样本学习4. 实验结果 1. 论文介绍 题目&#xff1a;FusionDiff: Multi-focus image fusion using denoising diffusion probabilistic models 作者&#xf…

微服务全链路监控技术的实践路径

&#x1f4e2;专注于分享软件测试干货内容&#xff0c;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;交流讨论&#xff1a;加入1000人软件测试技术学习交流群&#x1f4e2;资源分享&#xff1a;进了字节跳动之后&#xff0c;才…

【自动化测试】Jenkins持续集成-设置执行环境+构建触发器(超细整理)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、Jenkins流水线…

SSL证书为什么那么贵?有便宜的吗?

SSL证书在网络安全中扮演着关键的角色&#xff0c;确保数据传输的机密性和完整性。然而&#xff0c;SSL证书的价格差异却常常让人感到疑惑&#xff0c;为何有些证书如此昂贵&#xff0c;而另一些则相对便宜&#xff1f;这个问题涉及到多个因素&#xff0c;包括证书类型、品牌声…

【图的定义和术语,图的类型定义】

文章目录 图的定义和术语图的类型定义 图的定义和术语 图&#xff1a;G&#xff08;V,E&#xff09;V:顶点&#xff08;数据元素&#xff09;的有穷非空集合。 E&#xff1a;边的有穷集合。 无向图&#xff1a;每条边都是无方向的。 有向图&#xff1a;每条边都是有方向的。 …

新公众号没有留言功能怎么办?如何设置留言?

为什么公众号没有留言功能&#xff1f;根据要求&#xff0c;自2018年2月12日起&#xff0c;新申请的微信公众号默认无留言功能。有些人听过一个说法&#xff1a;公众号粉丝累计到一定程度或者原创文章数量累计到一定程度就可以开通留言功能。其实这个方法是2018年之前才可以&am…

数据结构第三课 -----线性表之双向链表

作者前言 &#x1f382; ✨✨✨✨✨✨&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f382; ​&#x1f382; 作者介绍&#xff1a; &#x1f382;&#x1f382; &#x1f382; &#x1f389;&#x1f389;&#x1f389…

全网火爆,Python接口自动化测试Mock服务详细总结(实战场景)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、Mock实现原理与…

GitHub加速配置

1. 找到要加速的域名 GitHub&#xff1a;github.com&#xff08;这只是加载主页面的&#xff09;GitHub下载&#xff1a;codeload.github.com&#xff08;不唯一&#xff0c;自己去下载链接看&#xff09; 2. 用域名到DNS解析服务器地址 ITDOG 3. 修改 Hosts 文件 依据解…

PicoDiagnostics (NVH设备软件)-PS软件设置文件类型介绍

作为远程技术指导人员&#xff0c;下面这个功能对你来说可能非常有帮助。 在PicoScope 7 软件的文件保存格式里&#xff0c;通常选择的是<PS 数据文件>类型&#xff0c;容易忽略其他实用的保存文件类型&#xff0c;下面我们介绍<PS设置文件>类型。 PS 数据文件&…

深入理解计算机系统 笔记

写在前面 记录一下《深入理解计算机系统》中的要点。 一、第一章 计算机系统漫游 1. 程序的编译周期 最初编写的源程序xxx.c是一个文本文件&#xff1b;编译系统把源程序编译到可执行目标程序需要经过如下4个阶段&#xff1b; 1.1 预处理阶段 由预处理器&#xff08;cpp&a…

【带头学C++】----- 六、结构体 ---- 6.1 结构体概述

6.1 结构体概述&#xff08;struct&#xff09; 结构体&#xff08;struct&#xff09;是C中用于自定义数据类型的一种机制&#xff0c;它允许将多个不同类型的变量&#xff08;成员变量&#xff09;组合在一起形成一个新的数据类型。结构体允许程序员创建一个包含不同数据类型…

HPV转阴如何合理饮食,对此HPV专家谭巍主任提出自己看法

HPV感染不是小事&#xff0c;如果体内长期携带病毒不转阴&#xff0c;则有可能引发一系列病变。但在HPV转阴方面饮食有着不可或缺的作用&#xff0c;合理饮食对于促进HPV转阴至关重要。而关于HPV人群合理饮食问题&#xff0c;劲松HPV防治诊疗中心主任谭巍将提出一些建议&#x…

数据同步工具调研选型:SeaTunnel 与 DataX 、Sqoop、Flume、Flink CDC 对比

产品概述 Apache SeaTunnel 是一个非常易用的超高性能分布式数据集成产品&#xff0c;支持海量数据的离线及实时同步。每天可稳定高效同步万亿级数据&#xff0c;已应用于数百家企业生产&#xff0c;也是首个由国人主导贡献到 Apache 基金会的数据集成顶级项目。 SeaTunnel 主…

【shardingjdbc】sharding-jdbc分库分表入门demo及原理分析

文章目录 场景配置&#xff1a;概念及原理:代码:思考: 本文中&#xff0c;demo案例涉及场景为sharding jdbc的分库情况。 通俗点说就是由原来的db0_table水平拆分为 db1 t_table &#xff0c;db2.t_table。 demo本身很简单&#xff0c;难点在于分片策略配置到底该怎么写&#x…

宝马所有发动机号码的位置以及型号说明

每个汽车制造商都会为自己生产的汽车底盘和发动机分配一个内部代码&#xff0c;以标识研发和生产项目&#xff0c;而宝马对这些底盘代码和发动机代码更是规划的井井有条&#xff0c;比如发动机有M、N、B、S、P或W等代码标识&#xff0c;而底盘和车身则有E、F、G或i等代码标识。…

2FRE16-43/160LBK4M比例流量阀放大器

比例阀控制放大器技术是电液比例系统中的一个重要组成部分&#xff0c;它主要负责对比例阀进行控制。这种技术可以实现对液压流量或压力的精确控制&#xff0c;从而使系统以更高的精度和更快的响应速度执行各种操作。 比例阀控制放大器主要由三个部分组成&#xff1a;比例阀、…

58 权限提升-网站权限后台漏洞第三方获取

目录 当前知识点在渗透流程中的点当前知识点在权限提升的重点当前知识点权限提升权限介绍利用成功后的思想需要总结的思路演示案例:某挂壁程序后台权限提升-后台功能某BC广告导航页权限提升-漏洞层面苏丹大西瓜GlassFish中间件-第三方 涉及资源 这里包括网站权限、其它权限、系…