首页 > 编程 > Java > 正文

openoffic+java+spring 多线程 转换doc,ppt,xls -> html/pdf

2019-11-08 01:00:48
字体:
来源:转载
供稿:网友

前提摘要:

你是否想在自己的项目中像百度文库一样实现文档的在线预览,又不想使用第三方的收费服务,那么这篇文章你选对了!在搜索大量的资料,我发现网络上对openoffice的使用时最方便的,但是没有可以多线程转换的代码,试想一下,在一个许多许许多的用户同时文件转换的时候,没有多线程,那不等急死了,一个一个的转换。。。

科普知识:

1.OpenOffice: OpenOffice.org 是一套跨平台的办公室软件套件,能在Windows、linux、MacOS X (X11)和 Solaris 等操作系统上执行。它与各个主要的办公室软件套件兼容。OpenOffice.org 是自由软件,任何人都可以免费下载、使用及推广它。

java中引入OpenOffice的使用当然需要借助PRocess的使用才可以,需要调用本地命令的形式使用,Process的使用就不多说了,我就直接放上我所使用的代码:

private List<Process> process = new ArrayList<Process>();
OpenOffice_HOME = PropertiesUtils.getVal("tools.properties", "OpenOffice_HOME");
String command = OpenOffice_HOME + "/program/soffice.exe -headless -accept=/"socket,host=127.0.0.1,port="+port+";urp;/"";  process.add(Runtime.getRuntime().exec(command));这里的OpenOffice_HOME 就是 openoffice的安装目录,到program 文件夹上一层即可;

重点:这里process 放入集合中,可以在使用完毕,或者服务器关闭时,进行关闭进程;这里多线程的操作是开多个端口,每个端口同时进行转换工作,转换完毕,就是放该端口连接让给其他的线程使用,这样就可以多线程操作了;

Java环境下操作openoffice 要使用 JodConverter 来操作,JodCconverter有两个版本:

1.最新的3.0版本身就加入了线程池的支持,本身就可以开启多端口进程,看源码确实使用了线程池,但是实际测试中,并没有真正的多线程的转换,并且正统的文档太少了,源代码source中都没有API的解说,而且在3.0版本中可以不借助process,本身的API已经有启动服务的方法了,这确实很简单,如果各位有折腾的精神和时间,可以继续尝试,maven 引入 3.0 貌似有问题,建议自行下载jar包。

操作:这里我是用的是JodConverter 2.2.1 的包,直接引入maven依赖就可以用了,前提是maven项目

pom.xml 中配置:

<dependency><groupId>com.artofsolving</groupId><artifactId>jodconverter</artifactId><version>2.2.1</version></dependency>转换文件使用到的工具类:

package campus_mooc.common_utils.utils;import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.net.ConnectException;import java.text.SimpleDateFormat;import java.util.ArrayList;import java.util.Date;import java.util.List;import java.util.UUID;import java.util.concurrent.BlockingQueue;import java.util.concurrent.LinkedBlockingQueue;import javax.annotation.PostConstruct;import javax.annotation.PreDestroy;import javax.transaction.Transactional;import org.apache.log4j.Logger;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.stereotype.Component;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 campus_mooc.core.commons.comstatic.ConfigStatic;/** * 利用jodconverter(基于OpenOffice服务)将文件(*.doc、*.docx、*.xls、*.PPT)转化为html格式或者pdf格式, * 使用前请检查OpenOffice服务是否已经开启, OpenOffice进程名称:soffice.exe | soffice.bin * @author lcx */@Component(value="doc2HtmlUtil")//这里我直接将工具交由Spring容器管理@Transactionalpublic class Doc2HtmlUtil{    private final Logger logger = Logger.getLogger(Doc2HtmlUtil.class);    private final String OpenOffice_HOME = PropertiesUtils.getVal("tools.properties", "OpenOffice_HOME");        private List<Process> process = new ArrayList<Process>();//process集合,方便服务器关闭时,关闭openoffice进程        public BlockingQueue<Integer> queue = new LinkedBlockingQueue<>();//这里用线程安全的queue管理运行的端口号        @Autowired    JdbcTemplate jdbcTemplate;        @PostConstruct    public void startAllService() throws IOException, NumberFormatException, InterruptedException{    	    	String portsStr = ConfigStatic.OPENOFFICE_PORT_STR;//我将使用的端口号卸载properties文件中,便于写改    	    	String[] ports = portsStr.split(",");    			for (String port : ports) {			//添加到队列 用于线程获取端口 进行连接			queue.put(Integer.parseInt(port));			//启动OpenOffice的服务  	        String command = OpenOffice_HOME  	                + "/program/soffice.exe -headless -accept=/"socket,host=127.0.0.1,port="+port+";urp;/"";//这里根据port进行进程开启	        process.add(Runtime.getRuntime().exec(command));	        logger.debug("[startAllService-port-["+port+"]-success]");		}		logger.debug("[startAllService-success]");    }        @PreDestroy//服务器关闭时执行 循环关闭所有的打开的openoffice进程    public void stopAllService(){    	for (Process p : process) {			p.destroy();		}    	logger.debug("[stopAllService-success]");    }        /**    * 根据端口获取连接服务  每个转换操作时,JodConverter需要用一个连接连接到端口,(这里类比数据库的连接)
    * @throws ConnectException 
    */    public OpenOfficeConnection getConnect(int port) throws ConnectException{    	logger.debug("[connectPort-port:"+port+"]");    	return new SocketOpenOfficeConnection(port);    }    	public Doc2HtmlUtil(){	}		/**	 * 转换文件成html	 * 	 * @param fromFileInputStream:	 * @throws IOException	 * @throws InterruptedException 	 */	public String file2Html(InputStream fromFileInputStream, String toFilePath, String type) throws IOException, InterruptedException {		String methodName = "[file2Html]";		Date date = new Date();		SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");		String timesuffix = UUID.randomUUID().toString()+sdf.format(date);		String docFileName = null;		String htmFileName = null;		if ("doc".equals(type) || "docx".equals(type)) {			docFileName = "doc_" + timesuffix + ".doc";			htmFileName = "doc_" + timesuffix + ".html";		} else if ("xls".equals(type) || "xlsx".equals(type)) {			docFileName = "xls_" + timesuffix + ".xls";			htmFileName = "xls_" + timesuffix + ".html";		} else if ("ppt".equals(type) || "pptx".equals(type)) {			docFileName = "ppt_" + timesuffix + ".ppt";			htmFileName = "ppt_" + timesuffix + ".html";		} else {			return null;		}		File htmlOutputFile = new File(toFilePath + File.separatorChar + htmFileName);		File docInputFile = new File(toFilePath + File.separatorChar + docFileName);		if (htmlOutputFile.exists())			htmlOutputFile.delete();		htmlOutputFile.createNewFile();		if (docInputFile.exists())			docInputFile.delete();		docInputFile.createNewFile();				/**		 * 由fromFileInputStream构建输入文件		 */		try {			OutputStream os = new FileOutputStream(docInputFile);			int bytesRead = 0;			byte[] buffer = new byte[1024 * 8];			while ((bytesRead = fromFileInputStream.read(buffer)) != -1) {				os.write(buffer, 0, bytesRead);			}			os.close();			fromFileInputStream.close();		} catch (IOException e) {		}		
		//这里是重点,每次转换从集合读取一个未使用的端口(直接拿走,这样其他线程就不会读取到这个端口号,不会尝试去使用)		//计时并读取一个未使用的端口		long old = System.currentTimeMillis();		int port = queue.take();		//获取并开启连接		OpenOfficeConnection connection = getConnect(port);		connection.connect();		DocumentConverter converter = new OpenOfficeDocumentConverter(connection); 		try {			converter.convert(docInputFile, htmlOutputFile);		} catch (Exception e) {			System.out.println("exception:" + e.getMessage());		}		//关闭连接		connection.disconnect();		//计算花费时间 将端口放入池中		System.out.println(Thread.currentThread().getName() + "disConnect-port=" + port + "-time=" + (System.currentTimeMillis() - old));		queue.put(port);//端口号使用完毕之后 放回队列中,其他线程有机会使用				// 转换完之后删除Word文件		docInputFile.delete();		logger.debug(methodName + "htmFileName:" + htmFileName);		return htmFileName;	}	/**	 * 转换文件成pdf	 * 	 * @param fromFileInputStream:	 * @throws IOException	 * @throws InterruptedException 	 */	public String file2pdf(InputStream fromFileInputStream, String toFilePath, String type) throws IOException, InterruptedException {		String methodName = "[file2pdf]";		Date date = new Date();		SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");		String timesuffix = UUID.randomUUID().toString()+sdf.format(date);		String docFileName = null;		String htmFileName = null;		if ("doc".equals(type) || "docx".equals(type)) {			docFileName = "doc_" + timesuffix + ".doc";			htmFileName = "doc_" + timesuffix + ".pdf";		} else if ("xls".equals(type) || "xlsx".equals(type)) {			docFileName = "xls_" + timesuffix + ".xls";			htmFileName = "xls_" + timesuffix + ".pdf";		} else if ("ppt".equals(type) || "pptx".equals(type)) {			docFileName = "ppt_" + timesuffix + ".ppt";			htmFileName = "ppt_" + timesuffix + ".pdf";		} else {			return null;		}		File htmlOutputFile = new File(toFilePath + File.separatorChar + htmFileName);		File docInputFile = new File(toFilePath + File.separatorChar + docFileName);		if (htmlOutputFile.exists())			htmlOutputFile.delete();		htmlOutputFile.createNewFile();		if (docInputFile.exists())			docInputFile.delete();		docInputFile.createNewFile();		/**		 * 由fromFileInputStream构建输入文件		 */		try {			OutputStream os = new FileOutputStream(docInputFile);			int bytesRead = 0;			byte[] buffer = new byte[1024 * 8];			while ((bytesRead = fromFileInputStream.read(buffer)) != -1) {				os.write(buffer, 0, bytesRead);			}			os.close();			fromFileInputStream.close();		} catch (IOException e) {		}		//计时并读取一个未使用的端口		long old = System.currentTimeMillis();		int port = queue.take();		//获取并开启连接		OpenOfficeConnection connection = getConnect(port);		connection.connect();		//OfficeDocumentConverter converter = new OfficeDocumentConverter(officeManager);		DocumentConverter converter = new OpenOfficeDocumentConverter(connection); 		try {			converter.convert(docInputFile, htmlOutputFile);		} catch (Exception e) {			System.out.println("exception:" + e.getMessage());		}		//关闭连接		connection.disconnect();		//计算花费时间 将端口放入池中		System.out.println(Thread.currentThread().getName() + "disConnect-port=" + port + "-time=" + (System.currentTimeMillis() - old));		queue.put(port);		// 转换完之后删除word文件		docInputFile.delete();		logger.debug(methodName + "htmFileName:" + htmFileName);		return htmFileName;	}}

然后我们写一个单元测试去测试Spring环境下的工具类使用情况:

package jod;import java.io.IOException;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.test.context.ContextConfiguration;import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;import campus_mooc.common_utils.utils.FileInfoHelps;import campus_mooc.common_utils.utils.ThreadManager;@RunWith(value = SpringJUnit4ClassRunner.class)@ContextConfiguration(locations = { "classpath:applicationContext.xml" })public class Doc2HtmlUtilTest {	@Autowired	ThreadManager threadManager;	@Autowired	FileInfoHelps fileInfoHelps;	@Test	public void generatePreviewFileTest() throws IOException {		for (int i = 0; i < 15; i++) {			fileInfoHelps.generatePreviewFile(15, "openoffice//test.doc");//因为是为了项目所使用的,下面会附上fileInfoHel的代码		}	}}
package campus_mooc.common_utils.utils;import java.io.File;import java.io.FileInputStream;import java.io.IOException;import javax.transaction.Transactional;import org.apache.commons.io.FilenameUtils;import org.apache.log4j.Logger;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;import org.springframework.stereotype.Component;import org.springframework.web.multipart.MultipartFile;import campus_mooc.common_utils.exception.SystemException;import campus_mooc.core.module.domain.FileResource;import campus_mooc.core.module.service.IResourceService;/** * 文件帮助类 *  * @author lcx */@Component(value="fileInfoHelps")@Transactionalpublic class FileInfoHelps {		Logger logger = Logger.getLogger(FileInfoHelps.class);		@Value("${rootPath}")	String rootPath;// 保存视频文件根目录	@Autowired	IResourceService resourceService;		@Autowired	ThreadManager threadManager;		@Autowired	Doc2HtmlUtil doc2HtmlUtil;		public FileInfoHelps() {		//使用spring@value注入属性文件值		//rootPath = PropertiesUtils.getVal("ueditor.properties", "attachmentLocation");	}	public FileInfo saveFile(String relativePath, MultipartFile mpf) throws SystemException, IOException {		String methodName = "[FileInfoHelps->saveFile]";		// 真实文件保存绝对路径		String absPath = rootPath + relativePath;		// 文件扩展名		String ext = FilenameUtils.getExtension(absPath);		// 保存封面文件		try {			// 创建文件上一层文件夹			BaseCommonUtil.mkdir(absPath);			mpf.transferTo(new File(absPath));			logger.debug(methodName + "【" + mpf.getOriginalFilename() + "】文件保存成功!");		} catch (Exception e) {			// 文件保存出错处理			e.printStackTrace();			logger.debug(methodName + "【" + mpf.getOriginalFilename() + "】文件保存失败!");			return null;		}		return new FileInfo(ext, absPath,relativePath);	}		public boolean isPreviewFile(String ext) {		if (ext.toUpperCase().equals("DOC")) {			return true;		}		if (ext.toUpperCase().equals("PPT")) {			return true;		}		if (ext.toUpperCase().equals("XLS")) {			return true;		}		return false;	}	
	/**
	*预览文件生成代码
	*/	public void generatePreviewFile(int res_id,String relativePath) throws IOException{		String ext = FilenameUtils.getExtension(relativePath);		String absPath = rootPath+relativePath;		//预览文件保存在文件的当前目录下的preview的文件下		String savePath = absPath.substring(0, BaseCommonUtil.replaceFliePathStr(absPath).lastIndexOf("/"))+File.separatorChar+"preview";		//创建保存路径		if(!new File(savePath).exists()){			new File(savePath).mkdirs();		}		//预览文件线程操作		threadManager.execute(new Runnable() {			@Override			public void run() {				try {					//生成html预览文件					FileInputStream fis = new FileInputStream(new File(rootPath + relativePath));					String htmlName = doc2HtmlUtil.file2Html(fis, savePath, ext.toLowerCase());					String htmlPreview = relativePath.substring(0, BaseCommonUtil.replaceFliePathStr(relativePath).lastIndexOf("/"))+File.separatorChar+"preview"+File.separatorChar+htmlName;									//生成pdf预览文件					FileInputStream fis_ = new FileInputStream(new File(rootPath + relativePath));					String pdfName = doc2HtmlUtil.file2pdf(fis_,savePath, ext.toLowerCase());					String pdfPreview = relativePath.substring(0, BaseCommonUtil.replaceFliePathStr(relativePath).lastIndexOf("/"))+File.separatorChar+"preview"+File.separatorChar+pdfName;										FileResource fr = new FileResource();					fr.setId(res_id);					fr.setHtmlPreview(htmlPreview);					fr.setPdfPreview(pdfPreview);					resourceService.jdbcUpdateFilePreviewPath(fr);//在这里可以在预览文件生成后,写入数据库,前台只需要c标签判断非空显示预览按钮就可以啦				} catch (Exception ex) {					ex.printStackTrace();				}			}		});	}		public String getRootPath() {		return rootPath;	}	public void setRootPath(String rootPath) {		this.rootPath = rootPath;	}}

PS:说再多的文字不如读一读代码,在这里再强调一下这里多线程的思路,实例化多个不同端口的openoffice进程,都保持运行状态,把端口放在线程安全的集合中,队列最好,保证每个端口都可以被使用,每个线程获取到一个端口之后,别的线程不能获得相同端口,知道该线程操作完,将端口放回集合,其他线程继续有机会读取到该端口,

线程的管理使用JDK5 加入的ExecutorService 线程池管理,如果在单元测试单程序测试的话,一定要让主线程等待子线程结束,否则还没结果,测试就会结束,可以使用JD5的

CountDownLatch来实现主线程等待,同样使用thread.join方法也行,建议用CountDownLatch,便捷使用,有问题欢迎联系我QQ346640094

代码:

package campus_mooc.common_utils.utils;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import org.springframework.stereotype.Component;/** * 线程池 * @author Administrator * */@Component(value="threadManager")//交给Spring管理public class ThreadManager {	private ExecutorService executorService;	/**	 * 	 */	public ThreadManager() {		executorService = Executors.newFixedThreadPool(10);//初始化10的线程池,当执行的线程超过10,会等待线程池有空位	}		public void execute(Runnable runnable){		executorService.execute(runnable);	}}


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表