SpringBoot使用poi将word转换为PDF并且展示

1.前言

由于最近做了一个需求,界面上有一个按钮,点击按钮后将一个文件夹中的word文档显示在页面中,并且有一个下拉框可以选择不同的文档,选择文档可以显示该文档。这里我选择使用fr.opensagres.poi.xwpf.converter.pdf-gae依赖包来实现。

2.依赖

这里我只依赖了这些依赖包

java 复制代码
        <dependency>
            <groupId>fr.opensagres.xdocreport</groupId>
            <artifactId>fr.opensagres.poi.xwpf.converter.pdf-gae</artifactId>
            <version>2.0.1</version>
        </dependency>

        <!-- Apache PDFBox 依赖用于.docx转PDF -->
        <dependency>
            <groupId>org.apache.pdfbox</groupId>
            <artifactId>pdfbox</artifactId>
            <version>3.0.0</version> <!-- 根据最新版本调整 -->
        </dependency>

3.代码

Java代码部分,这里我使用了两个文件夹中的文档

java 复制代码
package com.hxgis.controller;

import java.io.File;
import java.io.FileInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import fr.opensagres.poi.xwpf.converter.pdf.PdfConverter;
import fr.opensagres.poi.xwpf.converter.pdf.PdfOptions;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.ResponseEntity;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
//import org.apache.poi.xwpf.converter.pdf.PdfOptions;
//import org.apache.poi.xwpf.converter.pdf.PdfConverter;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * ClassName: DocumentController
 * Package: com.hxgis.controller
 * Description:
 *
 * @Author dhn
 * @Create 2024/1/31 11:56
 * @Version 1.0
 */
@RestController
@RequestMapping("/api/document")
public class DocumentController {
    private final String MONTH_PATH = "D:/ftp_data/month"; // 修改为你的文档存储路径
    private final String QUARTER_PATH = "D:/ftp_data/quarter"; // 修改为你的文档存储路径

//    private final String MONTH_PATH = "/home/geli/hbnyWeatherReport/month"; // 修改为你的文档存储路径
//    private final String QUARTER_PATH = "/home/geli/hbnyWeatherReport/quarter"; // 修改为你的文档存储路径

    @GetMapping("/monthLatest")
    public ResponseEntity<Resource> getDocument(@RequestParam(required = false) String name) throws IOException {
        File folder = new File(MONTH_PATH);
        File[] files = folder.listFiles((dir, filename) -> filename.endsWith(".docx"));

        if (files == null || files.length == 0) {
            return ResponseEntity.notFound().build();
        }

        File fileToConvert;

        if (name != null && !name.isEmpty()) {
            // 用户选择了特定的文件
            fileToConvert = Arrays.stream(files)
                    .filter(file -> file.getName().equals(name))
                    .findFirst()
                    .orElse(null);
        } else {
            // 没有特定选择,找最新的文件
            Pattern pattern = Pattern.compile("(\\d{4})年(\\d{2})月风光资源趋势预测\\.docx");
            fileToConvert = Arrays.stream(files)
                    .filter(file -> pattern.matcher(file.getName()).matches())
                    .max(Comparator.comparingInt(file -> {
                        Matcher matcher = pattern.matcher(file.getName());
                        if (matcher.find()) {
                            int year = Integer.parseInt(matcher.group(1));
                            int month = Integer.parseInt(matcher.group(2));
                            return year * 100 + month;
                        }
                        return 0;
                    }))
                    .orElse(null);
        }

        if (fileToConvert == null) {
            return ResponseEntity.notFound().build();
        }

        // 以下为文件转换为PDF的代码
        try (XWPFDocument document = new XWPFDocument(new FileInputStream(fileToConvert));
             ByteArrayOutputStream out = new ByteArrayOutputStream()) {

            PdfOptions options = PdfOptions.create();
            PdfConverter.getInstance().convert(document, out, options);

            byte[] pdfContent = out.toByteArray();
            ByteArrayResource resource = new ByteArrayResource(pdfContent);

            return ResponseEntity.ok()
                    .contentLength(pdfContent.length)
                    .header("Content-type", "application/pdf")
                    .body(resource);
        }
    }


    @GetMapping("/quarterLatest")
    public ResponseEntity<Resource> quarterLatest(@RequestParam(required = false) String name) throws IOException {
        File folder = new File(QUARTER_PATH);
        File[] files = folder.listFiles((dir, filename) -> filename.endsWith(".docx"));

        if (files == null || files.length == 0) {
            return ResponseEntity.notFound().build();
        }

        File fileToConvert;

        if (name != null && !name.isEmpty()) {
            // 用户选择了特定的文件
            fileToConvert = Arrays.stream(files)
                    .filter(file -> file.getName().equals(name))
                    .findFirst()
                    .orElse(null);
        } else {
            // 没有特定选择,找最新的文件
            Pattern pattern = Pattern.compile("(\\d{4})年(?:\\d{2})月-(\\d{4})年(?:\\d{2})月风光资源趋势预测\\.docx|" +
                    "(\\d{4})年(?:\\d{2})-(\\d{2})月风光资源趋势预测\\.docx");
            fileToConvert = Arrays.stream(files)
                    .filter(file -> pattern.matcher(file.getName()).matches())
                    .max(Comparator.comparingInt(file -> {
                        Matcher matcher = pattern.matcher(file.getName());
                        if (matcher.find()) {
                            if (matcher.group(1) != null) {
                                // 处理跨年文件名
                                int yearStart = Integer.parseInt(matcher.group(1));
                                int yearEnd = Integer.parseInt(matcher.group(2));
                                return yearEnd * 100 + (matcher.group(4) != null ? Integer.parseInt(matcher.group(4)) : 0);
                            } else if (matcher.group(3) != null) {
                                // 处理同一年文件名
                                int year = Integer.parseInt(matcher.group(3));
                                int monthEnd = Integer.parseInt(matcher.group(4));
                                return year * 100 + monthEnd;
                            }
                        }
                        return 0;
                    }))
                    .orElse(null);
        }

        if (fileToConvert == null) {
            return ResponseEntity.notFound().build();
        }

        // 文件转换为PDF的代码
        try (XWPFDocument document = new XWPFDocument(new FileInputStream(fileToConvert));
             ByteArrayOutputStream out = new ByteArrayOutputStream()) {

            PdfOptions options = PdfOptions.create();
            PdfConverter.getInstance().convert(document, out, options);

            byte[] pdfContent = out.toByteArray();
            ByteArrayResource resource = new ByteArrayResource(pdfContent);

            return ResponseEntity.ok()
                    .contentLength(pdfContent.length)
                    .header("Content-type", "application/pdf")
                    .body(resource);
        }
    }



    @GetMapping("/monthList")
    public List<String> listAllDocs() {
        File folder = new File(MONTH_PATH);
        // 修改为获取.docx文件
        File[] files = folder.listFiles((dir, name) -> name.endsWith(".docx"));

        if (files == null) {
            return Collections.emptyList();
        }

        // 返回文件名列表
        return Arrays.stream(files)
                .map(File::getName)
                .collect(Collectors.toList());
    }

    @GetMapping("/quarterList")
    public List<String> quarterList() {
        File folder = new File(QUARTER_PATH);
        // 修改为获取.docx文件
        File[] files = folder.listFiles((dir, name) -> name.endsWith(".docx"));

        if (files == null) {
            return Collections.emptyList();
        }

        // 返回文件名列表
        return Arrays.stream(files)
                .map(File::getName)
                .collect(Collectors.toList());
    }

}

前端代码html代码,使用两个按钮,点击后弹出模态框,在模态框中有iframe来展示pdf:

html 复制代码
<!-- 模态框(Modal) -->
<div class="modal fade" id="pdfModalMonth" tabindex="-1" role="dialog" aria-labelledby="pdfModalLabelMonth" aria-hidden="true">
    <div class="modal-dialog" style="max-width: 90%; width: auto;">
        <div class="modal-content">
            <div class="modal-header">
                <h4 class="modal-title" id="pdfModalLabelMonth">月度预测文档</h4>
                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                    <span aria-hidden="true">&times;</span>
                </button>
            </div>
            <div class="modal-body">
                <!-- 下拉框用于选择文档 -->
                <select id="MonthList" class="form-control">
                    <option value="">请选择文档</option>
                    <!-- 文档列表将在这里填充 -->
                </select>
                <iframe id="MonthViewer" style="width:100%; height:700px;"></iframe> <!-- 可以根据需要调整高度 -->
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
            </div>
        </div>
    </div>
</div>

<div class="modal fade" id="pdfModalQuarter" tabindex="-1" role="dialog" aria-labelledby="pdfModalLabelQuarter" aria-hidden="true">
    <div class="modal-dialog" style="max-width: 90%; width: auto;">
        <div class="modal-content">
            <div class="modal-header">
                <h4 class="modal-title" id="pdfModalLabelQuarter">月度预测文档</h4>
                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                    <span aria-hidden="true">&times;</span>
                </button>
            </div>
            <div class="modal-body">
                <!-- 下拉框用于选择文档 -->
                <select id="QuarterList" class="form-control">
                    <option value="">请选择文档</option>
                    <!-- 文档列表将在这里填充 -->
                </select>
                <iframe id="QuarterViewer" style="width:100%; height:700px;"></iframe> <!-- 可以根据需要调整高度 -->
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
            </div>
        </div>
    </div>
</div>
<a class="btn btn-sm btn-primary" id="loadMonth"  style="text-decoration: none;color: #ffffff">月度预测</a>
            <a class="btn btn-sm btn-primary" id="loadQuarter"  style="text-decoration: none;color: #ffffff">季度预测</a>

js代码:

javascript 复制代码
$(document).ready(function() {
    // 加载月度列表
    function loadMonthList() {
        $.get("/api/document/monthList", function(data) {
            // 清空现有的选项
            $("#MonthList").empty();
            $("#MonthList").append('<option value="">请选择文档</option>');

            // 假设文档名包含日期,例如 "2024年01月风光资源趋势预测.docx"
            // 对文档进行倒序排序
            data.sort(function(a, b) {
                // 转换为日期格式进行比较
                var dateA = new Date(a.split('年')[0], a.split('年')[1].split('月')[0] - 1);
                var dateB = new Date(b.split('年')[0], b.split('年')[1].split('月')[0] - 1);
                return dateB - dateA; // 从新到旧排序
            });

            // 添加新的选项
            data.forEach(function(doc) {
                $("#MonthList").append('<option value="' + doc + '">' + doc + '</option>');
            });
        });
    }

    // 当点击加载最新文档的按钮时
    $("#loadMonth").click(function() {
        loadMonthList(); // 调用函数加载文档列表
        // 加载最新的PDF文档
        $("#MonthViewer").attr("src", "/api/document/monthLatest");
        // 显示模态框
        $("#pdfModalMonth").modal("show");
    });

    // 当选择不同的文档时
    $("#MonthList").change(function() {
        var selectedFile = $(this).val();
        if(selectedFile) {
            $("#MonthViewer").attr("src", "/api/document/monthLatest?name=" + encodeURIComponent(selectedFile));
        }
    });


    // 加载季度列表
    function parseDateFromDocName(docName) {
        var year, month;
        var parts = docName.match(/(\d{4})年(\d{2})-(\d{2})月|(\d{4})年(\d{2})月-(\d{4})年(\d{2})月/);

        if (parts) {
            if (parts[1]) {
                // 格式是 "YYYY年MM-DD月"
                year = parseInt(parts[1], 10);
                month = parseInt(parts[2], 10); // 使用开始月份
            } else {
                // 格式是 "YYYY年MM月-YYYY年MM月"
                year = parseInt(parts[4], 10);
                month = parseInt(parts[5], 10); // 使用开始月份
            }
        }

        return new Date(year, month - 1); // JavaScript中的月份是从0开始的
    }

    function loadQuarterList() {
        $.get("/api/document/quarterList", function(data) {
            // 清空现有的选项
            $("#QuarterList").empty();
            $("#QuarterList").append('<option value="">请选择文档</option>');

            // 对文档进行倒序排序
            data.sort(function(a, b) {
                var dateA = parseDateFromDocName(a);
                var dateB = parseDateFromDocName(b);
                return dateB - dateA; // 从新到旧排序
            });

            // 添加新的选项
            data.forEach(function(doc) {
                $("#QuarterList").append('<option value="' + doc + '">' + doc + '</option>');
            });
        });
    }



    // 当点击加载最新文档的按钮时
    $("#loadQuarter").click(function() {
        loadQuarterList(); // 调用函数加载文档列表
        // 加载最新的PDF文档
        $("#QuarterViewer").attr("src", "/api/document/quarterLatest");
        // 显示模态框
        $("#pdfModalQuarter").modal("show");
    });

    // 当选择不同的文档时
    $("#QuarterList").change(function() {
        var selectedFile = $(this).val();
        if(selectedFile) {
            $("#QuarterViewer").attr("src", "/api/document/quarterLatest?name=" + encodeURIComponent(selectedFile));
        }
    });
});

4.遇到的问题

做完一切后,发现有些文档中的标题的中文没有显示出来,我就对比显示的文档和没显示的文档,发现是因为字体的原因,宋体是可以显示出来的,但是宋体(中文)显示不出来。把宋体(中文)改成宋体就可以显示。
但是这只是我windows系统上运行是没问题的,我把项目部署到服务器(centos)后,发现中文一点都展示不出来,这时候我就很纳闷,为什么在windows上能显示出来,linux上显示不出来,经过查阅资料,我发现是由于Linux上缺乏一些中文字体,例如宋体、仿宋等,这些字体是我文档中用到的字体,所以下一步我要将windows中的字体放在服务器上。

5.在Linux中安装宋体

在Linux系统中安装宋体(SimSun)字体,需要手动下载字体文件或从Windows系统中复制字体文件,然后将其安装到Linux系统中。宋体不包含在开源字体包中,因为它是微软的商业字体。下面是一般步骤:

  1. 从Windows复制:如果你有访问Windows系统的权限,可以从C:\Windows\Fonts目录找到simsun.ttc(宋体)和其他中文字体文件,并将其复制到你的Linux系统中。
  2. 在Linux系统上安装字体一般有以下几个步骤:
    1. 创建字体目录(如果尚不存在)
bash 复制代码
sudo mkdir -p /usr/share/fonts/chinese
  1. 将字体文件复制到创建的目录中,假设你已经将simsun.ttc字体文件复制到了Linux系统的某个位置(例如~/Downloads),运行以下命令将其移动到字体目录:
bash 复制代码
sudo cp ~/Downloads/simsun.ttc /usr/share/fonts/chinese/
  1. 更新字体缓存,安装完字体后,需要更新字体缓存,以便系统识别新安装的字体:
bash 复制代码
sudo fc-cache -fv
  1. 安装后,你可以使用fc-list命令确认字体是否已正确安装:
bash 复制代码
fc-list | grep "simsun"

至此,大功告成!

相关推荐
Dovis(誓平步青云)21 分钟前
《Linux CPU频率为何忽高忽低:cpufreq、idle状态与性能抖动教程》
linux·运维·服务器·spring boot·后端·生成对抗网络
复园电子2 小时前
企业级PDF批量盖章方案选型指南
数据库·pdf
碎碎念_49213 小时前
SpringBoot + Vue 前后端分离从 0 到 1 完整环境配置流程
vue.js·spring boot·后端
罗政14 小时前
PDF 批量合并工具:本地 AI 自动排序、识别正文日期与合同编号
数据库·pdf·php
Fuzio17 小时前
用 Spring Boot + Vue + Fuzio 构建现代 Java 桌面应用
vue.js·spring boot
乐观的Terry18 小时前
1、为什么要自己造发布系统
java·spring boot·后端·spring cloud·架构
Metaphor69218 小时前
使用 Python 添加、隐藏和删除 PDF 图层
python·pdf·图层
wtsolutions20 小时前
Word 文档 怎么批量加密 批量设置打开密码
word·加密
web行路人20 小时前
Spring Boot 第四周:集成 Spring Security 与 JWT 鉴权实践
spring boot·后端·spring
QQ_216962909621 小时前
【项目编号:project05966】SpringBoot/Vue 电影推荐系统:用户端推荐、电影资讯与后台数据管理全流程
vue.js·spring boot·后端