最近工作遇到需要开发java导出Word操作,这里我选用的模板是freemaker。
导出的前期操作在网络上一找一大把,我这里复述下我的操作:
1.将要导出的模板打到Word中,然后另存为xml格式的文件。
文档中要作为动态替换的内容信息,需要使用${}包住,值得注意的是,在Word文档中,如果Word识别认为需要替换的部分存在语病,被波浪线标注出来了,那么在转换为xml格式的时候,会被拆开,无法被当做一个字段来处理,所以需要在转换xml之前的Word中,将语病去除修改好。
2.将修改好的xml文件,以ftl的格式保存,当做在代码中使用的模板,放在静态文件目录中
3.下面就是导出部分,我们首先实现代码中拼装字段,输出到本地中
public void createDoc(Map<String, Object> dataMap, String fileName) throws UnsupportedEncodingException {
//dataMap 要填入模本的数据文件
//设置模本装置方法和路径,FreeMarker支持多种模板装载方法。可以重servlet,classpath,数据库装载,
//这里我们的模板是放在template包下面
configuration.setClassForTemplateLoading(this.getClass(), "/template");
Template t = null;
try {
//occupationalHealth.ftl为要装载的模板
t = configuration.getTemplate("occupationalHealth.ftl");
} catch (IOException e) {
e.printStackTrace();
}
//输出文档路径及名称
File outFile = new File(fileName);
//如果不存在临时路径则创建
if (!outFile.getParentFile().exists()) {
outFile.getParentFile().mkdirs();
}
Writer out = null;
FileOutputStream fos = null;
try {
fos = new FileOutputStream(outFile);
OutputStreamWriter oWriter = new OutputStreamWriter(fos, "UTF-8");
//这个地方对流的编码不可或缺,使用main()单独调用时,应该可以,但是如果是web请求导出时导出后word文档就会打不开,并且包XML文件错误。主要是编码格式不正确,无法解析。
//out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile)));
out = new BufferedWriter(oWriter);
} catch (FileNotFoundException e1) {
e1.printStackTrace();
}
try {
t.process(dataMap, out);
out.close();
fos.close();
} catch (TemplateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
我个人认为这里的写法有很多,网上能搜索出很多大同小异的操作,核心方法就是 ,先加载本地ftl模板,成为输出流的形式,再通过process方法将传入的要替换的参数与模板处理成Word文件输出到指定的目录,这里我的参数也贴一下:
Map<String, Object> dataMap = new HashMap<String, Object>();
dataMap.put("depart", exportDomain.getEnterpriseName());
dataMap.put("factory", exportDomain.getWorkShop());
dataMap.put("employee", exportDomain.getName());
dataMap.put("year", "2021");
dataMap.put("month", "08");
dataMap.put("day", "31");
dataMap.put("medical", "motoGP");
exportAndPrintService.createDoc(response,dataMap, "D:/outFile.doc");
其实上面便是输出到本地的几个关键点的罗列,下面,如果想以io流的形式输出到前端,供我们下载,则需要做一些调整:
首先,createDoc方法不能是void类型了,需要设置为file类型:
public File createDoc(Map<String, Object> dataMap, String fileName) throws UnsupportedEncodingException { //dataMap 要填入模本的数据文件 //设置模本装置方法和路径,FreeMarker支持多种模板装载方法。可以重servlet,classpath,数据库装载, //这里我们的模板是放在template包下面 configuration.setClassForTemplateLoading(this.getClass(), "/template"); Template t = null; try { //occupationalHealth.ftl为要装载的模板 t = configuration.getTemplate("occupationalHealth.ftl"); } catch (IOException e) { e.printStackTrace(); } //输出文档路径及名称 File outFile = new File(fileName); //如果不存在临时路径则创建 if (!outFile.getParentFile().exists()) { outFile.getParentFile().mkdirs(); } Writer out = null; FileOutputStream fos = null; try { fos = new FileOutputStream(outFile); OutputStreamWriter oWriter = new OutputStreamWriter(fos, "UTF-8"); //这个地方对流的编码不可或缺,使用main()单独调用时,应该可以,但是如果是web请求导出时导出后word文档就会打不开,并且包XML文件错误。主要是编码格式不正确,无法解析。 //out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile))); out = new BufferedWriter(oWriter); } catch (FileNotFoundException e1) { e1.printStackTrace(); } try { t.process(dataMap, out); out.close(); fos.close(); return outFile; } catch (TemplateException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return null; }
再最后我们返回了文档路径与名称,这是为后面能够刷出到页面返回了重要的内容
接下来就是关键的一点,也是困扰我很久,怎么将本地生成的文件以流的形式刷 到前端,方法如下:
public void exportMillCertificateWord(HttpServletResponse response, Map map, String fileName) throws IOException {
File file = null;
InputStream fin = null;
ServletOutputStream out = null;
try {
file = createDoc(map, fileName);
fin = new FileInputStream(file);
response.setCharacterEncoding("utf-8");
response.setContentType("application/msword");
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(); // 删除临时文件
}
}
}
这个方法首先接收到map,与filename参数,为了能够返回页面,也要定义response。然后在方法中调用刚才写的加载模板与参数,生成本地文档的方法,拿到生成的文档后,我们再将其读到返回值中返回给前端,也就是说,freemaker生成Word读到前端,需要一个临时地址来存放文档,读完后,再由程序删除即可,本地调试可以是本地磁盘,服务器上可以是部署路径,这一点是我研究了一天才弄明白的,我认为这是整个导出操作的关键点所在,导出网上有大把的案例,前端处理网上也有大把案例,但是这个关键点,我没有看到讲述过,记录下来,分享一下。
也顺便贴一下前端处理这个来之不易的输出流的方法:
function exportMethod(data) {
axios({
method: 'get',
url: baseUrl + data.url,//接口地址
responseType: 'blob',
headers: {
'Content-Type': 'application/json'
}
}).then((res) => {
let type = "", type2 = []
if (data.type == "doc") {
type = 'application/msword'
type2 = ['.doc', '.docx']
}
const link = document.createElement('a')
let blob = new Blob([res.data], { type })
link.style.display = 'none'
link.href = URL.createObjectURL(blob)
link.download = data.fileName
link.type = type2
document.body.appendChild(link)
link.click()
}).catch(error => {
console.log(error)
})
}
前端是vue写的,相信不同的工具的编写方式也是一搜一大把,至于自定义下载路径,则是浏览器的配置问题了。
工作之余匆匆总结下,有问题欢迎指出。