Java使用freemarker、wkhtmltopdf根据自定义ftl模板导出html以及pdf
- 一、先看一下效果:
-
- 1. 控制台输出的:
- 2. 生成的文件:
- 3. pdf内容:
- 二、准备工作
- 三、项目结构如下:
- 四、具体实现代码如下:
-
- 1. 用到的jar(主要的就是这几个)
- 2. 将模板生成html (HtmlUtil.java)
- 3. HtmlToPdfInterceptor.java
- 4. Html转pdf (HtmlToPdf.java)
- 5. 测试类(HtmlToPdfMain.java)
- 6. ftl模板(model.ftl)
这段时间要搞一个导出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(主要的就是这几个)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | <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)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | 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)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | 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)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | 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)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | <!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> |