本文主要叙述了Java根据Docx模板生成文件的流程以及一些相关经验。

​ 在这里记录一下在工作中遇到的一个问题:将客户提供的合同模板填充数据库中查询出来的数据,然后在合同的页眉添加防伪二维码或水印再提供给客户下载。在写的过程中走了一些弯路,不过好在最后还是成功解决~

前言

​ 首先,客户提供的模板非常多有Xls,Xlsx,Docx这三种格式,我首先看了下网上的解决方案:有的是用 Apache POI 包直接来进行读写,当然也有GitHub上大佬封装好的jar包底层也是调用的 Apache POI 包但是操作简化了很多。我这里用到了2个包:Poi-tl DocumentationAutoPoi 其中 AutoPoi 是 Excel 和 Word 都能进行操作的而 Poi-tl 则是专注于操作 Docx 。

​ 因为 AutoPoi 是大部分 Office 文件都能操作的 所以我首先选择使用了它,按照文档上的提示替换好所有模板中的标签的后,测试 Docx 的文件没啥问题,但是 Excel 的文件如果涉及到文件中有多个明细行要写入的时候 文档下面的内容就会错位… 最后我将问题定位到客户提供的模板上,只要将模板中表格的“自动换行” 属性去除掉再导出的文件就正常了✌。

​ 前面提到导出的文件是要添加水印和防伪二维码的,Word 仍然是没有什么问题,只不过 Excel 文件添加水印和防伪二维码非常困难🙃,首先 Excel 是不支持“水印”这个功能的,想要做到类似的网上的方法大致分为2种 :设置 Excel 的背景图片但是这种方式打印的时候不能将“水印”打印出来,第二种是使用“艺术字”功能程序在 Excel 文件上按照一定的逻辑贴上,这种方法虽然可以打印出来但用户可以轻松的编辑“艺术字”所以也是不可取的,再说说防伪二维码:Excel 在页眉上写一张图片的方法也是非常少的,试了很多大多都不可用。

​ 最后的解决方案是:和客户协商将所有的 Excel 文件都转为Docx文件,后端生成 Jar 包则从 AutoPoi 换为 Poi-tl ,这样就不用去支持Excel 了!🤣🤣 Poi-tl 设置页眉非常方便且语法和 AutoPoi 很相近,稍作修改就能兼容。下面简单说下用法。

代码

将一个普通的值放到文件中

在后端中这样写:

1
2
3
4
XWPFTemplate template = XWPFTemplate.compile("template.docx").render(  
new HashMap<String, Object>(){{put("title", "Hi, poi-tl Word模板引擎");}}
);
template.writeAndClose(new FileOutputStream("output.docx"));

在模板中设置标签{{title}}到对应位置

将一个表格放到文件中

后端中需要这样写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//组装表格列表数据
List<Map<String,Object>> detailList=new ArrayList<Map<String,Object>>();
for (int i = 0; i < 6; i++) {
Map<String,Object> detailMap = new HashMap<String, Object>();
detailMap.put("index", i+1);//序号
detailMap.put("goods", "商品"+i);//商品名称
detailList.add(detailMap);
}
HackLoopTableRenderPolicy policy = new HackLoopTableRenderPolicy();
Configure config = Configure.newBuilder().bind("detailList", policy).build();
XWPFTemplate template = XWPFTemplate.compile("填写模板地址", config).render(
new HashMap<String, Object>() {{
put("detailList", detailList);
put("title", "Hi, poi-tl Word模板引擎");
}}
);
FileOutputStream fos = new FileOutputStream("填写实例化路径");
template.write(fos);

模板中需要这样写:

{{title}}

{{detailList}}序号 商品名称
[index] [goods]

页眉添加防伪二维码

Poi-tl支持很多格式的图片,我这里用的是 Byte[] 形式的 PNG 二维码图片,生成二维码的 Jar 包很多,这里不再深究。

后端中需要这样写:

1
map.put("picture",PictureRenderData(50, 50, PictureType.PNG, Byte[]形式的PNG对象));

在模板中图片标签以@开始:{{@picture}} 且只要将{{@picture}}标签放到页眉中就可以做到每页都有防伪二维码的效果!

模板获取

如果你的模板在 resource 下,那么我推荐你使用相对位置获取输入流,这样可以防止在 Linux 上部署时找不到模板~✨

1
InputStream mouldPath = this.getClass().getResourceAsStream("/templates/1.docx);

打好包的文件如果不能读取或者读取的是损坏的文件那么可能是 Maven 对文件进行了编译导致文件损坏,这时需要在项目根pom文件添加以下过滤内容,其中的nonFilteredFileExtension节点按需填写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>${maven-resources-plugin.version}</version>
<configuration>
<encoding>UTF-8</encoding>
<nonFilteredFileExtensions>
<nonFilteredFileExtension>xls</nonFilteredFileExtension>
<nonFilteredFileExtension>xlsx</nonFilteredFileExtension>
<nonFilteredFileExtension>doc</nonFilteredFileExtension>
<nonFilteredFileExtension>docx</nonFilteredFileExtension>
<nonFilteredFileExtension>pdf</nonFilteredFileExtension>
</nonFilteredFileExtensions>
</configuration>
</plugin>

前端下载

Controller层

1
2
3
4
5
6
7
8
9
10
11
12
public ApiResult<Boolean> exportDataWord(HttpServletResponse response){
XWPFTemplate template = 填充好数据后返回的XWPFTemplate对象;
String fileName = "文件名.docx"
response.setContentType("application/force-download");
response.addHeader("Content-Disposition", "attachment;fileName=" + fileName);
OutputStream out = response.getOutputStream();
template.write(out);
out.flush();
out.close();
template.close();
return ApiResult.result(true);
}

Vue前端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
this.$http.request({
url: url,
method: "POST",
showLoading: true,
responseType: "blob"
}).then((res) => {
let blob = new Blob([res],{ type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'});
let downloadElement = document.createElement("a");
let href = window.URL.createObjectURL(blob); //创建下载的链接
downloadElement.href = href;
downloadElement.download = new Date().getTime()+".docx"; //下载后文件名
document.body.appendChild(downloadElement);
downloadElement.click(); //点击下载
document.body.removeChild(downloadElement); //下载完成移除元素
window.URL.revokeObjectURL(href); //释放掉blob对象
}).finally(() => {
this.Query();
});

其中 type 参数按需填写

文件后缀 blob对应的type
.doc application/msword
.docx application/vnd.openxmlformats-officedocument.wordprocessingml.document
.xls application/vnd.ms-excel
.xlsx application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
.txt text/plain

友情提示:如果下载不了可以调试前端看看是不是被$http拦截器给拦截了,并做了其他的处理。

Comments

⬆︎TOP