Java基于itextPDF实现pdf动态导出

Java基于itextPDF实现pdf动态导出

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

1、制作PDF导出模板

第一步,我们需要制作一个PDF模板,可以先使用WORD去制作,制作完成以后再转为PDF。

当转为PDF以后,我们就需要去给PDF设置表单域了,表单域的名称和你要填充的数据名称需要一一对应。

这里推荐几个可以编辑表单域的软件:Adobe Acrobat 、 万兴PDF、PDFill、Nitro

我这里懒省事用的万兴PDF(免费版有水印),具体哪个更好用一点请大家自行判断。

2 、集成itextpdf

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

xml 复制代码
<dependency>
   <groupId>com.itextpdf</groupId>
   <artifactId>itextpdf</artifactId>
   <version>5.5.13</version>
</dependency>

3 、编写实体

编写导出PDF需要用到的实体,这里注意,实体中的属性名需要和表单域名一一对应。

同时为了方便测试,在无参构造中初始化了一些默认数据。

java 复制代码
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中并实现导出。

java 复制代码
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来方便我们通过浏览器的请求的方式去测试

java 复制代码
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

相关推荐
Dola_Pan2 小时前
Linux文件IO(二)-文件操作使用详解
java·linux·服务器
wang_book2 小时前
Gitlab学习(007 gitlab项目操作)
java·运维·git·学习·spring·gitlab
蜗牛^^O^3 小时前
Docker和K8S
java·docker·kubernetes
从心归零3 小时前
sshj使用代理连接服务器
java·服务器·sshj
IT毕设梦工厂4 小时前
计算机毕业设计选题推荐-在线拍卖系统-Java/Python项目实战
java·spring boot·python·django·毕业设计·源码·课程设计
Ylucius5 小时前
动态语言? 静态语言? ------区别何在?java,js,c,c++,python分给是静态or动态语言?
java·c语言·javascript·c++·python·学习
七夜zippoe5 小时前
分布式系统实战经验
java·分布式
是梦终空5 小时前
JAVA毕业设计176—基于Java+Springboot+vue3的交通旅游订票管理系统(源代码+数据库)
java·spring boot·vue·毕业设计·课程设计·源代码·交通订票
落落落sss5 小时前
sharding-jdbc分库分表
android·java·开发语言·数据库·servlet·oracle
码爸5 小时前
flink doris批量sink
java·前端·flink