当前位置: 首页 > news >正文

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**/@Overridepublic 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可以替换为 attachmentresponse.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();// 根据模板生成新的PDFpdfStamper = 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: 业务接口**/@Resourceprivate 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

相关文章:

Java基于itextPDF实现pdf动态导出

Java基于itextPDF实现pdf动态导出 1、制作PDF导出模板2 、集成itextpdf3 、编写实体4 、编写主要代码5、编写controller并测试补充&#xff1a;踩坑记录 现在的业务越来越复杂了&#xff0c;有些业务场景已经不能满足与EXCEL导出和WORD导出了&#xff0c;例如准考证打印&#x…...

【Liunx】配置IP地址与MAC地址绑定

配置IP地址与MAC地址绑定 A.查询MAC地址B.绑定前的准备1.资源&#xff1a;(1) 服务器Server1:192.168.122.1(2) 服务器Server1:192.168.122.2 2. Server1按照dhcp服务 C.开始绑定操作1.修改dhcp配置文件2.生效 A.查询MAC地址 点击这里查看【如何查询服务器IP与MAC地址】 B.绑定…...

Mybatis-Plus最新教程

目录 原理&#xff1a;MybatisPlus通过扫描实体类&#xff0c;并基于反射获取实体类信息作为数据库信息。 ​编辑1.添加依赖 2.常用注解 3.常见配置&#xff1a; 4.条件构造器 5.QueryWrapper 6.UpdateWrapper 7.LambdaQueryWrapper:避免硬编码 8.自定义SQL 9.Iservic…...

【Shell脚本11】Shell 函数

Shell 函数 linux shell 可以用户定义函数&#xff0c;然后在shell脚本中可以随便调用。 shell中函数的定义格式如下&#xff1a; [ function ] funname [()]{action;[return int;]}说明&#xff1a; 1、可以带function fun() 定义&#xff0c;也可以直接fun() 定义,不带任何…...

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

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

刷题笔记(第七天)

1.找出对象 obj 不在原型链上的属性(注意这题测试例子的冒号后面也有一个空格~) 返回数组&#xff0c;格式为 key: value结果数组不要求顺序 输入&#xff1a; var C function() {this.foo ‘bar’; this.baz ‘bim’;}; C.prototype.bop ‘bip’; iterate(new C()); 输出…...

python3.7 + pygame1.9.3实现小游戏《外星人入侵》(五):计分

本小节首先在游戏画面中添加一个Play按钮&#xff0c;用于根据需要启动游戏&#xff0c;为此在game_stats.py中输入以下代码&#xff1a; class GameStats():def __init__(self,ai_settings):# 初始化统计信息self.ai_settings ai_settingsself.reset_stats()#让游戏一开始处…...

[量化投资-学习笔记014]Python+TDengine从零开始搭建量化分析平台-Python知识点汇总

以下内容总结了之前章节涉及到的 Python 知识点&#xff0c;看过之前的章节同学&#xff0c;就不用打开了。 1. Restful 访问 TDengine 数据库 知识点&#xff1a; 发送给 TDengine 的 HTTP Body 里面是 SQL 明文&#xff0c;请求方式为 POST。TDenging 返回的结果是 JSON 格…...

[论文分享] Never Mind the Malware, Here’s the Stegomalware

Never Mind the Malware, Here’s the Stegomalware [IEEE Security & Privacy 2022] Luca Caviglione | National Research Council of Italy Wojciech Mazurczyk | Warsaw University of Technology and FernUniversitt in Hagen 近年来&#xff0c;隐写技术已逐渐被观…...

代号:408 —— 1000道精心打磨的计算机考研题

文章目录 &#x1f4cb;前言&#x1f3af;计算机科学与技术专业介绍&#xff08;14年发布&#xff09;&#x1f9e9;培养目标&#x1f9e9;毕业生应具备的知识和能力&#x1f9e9;主要课程 &#x1f3af;代号&#xff1a;408&#x1f525;文末送书&#x1f9e9;有什么优势&…...

《QT从基础到进阶·十六》QT实现客户端和服务端的简单交互

QT版本&#xff1a;5.15.2 VS版本&#xff1a;2019 客户端程序主要包含三块&#xff1a;连接服务器&#xff0c;发送消息&#xff0c;关闭客户端 服务端程序主要包含三块&#xff1a;打开消息监听&#xff0c;接收消息并反馈&#xff0c;关闭服务端 1、先打开服务端监听功能 …...

行业追踪,2023-11-13

自动复盘 2023-11-13 凡所有相&#xff0c;皆是虚妄。若见诸相非相&#xff0c;即见如来。 k 线图是最好的老师&#xff0c;每天持续发布板块的rps排名&#xff0c;追踪板块&#xff0c;板块来开仓&#xff0c;板块去清仓&#xff0c;丢弃自以为是的想法&#xff0c;板块去留让…...

开放领域对话系统架构

开放领域对话系统是指针对非特定领域或行业的对话系统&#xff0c;它可以与用户进行自由的对话&#xff0c;不受特定领域或行业的知识和规则的限制。开放领域对话系统需要具备更广泛的语言理解和生成能力&#xff0c;以便与用户进行自然、流畅的对话。 与垂直领域对话系统相比…...

终端神器:tmux

安装tmux简单使用自己的理解&#xff08;小白专属&#xff09; 使用的初衷&#xff1a; 在Linux终端下&#xff0c;由于session&#xff08;会话&#xff09;和windows&#xff08;窗口&#xff09;是绑定一起的&#xff0c;你打开一个终端的黑窗口就是打开一个会话&#xff0c…...

Elasticsearch学习(一)

ElasticSearch学习&#xff08;一&#xff09; 1 什么是Elasticsearch 1.什么是搜索&#xff1f; 百度&#xff1a;我们比如说想找寻任何信息时候就会上百度上搜索一下 比如说&#xff1a;电影、图片、小说等等…&#xff08;提到搜索的第一印象&#xff09; 百度 &#x…...

CSS3的常见边框汇总

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>CSS3 边框</title><style>body, ul, li, dl, dt, dd, h1, h2, h3, h4, h5 {margin: 0;padding: 0;}body {background-color: #F7F7F7;}.wr…...

酷柚易汛ERP-购货订单操作指南

1、应用场景 先下购货订单&#xff0c;收货入库后生成购货单。 2、主要操作 2.1 新增购货订单 打开【购货】-【购货订单】新增购货订单。&#xff08;*为必填项&#xff0c;其他为选填&#xff09; ① 录入供应商&#xff1a;点击供应商字段框的 &#xff0c;在弹框中选择供…...

【数据仓库】数仓分层方法详解与层次调用规范

文章目录 一. 数仓分层的意义1. 清晰数据结构。2. 减少重复开发3. 方便数据血缘追踪4. 把复杂问题简单化5. 屏蔽原始数据的异常6. 数据仓库的可维护性 二. 如何进行数仓分层&#xff1f;1. ODS层2. DW层2.1. DW层分类2.2. DWD层2.3. DWS 3. ADS层 4、层次调用规范 一. 数仓分层…...

记一次线上问题引发的对 Mysql 锁机制分析

背景 最近双十一开门红期间组内出现了一次因 Mysql 死锁导致的线上问题&#xff0c;当时从监控可以看到数据库活跃连接数飙升&#xff0c;导致应用层数据库连接池被打满&#xff0c;后续所有请求都因获取不到连接而失败 整体业务代码精简逻辑如下&#xff1a; Transaction p…...

Android 工厂模式距离传感器逻辑优化

Android 工厂模式距离传感器逻辑优化 接到客户反馈提到距离传感器校准完毕之后&#xff0c;每次测试完成界面都会弹出“请点击校准按钮进行校准&#xff01;”Toast弹窗&#xff0c;需要对弹窗的显示逻辑进行优化&#xff0c;即只让其在首次进入距离传感器测试界面时弹出&#…...

三维GIS开发cesium智慧地铁教程(5)Cesium相机控制

一、环境搭建 <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"> 关键配置点&#xff1a; 路径验证&#xff1a;确保相对路径.…...

Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)

文章目录 1.什么是Redis&#xff1f;2.为什么要使用redis作为mysql的缓存&#xff1f;3.什么是缓存雪崩、缓存穿透、缓存击穿&#xff1f;3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...

可靠性+灵活性:电力载波技术在楼宇自控中的核心价值

可靠性灵活性&#xff1a;电力载波技术在楼宇自控中的核心价值 在智能楼宇的自动化控制中&#xff0c;电力载波技术&#xff08;PLC&#xff09;凭借其独特的优势&#xff0c;正成为构建高效、稳定、灵活系统的核心解决方案。它利用现有电力线路传输数据&#xff0c;无需额外布…...

聊聊 Pulsar:Producer 源码解析

一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台&#xff0c;以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中&#xff0c;Producer&#xff08;生产者&#xff09; 是连接客户端应用与消息队列的第一步。生产者…...

TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案

一、TRS收益互换的本质与业务逻辑 &#xff08;一&#xff09;概念解析 TRS&#xff08;Total Return Swap&#xff09;收益互换是一种金融衍生工具&#xff0c;指交易双方约定在未来一定期限内&#xff0c;基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...

nnUNet V2修改网络——暴力替换网络为UNet++

更换前,要用nnUNet V2跑通所用数据集,证明nnUNet V2、数据集、运行环境等没有问题 阅读nnU-Net V2 的 U-Net结构,初步了解要修改的网络,知己知彼,修改起来才能游刃有余。 U-Net存在两个局限,一是网络的最佳深度因应用场景而异,这取决于任务的难度和可用于训练的标注数…...

6️⃣Go 语言中的哈希、加密与序列化:通往区块链世界的钥匙

Go 语言中的哈希、加密与序列化:通往区块链世界的钥匙 一、前言:离区块链还有多远? 区块链听起来可能遥不可及,似乎是只有密码学专家和资深工程师才能涉足的领域。但事实上,构建一个区块链的核心并不复杂,尤其当你已经掌握了一门系统编程语言,比如 Go。 要真正理解区…...

flow_controllers

关键点&#xff1a; 流控制器类型&#xff1a; 同步&#xff08;Sync&#xff09;&#xff1a;发布操作会阻塞&#xff0c;直到数据被确认发送。异步&#xff08;Async&#xff09;&#xff1a;发布操作非阻塞&#xff0c;数据发送由后台线程处理。纯同步&#xff08;PureSync…...

【Java】Ajax 技术详解

文章目录 1. Filter 过滤器1.1 Filter 概述1.2 Filter 快速入门开发步骤:1.3 Filter 执行流程1.4 Filter 拦截路径配置1.5 过滤器链2. Listener 监听器2.1 Listener 概述2.2 ServletContextListener3. Ajax 技术3.1 Ajax 概述3.2 Ajax 快速入门服务端实现:客户端实现:4. Axi…...

【技巧】dify前端源代码修改第一弹-增加tab页

回到目录 【技巧】dify前端源代码修改第一弹-增加tab页 尝试修改dify的前端源代码&#xff0c;在知识库增加一个tab页"HELLO WORLD"&#xff0c;完成后的效果如下 [gif01] 1. 前端代码进入调试模式 参考 【部署】win10的wsl环境下启动dify的web前端服务 启动调试…...