问题

开发过程,需要将批量的office文档,包括word文档(doc,docx),ppt文档(ppt,pptx),excel文档(xls,xlsx)等转成pdf,以此来实现预览。市面上常用的方案包括有openoffice,libreoffice等。

openoffice

其中openoffice是一个软件,需要在服务器上安装该软件,然后通过命令的形式调用openoffice转换组件(我是在java里头使用),来实现转换。虽然可行,但是问题也是存在的。

  1. 使用久了,容易在后台运行多个openoffice的服务,内存占用比较大
  2. openoffice转换excel文档,会出现换行的情况,如下:

原来excel内容格式:

XWPFTemplate导出word表格 office lens导出pdf_ppt转pdf

经过openoffice转换之后:

XWPFTemplate导出word表格 office lens导出pdf_excel转pdf_02

libreoffice

跟openoffice很像,也是一个软件,使用方式,也是先安装软件,然后通过命令的形式来调用转换组件(我是基于node来调用的),因为出现了跟openoffice一样的问题:转换excel会换行,所以我直接不考虑了。

解决

经过多方的查探,终于发现了可以通过使用aspose来转换(此处如果涉及到aspose侵权的问题,请联系笔者下架该文章,笔者也是使用aspose学习用),我使用aspose也做了多次的使用方式升级。

第一次,直接通过java来调用api,实现转换,速度挺快的,2-3s就可以转换一个

第二次,将转换的api打包成可执行jar包,供node程序来调用(因为node本身程序比较轻便,而转换的话,靠java的aspose的api,因此使用这种方式来完成)

第一种方式,我这边就不演示了,直接上第二种方式的源码:

  1. 利用idea创建一个最简单的maven工程
  1. 先上下后面全部做好的项目的目录:
  1. 配置pom文件
  1. 配置内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>convertPdf</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>com.future.aspose</groupId>
            <artifactId>aspose.slides-15.9.0</artifactId>
            <version>15.9.0</version>
        </dependency>
        <dependency>
            <groupId>com.future.aspose</groupId>
            <artifactId>aspose.words-16.8.0</artifactId>
            <version>16.8.0</version>
        </dependency>
        <dependency>
            <groupId>com.future.aspose</groupId>
            <artifactId>aspose.pdf-11.8.0</artifactId>
            <version>11.8.0</version>
        </dependency>
        <dependency>
            <groupId>com.future.aspose</groupId>
            <artifactId>aspose.cells-9.0.0</artifactId>
            <version>9.0.0</version>
        </dependency>
    </dependencies>
    <build>
        <finalName>convertPdf</finalName>
        <plugins>
            <plugin>
                <!-- 配置插件坐标 -->
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>2.6</version>
                <configuration>
                    <appendAssemblyId>false</appendAssemblyId>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                    <archive>
                        <manifest>
                            <!-- 此处指定main方法入口的class -->
                            <mainClass>org.example.ConvertPdf</mainClass>
                        </manifest>
                    </archive>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>
  1. 创建文件夹和工具类文件
  1. 创建文件ConverPdf文件(查看代码的话,直接从最后一行的main方法开始看,方便理解
package org.example;

import com.aspose.cells.*;
import com.aspose.slides.Presentation;
import com.aspose.slides.SaveFormat;
import com.aspose.words.Document;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.Objects;

/**
 * @author 扫地僧
 */
public class ConvertPdf {
    /**
     * 获取license
     * @return
     */
    public static void getWordLicense() throws Exception {
        try (InputStream license = Thread.currentThread().getContextClassLoader().getResourceAsStream("./license.xml")) {
            com.aspose.words.License aposeLic = new com.aspose.words.License();
            aposeLic.setLicense(license);
        } catch (Exception e){
            throw new Exception("验证License失败!");
        }
    }

    /**
     * 获取license
     * @return
     */
    public static void getPPTLicense() throws Exception {
        try (InputStream license = Thread.currentThread().getContextClassLoader().getResourceAsStream("./license.xml")){
            com.aspose.slides.License aposeLic = new com.aspose.slides.License();
            aposeLic.setLicense(license);
        } catch (Exception e){
            throw new Exception("验证License失败!");
        }
    }

    /**
     * 获取license
     * @return
     */
    public static void getExcelLicense() throws Exception {
        try (InputStream license = Thread.currentThread().getContextClassLoader().getResourceAsStream("./license.xml")){
            License aposeLic = new License();
            aposeLic.setLicense(license);
        } catch (Exception e){
            throw new Exception("验证License失败!");
        }
    }

    public static void word2Pdf(InputStream in, String pdfinputFile) throws Exception{
        // 验证License
        getWordLicense();

        try (FileOutputStream fileOS = new FileOutputStream(new File(pdfinputFile))) {
            Document doc = new Document(in);
            doc.save(fileOS, com.aspose.words.SaveFormat.PDF);
        }
    }
    public static void ppt2Pdf(InputStream in, String pdfinputFile) throws Exception{
        // 验证License
        getPPTLicense();

        try (FileOutputStream fileOS = new FileOutputStream(new File(pdfinputFile))) {
            Presentation ppt = new Presentation(in);
            ppt.save(fileOS, SaveFormat.Pdf);
        }
    }
    public static void excel2Pdf(InputStream in, String pdfinputFile) throws Exception {
        // 验证License
        getExcelLicense();

        Workbook excel = new Workbook(in);
        PdfSaveOptions pdfOptions = new PdfSaveOptions();
        pdfOptions.setOnePagePerSheet(true);
        Style style = excel.createStyle();
        style.setBorder(BorderType.BOTTOM_BORDER, CellBorderType.THIN, Color.getLightGray());
        style.setBorder(BorderType.LEFT_BORDER, CellBorderType.THIN, Color.getLightGray());
        style.setBorder(BorderType.TOP_BORDER, CellBorderType.THIN, Color.getLightGray());
        style.setBorder(BorderType.RIGHT_BORDER, CellBorderType.THIN, Color.getLightGray());
        excel.setDefaultStyle(style);
        excel.save(pdfinputFile,pdfOptions);
    }

    /**
     * 执行转换
     */
    public static int execute (String inputFile, String outputFile) {
        System.out.println("[文件转pdf]开始...");

        System.out.println("[文件转pdf]目标文件:" + inputFile);
        try {
            File originalFile = new File(inputFile);
            if (!originalFile.exists()) {
                throw new Exception("文件不存在");
            }

            try (FileInputStream in = new FileInputStream(inputFile)) {
                if (inputFile.endsWith(".xlsx") || inputFile.endsWith(".xls")) {
                    outputFile += ".pdf";
                    FileUtils.deleteFile(outputFile);
                    excel2Pdf(in,outputFile);
                } else if (inputFile.endsWith(".pptx") || inputFile.endsWith(".ppt")){
                    // 先删除再转换
                    outputFile += ".pdf";
                    FileUtils.deleteFile(outputFile);
                    ppt2Pdf(in, outputFile);
                } else if (inputFile.endsWith(".doc") || inputFile.endsWith(".docx") ) {
                    // 先删除再转换
                    outputFile += ".pdf";
                    FileUtils.deleteFile(outputFile);
                    word2Pdf(in,outputFile);
                } else {
                    String postfix = "." + FileUtils.getPostfix(inputFile);
                    // 先删除再转换
                    outputFile += postfix;
                    FileUtils.deleteFile(outputFile);
                    FileUtils.copyFile(inputFile, FileUtils.getPrefix(outputFile), outputFile);
                }
            }
        } catch (Exception e) {
            System.out.println("[文件转pdf]异常:" + e.getMessage());
        }

        System.out.println("[文件转pdf]结束!");
        return fileExist(outputFile);
    }

    /**
     * 判断文件是否存在 存在代表转换成功
     * @return
     */
    private static int fileExist (String inputFile) {
        if (Objects.nonNull(inputFile) && (new File(inputFile)).exists()) {
            return 1;
        }
        return 0;
    }

    /**
     * 因为是要打包成可执行的jar包,因此输入的参数要从args里头获取
     * 打包成功控制台输出1
     * 打包失败控制台输出0
     * 通过捕捉控制台的信息,就可以知道是否打包成功了
     * @param args
     */
    public static void main(String[] args) {
        if (args.length < 2) {
            System.out.println(0);
        } else {
            String inputFile = args[0];
            String outputFile = args[1];
            if (Objects.isNull(inputFile) || Objects.isNull(outputFile)) {
                System.out.println(0);
            } else {
                // 调用转换pdf的API
                System.out.println(execute(inputFile, outputFile));
            }
        }
    }
}
  1. 创建FileUtils文件
package org.example;

import java.io.*;

/**
 * org.example.FileUtils.java
 * description:文件处理工具类
 * author 魏霖涛
 * @version $Revision: 1.3 $ $Date: 2017/02/06 07:02:48 $ $Author: weilintao $
 * history 1.0.0 2016-1-6 created by weilintao
 */
public class FileUtils {
	/**
	 * 获取输出文件
	 *
	 * @param inputFilePath
	 * @return
	 */
	public static String getOutputFilePath(String inputFilePath) {
		String outputFilePath = inputFilePath.replaceAll("." + getPostfix(inputFilePath), ".pdf");
		return outputFilePath;
	}

	/**
	 * 获取inputFilePath的后缀名,如:"e:/test.pptx"的后缀名为:"pptx"<br>
	 *
	 * @param inputFilePath
	 * @return
	 */
	public static String getPostfix(String inputFilePath) {
		return inputFilePath.substring(inputFilePath.lastIndexOf(".") + 1);
	}

	/**
	 * 获取inputFilePath的前缀,如:"e:/test.pptx"的前缀为:"e:/"<br>
	 *
	 * @param inputFilePath
	 * @return
	 */
	public static String getPrefix(String inputFilePath) {
		return inputFilePath.substring(0, inputFilePath.lastIndexOf("/") + 1);
	}

	/**
	 * 获取inputFilePath的文件名,如:"e:/test.pptx"的文件名为:"test.pptx"<br>
	 *     modify: 添加如果lastIndexOf("/") == -1的判断
	 *
	 * @param inputFilePath
	 * @return
	 */
	public static String getFileName(String inputFilePath) {
		if (inputFilePath.lastIndexOf("/") == -1) {
			return inputFilePath;
		}
		return inputFilePath.substring(inputFilePath.lastIndexOf("/")+1);
	}
	/**
	 * 删除文件
	 * @param filepath
	 */
	public static void deleteFile(String filepath){
		File file = new File(filepath);
		if(file.exists()){
			file.delete();
		}
	}
	
	/**
	 * 删除文件夹
	 * 
	 * @param folderPath 文件夹完整绝对路径
	 * 
	 */
	public static void delFolder(String folderPath) {
		try {
			// 删除完里面所有内容
			delAllFile(folderPath);
			String filePath = folderPath;
			filePath = filePath.toString();
			File myFilePath = new File(filePath);
			// 删除空文件夹
			myFilePath.delete();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 删除指定文件夹下所有文件
	 * 
	 * @param path 文件夹完整绝对路径
	 * 
	 * 
	 */
	public static boolean delAllFile(String path) {
		boolean bea = false;
		File file = new File(path);
		if (!file.exists()) {
			return bea;
		}
		if (!file.isDirectory()) {
			return bea;
		}
		String[] tempList = file.list();
		File temp = null;
		for (int i = 0; i < tempList.length; i++) {
			if (path.endsWith(File.separator)) {
				temp = new File(path + tempList[i]);
			} else {
				temp = new File(path + File.separator + tempList[i]);
			}
			if (temp.isFile()) {
				temp.delete();
			}
			if (temp.isDirectory()) {
				// 先删除文件夹里面的文件
				delAllFile(path + "/" + tempList[i]);
				// 再删除空文件夹
				delFolder(path + "/" + tempList[i]);
				bea = true;
			}
		}
		return bea;
	}
	public static void deleteFile(File file){
		if(file.exists()){
			file.delete();
		}
	}

	/**
	 * 新建目录
	 * @param directory 目录路径 如: e:/
	 */
	public static void createFileDirectory(String directory){
		File file = new File(directory);
		if(!file.exists()){
			//几级目录没有就建立几级 mkdir:只能建立第一级
			file.mkdirs();
		}
	}
	
	/** 
	 * 复制单个文件 
	 * @param oldPath String 原文件路径 如:c:/fqf.txt 
	 * @param newPath String 复制后路径 如:f:/
	 * @param filename String 目标文件名 如:fqf.txt 
	 *  boolean 
	 */ 
	 public static void copyFile(String oldPath, String newPath, String filename) {
		 InputStream inStream = null;
		 FileOutputStream fs = null;
		 try { 
			 int bytesum = 0; 
			 int byteread = 0; 
			 File oldfile = new File(oldPath);
			 //文件存在时
			 if (oldfile.exists()) {
				 //读入原文件
				 inStream = new FileInputStream(oldPath);
				 createFileDirectory(newPath);
				 fs = new FileOutputStream(newPath + filename); 
				 byte[] buffer = new byte[1444];  
				 while ( (byteread = inStream.read(buffer)) != -1) {
				 //字节数 文件大小
				 bytesum += byteread;
				 fs.write(buffer, 0, byteread); 
				 }
				 System.out.println("该文件总共字节数为:" + bytesum);
				 inStream.close();
			 } else {
				 System.out.println("文件"+oldPath+"不存在,退出");
			 }
		 }catch (Exception e) {
			 System.out.println("复制单个文件操作出错");
			 e.printStackTrace(); 
		 }finally{
			 if(inStream != null){
				 try {
					inStream.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			 }
			 if(fs != null){
				 try {
					fs.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			 }
		 } 
	 }
	
	public static boolean fileExist(String filepath){
		File file = new File(filepath);
		if(file.exists()){
			
			return true;
		}
		return false;
	}
	
	public static boolean directoryExist(String filepath){
		File file = new File(filepath);
		if(file.exists()){
			return true;
		}
		return false;
	}
 
	public static void main(String args[]){
	}
  
  /**
   * 复制文件 复制文件夹时候用
   * @param sourcefile
   * @param targetFile
   * @throws IOException
   */
  public static void copyFile(File sourcefile,File targetFile){
        try{
	        //新建文件输入流并对它进行缓冲
	        FileInputStream input=new FileInputStream(sourcefile);
	        BufferedInputStream inbuff=new BufferedInputStream(input);
	        
	        //新建文件输出流并对它进行缓冲
	        FileOutputStream out=new FileOutputStream(targetFile);
	        BufferedOutputStream outbuff=new BufferedOutputStream(out);
	        
	        //缓冲数组
	        byte[] b=new byte[1024*5];
	        int len=0;
	        while((len=inbuff.read(b))!=-1){
	            outbuff.write(b, 0, len);
	        }
	        
	        //刷新此缓冲的输出流
	        outbuff.flush();
	        
	        //关闭流
	        inbuff.close();
	        outbuff.close();
	        out.close();
	        input.close();
        }catch (Exception e) {
			e.printStackTrace();
		}
        
    }
    
  /**
   * 复制文件夹里面的内容到另外一个文件夹
   * @param sourceDir 源文件夹 D:/min_res/video/1565"
   * @param targetDir 目标文件夹 D:/min_res/video/15651" 这个文件夹允许不存在
   * @throws IOException
   */
    public static void copyDirectiory(String sourceDir,String targetDir){
        try{
	        //新建目标目录
	        (new File(targetDir)).mkdirs();
	        //获取源文件夹当下的文件或目录
	        File[] file=(new File(sourceDir)).listFiles();
	        for (int i = 0; i < file.length; i++) {
	            if(file[i].isFile()){
	                //源文件
	                File sourceFile=file[i];
					//目标文件
	                File targetFile=new File(new File(targetDir).getAbsolutePath()+File.separator+file[i].getName());
	                copyFile(sourceFile, targetFile);
	            }
	            if(file[i].isDirectory()){
	                //准备复制的源文件夹
	                String dir1=sourceDir+"/"+file[i].getName();
	                //准备复制的目标文件夹
	                String dir2=targetDir+"/"+file[i].getName();
	                
	                copyDirectiory(dir1, dir2);
	            }
	        }
        }catch (Exception e) {
        	e.printStackTrace();
        }
        
    }
}
  1. 创建license.xml文件
<License>
  <Data>
    <Products>
      <Product>Aspose.Total for Java</Product>
      <Product>Aspose.Words for Java</Product>
    </Products>
    <EditionType>Enterprise</EditionType>
    <SubscriptionExpiry>20991231</SubscriptionExpiry>
    <LicenseExpiry>20991231</LicenseExpiry>
    <SerialNumber>8bfe198c-7f0c-4ef8-8ff0-acc3237bf0d7</SerialNumber>
  </Data>
  <Signature>sNLLKGMUdF0r8O1kKilWAGdgfs2BvJb/2Xp8p5iuDVfZXmhppo+d0Ran1P9TKdjV4ABwAgKXxJ3jcQTqE/2IRfqwnPf8itN8aFZlV3TJPYeD3yWE7IT55Gz6EijUpC7aKeoohTb4w2fpox58wWoF3SNp6sK6jDfiAUGEHYJ9pjU=</Signature>
</License>
  1. 上述工作做好之后,项目就都准备好了,然后就是执行maven的package命令,打包生成一个jar包文件(这个步骤要是不会的,可以评论区留言,因为这个步骤比较简单,因此这边不上教程了)
  1. 打包生成的文件如下:
  1. 上述的convertPdf就是我们要的可执行jar包,该jar使用的方式是,在cmd窗口通过java -jar convertPdf.jar 待转换office文件全路径 转换成功的pdf文件的路径命令来使用
  1. eg: java -jar ./convertPdf.jar e:/input/111.doc e:/output/111  (注意:输出文件111表示文件的名称(后缀名不要添加))
  1. 编写node程序来调用jar文件:
let child_process = require('child_process');
let iconv = require('iconv-lite');
let encoding = 'cp936';
let binaryEncoding = 'binary';
/**
 * 转换office文档和pdf文档为pdf文档
 *
 * @param inputFile 输入文件全路径 eg: e:/input/111.doc
 * @param outputFile 输出文件路径和文件名,eg: e:/output/111  111表示文件的名称(后缀名不要添加)
 * @return {Promise<any>}
 */
let convert = (inputFile, outputFile)=>{
    return  new Promise(((resolve, reject) => {
        let cmdStr = `java -jar ./convertPdf.jar ${inputFile} ${outputFile}`;
        child_process.exec(cmdStr,{ encoding: binaryEncoding },function(err,stdout,srderr){
            if (err) {
                reject(iconv.decode(new Buffer(srderr, binaryEncoding), encoding))
            } else {
                resolve(iconv.decode(new Buffer(stdout, binaryEncoding), encoding))
            }
        });
    })).catch(e=>{
        console.error(`[office文档转换]转换失败:${e.message}`)
    })
}

/**
 * 格式化cmd命令执行的结果
 * @param msg
 * @return {*}
 */
let formatMsg = (msg)=>{
    let tmp = msg.split('\r\n')
    if (tmp.length > 0) {
        tmp.splice(tmp.length - 1, 1)
        return tmp
    } else {
        return null
    }
}

// 测试代码
(async ()=>{
    let result = await convert("C:\\Users\\zp89\\Desktop\\pdf转换测试\\ce.xlsx", "C:\\Users\\zp89\\Desktop\\pdf转换测试\\" + Date.now())
    console.log("运行结果:")
    console.log(formatMsg(result))
})()
  1. 效果如下(测试的excel有两个sheet)