Java基于itextPDF实现pdf动态导出

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.mfbz.cn/a/146235.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

PCA9698的IIC转接GPIO控制N路灯

PCA9698验证灯的办法和PCA9535验证6路数字继电器&#xff0c;编译成ko直接Insmod&#xff0c;然后查看/dev/节点有了吗&#xff1f;然后用iictool命令往对应iic地址上面写数据&#xff0c;看看灯亮灭或者听继电器开关声响&#xff0c;至于写多少&#xff0c;研究芯片手册上面参…

03 # 类型基础:动态类型与静态类型

通俗定义 静态类型语言&#xff1a;在编译阶段确定所有变量的类型 编译阶段确定属性偏移量用偏移量访问代替属性名访问偏移量信息共享 动态类型语言&#xff1a;在执行阶段确定所有变量的类型 在程序运行时&#xff0c;动态计算属性偏移量需要额外的空间存储属性名所有对象的…

Ansys Lumerical | 用于增强现实系统的表面浮雕光栅

在本示例中&#xff0c;我们使用 RCWA 求解器设计了一个斜面浮雕光栅 (SRG)&#xff0c;它将用于将光线耦合到单色增强现实 (AR) 系统的波导中。光栅的几何形状经过优化&#xff0c;可将正常入射光导入-1 光栅阶次。 然后我们将光栅特性导出为 Lumerical Sub-Wavelength Model …

Xshell+Xftp通过代理的方式访问局域网内网服务器

最近在部署项目时遇到只有1台服务器拥有公网ip&#xff0c;其它服务器只有局域网ip&#xff0c;当然其它服务器可以正常访问网络&#xff0c;例如如下模型。之前访问其它几台服务器&#xff0c;都是先通过登录公网IP服务器&#xff0c;然后在Xshell里面执行ssh远程连接&#xf…

Android Matrix的使用详解(通过矩阵获取到图片缩放比例和角度)

网上查了好久相关的资料&#xff0c;都没有明确的答案。最终通过多次测试结果&#xff0c;结合安卓定义的矩阵含义&#xff0c;推算出来矩阵的数学含义以及相关的计算公式 1.获取Matrix矩阵&#xff1a; Matrix matrix new Matrix(); float[] matrixValues new float[9]; …

ArkUI实战,深入浅出OpenHarmony应用开发

前言 | 《ArkUI实战》《ArkUI实战》深入浅出的介绍了OpenHarmony开发框架ArkUI组件的使用和应用开发流程&#xff0c;是OpenHarmony应用开发的必备电子书。https://www.arkui.club/ OpenHarmony开发资料归档__南先森-Laval社区OpenHarmony入门看这里 _南先森 Laval社区https:/…

uniapp+vite+vue3开发跨平台app,运行到安卓模拟器调试方法

因为没有使用hbuilder开发uniapp&#xff0c;而是使用了vscode和vite来开发的&#xff0c;所以怎么将这个程序运行到安卓模拟器调试开发呢&#xff1f;其实方法很简单&#xff0c;使用android studio创建一个模拟器或者其他mumu模拟器&#xff0c;然后将项目使用hbuilder打开&a…

IOS上架流程

准备 开发者账号完工的项目 上架步骤 一、创建App ID二、创建证书请求文件 &#xff08;CSR文件&#xff09;三、创建发布证书 &#xff08;CER&#xff09;四、创建Provisioning Profiles配置文件 &#xff08;PP文件&#xff09;五、在App Store创建应用六、打包上架 一、…

STM32中独立看门狗和窗口看门狗的使用方法

独立看门狗&#xff08;Independent Watchdog&#xff0c;IWDG&#xff09;和窗口看门狗&#xff08;Window Watchdog&#xff0c;WWDG&#xff09;是STM32微控制器中提供的两种看门狗定时器。看门狗定时器是一种硬件计时器&#xff0c;用于监视系统的运行状态&#xff0c;并在…

ORACLE数据库实验总集 实验一 Oracle数据库安装与配置

一、实验目的 &#xff08;1&#xff09;掌握 Oracle数据库服务器的安装与配置 &#xff08;2&#xff09;了解如何检查安装后的数据库服务器产品&#xff0c;验证安装是否成功。 &#xff08;3&#xff09;掌握 Oracle数据库服务器安装过程中出现的问题的解决方法。 二、实验…

Java继承和多态(2)

&#x1f435;本篇文章将对多态的相关知识进行讲解 一、向上转型 向上转型是实现多态的条件之一&#xff1b;向上转型是让子类对象转换为父类对象或者是让父类的引用指向子类对象&#xff0c;直观的表现形式就是将子类的对象赋值给父类对象的引用&#xff1b;下面讲解向上转型…

gdb详解【Linux知识贩卖机】

你背朝太阳&#xff0c;就只能看到自己的影子。 --纪伯伦语录 文章目录 简介准备常用命令查看代码&#xff08;list&#xff09;运行&#xff08;run&#xff09;打断点&#xff08;break&#xff09;逐语句&#xff08;step&#xff09;逐过程&#xff08;next&#xff09;完成…

电源管理芯片知识分享:电源芯片的特点及故障检测方法

电源管理芯片用于对电源的控制和管理&#xff0c;提高设备的性能&#xff0c;被广泛应用于智能家居、电子商务、能源管理、汽车等领域&#xff0c;是现代电子设备不可缺少的部分。因此&#xff0c;对于电源管理芯片的检测也是十分重要的&#xff0c;发现其故障并及时解决&#…

MySQL8.0学习笔记

1. CMD命令 1.1 数据库启动与停止 (1) 启动数据库&#xff1a;net start mysql80 (2) 停止数据库&#xff1a;net stop mysql80 1.2 数据库连接与退出 (1) 连接数据库&#xff1a;mysql [-hlocalhost -P3306] -uroot -p[123456] // 本地数据库可省略-h -P (2) 退出数据库…

2023年亚太杯数学建模思路 - 案例:ID3-决策树分类算法

文章目录 0 赛题思路1 算法介绍2 FP树表示法3 构建FP树4 实现代码 建模资料 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 算法介绍 FP-Tree算法全称是FrequentPattern Tree算法&#xff0c;就是频繁模…

Leetcode刷题详解——太平洋大西洋水流问题

1. 题目链接&#xff1a;417. 太平洋大西洋水流问题 2. 题目描述&#xff1a; 有一个 m n 的矩形岛屿&#xff0c;与 太平洋 和 大西洋 相邻。 “太平洋” 处于大陆的左边界和上边界&#xff0c;而 “大西洋” 处于大陆的右边界和下边界。 这个岛被分割成一个由若干方形单元格…

B : 赫夫曼编码长度

Description 每行一个大小写英文字母组成的字符串&#xff0c;长度不大于 1000&#xff0c;通过前缀编码后最短的编码长度。 Input 每组数据一行&#xff0c;大小写英文字母 Output 每组数据输出赫夫曼编码长度 Sample 思路&#xff1a; string res "";//用于…

市场火爆的AI实景自动直播是什么?一文带你了解清楚!

最近AI实景自动直播在各大短视频平台爆火出了新高度&#xff0c;在如今全民直播的时代&#xff0c;直播已经成为大多数商家商家必须要会的技能&#xff0c;包括全国头部品牌也在纷纷加码直播&#xff0c;甚至早早就开启了直播矩阵的玩法&#xff0c;中腰部商家也在考虑如何入手…

【3】Spring Boot 3 集成mybatis-plus+druid+mysql

目录 【3】Spring Boot 3 集成组件&#xff1a;Druid Mybatis Plus Mysql集成方案1. Hikari jdbc mysql 集成方案增加依赖添加配置Spring Testng 测试用例 2. Druid Mybatis Plus Mysql集成方案2.1 配置Druid添加依赖配置启动Spring Boot Web StarterSpring Testng测试用…

Linux开发工具03:使用GCC、make和CMake编译代码

写在前面 这里主要记录一下如何使用GCC、make和CMake编译代码&#xff1b; 一、GCC g是GCC下专门用于编译C项目的编译器&#xff1b; 假设目录结构如下&#xff1a; include&#xff1a;包含分离的.h和.cpp文件&#xff1b;src&#xff1a;包含主函数入口main.cpp&#xff1…