最近,我们的系统新增了一个客服模块,其中一个重要功能是能够以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文件中添加如下依赖
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13.2</version>
</dependency>
代码编写
service代码
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代码
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);
}
}
代码到这就完了,我们可以启动服务,查看下展示的效果:
_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
下,执行如下权限命令:
sudo chmod -R 777 Fonts
然后执行以下命令使字体生效
sudo fc-cache -fv
- centos
将windows的字体库Fonts下的文件 复制到目录 /usr/share/fonts
下,依次执行如下命令:
yum install -y mkfontscale
yum install -y fontconfig
cd /usr/share/fonts/
mkfontscale
mkfontdir
fc-cache
fc-list
构建具有windows字体库的docker镜像
- ubuntu
Dockerfile
# 基于哪个镜像
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
# 基于哪个镜像
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内容,包括图表、表格等,以满足项目的特定要求。希望这篇文章对你有所帮助!如果有任何问题或建议,请随时提出。