目录
- 1.需求分析
- 2.项目环境搭建
- 3.将 HTML 转换为 PDF
- 3.1.代码实现
- mail.html
- HtmlToPDFController.java
- PDFConverterService.java
- PDFConverterServiceImpl.java
- 3.2.测试
- 3.3.注意事项
- 4.将生成的 PDF 上传到 FTP 服务器
- 4.1.搭建 FTP 服务器
- 4.2.配置文件
- 4.3.代码实现
- FtpUtil.java
- FTPController.java
- 4.4.测试
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.代码实现
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>
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";
}
}
PDFConverterService.java
package com.example.htmltopdf.service;
public interface PDFConverterService {
void convertHtmlToPDF();
}
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.代码实现
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);
}
}
}
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 文件: