项目中需要导出word文档,没写过就上网上搜了下,因为导出的word是有一定格式的,所以选择了依据模板导出的文章。为什么要在记录一遍呢,一是可以加深记忆,以后好找,二是因为踩了坑,在这里记录下来。
首先将实现代码记录下来:
工具类:
/**
* 导出word文档工具类
*
* @Author: ljp
* @CreateDate: 2021/1/27 10:20
*/
@Component
public class WordUtil {
/**
* description:
* 导出word模板
*
* @param params:模板中需要替换的参数可多个传递 比如 若想文字能够多行,在参数Map<String,Object>中的Object放入List<string>
* @param filename:导出的word文件名
* @param response:
* @return void
* @Author any
* @Date 2020/8/10 15:29
*/
public void exportWord(Map<String, Object> params, String filename, HttpServletResponse response) throws IOException, InvalidFormatException {
//固定模板
InputStream is = new ClassPathResource("/templates/" + "temp.docx").getInputStream();
XWPFDocument doc = new XWPFDocument(is);
iterateTable(params, doc);
OutputStream os = response.getOutputStream();
//设置导出的内容是doc
response.setContentType("application/octet-stream; charset=utf-8");
response.setHeader("Content-disposition", "attachment; filename=" + filename);
doc.write(os);
close(os);
}
//开始遍历文档中的表格
private void iterateTable(Map<String, Object> params, XWPFDocument doc) {
List<XWPFTableRow> rows = null;
List<XWPFTableCell> cells = null;
List<XWPFTable> tables = doc.getTables();
//遍历这个word文档的所有table表格
for (XWPFTable table : tables) {
rows = table.getRows();
//遍历这个表格的所有行
for (XWPFTableRow row : rows) {
cells = row.getTableCells();
//遍历这一行的单元格
for (XWPFTableCell cell : cells) {
//判断该单元格的内容是否是字符串字段
if (strMatcher(cell.getText()).find()) {
//替换字符串 字符串可以多行 也可以一行
replaceInStr(cell, params);
continue;
}
}
}
}
}
//返回模板中变量的匹配Matcher类
private Matcher strMatcher(String str) {
Pattern pattern = Pattern.compile("\\$\\{(.+?)\\}");
Matcher matcher = pattern.matcher(str);
return matcher;
}
//替换模板Table中相应字段为对应的字符串值
private void replaceInStr(XWPFTableCell cell, Map<String, Object> params) {
//两种数据类型 一种是直接String 还有一种是List<String>
String key = cell.getText().substring(2, cell.getText().length() - 1);
Integer datatype = getMapStrDataTypeValue(params, key);
List<XWPFParagraph> parags = cell.getParagraphs();
//先清空单元格中所有的段落
for (int i = 0; i < parags.size(); i++) {
cell.removeParagraph(i);
}
if (datatype.equals(0)) {
return;
} else if (datatype.equals(2)) {
//如果类型是2 说明数据类型是List<String>
List<String> strs = (List<String>) params.get(key);
Iterator<String> iterator = strs.iterator();
while (iterator.hasNext()) {
XWPFParagraph para = cell.addParagraph();
XWPFRun run = para.createRun();
run.setText(iterator.next());
}
} else if (datatype.equals(3)) {
String str = params.get(key).toString();
cell.setText(str);
}
}
//处理模板中的变量名字,去掉${} 然后根据这个变量名在参数map中查找对应的Value值
private Integer getMapStrDataTypeValue(Map<String, Object> params, String key) {
if (params.get(key) == null) {
return 0;
} else if (params.get(key) instanceof List) {
return 2;
} else if (params.get(key) instanceof String) {
return 3;
} else {
throw new RuntimeException("Str data type error!");
}
}
/**
* 关闭输入流
*
* @param is
*/
private void close(InputStream is) {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 关闭输出流
*
* @param os
*/
private void close(OutputStream os) {
if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
控制器代码实现:
@Autowired
private WordUtil wordUtil;
@GetMapping("/download")
public void download(HttpServletResponse response) throws IOException, InvalidFormatException {
Map<String, Object> paramMap = new HashMap<>(16);
paramMap.put("projectName", "张三");
paramMap.put("workSegment", "李四");
//模板的导出文件名字
String filename = "test.docx";
wordUtil.exportWord(paramMap, filename, response);
}
文档中的模板变量需要使用${}来包裹,如下图:
以上就是简单的实现,讲讲我遇到的坑,需求中的文档长这样:
最开始我是按照这个编辑的文本,因为有两层表格,所以读不到里面表格的内容,所以每次导出里面的变量都没有成功替换,一直纳闷跟人家写的一模一样,debug走了一遍才找到原因,去掉外边那一层边框以后就正常了。