最近,
项目有这样一个需求:
根据我选择的模板(docx文件),和我表单填的数据,生成相应的合同文件(docx),该合同要能网页在线浏览/打印/下载在合同中还要放置签字图片和身份证图片
我的实现:
1上传一份合同模板,在用户提交表单数据的时候,把数据写进合同模板,生成完整合同单独保存。这里需要用到docx模板插件能插入图片的那种(身份证读卡器和签字生成的图片要放进合同里)
2再把这份文件后缀是docx的合同文件转换成PDF,再存一份(因为要浏览,刚好多数浏览器支持网页浏览PDF文件和打印下载,不用我自己去实现,想着再转存一份pdf文件,就能实现需求了)。这里需要用到转PDF插件
3等于每个生成的合同都有两份,一份docx源文件,一份PDF格式副本。
4当我页面需要网页在线浏览/打印/下载的时候,就把PDF的副本放上去用另外一个PDF浏览打印插件去实现。这里需要用到PDF浏览打印的js(展示在网页上的是合同的PDF文件,可以浏览/打印/下载)
总共三个插件,本着开源(免费)的态度去找,最终淘汰了一些,我选择了以下三款插件
分别是:
1,poi-tl:docx模板引擎,作用是根据输入的符号把他替换成相应的值
模板:
生成后:
坑点:相关依赖jar多,少个包报错排查半天没查出来,还有就是它依赖poi的版本既不能太高,也不能低,不然报错。我用的是3.17
2,PDFObject:PDF浏览打印的js,只要导入这个js,把pdf文件所在的统一资源定位地址输进去就是,记住!不是绝对路径,是映射过的虚拟路径
3,liberOffice:基于office文件转换PDF文件的插件。
这个有点坑,容我细说:
模板生成工具类涉及的插件我查了后就定了两个(免费的)。分别是liberOffice和OpenOffice,他们各有优劣
liberOffice生成的pdf精准度高,配置相对简单,不需要敲指令,但是生成速度很慢,一个文件需要等待数秒以上,影响用户体验,有时会崩溃,感觉没OpenOffice稳定。
OpenOffice生成的pdf精准度低,可能失真和样式混乱。配置麻烦,每次重启服务器都要敲指令,但是生成速度很快很快,很少用时超过一秒的。
这是二者差别,因为插件都是用来生成合同用的,所以要高精准度,我选择了liberOffice,牺牲了速度(性能)。
下面附上我的工具类
根据LiberOffice生成pdf工具类,入参为文件全路径,执行完生成一份地址和名字一模一样的PDF文件,还需要配一下LiberOffice安装路径
package com.hk.Labor.utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.commons.io.FileUtils;
import org.artofsolving.jodconverter.OfficeDocumentConverter;
import org.artofsolving.jodconverter.office.DefaultOfficeManagerConfiguration;
import org.artofsolving.jodconverter.office.OfficeManager;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.regex.Pattern;
/**
* 文件格式转换PDF 可转换的文件类型:各种office文件,图片以及普通txt
*/
public class DocConverter {
protected static final Logger LOG = LoggerFactory.getLogger(DocConverter.class);
private String fileString;
private String outputPath = "";// 输入路径 ,如果不设置就输出在默认 的位�?
private String fileName;
private File pdfFile;
private File docFile;
public DocConverter(String fileString) {
ini(fileString);
System.out.println("文件路径"+fileString);
}
public DocConverter() {
// TODO 自动生成的构造函数存根
}
/**
* * 重新设置file
*
* @param fileString
* 32.
*/
public void setFile(String fileString) {
ini(fileString);
}
/**
* * 初始
*
* @param fileString
*
*/
private void ini(String fileString) {
this.fileString = fileString;
fileName = fileString.substring(0, fileString.lastIndexOf("."));
docFile = new File(fileString);
pdfFile = new File(fileName+ ".pdf");
}
/**
* 转PDF方法(LibleOffice插件)
*
* @param file docFile, pdfFile
*
*/
public void doc2pdf() throws Exception {
String LibreOffice_HOME = getLibreOfficeHome();
String fileName = docFile.getName();
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH-mm-ss").format(new Date()) + "文件" + docFile.getName());
System.out.println(fileName.substring(fileName.lastIndexOf(".")));
if (fileName.substring(fileName.lastIndexOf(".")).equalsIgnoreCase(".txt")) {
System.out.println("处理txt文件");
new DocConverter().TXTHandler(docFile);
}
DefaultOfficeManagerConfiguration configuration = new DefaultOfficeManagerConfiguration();
// libreOffice的安装目录
configuration.setOfficeHome(new File(LibreOffice_HOME));
// 端口号
configuration.setPortNumber(8100);
configuration.setTaskExecutionTimeout(1000 * 60 * 25L);
// 设置任务执行超时为10分钟
configuration.setTaskQueueTimeout(1000 * 60 * 60 * 24L);
// 设置任务队列超时为24小时
OfficeManager officeManager = configuration.buildOfficeManager();
officeManager.start();
System.out.println(new Date().toString() + "开始转换......");
OfficeDocumentConverter converter = new OfficeDocumentConverter(officeManager);
converter.getFormatRegistry();
try {
converter.convert(docFile, pdfFile);
} catch (Exception e) {
e.printStackTrace();
System.out.println("转换失败");
} finally {
officeManager.stop();
}
System.out.println(new Date().toString() + "转换结束....");
}
static String loadStream(InputStream in) throws IOException {
int ptr = 0;
in = new BufferedInputStream(in);
StringBuffer buffer = new StringBuffer();
while ((ptr = in.read()) != -1) {
buffer.append((char) ptr);
}
return buffer.toString();
}
/**
* 打开libreOffice服务的方法
*
* @return
*/
public String getLibreOfficeHome() {
String osName = System.getProperty("os.name");
if (Pattern.matches("Linux.*", osName)) {
//获取linux系统下libreoffice主程序的位置
//这里需要返回libreOffice安装路径,我的路径写在了自己建的Properties文件里
return Utils.getProperties("libre_office_linux");
} else if (Pattern.matches("Windows.*", osName)) {
//获取windows系统下libreoffice主程序的位置
//这里需要返回libreOffice安装路径,我的路径写在了自己建的Properties文件里
return Utils.getProperties("libre_office_windows");
}
return null;
}
//测试方法
public static void main(String[] args) throws Exception {
String printfPDFPath = "E:/Labor/tplfile/党建交接.docx";
printfPDFPath = printfPDFPath.replace("/", "\\\\");
DocConverter demo = new DocConverter(printfPDFPath);
demo.doc2pdf();
}
/**
* 转换txt文件编码的方法
*
* @param file
* @return
*/
public File TXTHandler(File file) {
//或GBK
String code = "gb2312";
byte[] head = new byte[3];
try {
InputStream inputStream = new FileInputStream(file);
inputStream.read(head);
if (head[0] == -1 && head[1] == -2) {
code = "UTF-16";
} else if (head[0] == -2 && head[1] == -1) {
code = "Unicode";
} else if (head[0] == -17 && head[1] == -69 && head[2] == -65) {
code = "UTF-8";
}
inputStream.close();
System.out.println(code);
if (code.equals("UTF-8")) {
return file;
}
String str = FileUtils.readFileToString(file, code);
FileUtils.writeStringToFile(file, str, "UTF-8");
System.out.println("转码结束");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return file;
}
}
根据OpenOffice生成pdf工具类,入参为文件全路径,执行完生成一份地址和名字一模一样的PDF文件,但每次开机都需要启动一条dos指令
package com.hk.Labor.utils;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.artofsolving.jodconverter.DocumentConverter;
import com.artofsolving.jodconverter.openoffice.connection.OpenOfficeConnection;
import com.artofsolving.jodconverter.openoffice.connection.SocketOpenOfficeConnection;
import com.artofsolving.jodconverter.openoffice.converter.OpenOfficeDocumentConverter;
import com.mysql.jdbc.log.Log4JLogger;
/**
* OpenOffice转换PDF工具
* 想使用它还需要dos端口启用指令:soffice -headless -
* accept="socket,host=127.0.0.1,port=8100;urp;" -nofirststartwizard该指令需在OpenOffice
* 安装目录下的DOS窗口输入启用,且每次重启服务器都用重新启动该指令,OpenOffice这点麻烦
*/
public class DocOpenOfficeUtil {
protected static final Logger LOG = LoggerFactory.getLogger(DocConverter.class);
private String fileString;
private String outputPath = "";// 输入路径 ,如果不设置就输出在默认 的位
private String fileName;
private File pdfFile;
private File docFile;
public DocOpenOfficeUtil(String fileString) {
ini(fileString);
}
/**
* * 閲嶆柊璁剧疆file
*
* @param fileString
* 32.
*/
public void setFile(String fileString) {
ini(fileString);
}
/**
* * 鍒濆锟�?
*
* @param fileString
*
*/
private void ini(String fileString) {
this.fileString = fileString;
fileName = fileString.substring(0, fileString.lastIndexOf("."));
docFile = new File(fileString);
pdfFile = new File(fileName+ ".pdf");
}
/**
* 转PDF方法(OpenOffice插件)
*
* @param file
*
*/
public void doc2pdf() throws Exception {
if (docFile.exists()) {
// if (!pdfFile.exists()) {
OpenOfficeConnection connection = new SocketOpenOfficeConnection(8100);
try {
connection.connect();
DocumentConverter converter = new OpenOfficeDocumentConverter(
connection);
converter.convert(docFile, pdfFile);
// close the connection
connection.disconnect();
System.out.println("****pdf转换中? "+ pdfFile.getPath() + "****");
} catch (java.net.ConnectException e) {
e.printStackTrace();
System.out.println("****pdf转换失败****");
throw e;
} catch (com.artofsolving.jodconverter.openoffice.connection.OpenOfficeException e) {
e.printStackTrace();
System.out.println("****pdf转换失败****");
throw e;
} catch (Exception e) {
e.printStackTrace();
throw e;
}
//} else {
// System.out.println("****宸茬粡杞崲涓簆df锛屼笉锟�?瑕佸啀杩涜杞寲 ****");
//}
} else {
System.out.println("*****pdf转换失败,文件不存在****");
}
}
static String loadStream(InputStream in) throws IOException {
int ptr = 0;
in = new BufferedInputStream(in);
StringBuffer buffer = new StringBuffer();
while ((ptr = in.read()) != -1) {
buffer.append((char) ptr);
}
return buffer.toString();
}
}
使用方法:
//liberOffice
DocConverter demo = new DocConverter(文件路径);
demo.doc2pdf();
//OpenOffice
DocOpenOfficeUtil demo2 = new DocOpenOfficeUtil(文件路径);
demo2.doc2pdf();
docx模板工具方法:
/**
* 替换模板字段生成新的合同docx,方法内有详细注解说明
* @param InputPath 模板的地址(绝对路径)例:"E:/Labor/tplfile/20181204101817265_146.docx"
* @param OutputPath 输出生成的docx文档的路径(绝对路径)例:"E:/Labor/tplfile/20181204101817265_146.docx"
* @param replaceMap 要替换的所有字段或图片的Map
* @return boolean 成功true,失败false 成功后,会同时生成pdf,docx格式的两份文件,路径跟docx文档的路径一样,名字也一样,只是后缀名不同。
* @throws IOException
*/
public boolean templateChangeAndGenerate(String InputPath,String OutputPath,Map replaceMap) {
try {
//一行代码
XWPFTemplate template = XWPFTemplate.compile(InputPath).render(replaceMap);
//输出目录
FileOutputStream out;
out = new FileOutputStream(OutputPath);
template.write(out);
out.flush();
out.close();
OutputPath = OutputPath.replace("/", "\\\\");
//DocConverter demo = new DocConverter(OutputPath);
//demo.doc2pdf();
template.close();
} catch (Exception e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
return false;
}
return true;}
主代码涉及保密协议,我不发了。这个方案是实现了这个需求,我之前是只要用户点生成合同,就同时执行转换PDF方法。但是因为用LiberOffice的转化PDF速度太慢,而合同又是先生成并同时转换PDF,导致生成一个合同就要等半天,批量生成简直噩梦,严重影响用户体验,所以我把转换PDF方法放在了用户点击浏览后才触发生成PDF副本用来页面展示,而生成合同时,仅仅生成一份docx合同原件,不调用转换方法。然后用户体验好多了。
还有就是占存储空间,每份合同附带了分PDF副本。
坑1:如果用户上传的word模板中的字体你电脑中的字体库里没有,那么生成的PDF样式也会混乱,但只要电脑里安装了字体包就没事了
坑2:Liberoffice和OpenOffice是同源产品,如果想使用OpenOffice就必须把Liberoffice卸载干净,相反使用Liberoffice也是一样。