SpringBoot+Freemark渲染html导出PDF

SpringBoot+Freemark渲染html导出PDF

摘要

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

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

Maven依赖


    org.springframework.boot
    spring-boot-starter-freemarker


    com.itextpdf
    html2pdf
    4.0.3

核心代码

@GetMapping(value = "/export-pdf")
public ResponseEntity exportPDF(TopoDeviceQueryDto queryDto) {
    ResponseEntity response = null;
        try {
        HashMap 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 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 getBean(Class type) {
        return applicationContext.getBean(type);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        ApplicationContextUtil.applicationContext = applicationContext;
    }


}



    
    网络拓扑结构表
    







<#if topDeviceList??> <#list topDeviceList as top_device>

网络拓扑结构表

设备名称 符号 端口 地址 网络中的单元
<#if top_device.deviceName??>${top_device.deviceName} <#if top_device.mark??>${top_device.mark} <#if top_device.port??>${top_device.port} <#if top_device.ip??>${top_device.ip} <#if top_device.unit??>${top_device.unit}
image-20230324111612922.png

踩坑记录

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

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

    @GetMapping(value = "/export-pdf")
    public ResponseEntity exportPDF(TopoDeviceQueryDto queryDto) {
        ResponseEntity response = null;
        try {
            HashMap 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 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 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 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 getBean(Class 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

你可能感兴趣的:(SpringBoot+Freemark渲染html导出PDF)