Windows/Linux环境下安装并使用Libreoffice(SpingBoot 整合Libreoffice)&Linux字体库新增字体
现有功能需求需将word模板转成pdf的方式,进行在线预览或打印,实现这个需求有几种当时,如下:
1、使用jacob:word模板需要创建书签,以替换书签的方式来填充数据。然后转成pdf的格式。使用简单方便,但是只能在windows环境下使用,不能在linux环境使用
2、使用aspose.words:通过创建word模板的一个域的标识,来替换相应的数据,具体百度,也是使用特别方便的一个组件,基本上手就能用,能在windows和Linux下使用,可惜是商用的,项目需要发布上线的话还是先获取版权的好,个人使用的话倒是没所谓。
3、使用POI:word模板需要创建特殊的标识,如:{name},来标识需要替换的数据位置,操作word文档简单,可在windows和Linux下使用,但是使用POI将word转成pdf时,会出现pdf与word模板格式不一致的问题,格式会错乱。
综上考虑实现word转pdf,并能在Linux环境下使用,采用POI+Libreoffice的方式实现,其中POI实现替换word模板中特殊标识去替换数据,使用Libreoffice是将替换好的word文档转成pdf,转成的pdf格式和word模板的一致。
文末附上实现的代码案例,使用Libreoffice需先本机安装
windows环境下安装Libreoffice
准备好安装包,双击安装
安装好的目录如下:program
安装完后需要编写一个 Libreoffice的启动脚本
Libreoffice的启动脚本命名为:startLibreOffice.bat
脚本位置随意,调用的时候需要用到,配置文件写明位置即可
编辑脚本如下:其中E:\Install\program\soffice.exe为我的Libreoffice安装路径文件启动位置,个人安装目录不同自行修改
taskkill /f /fi "IMAGENAME eq soffice.exe"
taskkill /f /fi "IMAGENAME eq soffice.bin"
ping -n 5 127.0.0.1
E:\grFile\Install\program\soffice.exe -headless -nologo -nofirststartwizard -accept="socket,host=127.0.0.1,port=8100;urp;"
echo "start complete"
exit
到此,windows环境下Libreoffice安装完毕
遇到个问题,多次试验,安装的文件名尽量不要使用中文以及含空格的文件名
linux环境下安装Libreoffice
准备好Linux版本的安装包,上传到linux服务器上
将Libreoffice的安装包上传到 /opt 目录下
打指令
1. cd /opt
2. tar -zxvf LibreOffice_5.3.6_Linux_x86-64_rpm.tar.gz 解压到当前路径
3. sudo yum install ./LibreOffice_5.3.6_Linux_x86-64_rpm/RPMS/*.rpm 安装
4. cd /opt/libreoffice5.3/program,这里就是程序目录了
开始调试是否安装完成,运行以下指令
/opt/libreoffice5.3/program/soffice.bin -headless --nologo --nofirststartwizard --accept="socket,host=127.0.0.1,port=8100;urp;"
运行上述命令,报错一个解决一个,直到成功为止
1. 执行sudo updatedb命令,如果没有这个命令,就需要先安装, yum install mlocate
2. 报错找不到libcairo.so.2: yum install cairo-devel
3. 报错找不到libcups.so.2: yum install libXinerama cups-libs
4. 报错找不到libGL.so.1: yum install libGL
5. 报错找不到libSM.so.6: yum install libSM
6. 如果需要删除,则yum remove libcairo.so.2
可能有缺包情况:lib*.so.*,则直接yum install即可
记住要刷新缓存:sudo updatedb
如果不能用,就得安装下: yum install mlocate
调试完成后,将Libreoffice放在后台运行,输入指令
nohup /opt/libreoffice5.3/program/soffice.bin -headless --nologo --nofirststartwizard --accept="socket,host=127.0.0.1,port=8100;urp;" &
运行后,查看是否运行,输入指令
ps aux | grep soffice
或者
netstat -tnlp检查是否启动成功。
如果需要关闭,则记下第二列的编码,运行 kill -9 编码
到此,Linux环境下Libreoffice安装完毕
Linux环境下不需要写启动脚本,直接后台运行,挂掉了参考上面指令后台启动
遇到个问题,多次试验,word转成pdf后,pdf的所有中文内容出现正方形的小框框。这个是因为Linux系统的字体库没有对应字体,需要另外新建字体库
Linux字体库新增字体
可以将windows系统下的字体直接复制粘贴上传到Linux系统下
windows系统下的字体位置:C:\Windows\Fonts
输入指令步骤如下:
1. yum -y install fontconfig
2. cd /usr/share,如果有fontconfig和fonts,则说明安装成功
3. 打开这个fonts文件夹下,新建一个叫chinese文件夹
4. 然后将准备好的字体文件全部拷进去(windows系统的可以)
5. chmod -R 755 /usr/share/fonts/chinese,给予该文件夹权限
6. yum -y install ttmkfdir
7. ttmkfdir -e /usr/share/X11/fonts/encodings/encodings.dir
8. vi /etc/fonts/fonts.conf,进入这个配置文件,
9. 在Font directory list配置项下,添加<dir>/usr/share/fonts/chinese</dir>
10. 退出后执行[fc-cache]刷新缓存
11. 此时重启下libreOffice比较保险
至此,Linux字体库新增字体完成
环境安装完成,实现功能
maven导入依赖
<!-- 连接libreOffice驱动包 -->
<dependency>
<groupId>com.artofsolving</groupId>
<artifactId>jodconverter</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>org.openoffice</groupId>
<artifactId>bootstrap-connector</artifactId>
<version>0.1.1</version>
</dependency>
application.properties配置信息
windows环境下的配置信息
libreOffice5Bat为libreoffice的启动脚本位置,详情查看安装时的准备
#libreOffice5
libreOffice5Bat=E\:/libreoffice/startLibreOffice.bat
libreOfficeTempPath=E\:/temp/
libreOfficeUrl=127.0.0.1
libreOfficePort=8100
Linux环境下的配置信息
#libreOffice5
libreOfficeTempPath=/apps/tmp/
libreOfficeUrl=127.0.0.1
libreOfficePort=8100
实现功能的工具类
对word模板的操作类
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
/**
* 对word模板的操作,包含替换文本和列表向下扩展两种<br>
* 本工具类的使用需要配合ExportWord.class,具体Demo看ExportWord类即可
* @author
*/
public class WordOperator {
/**
* 模板替换标签开头
*/
private final static String LABEL_STR = "{";
/**
* 模板替换标签结尾
*/
private final static String LABEL_END = "}";
/**
* 加载word对象
*/
private XWPFDocument doc;
/**
* 构造方法,把模板文档加载进来<br>
* 因为微软只提供XWPFRun对象替换文本,而它在切割段落文本的时候却是非常没有规律的,故而本方法要求模板必须遵循一定的规范来编写:<br>
* 1.模板里除了标签外,其余地方禁止使用"{"和"}"字符串;<br>
* 2.不允许出现"{}"这种写法,即标签必须包含key;
*/
public WordOperator(InputStream is) throws Exception {
doc = new XWPFDocument(is);
}
/**
* 要替换标签的KV对
*/
private Map<String, String> replaceMap;
/**
* 在word模板文档里,用{key}标签来表示要替换的内容,把key对应的value存储于replaceMap里,即可完成替换
*/
public WordOperator replaceText(Map<String, String> result) {
this.replaceMap = result;
// 替换文本内容的标签
List<XWPFParagraph> paragraphList = doc.getParagraphs();
replaceText(paragraphList);
// 替换表格内部标签
List<XWPFTable> tableList = doc.getTables();
for (XWPFTable table : tableList) {
for (int i = 0; i < table.getNumberOfRows(); i++) {
XWPFTableRow row = table.getRow(i);
List<XWPFTableCell> tableCellList = row.getTableCells();
for (XWPFTableCell cell : tableCellList) {
replaceText(cell.getParagraphs());
}
}
}
return this;
}
/**
* 把替换段落抽取出一个方法,在替换文本和替换表格里都可以调用
*/
private void replaceText(List<XWPFParagraph> paragraphList) {
int labelLen = LABEL_STR.length();
// 取出word模板里的全部段落,遍历
for (int i = 0; i < paragraphList.size(); i++) {
XWPFParagraph paragraph = paragraphList.get(i);
// 拿出每一个段落,判断内容里是否包含字符串"{"和"}",只有两者同时存在了才执行替换标签的逻辑
String paragraphText = paragraph.getText();
if (!StringUtils.isBlank(paragraphText) && paragraphText.indexOf(LABEL_STR) != -1 && paragraphText.indexOf(LABEL_END) != -1) {
// 每一个段落分很多小段文本,官方API只能否通过XWPFRun对象执行替换文本功能
List<XWPFRun> runList = paragraph.getRuns();
// 组装replaceMap的key
String key = "";
// 是否检测到有"{"字符串,有为true,没有为false
boolean include = false;
for (int j = 0; j < runList.size(); j++) {
XWPFRun run = runList.get(j);
String text = run.getText(0);
//有些模板不太规范,可能存在大量空格无法识别,内容为null,空指针异常,跳过即可
if(StringUtils.isBlank(text)) {
continue;
}
System.out.println(text);
// 取出每一个小段里标签头和尾的角标
int labelStrIndex = text.indexOf(LABEL_STR);
int labelEndIndex = text.indexOf(LABEL_END);
// 只有{
if (labelStrIndex != -1 && labelEndIndex == -1) {
// 例如有"aaa{bbb",则aaa不动,{bbb去掉,同时把bbb累加到key里
String textBefore = text.substring(0, labelStrIndex);
String textAfter = text.substring(labelStrIndex + labelLen);
run.setText(textBefore, 0);
key += textAfter;
include = true;
// 只有}
} else if (labelStrIndex == -1 && labelEndIndex != -1) {
// 例如有"aaa}bbb",则aaa}去掉,bbb不动,同时把aaa累加到key里
String textBefore = text.substring(0, labelEndIndex);
String textAfter = text.substring(labelEndIndex + labelLen);
key += textBefore;
System.out.println(key);
Object value = replaceMap.get(key);
System.out.println(value);
if (StringUtils.isBlank(value)) {
value = " ";
}
key = "";
run.setText(value + textAfter, 0);
include = false;
// 两个都没有
} else if (labelStrIndex == -1 && labelEndIndex == -1) {
// 例如有"aaa",如果之前有{了,则把内容累加到key里,同时去掉内容;如果没有{,则说明是普通文本,跳过不管
if (include) {
key += text;
run.setText(" ", 0);
}
// 两个都存在,这种情况比较复杂。经过多次试验,XWPFRun切割内容,只会同时各出现1次而已
} else {
// {在前,}在后
if (labelStrIndex < labelEndIndex) {
// 例如有"aaa{bbb}ccc",则aaa不动,{bbb}去掉同时进行替换,ccc保留(因为有规范,所以这是一个独立完整的标签,且bbb绝对有值)
String textBefore = text.substring(0, labelStrIndex);
String textMiddle = text.substring(labelStrIndex + labelLen, labelEndIndex);
String textAfter = text.substring(labelEndIndex + labelLen);
Object value = replaceMap.get(textMiddle);
if (StringUtils.isBlank(value)) {
value = " ";
}
run.setText(textBefore + value + textAfter, 0);
key = "";
include = false;
// }在前,{在后
} else {
// 例如有"aaa}bbb{ccc",则aaa}去掉,且进行替换;bbb不动,{ccc去掉,累加到key里去
String textBefore = text.substring(0, labelEndIndex);
String textMiddle = text.substring(labelEndIndex + labelLen, labelStrIndex);
String textAfter = text.substring(labelStrIndex + labelLen);
key += textBefore;
Object value = replaceMap.get(key);
if (StringUtils.isBlank(value)) {
value = " ";
}
run.setText(value + textMiddle, 0);
key = textAfter;
include = true;
}
}
}
}
}
}
/**
* 表格向下扩展数据行,因为POI对word格式支持较差,故我们做一些约定<br>
* 1.约定表格要有一行模板行,用于扩展数据行的样式效仿;<br>
* 2.模板表格里最好不要有合并行,如非常需要,则可以用隐藏单元格或设置白色边框的方式巧妙替代合并行;<br>
* 3.模板行下方不允许出现其他行<br>
* @param tableIndex 表格角标,第1个表为0
* @param tempRow 模板行角标,第2行为1(模板行不是标题行,而是扩展数据行样式的效仿对象)
* @param isDelTempRow true-删除模板行;false-不删除模板行
* @param rowlist 要插入的数据集合,格式为[行集合<列集合>,列集合的长度最好与模板行长度一致]
*/
public WordOperator insert2Table(int tableIndex, int tmpRowIndex, boolean isDelTmpRow, List<List<String>> rowlist) {
// 因为需要对rowlist进行添加操作,故而我们重新做一个变量,防止因为改变而产生问题
List<List<String>> dataList = new ArrayList<List<String>>();
dataList.addAll(rowlist);
// 根据角标取出表格对像
XWPFTable table = doc.getTables().get(tableIndex);
// 再根据行号获取模板行
XWPFTableRow tmpRow = table.getRow(tmpRowIndex);
// 不知道是什么原因,最后一列数据会跑到模板行上一行,故而这里在最末添加一组数据,解决这个bug
dataList.add(new ArrayList<String>());
// 遍历数据集合
for (int i = 0, len = dataList.size(); i < dataList.size(); i++) {
// 计算数据行编号
int dataIndex = tmpRowIndex + 1 + i;
// 按照模板行样式添加一行到数据行
table.addRow(tmpRow, dataIndex);
if (i < len - 1) {
// 取出这一行
XWPFTableRow row = table.getRow(dataIndex);
// 取出这一行全部单元格
List<XWPFTableCell> cellList = row.getTableCells();
// 取出这一行对应的数据,数据与单元格角标位置是一致的,一一替换文本
List<String> colList = dataList.get(i);
for (int j = 0; j < cellList.size(); j++) {
setCellText(cellList.get(j), colList, j);
}
}
}
// 删掉那一行为了bug而添加的空白行(因为空白行跑到模板行上一行,所以tmpRowIndex为该行角标)
table.removeRow(tmpRowIndex);
// 删除模版行(因为上面已经删除了空白行了,所以tmpRowIndex就变成了下一行模板行了)
if (isDelTmpRow) {
table.removeRow(tmpRowIndex);
}
return this;
}
/**
* 替换单元格文本
* @param cell 单元格
* @param colList 替换文本的集合(角标越位了,就置空)
* @param j 单元格角标,也就是文本集合的角标
*/
private boolean setCellText(XWPFTableCell cell, List<String> colList, int j) {
// 无论一个单元格被切割成多少个run,只需要修改第一个即可,改完就return掉了
List<XWPFParagraph> paragraphs = cell.getParagraphs();
for (XWPFParagraph p : paragraphs) {
List<XWPFRun> runs = p.getRuns();
for (XWPFRun r : runs) {
try {
r.setText(colList.get(j), 0);
} catch (Exception e) {
r.setText(" ", 0);
}
return true;
}
}
return false;
}
/**
* 给定输出流即可将word文档导出到
*/
public void write(OutputStream os) throws Exception {
doc.write(os);
}
}
导出word工具类(基于POI)
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.xmlbeans.XmlOptions;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBody;
import org.springframework.core.io.ClassPathResource;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* 导出word工具类(基于POI)
* @author
*/
public class ExportWord {
/**
* 举个例子
*/
@RequestMapping("demo")
public void demo(HttpServletRequest request, HttpServletResponse response) {
try {
// 替换标签{aa}和{bb}
Map<String, String> map = new HashMap<String, String>();
map.put("aa", "替换aa");
map.put("bb", "替换bb");
// 列表4列,从左向右装填数据,然后向下扩展两行数据行
List<List<String>> rwoList = new ArrayList<List<String>>();
List<String> l1 = new ArrayList<String>();
l1.add("a1");
l1.add("b1");
l1.add("c1");
l1.add("d1");
rwoList.add(l1);
List<String> l2 = new ArrayList<String>();
l2.add("a2");
l2.add("b2");
l2.add("c2");
l2.add("d2");
rwoList.add(l2);
// 加载第1个模板,执行替换标签、给第1个表格扩展数据(第二行为模板行,扩展后删除模板行)
WordOperator wo = ExportWord.getWordOperator("demo/demo.docx");
wo.replaceText(map).insert2Table(1, 1, true, rwoList);
// 加载第2个模板,执行第2个表格扩展数据(第二行为模板行,扩展后仍然保留模板行)
WordOperator wo2 = ExportWord.getWordOperator("demo/demo2.docx");
wo2.insert2Table(1, 1, false, rwoList);
// 以下6个方法,任选1个调用:
// --> 下载word
ExportWord.download_word(request, response, "导出文件", wo);
// --> 下载word(多个模板合并)
ExportWord.download_word(request, response, "导出文件", wo, wo2);
// --> 下载pdf
ExportWord.download_pdf(request, response, "导出文件", wo);
// --> 下载pdf(多个模板合并)
ExportWord.download_pdf(request, response, "导出文件", wo, wo2);
// --> 预览pdf
ExportWord.preview_pdf(request, response, "导出文件", wo);
// --> 预览pdf(多个模板合并)
ExportWord.preview_pdf(request, response, "导出文件", wo, wo2);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 模板根路径
*/
public final static String FILE_PATH = "/dot/";
/**
* 获取WordOperator对象
* @param templateName word模板名字(必须放在/src/main/resources/dot下)
*/
public static WordOperator getWordOperator(String templateName) throws Exception {
return new WordOperator(new FileInputStream(new ClassPathResource( FILE_PATH + templateName).getFile()));
}
/**
* 下载生成的word文档
* @param request 请求
* @param response 响应
* @param fileName 新文档名字(不带后缀名)
* @param wordOperator 文档操作对象
*/
public static void download_word(HttpServletRequest request, HttpServletResponse response, String fileName, WordOperator wordOperator) throws Exception {
ServletOutputStream os = null;
try {
// 设置好响应头,然后将文件保存到输出流里即可
response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
response.setHeader("Content-Disposition", "attachment;filename=" + FileUtils.encodeFileName(FileUtils.addRandom(fileName) + ".docx", request));
os = response.getOutputStream();
wordOperator.write(os);
os.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭输出流
if (os != null) {
os.close();
}
}
}
/**
* 下载生成的word文档(多文件合并)
* @param request 请求
* @param response 响应
* @param fileName 新文档名字(不带后缀名)
* @param woList 文档操作对象集合(合并顺序)
*/
public static void download_word(HttpServletRequest request, HttpServletResponse response, String fileName, List<WordOperator> woList) throws Exception {
download_word(request, response, fileName, woList.toArray(new WordOperator[0]));
}
/**
* 下载生成的word文档(多文件合并)
* @param request 请求
* @param response 响应
* @param fileName 新文档名字(不带后缀名)
* @param woList 文档操作对象集合(合并顺序)
*/
public static void download_word(HttpServletRequest request, HttpServletResponse response, String fileName, WordOperator... woList) throws Exception {
ServletOutputStream os = null;
List<String> tempDocList = new ArrayList<String>();
try {
// 设置响应头
response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
response.setHeader("Content-Disposition", "attachment;filename=" + FileUtils.encodeFileName(FileUtils.addRandom(fileName) + ".docx", request));
os = response.getOutputStream();
// 一个wo对应一份文档,循环保存下来
for (WordOperator wo : woList) {
String tempDoc = getPath() +"/" + UUID.randomUUID().toString() + ".docx";
wo.write(new FileOutputStream(tempDoc));
tempDocList.add(tempDoc);
}
// 然后把上面保存的文档合并起来
String tempDoc = getPath() +"/" + mergeDocx(tempDocList);
tempDocList.add(tempDoc);
// 将这份文档以流的形式保存到输出流中
IOUtils.copy(new FileInputStream(new File(tempDoc)), os);
os.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭输出流
if (os != null) {
os.close();
}
// 将全部临时文档删除掉
for (String tempDoc : tempDocList) {
FileUtils.delFile(tempDoc);
}
}
}
/**
* 下载生成的pdf文档
* @param request 请求
* @param response 响应
* @param fileName 新文档名字(不带后缀名)
* @param wordOperator 文档操作对象
*/
public static void download_pdf(HttpServletRequest request, HttpServletResponse response, String fileName, WordOperator wordOperator) throws Exception {
ServletOutputStream os = null;
String tempDoc = null;
String tempPdf = null;
try {
// 设置响应头
response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
response.setContentType(MimeUtils.getByExtension("pdf") + ";charset=UTF-8");
response.setHeader("Content-Disposition", "attachment;filename=" + FileUtils.encodeFileName(FileUtils.addRandom(fileName) + ".pdf", request));
// 先将生成的word文档保存起来
tempDoc = getPath() +"/" + UUID.randomUUID().toString() + ".docx";
wordOperator.write(new FileOutputStream(tempDoc));
// 然后将word转成pdf保存起来
tempPdf = getPath() +"/" + UUID.randomUUID().toString() + ".pdf";
PDFUtils.office2PDF(tempDoc, tempPdf);
// 将这份文档以流的形式保存到输出流中
os = response.getOutputStream();
FileUtils.copyFile(new File(tempPdf), os);
os.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭输出流
if (os != null) {
os.close();
}
// 将临时文档删除掉
if (tempDoc != null) {
FileUtils.delFile(tempDoc);
}
// 将临时文档删除掉
if (tempPdf != null) {
FileUtils.delFile(tempPdf);
}
}
}
/**
* 下载生成的pdf文档(多文件合并)
* @param request 请求
* @param response 响应
* @param fileName 新文档名字(不带后缀名)
* @param woList 文档操作对象集合(合并顺序)
*/
public static void download_pdf(HttpServletRequest request, HttpServletResponse response, String fileName, List<WordOperator> woList) throws Exception {
download_pdf(request, response, fileName, woList.toArray(new WordOperator[0]));
}
/**
* 下载生成的pdf文档(多文件合并)
* @param request 请求
* @param response 响应
* @param fileName 新文档名字(不带后缀名)
* @param woList 文档操作对象集合(合并顺序)
*/
public static void download_pdf(HttpServletRequest request, HttpServletResponse response, String fileName, WordOperator... woList) throws Exception {
List<String> tempDocList = new ArrayList<String>();
ServletOutputStream os = null;
try {
// 设置响应头
response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
response.setContentType(MimeUtils.getByExtension("pdf") + ";charset=UTF-8");
response.setHeader("Content-Disposition", "attachment;filename=" + FileUtils.encodeFileName(FileUtils.addRandom(fileName) + ".pdf", request));
// 一个wo对应一份文档,循环保存下来
for (WordOperator wo : woList) {
String tempDoc = getPath() +"/" + UUID.randomUUID().toString() + ".docx";
wo.write(new FileOutputStream(tempDoc));
tempDocList.add(tempDoc);
}
// 然后把上面保存的文档合并起来
String tempDoc = getPath() +"/" + mergeDocx(tempDocList);
tempDocList.add(tempDoc);
// 再把这份合并好的文档转成pdf
String tempPdf = getPath() +"/" + UUID.randomUUID().toString() + ".pdf";
PDFUtils.office2PDF(tempDoc, tempPdf);
tempDocList.add(tempPdf);
// 将这份文档以流的形式保存到输出流中
os = response.getOutputStream();
FileUtils.copyFile(new File(tempPdf), os);
os.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭输出流
if (os != null) {
os.close();
}
// 将全部临时文档删除掉
for (String tempDoc : tempDocList) {
FileUtils.delFile(tempDoc);
}
}
}
/**
* 预览生成的pdf文档
* @param request 请求
* @param response 响应
* @param fileName 新文档名字(不带后缀名)
* @param wordOperator 文档操作对象
*/
public static void preview_pdf(HttpServletRequest request, HttpServletResponse response, String fileName, WordOperator wordOperator) throws Exception {
String tempDoc = null;
String tempPdf = null;
ServletOutputStream os = null;
try {
// 设置响应头
response.setContentType(MimeUtils.getByExtension("pdf") + ";charset=UTF-8");
response.setHeader("Content-Disposition", "inline;filename=" + FileUtils.encodeFileName(FileUtils.addRandom(fileName) + ".pdf", request));
// 先将生成的word文档保存起来
tempDoc =getPath() +"/"+ UUID.randomUUID().toString() + ".docx";
wordOperator.write(new FileOutputStream(tempDoc));
// 然后将word转成pdf保存起来
tempPdf = getPath() +"/"+UUID.randomUUID().toString() + ".pdf";
PDFUtils.office2PDF(tempDoc, tempPdf);
// 将pdf文档以流的形式输送给response
os = response.getOutputStream();
File pdfFile = new File(tempPdf);
FileUtils.copyFile(pdfFile, os);
os.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭输出流
if (os != null) {
os.close();
}
// 将word和pdf两个临时文件删除掉
if (tempDoc != null) {
FileUtils.delFile(tempDoc);
}
if (tempPdf != null) {
FileUtils.delFile(tempPdf);
}
}
}
/**
* 预览生成的pdf文档(多文件合并)
* @param request 请求
* @param response 响应
* @param fileName 新文档名字(不带后缀名)
* @param woList 文档操作对象集合(合并顺序)
*/
public static void preview_pdf(HttpServletRequest request, HttpServletResponse response, String fileName, List<WordOperator> woList) throws Exception {
preview_pdf(request, response, fileName, woList.toArray(new WordOperator[0]));
}
/**
* 预览生成的pdf文档(多文件合并)
* @param request 请求
* @param response 响应
* @param fileName 新文档名字(不带后缀名)
* @param woList 文档操作对象集合(合并顺序)
*/
public static void preview_pdf(HttpServletRequest request, HttpServletResponse response, String fileName, WordOperator... woList) throws Exception {
List<String> tempDocList = new ArrayList<String>();
ServletOutputStream os = null;
try {
// 设置响应头
response.setContentType(MimeUtils.getByExtension("pdf") + ";charset=UTF-8");
response.setHeader("Content-Disposition", "inline; filename=" + FileUtils.encodeFileName(FileUtils.addRandom(fileName) + ".pdf", request));
// 一个wo对应一份文档,循环保存下来
for (WordOperator wo : woList) {
String tempDoc = getPath() +"/" + UUID.randomUUID().toString() + ".docx";
wo.write(new FileOutputStream(tempDoc));
tempDocList.add(tempDoc);
}
// 然后把上面保存的文档合并起来
String tempDoc = getPath() +"/" + mergeDocx(tempDocList);
tempDocList.add(tempDoc);
// 再把这份合并好的文档转成pdf
String tempPdf = getPath() +"/" + UUID.randomUUID().toString() + ".pdf";
PDFUtils.office2PDF(tempDoc, tempPdf);
tempDocList.add(tempPdf);
// 将这份文档以流的形式保存到输出流中
os = response.getOutputStream();
FileUtils.copyFile(new File(tempPdf), os);
os.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭输出流
if (os != null) {
os.close();
}
// 将全部临时文档删除掉
for (String tempDoc : tempDocList) {
FileUtils.delFile(tempDoc);
}
}
}
/**
* 合并若干个文档(按顺序合并)
* @param fileNames 需要合并的几个word文档全路径
* @return 返回合并好的文档名称,位置在DICK下
*/
private static String mergeDocx(List<String> fileNames) throws Exception {
XmlOptions optionsOuter = new XmlOptions();
optionsOuter.setSaveOuter();
CTBody firstCTBody = null;
XWPFDocument firstXWPFDocument = null;
String prefix = "", firstMainPart = "", addPart = "", sufix = "";
for (int i = 0; i < fileNames.size(); i++) {
String fileName = fileNames.get(i);
InputStream in = new FileInputStream(fileName);
OPCPackage srcPackage = OPCPackage.open(in);
XWPFDocument srcDocument = new XWPFDocument(srcPackage);
CTBody srcBody = srcDocument.getDocument().getBody();
if (i == 0) {
firstXWPFDocument = srcDocument;
firstCTBody = srcBody;
String srcString = srcBody.xmlText();
prefix = srcString.substring(0, srcString.indexOf(">") + 1);
firstMainPart = srcString.substring(srcString.indexOf(">") + 1, srcString.lastIndexOf("<"));
sufix = srcString.substring(srcString.lastIndexOf("<"));
} else {
String appendString = srcBody.xmlText(optionsOuter);
addPart += appendString.substring(appendString.indexOf(">") + 1, appendString.lastIndexOf("<"));
}
}
CTBody makeBody = CTBody.Factory.parse(prefix + firstMainPart + addPart + sufix);
firstCTBody.set(makeBody);
String tmpFileName = UUID.randomUUID().toString() + ".docx";
OutputStream dest = new FileOutputStream(getPath() +"/" + tmpFileName);
firstXWPFDocument.write(dest);
return tmpFileName;
}
/**
* 创建当前项目绝对路径下的临时文件保存位置
*/
public static String getPath() throws IOException {
File directory = new File("");
//获取当前项目所在的绝对路径
String courseFile = directory.getCanonicalPath() ;
File file=new File(courseFile+"/temp");
//创建临时文件保存位置
if(!file.exists()){
file.mkdir();
}
String path=file.getCanonicalPath();
return path;
}
}
PDF文档工具类(libreOffice)
import java.io.File;
import java.net.ConnectException;
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.grkj.Global;
import com.grkj.common.utils.StringUtils;
/**
* PDF文档工具类
* @author
*/
public class PDFUtils {
/**
* 连接libreOffice服务的地址和端口号
* 读取properties文件的配置信息
*/
private final static String LIBREOFFICE_URL = Global.getConfig("libreOfficeUrl");
private final static int LIBREOFFICE_PORT = new Integer(Global.getConfig("libreOfficePort"));
/**
* WORD转PDF方法
* @param sourceFile 要被转化的word文档路径(如E:\\1.docx)
* @param destFile 转成之后pdf文档存放路径(如E:\\1.pdf)
*/
public static void office2PDF(String sourceFile, String destFile) {
try {
File inputFile = new File(sourceFile);
File outputFile = new File(destFile);
if (!outputFile.getParentFile().exists()) {
outputFile.getParentFile().mkdirs();
}
OpenOfficeConnection connection = new SocketOpenOfficeConnection(LIBREOFFICE_URL, LIBREOFFICE_PORT);
connection.connect();
DocumentConverter converter = new OpenOfficeDocumentConverter(connection);
converter.convert(inputFile, outputFile);
connection.disconnect();
} catch (ConnectException e) {
try {
// 如果报错的是连接不上,则找到重启libreoffice5重启bat脚本,执行下
String libreOffice5Bat = Global.getConfig("libreOffice5Bat");
if(!StringUtils.isBlank(libreOffice5Bat)) {
String cmd = "cmd /k start " + libreOffice5Bat;
Runtime.getRuntime().exec(cmd);
// 脚本里是先杀线程,过五秒,再启动程序,所以这里等六秒,再重新执行主逻辑
Thread.sleep(6000);
office2PDF(sourceFile, destFile);
}
} catch (Exception e1) {
e1.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
demo
@RequestMapping(value="/getPrintFile")
public void getDownLoadPrintFiles(HttpServletRequest request, HttpServletResponse response) throws Exception {
//需要替换掉模板里的特殊标识数据,key要和标识字符一致
Map<String, String> result =mapper.getList();
//word的操作类(POI),替换特殊标识字符内容如:{name}
WordOperator op = ExportWord.getWordOperator("模板.docx").replaceText(result);;
//下载word文档
ExportWord.download_word(request, response,"模板", op);
//word的操作类(POI),替换特殊标识字符内容如:{name}
WordOperator op = ExportWord.getWordOperator("模板.docx").replaceText(result);
//转为pdf文档
ExportWord.preview_pdf(request, response,"模板", op);
}
}