Java 使用freemarker、wkhtmltopdf根据自定义ftl模板导出html以及pdf

这段时间要搞一个导出pdf功能,刚开始想着用模板导出,结果发现不符合要求,因为我要动态的导出pdf,如果用一个pdf模板导出的话就不太灵活,因为你的模板是什么样的你导出的pdf也就是什么样的,所以我费了千辛万苦找到了一个解决办法,利用freemarker的ftl模板导出成html然后通过html导出相应的pdf,因为html里面自己想怎么写就怎么写,这样不就是非常完美了吗,想想就很激动,话不多说,下面我把相应的jar、模板、工具类以及测试类都粘出来了,有兴趣的小伙伴可以搞搞。

我这里用到的是SpringBoot项目演示的

一、先看一下效果:

(把下面的所有文件都弄到自己的项目中,然后运行第五个HtmlToPdfMain.java中的runMain方法就行了)

1. 控制台输出的:

控制台输出的

2. 生成的文件:

在这里插入图片描述

3. pdf内容:

(我这里弄得是一个表格数据,如果有别的需求可以修改model.ftl模板来响应的时间具体页面)

在这里插入图片描述

二、准备工作

1.先在网上下个wkhtmltopdf,地址是:点击下载(如果不想在官网上下载的话、随便找个下载就行)
2.然后安装
3.记住你的安装路径。这个地址后面会用到,在HtmlToPdf.java中的toPdfTool就是你的安装路径拼上你安装的应用程序

安装工作就到这里了,下面来看具体的实现

三、项目结构如下:

在这里插入图片描述

四、具体实现代码如下:

1. 用到的jar(主要的就是这几个)

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.30</version>
        </dependency>
        <dependency>
            <groupId>org.apache.ant</groupId>
            <artifactId>ant</artifactId>
            <version>1.10.9</version>
        </dependency>
        <!--PDF-->
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itextpdf</artifactId>
            <version>5.4.3</version>
        </dependency>

        <!--单元测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.1</version>
            <scope>compile</scope>
        </dependency>
        <!--单元测试的时候获取request-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.2.8.RELEASE</version>
        </dependency>

2. 将模板生成html (HtmlUtil.java)

package com.example.demo.pro;


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

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

/**
 * 使用.ftl模板 生成Html工具类
 */
public class HtmlUtil {
    /**
     * @param modelpath 模板路径 不包含模板名称
     * @param template 模板名称
     * @param dataMap 数据
     * @param descPath 临时路径
     * @param fileName 临时文件名称
     * @throws IOException
     * @throws TemplateException
     */
    public static void createHtml(String modelpath,String template, Map dataMap,String descPath,String fileName) throws IOException, TemplateException {
        Configuration configuration=new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
        //如果你用的是版本低的freemarker 可以用地下的这个创建Configuration
        //Configuration configuration=new Configuration();

        configuration.setDefaultEncoding("utf-8");

        //如果你的模板放到了静态资源下的话就用这个 传入相应的路径就行
        configuration.setDirectoryForTemplateLoading(new File(modelpath));
        //如果模板和HtmlUtil这了类放在同一个文件夹下的话用这个
        //configuration.setClassForTemplateLoading(HtmlUtil.class,"");

        Template t = configuration.getTemplate(template);
        configuration.setClassicCompatible(true);
        File file = new File(descPath+File.separator+fileName);
        String parent = file.getParent();
        File dir = new File(parent);
        if (parent!=null&&!dir.exists()){
            dir.mkdirs();
        }
        boolean exists = file.exists();
        if (!exists){
            file.createNewFile();
        }
        Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file),"UTF-8"));
        t.process(dataMap, out);
        out.flush();
        out.close();
    }
}

3. HtmlToPdfInterceptor.java

package com.example.demo.pro;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

public class HtmlToPdfInterceptor extends Thread {
    private InputStream is;


    public HtmlToPdfInterceptor(InputStream is) {
        // TODO Auto-generated constructor stub
        this.is = is;
    }

    /**
     * 起一个线程执行流的结果 可有可无 想看日志的话可以要
     */
    public void run(){
        try{
            InputStreamReader isr = new InputStreamReader(is, "utf-8");
            BufferedReader br = new BufferedReader(isr);
            String line = null;
            while ((line = br.readLine()) != null) {
                System.out.println("-------*******-------"+line.toString()); //输出内容
            }
        }catch (IOException e){
            e.printStackTrace();
        }
    }

}



4. Html转pdf (HtmlToPdf.java)

package com.example.demo.pro;


import java.io.File;
import java.io.IOException;

/**
 * html转pdf
 */
public class HtmlToPdf {
	// wkhtmltopdf在系统中安装的路径 第一个wkhtmltopdf是安装的路径 后面是安装后文件中的exe
	private static  String toPdfTool = "D:\\Exe\\wkhtmltopdf\\bin\\wkhtmltopdf.exe";

	/**
	 * html转pdf
	 *
	 * @param srcPath
	 *            html路径,可以是硬盘上的路径,也可以是网络路径
	 * @param destPath
	 *            pdf保存路径
	 * @return 转换成功返回true
	 */
	public static boolean convert(String srcPath, String destPath) throws IOException {
		File file = new File(destPath);
		File parent = file.getParentFile();
		// 如果pdf保存路径不存在,则创建路径
		if (!parent.exists()) {
			parent.mkdirs();
		}
		if (!file.exists()){
			file.createNewFile();
		}
		StringBuilder cmd = new StringBuilder();
		if (System.getProperty("os.name").indexOf("Windows") == -1) {
			// 非windows 系统
			toPdfTool = "/inco/usr/local/bin/wkhtmltopdf";
		}
		cmd.append(toPdfTool);
		cmd.append(" ");
		cmd.append("  --header-line");// 页眉下面的线
		cmd.append(" --header-center Pdf标头 ");//页眉中间内容
		cmd.append("  --margin-top 3cm ");// 设置页面上边距 (default 10mm)
		//cmd.append(" --header-html  file:///" + "https://blog.csdn.net/x6582026/article/details/53835835");// (添加一个HTML页眉,后面是网址)
		cmd.append(" --header-spacing 5 ");// (设置页眉和内容的距离,默认0)
		cmd.append(" --footer-center 第[page]页/共[topage]页");//设置在中心位置的页脚内容
		//cmd.append(" --footer-html  file:///" + "https://blog.csdn.net/x6582026/article/details/53835835");// (添加一个HTML页脚,后面是网址)
		cmd.append(" --footer-line");// * 显示一条线在页脚内容上)
		cmd.append(" --footer-spacing 5 ");// (设置页脚和内容的距离)
		File file1 = new File(srcPath);
		cmd.append(file1.getAbsolutePath());
		cmd.append(" ");
		String absolutePath = file.getAbsolutePath();
		cmd.append(absolutePath);
		boolean result = true;
		try {
			Process proc = Runtime.getRuntime().exec(cmd.toString());
			//起一个线程执行流的结果 可有可无 想看日志的话可以要 不想的话可以删除当前行的下面四行
			HtmlToPdfInterceptor error = new HtmlToPdfInterceptor(proc.getErrorStream());
			HtmlToPdfInterceptor output = new HtmlToPdfInterceptor(proc.getInputStream());
			error.start();
			output.start();
			proc.waitFor();
		} catch (Exception e) {
			result = false;
			e.printStackTrace();
		}

		return result;
	}
}

5. 测试类(HtmlToPdfMain.java)

package com.example.demo.pro;

import freemarker.template.TemplateException;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class HtmlToPdfMain {

    //声明request变量
    private MockHttpServletRequest request = new MockHttpServletRequest();

	//run
    @Test
    public void runMain() throws IOException, TemplateException {
        //创建request对象并设置字符编码
        request.setCharacterEncoding("UTF-8");
		//如果是linux系统的话将\\改为File.separator来代替
        //生成html
        //第一个参数 模板路径,  第二个参数 模板名称, 第三个参数 数据源, 第四个参数 保存路径, 第五个参数 保存名称
        HtmlUtil.createHtml(ToPath(request),"model.ftl",DataMap(),"E:\\","model.html");
        //将html转换为Pdf
        HtmlToPdf.convert("E:\\model.html","E:\\model.pdf");
    }




    /**
     * 获取路径
     * @param request
     * @return
     */
    public static String ToPath(MockHttpServletRequest request){
        //path1 根据class的地址获取项目编译后的路径
        //如果这种获取的方法在linux环境上获取的地址不对,那就使用request的方法获取路径 具体写法请看path
        String path = request.getServletContext().getRealPath("/templates/");
        System.out.println("Request获取Path = " + path);

        String path1 = (String.valueOf(HtmlToPdfMain.class.getResource("/templates/"))).replaceAll("file:/", "").replaceAll("%20", " ").trim();
        System.out.println("类名获取Path1 = " + path1);

        String path2 = (String.valueOf(HtmlToPdfMain.class.getResource(""))).replaceAll("file:/", "").replaceAll("%20", " ").trim();
        System.out.println("获取当前类的所在路径Path2 = " + path2);

        String path3 = (String.valueOf(HtmlToPdfMain.class.getResource("/"))).replaceAll("file:/", "").replaceAll("%20", " ").trim();
        System.out.println("获取当前类的所在路径Path3 = " + path3);

        return path;
    }

    /**
     * 数据源
     * @return
     */
    public static Map DataMap(){
        Map map = new HashMap();
        map.put("jsxm", "甄士隐 ");
        map.put("ssyx", "北京师范学院 ");
        map.put("sksj", "2020-11-08 4444 ");

        List<Map> user = new ArrayList<>();
        Map a = new ConcurrentHashMap();
        a.put("tmlx","教师教学状态(80分)");
        a.put("tmmc","思政育人(10分)");
        a.put("tmxx","A.在教学");
        a.put("tmfz","10");//分数
        a.put("sfxz","0");//0是没有选着 1是选着了
        a.put("tmlxkhsl","4");//题目类型跨行数量
        a.put("tmmckhsl","2");//题目名称跨行数量
        user.add(a);
        a = new ConcurrentHashMap();
        a.put("tmlx","教师教学状态(80分)");
        a.put("tmmc","思政育人(10分)");
        a.put("tmxx","B.在教学");
        a.put("tmfz","8");
        a.put("sfxz","1");
        a.put("tmlxkhsl","4");//题目类型跨行数量
        a.put("tmmckhsl","2");//题目名称跨行数量
        user.add(a);
        a = new ConcurrentHashMap();
        a.put("tmlx","教师教学状态(80分)");
        a.put("tmmc","精神状态(10分)");
        a.put("tmxx","A.精神饱满");
        a.put("tmfz","10");
        a.put("sfxz","0");
        a.put("tmlxkhsl","4");//题目类型跨行数量
        a.put("tmmckhsl","2");//题目名称跨行数量
        user.add(a);
        a = new ConcurrentHashMap();
        a.put("tmlx","教师教学状态(80分)");
        a.put("tmmc","精神状态(10分)");
        a.put("tmxx","B.精神饱满");
        a.put("tmfz","8");
        a.put("sfxz","1");
        a.put("tmlxkhsl","4");//题目类型跨行数量
        a.put("tmmckhsl","2");//题目名称跨行数量
        user.add(a);
        map.put("userList",user);
        return map;
    }
}

6. ftl模板(model.ftl)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        *{
            margin: 0;
            padding: 0;
        }
    </style>
</head>
<body style="text-align: center;">
<#--   这里些相应的html就行-->
<#--   这里我把freemarker经常用到的几个标签写在了下面-->

<#--assign 类似 c:set 一样 就是初始化一个变量-->
<#--list 类似 c:forEach 一样 循环list-->
<#--if 类似 c:if 一样 判断条件 -->
<#--在html中取值的话统一用${字段名} 在freemarker中取值是直接写字段名就行-->

<div class="tab-main">
    <table border="1" style="margin: auto;">
        <tr>
            <td style="width:10%;">教师姓名</td>
            <td style="width:15%;">${jsxm}</td>
            <td style="width:10%;">所属院部</td>
            <td style="width:35%;">${ssyx}</td>
            <td style="width:10%;">上课时间</td>
            <td style="width:20%;" colspan="2">${sksj}</td>
        </tr>
        <tr>
            <td rowspan="2" colspan="2">评价内容</td>
            <td colspan="6">评价结果</td>
        </tr>
        <tr>
            <td colspan="3">评价标准</td>
            <td>分值</td>
            <td>评价</td>
        </tr>

        <#--    循环开始-->
        <#assign tmlx = '' tmmc = '' tmxx = '' tmfz = '' sfxz = '' tmlxkhsl ='' tmmckhsl=''/><#--这里定义了几个变量-->
        <#list userList as user>
            <tr>
                <#if tmlx??><#--这个代表tmlx不等于空-->
                    <#if tmlx == user.tmlx >
                    <#else>
                        <#assign tmlx = user.tmlx/>
                        <td rowspan="${user.tmlxkhsl}">${user.tmlx}</td>
                    </#if>
                <#else><#--否者 如果还有if的话就是 <#elseif 判断条件> -->
                    <#assign tmlx = user.tmlx/>
                    <td rowspan="${user.tmlxkhsl}">${user.tmlx}</td>
                </#if>

                <#if tmmc??>
                    <#if tmmc == user.tmmc >
                    <#else>
                        <#assign tmmc = user.tmmc/>
                        <td rowspan="${user.tmmckhsl}">${user.tmmc}</td>
                    </#if>
                <#else>
                    <#assign tmmc = user.tmmc/>
                    <td rowspan="${user.tmmckhsl}">${user.tmmc}</td>
                </#if>

                <td colspan="3">${user.tmxx}</td>
                <td >${user.tmfz}</td>

                <#if user.sfxz?? && user.sfxz == '1' >
                    <td></td>
                <#else>
                    <td></td>
                </#if>
            </tr>
        </#list>
        <#--    //结束-->

    </table>
</div>
</body>
</html>

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
您好,要实现在Java使用FreeMarker导出多个FTL模板文件,可以按照以下步骤进行: 1.在Java项目中引入FreeMarker依赖库。 2.创建多个FTL模板文件,并将它们保存在指定的目录下。 3.在Java代码中,使用FreeMarker的Configuration类来加载FTL模板文件,并将模板数据填充到模板文件中,生成输出结果。 4.如果需要嵌套使用多个FTL模板文件,可以使用include指令或import指令,将一个FTL模板文件中的内容插入到另一个FTL模板文件中。 下面是一个简单的例子,演示了如何在Java使用FreeMarker导出多个FTL模板文件: ``` public static void main(String[] args) throws Exception { //创建Configuration对象 Configuration cfg = new Configuration(Configuration.VERSION_2_3_23); cfg.setDirectoryForTemplateLoading(new File("templates")); //加载第一个模板文件 Template template1 = cfg.getTemplate("template1.ftl"); //设置第一个模板文件的数据模型 Map<String, Object> data1 = new HashMap<>(); data1.put("name", "John"); //将第一个模板文件的输出结果作为第二个模板文件的输入数据 StringWriter stringWriter = new StringWriter(); template1.process(data1, stringWriter); String result1 = stringWriter.toString(); //加载第二个模板文件 Template template2 = cfg.getTemplate("template2.ftl"); //设置第二个模板文件的数据模型 Map<String, Object> data2 = new HashMap<>(); data2.put("content", result1); //将第二个模板文件的输出结果写入到文件中 Writer out = new FileWriter(new File("output.html")); template2.process(data2, out); out.flush(); out.close(); } ``` 在上面的代码中,我们首先创建了一个Configuration对象,并指定了FTL模板文件所在的目录。然后,我们加载了两个FTL模板文件,并设置了它们的数据模型。最后,我们将第一个模板文件的输出结果作为第二个模板文件的输入数据,并将第二个模板文件的输出结果写入到文件中。 在实际应用中,您可能需要更复杂的模板嵌套结构,但是基本的实现思路是相同的。希望这个例子能帮助您理解如何在Java使用FreeMarker导出多个FTL模板文件。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值