功能实现——使用 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…

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

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

数仓架构解析(第45天)

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

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

目录 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可…

动量参数(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…

科普文: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…

EtherNet/IP转Profinet协议网关(经典配置案例)

怎么样才能把EtherNet/IP和Profinet网络连接起来呢?这几天有几个朋友问到了这个问题&#xff0c;作者在这里统一为大家详细说明一下。其实有一个设备可以很轻松地解决这个问题&#xff0c;名为JM-PN-EIP&#xff0c;下面是详细介绍。 一&#xff0c;设备主要功能 1、捷米特J…

nodepad++已打开的文件怎么按照字母/文字顺序排列?

nodepad已打开的文件怎么按照字母/文字顺序排列&#xff1f; 点击菜单栏 “窗口” -> “排序方式” &#xff08;可选择升序或降序&#xff09;

加密货币赋能跨境电商:PayPal供应链金融服务如何引领行业新趋势

跨境电商行业近年来呈现出爆发式增长&#xff0c;随着全球化贸易壁垒的降低和数字经济的快速发展&#xff0c;越来越多的商家和消费者跨越国界进行交易。根据eMarketer的数据&#xff0c;全球跨境电商交易额在2023年已超过4万亿美元&#xff0c;并预计在未来几年内仍将保持两位…

centos7 xtrabackup mysql(8)增量备份(1)

centos7 xtrabackup mysql&#xff08;8&#xff09;增量备份&#xff08;1&#xff09; 参考 xtrabackup-8.0的安装、备份以及恢复&#xff08;innoxtrabackup有待测试&#xff09; https://blog.csdn.net/DWJRIVER/article/details/117792271 https://blog.csdn.net/qq_28…

开发环境搭建——Tomcat安装配置

一、Tomcat安装 1、解压下载好的安装包&#xff0c;将解压后的文件放到任意一个盘中&#xff0c;注意&#xff0c;尽量不要有中文 2、运行Tomcat&#xff0c;测试Tomcat是否正常连接使用 双击bin目录下的startup.bat文件&#xff0c;启动Tomcat 出现下面的界面标识Tomcat启动…

顺序栈和链栈的操作实现

目录 一. 前言 二.顺序栈 三. 链栈 一. 前言 简而言之&#xff0c;顺序栈就是栈的顺序存储&#xff0c;链栈就是栈的链式存储。 二.顺序栈 下面我们来看下顺序栈的结构定义&#xff1a; typedef char SElemType; #define MAXSIZE 100 typedef struct{SElemType * base; //…

使用git命令行的方式,将本地项目上传到远程仓库

在国内的开发环境中&#xff0c;git的使用是必不可少的。Git 是一款分布式版本控制系统&#xff0c;用于有效管理和追踪文件的变更历史及协作开发。本片文章就来介绍一下怎样使用git命令行的方式&#xff0c;将本地项目上传到远程仓库&#xff0c;虽然现在的IDE中基本都配置了g…