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

目录

  • 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 文件:

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/51185.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

谷粒商城实战笔记-75-商品服务-API-品牌管理-品牌分类关联与级联更新

文章目录 一&#xff0c;引入Mybatis Plus分页插件二&#xff0c;品牌列表的模糊查询三&#xff0c;增加品牌测试数据四&#xff0c;开发后台品牌关联分类接口1&#xff0c;接口product/categorybrandrelation/catelog/list2&#xff0c;接口product/categorybrandrelation/sav…

拓扑排序与有向无环图 -- Kahn算法和深度优先搜索

拓扑排序与有向无环图 文章目录 拓扑排序与有向无环图1. 什么是拓扑排序快速排序&#xff08;Quick Sort&#xff09;拓扑排序&#xff08;Topological Sort&#xff09;主要区别 2. 拓扑排序与有向无环图之间的契合性3. Kahn算法实现拓扑排序算法思想算法步骤算法代码 4. 深度…

线性回归从零实现

《李沐动手学深度学习》P30 import numpy as np import matplotlib.pyplot as plt import random# y x * w b noise # x dimension: (num_samples, 2) # w dimension: (2, 1) # b dimension: (1, 1) # noise dimension: (num_samples, 1) def generate_data(num_samples, w…

汽车长翅膀:GPU 是如何加速深度学习模型的训练和推理过程的?

编者按&#xff1a;深度学习的飞速发展离不开硬件技术的突破&#xff0c;而 GPU 的崛起无疑是其中最大的推力之一。但你是否曾好奇过&#xff0c;为何一行简单的“.to(‘cuda’)”代码就能让模型的训练速度突飞猛进&#xff1f;本文正是为解答这个疑问而作。 作者以独特的视角&…

数仓架构解析(第45天)

系列文章目录 经典数仓架构传统离线大数据架构 文章目录 系列文章目录烂橙子-终生成长群群主前言1. 经典数仓架构2. 传统离线大数据架构 烂橙子-终生成长群群主 前言 经典数仓架构 传统离线大数据架构 背景解析 1. 经典数仓架构 1991年&#xff0c;比尔恩门&#xff08;Bill…

Python 中 Caffe 库的使用方法

Caffe 是一个由伯克利视觉与学习中心 (Berkeley Vision and Learning Center, BVLC) 开发的深度学习框架。它特别适用于图像分类和图像分割任务。以下是一个关于如何使用 Caffe 库的详细指南&#xff0c;包括安装、配置、构建和训练模型的步骤。 1. 安装 Caffe 安装 Caffe 可以…

Pinokio:一键安装开源 AI 应用

整合了几乎所有市面上开源的 AI 工具傻瓜式地一键安装AI 工具支持全平台&#xff1a;Windows、Mac、Linux官网&#xff1a;https://pinokio.computer项目仓库&#xff1a;GitHub - pinokiocomputer/pinokio: AI Browser文章地址&#xff1a;https://blog.i68.ltd/archives/Pino…

牛客算法题解:数字统计、两个数组的交集、点击消除

目录 BC153 [NOIP2010]数字统计 ▐ 题解 NC313 两个数组的交集 ▐ 题解 AB5 点击消除 ▐ 题解 BC153 [NOIP2010]数字统计 题目描述&#xff1a; 题目链接&#xff1a; [NOIP2010]数字统计_牛客题霸_牛客网 (nowcoder.com) ▐ 题解 题目要求统计出某段数组中一共有多少个…

关于Buffer和Channel的注意事项和细节

1.举例 package org.example.demo;import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.util.RandomAccess;/*** MappedByteBuffer可…

Linux入门级常用命令行(二)

目录 1、mv指令 2、rm指令 3、通配符* 4、chmod指令 5、tar指令 1、mv指令 功能 用于移动或重命名文件和目录的命令 基本用法 mv [选项] 源文件或目录 目标文件或目录 常用选项 -i&#xff1a;在覆盖文件之前提示用户确认。-f&#xff1a;强制移动或重命名&#xff0…

动量参数(Momentum Parameter)

动量参数&#xff08;Momentum Parameter&#xff09;在机器学习中指的是一种用于加速梯度下降算法的技术&#xff0c;特别是深度学习中优化神经网络权重时。简单来说&#xff0c;动量参数是一种帮助优化过程加速并减少震荡的技术。 具体来说&#xff0c;动量参数具有以下特点…

网络编程——wireshark抓包、tcp粘包

目录 一、前言 1.1 什么是粘包 1.2 为什么UDP不会粘包 二、编写程序 文件树 客户端程序 服务器程序 tcp程序 头文件 makefile 三、 实验现象 四、改进实验 五、小作业 一、前言 最近在做网络芯片的驱动&#xff0c;验证功能的时候需要借助wireshark这个工具&…

猫头虎分享:Numpy知识点一文带你详细学习np.random.randn()

&#x1f42f; 猫头虎分享&#xff1a;Numpy知识点一文带你详细学习np.random.randn() 摘要 Numpy 是数据科学和机器学习领域中不可或缺的工具。在本篇文章中&#xff0c;我们将深入探讨 np.random.randn()&#xff0c;一个用于生成标准正态分布的强大函数。通过详细的代码示…

Android Studio 一键删除 Recent Projects信息的方法

Android Studio打开项目多了就一堆最近项目的记录&#xff0c;在IDE里面只能一个个手动删除。 File - Recent Projects 解决方案&#xff1a;修改配置文件 Note&#xff1a;方法不唯一。 Android Studio 存储了一个包含最近打开项目信息的配置文件。通过手动编辑或删除recentP…

会员管理系统需求文档示例

1. 引言 目的&#xff1a; 本需求文档旨在明确会员管理系统的目标、功能和非功能性需求&#xff0c;以指导系统的设计、开发和测试过程。 背景&#xff1a; 随着公司业务的不断增长&#xff0c;我们需要一个高效、可靠的会员管理系统来帮助我们更好地管理客户关系、提高服务质…

科普文:kubernets原理

kubernetes 已经成为容器编排领域的王者&#xff0c;它是基于容器的集群编排引擎&#xff0c;具备扩展集群、滚动升级回滚、弹性伸缩、自动治愈、服务发现等多种特性能力。 本文将带着大家快速了解 kubernetes &#xff0c;了解我们谈论 kubernetes 都是在谈论什么。 一、背…

详细介绍BIO、NIO、IO多路复用(select、poll、epoll)

BIO、NIO、IO多路复用 BIO(Blocking IO)NIO(Non-blocking IO) 同步非阻塞IOIO多路复用selectpollepoll Redis的IO多路复用 BIO(Blocking IO) 最基础的IO模型&#xff0c;当进行IO操作时&#xff0c;线程会被阻塞&#xff0c;直到操作完成。 比如read和write&#xff0c;通常IO…

Python的输入规则

Python的输入特别有意思&#xff0c;它和C的输入不一样&#xff0c;它的输入的原型是类似于C的string类型&#xff0c;但是对于一些有意思的算法题来说&#xff0c;光是读入string型的内容并不容易解题&#xff0c;于是我们可以从两个方面来将输入给转化。 1. 先使用函数input…

SGLang 大模型推理框架 qwen2部署使用案例;openai接口调用、requests调用

参考: https://github.com/sgl-project/sglang 纯python写,号称比vllm、tensorRT还快 暂时支持模型 安装 可以pip、源码、docker安装,这里用的pip 注意flashinfer安装最新版,不然会可能出错误ImportError: cannot import name ‘top_k_top_p_sampling_from_probs’ fr…

ConcurrentHashMap 和 Hashtable 的区别

ConcurrentHashMap 概念 ConcurrentHashMap 和 Hashtable 的区别主要体现在实现线程安全的方式上不同JDK1.7的 ConcurrentHashMap 底层采用 分段的数组链表 实现JDK1.8 采用的数据结构是数组链表红黑二叉树在JDK1.7的时候&#xff0c;ConcurrentHashMap&#xff08;分段锁&…