本文共 7937 字,大约阅读时间需要 26 分钟。
2021SC@SDUSC
目录
前言:
该项目支持预览多种office文件如:doc、docx文档,word文档,ppt、pptx文档预览,word预览,pdf文档。其中word文档,pptx、ppt文档等有两种预览方式:图片预览、pdf预览。
- 图片预览:文件大,前台加载过慢
- pdf预览:内网访问,加载pdf快
对office文件的操作大多都依赖org.artofsolving.jodconverter包,相比com.artofsolving. jodconverter而言,org能操作的文件类型更多,但是文字清晰度比com低一点。
如果平均上传的文件不大于5M,且不超过5个文件,org包可以在10秒内处理完成。 但有个性能问题,文件超过20M时会出现超时,而源码中设置的单个pdf转换任务的执行时间是120s,如果文件太大就会导致超时报错,并重新进行连接,处理下一个任务。包的maven配置如下:
org.artofsolving.jodconverter jodconverter-core 3.0-beta-4
office插件管理分析:
该项目中涉及到很多office文件的转换,office发展时间很长,已经是一个庞大、稳定的文件体系,里面有非常多复杂的规则,单靠我们自己是很难写出一个office文件转换的功能插件来的,但是好在现在市场上提供了一些可以操作office的接口,只要使用好接口即可在我们个人的项目中实现office文件转换。
项目中使用的office工具类如下:
import org.artofsolving.jodconverter.OfficeDocumentConverter;
import org.artofsolving.jodconverter.office.DefaultOfficeManagerConfiguration;
import org.artofsolving.jodconverter.office.OfficeManager;
import org.artofsolving.jodconverter.office.OfficeUtils;
maven配置如下:
OfficePluginManager类是实现office文件管理的基础,提供对office文件的相关操作。在早期的系统版本中,这个类的名字叫ConverterUtils,实现功能大致一样,本文就接受较新的版本。OfficePluginManager 类中定义变量如下:
private final Logger logger = LoggerFactory.getLogger(OfficePluginManager.class); private OfficeManager officeManager; @Value("${office.plugin.server.ports:2001,2002}") private String serverPorts; @Value("${office.plugin.task.timeout:5m}") private String timeOut;
1、public void startOfficeManager(); 启动Office组件进程
准备工作:先定义一个File变量来检测是否存在office组件可以对office文件进行操作;开始对文件转换之前要结束office进程。
在方法定义处看到了@PostConstruct注解,该注解被用来修饰一个非静态的void方法,是一种JSR-250的规范。被@PostConstruct修饰的方法当bean创建完成的时候,会后置执行@PostConstruct修饰的方法,并且只会被服务器执行一次。通常我们会是在Spring框架中使用到@PostConstruct注解,该注解的方法在整个Bean初始化中的执行顺序是:Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注释的方法)。
该方法中首先声明File变量officeHome,通过调用officeUtils工具类的getDefaultOfficeHome方法获取,如果officehome为空,说明在系统中找不到office组件建立所需的条件,office.home的配置有误会,抛出运行异常提醒用户确认office.home的配置。若可以定义成功说明office组件配置没有问题,进入下一行代码声明bool变量killOffice,调用killProcess方法赋值,这个方法我们稍后进行具体分析。如果killProcess执行有返回true,说明此时系统中已经存在一个正在运行的office组件进程,则将会自动结束现在这条进程。
public void startOfficeManager(){ File officeHome = OfficeUtils.getDefaultOfficeHome(); if (officeHome == null) { throw new RuntimeException("找不到office组件,请确认'office.home'配置是否有误"); } boolean killOffice = killProcess(); if (killOffice) { logger.warn("检测到有正在运行的office进程,已自动结束该进程"); } try { DefaultOfficeManagerConfiguration configuration = new DefaultOfficeManagerConfiguration(); configuration.setOfficeHome(officeHome); String []portsString = serverPorts.split(","); int[] ports = Arrays.stream(portsString).mapToInt(Integer::parseInt).toArray(); configuration.setPortNumbers(ports); long timeout = DurationStyle.detectAndParse(timeOut).toMillis(); // 设置任务执行超时为5分钟 configuration.setTaskExecutionTimeout(timeout); // 设置任务队列超时为24小时 //configuration.setTaskQueueTimeout(1000 * 60 * 60 * 24L); officeManager = configuration.buildOfficeManager(); officeManager.start(); } catch (Exception e) { logger.error("启动office组件失败,请检查office组件是否可用"); throw e; } }
在下一行try{}catch{}中声明DefaultOfficeManagerConfiguration configuration变量,为之前声明的officehome设置安装目录,设计端口数为队列中的流数,设置任务执行超时为五分钟,设置任务队列超时为24小时,最后将configuration赋值给officemanagereManager:officeManager = configuration.buildOfficeManager(),最后启动officeManager线程: officeManager.start(),为项目中的office文件提高转换接口和资源。
2、public OfficeDocumentConverter getDocumentConverter()
实例化OfficeDocumentConverter变量converter ,为converter设置好默认加载参数后返回文件转换器converter。
public OfficeDocumentConverter getDocumentConverter() { OfficeDocumentConverter converter = new OfficeDocumentConverter(officeManager, new OfficePluginExtendFormatRegistry()); converter.setDefaultLoadProperties(getLoadProperties()); return converter; }
3、private Map<String,?> getLoadProperties()
实例化HaspMap对象loadproperties,设置"Hidden"为 true:设置"ReadOnly",为true转换文件时隐藏进程;设置 "UpdateDocMode",为UpdateDocMode.QUIET_UPDATE;设置"CharacterSet"为 StandardCharsets.UTF_8.name()即字符集为UTF-8。
private MapgetLoadProperties() { Map loadProperties = new HashMap<>(10); loadProperties.put("Hidden", true); loadProperties.put("ReadOnly", true); loadProperties.put("UpdateDocMode", UpdateDocMode.QUIET_UPDATE); loadProperties.put("CharacterSet", StandardCharsets.UTF_8.name()); return loadProperties; }
Map是一个接口,即Interface Map<K,V>,它的每个元素包含一个key和一个value对象,在这两个对象之间存在一种映射的对应关系。所有从Map集合中访问元素时,只有指定了key就可以找到对应的value,因此key必须是唯一的且不能重复,当key相同时,后面的value值会覆盖之前的value值。
HashMap,即Class HashMap<K,V>,是基于哈希表的Map接口实现,提供所有可选的映射操作,并允许空值和空键。这个类不保证map的顺序,特别是不保证该顺序会随着时间的推移保持不变!!
这个方法用来获取加载参数,设置如下:系统对上传的文件只能读、进程在工作时隐藏,字符集是UTF-8类型。
4、private boolean killProcess():判断当前系统中是否已经存在正在运行的office组件进程。
该方法在启动office组件时调用,一个系统中只需要存在一个office组件,因此需要在建立组件时判断一下当前是否存在,若存在则销毁。
首先声明Propreties变量props,调用System.getProperties获取系统参数,主要是为了获取操作系统的名称。之后在try{}catch{}语句中调用props.getProperty("os.name"))获取当前项目运行的操作系统。
若当前项目是在windows系统下,声明Process变量p,调用Runtime.getRuntime类获取Runtime类的实例,runtime是单实例的,每个Java应用程序都有一个该类的实例,它允许应用程序和运行应用程序的环境进行交互。使用exec
方法执行字符串命令并返回一个process
对象。
声明ByteArrayOutputStream对象baos在内存中创建一个字节数组缓冲区,所有发送到输出流的数据保存在该字节数组缓冲区中。创建字节数组输出流对象有以下几种方式。之后再声明一个InputStream对象os作为当前运行进程的输入流获取和一个大小为256的byte数组。
若os可以读入数据,则baos写出,如果写出字符串中包含"soffice.bin",说明此时系统里有正在运行的office组件,之后自动结束该进程,并返回true以新进程。
private boolean killProcess() { boolean flag = false; Properties props = System.getProperties(); try { if (props.getProperty("os.name").toLowerCase().contains("windows")) { Process p = Runtime.getRuntime().exec("cmd /c tasklist "); ByteArrayOutputStream baos = new ByteArrayOutputStream(); InputStream os = p.getInputStream(); byte[] b = new byte[256]; while (os.read(b) > 0) { baos.write(b); } String s = baos.toString(); if (s.contains("soffice.bin")) { Runtime.getRuntime().exec("taskkill /im " + "soffice.bin" + " /f"); flag = true; } } else { Process p = Runtime.getRuntime().exec(new String[]{"sh","-c","ps -ef | grep " + "soffice.bin"}); ByteArrayOutputStream baos = new ByteArrayOutputStream(); InputStream os = p.getInputStream(); byte[] b = new byte[256]; while (os.read(b) > 0) { baos.write(b); } String s = baos.toString(); if (StringUtils.ordinalIndexOf(s, "soffice.bin", 3) > 0) { String[] cmd ={"sh","-c","kill -15 `ps -ef|grep " + "soffice.bin" + "|awk 'NR==1{print $2}'`"}; Runtime.getRuntime().exec(cmd); flag = true; } } } catch (IOException e) { logger.error("检测office进程异常", e); } return flag; }
若当前系统不是windows系统,执行过程与上述语句相似,只不过将获取线程的方法由Runtime.getRuntime().exec("cmd /c tasklist ")换成Runtime.getRuntime().exec(new String[]{"sh","-c","ps -ef | grep " + "soffice.bin"})。毁灭线程的方法由Runtime.getRuntime().exec("taskkill /im " + "soffice.bin" + " /f")换成String[] cmd ={"sh","-c","kill -15 `ps -ef|grep " + "soffice.bin" + "|awk 'NR==1{print $2}'`"}。
5、public void destroyOfficeManager();
负责销毁最初创建的OfficeManager对象,防止内存泄露。
该销毁方法前有一个@PreDestroy注解,被该注解修饰的方法会在服务器卸载Servlet的时候运行,并且只会被服务器调用一次,类似于Servlet的destroy()方法,但是会在destroy()方法之后运行,在Servlet被彻底卸载之前。
项目结束前运行此方法,这是为了保证线程安全,如果office组件存在且还在运行,就会销毁掉线程。
public void destroyOfficeManager(){ if (null != officeManager && officeManager.isRunning()) { logger.info("Shutting down office process"); officeManager.stop(); } }
转载地址:https://blog.csdn.net/m0_55503427/article/details/121441822 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!