springboot实战之office文档在线预览

  • 2019 年 11 月 27 日
  • 笔记

前言

文档在线预览在企业级应用开发也算是比较常遇见的需求了,通常处理这方面的需求大致有如下的方案,

1、购买成熟的第三方产品

比如永中DCS,其产品介绍可以查看如下链接

http://dcs.yozosoft.com/help.html

在比如idocv,其产品介绍可以查看如下链接

https://www.idocv.com/docs.html

2、自研实现文档预览服务器

标题取得高大上,常用的方法基本上也是基于第三方类库进行实现

  • 比如flash的flexpaper将文档转换为swf格式,然后使用flash在网页中浏览
  • 在比如java可以使用jodConverter+openoffice/libreoffice

3、文档预览是选择成熟第三产品还是自研?

如果公司成本预算充足,建议使用第三方成熟的产品,俗话说术业有专攻,正常专门做这方面的产品,不管是在技术上还是运维上都会比较成熟可靠,如果成本有限,还是自研吧,实现一个简单版本的文档预览服务器还是比较容易,但就是可能会采坑比较多。这边的选择只是针对文档预览,而非所有技术、产品选择都按这样选择,毕竟很多时候的自研的成本远远大于购买第三方服务,要具体情况具体分析

下边以jodconverter+LibreOffice为例,来实现一个简单的文档预览。这边为啥选LibreOffice而不选OpenOffice,其原因可以参考如下如下文章

OpenOffice与LibreOffice,哪个更适合你 https://www.linuxdashen.com/openoffice%E4%B8%8Elibreoffice%EF%BC%8C%E5%93%AA%E4%B8%AA%E6%9B%B4%E9%80%82%E5%90%88%E4%BD%A0 Apache OpenOffice 与 LibreOffice 之间的抉择 https://yq.aliyun.com/articles/81336

技术产品选型,可以从产品官方文档入手,产品的更新迭代活跃度、社区活跃度、是否易用等维度来进行考量,总归一句话不管黑猫白猫,能抓住老鼠的猫就是好猫,能够满足需求就行

正文

实现流程

安装LibreOffice

1、window环境

可以通过下方链接下载,并按提示一步一步安装

https://zh-cn.libreoffice.org/download/download/

2、centos环境

#安装文件        yum -y install libreoffice        #安装中文包        yum -y install libreoffice-langpack-zh-Han*        #安装的目录在/usr/lib64/libreoffice

通过命令行测试LibreOffice

#centos    /usr/bin/libreoffice  --invisible --convert-to pdf  <待转换的word路径> --outdir <生成的pdf路径>      比如:/usr/bin/libreoffice  --invisible --convert-to pdf  test.txt --outdir abc        # windows    soffice.exe --headless --invisible --convert-to pdf test.txt --outdir d:abc

代码实现

1、pom.xml

<!-- office预览相关-->      <dependency>        <groupId>org.jodconverter</groupId>        <artifactId>jodconverter-core</artifactId>      </dependency>      <dependency>        <groupId>org.jodconverter</groupId>        <artifactId>jodconverter-local</artifactId>      </dependency>      <dependency>        <groupId>org.jodconverter</groupId>        <artifactId>jodconverter-spring-boot-starter</artifactId>      </dependency>      <dependency>        <groupId>org.jodconverter</groupId>        <artifactId>jodconverter-online</artifactId>      </dependency>      <dependency>        <groupId>org.libreoffice</groupId>        <artifactId>ridl</artifactId>      </dependency>

2、application.yml

jodconverter:    local:      enabled: true      office-home: ${office.home}      port-numbers: 8100,8101,8102      max-tasks-per-process: 100    store:      path: ${store.path}

其中office.home和store.path,这两个属性一个是LibreOffice的安装路径,一个是LibreOffice转换后存放文档的路径,其中这两个属性的具体内容配置在pom.xml,内容如下

<profiles>      <profile>        <id>win</id>        <properties>          <office.home>D:/Program Files/LibreOffice</office.home>          <store.path>D:/data/preview</store.path>        </properties>        <activation>          <activeByDefault>true</activeByDefault>        </activation>      </profile>      <profile>        <id>linux</id>        <properties>          <office.home>/usr/lib64/libreoffice</office.home>          <store.path>/data/preview</store.path>        </properties>        <activation>          <activeByDefault>false</activeByDefault>        </activation>      </profile>    </profiles>

通过maven的profile来指定环境,默认是window环境,如果要切换到linux环境,则打包是执行

mvn clean package -P linux

或者打包时,改下pom.xml,把默认环境切换为linux

3、转换文档的核心代码

@Service  @Slf4j  public class PreviewServiceImpl implements PreviewService {      @Value("${jodconverter.store.path}")    private String storePath;      @Autowired    private DocumentConverter documentConverter;      @Override    public FileConvertResultDTO convertFile2pdf(File sourceFile,String fileExt) {      FileConvertResultDTO fileConvertResultDTO = new FileConvertResultDTO();      try {        fileExt = fileExt.toLowerCase();        String fileName = FileUtil.getWithoutExtension(sourceFile.getName());        String targetFileExt = getTargetFileExt(fileExt);        File targetFile = new File(storePath+ FileUtil.SLASH_ONE + fileName + FileUtil.DOT + targetFileExt);        documentConverter.convert(sourceFile).as(DefaultDocumentFormatRegistry.getFormatByExtension(fileExt))            .to(targetFile).as(DefaultDocumentFormatRegistry.getFormatByExtension(targetFileExt)).execute();        fileConvertResultDTO.setStatus("success");        fileConvertResultDTO.setTargetFileName(targetFile.getName());      } catch (OfficeException e) {        log.error("convertFile2pdf error : " + e.getMessage(),e);        fileConvertResultDTO.setStatus("fail");      }      return fileConvertResultDTO;      }      @Override    public FileConvertResultDTO convertInputStream2pdf(InputStream in, String fileName, String fileExt) {      FileConvertResultDTO fileConvertResultDTO = new FileConvertResultDTO();      try {        fileExt = fileExt.toLowerCase();        fileName = FileUtil.getWithoutExtension(fileName);        String targetFileExt = getTargetFileExt(fileExt);        File targetFile = new File(storePath+ FileUtil.SLASH_ONE + fileName + FileUtil.DOT + targetFileExt);        documentConverter.convert(in).as(DefaultDocumentFormatRegistry.getFormatByExtension(fileExt))            .to(targetFile).as(DefaultDocumentFormatRegistry.getFormatByExtension(targetFileExt)).execute();        fileConvertResultDTO.setStatus("success");        fileConvertResultDTO.setTargetFileName(targetFile.getName());      } catch (OfficeException e) {        log.error("convertInputStream2pdf error : " + e.getMessage(),e);        fileConvertResultDTO.setStatus("fail");      }      return fileConvertResultDTO;    }      /**     * 获取想要转换的格式类型     * @return     */    private String getTargetFileExt(String originFileExt){       if(Constants.fileType2Htmls.contains(originFileExt)){         return FileUtil.HTML;       }       return FileUtil.PDF;    }      @PostConstruct    private void init(){      File targetDir = new File(storePath);      if(!targetDir.exists()){        targetDir.mkdirs();      }    }  }

4、拉取office服务器文档代码

@GetMapping(value="/readFile")    public ResponseEntity<byte[]> readFile(String fileName){      if(StringUtils.isBlank(fileName)){        log.warn("fileName is blank");        return new ResponseEntity<>(HttpStatus.BAD_REQUEST);      }        String fileExt = FileUtil.getExtension(fileName);      if(StringUtils.isBlank(fileExt)){        fileName = fileName + FileUtil.PDF;      }      String filePath = storePath + FileUtil.SLASH_ONE + fileName;      File file = new File(filePath);          if(!file.exists()){          log.warn("fileName:{} is not found",fileName);          return new ResponseEntity<>(HttpStatus.NOT_FOUND);      }        try {        //判断文件类型        String mimeType = URLConnection.guessContentTypeFromName(file.getName());        if(mimeType == null) {          mimeType = "application/octet-stream";        }        response.setContentType(mimeType);          //设置文件响应大小        response.setContentLengthLong(file.length());          byte[] bytes = FileUtil.readFileToByteArray(file);        response.getOutputStream().write(bytes);        return new ResponseEntity<>(bytes,HttpStatus.OK);      } catch (IOException e) {        log.error("readFile error:"+e.getMessage(),e);      }      return new ResponseEntity<>(HttpStatus.EXPECTATION_FAILED);      }

5、测试验证

测试步骤按上方的流程图来测试 a、上传文档

b、点击确定按钮,进行跳转预览

参考文档

自由免费的全能办公套件

文档在线预览方案

https://blog.csdn.net/xiaqingxue930914/article/details/81121581

SpringBoot使用LibreOffice转换PDF

https://segmentfault.com/a/1190000015129654

总结

java版的office文档预览,本质上就利用jodconverter去连接openoffice或libreoffice服务,相当于我们自己实现的office服务器本质上算是一个openoffice或libreoffice代理服务器。在实现过程中,当excel转换pdf时,会存在一些坑点,比如excel的列的宽度大的时候生成的pdf会自动换行,有多个sheet页的时候默认也只能生成出来一个。解决的方案是,当遇到文档类型为excel时,就不要转换为pdf格式,而是转换为html格式

demo链接

https://github.com/lyb-geek/springboot-learning/tree/master/springboot-office-preview