相关文章推荐
捣蛋的手术刀  ·  primeng table column ...·  8 月前    · 
逆袭的斑马  ·  Can‘t pickle ...·  1 年前    · 
豁达的帽子  ·  python ...·  1 年前    · 

SpringBoot+Freemark渲染html导出PDF

PDF 导出在需要将信息纸质化存档时会使用到。这里将介绍在 spring boot 框架下使用 Freemarker + iText 将 ftl 模板渲染成 html,然后导出 PDF 文件

Freemarker 官方文档: freemarker.apache.org/docs/index.…

Maven依赖

<dependency>
	<groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>html2pdf</artifactId>
    <version>4.0.3</version>
</dependency>
@GetMapping(value = "/export-pdf")
public ResponseEntity exportPDF(TopoDeviceQueryDto queryDto) {
    ResponseEntity response = null;
        try {
        HashMap<String, Object> mapData = Maps.newHashMap();
        mapData.put("topDeviceList", topoDeviceService.findByList(queryDto));
        String templateContent = HtmlUtils.getTemplateContent("TopoDevice.ftl", mapData);
        response = this.getDownloadResponse(HtmlUtils.html2Pdf(templateContent), "device info.pdf");
    } catch (Exception e) {
        log.error("error occurs when downloading file");
        response = ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
    return response;
@Slf4j
public class HtmlUtils {
     * @return
     * @throws Exception
    public static String getTemplateDirectory() {
        ClassLoader classLoader = HtmlUtils.class.getClassLoader();
        URL resource = classLoader.getResource("templates");
        try {
            return Objects.requireNonNull(resource).toURI().getPath();
        } catch (URISyntaxException e) {
            log.error("获取模板文件夹失败,{}", e);
        return null;
     * 获取模板内容
     * @param templateName 模板文件名
     * @param paramMap     模板参数
     * @return
     * @throws Exception
    public static String getTemplateContent(String templateName, Map<String, Object> paramMap) throws Exception {
        Configuration config = ApplicationContextUtil.getBean(FreeMarkerConfigurer.class).getConfiguration();
        config.setDefaultEncoding("UTF-8");
        Template template = config.getTemplate(templateName, "UTF-8");
        return FreeMarkerTemplateUtils.processTemplateIntoString(template, paramMap);
     * HTML 转 PDF
     * @param content html内容
     * @param outPath 输出pdf路径
     * @return 是否创建成功
    public static boolean html2Pdf(String content, String outPath) {
        try {
            ConverterProperties converterProperties = new ConverterProperties();
            converterProperties.setCharset("UTF-8");
            FontProvider fontProvider = new FontProvider();
            fontProvider.addSystemFonts();
            converterProperties.setFontProvider(fontProvider);
            HtmlConverter.convertToPdf(content, new FileOutputStream(outPath), converterProperties);
        } catch (Exception e) {
            log.error("生成模板内容失败,{}", e);
            return false;
        return true;
     * HTML 转 PDF
     * @param content html内容
     * @return PDF字节数组
    public static ByteArrayOutputStream html2Pdf(String content) {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        try {
            ConverterProperties converterProperties = new ConverterProperties();
            converterProperties.setCharset("UTF-8");
            FontProvider fontProvider = new FontProvider();
            fontProvider.addSystemFonts();
            converterProperties.setFontProvider(fontProvider);
            HtmlConverter.convertToPdf(content, outputStream, converterProperties);
        } catch (Exception e) {
            log.error("生成 PDF 失败,{}", e);
        return outputStream;
@Configuration
@AutoConfigureOrder(-1)
public class ApplicationContextUtil implements ApplicationContextAware {
    private static ApplicationContext applicationContext = null;
    public static Object getBeanByName(String beanName) {
        if (applicationContext == null) {
            return null;
        return applicationContext.getBean(beanName);
    public static <T> T getBean(Class<T> type) {
        return applicationContext.getBean(type);
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        ApplicationContextUtil.applicationContext = applicationContext;
<!DOCTYPE html>
<html lang="en">
    <meta charset="UFT-8">
    <title>网络拓扑结构表</title>
    <style>
        .vertical-line {
            height: 26px;
            border-right: 2px solid #d0d0d0;
            float: left;
            margin-top: -10px;
            margin-left: -15px;
        .horizontal-line {
            width: 15px;
            border-top: 2px solid #d0d0d0;
            float: left;
            margin-top: 16px;
            margin-left: -15px;
    </style>
</head>
<style type="text/css">
    table {
        border-collapse: collapse;
        margin: 0 auto;
        text-align: center;
    .table td,
    .table th {
        border: 1px solid #cad9ea;
        color: #666;
        height: 30px;
    .table thead th {
        background-color: #cce8eb;
        width: 100px;
    .table tr:nth-child(odd) {
        background: #fff;
    .table tr:nth-child(even) {
        background: #f5fafa;
    .tableB th {
        border: 1px solid #cad9ea;
        color: #666;
        height: 30px;
    .tableB thead th {
        background-color: #e7f1ef;
        width: 100px;
</style>
<!-- Table goes in the document BODY -->
    <table width="90%" class="table">
        <caption>
            <h2>网络拓扑结构表</h2>
        </caption>
        <thead>
            <th>设备名称</th>
            <th>符号</th>
            <th>端口</th>
            <th>地址</th>
            <th>网络中的单元</th>
        </thead>
        <tbody>
        <#if topDeviceList??>
            <#list topDeviceList as top_device>
                    <td><#if top_device.deviceName??>${top_device.deviceName}</#if></td>
                    <td><#if top_device.mark??>${top_device.mark}</#if></td>
                    <td><#if top_device.port??>${top_device.port}</#if></td>
                    <td><#if top_device.ip??>${top_device.ip}</#if></td>
                    <td><#if top_device.unit??>${top_device.unit}</#if></td>
            </#list>
        </tbody>
    </table>
</div>
</body>
</html>

解决Spring项目打成Jar包后Freemarker找不到模板的问题

freemarker在jar包中无法使用类加载器获取resourse目录下的templates文件,出现的问题代码如下:(本地测试正常,打包jar后无法获取模板)

	@GetMapping(value = "/export-pdf")
    public ResponseEntity exportPDF(TopoDeviceQueryDto queryDto) {
        ResponseEntity response = null;
        try {
            HashMap<String, Object> mapData = Maps.newHashMap();
            mapData.put("topDeviceList", topoDeviceService.findByList(queryDto));
            String templateContent = HtmlUtils.getTemplateContent("TopoDevice.ftl", mapData);
            response = this.getDownloadResponse(HtmlUtils.html2Pdf(templateContent), "device info.pdf");
        } catch (Exception e) {
            log.error("error occurs when downloading file");
            response = ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
        return response;
	public static String getTemplateDirectory() {
        ClassLoader classLoader = HtmlUtils.class.getClassLoader();
        URL resource = classLoader.getResource("templates");
        try {
            return Objects.requireNonNull(resource).toURI().getPath();
        } catch (URISyntaxException e) {
            log.error("获取模板文件夹失败,{}", e);
        return null;
     * 获取模板内容
     * @param templateDirectory 模板文件夹
     * @param templateName      模板文件名
     * @param paramMap          模板参数
     * @return
     * @throws Exception
    public static String getTemplateContent(String templateDirectory, String templateName, Map<String, Object> paramMap) throws Exception {
        Configuration configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
        try {
            configuration.setDirectoryForTemplateLoading(new File(templateDirectory));
        } catch (Exception e) {
            System.out.println("-- exception --");
        Writer out = new StringWriter();
        Template template = configuration.getTemplate(templateName, "UTF-8");
        template.process(paramMap, out);
        out.flush();
        out.close();
        return out.toString();
     * HTML 转 PDF
     * @param content html内容
     * @return PDF字节数组
    public static ByteArrayOutputStream html2Pdf(String content) {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        try {
            ConverterProperties converterProperties = new ConverterProperties();
            converterProperties.setCharset("UTF-8");
            FontProvider fontProvider = new FontProvider();
            fontProvider.addSystemFonts();
            converterProperties.setFontProvider(fontProvider);
            HtmlConverter.convertToPdf(content, outputStream, converterProperties);
        } catch (Exception e) {
            log.error("生成 PDF 失败,{}", e);
        return outputStream;

修改后的代码(打包jar后正常获取模板信息)

	@GetMapping(value = "/export-pdf")
    public ResponseEntity exportPDF(TopoDeviceQueryDto queryDto) {
        ResponseEntity response = null;
        try {
            HashMap<String, Object> mapData = Maps.newHashMap();
            mapData.put("topDeviceList", topoDeviceService.findByList(queryDto));
            String templateContent = HtmlUtils.getTemplateContent("TopoDevice.ftl", mapData);
            response = this.getDownloadResponse(HtmlUtils.html2Pdf(templateContent), "device info.pdf");
        } catch (Exception e) {
            log.error("error occurs when downloading file");
            response = ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
        return response;
     * 获取模板内容
     * @param templateName 模板文件名
     * @param paramMap     模板参数
     * @return
     * @throws Exception
    public static String getTemplateContent(String templateName, Map<String, Object> paramMap) throws Exception {
        Configuration config = ApplicationContextUtil.getBean(FreeMarkerConfigurer.class).getConfiguration();
        config.setDefaultEncoding("UTF-8");
        Template template = config.getTemplate(templateName, "UTF-8");
        return FreeMarkerTemplateUtils.processTemplateIntoString(template, paramMap);
	public static <T> T getBean(Class<T> type) {
        return applicationContext.getBean(type);
     * HTML 转 PDF
     * @param content html内容
     * @return PDF字节数组
    public static ByteArrayOutputStream html2Pdf(String content) {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        try {
            ConverterProperties converterProperties = new ConverterProperties();
            converterProperties.setCharset("UTF-8");
            FontProvider fontProvider = new FontProvider();
            fontProvider.addSystemFonts();
            converterProperties.setFontProvider(fontProvider);
            HtmlConverter.convertToPdf(content, outputStream, converterProperties);
        } catch (Exception e) {
            log.error("生成 PDF 失败,{}", e);
        return outputStream;

ps:.ftl模板文件放在 templates 目录下

总结:freemarker无法使用类加载器获取jar包中的resourse目录下的templates文件 解决办法:注入FreeMarkerConfigurer配置类,因为freemarker模板的默认目录就在resourse下的templates目录下, 使用freeMarkerConfigurer.getConfiguration().getTemplate("mailTemplate.ftl")可直接获取到对应的模板文件

解决Freemark导出Pdf部署Linux乱码问题

项目部署完之后发现Linux下个别字体丢失的问题,刚开始以为是编码问题,经排查是Linux系统中文字体缺失问题

第一步:拷贝Windows下系统的中文字体 Windows下字体的目录是在C:\Windows\Fonts

第二步:移动字体库到linux系统下的字体库文件夹/usr/share/fonts/下

第三步:让linux系统识别新的中文字体:终端输入:sudo fc-cache -fv