Springboot导出pdf文件(idea、jar包完美运行)

思路如下:

1、制作模板(建议写静态HTML,样式会好看些):按照导出的格式弄一个word文件,然后通过转换工具转成HTML,但是一般转换的会有一些问题,需要删除一些样式字体等

推荐一个在线转换(百度随便搜一下):在线WORD转HTMLhttps://cn.office-converter.com/Word-to-HTML-Converter​​​​

在样式中添加字体样式(重要),这是不乱码的关键一步

<style type="text/css">
        body {
            font-family: "simsun";
        }
</style>

2、准备字体:simsun.tcc,在windows目录也有拷一个就行:C:\Windows\Fonts

3、在项目中导入相关的依赖,主要是Freemarker、itextpdf

        <properties>
            <flying-saucer-pdf.version>9.1.16</flying-saucer-pdf.version>
            <itextpdf.version>5.5.13</itextpdf.version>
        </properties>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>
        <dependency>
            <groupId>org.xhtmlrenderer</groupId>
            <artifactId>flying-saucer-pdf</artifactId>
            <version>${flying-saucer-pdf.version}</version>
        </dependency>
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itextpdf</artifactId>
            <version>${itextpdf.version}</version>
        </dependency>
        <dependency>
            <groupId>com.itextpdf.tool</groupId>
            <artifactId>xmlworker</artifactId>
            <version>${itextpdf.version}</version>
        </dependency>>

此处为核心:maven打包会压缩字体,需要排除一下

<plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <version>2.7</version>
                <configuration>
                    <nonFilteredFileExtensions>
                        <nonFilteredFileExtension>ttc</nonFilteredFileExtension>
                        <nonFilteredFileExtension>html</nonFilteredFileExtension>
                    </nonFilteredFileExtensions>
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>org.apache.maven.shared</groupId>
                        <artifactId>maven-filtering</artifactId>
                        <version>1.3</version>
                    </dependency>
                </dependencies>
            </plugin>

下面贴一下核心代码:

渲染模板、渲染PDF

package com.yx.exportpdfdemo.utils;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.PageSize;
import com.itextpdf.text.pdf.PdfWriter;
import com.itextpdf.tool.xml.XMLWorkerFontProvider;
import com.itextpdf.tool.xml.XMLWorkerHelper;
import freemarker.template.Template;
import org.springframework.core.io.ClassPathResource;

import java.io.*;
import java.nio.charset.Charset;
import java.util.Map;


/**
 * @Author: yaoxin
 * @Description: pdf 导出工具类
 * @createDate: 2022/3/26 10:20
 **/
public class PDFUtil {
    /**
     * 字体缓存目录
     */
    private static String FONT_CACHE_DIR = null;

    /**
     * freemarker渲染html
     *
     * @param data 数据
     * @return
     */
    public static String freeMarkerRender(Map<String, Object> data, Template template) {
        Writer out = new StringWriter();
        try {
            template.setOutputEncoding("UTF-8");
            // 合并数据模型与模板
            //将合并后的数据和模板写入到流中,这里使用的字符流
            template.process(data, out);
            out.flush();
            return out.toString();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                out.close();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
        return null;
    }

    /**
     * 创建pdf文件并渲染pdf
     *
     * @param content pdf内容
     * @param tempDir pdf生成目录
     * @param font    字体resource路径
     * @throws IOException
     * @throws DocumentException
     */
    public static void createPdf(String content, String tempDir, String font) throws IOException, DocumentException {
        Document document = new Document(PageSize.A4, 36.0F, 36.0F, 60.0F, 36.0F);
        PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(tempDir));
        document.open();
        XMLWorkerFontProvider fontImp = new XMLWorkerFontProvider(XMLWorkerFontProvider.DONTLOOKFORFONTS);

        /**
         * 此处从resource目录下获取字体并拷贝到临时目录,并缓存目录
         * 因为打成jar包后无法获取直接获取文件,最好是拷贝到服务器写绝对目录
         */
        if (StrUtil.isBlankOrUndefined(FONT_CACHE_DIR)) {
            File fontTemp = new File(System.getProperty("java.io.tmpdir") + File.separator + "simsun.ttc");
            if (!fontTemp.exists()) {
                fontTemp.createNewFile();
            }
            ClassPathResource simResource = new ClassPathResource(font);
            FileUtil.writeFromStream(simResource.getInputStream(), fontTemp);
            FONT_CACHE_DIR = fontTemp.getPath();
        }
        fontImp.register(FONT_CACHE_DIR);
        XMLWorkerHelper.getInstance().parseXHtml(writer, document,
                new ByteArrayInputStream(content.getBytes()), null, Charset.forName("UTF-8"), fontImp);
        document.close();
        writer.close();
    }
}

Controller:

package com.yx.exportpdfdemo.controller;

import cn.hutool.json.JSONUtil;
import com.yx.exportpdfdemo.config.PDFExportConfig;
import com.yx.exportpdfdemo.utils.PDFUtil;
import freemarker.template.Template;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;

import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author: yaoxin
 * @Description:
 * @createDate: 2022/3/28 17:23
 **/

@RestController
@AllArgsConstructor
public class UserController {

    private final FreeMarkerConfigurer freeMarkerConfigurer;

    private final PDFExportConfig pdfExportConfig;
    /**
     * 导出文件名
     */
    private static String EXPORT_NAME = "export";
    /**
     * 导出后缀
     */
    private static String EXPORT_SUFFIX = "pdf";

    /**
     * 测试导出pdf
     *
     * @param response
     */
    @SneakyThrows
    @GetMapping(value = "/export")
    public void domesticViolenceSee(HttpServletResponse response) {

        //添加测试数据
        Map<String, Object> data = new HashMap();
        data.put("username", "法外狂徒张三");
        data.put("age", "24");
        //根据配置类拿到模板名称,再通过FreeMarkerConfigurer获取Template对象
        Template template = freeMarkerConfigurer.getConfiguration().getTemplate(pdfExportConfig.getUserFtl());
        String content = PDFUtil.freeMarkerRender(data, template);
        File exportFile = File.createTempFile(EXPORT_NAME, EXPORT_SUFFIX);
        PDFUtil.createPdf(content, exportFile.getPath(), pdfExportConfig.getFont());
        FileInputStream fin;
        try {
            fin = new FileInputStream(exportFile);
            OutputStream output = response.getOutputStream();
            byte[] buf = new byte[1024];
            int r;
            response.setContentType("application/pdf;charset=GB2312");
            while ((r = fin.read(buf, 0, buf.length)) != -1) {
                output.write(buf, 0, r);
            }
            fin.close();
            output.close();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            exportFile.delete();
        }
    }
}

配置类:

package com.yx.exportpdfdemo.config;

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

/**
 * 配置类
 */
@Configuration
@Data
public class PDFExportConfig {

    /**
     * 字体目录
     */
    @Value("${font}")
    private String font;

    /**
     * 线索报告——查看 模板名称
     */
    @Value("${user-ftl}")
    private String userFtl;


}

HTML模板:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <style type="text/css">
        body {
            font-family: "simsun";
        }
        table {
            border-collapse: collapse;
            border-spacing: 0;
            empxy-cells: show
        }

        td, th {
            vertical-align: top;
            font-size: 12px;
        }

        h1, h2, h3, h4, h5, h6 {
            clear: both;
        }

        ol, ul {
            margin: 0;
            padding: 0;
        }

        li {
            list-style: none;
            margin: 0;
            padding: 0;
        }

        /* "li span.odfLiEnd" - IE 7 issue*/
        li span. {
            clear: both;
            line-height: 0;
            width: 0;
            height: 0;
            margin: 0;
            padding: 0;
        }

        span.footnodeNumber {
            padding-right: 1em;
        }

        span.annotation_style_by_filter {
            font-size: 95%;
            background-color: #fff000;
            margin: 0;
            border: 0;
            padding: 0;
        }

        span.heading_numbering {
            margin-right: 0.8rem;
        }

        * {
            margin: 0;
        }

        .P1 {
            font-size: 10.5px;
            margin-bottom: 0in;
            margin-top: 0in;
            text-align: justify ! important;

            writing-mode: horizontal-tb;
            direction: ltr;
        }

        .Standard {
            font-size: 10.5px;

            writing-mode: horizontal-tb;
            direction: ltr;
            margin-top: 0in;
            margin-bottom: 0in;
            text-align: justify ! important;
        }

        .Table1 {
            width: 5.7611in;
            margin-left: 0.0035in;
            margin-top: 0in;
            margin-bottom: 0in;
            margin-right: auto;
            writing-mode: horizontal-tb;
            direction: ltr;
        }

        .Table1_A1 {
            padding-left: 0.075in;
            padding-right: 0.075in;
            padding-top: 0in;
            padding-bottom: 0in;
            border-width: 0.0176cm;
            border-style: solid;
            border-color: #000000;
            writing-mode: horizontal-tb;
            direction: ltr;
        }


    </style>
</head>
<body dir="ltr" style="max-width:8.2681in;margin-top:1in; margin-bottom:1in; margin-left:1.25in; margin-right:1.25in; ">
<table border="0" cellspacing="0" cellpadding="0" class="Table1">
    <colgroup>
        <col width="320"/>
        <col width="320"/>
    </colgroup>
    <p>测试导出PDF</p>
    <tr class="Table11">
        <td style="text-align:left;width:2.8806in; " class="Table1_A1"><p class="P1">姓名</p></td>
        <td style="text-align:left;width:2.8799in; " class="Table1_A1"><p class="P1">${username}</p></td>
    </tr>
    <tr class="Table11">
        <td style="text-align:left;width:2.8806in; " class="Table1_A1"><p class="P1">年龄</p></td>
        <td style="text-align:left;width:2.8799in; " class="Table1_A1"><p class="P1">${age}</p></td>
    </tr>
</table>
<p class="Standard"></p></body>
</html>

YML:

spring:
  application:
    name: demo

  freemarker:
    enabled: true
    content-type: text/html
    suffix: .html  #后缀名
    charset: UTF-8 #编码格式
    template-loader-path: classpath:/templates/ #此为freemarker默认配置
server:
  port: 8090

#字体
font: simsun.ttc
user-ftl: user-ftl.html

此为导出结果:

DEMO代码