2021SC@SDUSC
目录
1、public void startOfficeManager(); 启动Office组件进程
2、public OfficeDocumentConverter getDocumentConverter()
3、private Map getLoadProperties()
4、private boolean killProcess():判断当前系统中是否已经存在正在运行的office组件进程。
5、public void destroyOfficeManager();
前言:
该项目支持预览多种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配置如下:
<dependency>
<groupId>org.artofsolving.jodconverter</groupId>
<artifactId>jodconverter-core</artifactId>
<version>3.0-beta-4</version>
</dependency>
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<String,?> getLoadProperties() {
Map<String,Object> 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();
}
}