• 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.

    <dependency>
       <groupId>com.itextpdfgroupId>
       <artifactId>itextpdfartifactId>
       <version>5.5.13version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    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();
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79

    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);
                }
            }
        }
    
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146

    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);
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

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

    补充:踩坑记录

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

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

  • 相关阅读:
    Netty 学习记录
    分文件编译
    锅炉烟气在线监测设备 环保数采仪
    Powerdesigner支持的数据库系统
    JavaEE-多线程-Volatile关键字
    Go Exec 僵尸与孤儿进程
    [JavaScript 刷题] 搜索 - 最短单词路径, leetcode 127
    笔记:Python 字符串与正则表达式(练习题)
    Java练手任务总结【19】
    ubuntu 内核版本
  • 原文地址:https://blog.csdn.net/qq_45004609/article/details/134390616