freemarker+springboot实现word生成下载(导出Word)
编译器:idea
项目构建工具:maven
数据库:mysql、navicat
一、导入pom依赖
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.31</version>
</dependency>
二、创建Word模板
创建你想要生成的word的模板,如上图,绿框内是需要填充数据的地方(先用中文占位一下)。然后在保存文件时选择保存成xml,我选择的保存位置是项目的classpath路径下的templates目录下。如下图:
保存好后在项目中打开:
找一个格式化xml的在线工具(比如:https://c.runoob.com/front-end/710/
),将格式化后的代码粘到report.xml里:
下一步制造填充模板需要的数据,我使用的是之前写好的一个接口,获取到的数据如下:
{
"title": "主题",
"status": "已完成",
"time": "2023-04-11 15:40:53",
"timeLen": "6分钟",
"committee": "1",
"remark": "某段会议描述",
"record": [
{
"name": "王某",
"updateTime": "2023-04-11 10:43:06",
"recognition": "而是"
},
{
"name": "张三",
"updateTime": "2023-04-11 10:43:17",
"recognition": "啥"
},
{
"name": "李四",
"updateTime": "2023-04-11 10:43:17",
"recognition": "喂。"
},
{
"name": "王五",
"updateTime": "2023-04-11 10:44:47",
"recognition": "我喂。"
}
],
"partyNames": [
"张三",
"李四"
],
"rate": "50"
}
将需要填充给模板的数据放到一个dataMap中:
将占位符放到Word模板的正确位置:
检索前面绿框圈出来的文字,修改为图下样式:
其他位置也做如此替换:
添加<#if>判断
如果${report.commitee}在模板中出现了,但是dataMap中没有这个数据,或者为空就会报错,所以可以添加一个if判断来避免这个异常。
list循环
当传入的数据是一个数组对象(例如本示例中的List
二、创建生成Word文件的工具类
package com.webro.utils;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.Version;
import java.io.*;
import java.util.Map;
/**
* Word文档工具类
*/
public class WordUtil {
/**
* 使用FreeMarker自动生成Word文档
* @param dataMap 生成Word文档所需要的数据
* @param
*/
public static void generateWord(Map<String, Object> dataMap, OutputStream outputStream) throws Exception {
// 获取当前项目路径
String projectPath = System.getProperty("user.dir");
// 设置FreeMarker的版本和编码格式
Configuration configuration = new Configuration(new Version("2.3.31"));
configuration.setDefaultEncoding("UTF-8");
// 设置FreeMarker生成Word文档所需要的模板的路径
configuration.setDirectoryForTemplateLoading(new File(projectPath+"\\src\\main\\resources\\templates"));
// 设置FreeMarker生成Word文档所需要的模板
Template t = configuration.getTemplate("MinutesMeeting.ftl", "UTF-8");
//FreeMarker使用Word模板和数据生成Word文档
Writer out = new BufferedWriter(new OutputStreamWriter(outputStream, "UTF-8"));
t.process(dataMap, out);
out.flush();
}
}
其中OutputStream、BufferedWriter方法的讲解:https://www.cnblogs.com/qianyx/articles/17348979.html
三、编写controller层方法
/**
* 导出会议纪要freemarker版本
*
* @param
* @return
*/
@GetMapping("exportMinutesMeeting/{id}")
public ResponseEntity<byte[]> exportMinutesMeeting(@PathVariable("id") String id) throws Exception {
// 生成文件
Map<String,Object> dataMap = new HashMap<>();
MinutesMeeting minutesMeeting = meetingService.getBasicMinutes(id);
dataMap.put("report",minutesMeeting);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
WordUtil.generateWord(dataMap,outputStream);
HttpHeaders headers = new HttpHeaders();
//Content-Disposition的作用:告知浏览器以何种方式显示响应返回的文件,用浏览器打开还是以附件的形式下载到本地保存
//attachment表示以附件方式下载 inline表示在线打开 "Content-Disposition: inline; filename=文件名.mp3"
// filename表示文件的默认名称,因为网络传输只支持URL编码,因此需要将文件名URL编码后进行传输,前端收到后需要反编码才能获取到真正的名称
String fileName = minutesMeeting.getOrgName()+"-"+minutesMeeting.getTitle()+".doc";
headers.add("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
headers.add("Content-Length", "" + outputStream.toByteArray().length);
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
// 将文件内容和头部信息封装到响应实体中并返回
return new ResponseEntity<>(outputStream.toByteArray(), headers, HttpStatus.OK);
}
这里本来使用的是HttpServletResponse来编辑响应信息,但是无论怎么改,都报错:getOutputStream() has already been called,经过百度,发现可能是和@Controller注解里的getOutputStream()方法冲突,所以改使用HttpServletResponse。