本文主要叙述了Java根据Docx模板生成文件的流程以及一些相关经验。
在这里记录一下在工作中遇到的一个问题:将客户提供的合同模板填充数据库中查询出来的数据,然后在合同的页眉添加防伪二维码或水印再提供给客户下载。在写的过程中走了一些弯路,不过好在最后还是成功解决~
前言
首先,客户提供的模板非常多有Xls,Xlsx,Docx这三种格式,我首先看了下网上的解决方案:有的是用 Apache POI 包直接来进行读写,当然也有GitHub上大佬封装好的jar包底层也是调用的 Apache POI 包但是操作简化了很多。我这里用到了2个包:Poi-tl Documentation,AutoPoi 其中 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); }).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拦截器给拦截了,并做了其他的处理。