云计算百科
云计算领域专业知识百科平台

功能实现——使用 OpenPDF 将 HTML 转换为 PDF,并将其上传到 FTP 服务器

目录

  • 1.需求分析
  • 2.项目环境搭建
  • 3.将 HTML 转换为 PDF
    • 3.1.代码实现
      • 3.1.1.mail.html
      • 3.1.2.HtmlToPDFController.java
      • 3.1.3.PDFConverterService.java
      • 3.1.4.PDFConverterServiceImpl.java
    • 3.2.测试
    • 3.3.注意事项
  • 4.将生成的 PDF 上传到 FTP 服务器
    • 4.1.搭建 FTP 服务器
    • 4.2.配置文件
    • 4.3.代码实现
      • 4.3.1.FtpUtil.java
      • 4.3.2.FTPController.java
    • 4.4.测试
    • 4.5.拓展

1.需求分析

使用 OpenPDF 将 HTML 文件转换为 PDF,并将其上传到 FTP 服务器。

2.项目环境搭建

(1)在 IDEA 中创建一个 Spring Boot 项目,具体可以参考【环境搭建】使用IDEA创建SpringBoot项目详细步骤这篇文章。

(2)pom.xml 中添加如下依赖:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.16.2</version>
</dependency>

<dependency>
<groupId>com.github.librepdf</groupId>
<artifactId>openpdf</artifactId>
<version>1.3.32</version>
</dependency>

<dependency>
<groupId>org.xhtmlrenderer</groupId>
<artifactId>flying-saucer-pdf-openpdf</artifactId>
<version>9.3.1</version>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
</dependency>

<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>3.6</version>
</dependency>

3.将 HTML 转换为 PDF

3.1.代码实现

3.1.1.mail.html

邮件模板 mail.html 如下:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>入职欢迎邮件</title>

<style>
body {
font-family: SimHei;
}
</style>

</head>
<body>
欢迎 <span th:text="${name}"></span> 加入 XXXX 大家庭,您的入职信息如下:
<table border="1">
<tr>
<td>姓名</td>
<td th:text="${name}"></td>
</tr>
<tr>
<td>职位</td>
<td th:text="${posName}"></td>
</tr>
<tr>
<td>职称</td>
<td th:text="${jobLevelName}"></td>
</tr>
<tr>
<td>部门</td>
<td th:text="${departmentName}"></td>
</tr>
</table>
<p>我们公司的工作忠旨是严格,创新,诚信,您的加入将为我们带来新鲜的血液,带来创新的思维,
以及为我们树立良好的公司形象!希望在以后的工作中我们能够齐心协力,与时俱进,团结协作!
同时也祝您在本公司,工作愉快,实现自己的人生价值!希望在未来的日子里,携手共进!</p>
</body>
</html>

3.1.2.HtmlToPDFController.java

package com.example.htmltopdf.controller;

import com.example.htmltopdf.service.PDFConverterService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/htp")
public class HtmlToPDFController {

@Autowired
private PDFConverterService pdfConverterService;

@PostMapping("/converter")
public String htmlToPDF() {
pdfConverterService.convertHtmlToPDF();
return "success";
}

}

3.1.3.PDFConverterService.java

package com.example.htmltopdf.service;

public interface PDFConverterService {

void convertHtmlToPDF();

}

3.1.4.PDFConverterServiceImpl.java

package com.example.htmltopdf.service.impl;

import com.example.htmltopdf.service.PDFConverterService;
import com.lowagie.text.pdf.BaseFont;
import lombok.extern.slf4j.Slf4j;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import org.xhtmlrenderer.pdf.ITextRenderer;

import java.io.*;
import java.util.HashMap;
import java.util.Map;

@Slf4j
@Service
public class PDFConverterServiceImpl implements PDFConverterService {

@Autowired
private TemplateEngine templateEngine;

@Override
public void convertHtmlToPDF() {
Context context = new Context();
context.setVariables(assembleParameters());
System.out.println("Processing template…");
String htmlContent = templateEngine.process("mail", context);

Document doc = Jsoup.parse(htmlContent, "utf-8");
//默认是以 html 的方式
doc.outputSettings().syntax(Document.OutputSettings.Syntax.xml);
//需改用 xml 的方式格式,否则用 openPdf 转化时 PDF 时可能排版错乱
byte[] pdfBytes = html2PDF(doc.outerHtml());

//文件保存本地路径
String filePath = "output.pdf";
try (FileOutputStream fos = new FileOutputStream(filePath)) {
//将 byte[] 数据写入文件
fos.write(pdfBytes);
log.info("PDF 文件保存成功:{}", filePath);
} catch (IOException e) {
log.info("保存 PDF 文件时出现错误:{}", e.getMessage());
e.printStackTrace();
}
}

public Map<String, Object> assembleParameters() {
HashMap<String, Object> map = new HashMap<>();
map.put("name", "Tom");
map.put("posName", "software");
map.put("jobLevelName", "高级");
map.put("departmentName", "软件部");
return map;
}

public byte[] html2PDF(String html) {
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
ITextRenderer renderer=new ITextRenderer();

// 加载字体文件(SimHei为示例,请根据实际字体文件替换路径)
String fontFile = "/static/fonts/SimHei.ttf";
renderer.getFontResolver().addFont(fontFile, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);

renderer.setDocumentFromString(html);
renderer.layout();
renderer.createPDF(outputStream);
return outputStream.toByteArray();
} catch (Exception e) {
e.printStackTrace();
}
return new byte[0];
}

}

3.2.测试

启动项目后,在 Postman 中进行接口测试(注意是 POST 请求):

http://localhost:8080/htp/converter

运行成功后会发现已经生成了如下 PDF 文件:

在这里插入图片描述

PDF 中的内容如下:

在这里插入图片描述

3.3.注意事项

在 HTML 转为 PDF 时,如果页面存在中文字符,可能会出现转换后中文字符不显示的情况!本文的解决办法如下:

(1)在 HTML 页面中设置字体系列(下面以黑体 SimHei 为例):

<style>
body
{
font-family: SimHei;
}
</style>

(2)使用 ITextRenderer 转换时加载对应字体(具体见 html2PDF 方法):

// 加载字体文件(SimHei为示例,请根据实际字体文件替换路径)
String fontFile = "/static/fonts/SimHei.ttf";
renderer.getFontResolver().addFont(fontFile, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);

4.将生成的 PDF 上传到 FTP 服务器

相关链接: Java实现文件上传到ftp服务器 Java从ftp服务器上传与下载文件

4.1.搭建 FTP 服务器

下面所使用的 TFP 服务器搭建在 CentOS 7 上,具体搭建过程可见 Linux – 搭建 FTP 服务器这篇文章。

4.2.配置文件

application.yml 中的内容如下:

server:
port: 8080

ftp:
server:
host: 192.168.101.65
port: 21
username: sc
password: 123
remoteDir: /home/sc

4.3.代码实现

4.3.1.FtpUtil.java

package com.example.htmltopdf.utils;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;

import java.io.*;
import java.nio.charset.StandardCharsets;

@Slf4j
public class FTPUtil {

/**
* 获取一个 FTP 连接
*
* @param host ip 地址
* @param port 端口
* @param username 用户名
* @param password 密码
* @return 返回 FTP 连接对象
* @throws Exception 连接 FTP 时发生的各种异常
*/

public static FTPClient getFtpClient(String host, Integer port, String username, String password) throws Exception {
FTPClient ftpClient = new FTPClient();

//连接服务器
ftpClient.connect(host, port);

int reply = ftpClient.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply)) {
log.error("无法连接至 ftp 服务器,host:{},port:{}", host, port);
ftpClient.disconnect();
return null;
}

//登入服务器
boolean login = ftpClient.login(username, password);
if (!login) {
log.error("登录失败, 用户名或密码错误");
ftpClient.logout();
ftpClient.disconnect();
return null;
}

//连接并且成功登陆 FTP 服务器
log.info("login success ftp server, host: {}, port: {}, user: {}", host, port, username);

//设置通道字符集, 要与服务端设置一致
ftpClient.setControlEncoding("UTF-8");
//设置文件传输编码类型, 字节传输:BINARY_FILE_TYPE, 文本传输:ASCII_FILE_TYPE, 建议使用BINARY_FILE_TYPE进行文件传输
ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
//主动模式: enterLocalActiveMode(),被动模式: enterLocalPassiveMode(),一般选择被动模式
ftpClient.enterLocalPassiveMode();
//切换目录
//ftpClient.changeWorkingDirectory("xxxx");

return ftpClient;
}

/**
* 断开 FTP 连接
*
* @param ftpClient FTP 连接客户端
*/

public static void disConnect(FTPClient ftpClient) {
if (ftpClient == null) {
return;
}
try {
log.info("断开 FTP 连接,host: {},port: {}", ftpClient.getPassiveHost(), ftpClient.getPassivePort());
ftpClient.logout();
ftpClient.disconnect();
} catch (IOException e) {
e.printStackTrace();
log.error("FTP 连接断开异常,请检查!");
}

}

/**
* 文件下载
*
* @param ftpClient FTP 连接客户端
* @param path 文件路径
* @param downPath 文件名称
*/

public static void download(FTPClient ftpClient, String path, String downPath) throws Exception {
if (ftpClient == null || path == null || downPath == null) {
return;
}

//中文目录处理存在问题, 转化为 FTP 能够识别中文的字符集
String remotePath;
try {
remotePath = new String(path.getBytes(StandardCharsets.UTF_8), FTP.DEFAULT_CONTROL_ENCODING);
} catch (UnsupportedEncodingException e) {
remotePath = path;
}

InputStream inputStream = ftpClient.retrieveFileStream(remotePath);
if (inputStream == null) {
log.error("{} 在 TFP 服务器中不存在,请检查", path);
return;
}

FileOutputStream outputStream = new FileOutputStream(downPath);
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream);
try {
byte[] buffer = new byte[2048];
int i;
while ((i = bufferedInputStream.read(buffer)) != 1) {
bufferedOutputStream.write(buffer, 0, i);
bufferedOutputStream.flush();
}
} catch (Exception e) {
log.error("文件下载异常", e);
log.error("{} 下载异常,请检查!", path);
}

inputStream.close();
outputStream.close();
bufferedInputStream.close();
bufferedOutputStream.close();

//关闭流之后必须执行,否则下一个文件导致流为空
boolean complete = ftpClient.completePendingCommand();
if (complete) {
log.info("文件 {} 下载完成", remotePath);
} else {
log.error("文件 {} 下载失败", remotePath);
}
}

/**
* 上传文件
*
* @param ftpClient FTP 连接客户端
* @param sourcePath 源地址
*/

public static void upload(FTPClient ftpClient, String sourcePath, String remoteDir) throws Exception {
if (ftpClient == null || sourcePath == null) {
return;
}

File file = new File(sourcePath);
if (!file.exists() || !file.isFile()) {
return;
}

//中文目录处理存在问题,转化为 TFP 能够识别中文的字符集
String remotePath = new String((remoteDir + "/" + file.getName())
.getBytes(StandardCharsets.UTF_8), FTP.DEFAULT_CONTROL_ENCODING);
try (
InputStream inputStream = new FileInputStream(file);
OutputStream outputStream = ftpClient.storeFileStream(remotePath);
) {
byte[] buffer = new byte[2048];
int length;
while ((length = inputStream.read(buffer)) != 1) {
outputStream.write(buffer, 0, length);
outputStream.flush();
}
} catch (Exception e) {
log.error("文件上传异常", e);
}

// 关闭流之后必须执行,否则下一个文件导致流为空
boolean complete = ftpClient.completePendingCommand();
if (complete) {
log.info("文件 {} 上传完成", remotePath);
} else {
log.error("文件 {} 上传失败", remotePath);
}
}
}

4.3.2.FTPController.java

package com.example.htmltopdf.controller;

import com.example.htmltopdf.utils.FTPUtil;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/ftp")
public class FTPController {

@Value("${ftp.server.host}")
private String FTP_HOST;

@Value("${ftp.server.port}")
private int FTP_PORT;

@Value("${ftp.server.username}")
private String FTP_USERNAME;

@Value("${ftp.server.password}")
private String FTP_PASSWORD;

@Value("${ftp.server.remoteDir}")
private String FTP_REMOTE_DIR;

@PostMapping("/upload")
public String uploadFile() throws Exception {
System.out.println();
FTPClient ftpClient = FTPUtil.getFtpClient(FTP_HOST, FTP_PORT, FTP_USERNAME, FTP_PASSWORD);

// 展示文件夹
assert ftpClient != null;
FTPFile[] ftpFiles = ftpClient.listDirectories();
for (FTPFile file : ftpFiles) {
System.out.println(file.getName());
}

//上传文件
FTPUtil.upload(ftpClient, "output.pdf", FTP_REMOTE_DIR);

//下载文件
FTPUtil.download(ftpClient, FTP_REMOTE_DIR + "/output.pdf", "E:\\\\output.pdf");

FTPUtil.disConnect(ftpClient);

return "success";
}

}

4.4.测试

启动项目后,在 Postman 中进行接口测试(注意是 POST 请求):

http://localhost:8080/ftp/upload

运行成功后会发现 FTP 服务器对应目录中已经有了该 PDF 文件:

在这里插入图片描述

并且下载功能也是正常的,在本地的 E 盘中也出现该 PDF 文件:

在这里插入图片描述

4.5.拓展

如果需要同时上传多个 PDF 文件时,除了可以将其全部上传之外,还可以考虑将它们压缩打包之后再上传。相关实现代码如下所示:

public class FTPController {

@Value("${ftp.server.host}")
private String FTP_HOST;

@Value("${ftp.server.port}")
private int FTP_PORT;

@Value("${ftp.server.username}")
private String FTP_USERNAME;

@Value("${ftp.server.password}")
private String FTP_PASSWORD;

@Value("${ftp.server.remoteDir}")
private String FTP_REMOTE_DIR;

@PostMapping("/uploadZip")
public String uploadZipFile() throws Exception {
FTPClient ftpClient = FTPUtil.getFtpClient(FTP_HOST, FTP_PORT, FTP_USERNAME, FTP_PASSWORD);

//将本地的多个 PDF 文件进行压缩,生成 all_output.zip 文件
List<String> tmpFilePaths = new ArrayList<>();
tmpFilePaths.add("1725091159095_output.pdf");
tmpFilePaths.add("1725091160127_output.pdf");
tmpFilePaths.add("1725091160976_output.pdf");

//输出 ZIP 文件路径
String zipFileName = compressPDFsToZip(tmpFilePaths);

//上传压缩包
FTPUtil.upload(ftpClient, zipFileName, FTP_REMOTE_DIR);

//下载压缩包
String downPath = "E:\\\\" + System.currentTimeMillis() + zipFileName;
FTPUtil.download(ftpClient, FTP_REMOTE_DIR + "/" + zipFileName, downPath);

FTPUtil.disConnect(ftpClient);

return "success";
}

/**
* @description 将多个 PDF 文件进行压缩
* @param tmpFilePaths 多个 PDF 文件路径
* @return 压缩包路径
* */

public String compressPDFsToZip(List<String> tmpFilePaths) throws IOException {
String zipFileName = "compressed_pdfs.zip";
// 创建 ZIP 文件输出流
try (ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(zipFileName))) {
// 遍历 PDF 文件
for (String tmpFilepath : tmpFilePaths) {
File fileToZip = new File(tmpFilepath);
try (FileInputStream fis = new FileInputStream(fileToZip)) {
// 创建 ZIP 文件条目
ZipEntry zipEntry = new ZipEntry(fileToZip.getName());
zipOut.putNextEntry(zipEntry);

// 将 PDF 文件写入 ZIP 文件
byte[] buffer = new byte[1024];
int length;
while ((length = fis.read(buffer)) > 0) {
zipOut.write(buffer, 0, length);
}

zipOut.closeEntry();
}
}
} catch (IOException e) {
System.err.println("An error occurred while creating the ZIP file: " + e.getMessage());
e.printStackTrace();
}
return zipFileName;
}
}

赞(0)
未经允许不得转载:网硕互联帮助中心 » 功能实现——使用 OpenPDF 将 HTML 转换为 PDF,并将其上传到 FTP 服务器
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!