需求:
根据指定的word模板,用代码生成word文件,数据为业务数据
方案:
引用freemarker工具包
步骤:
- 引入freemarker maven依赖 或者直接导入jar包
<dependency>
<groupId>freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.8</version>
</dependency>
- 编写word模板,编写完后缀是.docx,另存为xml后缀文件,修改文件内容
${XXX}
,调整完修改后缀为.ftl
几个注意点:
(1)生成word文档时,需注意ftl模板中替换字段格式错误,el表达式正确格式:${field},中间不能有标签隔开
(2)若为表格输出,list循环,则在列名之后的数据行进行循环<#list myListData as tmp>...</#list>
,如
<#list myListData as tmp>
<w:tr wsp:rsidR="008F01FC" wsp:rsidRPr="008F01FC" wsp:rsidTr="008F01FC">
<w:tc>
<w:tcPr>
<w:tcW w:w="2074" w:type="dxa"/>
<w:shd w:val="clear" w:color="auto" w:fill="auto"/>
</w:tcPr>
<w:p wsp:rsidR="001D38F7" wsp:rsidRPr="008F01FC" wsp:rsidRDefault="001D38F7">
<w:pPr>
<w:rPr>
<w:rFonts w:hint="fareast"/>
</w:rPr>
</w:pPr>
<w:r wsp:rsidRPr="008F01FC">
<w:rPr>
<w:rFonts w:hint="fareast"/>
</w:rPr>
<w:t>${tmp.name}</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:tcPr>
<w:tcW w:w="2074" w:type="dxa"/>
<w:shd w:val="clear" w:color="auto" w:fill="auto"/>
</w:tcPr>
<w:p wsp:rsidR="001D38F7" wsp:rsidRPr="008F01FC" wsp:rsidRDefault="001D38F7">
<w:pPr>
<w:rPr>
<w:rFonts w:hint="fareast"/>
</w:rPr>
</w:pPr>
<w:r wsp:rsidRPr="008F01FC">
<w:rPr>
<w:rFonts w:hint="fareast"/>
</w:rPr>
<w:t>${tmp.tel}</w:t>
</w:r>
</w:p>
</w:tc>
</w:tr>
</#list>
我的模板样例如下:
3. 工具类如下:
import freemarker.template.Configuration;
import freemarker.template.Template;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.*;
public class TestJava2Word2 {
public static void main(String[] args) throws IOException {
Map<String,Object> dataMap = new HashMap<String, Object>();
try {
/*===== 1.组装数据 dataMap =====*/
//插入文本示例
dataMap.put("username","张三");
dataMap.put("address","深圳市南山区西丽镇");
dataMap.put("num", Math.floor(Math.random()*10));
SimpleDateFormat sdf=new SimpleDateFormat("yyyy年MM月dd日");
dataMap.put("date",sdf.format(new Date()));
dataMap.put("content","华语天王周杰伦在北京召开周杰伦战略发布会。在发布会上,推出时尚与专业结合的:引起了广泛的关注和讨论。现在终于正式与大家见面,天猫、官网同时上线,首卖期间还有好礼相赠!买就送,就是这么任性!");
List<Map<String, Object>> newsList=new ArrayList<Map<String,Object>>();
//插入表格示例
for(int i=1;i<=10;i++){
Map<String, Object> map=new HashMap<String, Object>();
map.put("name", "王"+i);
map.put("tel", "137"+i);
map.put("age", "年龄"+i);
map.put("address", System.currentTimeMillis());
newsList.add(map);
}
dataMap.put("myListData",newsList);
//插入图片示例
String myPic1 = "";
String myPic2 = "";
try {
myPic1 = WordUtil.getImageString("D:\\testword\\11.png");
myPic2 = WordUtil.getImageString("D:\\testword\\11.png");
} catch (IOException e) {
e.printStackTrace();
}
dataMap.put("myPic1", myPic1);
dataMap.put("myPic2", myPic2);
// System.out.println("******"+dataMap.toString());
/*===== 2.Configuration 用于读取ftl文件 =====*/
Configuration configuration = new Configuration();//创建配置实例
configuration.setDefaultEncoding("utf-8");
//以下是两种指定ftl文件所在目录路径的方式,注意这两种方式都是:指定ftl文件所在目录的路径,而不是ftl文件的路径
//指定路径的第一种方式(根据某个类的相对路径,例:/com/wordtemplate)
// configuration.setClassForTemplateLoading(TestJava2Word2.class, "wordtemplate");
//指定路径的第二种方式,配置文件在系统中的绝对路径,示例路径是D:/testword/testTemplate.ftl
configuration.setDirectoryForTemplateLoading(new File("D:\\testword\\"));
/*===== 3.输出文档路径及名称 =====*/
File outFile = new File("D:\\testword\\测试报告导出.doc");
// 如果输出目标文件夹不存在,则创建
if (!outFile.getParentFile().exists()) {
outFile.getParentFile().mkdirs();
}
if(outFile .exists()) {
outFile = new File("D:\\testword\\测试报告导出("+1+").doc");
}
/*===== 4.以utf-8的编码读取ftl文件 =====*/
Template template = configuration.getTemplate("testTemplate2.ftl", "utf-8");
Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "utf-8"), 10240);
template.process(dataMap, out);
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
其中,生成文件的那一段写的不够好,后面又进行了优化,实现如下功能:若目录文件存在,文件名后缀加(1)、(2)依此类推,将在下一篇博客中分享,欢迎阅读。
这里需要阐述的一点是:
configuration.setClassForTemplateLoading()的使用。使用中这是个坑,需要深入理解,尤其是那个基于class类路径的方法,需要理解实际指定的路径位置,不然会导致看着磁盘上有文件但项目就是报错找不到文件,然后一脸懵,实际是路径不一致啦~~
Freemarker提供了3种加载模板目录的方法。它使用Configuration类加载模板,共有3种使用方法:
方 法 | 说 明 |
---|---|
public void setClassForTemplateLoading(Class clazz, String pathPrefix); | 第一种基于当前class路径加载,需要注意的是,路径是编译以后的class的路径。例如:configuration.setClassForTemplateLoading(TestJava2Word2.class, “wordtemplate”); 实际指定的路径是:TestJava2Word2.class类的包路径 + wordtemplate,可以用System.out.println("******"+TestJava2Word2.class.getResource("").getPath());验证路径。 |
public void setDirectoryForTemplateLoading(File dir) throws IOException; | 第二种基于文件绝对路径加载。 比如加载/home/user/template下的模板文件。 |
public void setServletContextForTemplateLoading(Object servletContext, String path); | 第三种基于web项目服务器路径加载。 第二个参数是基于WebRoot下的。比如: setServletContextForTemplateLoading(context, “/ftl”) 就是 /WebRoot/ftl目录。 |
(建议方案:在服务器上建个目录专门放模板,然后模板加载的路径指定到该目录下。)
图片转换字节代码 WordUtil.java:
import sun.misc.BASE64Encoder;
......
/**
* 将图片转换为BASE64为字符串
* @param filename
* @return
* @throws IOException
*/
public static String getImageString(String filename) throws IOException {
InputStream in = null;
byte[] data = null;
try {
in = new FileInputStream(filename);
data = new byte[in.available()];
in.read(data);
in.close();
} catch (IOException e) {
throw e;
} finally {
if(in != null) in.close();
}
BASE64Encoder encoder = new BASE64Encoder();
return data != null ? encoder.encode(data) : "";
}
- 输出文件样例: