Spring Boot 使用Itext绘制并导出PDF

news2025/5/17 2:55:54

最终效果

在这里插入图片描述

其实可以加分页,但是没有那么精细的需求,所以我最后就没有加,有兴趣的可以尝试下。

项目依赖

<!--    Spring Boot 版本有点老    -->
<spring-boot.version>2.3.12.RELEASE</spring-boot.version>

<!--    依赖    -->
<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itextpdf</artifactId>
    <version>5.5.13</version>
</dependency>

<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itext-asian</artifactId>
    <version>5.2.0</version>
</dependency>
        

实现代码

出奇的简单,就一个核心的方法

核心实现方法

package com.an.pdfhandle.service;

import com.an.pdfhandle.entity.Member;
import com.an.pdfhandle.entity.Team;
import com.itextpdf.text.*;
import com.itextpdf.text.pdf.*;

import java.io.OutputStream;
import java.net.URL;
import java.util.List;

public class PdfExportService {


    // 样式化导出为类似画布的横向排版:iText5 实现核心逻辑(支持多个团队)

    public void exportTeamCanvasStyle(List<Team> teams, OutputStream outputStream) throws Exception {
        Document document = new Document(PageSize.A4, 36, 36, 36, 36); // A4 纵向页面
        PdfWriter.getInstance(document, outputStream);
        document.open();

        BaseFont baseFont = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
        Font nameFont = new Font(baseFont, 12);
        Font labelFont = new Font(baseFont, 12, Font.BOLD);
        Font titleFont = new Font(baseFont, 16, Font.BOLD);

        for (Team team : teams) {
            // --- 信息页(第一页) ---
            document.newPage();
            document.add(new Paragraph("参 赛 队:" + team.getName(), titleFont));
            document.add(Chunk.NEWLINE);
            // 绘制基本代表队基本信息
            Paragraph p1 = new Paragraph();
            p1.setFont(nameFont);
            p1.add("领   队:" + team.getLeaderName() + "          联系电话:" + team.getLeaderPhone() + "\n");
            p1.add("主训教练:" + team.getCoachName() + "           队    医:" + team.getDoctorName() + "\n");
            p1.add("助理教练:" + team.getAssistantCoachName() + "           联系电话:" + team.getCoachPhone());
            document.add(p1);

            document.add(Chunk.NEWLINE);
            // 绘制Table
            PdfPTable infoTable = new PdfPTable(8);
            infoTable.setWidthPercentage(100);
            infoTable.setWidths(new float[]{1f, 2f, 2f, 1f, 2.5f, 2f, 4f, 2f});
            String[] headers = {"序号", "身份", "姓名", "性别", "身高/体重", "比赛号码", "身份证号", "服装号码"};
            for (String h : headers) {
                PdfPCell cell = new PdfPCell(new Phrase(h, labelFont));
                cell.setHorizontalAlignment(Element.ALIGN_CENTER);
                cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
                cell.setPaddingTop(7f); // 4cm ≈ 113pt,每边加大间距
                cell.setPaddingBottom(7f);
                infoTable.addCell(cell);
            }
            int index = 1;
            for (Member m : team.getMembers()) {
                PdfPCell[] cells = new PdfPCell[] {
                        new PdfPCell(new Phrase(String.valueOf(index++), nameFont)),
                        new PdfPCell(new Phrase(m.getRole(), nameFont)),
                        new PdfPCell(new Phrase(m.getName(), nameFont)),
                        new PdfPCell(new Phrase(m.getGender(), nameFont)),
                        new PdfPCell(new Phrase(m.getHeightWeight(), nameFont)),
                        new PdfPCell(new Phrase(m.getCode(), nameFont)),
                        new PdfPCell(new Phrase(m.getIdCard(), nameFont)),
                        new PdfPCell(new Phrase(m.getClothingSize(), nameFont))
                };
                for (PdfPCell cell : cells) {
                    cell.setHorizontalAlignment(Element.ALIGN_CENTER);
                    cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
                    cell.setPaddingTop(7f); // 增加内边距
                    cell.setPaddingBottom(7f);
                    infoTable.addCell(cell);
                }
            }
            document.add(infoTable);

            // --- 画布页(第二页) ---
            document.newPage();
            // 代表队名称
            Paragraph title = new Paragraph("队名:" + team.getName(), titleFont);
            title.setAlignment(Element.ALIGN_LEFT);
            document.add(title);
            document.add(Chunk.NEWLINE);
            // 底部头像(其实也是一个表格,但是没有边框)
            PdfPTable table = new PdfPTable(3); // 每行最多3个头像(适合纵向)
            table.setWidthPercentage(100);
            table.getDefaultCell().setBorder(Rectangle.NO_BORDER);

            for (Member member : team.getMembers()) {
                PdfPCell cell = new PdfPCell();
                cell.setBorder(Rectangle.NO_BORDER);
                // 图片
                if (member.getImageUrl() != null) {
                    try {
                        Image img = Image.getInstance(new URL(member.getImageUrl()));
                        img.scaleToFit(100, 120);
                        cell.addElement(img);
                    } catch (Exception e) {
                        // 可添加默认图
                    }
                }
                // 图片下的文字信息
                Paragraph info = new Paragraph();
                info.setLeading(14);
                info.add(new Chunk(member.getRole() + ":", labelFont));
                info.add(new Chunk(member.getName() + "\n", nameFont));
                info.add(new Chunk(member.getIdCard() + "\n", nameFont));
                info.add(new Chunk("号码:" + member.getCode(), nameFont));
                cell.addElement(info);

                table.addCell(cell);
            }

            int remainder = team.getMembers().size() % 3;
            if (remainder != 0) {
                for (int i = 0; i < 3 - remainder; i++) {
                    PdfPCell empty = new PdfPCell();
                    empty.setBorder(Rectangle.NO_BORDER);
                    table.addCell(empty);
                }
            }

            document.add(table);
        }

        document.close();
    }

}

调用Controller

package com.an.pdfhandle.demos.web;

import com.an.pdfhandle.entity.Member;
import com.an.pdfhandle.entity.Team;
import com.an.pdfhandle.service.PdfExportService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.List;

@RestController
public class PdfController {

    @GetMapping("/exportCanvasStyle")
    public void exportPdfCanvasStyle(HttpServletResponse response) throws Exception {
        // 模拟数据
        List<Team> teams = Arrays.asList(
                new Team("测试1代表队",
                        "大袋",
                        "15635748705",
                        "张三",
                        "李四",
                        "张三",
                        "15635748705"
                        , Arrays.asList(
                        new Member("张三", "运动员", "10","男","178/70KG","2XL", "110101199001010011", "http://101.37.161.72:8888/an/M00/00/00/rBoEmGRy2TuARDgsAADin4gdP7Q119.jpg"),
                        new Member("李四", "运动员", "11", "男","178/70KG","2XL", "110101199202020022", "http://101.37.161.72:8888/an/M00/00/00/rBoEmGRy2TuARDgsAADin4gdP7Q119.jpg")
                )),
                new Team("测试2代表队",
                        "大袋",
                        "15635748705",
                        "张三",
                        "李四",
                        "张三",
                        "15635748705", Arrays.asList(
                        new Member("王五", "运动员", "21", "男","178/70KG","2XL", "110101199303030033", "http://101.37.161.72:8888/an/M00/00/00/rBoEmGRy2TuARDgsAADin4gdP7Q119.jpg"),
                        new Member("赵六", "运动员", "22", "男","178/80KG","3XL", "110101199303030033", "http://101.37.161.72:8888/an/M00/00/00/rBoEmGRy2TuARDgsAADin4gdP7Q119.jpg")
                ))
        );

        response.setContentType("application/pdf");
        response.setHeader("Content-Disposition", "attachment; filename=teams.pdf");

        new PdfExportService().exportTeamCanvasStyle(teams, response.getOutputStream());
    }
}

到此结束,其实生成的比较粗糙,这里需要反思下,我的后端的排版布局能力还是比较差的,哈哈,有待提高。

使用前端直接用DOM元素往出导PDF的话会美观点,毕竟CSS用的更加顺手点, 使用VUE导出可以看我另一篇博客

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

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

相关文章

【测试】BUG

目录 1、描述BUG的要素&#xff1a; 2、BUG的级别 3、BUG的状态的流转 4、与开发产⽣争执怎么办&#xff08;⾼频考题&#xff09; 什么是BUG&#xff1f;&#xff1f;&#xff1f; 程序与规格说明之间的不匹配才是错误 1、描述BUG的要素&#xff1a; 问题出现的版本、问…

Mac 环境下 JDK 版本切换全指南

概要 在 macOS 上安装了多个 JDK 后&#xff0c;可以通过系统自带的 /usr/libexec/java_home 工具来查询并切换不同版本的 Java。只需在终端中执行 /usr/libexec/java_home -V 列出所有已安装的 JDK&#xff0c;然后将你想使用的版本路径赋值给环境变量 JAVA_HOME&#xff0c;…

Pillow 移除或更改了 FreeTypeFont.getsize() 方法

w, h self.font.getsize(label) # text width, height AttributeError: FreeTypeFont object has no attribute getsize 在Pillow 项目的变更日志里可以查到哪个版本移除了 getsize() 方法&#xff0c;Pillow仓库&#xff1a; Releases python-pillow/Pillow GitHub 因为…

视频编辑软件无限音频、视频、图文轨

威力导演APP的特色功能包括无限音频、视频、图文轨&#xff0c;以及上百种二/三维特技转场、音/视频滤镜和多种音视频混编输出。此外&#xff0c;它还支持实时高清HDV格式、模拟信号输出&#xff0c;并具有DV25、DVACM、DV、HDV输入和输出等功能。在视频编辑领域&#xff0c;威…

uniapp-商城-53-后台 商家信息(更新修改和深浅copy)

1、概述 文章主要讨论了在数据库管理中如何处理用户上传和修改商家信息的问题&#xff0c;特别是通过深浅拷贝技术来确保数据更新的准确性和安全性。 首先&#xff0c;解释了深拷贝和浅拷贝的区别&#xff1a;浅拷贝使得两个变量共享相同的内存地址&#xff0c;而深拷贝则创建新…

[Java实战]Spring Boot 整合 Thymeleaf (十)

[Java实战]Spring Boot 整合 Thymeleaf &#xff08;十&#xff09; 引言 在 Java Web 开发领域&#xff0c;Thymeleaf 以其自然模板、无缝 Spring 集成和强大的表达式引擎脱颖而出&#xff0c;成为 Spring Boot 官方推荐的模板引擎。本文将深度解析 Spring Boot 与 Thymelea…

监控易一体化运维:网络流量分析的智慧引擎

在数字化时代&#xff0c;企业运营与网络紧密相连&#xff0c;网络性能的优劣直接影响企业的发展步伐。网络流量管理在企业网络运维中占据非常关键的地位。监控易一体化运维管理软件&#xff0c;凭借其强大的网络流量分析功能&#xff0c;为企业网络的稳定高效运行提供了有力保…

IDEA+git将分支合并到主分支、IDEA合并分支

文章目录 一、合并分支二、可能遇到的问题2.1、代码冲突 开发过程中我们可能在开发分支(dev)中进行开发&#xff0c;等上线后将代码合并到主分支(master)中&#xff0c;本文讲解如何在IDEA中将dev分支的代码合并到master分支中。 一、合并分支 功能说明&#xff1a;将dev分支的…

uniapp+vue3中自动导入ref等依赖

前言&#xff1a; 在我们使用uni-appvue3创建项目&#xff0c;开发的过程中&#xff0c;老是需要导入我们的ref、onshow等&#xff0c;那么能不能自动导入&#xff0c;不用我们每个页面都写呢&#xff1f;是没问题的&#xff0c;这里让他的小帮手来帮你减轻负担&#xff1a;他就…

【.net core】.net core 6.0添加WCF服务引用

在 .NET Core 6.0 (.NET 6) 中&#xff0c;调用 WCF 服务 是完全支持的&#xff0c;只要服务使用的是 basicHttpBinding 或类似 HTTP 协议的绑定&#xff08;如 wsHttpBinding&#xff0c;但不推荐&#xff09; .NET Core不支持 net.tcp,只能用http形式。 .net core调用WCF服务…

小结: js 在浏览器执行原理

浏览器多进程与多线程 现代浏览器的标签环境隔离主要通过多进程架构和多线程机制实现&#xff0c;以确保安全、性能和稳定性。以下是浏览器实现标签环境隔离的多进程和多线程交互架构的详细解析&#xff1a; ------------------- ------------------- -----------…

【实战篇】低代码报表开发——平台运营日报表的开发实录

前言 myBuilder的推广有段时间了&#xff0c;想开发个报表看看平台运营的情况。采用myBuilder强大的报表、数据交换模块功能&#xff0c;直接开干。 1. 报表指标思考与概要设计 首先是报表模块的概要设计&#xff0c;先构思一下&#xff0c;我希望报表能查看新用户注册、活跃…

使用Qt操作SQLite数据库

目录 一、开发成果二、环境配置与基础概念1. 引入SQL模块2. SQLite数据库特性三、数据库连接与操作流程1. 创建并连接数据库2. 执行SQL语句3. 查询与遍历数据四、进阶操作与最佳实践1. 事务处理2. 错误处理3. 使用模型/视图架构五、完整代码示例(学生人员管理)1.mainwindow.h…

ZYNQ笔记(二十):Clocking Wizard 动态配置

版本&#xff1a;Vivado2020.2&#xff08;Vitis&#xff09; 任务&#xff1a;ZYNQ PS端 通过 AXI4Lite 接口配置 Clocking Wizard IP核输出时钟频率 目录 一、介绍 二、寄存器定义 三、配置 四、PS端代码 一、介绍 Xilinx 的 Clock Wizard IP核 用于在 FPGA 中生成和管理…

探秘高可用负载均衡集群:企业网络架构的稳固基石

目录 高可用负载均衡集群 一、集群的本质与核心价值​ 二、高可用集群与负载均衡集群的定义​ 高可用集群&#xff08;HA Cluster&#xff09;​ 负载均衡集群&#xff08;Load Balance Cluster&#xff09;​ 三&#xff0e;高可用与负载均衡的完美融合 四&#xff0e;…

JAVA:ResponseBodyEmitter 实现异步流式推送的技术指南

1、简述 在许多场景下,我们希望后端能够以流式、实时的方式推送数据给前端,比如消息通知、日志实时展示、进度条更新等。Spring Boot 提供了 ResponseBodyEmitter 机制,可以让我们在 Controller 中异步地推送数据,从而实现实时流式输出。 样例代码:https://gitee.com/lh…

CSS- 1.1 css选择器

本系列可作为前端学习系列的笔记&#xff0c;代码的运行环境是在HBuilder中&#xff0c;小编会将代码复制下来&#xff0c;大家复制下来就可以练习了&#xff0c;方便大家学习。 HTML系列文章 已经收录在前端专栏&#xff0c;有需要的宝宝们可以点击前端专栏查看&#xff01; 系…

ABP-Book Store Application中文讲解 - Part 2: The Book List Page

本章用于介绍如何创建Book List Page。 TBD 1. 汇总 ABP-Book Store Application中文讲解-汇总-CSDN博客 2. 前一章 ABP-Book Store Application中文讲解 - Part 1: Creating the Server Side 项目之间的引用关系。 目录 1. 多语言配置 1.1 zh-Hans.json 1.2 en.jso…

08 web 自动化之 PO 设计模式详解

文章目录 一、什么是 POM二、如何基于 POM 进行自动化框架架构&#xff1f;1、base 层封装2、pageobjects 层封装3、TestCases 层封装 三、元素和方法分离&数据分离1、哪些部分可以进行分离2、示例代码 四、总结 一、什么是 POM POM page object model 页面对象模型 WEB 自…

langchain4j集成QWen、Redis聊天记忆持久化

langchain4j实现聊天记忆默认是基于进程内存的方式&#xff0c;InMemoryChatMemoryStore是具体的实现了&#xff0c;是将聊天记录到一个map中&#xff0c;如果用户大的话&#xff0c;会造成内存溢出以及数据安全问题。位了解决这个问题 langchain4提供了ChatMemoryStore接口&am…