1.目的:项目中遇到一个word导出的功能,需要在固定的模板里把一些字段替换掉,然后返回给前端,然后就实现
2.准备文件
a.将word文档需要填充的字段用占位字段替换${key},和map的key对应
b.将word打开然后另存为xml文件,直接修改文件的后缀xml改为ftl,这样就生成想要的模板文件啦。
c.我是放入项目文件下的
3.代码实现
package com.example.demo.service.impl;
import com.example.demo.service.WordService;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.Version;
import org.apache.tomcat.util.codec.binary.Base64;
import org.springframework.stereotype.Service;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
@Service
public class WordServiceImpl implements WordService {
@Override
public String changWord(String id, HttpServletResponse httpServletResponse) {
//用于区别不同请求的
if (id == null) {
id=String.valueOf(System.currentTimeMillis());
}
Map<String,Object> dataMap = new HashMap<String, Object>();
try {
//模板的参数,其中map各个字段必须要存在key,value可以为空
dataMap.put("date","2020-06-07");
dataMap.put("money","200");
dataMap.put("author","taotao");
dataMap.put("tie","测试文件");
Configuration configuration = new Configuration(new Version("2.3.0"));
configuration.setDefaultEncoding("utf-8");
dataMap.put("data", "test");
configuration.setDirectoryForTemplateLoading(new File("./config"));
//输出文档路径及名称
File outFile = new File("./config/test"+id+".doc");
//以utf-8的编码读取ftl文件,模板的位置
Template template = configuration.getTemplate("test.ftl", "utf-8");
Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "utf-8"), 10240);
template.process(dataMap, out);
out.close();
try {
FileInputStream fileInputStream = new FileInputStream(outFile);
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setHeader("Content-Disposition","attachment;filename=test"+id+".doc");
ServletOutputStream outputStream = httpServletResponse.getOutputStream();
byte[] data= new byte[1024];
//浏览器直接下载
int len;
while ((len=fileInputStream.read(data))>0){
outputStream.write(data,0,len);
}
outputStream.close();
fileInputStream.close();
//字符流返回给前端
StringBuffer stringBuffer = new StringBuffer();
for (int i = 0; i <data.length ; i++) {
stringBuffer.append((char)data[i]);
}
//base64加密返回给前段
String s = new Base64().encodeAsString(data);
//用于文件删除
outFile.delete();
//字节流返回
// return new String(stringBuffer);
} catch (Exception e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
4.测试
a.在浏览器中输出
localhost:8090/word/test?id=32312就可以看到下载的文件了。
b.对于postman来说张这样
然后点击download就行。
c.对于字符流返回的是一个string串然后你可以
点击download,他会默认存一个txt把它另存为doc,然后效果是一模一样的。
总结:word输出比较简单主要是生成模板,然后是要输出直接下载的还是base加密的还是字符串就看自己了,在实际操作中word页数比较多,然后输出base64的字段较多,此时vue解密时,ie竟然不能获取全字符串,而chorm是可以的,这个前端问题还没解决,所以我目前用的是字符串而非base64的方式。
方案二:
直接通过poi进行字段的替换,为啥这样做呢,因为在实际的使用中竟然还有人在使用低版本的wps,你敢信???帮政府做的word居然乱码,但是在我们这试都好好的,就开始排查问题呀。
大概用了一整天查这个问题,首先复现问题,问他们要了一份他们的版本(版本号:8.1.0.3000),然后我们的是(9.1.0.5024),只能把本地的wps卸载了然后再装他们版本的wps。我敲真的乱码,打开张这样和返回结果一样的,
为啥呢,再看下wps的升级,搜索xml,我去竟然在9.1.0.4940的版本新增了对xml框架的支持,我敲就是这之前的都不行啊。ps:word都可以就是低版本的wps妖路啊。
哎,看来xml作为模板的路是不行的了,没法只能另起炉灶了。
2.用啥方法捏?
最终只能用比较笨的方法了,poi来操作doc对象然后对字符串进行替换。
1.创建模板doc文件
2.引入依赖
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>3.9</version>
</dependency>
3.代码实现
public String getWordByDoc(String id, HttpServletResponse httpServletResponse) {
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("aa", "123");
dataMap.put("bb", "还不错");
dataMap.put("cc", 1);
dataMap.put("date", "2020-07-27");
try {
//模板文件
FileInputStream fileInputStream = new FileInputStream("./config/1.doc");
HWPFDocument hwpfDocument = new HWPFDocument(fileInputStream);
Range range = hwpfDocument.getRange();
for (Map.Entry<String, Object> entry : dataMap.entrySet()
) {
range.replaceText("${" + entry.getKey() + "}", entry.getValue().toString());
}
//生成的文件
File file = new File("./config/newDoc.doc");
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
hwpfDocument.write(byteArrayOutputStream);
FileOutputStream fileOutputStream = new FileOutputStream(file);
fileOutputStream.write(byteArrayOutputStream.toByteArray());
fileInputStream.close();
fileOutputStream.close();
byte[] bytes = FileUtils.readFileToByteArray(file);
return new String(bytes);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
4.测试
允许代码生成了doc
ok,大功告成。
两种方案的对比
1.flt方法比较智能,生成的是xml;doc能支持低版本的wps,但是用的是遍历不太行。