Skip to content

Spring Boot 导出PDF文件

Published:

SpringBoot导出PDF文件

需求描述

最近项目有一个需求,需要导出 PDF 文件,由于之前并没有做过这方面的需求,从网上花了很长时间才找到相关的资料。最后采用了aspose.words + freemarker的方式解决的问题,制作 freemarker 模板,将 freemarker 模板转为 word 文件。然后再使用 aspose.words 将 word 文件转为 PDF文件。一开始也尝试过使用 docx4j 将 word 文件转为 PDF,但转完正文中文乱码、还有其他各种格式问题,最后并没有采用。

所需依赖

由于 aspose.words 是破解版,在互联网仓库无法下载,需手动将 jar 包引入本地库,如果公司有私服的话也可以上传到自己公司的私服,引入本地库命令如下:

mvn install:install-file -DgroupId=com.aspose -DartifactId=aspose.words -Dversion=15.8 -Dpackaging=jar -Dfile=aspose-words-15.8.0-jdk16.jar
<!-- freemarker模板引擎,用于定义代码生成模板 -->
<dependency>
	<groupId>org.freemarker</groupId>
	<artifactId>freemarker</artifactId>
	<version>2.3.28</version>
</dependency>

<dependency>
	<groupId>com.aspose</groupId>
	<artifactId>aspose.words</artifactId>
	<version>15.8</version>
</dependency>

制作freemarker模板

在 word 中编写你想要的文档,将 word 文档另存为 xml 格式,将需要注入数据的地方修改为 ${xxx} 格式,并且记住你的字段名,如果有多条数据则添加 <#list userList as user></#list> 标签,userList 是 Map 里面的 key。

image-20210402142437815

image-20210402142525047

image-20210402142552739

编辑好之后另存为 .ftl 文件,这就是模板。可以存放到项目里的 resource 下的 templates 文件夹下

image-20210402142741541

word文档生成工具类

package com.cxhello.example.util;

import freemarker.template.Configuration;
import freemarker.template.Template;

import java.io.*;
import java.util.Map;

/**
 * @author cxhello
 * @date 2021/4/2
 */
public class WordUtil {

    private static final String ENCODING = "UTF-8";

    /**
     * 生成word文档
     * @param templateName
     * @param map
     * @return
     * @throws IOException
     */
    public static File generateWord(String templateName, Map map) throws IOException {
        Configuration configuration = getConfiguration();
        Template template = configuration.getTemplate(templateName);
        File file = createDoc(map, template);
        return file;
    }

    private static Configuration getConfiguration() {
        Configuration configuration = new Configuration(Configuration.VERSION_2_3_23);
        configuration.setDefaultEncoding(ENCODING);
        configuration.setClassForTemplateLoading(WordUtil.class, "/templates/");
        return configuration;
    }

    private static File createDoc(Map<?, ?> dataMap, Template template) {
        String name = "sellPlan.doc";
        File f = new File(name);
        Template t = template;
        try {
            // 这个地方不能使用FileWriter因为需要指定编码类型否则生成的Word文档会因为有无法识别的编码而无法打开
            Writer w = new OutputStreamWriter(new FileOutputStream(f), ENCODING);
            t.process(dataMap, w);
            w.close();
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new RuntimeException(ex);
        }
        return f;
    }

}

word转换PDF工具类

package com.cxhello.example.util;

import com.aspose.words.Document;
import com.aspose.words.License;
import com.aspose.words.SaveFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.URLEncoder;

/**
 * @author cxhello
 * @date 2021/4/2
 */
public class PdfUtil {

    private static final Logger logger = LoggerFactory.getLogger(PdfUtil.class);

    public static void covertDocToPdf(File file, String fileName, HttpServletResponse response) {
        response.setContentType("application/pdf");
        try {
            if (!isWordLicense()) {
                logger.error("License验证不通过");
            }
            fileName = fileName + ".pdf";
            response.setHeader("Content-Disposition", "attachment;filename="
                    .concat(String.valueOf(URLEncoder.encode(fileName, "UTF-8"))));
            FileInputStream fileInputStream = new FileInputStream(file);
            Document doc = new Document(fileInputStream);
            fileInputStream.close();
            doc.save(response.getOutputStream(), SaveFormat.PDF);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (file != null) {
                file.delete();
            }
        }
    }

    public static boolean isWordLicense() {
        boolean result = false;
        try {
            InputStream inputStream = PdfUtil.class.getClassLoader().getResourceAsStream("license.xml");
            License license = new License();
            license.setLicense(inputStream);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

}

业务类

package com.cxhello.example.controller;

import com.alibaba.fastjson.JSON;
import com.cxhello.example.entity.StandardReportData;
import com.cxhello.example.util.PdfUtil;
import com.cxhello.example.util.WordUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @author cxhello
 * @date 2021/4/2
 */
@Controller
public class ExportController {

    private static final Logger logger = LoggerFactory.getLogger(ExportController.class);

    @GetMapping("/exportPdf")
    public void exportPdf(HttpServletResponse response) {
        Map<String, Object> map = new HashMap<>();
        String waterName = "测试水厂";
        map.put("waterName", waterName);
        map.put("time", "2021-03-23");
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String date = simpleDateFormat.format(new Date());
        map.put("date", date);
        Resource resource = new ClassPathResource("test.json");
        InputStream is = null;
        InputStreamReader inputStreamReader = null;
        BufferedReader bufferedReader = null;
        try {
            is = resource.getInputStream();
            inputStreamReader = new InputStreamReader(is);
            bufferedReader = new BufferedReader(inputStreamReader);
            StringBuilder str = new StringBuilder();
            String s;
            while ((s = bufferedReader.readLine()) != null) {
                str.append(s);
            }
            StandardReportData standardReportData = JSON.parseObject(str.toString(), StandardReportData.class);
            map.put("standardReportData", standardReportData);
            File file = WordUtil.generateWord("template.ftl", map);
            PdfUtil.covertDocToPdfSecond(file, waterName + "标准报告", response);
        } catch (IOException e) {
            logger.error("System error", e);
        } finally {
            try {
                is.close();
                inputStreamReader.close();
                bufferedReader.close();
            } catch (IOException e) {
                logger.error("System error", e);
            }
        }
    }

}

Linux导出中文乱码问题

在 Linux 服务器使用 aspose.word 转换 word 文件为 pdf 的时候显示中文乱码,但是在 windows 服务器上使用可以正常转换。通过查资料分析后确认是由于 Linux 服务器缺少对应的字库导致文件转换出现乱码的。

安装字库,将 windows 机器的 C:\Windows\fonts 目录下的全部文件拷贝到生产服务器字体安装目录下,然后执行以下命令更新字体缓存。

# 查看linux目前的所有字体
fc-list
# 查看Linux目前的所有中文字体
fc-list :lang=zh
# 拷贝到linux下的字体目录
mkdir /usr/share/fonts/windows
cp /local/src/fonts/* /usr/share/fonts/windows
# 执行安装字体命令
cd /usr/share/fonts
sudo mkfontscale
sudo mkfontdir 
sudo fc-cache -fv
# 执行命令让字体生效
source /etc/profile
# 如果安装失败,可以考虑修改字体权限
chmod 755 *.ttf

大坑

如果你的项目是用 docker 部署的,记得一定要把所需要的字体拷贝到 docker 容器的 /usr/share/fonts 中,我在 Linux 物理机安装了中文字体,发现导出还是中文乱码,我还一直在找问题,以为是物理机的问题。最后才发现我的项目是在 docker 容器部署的。😂😂😂

代码地址

https://github.com/cxhello/springboot-pdf

参考文章

https://blog.csdn.net/tomcat_zhu/article/details/89397044

https://blog.csdn.net/hunwanjie/article/details/97135633

https://blog.csdn.net/weixin_44723536/article/details/91445959?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control&dist_request_id=1328741.37842.16169854640636425&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control

https://cloud.tencent.com/developer/article/1639923

https://www.jianshu.com/p/86716c7122ef

https://www.cnblogs.com/stsinghua/p/13558544.html