优化版本在线预览Demo:https://pan.baidu.com/s/1jpOjmiTsilgThBmOKNRuoQ
1)思路:
1.利用 OpenOffice 以及 jodconverter 转换各种office文件为pdf格式
2.设置response的contentType为application/pdf,直接用IO将pdf文件输出就可以了(缺点:若用户使用ie浏览器则是下载而不是预览)
2)弊端:
多个用户请求过来了,我要一个一个的转换,每来一次请求我就转换一次,并且转换稍大文件速度慢,用户体验不好,并发访问请求转换同一个文件还会报错
3)优化思路:
1.利用多进程--利用SpringMVC一个用户请求就有一个新的线程为此服务,但转换的openoffice进程只有一个,所以就算使用多线程还是相当于排队等一个转好了,下一个才会去转换。所以这里使用多进程(这里没有使用Process类)。
2.所有文件只转换一次--因为转换的pdf文件都放在某个目录下(以Windows为里,这里放在了E:pdfFile/),所以只要我先判断对应的目录下有无pdf文件,若有就直接取出,若没有就去转换。
3.用锁处理并发转换问题--有这么一种情况,两个用户同时请求预览同一个文件,同时发现文件还为转换为pdf,同时去转换导致转换异常,这时可以考虑加锁去处理,如果有一方正在转换了,另一方就直接等待转换好去现成的就可以了。
优化点1)利用多进程实现:
1.开启多个端口不同的openOffice进程,并将这些进程放在一个阻塞队列中(这里用的并发包中的BlockingQueue),这样来了一个请求我就从这个阻塞队列中取出一个openOffice进程去处理,处理完之后我再把这个openOffice进程放回去,当这个队列空了,其他的线程就只能等待别的线程转换完之后放回去了才能再次转换了,这里端口放在了*.properties文件中,方便修改,这里选用了 9001 、9002、 9003、 9004 这四个端口,开启了四个openoffice进程待程序使用。
openOfficeHome=C:/Program Files (x86)/OpenOffice 4
ports=9001,9002,9003,9004
windowsCommand=/program/soffice.exe -headless -accept=\"socket,host=127.0.0.1,port=#Placeholder;urp;\"
converted_pdf_home=E:/pdfFile/
再启动服务器的时候就把有的进程都启动好,并且都放入阻塞队列中
private final static String PORTS = "ports";
private final static String PROPERTIES_FILE_NAME = "openOffice.properties";
private final static String[] ports =
PropertiesUtil.getPropertyByFileAndName(PROPERTIES_FILE_NAME,PORTS).split(",");
private final static String OPPEN_OFFICE_HOME = "openOfficeHome";
private final static String CONVERTED_PDF_HOME = "converted_pdf_home";
private static OfficeManager OfficeManager = null;
public static BlockingQueue OfficeManagerQueue = new LinkedBlockingDeque();
private static Lock lock = new ReentrantLock();
private static Map fileMap = new ConcurrentHashMap<>(16);
@PostConstruct
public void initOpenOfficeService() {
DefaultOfficeManagerBuilder builder = new DefaultOfficeManagerBuilder();
builder.setOfficeHome(getOfficeHome());
for(String port : ports){
builder.setPortNumbers(Integer.parseInt(port));
OfficeManager = builder.build();
try {
OfficeManager.start();
System.out.println("##############officeManager start !");
} catch (OfficeException e) {
//打印日志
System.out.println("start openOffice Fail!");
e.printStackTrace();
}
try {
//都放入阻塞队列中
OfficeManagerQueue.put(OfficeManager);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在关闭的时候将所有进程停止
@PreDestroy
public void destroyOpenOfficeService(){
for(OfficeManager manager : OfficeManagerQueue){
try {
System.out.println("close all officeManager");
manager.stop();
} catch (OfficeException e) {
System.out.println("officeManager stop fail!"+e.getMessage());
e.printStackTrace();
}
}
}
优化点2)所有文件只转换一次实现逻辑
这里再请求的时候做一个简单的逻辑判断,如果请求预览的文件本来就是pdf就直接返回回去了,否则就判断这个office文件是否被转换过,因为转换后的文件都存放在目录E:/pdfFile/中去了,这里只要判断这个目录下是否存在对应的pdf文件即可,最后如果又不是pdf文件,又没有被转换过就真正的去转换了
private String fileHandler(String fileName){
String fileSuffix = StringUtil.getFileSuffix(fileName);
String result = null;
System.out.println(fileSuffix);
if("pdf".equals(fileSuffix))
{
System.out.println("file is pdf type");
result = BASE_PATH + fileName;
return result;
}
else if(new File("E:/pdfFile/"+fileName.replaceAll("\\."+ StringUtil.getFileSuffix(fileName),".pdf")).exists())
{
System.out.println("file has been converted");
result = "E:/pdfFile/"+fileName.replaceAll("\\."+ StringUtil.getFileSuffix(fileName),".pdf");
}
else
{
System.out.println("file start conveted");
openOfficeServicel.openOfficeToPDF(BASE_PATH+fileName);
result = "E:/pdfFile/"+fileName.replaceAll("\\."+ StringUtil.getFileSuffix(fileName),".pdf");
}
return result;
}
优化点3)用锁处理并发转换问题实现
public static void convertFile(File sourceFile,
String after_convert_file_path,String sourceFilePath,OfficeManager officeManager) throws OfficeException {
File outputFile = new File(after_convert_file_path);
if(!outputFile.getParentFile().exists()){
//如果上级目录不存在也就是E:/pdfFile这个文件夹不存在则创建一个
outputFile.getParentFile().mkdirs();
}
OfficeDocumentConverter converter = new OfficeDocumentConverter(officeManager);
//加锁
lock.lock();
if(!sourceFile.renameTo(sourceFile)){
System.out.println("文件" + sourceFile.getName() + "正在被使用");
while(true){
if(new File("E:/pdfFile/"+StringUtil.converSuffixToPdf(sourceFile.getName())).exists()){
System.out.println("文件有了");
try {
OfficeManagerQueue.put(officeManager);
} catch (InterruptedException e) {
e.printStackTrace();
}
//释放锁并return
lock.unlock();
return;
}
}
}
//释放锁
lock.unlock();
converter.convert(sourceFile,outputFile);
try {
OfficeManagerQueue.put(officeManager);
System.out.println("blockingQueue puted OfficeManagerQueue size :" + OfficeManagerQueue.size());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
后面测试发现这种方式有很多问题:
1、它并不能处理并发问题,只能解决两个请求先后请求转换同一个office文件,第一个正在转换的时候,第二个不会去转换。
2、如果有三个请求,前面两个请求转换同一office文件(比如就叫 "java面试题.doc"),第三个请求转换另外一个office文件(比如是 "记录.xlsx"),那么第一个请求进入这个方法的时候先拿到锁,然后继续往下走把锁释放了调用convert方法去转换了。第二个请求过来拿到了锁,然后一直持有着,直到第一个转换完了,对应目录下有转换好的pdf文件才把锁释放,导致第三个请求就一直阻塞在这里
3.并发的时候没有达到目的,并发的时候请求预览同一office文件的两个线程同时到了这个方法,第一个线程拿到了锁然后发现没有人在操作这个文件把锁释放了,准备去转换为pdf文件了。就在锁释放的一瞬间,第二个线程拿到了锁,判断这个这个office文件有没有被操作,结果第一个线程还没有来得及操作,第二个线程已经得到判断结果(没有人操作这个文件),然后把锁释放,也去转换这个文件去了,就发生了转换异常。
public void convertFile(File sourceFile,
String after_convert_file_path,String sourceFilePath,OfficeManager officeManager) throws OfficeException {
File outputFile = new File(after_convert_file_path);
if(!outputFile.getParentFile().exists()){
//如果上级目录不存在也就是E:/pdfFile这个文件夹不存在则创建一个
outputFile.getParentFile().mkdirs();
}
OfficeDocumentConverter converter = new OfficeDocumentConverter(officeManager);
RandomAccessFile raf = new RandomAccessFile(sourceFile, "rw");
FileChannel fc = raf.getChannel();
try {
FileLock fl = fc.tryLock();
if (fl== null) {
System.out.println("获得文件锁失败");
}
} catch (IOException e) {
Sytem.out.println();
}
converter.convert(sourceFile,outputFile);
try {
fl.release();
} catch (IOException e) {
e.printStackTrace();
}
try {
OfficeManagerQueue.put(officeManager);
System.out.println("blockingQueue puted OfficeManagerQueue size :" + OfficeManagerQueue.size());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
具体代码有点忘记了,最终导致失败的最大问题就是,对文件加锁导致后面真正去转换的时候要操作文件直接报 could not open file 不能打开文件错误。
public void convertFile(File sourceFile,
String after_convert_file_path,String sourceFilePath,OfficeManager officeManager) throws OfficeException {
File outputFile = new File(after_convert_file_path);
if(!outputFile.getParentFile().exists()){
//如果上级目录不存在也就是E:/pdfFile这个文件夹不存在则创建一个
outputFile.getParentFile().mkdirs();
}
OfficeDocumentConverter converter = new OfficeDocumentConverter(officeManager);
lock.lock();
if(fileMap.containsKey(sourceFile.getName()))
{
lock.unlock();
while(true) {
if (new File("E:/pdfFile/java.pdf").exists()) {
System.out.println("文件有了");
try {
OfficeManagerQueue.put(officeManager);
} catch (InterruptedException e) {
e.printStackTrace();
}
return;
}
}
}
else
{
fileMap.put(sourceFile.getName(),sourceFile.getName());
}
lock.unlock();
converter.convert(sourceFile,outputFile);
try {
OfficeManagerQueue.put(officeManager);
System.out.println("blockingQueue puted OfficeManagerQueue size :" + OfficeManagerQueue.size());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
定义变量、常量、初始化将openoffice进程启动并放入阻塞队列,以及关闭服务时停止openoffice
private final static String PORTS = "ports";
private final static String PROPERTIES_FILE_NAME = "openOffice.properties";
private final static String[] ports =
PropertiesUtil.getPropertyByFileAndName(PROPERTIES_FILE_NAME,PORTS).split(",");
private final static String OPPEN_OFFICE_HOME = "openOfficeHome";
private final static String CONVERTED_PDF_HOME = "converted_pdf_home";
private static OfficeManager OfficeManager = null;
public static BlockingQueue OfficeManagerQueue = new LinkedBlockingDeque();
private static Lock lock = new ReentrantLock();
private static Map fileMap = new ConcurrentHashMap<>(16);
@PostConstruct
public void initOpenOfficeService() {
DefaultOfficeManagerBuilder builder = new DefaultOfficeManagerBuilder();
builder.setOfficeHome(getOfficeHome());
for(String port : ports){
builder.setPortNumbers(Integer.parseInt(port));
OfficeManager = builder.build();
try {
OfficeManager.start();
System.out.println("##############officeManager start !");
} catch (OfficeException e) {
//打印日志
System.out.println("start openOffice Fail!");
e.printStackTrace();
}
try {
OfficeManagerQueue.put(OfficeManager);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@PreDestroy
public void destroyOpenOfficeService(){
for(OfficeManager manager : OfficeManagerQueue){
try {
System.out.println("close all officeManager");
manager.stop();
} catch (OfficeException e) {
System.out.println("officeManager stop fail!"+e.getMessage());
e.printStackTrace();
}
}
}
转换文件方法
public void convertFile(File sourceFile,
String after_convert_file_path,String sourceFilePath,OfficeManager officeManager) throws OfficeException {
File outputFile = new File(after_convert_file_path);
if(!outputFile.getParentFile().exists()){
//如果上级目录不存在也就是E:/pdfFile这个文件夹不存在则创建一个
outputFile.getParentFile().mkdirs();
}
OfficeDocumentConverter converter = new OfficeDocumentConverter(officeManager);
lock.lock();
if(fileMap.containsKey(sourceFile.getName()))
{
lock.unlock();
while(true) {
if (new File("E:/pdfFile/java.pdf").exists()) {
System.out.println("文件有了");
try {
OfficeManagerQueue.put(officeManager);
} catch (InterruptedException e) {
e.printStackTrace();
}
return;
}
}
}
else
{
fileMap.put(sourceFile.getName(),sourceFile.getName());
}
lock.unlock();
converter.convert(sourceFile,outputFile);
try {
OfficeManagerQueue.put(officeManager);
System.out.println("blockingQueue puted OfficeManagerQueue size :" + OfficeManagerQueue.size());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
转换文件之前的准备
public void office2pdf(String sourceFilePath){
OfficeManager officeManager = null;
File sourceFile = new File(sourceFilePath);
try{
if(StringUtil.isEmpty(sourceFilePath))
{
//打印日志...
return ;
}
if(!sourceFile.exists())
{
//打印日志...
return ;
}
String after_convert_file_path = getAfterConverFilePath(sourceFilePath);
officeManager = OfficeManagerQueue.take();
System.out.println("blockingQueue taked , OfficeManagerQueue size :" + OfficeManagerQueue.size());
convertFile(sourceFile,after_convert_file_path,sourceFilePath,officeManager);
}catch (Exception e){
e.printStackTrace();
try {
OfficeManagerQueue.put(officeManager);
System.out.println("blockingQueue put , OfficeManagerQueue size :" + OfficeManagerQueue.size());
} catch (InterruptedException e1) {
// lock.unlock();
e1.printStackTrace();
return;
}
System.out.println("转换异常");
}
}