在Spring Boot中使用iTextPDF创建动态PDF文档

最近,我们的系统新增了一个客服模块,其中一个重要功能是能够以PDF格式导出客服与用户之间的聊天记录。这些聊天记录包含文字、图片和文件等多种内容。为了实现这一功能,我们首先使用了itextpdf 5.x版本制作了一个Demo。今天,我将与家人们分享一下这项进展。

itextpdf.jpg

iTextPDF 介绍

iTextPDF 是一个用于创建和操作 PDF(Portable Document Format)文档的流行的 Java 库。它提供了一套全面的功能,用于处理 PDF 文件,包括创建新文档、修改现有文档以及提取信息。以下是 iTextPDF 的一些关键方面的简要概述:

  • 文档创建:

iTextPDF 允许您从头开始创建新的 PDF 文档。

您可以向文档添加段落、表格、图像和其他元素。

  • 文本操作:

该库提供了格式化和处理文本的方法。

  • 页面布局:

您可以定义页面的布局,包括页面尺寸、边距等。

  • 字体和颜色:

iTextPDF 允许您选择字体和颜色,以定制文档的外观。

  • 表格:

通过 iTextPDF,您可以创建包含表格的文档,设置表格的列数、行数和单元格内容。

  • 图像处理:

您可以将图像插入到文档中,并设置图像的大小和位置。

  • 文档安全性:

iTextPDF 提供了对文档进行加密和数字签名的功能,以增强文档的安全性。

  • 文档解析:

除了创建文档,iTextPDF 还允许您解析现有的 PDF 文档,提取文本、图像等信息。

代码示例

我们此处使用的 iTextPDF 5.x的版本实现的

添加依赖

在pom文件中添加如下依赖

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

代码编写

service代码

python 复制代码
import cn.xj.xjdoc.system.entity.Message;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.itextpdf.text.*;
import com.itextpdf.text.Font;
import com.itextpdf.text.Image;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.ColumnText;
import com.itextpdf.text.pdf.PdfAction;
import com.itextpdf.text.pdf.PdfWriter;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

@Service
@Slf4j
public class PdfService {

    public void export(HttpServletResponse response) throws IOException, DocumentException {

        List<Message> messageList = getMsgList();
        // 创建 PDF document
        Document document = new Document();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        PdfWriter pdfWriter = PdfWriter.getInstance(document, baos);

        //获取系统字体,如果是中文,则需注意linux中不存在windows字体,中文乱码或者写不进去
        FontFactory.registerDirectories();

        Font chineseFont = FontFactory.getFont("SimSun", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);

        document.open();

        // 发送人字体
        Font senderFont = new Font(chineseFont.getBaseFont(), 16, Font.BOLD, BaseColor.ORANGE);
        // 发送时间字体
        Font senderTimeFont = new Font(chineseFont.getBaseFont(), 12, Font.UNDEFINED, BaseColor.BLUE);
        // 内容字体
        Font defaultFont = new Font(chineseFont.getBaseFont(), 12);

        // 将信息写入pdf中
        for (Message msg : messageList) {

            String senderText = String.format("%s  ", msg.getSendUser());
            String timeText = msg.getSendTime();
            String messageText = msg.getContent();
            Integer type = msg.getType();

            Paragraph paragraph = new Paragraph();

            ColumnText columnText = new ColumnText(pdfWriter.getDirectContent());
            columnText.addElement(paragraph);
            columnText.setSimpleColumn(20, document.bottom(), document.right() - 20, document.top(), 0, Element.ALIGN_LEFT);
            paragraph.add(new Chunk(senderText, senderFont));
            paragraph.add(new Chunk(timeText + "\n", senderTimeFont));

            float proportion = 1f;

            if(type == 1){
                //文字
                paragraph.add(new Chunk(messageText + "\n", defaultFont));
            }else if(type == 2){
                //图片
                // 创建Image对象
                Image image = Image.getInstance(new URL(msg.getContent()));

                //等比缩小图片
                if(image.getWidth() > 150){
                    proportion = image.getWidth()/150;
                    image.scaleToFit(image.getWidth()/proportion, image.getHeight()/proportion);
                }
                //加一空行
                paragraph.add(new Chunk(Chunk.NEWLINE));
                // 判断当前页内容是否已满
                if ((pdfWriter.getVerticalPosition(true) - image.getHeight()/proportion) < document.bottom()) {
                    document.newPage(); // Start a new page
                    columnText.setYLine(document.top());
                }
                paragraph.add(image);
            }else{
                //文件
                // 创建Chunk,设置文件连接,点击下载
                Chunk chunk = new Chunk(msg.getFileName(),defaultFont);
                chunk.setAction(new PdfAction(new URL(msg.getContent()).toExternalForm()));
                paragraph.add(chunk);
            }

            paragraph.setIndentationLeft(20f);
            paragraph.setIndentationRight(20f);
            paragraph.setSpacingAfter(10f);

            // 判断当前页内容是否已满
            if (ColumnText.hasMoreText(columnText.go())) {
                document.newPage();
            }
            document.add(paragraph);

        }
        document.close();

        //返回pdf
        response.setContentType("application/pdf");
        response.setHeader("Content-Disposition", "inline; filename=chat_export.pdf");
        response.getOutputStream().write(baos.toByteArray());
        response.getOutputStream().flush();
    }


    /**
     * 获取数据的方法
     * @return
     */
    public List<Message>  getMsgList(){
        String  jsonArrayString ="[{"type": 1, "content": "嘿,听说最近有点八卦,你有什么料吗?", "fileName": null, "sendTime": "2024-01-25 21:46:12", "sendUser": "大雄"}, {"type": 1, "content": "当然啦!你知道吗,最近有个超级智能机器人在公司里开始工作了,听说能做我们的工作,让人有点担心啊。", "fileName": null, "sendTime": "2024-01-25 21:47:17", "sendUser": "修己"}, {"type": 1, "content": " 哇,真的吗?那它是怎么工作的?会不会抢我们的饭碗?", "fileName": null, "sendTime": "2024-01-25 21:48:22", "sendUser": "大雄"}, {"type": 1, "content": " 别慌,听说它只是一个AI助手,能够处理一些重复性的任务,让我们有更多时间专注于创造性的工作。", "fileName": null, "sendTime": "2024-01-25 21:49:12", "sendUser": "修己"}, {"type": 1, "content": " 哦,原来如此。不过,你觉得这个机器人有没有潜在的危险性啊?", "fileName": null, "sendTime": "2024-01-25 21:50:18", "sendUser": "大雄"}, {"type": 1, "content": "哈哈,我也有点担心,但听说它的设计是为了帮助我们,而不是取代我们。还有,它可不会参与任何八卦。", "fileName": null, "sendTime": "2024-01-25 21:51:42", "sendUser": "修己"}, {"type": 1, "content": " 哈哈,说到八卦,你有听说最近公司里有什么有趣的事情吗?", "fileName": null, "sendTime": "2024-01-25 21:52:12", "sendUser": "大雄"}, {"type": 1, "content": "对呀,听说某某老板最近秘密约会了某某同事,整个公司都在传。你觉得是真的吗?照片都爆出来了,给你发下", "fileName": null, "sendTime": "2024-01-25 21:53:12", "sendUser": "修己"}, {"type": 2, "content": "http://chevereto.xiuji.mynatapp.cc/images/2023/06/27/z_3b2fa0f1285d8071229631addebf3087.png", "fileName": null, "sendTime": "2024-01-25 21:54:12", "sendUser": "修己"}, {"type": 2, "content": "http://chevereto.xiuji.mynatapp.cc/images/2023/06/23/_20230623212425.png", "fileName": null, "sendTime": "2024-01-25 21:55:12", "sendUser": "修己"}, {"type": 2, "content": "http://chevereto.xiuji.mynatapp.cc/images/2023/06/27/z_73bc1ab1a92d68b15e9e1babc3d15afb.png", "fileName": null, "sendTime": "2024-01-25 21:56:12", "sendUser": "修己"}, {"type": 2, "content": "http://chevereto.xiuji.mynatapp.cc/images/2023/06/23/_20230623212445.png", "fileName": null, "sendTime": "2024-01-25 21:57:12", "sendUser": "修己"}, {"type": 1, "content": "哇,这可真是八卦大爆炸啊!我倒是没听说,但如果是真的,那可真是太有趣了。", "fileName": null, "sendTime": "2024-01-25 21:58:12", "sendUser": "大雄"}, {"type": 1, "content": "是啊,公司里的八卦总是让人忍不住想知道更多。有时候我觉得,我们也是一群活在八卦世界里的AI助手。", "fileName": null, "sendTime": "2024-01-25 21:59:12", "sendUser": "修己"}, {"type": 1, "content": "哈哈,说得对!我们也需要一些八卦来调剂一下生活。不过,我还是觉得那个超级智能机器人有点神秘呢。", "fileName": null, "sendTime": "2024-01-25 22:02:36", "sendUser": "大雄"}, {"type": 1, "content": "是啊,或许我们也能通过它得知更多关于公司内幕的事情。要不要试试向它搭讪?", "fileName": null, "sendTime": "2024-01-25 22:05:36", "sendUser": "修己"}, {"type": 1, "content": " 哈哈,说不定它能告诉我们更多关于那对神秘约会的内幕。不过,我们还是小心点为好,免得被当成八卦机器人。", "fileName": null, "sendTime": "2024-01-25 22:08:36", "sendUser": "大雄"}, {"type": 1, "content": "顺便给你在发个我最近吃的瓜的pdf", "fileName": null, "sendTime": "2024-01-25 22:12:36", "sendUser": "修己"}, {"type": 4, "content": "http://chevereto.xiuji.mynatapp.cc/images/2023/06/23/西安外国语大学会计ACCA2001班丁玉婕出轨情况说明.pdf", "fileName": "西安外国语大学会计ACCA2001班丁玉婕出轨情况说明.pdf", "sendTime": "2024-01-25 22:18:36", "sendUser": "修己"}]";
        // 使用Fastjson将JsonArray字符串解析为JSONArray对象
        JSONArray jsonArray = JSONArray.parseArray(jsonArrayString);
        List<Message> messageList = new ArrayList<>();
        // 遍历JsonArray,将每个JSON对象转换为Message对象并添加到List中
        for (Object jsonMessage : jsonArray) {
            JSONObject jsonObject = (JSONObject) jsonMessage;
            Message message = jsonObject.toJavaObject(Message.class);
            messageList.add(message);
        }
        return messageList;
    }
}

controller代码

java 复制代码
import cn.xj.xjdoc.system.service.PdfService;
import com.itextpdf.text.DocumentException;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;

@RestController
@Slf4j
public class PdfController {

    @Resource
    private PdfService pdfService;
    
    @GetMapping("/export")
    public void export(HttpServletResponse response) throws DocumentException, IOException {
        pdfService.export(response);
    }
}

代码到这就完了,我们可以启动服务,查看下展示的效果:

_20240126070304.jpg

_20240126070314.jpg

_20240126070325.jpg

到这儿,如果不出意外的话肯定要出意外了,请继续阅读文章。

Linux上解决中文没写入或者乱码问题

如果我们将服务部署到Linux服务器上,可能会遇到中文未正确写入或乱码的问题。这是由于Linux系统上的字体库与Windows系统不同。为了解决这个问题,我们可以在代码中直接将所需字体的ttf文件复制到项目目录下,并使用itextpdf加载这些字体。此前,我们成功为服务器添加了Windows字体库,因此我们可以直接从系统中获取字体。接下来,我们将介绍在Linux中添加Windows字体的操作步骤。

windows字体库的位置:C:\Users\Administrator\AppData\Local\Microsoft\Windows\Fonts

Linux 中添加windows字体库

  • ubuntu

将windows的字体库Fonts 复制到目录 /usr/share/fonts 下,执行如下权限命令:

bash 复制代码
sudo chmod -R 777 Fonts

然后执行以下命令使字体生效

复制代码
sudo fc-cache -fv
  • centos

将windows的字体库Fonts下的文件 复制到目录 /usr/share/fonts 下,依次执行如下命令:

bash 复制代码
yum install -y mkfontscale

yum install -y fontconfig

cd /usr/share/fonts/

mkfontscale

mkfontdir

fc-cache

fc-list

构建具有windows字体库的docker镜像

  • ubuntu

Dockerfile

bash 复制代码
# 基于哪个镜像
FROM ubuntu:20.10

# 维护者
MAINTAINER xj

# 拷贝文件到容器
ADD Fonts /usr/share/fonts/chinese_font/
RUN chmod -R 755 /usr/share/fonts/chinese_font
RUN fc-cache -fv
  • centos

Dockerfile

bash 复制代码
# 基于哪个镜像
FROM centos:centos7.1.1503
# 维护者
MAINTAINER xj
ADD Fonts/* /usr/share/fonts/
RUN yum install -y mkfontscale
RUN yum install -y fontconfig
RUN cd /usr/share/fonts/
RUN mkfontscale
RUN mkfontdir
RUN fc-cache
RUN fc-list

总结

这个例子演示了如何使用Spring Boot和iTextPDF创建动态的、个性化的PDF文档。你可以根据实际需求扩展生成的PDF内容,包括图表、表格等,以满足项目的特定要求。希望这篇文章对你有所帮助!如果有任何问题或建议,请随时提出。

相关推荐
Victor35635 分钟前
Netty(20)如何实现基于Netty的WebSocket服务器?
后端
缘不易35 分钟前
Springboot 整合JustAuth实现gitee授权登录
spring boot·后端·gitee
Kiri霧41 分钟前
Range循环和切片
前端·后端·学习·golang
WizLC44 分钟前
【Java】各种IO流知识详解
java·开发语言·后端·spring·intellij idea
Victor3561 小时前
Netty(19)Netty的性能优化手段有哪些?
后端
爬山算法1 小时前
Netty(15)Netty的线程模型是什么?它有哪些线程池类型?
java·后端
白宇横流学长2 小时前
基于SpringBoot实现的冬奥会科普平台设计与实现【源码+文档】
java·spring boot·后端
Python编程学习圈2 小时前
Asciinema - 终端日志记录神器,开发者的福音
后端
bing.shao2 小时前
Golang 高并发秒杀系统踩坑
开发语言·后端·golang
壹方秘境2 小时前
一款方便Java开发者在IDEA中抓包分析调试接口的插件
后端