在项目中很容易会遇到需要动态生成pdf的应用场景,其实现方式也比较多
由于项目的关系,对于这种组件性的开发方式我想的是怎么方便怎么来,怎么快就怎么来
在咨询了之前做政务系统的同学后,他们都一致推荐我使用aspose框架来实现,因为它的效率高并且简单方便使用
在看了下aspose的官网和对demo简单体验了之后感觉确实很方便,为了后面项目中万一又遇到有需要的场景可以信手拈来,这里我简单在此处记录一下
下载地址
https://downloads.aspose.com/words/java
选择版本:Aspose.Words for Java 19.1
(之所以选择那个版本,是因为网上有很多这个版本的pj教程,太新的担心pj不了,当然不pj也可以用但是有水印)
如果直接下载jar的话也可通过aspose官方的maven仓库地址进行下载:
https://repository.aspose.com/repo/com/aspose/aspose-words/19.1/aspose-words-19.1-jdk16.jar
pj教程:
https://www.jianshu.com/p/be0cbdc389cc
也可以直接下载已破解好的jar,如果自己能找到的话
使用示例
这里演示一个目前我的项目中可能需要满足的一个需求,通过一个word模板生成pdf,动态填充word中的内容,同时支持生成多个pdf但要求pdf合并到一个pdf中
添加maven依赖
将下载好的jar放到项目中,然后添加依赖
<!--aspose-words依赖,直接依赖本地项目中的jar-->
<dependency>
<groupId>com.aspose</groupId>
<artifactId>aspose-words</artifactId>
<version>aspose-words-19.1-jdk16.jar</version>
<scope>system</scope>
<systemPath>${pom.basedir}/libs/aspose-words-19.1-jdk16-crack.jar</systemPath>
</dependency>
如果用本地jar打包时记得添加资源配置:
<build>
<resources>
<resource>
<directory>libs</directory>
<targetPath>BOOT-INF/lib/</targetPath>
<includes>
<include>**/*.jar</include>
</includes>
</resource>
</resources>
</build>
word模板准备
我们的最终目的是生成pdf,但为了方便还是先通过word模板生成word之后再输出为pdf。
为了排版方便建议还是使用word中的表格来进行排版,对于可变的地方可以添加【文字型窗体域】便于后面程序的文字进行填充
在word模板准备好了之后就可以直接开始写代码了
测试代码
import com.aspose.words.*;
import java.io.File;
import java.io.FileOutputStream;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
/**
* @author puhaiyang
* @date 2020/10/11 12:12
* 测试aspose生成pdf工具类
*/
public class AsposeWordsUtils {
/**
* Word转PDF操作
*
* @param doc 源文件
* @param targetFile 目标文件
*/
public static void doc2pdf(Document doc, String targetFile) {
try {
long old = System.currentTimeMillis();
//新建一个空白pdf文档
File file = new File(targetFile);
FileOutputStream os = new FileOutputStream(file);
//全面支持DOC, DOCX, OOXML, RTF HTML, OpenDocument, PDF, EPUB, XPS, SWF 相互转换
doc.save(os, SaveFormat.PDF);
os.close();
long now = System.currentTimeMillis();
//转化用时
System.out.println("共耗时:" + ((now - old) / 1000.0) + "秒");
} catch (Exception e) {
e.printStackTrace();
}
}
private static class UserInfo {
public UserInfo(String userNumber, String userName, String depaName, String desc, Date pubDate) {
this.userNumber = userNumber;
this.userName = userName;
this.depaName = depaName;
this.desc = desc;
this.pubDate = pubDate;
}
private String userNumber;
private String userName;
private String depaName;
private String desc;
private Date pubDate;
public String getUserNumber() {
return userNumber;
}
public void setUserNumber(String userNumber) {
this.userNumber = userNumber;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getDepaName() {
return depaName;
}
public void setDepaName(String depaName) {
this.depaName = depaName;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public Date getPubDate() {
return pubDate;
}
public void setPubDate(Date pubDate) {
this.pubDate = pubDate;
}
}
public static void main(String[] args) throws Exception {
ArrayList<UserInfo> userInfoList = getUserInfoList();
//模板word
String template = "C:/Users/ping/Desktop/temp/aspose/test/forTest.docx";
Document firstDocument = null;
for (UserInfo userInfo : userInfoList) {
//目标word
Document document = new Document(template);
Range range = document.getRange();
range.replace("ygbh", userInfo.getUserNumber(), new FindReplaceOptions());
range.replace("ygxm", userInfo.getUserName(), new FindReplaceOptions());
range.replace("szbm", userInfo.getDepaName(), new FindReplaceOptions());
range.replace("zygx", userInfo.getDesc(), new FindReplaceOptions());
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
LocalDateTime localDateTime = LocalDateTime.ofInstant(userInfo.getPubDate().toInstant(), ZoneId.systemDefault());
range.replace("bfrq", formatter.format(localDateTime), new FindReplaceOptions());
if (firstDocument == null) {
firstDocument = document;
} else {
//添加文档
firstDocument.appendDocument(document, ImportFormatMode.KEEP_DIFFERENT_STYLES);
}
}
doc2pdf(firstDocument, "C:/Users/ping/Desktop/temp/aspose/test/descForTest.pdf");
}
private static ArrayList<UserInfo> getUserInfoList() {
ArrayList<UserInfo> userInfoList = new ArrayList<UserInfo>();
userInfoList.add(new UserInfo("666001", "海洋哥",
"滑水部-酱油项目组-基础架构组",
"研究如何滑水及如何深入打酱油,对如何平滑滑水取得了重要贡献", new Date()));
userInfoList.add(new UserInfo("666002", "帅哥",
"核心项目事业部-超级潜水部-默默潜水研究组-项目三组",
"对如何潜水取得了重要贡献", new Date()));
return userInfoList;
}
}
整体来看代码还是很少的,写完后直接运行跑一下
输出结果
输出文件
生成的pdf文件预览
效果还是可以的,文字该换行显示的还是换行显示了,效果不错代码执行效率也很快,非常简单。
以后再遇到要求项目中实现动态生成PDF简直就是分分钟的事呀!nice~
linux下导出pdf乱码解决办法
如果在windows下导出pdf一切正常,但在linux下导出pdf中文乱码了,则需要通过添加字体文件并在aspose的代码中指定所在的字体文件
//指定字体目录
FontSettings.getDefaultInstance().setFontsFolder("/opt/user/otherfonts/", false);
//执行保存
doc.save(pdfPath);
具体办法是先找到字体对应的字体文件,在windows不报错的话就从windows上先找下, windows的字体文件都在这个目录下
C:\Windows\Fonts\
方式一,直接部署方式
找到对应的字体然后拷贝到linux某个目中即可
方式二,docker部署方式
但是如果是docker的话,则可以通过volume映射字体目录或通过Dockerfile直接将字体打在docker镜像中,如果打在镜像中的话会导致镜像文件很大
方式三,通过k8s方式
如果是通过kubernetes来部署的项目可以通过pvc来搞定,但如果没得对应的文件服务器,则可以用过init-containers配合emptyDir类型的volumes来解决,参考地址:https://kubernetes.io/docs/concepts/workloads/pods/init-containers/
{
"template": {
"spec": {
"containers": [
{
"image": "xxxxxxxxxxxxxxxxxxxx",
"volumeMounts": [
{
"mountPath": "/opt/user/otherfonts",
"name": "font-depend"
}
]
}
],
"initContainers": [
{
"command": [
"sh",
"-c",
"set -ex;mkdir -p /vmfontsdepend/fonts;cp -r /otherfonts/* /vmfontsdepend/fonts;"
],
"image": "ccr.ccs.tencentyun.com/haiyang/font-depend-image:1.0.0",
"imagePullPolicy": "IfNotPresent",
"name": "init-font-depends",
"volumeMounts": [
{
"mountPath": "/vmfontsdepend/fonts",
"name": "font-depend"
}
]
}
],
"volumes": [
{
"emptyDir": {},
"name": "font-depend"
}
]
}
}
}
其中ccr.ccs.tencentyun.com/haiyang/font-depend-image:1.0.0的Dockerfile为:
FROM alpine:3.8
LABEL maintainer="puhaiyang"
ADD fonts/* /otherfonts/
fonts目录下为需要导入的字体文件
参考资料
查找并替换docx的中文字(模板生成)
https://github.com/aspose-words/Aspose.Words-for-Java/blob/master/Examples/src/main/java/com/aspose/words/examples/quickstart/FindAndReplace.java
多个docx文档合并为一个(文档合并)
https://github.com/aspose-words/Aspose.Words-for-Java/blob/master/Examples/src/main/java/com/aspose/words/examples/quickstart/AppendDocuments.java
docx转为pdf
https://github.com/aspose-words/Aspose.Words-for-Java/blob/master/Examples/src/main/java/com/aspose/words/examples/loading_saving/ConvertToPDF.java