阅读量:0
目录
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 服务器
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 文件: