kkFileView代码分析(四)——office文件的转换(1)office插件管理
发布日期:2022-03-08 21:50:38 浏览次数:2 分类:技术文章

本文共 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 Map
getLoadProperties() { 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 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:kkFileView代码分析(二)——文件控制分析
下一篇:kkFiewView代码分析(三)有关CAD文件的转换

发表评论

最新留言

很好
[***.229.124.182]2024年03月23日 00时06分42秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章

java字符串三目_java字符串连接运算符和三目运算符 2019-04-21
java 堆内存 非堆内存_JVM 堆内存和非堆内存 2019-04-21
Java新手写什么demo_通过入门demo简单了解netty使用方法 2019-04-21
java国际化bundle_java语言国际化--ResouceBundle、struts 2019-04-21
java图片延迟加载_jQuery实现图片延迟加载 2019-04-21
java开发加入购物车功能_java web开发——购物车功能实现 2019-04-21
Java虚拟机不能满足_深入理解Java虚拟机--读书笔记1/3 2019-04-21
python 协程 asyncio_python – asyncio.as_completed是否会产生期货或协同程序? 2019-04-21
java设定xml文件的encoding_配置web-xml解决中文乱码问题,及各种乱码问题集结 2019-04-21
hanlp java api_java分词工具hanlp介绍 2019-04-21
nginx php 源码安装,Nginx1.12.2加php7.2.0的编译安装 2019-04-21
php 删除字节,php – 删除无效/不完整的多字节字符 2019-04-21
php 实现版本号对比,如何在PHP中实现比较版本号 2019-04-21
php sql 给数据库追加内容,php如何向数据库中的某串数据后追加内容【急】 2019-04-21
php微信小程序获取用户信息,微信小程序授权获取用户详细信息openid的实例详解... 2019-04-21
Java三元运算和if,Java三元运算符与<JDK8兼容性中的if / else 2019-04-21
graphql-php enum,php – 如何在不写长查询的情况下查询所有的GraphQL类型字段? 2019-04-21
php date 函数用法,php中date()日期时间函数使用方法 2019-04-21
php除法获取整数和余数,PHP除法取整和取余数 2019-04-21
java迷宫路径,Java中的迷宫路径查找器 2019-04-21