浅谈JAVA Web 关于Freemarke导出Word,Excel功能的案例

本文介绍使用Freemarker结合Spring Boot实现Word模板导出功能。通过具体代码示例和步骤说明,帮助读者理解如何将业务数据填充到Word模板并生成可下载的文档。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

                 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们

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值