需求:根据文件 url ,实现文件在线预览
解决方法:
1、前端
前端可以直接使用微软的共有接口来在线浏览office产品(word/excel/ppt),如下:
看起来简介明了,但是有个缺点:你的文件地址必须对外可访问。其原理是通过请求微软的服务去解析你的文件,如果你的文件对外不可访问,这个方法就不行。
2、后端
前端实现不了,只能后端用Java来处理了,思路是利用 open office 将文件转换成pdf或者html,我的实现方法是word/ppt转成pdf,excel转成html。excel转成html,转成PDF格式超级丑。
前言:
按下面两种方法实现,都有点麻烦,且最后成功了预览大文件有任务阻塞,导致后面的预览都预览不了,所以推荐下面链接这个方法,如果不是用springboot也可以改改相关代码,亲测有效!
https://blog.csdn.net/starboyss/article/details/78498974
Java开发预览word/excel/ppt需要安装open office,在windows和linux系统上都需要安装。
window安装:注意下载软件安装的时候,在安装类型选择的时候选择自定义安装,在自定义安装的时候要选择文件安装地址,这个地址是最后需要使用到的地址。安装完成后有一个openOffice 4/program文件夹。
openOffice相关jar包主要有两个版本,一种是和代码在同一台服务器上,一种是安装在远程的其他服务器上。前者支持office03/07文件转换,后者仅支持office03文件转换,可以通过将07版本的后缀改成03版本的方式来解决此问题,具体下面有代码。
引入jar包
org.jodconverter
jodconverter-core
4.0.0-RELEASE
相关代码:
import com.ctrip.common.exception.BusinessException;
import com.ctrip.framework.clogging.agent.log.ILog;
import com.ctrip.framework.clogging.agent.log.LogManager;
import org.jodconverter.OfficeDocumentConverter;
import org.jodconverter.office.DefaultOfficeManagerBuilder;
import org.jodconverter.office.OfficeException;
import org.jodconverter.office.OfficeManager;
import java.io.File;
import java.io.IOException;
import java.util.regex.Pattern;
//启动openoffice方法靠路径
public class Office2PreviewFile {
private static ILog logger = LogManager.getLogger(Office2PreviewFile.class);
/**
* 将office格式的文件转为pdf
*
* @param sourceFile 源文件
* @param targetFilePath 生成的pdf/html文件保存地址
* @return pdf文件
*/
public static File fileToPreviewFile(File sourceFile, String targetFilePath) throws BusinessException {
try {
//启动openOffice
OfficeManager officeManager = getOfficeManager();
OfficeDocumentConverter converter = new OfficeDocumentConverter(officeManager);
return convertFile(sourceFile, targetFilePath, converter);
} catch (Exception e) {
logger.error("文件转成PDF/HTML文件异常", e);
throw new BusinessException("文件转换异常");
}
}
/**
* 转换文件
*
* @param sourceFile 原文件
* @param targetFilePath 转换后存放位置
* @param converter 转换器
* @return 生成的文件
*/
private static File convertFile(File sourceFile, String targetFilePath, OfficeDocumentConverter converter) throws OfficeException, IOException {
File pdfFile = new File(targetFilePath);
if (pdfFile.exists()) {
logger.info("文件存在,直接返回," + targetFilePath);
return pdfFile;
}
boolean createFile = pdfFile.createNewFile();
logger.info("创建pdf/html文件:" + pdfFile.getAbsolutePath());
converter.convert(sourceFile, pdfFile);
return pdfFile;
}
private static OfficeManager getOfficeManager() throws BusinessException {
DefaultOfficeManagerBuilder builder = new DefaultOfficeManagerBuilder();
builder.setOfficeHome(getOfficeHome());
OfficeManager officeManager = builder.build();
try {
if (!officeManager.isRunning()) {
logger.info("启动open office");
officeManager.start();
}
} catch (OfficeException e) {
//打印日志
logger.error("启动 open office 失败", e);
}
return officeManager;
}
/**
* 获取openOffice的安装目录
*/
private static String getOfficeHome() throws BusinessException {
String osName = System.getProperty("os.name");
logger.info("转换文件时系统为:" + osName);
if (Pattern.matches("Windows.*", osName)) {
return "D:\\soft\\directory\\openOffice 4";
} else if (Pattern.matches("Linux.*", osName)) {
return "/opt/openoffice4";
} else if (Pattern.matches("Mac.*", osName)) {
return "/Application/openOfficeSoft";
}
throw new BusinessException("未找到正确系统");
}
public static void main(String[] args) throws BusinessException {
fileToPreviewFile(new File("D:\\fileOnlineTmp\\aa.xlsx"), "D:\\fileOnlineTmp\\aa.html");
fileToPreviewFile(new File("D:\\fileOnlineTmp\\aa.xlsx"), "D:\\fileOnlineTmp\\aa.pdf");
}
}
缺点:需要openOffice和代码部署在同一台服务器上;
优点:jar包版本比较高,支持office07版本,转换成功率更高。
jar包
com.artofsolving
jodconverter
2.2.1
相关代码
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.ctrip.common.exception.BusinessException;
import com.ctrip.framework.clogging.agent.log.ILog;
import com.ctrip.framework.clogging.agent.log.LogManager;
import java.io.File;
import java.io.IOException;
import java.net.ConnectException;
/*
* 转换成pdf/html文件
* 项目执行需要启动OpenOffice服务,选择自定义安装。
* 进入安装目录:cd C:\Program Files (x86)\OpenOffice.org 4\program
* 执行启动命令:soffice -headless -accept="socket,host=127.0.0.1,port=8100;urp;" -nofirststartwizard
*/
public class OpenOfficeUtils {
private static ILog logger = LogManager.getLogger(OpenOfficeUtils.class);
/**
* 任意文件转成pdf格式
*
* @param sourceFile 源文件,支持的格式有:
* @param targetFilePath 目标文件位置,如D:D:\fileOnlineTmp\aa.pdf
* @return pdf或html文件
*/
public static File fileToPreviewFile(File sourceFile, String targetFilePath) throws BusinessException {
File targetFile = new File(targetFilePath);
if (targetFile.exists()) {
return targetFile;
}
logger.info("开始文件转换 start :" + DateUtils.getNowStr());
OpenOfficeConnection connection = getConnection();
DocumentConverter converter = new OpenOfficeDocumentConverter(connection);
converter.convert(sourceFile, targetFile);
try {
targetFile.createNewFile();
logger.info("开始文件转换 end :" + DateUtils.getNowStr());
} catch (IOException e) {
logger.info("转换成pdf文件时,创建文件失败" + targetFilePath, e);
throw new BusinessException("转换成pdf文件时,创建文件失败");
}
connection.disconnect();
logger.info("转换文件成pdf完成");
return targetFile;
}
private static OpenOfficeConnection getConnection() throws BusinessException {
//输入端口,也可以输入IP+端口
OpenOfficeConnection connection = new SocketOpenOfficeConnection(8100);
try {
connection.connect();
} catch (ConnectException e) {
logger.info("连接open office失败", e);
throw new BusinessException("连接open office失败");
}
return connection;
}
public static void main(String[] args) throws BusinessException {
fileToPreviewFile(new File("D:\\fileOnlineTmp\\aa.xls"), "D:\\fileOnlineTmp\\aa.html");
fileToPreviewFile(new File("D:\\fileOnlineTmp\\bb.doc"), "D:\\fileOnlineTmp\\bb.pdf");
}
}
缺点:
1、需要先将openoffice服务启动;
2、该方式只支持office03产品转换,07的需要改后缀为03;
优点:可以单独部署在另一台服务器上;
1、当使用远程服务器时报错:
Exception in thread "main" com.artofsolving.jodconverter.openoffice.connection.OpenOfficeException: conversion failed: could not load input document
at com.artofsolving.jodconverter.openoffice.converter.OpenOfficeDocumentConverter.loadAndExport(OpenOfficeDocumentConverter.java:131)
at com.artofsolving.jodconverter.openoffice.converter.OpenOfficeDocumentConverter.convertInternal(OpenOfficeDocumentConverter.java:120)
at com.artofsolving.jodconverter.openoffice.converter.AbstractOpenOfficeDocumentConverter.convert(AbstractOpenOfficeDocumentConverter.java:104)
at com.artofsolving.jodconverter.openoffice.converter.AbstractOpenOfficeDocumentConverter.convert(AbstractOpenOfficeDocumentConverter.java:74)
at com.artofsolving.jodconverter.openoffice.converter.AbstractOpenOfficeDocumentConverter.convert(AbstractOpenOfficeDocumentConverter.java:70)
at com.ctrip.common.util.OpenOfficeUtils.fileToPreviewFile(OpenOfficeUtils.java:42)
at com.ctrip.common.util.OpenOfficeUtils.main(OpenOfficeUtils.java:67)
Caused by: com.sun.star.lang.IllegalArgumentException: URL seems to be an unsupported one.
修改:
DocumentConverter converter = new OpenOfficeDocumentConverter(connection);
改为:
DocumentConverter converter = new StreamOpenOfficeDocumentConverter(connection);
2、当连接远程服务器时报错:
connection failed: socket,host=192.168.25.174,port=8100,tcpNoDelay=1
解决:在启动OpenOffice时,将host=的ip地址写为0.0.0.0就可以通过java远程连接了
soffice -headless -accept="socket,host=0.0.0.0,port=8100;urp;" -nofirststartwizard &
3、转换后的pdf或者html中文字消失、乱码、成为方块,都是由于远程服务器上的字体原因,需要在安装openoffice的服务器上全部字体;
4、文件太大的话,转换会耗费很长时间,甚至连接中断,所以建议如果超过1M就建议不预览了,下载查看吧;
在上面通过两种方式实现了将office产品转换成PDF/HTML文件,保存起来之后还需要实现PDF/HTML预览,包括还要实现txt/image等预览。
这里主要将controller层到service简单介绍下
String directoryPath = request.getSession().getServletContext().getRealPath("/") + "onlinePreview";
File directoryFile = new File(directoryPath);
if (!directoryFile.exists()) {
logger.info("创建预览文件夹:" + directoryPath);
directoryFile.mkdir();
}
byte[] bytes;
try {
bytes = getFileBytes(tFile, directoryPath);
} catch (Exception e) {
logger.error("文件预览失败", e);
throw new BusinessException("文件预览失败,请下载查阅");
}
response.setContentType(getContentType(fileType));
OutputStream outputStream = response.getOutputStream();
outputStream.write(bytes);
outputStream.flush();
outputStream.close();
介绍下getFileBytes方法:
//pdf文件直接返回其字节数组,其他类型文件需要转换成pdf或html,再获取其字节数组,用于预览
private byte[] getFileBytes(TFile tFile, String directoryPath) throws BusinessException, IOException {
byte[] bytes = getDataFromURL(tFile.getFileUrl());
//pdf或者图片直接返回bytes,其他格式文件需要转成pdf或html文件,然后再返回转换后的文件bytes
if (notUseOpenOffice(tFile.getFileType())) {
return bytes;
}
File previewFile = getPreviewFileByOpenOffice(directoryPath, tFile, bytes);
return readInputStream(new FileInputStream(previewFile), previewFile.length());
}
private File getPreviewFileByOpenOffice(String directoryPath, TFile tFile, byte[] bytes) throws IOException, BusinessException {
String fileName = directoryPath + File.separator + tFile.getID();
String sourceFilePath = fileName + "." + getOpenOfficeFileType(tFile.getFileType());
logger.info("预览源文件地址:" + sourceFilePath);
File sourceFile = new File(sourceFilePath);
FileOutputStream fos = new FileOutputStream(sourceFile);
BufferedOutputStream bos = new BufferedOutputStream(fos);
bos.write(bytes);
bos.close();
fos.close();
logger.info("预览源文件生成完成");
String openOfficeType = getOpenOfficeType(tFile.getFileType());
return OpenOfficeUtils.fileToPreviewFile(sourceFile, fileName + "." + openOfficeType);
}
//如果转换后生成的文件不乱码,但是预览的时候却乱码,需要在response设置中设置contentType,同时针对不同文件返回类型,也需要设置不同的contentType值
private String getContentType(String fileType) {
if (FileUtil.isImage(fileType)) {
return "image/jpeg";
}
if ("txt".equals(fileType)) {
return "text/plain;charset=GBK";
}
String openOfficeType = getOpenOfficeType(fileType);
if ("html".equals(openOfficeType)) {
return "text/html;charset=UTF-8";
} else {
return "application/pdf";
}
}
至此预览完成,在预览的过程中,根据url生成了源文件,然后根据源文件可能又生成了转换后的pdf/html,这些文件都需要及时删除掉,可以在关闭预览时删除,也可以在定时任务中定时删除。
另外,预览结果直接在流中显示,前端会直接打开一个页面,这样如果文件稍微大一点耗费个几秒前端不好显示,或者有异常信息前端可能不好提示,这个时候可以将上面的接口拆分为两个接口。一个接口处理下载源文件件和转换成预览的文件,另一个接口直接去读预览的文件,这样在接口一处理时前端页面可以提示为”正在处理中,请等待“。
同时这种方法因为将读写拆分成了两个接口,如果有多个服务器,这两个接口不是请求在同一个服务器上就有问题,这个时候可以将预览的两个接口单独作为一个服务部署到固定一台机器上。