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

发布于:2024-08-03 ⋅ 阅读:(119) ⋅ 点赞:(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 服务器

相关链接:
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 文件:

在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到