Java关于Freemarker导出文件功能的案例讲解
本人技术一般,写这篇文章的目的,也仅仅是让以后一些朋友,少走一些弯路,少一点抱怨,让程序员生活并不是想象的那么糟糕。最近一直在做CRM,就在前几天,产品给我们提出了 "有关核心业务数据通过word模板输出",展示给客户功能的需求。由于项目二期一直处于需求调研阶段,所以经理把写通用接口的任务交给了我,方便以后的研发。关于文件输出的功能,网上有许多解决方案,比如用poi、jxt,都能够实现,但经过本人的调研,还是选用了Freemarker的输出方式,Freemarker比前两者都更简单、更便捷,其实无论是poi还是freemarker,都是对于ftl文件的操作,所以修改ftl文件,至为重要。网上有许多关于freemaker简单demo的案例,但也就仅仅停留在主函数简单的输出上,但现在的大多数项目都是基于web开发的,所以本次具体写出了web前台对于freemaker接口的调用。话不多说,以下是一个简单的Web与Freemarker结合的简单Demo,分享给初学者学习。
项目结构图如下,本次的demo还是基于Spring Boot的项目,后台使用Spring Boot框架。因为是简单的Demo,并没有使用相关的前台框架,也仅仅用到了简单的Jquery和AJAX技术,关于如何快速启动一个Boot项目,请看我上一篇文章进行学习。
1.freemarker的java代码
其实关于后台的代码,这里面service,本次写的都是死值,如果到真正的具体项目上,都应该是从数据库调出来的业务数据。关于Bean的编写,就是一行记录封成一个Bean,具体对照看下面的word模板,大家都会简单易懂。关于TempUtils也是本次最重要的核心代码,自己封装的freemarker接口进行调用,而且跟web进行结合,需要的是用户在前台进行下载操作,所以既要把word模板输出又要提供下载,大家必须仔细看一下,这也是本次demo最为重要的地方。关于Controller,还是通用的mvc那套体系,主要是为了接受前台的请求映射,而TimeUtil就是因为本人有强迫症,自己封装了一个时间工具类,保证生成的文件命名有流水号,无关紧要的一个类,希望大家能够理解我这种强迫症的人。下面开始上代码:
1.1 Transfer.java
package com.panhai.SpringBootHello.Entity;
public class Transfer {
String cargotype;
String datahabor;
String complete;
public String getCargotype() {
return cargotype;
}
public void setCargotype(String cargotype) {
this.cargotype = cargotype;
}
public String getDatahabor() {
return datahabor;
}
public void setDatahabor(String datahabor) {
this.datahabor = datahabor;
}
public String getComplete() {
return complete;
}
public void setComplete(String complete) {
this.complete = complete;
}
}
1.2 TempController.java
package com.panhai.SpringBootHello.Controller;
import java.io.IOException;
import java.util.Map;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import com.panhai.SpringBootHello.Service.TreansferService;
import freemarker.template.TemplateException;
@Controller
public class TempController {
@Autowired TreansferService treansferService;
@RequestMapping(value="temp/{id}", method = RequestMethod.GET)
@ResponseBody
public String printTemp(@PathVariable Integer id,HttpServletResponse response) throws Exception {
treansferService.printCargoTemp(id,response);
return "输出成功";
}
@RequestMapping(value = "detail", method = RequestMethod.GET)
@ResponseBody
public Map<String, Object> getData() throws IOException, TemplateException {
return treansferService.getCargoTemp();
}
}
1.3 TreansferService.java(造的都是假数据)
package com.panhai.SpringBootHello.Service;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Service;
import com.panhai.SpringBootHello.Util.TimeUtil;
import com.panhai.SpringBootHello.Util.TempUtils;
import com.panhai.SpringBootHello.Entity.Transfer;
import freemarker.template.TemplateException;
@Service
public class TreansferService {
public void printCargoTemp(Integer id,HttpServletResponse response) throws Exception {
Map<String,Object> datamap = new HashMap<String,Object>();
List<Transfer> Transferlist= new ArrayList<Transfer>();
Transfer transfer =new Transfer();
transfer.setCargotype("油品");
transfer.setDatahabor("锦州港");
transfer.setComplete("90");
Transfer transfer1 =new Transfer();
transfer1.setCargotype("");
transfer1.setDatahabor("大连港");
transfer1.setComplete("80");
Transfer transfer2 =new Transfer();
transfer2.setCargotype("粮品");
transfer2.setDatahabor("丹东港");
transfer2.setComplete("70");
Transfer transfer3 =new Transfer();
transfer3.setCargotype("");
transfer3.setDatahabor("葫芦岛港");
transfer3.setComplete("60");
Transfer transfer4 =new Transfer();
transfer4.setCargotype("氧化铝");
transfer4.setDatahabor("AA港");
transfer4.setComplete("50");
Transfer transfer5 =new Transfer();
transfer5.setCargotype("");
transfer5.setDatahabor("66");
transfer5.setComplete("88");
Transferlist.add(transfer);
Transferlist.add(transfer1);
Transferlist.add(transfer2);
Transferlist.add(transfer3);
Transferlist.add(transfer4);
Transferlist.add(transfer5);
System.out.println("传入id为:"+id);
datamap.put("Transferlist", Transferlist);
switch (id) {
case 1:
TempUtils.exportFile("src/main/resources/template", "datahabor.ftl",response, datamap,"doc","msword");
break;
case 2 :
TempUtils.exportFile("src/main/resources/template", "datahabor2.ftl",response, datamap,"xls","msexcel");
break;
}
System.out.println("打印成功");
}
public Map<String, Object> getCargoTemp() throws IOException, TemplateException {
Map<String,Object> datamap = new HashMap<String,Object>();
List<Transfer> Transferlist= new ArrayList<Transfer>();
Transfer transfer =new Transfer();
transfer.setCargotype("油品");
transfer.setDatahabor("锦州港");
transfer.setComplete("90");
Transfer transfer1 =new Transfer();
transfer1.setCargotype("");
transfer1.setDatahabor("大连港");
transfer1.setComplete("80");
Transfer transfer2 =new Transfer();
transfer2.setCargotype("粮品");
transfer2.setDatahabor("丹东港");
transfer2.setComplete("70");
Transfer transfer3 =new Transfer();
transfer3.setCargotype("");
transfer3.setDatahabor("葫芦岛港");
transfer3.setComplete("60");
Transfer transfer4 =new Transfer();
transfer4.setCargotype("氧化铝");
transfer4.setDatahabor("AA港");
transfer4.setComplete("50");
Transfer transfer5 =new Transfer();
transfer5.setCargotype("");
transfer5.setDatahabor("66");
transfer5.setComplete("77");
Transferlist.add(transfer);
Transferlist.add(transfer1);
Transferlist.add(transfer2);
Transferlist.add(transfer3);
Transferlist.add(transfer4);
Transferlist.add(transfer5);
datamap.put("Transferlist", Transferlist);
return datamap;
}
}
1.4 TempUtil.java(最重要的部分之一)
package com.panhai.SpringBootHello.Util;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.URLEncoder;
import java.util.Map;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import ch.qos.logback.core.subst.Token.Type;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
public class TempUtils {
private static File createDoc(Map<?, ?> dataMap, Template template,String suffix) {
String name = "datahabor"+TimeUtil.getDate()+"."+suffix;
File f = new File(name);
Template t = template;
try {
// 这个地方不能使用FileWriter因为需要指定编码类型否则生成的Word文档会因为有无法识别的编码而无法打开
Writer w = new OutputStreamWriter(new FileOutputStream(f), "utf-8");
t.process(dataMap, w);
w.close();
} catch (Exception ex) {
ex.printStackTrace();
throw new RuntimeException(ex);
}
return f;
}
/**
* 解析模板生成文件
* @param templateDir 模板目录
* @param templateName 模板名称
* @param map 从数据库取出的数据封装成map
* @param suffix 文件后缀名 doc/xlsx
* @param type 下载文件内容类型 msword
*/
public static void exportFile(String templateDir,String templateName,HttpServletResponse response,
Map map,String suffix,String type) throws IOException {
Configuration configuration= new Configuration();
configuration.setDefaultEncoding("utf-8");
configuration.setDirectoryForTemplateLoading(new File(templateDir));
Template freemarkerTemplate = configuration.getTemplate(templateName);
File file = null;
InputStream fin = null;
ServletOutputStream out = null;
try {
// 调用工具类的createDoc方法生成Word文档
file = createDoc(map,freemarkerTemplate,suffix);
fin = new FileInputStream(file);
response.setCharacterEncoding("utf-8");
response.setContentType("application/"+type);
// 设置浏览器以下载的方式处理该文件名
String fileName = "datahabor"+TimeUtil.getDate()+"."+suffix;
response.setHeader("Content-Disposition", "attachment;filename="
.concat(String.valueOf(URLEncoder.encode(fileName, "UTF-8"))));
out = response.getOutputStream();
byte[] buffer = new byte[512]; // 缓冲区
int bytesToRead = -1;
// 通过循环将读入的Word文件的内容输出到浏览器中
while((bytesToRead = fin.read(buffer)) != -1) {
out.write(buffer, 0, bytesToRead);
}
} finally {
if(fin != null) fin.close();
if(out != null) out.close();
if(file != null) file.delete(); // 删除临时文件
}
}
}
1.5 TimeUtil.java
package com.panhai.SpringBootHello.Util;
import java.text.SimpleDateFormat;
import java.util.Date;
public class TimeUtil {
public static String getDate() {
SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");//设置日期格式
return df.format(new Date());// new Date()为获取当前系统时间
}
}
2.ftl文件的生成
2.1 首先第一步,进行画word模板(根据你的业务需求),其中画红圈的地方,就是你需要写的ftl表达式,这跟我们过去jsp(尽管被淘汰了)页面写的EL表达式差不多,都是为了进行赋值用的,具体想学习的同学们可以自行百度,这里就不多做介绍了.
2.2 第二步,把做好的模板另存为XML格式,然后进行修改,这也是修改ftl文件,最核心的地方。需要加入相应的list标签,为了是动态加载数据,这也是前面封装成的bean,关于ftl表达式出现分家,或者格式不正确的情况,大家可以把做好的模板粘贴到excel表格中,然后再反粘贴回word,这样也避免了格式不正确的状况,这也是我跟小伙伴研究了好长时间,才研究出来的方法。
2.3 第三步,就是把你改好的xml文件,改为ftl格式,然后部署到项目上,就ok了。
3.相关前台代码
本次写前台代码,还是为了弥补以前的帖子,只有简单的主函数调用,而并没有与web相结合,所以本次前台只写了一点简单的前台例子,为了调后台写好的接口,仅供大家参考。
3.1 html,js代码
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<div id="div_print">
<center><h2>周边港口3月份完成情况</h2></center>
<div >
<center><table id="demo" border="1" style="width: 800px"></center>
<tr>
<td>货种</td>
<td>港口</td>
<td>3月份完成</td>
</tr>
</table>
</div>
</div>
<br>
<center><button onclick="aa()" style="height: 40px" >下载Word</button>
<button onclick="bb()" style="height: 40px" >下载Excel</button>
<input name="b_print" type="button" class="ipt" style="height: 40px" onClick="printdiv('div_print');" value=" Print ">
</center>
<br>
</body>
<script src="js/jquery-1.11.3.js"></script>
<script type="text/javascript">
function aa(){
var id = 1;
window.location.href='temp/'+id;
/* window.print(); */
}
function bb(){
window.location.href='temp/'+2;
/* window.print(); */
}
function printdiv(printpage){
/* var printData = document.getElementById("dvData").innerHTML;
window.document.body.innerHTML = printData;
window.print(); */
var headstr = "<html><head><title></title></head><body>";
var footstr = "</body>";
var newstr = document.all.item(printpage).innerHTML;
var oldstr = document.body.innerHTML;
document.body.innerHTML = headstr+newstr+footstr;
window.print();
document.body.innerHTML = oldstr;
return false;
}
$.ajax({
type:'get',
dataType:'json',
dataType:'json',
url:'/detail',
contentType:'application/json;charset=UTF-8',
success:function(data){//返回json结果
var list=data.Transferlist
var s = "";
for(var i = 0; i < list.length; i++) {
s = "<tr><td>" + list[i].cargotype + "</td><td>" + list[i].complete + "</td><td>" +
list[i].datahabor + "</td></tr>";
$("#demo").append(s);
}
console.log(list);
}
});
</script>
</html>
4.总结与展示
4.1 项目功能展示
其中前台还做了下载excel按钮,至于怎么输出excel,跟输出word的原理相同,只需要调用相同的方法,传入不同的参数值和加载不同的模板文件就okl,具体还是看大家的业务需求什么样,另外用此功能下载excel,会有扩展名与文件内容不匹配的错误,本人上网找了好多帖子,许多人都遇到了相同的问题,可能是因为freemarker真正输出的形式是xml,而我们后台手动改了后缀名,这个bug始终没有解决,也希望技术大牛可以通过留言,私信等方式,帮助大家共同解决这个问题,万分感谢!
4.2 总结
其实写这篇文章最开始的目的,就是想帮助那些在技术成长的路上,跟我一样迷茫,一样困惑的小伙伴们,也让你们感受到苦逼的程序员生活并不是那么难。因为我始终相信蝴蝶效应,也许多年前你放走的那只蝴蝶,或许轻轻扇动翅膀,就能激起你未来属于你的整个海洋。我也不知道还能在技术的道路上走多久,既然决定了走这条路,又何必在意以后会怎样。世界上所有的热爱都来自于坚持.
------献给还在熬夜加班996的coder们