简介
之前项目中有一个生成word文档的功能,根据数据库的数据进行word文档的生成,当时实现了该功能,但没有打笔记忘记了,特此打个笔记记录一下
我使用的freemarker的技术来实现word的生成,网上也都比较推荐该方式,他是通过根据一个模板,进行填充的方式实现word的生成
准备工作
定义word模板
我们要使用freemarker生成word,首先要自己去定义一个doc模板,模板中要填充的数据使用字符串模板的方式定义 ${ 字段名 }
定义完我们的模板之后,我们要将文档保存为xml的格式
修改保存的xml文件
保存完的xml文件是一行的不好查看,我们可以提前复制到idea中,使用ctrl + alt + l 进行格式化,就变成标准的xml格式了
保存为xml常见的问题就是${name}被拆开了,我们要进行修改
修改为下面这样(多余的wr直接删除即可)
基础信息的定义
基础信息的定义只要保证我们转化成的xml文件中的${name} , ${age}格式没错误即可
表格的定义
遍历实现,表格的数据填充
在xml文件中我们的 <w:tbl> 代表是我们的表格 ,<w:tr> 代表的是我们的行 ,<w:tc> 代表我们的列
我们要想使用表格的方式,肯定大部分是要使用我们的list集合进行数据填充的,我们可以通过下面的方式进行遍历填充数据
我们在表格的表头下的<w:tr>上添加
<#list bookList as book>
在我们</w:tr>下添加
</#list>
其中bookList代表我们要遍历的集合,book代表出来的结果,book.month代表book中的字段
实现单元格的合并
有两个标签
该标签表示,从这个开始进行合并
<w:vMerge w:val="restart"/>
该标签表示,只要带这个标签他就与上面的这个标签合并
<w:vMerge/>
我们将这两个标签定义到我们要合并的单元格的<w:tcPr>中
<w:tcPr>
<w:tcW w:w="2765" w:type="dxa"/>
<#-- 表示居中-->
<w:vAlign w:val="center"/>
<#-- 将month 等于1 和 2的进行合并-->
<#if book.month == 1>
<w:vMerge w:val="restart"/>
</#if>
<#if book.month == 2>
<w:vMerge/>
</#if>
</w:tcPr>
图片的插入
首先我们要在指定位置添加一个图片,我们保存为xml文件后,我们的图片信心会变为base64转码的字符串,将这部分进行删除,替换为${images},所以我们进行填充时也要是base64转码后的数据
修改xml文件为ftl文件
最后将我们的文件修改为 .ftl 格式的文件复制到我们 templates文件夹下
代码实现
首先要导入我们的freemarker依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
定义我们要填充的数据
public Map<String,Object> dataMap() throws IOException {
//获取基础数据
Map<String , Object> map = new HashMap<>();
map.put("name","qtt");
map.put("age","25");
//获取表格数据
List<Map<String,Object>> bookList = new ArrayList<>();
for (int i = 0; i < 5; i++) {
Map<String, Object> map1 = new HashMap<>();
map1.put("month",i);
map1.put("income", i);
map1.put("expense", i +100);
bookList.add(map1);
}
map.put("bookList",bookList);
//获取图片流且进行base64转码
File file = new File("C:\\Users\\Administrator\\Desktop\\teacher\\01.jpg");
FileInputStream fileInputStream = new FileInputStream(file);
byte[] bytes = new byte[fileInputStream.available()];
fileInputStream.read(bytes);
BASE64Encoder base64Encoder = new BASE64Encoder();
String encode = base64Encoder.encode(bytes);
map.put("images",encode);
//关闭流
fileInputStream.close();
return map;
}
定义我们我们的填充方法
@Test
public void insertWord() throws IOException, TemplateException {
//定义我们的编码方式
Configuration configuration = new Configuration();
configuration.setDefaultEncoding("UTF-8");
//指定我们word的目录
configuration.setDirectoryForTemplateLoading(new File("src/main/resources/templates"));
//指定我们要使用的word模板.ftl
Template template = configuration.getTemplate("模板.ftl", "UTF-8");
//指定输出流到的位置
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("E:/222/demo.doc"), "UTF-8"));
//执行填充输出
template.process(this.dataMap(),bufferedWriter);
//关闭io流
bufferedWriter.flush();
bufferedWriter.close();
}
我们还可以直接定义为下载,不用使用输出流指定下载地址,直接通过下载的方式指定地址
@GetMapping("/upload")
public void upload(HttpServletResponse response){
try {
//定义我们的编码方式
Configuration configuration = new Configuration();
configuration.setDefaultEncoding("UTF-8");
//指定我们word的目录
configuration.setDirectoryForTemplateLoading(new File("src/main/resources/templates"));
//指定我们要使用的word模板.ftl
Template template = configuration.getTemplate("模板.ftl", "UTF-8");
//返回word文档
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
String format = simpleDateFormat.format(new Date());
String fileName = URLEncoder.encode("接口文档" + format, "UTF-8");
response.setCharacterEncoding("UTF-8");
//请求头定义为下载
response.setHeader("Content-Disposition","attachment;filename="+fileName+".doc");
//获取apiDoc所需要的数据
Map<String, Object> map = dataMap();
//渲染模板
template.process(map, response.getWriter());
//response的Writer不需要我们手动关,tomcat会帮我们关的
} catch (Exception e) {
log.error("导出word异常:", e);
}
}
遇到的坑
1.优为注意:freemarker是不支持 null 的,所以我们的数据要么全部附上值,要么给为空的值设置为 " " ,否则会报错
2.我们在定义表格l遍历填充的时候,一定要注意传入的表格字段类型是list,不能是map,否则会报错
3.和并单元格的时候,查看的文章,它定义的标签是m是小写,实现不了合并,看其他文章都为大写M,修改实现了合并,有大小写区分
无法合并的情况
<w:vmerge w:val="restart"/>
<w:vmerge/>
修改后可以合并
<w:vMerge w:val="restart"/>
<w:vMerge/>