poi-tl-ext + poi-tl实现复杂docx导出(解决导出速度慢问题)
需求描述
首先我们的需求是一个复杂的word导出,其中包含表格,富文本,图片以及文字,篇幅高达50页,我们使用传统的实测导出需要30分钟!!!这样的结果肯定不能接受,所以找出一个解决方案。
过程描述
大家对于富文本的导出,首先是不是都想到了我们的国产软件包spire.doc,简单集成,方便易用,仅需一行代码搞定。
Document doc = new Document();
doc.loadFromStream(new ByteArrayInputStream(sbf.getBytes(StandardCharsets.UTF_8)),
FileFormat.Html, XHTMLValidationType.None);
并且官方也提供了免费版的jar包,总体来说还是不错。但是通过这个方式导出会在性能方面有劣势。并且免费版支持500个段落,500张图片大概在3分钟左右。毕竟白嫖还是不错的,哈哈哈,在小量导出还是有着优势。
这里是官方链接,感兴趣的同学可以去看看:
https://www.e-iceblue.cn/Introduce/Spire-Doc-JAVA.html
优化思路
框架优化
上面的代码可以看出,虽然可以一行搞定,但是过程不可控,导致无法进行性能优化,所以需要换一个我们可以进行调优的导出工具,经过查找,终于找到一个大佬写的工具,叫做poi-tl-ext ,其中封装了HtmlRenderPolicy策略,对于大部分标签都支持,可以自己更改源码,提升效率!
使用案例:
//配置
Configure config = Configure.newBuilder().build();
AbstractRenderPolicy htmlRenderPolicy = null;
htmlRenderPolicy = HtmlToWordUtil.createHtmlRenderPolicy(null);
config.customPolicy("simpleImg", htmlRenderPolicy);
// // 通用模板 152
XWPFTemplate template = null;
template = XWPFTemplate.compile(HtmlToWordUtil.getResourceInputStream("/out_template.docx"), config).render(map);
try {
template.writeToFile("D:\\out_1.docx");
long endTime =System.currentTimeMillis();
System.out.println("use Time====================>"+(endTime -startTime)/1000);
} catch (IOException e) {
e.printStackTrace();
}
也可以先对模板数据进行绑定策略,再进行导出:
Configure config = Configure.newBuilder()
.bind("softwareList", policy)
.bind("hardwareList",policy)
.bind("intelligentDevicesList",policy)
.build();
XWPFTemplate template = XWPFTemplate.compile(file.getParentFile()
.getPath()+"/templates/xxx.docx",config);
绑定好了直接渲染数据:
template.render(basicInformation);
basicInformation我这里的这个对象其中的属性就是我在word模板的变量,你们替换为自己的即可。
资源优化
其实对于有外部资源引用的,网络以及资源大小也会影响导出效率,所以可以根据这个思路进行解决。
我们这里主要是对于图片进行压缩,在上传图片时,生成一个缩略图上传,添加一个mini后缀,实际使用的路径时压缩后的路径。
下面时压缩图片的方法:
//定义缩略图的全路径
String fileSuffix = "image.jpg";
String contextPath = dirPath + File.separator + fileSuffix;
//压缩图片
MultipartFile newFile = null;
try {
//自定义宽高压缩(压缩方法很多,可以根据需求更改)
Thumbnails.of(file.getInputStream()).forceSize(100, 100).toFile(contextPath);
//压缩完成后将缩略图生成MultipartFile
FileItem fileItem = createFileItem(contextPath);
newFile = new CommonsMultipartFile(fileItem);
} catch (IOException e) {
e.printStackTrace();
}
然后只需要在导出的时候进行标签替换就好了,我们是使用的Jsoup:
String sbf = "<html " +
"xmlns:v=\"urn:schemas-microsoft-com:vml\" xmlns:o=\"urn:schemas-microsoft-com:office:office\" xmlns:w=\"urn:schemas-microsoft-com:office:word\" xmlns:m=\"http://schemas.microsoft.com/office/2004/12/omml\" xmlns=\"http://www.w3.org/TR/REC-html40\"" + //将版式从web版式改成页面试图
">" +
"<head>" +
"<!--[if gte mso 9]><xml><w:WordDocument><w:View>Print</w:View><w:TrackMoves>false</w:TrackMoves><w:TrackFormatting/><w:ValidateAgainstSchemas/><w:SaveIfXMLInvalid>false</w:SaveIfXMLInvalid><w:IgnoreMixedContent>false</w:IgnoreMixedContent><w:AlwaysShowPlaceholderText>false</w:AlwaysShowPlaceholderText><w:DoNotPromoteQF/><w:LidThemeOther>EN-US</w:LidThemeOther><w:LidThemeAsian>ZH-CN</w:LidThemeAsian><w:LidThemeComplexScript>X-NONE</w:LidThemeComplexScript><w:Compatibility><w:BreakWrappedTables/><w:SnapToGridInCell/><w:WrapTextWithPunct/><w:UseAsianBreakRules/><w:DontGrowAutofit/><w:SplitPgBreakAndParaMark/><w:DontVertAlignCellWithSp/><w:DontBreakConstrainedForcedTables/><w:DontVertAlignInTxbx/><w:Word11KerningPairs/><w:CachedColBalance/><w:UseFELayout/></w:Compatibility><w:BrowserLevel>MicrosoftInternetExplorer4</w:BrowserLevel><m:mathPr><m:mathFont m:val=\"Cambria Math\"/><m:brkBin m:val=\"before\"/><m:brkBinSub m:val=\"--\"/><m:smallFrac m:val=\"off\"/><m:dispDef/><m:lMargin m:val=\"0\"/> <m:rMargin m:val=\"0\"/><m:defJc m:val=\"centerGroup\"/><m:wrapIndent m:val=\"1440\"/><m:intLim m:val=\"subSup\"/><m:naryLim m:val=\"undOvr\"/></m:mathPr></w:WordDocument></xml><![endif]-->" +
"</head>" +
"<body>" +
// 富文本内容
body +
"</body></html>";
//加载html文件
org.jsoup.nodes.Document document = Jsoup.parse(sbf);
Elements elements = document.getElementsByTag("img");
elements.attr("width","400px").attr("height","auto");
elements.forEach(element -> {
String src = element.attr("src");
String suffix = FileUtil.getSuffix(src);
element.attr("src",src.substring(0,src.lastIndexOf("."))+"_mini."+suffix);
});
String html = document.outerHtml();
用户体验优化
虽然可以实现分钟级下载,但是为了用户体验,还是做成异步处理。大家无论是使用线程还是定时任务我就不做赘述了。
依赖整理
总所周知,poi的依赖总是会各种冲突,经过我血与泪的教训与实验,整理如下:
<dependency>
<groupId>io.github.draco1023</groupId>
<artifactId>poi-tl-ext</artifactId>
<version>0.4.5</version>
<exclusions>
<exclusion>
<artifactId>commons-io</artifactId>
<groupId>commons-io</groupId>
</exclusion>
<exclusion>
<artifactId>poi-tl</artifactId>
<groupId>com.deepoove</groupId>
</exclusion>
<exclusion>
<artifactId>poi-ooxml-schemas</artifactId>
<groupId>org.apache.poi</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.10.0</version>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.14.2</version>
</dependency>
资源链接
下面是poi-tl-ext大佬的github地址:
github
问题解决
对于表格导出大家可能会遇到没有边框问题,这是因为poi的行为导致,在表格单元格内插入表格时会先将所有内容全部清空。
直接在加载策略的时候设置一个属性就好:
HtmlRenderPolicy htmlPolicy = new HtmlRenderPolicy();
htmlPolicy.getConfig().setShowDefaultTableBorderInTableCell(true);