背景
业务需求,对系统中任务信息需要做下载打印,之前用是``poi-tl``实现的下载word(实现方式可参考根据模板导出word文档),现在需求变为下载pdf。补充信息:客户侧的服务器是离线服务器,尽量不在服务器安装第三方服务。
方案调研
关于word转pdf网上找到的可行方案大概5种,分别是:aspose-words、docx4j、openoffice、poi、spire.doc,综合网上信息得出的结论如下:aspose-words、spire.doc都是商业jar包,且费用较高,但是两个jar的生成效果确实是最好的,开源jar中openoffice是效果最好的,但是需要安装openoffice服务。
我的场景相对简单,需要转pdf的word只是一张表格,且字体只有“宋体”、“黑体”,所以还是自己测试对比了aspose-words、docx4j、poi的效果,因spire.doc同为商业软件且性能不如aspose-words,所以未作测试,openoffice依赖服务器安装openoffice服务,不便于迁移,所以也放弃了。测试效果与上面的结论基本一致,aspose-words最佳,docx4j把一页word变成了两页的pdf,第二页为空白页。poi因引入jar与项目中已有poi有冲突所以放弃。
方案确定
根据以上方案调研结果最终选择了aspose-words,具体实现如下:
1. 引入jar
<dependency>
<groupId>com.aspose</groupId>
<artifactId>aspose-words</artifactId>
<version>21.1</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/aspose-words-21.1-jdk17-cracked.jar</systemPath>
</dependency>
2. 代码实现
可以先将前面的word写入临时文件,Document根据文件路径读临时文件,也可以将word模板文件输出到流中,如下代码实现Document读流。
@SneakyThrows
public static void exportPdfFromInputStream(InputStream input, String fileName, HttpServletResponse response) {
Document doc = new Document(input);
OsInfo osInfo = SystemUtil.getOsInfo();
if(osInfo.isLinux()){
log.info("os is linux,加载字体文件...");
FontSettings.getDefaultInstance().setFontsFolder("/fonts/path",false);
}
response.setContentType("application/pdf;charset=UTF-8");
fileName = URLEncoder.encode(fileName,"UTF-8");
response.setHeader("Content-Disposition", "attachment;filename="+fileName+";"+"filename*=utf-8''"+fileName);
try (ServletOutputStream os = response.getOutputStream()) {
doc.save(os, SaveFormat.PDF);
}
}
3. 注意事项
springboot默认打包不会把scope=system的文件打入项目中,修改pom文件
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.5.RELEASE</version>
<!-- 打包时会将本地jar一起打包 -->
<configuration>
<includeSystemScope>true</includeSystemScope>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
4. jar下载地址
网上有很多aspose-words去水印版的jar下载,推荐两个地址:
一个是鱼
码云地址https://gitee.com/liuzy1988/aspose-words-crack
一个是渔
吾爱破解论坛https://www.52pojie.cn/thread-1824449-1-1.html
方案优化
以上所有方案在服务器部署均会遇到中文字体乱码的问题,通用解决方法有两种,将windows字体库C:\Windows\Fonts复制到linux字体库/usr/share/fonts,
方法1是服务器安装字体,https://segmentfault.com/a/1190000040275198
方法2是将linux中的字体路径指给程序,如上代码即如此实现FontSettings.getDefaultInstance().setFontsFolder("/fonts/path",false)。本项目中涉及字体较少,可以采用第三种方案,将字体文件打包到项目jar中。
1. 代码实现
在工具类初始化时,加载jar中的字体文件
OsInfo osInfo = SystemUtil.getOsInfo();
if(osInfo.isLinux()){
log.info("os is linux,加载字体文件...");
InputStream sunStream = AsposeUtil.class.getClassLoader().getResourceAsStream("fonts/simsun.ttc");
byte[] fontData = streamToByteArray(sunStream);
MemoryFontSource heiSource = new MemoryFontSource(fontData);
FontSettings.getDefaultInstance().setFontsSources(new FontSourceBase[]{heiSource});
}
2. 注意点
InputStream中读取数据不全的问题
InputStream读字节数组时,直接使用inputStream.read(byte[inputStream.available]),会出现字节流读取不全的问题,可以参考如下实现,或者使用commons-io包中的IOUtils.toByteArray(inputStream)
public static byte[] streamToByteArray(InputStream in) throws IOException {
ByteArrayOutputStream output = new ByteArrayOutputStream();
byte[] buffer = new byte[4096];
int n;
while (-1 != (n = in.read(buffer))) {
output.write(buffer, 0, n);
}
byte[] res = output.toByteArray();
in.close();
output.close();
return res;
}