springboot+freemarker生成pdf

265 阅读3分钟

简单介绍

概述

FreeMarker 是一款 模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页,电子邮件,配置文件,源代码等)的通用工具。 它不是面向最终用户的,而是一个Java类库,是一款程序员可以嵌入他们所开发产品的组件

适用场景

  • **模板化文档生成:**FreeMarker 是一个模板引擎,适用于生成具有固定格式的文档,比如发票、报告或合同。这些文档的结构和格式是固定的,但内容可以动态变化。
  • **内容动态填充:**使用 FreeMarker,你可以创建 Word 文档模板,并在运行时将数据填充到模板中,生成最终的 Word 文件。例如,生成个性化的信件或文档报告。
  • **简单文档生成:**适用于文档结构较为简单的场景,比如只包含文本和基本格式的文档。

准备工作

开发环境

正式开始之前,依然给出本文所基于的环境,避免环境问题可能给大家带来的影响。

  • JDK 17(理论上推荐不低于 1.8 版本)
  • IDEA INTELLIJ
  • SpringBoot 2.x

添加 FreeMarker 依赖

<!--freemarker模板依赖-->
<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>

添加 FreeMarker 相关配置

添加依赖后,我们需要在项目配置文件 application.yml 中添加 FreeMarker 的相关配置。

spring:
  freemarker:
#    模板路径
    template-loader-path: template-loader-path: classpath:/static/templates/
#    模板后缀名
    suffix: .ftl
#    页面编码
    charset: utf-8
#    页面缓存
    cache: false
#    文档类型
    content-type: text/html

代码实现

工具类和模板

工具类

import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.URLEncoder;
import java.util.Map;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import com.itextpdf.html2pdf.ConverterProperties;
import com.itextpdf.html2pdf.HtmlConverter;
import com.itextpdf.layout.font.FontProvider;
import lombok.extern.slf4j.Slf4j;
import freemarker.template.Template;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;

@Slf4j
@Component
public class ExportWordUtil {
	@Autowired
	private FreeMarkerConfigurer freeMarkerConfigurer;
	private String encoding="utf-8";

	/**
	 * 获取模板
	 *
	 * @param name
	 * @return
	 * @throws Exception
	 */
	public Template getTemplate(String name) throws Exception {
		return freeMarkerConfigurer.getConfiguration().getTemplate(name);
	}


    	/**
	 * 导出pdf文档到客户端
	 *
	 * @param response
	 * @param content
	 * @param fileName
	 * @throws Exception
	 */
	public void exportPdfToClient(HttpServletResponse response, String content, String fileName)
			throws Exception {
		response.reset();
		response.setCharacterEncoding("UTF-8");
		response.setContentType("application/octet-stream");
//		response.setContentType("application/pdf");
		response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8"));
		// 把本地文件发送给客户端
		ServletOutputStream outputStream = response.getOutputStream();
		ConverterProperties converterProperties = new ConverterProperties();
		converterProperties.setCharset("utf-8");
		FontProvider fontProvider = new FontProvider();
		fontProvider.addSystemFonts();
		converterProperties.setFontProvider(fontProvider);
		HtmlConverter.convertToPdf(content,outputStream,converterProperties);

	}

	/**
	 * 导出pdf文档项目根目录下
	 *
	 * @param content
	 * @param fileName
	 * @throws Exception
	 */
	public void exportPdfToProjectRoot( String content, String fileName)
			throws Exception {
		File file = new File(fileName);
		FileOutputStream outputStream = new FileOutputStream(file);
		ConverterProperties converterProperties = new ConverterProperties();
		converterProperties.setCharset("utf-8");
		FontProvider fontProvider = new FontProvider();
		fontProvider.addSystemFonts();
		converterProperties.setFontProvider(fontProvider);
		HtmlConverter.convertToPdf(content,outputStream,converterProperties);

	}

}
import com.itextpdf.html2pdf.ConverterProperties;
import com.itextpdf.html2pdf.HtmlConverter;
import com.itextpdf.layout.font.FontProvider;
import freemarker.template.Configuration;
import freemarker.template.Template;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Map;
import java.util.Objects;

@Slf4j
@Component
public class HtmlUtils {
    @Autowired
    private FreeMarkerConfigurer freeMarkerConfigurer;

    /**
     * @return
     * @throws Exception
     */
    public  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 String getTemplateContent(String templateName, Map<String, Object> paramMap) throws Exception {
        Configuration config = freeMarkerConfigurer.getConfiguration();
        Template template = config.getTemplate(templateName);
        return FreeMarkerTemplateUtils.processTemplateIntoString(template, paramMap);
    }
}

模板

hello,${name}!

功能实现

导出到项目根目录

controller代码
import com.example.freemarker.util.ExportWordUtil;
import com.example.freemarker.util.HtmlUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDate;
import java.util.HashMap;
import java.util.Map;


/**
 * FreeMarker测试案例
 *
 * @PACKAGE_NAME: com.example.freemarker.controller
 * @author: zqy
 * @DATE: 2024/8/30 15:47
 */
@RestController
@RequestMapping("/freemarker")
@Slf4j
public class HelloController {

    @Autowired
    private ExportWordUtil exportWordUtil;

    @Autowired
    private HtmlUtils htmlUtils;

    @GetMapping("/pdf/export/to-project-root")
    public void exportPdfToProjectRoot(HttpServletRequest request, HttpServletResponse response) throws Exception {

        String fileName = "test.pdf"; // 文件名称
        Map<String, Object> dataMap = new HashMap<>();
        dataMap.put("name", "freemarker导出pdf"); // 设置要导出的数据,这里的title要和word模板中保持一致
        String content = htmlUtils.getTemplateContent("test.ftl", dataMap);
        exportWordUtil.exportPdfToProjectRoot(content, fileName);
    }
}

导出到客户端

controller代码
import com.example.freemarker.util.ExportWordUtil;
import com.example.freemarker.util.HtmlUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDate;
import java.util.HashMap;
import java.util.Map;


/**
 * FreeMarker测试案例
 *
 * @PACKAGE_NAME: com.example.freemarker.controller
 * @author: zqy
 * @DATE: 2024/8/30 15:47
 */
@RestController
@RequestMapping("/freemarker")
@Slf4j
public class HelloController {

    @Autowired
    private ExportWordUtil exportWordUtil;

    @Autowired
    private HtmlUtils htmlUtils;

    @GetMapping("/pdf/export/to-client")
    public void exportPdfToClient(HttpServletRequest request, HttpServletResponse response) throws Exception {

        String fileName = "test.pdf"; // 文件名称
        Map<String, Object> dataMap = new HashMap<>();
        dataMap.put("name", "freemarker导出pdf"); // 设置要导出的数据,这里的title要和word模板中保持一致
        String content = htmlUtils.getTemplateContent("test.ftl", dataMap);
        exportWordUtil.exportPdfToClient(response,content, fileName);
    }
}