目录
- 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 {@Autowiredprivate 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 {@Autowiredprivate TemplateEngine templateEngine;@Overridepublic 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: 8080ftp:server:host: 192.168.101.65port: 21username: scpassword: 123remoteDir: /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 文件: