Rails 用 Ruby on Rails 实现适应各种平台的在线 Office 文档预览

alex_cheng · 2014年02月17日 · 最后由 hezoon 回复于 2021年07月09日 · 28770 次阅读
本帖已被管理员设置为精华贴

前言

在许多 Web 应用中都需要预览文档的功能。而用户可能用不同的设备访问 Web 应用,可能是装有 Windows 系统的 PC 台式机,也有可能是 iOS 系统的 iPad。一般来说,要预览的文档通常是主流的 Office 文档,包括.doc、.ppt、.docx、.pptx 文档,也包括 Adobe 的.pdf 文档。

因此开发一个能够在不同客户端上使用的,支持主流 Office 文档类型的在线文档预览系统就显得非常重要。

目标

实现基于 Web 的跨平台的在线预览功能,支持主流 Office 文档。另外我们的服务器是 Linux 系统,不能够安装 Microsoft Office 软件。

我的解决方案

先大致地说一下我是如何实现的。为了实现这个功能,我用了下面的组件:

  • PDF.js
  • OpenOffice
  • PyODConverter

PDF.js 是 Mozilla 实验室开发的关于 HTML5 技术的一个开源项目,用于在网页上显示 PDF 文档,而不需要任何浏览器插件等原生代码。目前这个开源项目放在了 GitHub 上,网址是https://github.com/andreasgal/pdf.js

演示地址:http://mozilla.github.io/pdf.js/web/viewer.html

OpenOffice是 Apache 开源软件基金会开发的一款开源软件,是可以在 Linux 上运行的 Office 软件。OpenOffice 可以打开主流的 Microsoft Office 的各种文档,例如.doc,.docx,.ppt,.pptx 格式的文档。OpenOffice 有一个文档转换服务,这个服务是以网络服务的形式运行的。启动这个服务,我就可以在任何类型的代码中通过端口调用这个服务。

PyODConverter是一个 Python 脚本,用于自动化的文档格式转换,依赖于 LibreOffice or OpenOffice。我用它把 Office 文档转成 PDF 文档。

PyODConverter 代码托管在 https://github.com/jamayka/barberry-plugin-openoffice/tree/master/externals/pyodconverter 上。

实现过程

整个实现分为“Office 文档转 PDF”“PDF 文档在 Web 端显示”两部分。 ,后端负责将各种 Office 文档通过 OpenOffice 服务转换成 PDF 文档,而前端通过 pdf.js 显示 PDF 的内容。由于是采用了 pdf.js 显示 PDF 文档,浏览器端不需要安装任何插件,但是需要支持部分 HTML5 的功能特性。因此旧浏览器例如 IE8,IE7,IE6 就不能够使用 pdf.js 了。

Office 文档转 PDF

我利用了 OpenOffice 服务将各种 Office 文档转换成了 PDF 文档。写的脚本是rake task脚本,这样我可以用rake命令运行此脚本。这个脚本作为一个守护进程运行,它不断地检查系统里是否有新的上传文件。如果有新上传的文件,则启动转换过程,步骤如下:

  1. 启动 OpenOffice 的服务
  2. 检查是否有新的 Office 文档上传
  3. 调用 PyODConverter 转换文档
  4. 更新 Model 上的字段,指向生成的用于预览的 PDF
  5. 重复步骤 2 到 4

整个 rake task 脚本的代码如下:

# -*- encoding : utf-8 -*-
    PYTHON_PATH = '/opt/openoffice4/program/python'
    CONVERTER_PATH = File.expand_path('../DocumentConverter.py', __FILE__)

    def gen_preview(beDaemon = false)
      begin
        no_preview_materials = CoursewareMaterial.where(preview_file_name: nil).where(['doc', 'docx', 'ppt', 'pptx'].map {|x| "upload_file_name LIKE '%.#{x}'"}.join(' or '))
        if no_preview_materials.size > 0
          puts "Prepare to process #{no_preview_materials.size} items."
          no_preview_materials.each do |material|
            begin
              generate_preview material
            rescue
              puts "[Exception] #{$!}".red
            end
          end
        end

        break if not beDaemon

        sleep 30
      end while true
    end


    def generate_preview material
      puts "Generating preview PDF for [#{material.upload_file_name}]..."
      if material.upload.path.to_s =~ /\.(doc|docx|ppt|pptx|pdf)$/
        tmpPdfPath = File.join(Dir.tmpdir, File.basename(material.upload.path, '.*') + '.pdf')

        # try to delete existing temporary file if it exists.
        begin
          File.delete tmpPdfPath
        rescue
          # do nothing
        end

        # relaunch openoffice if it quit.
        if `pgrep soffice`.size == 0
          puts 'Relaunch OpenOffice service.'
          spawn 'soffice "-accept=socket,host=localhost,port=8100;urp;StarOffice.ServiceManager" -norestore -nofirststartwizard -nologo -headless &'
        end

        out = nil
        cmd = "#{PYTHON_PATH} #{CONVERTER_PATH} '#{material.upload.path}' '#{tmpPdfPath}'"
        puts "Execute command: \n#{cmd}"
        out = `#{cmd}`
        if File.exists?(tmpPdfPath)
          material.preview = File.open(tmpPdfPath, 'rb')
          material.save
          File.delete tmpPdfPath
          puts "Preview PDF for [#{material.upload_file_name}] is generated.".green
        else
          puts "An error occured when invoke DocumentConverter.py. #{out}".red
        end
      else
        puts "The material [#{material.upload_file_name}] is not available for generating preview PDF.".yellow
      end
    end

PDF 文档在 Web 端显示

首先在 asset pipeline 中添加 pdf.js,这一步可以通过引入pdfjs-rails这个 Gem 实现。然后在页面中引用需要的 js 和 css 资源即可,而这些资源包含在pdfjs-rails中。

Gem pdfjs-rails代码托管在https://github.com/concordia-publishing-house/pdfjs-rails中。

然后在 view 上添加前端代码。我用的是 ERB,因此对应的文件是 preview.html.erb。此文件中的代码如下所示:

<%= javascript_include_tag "compatibility" %>
<%= javascript_include_tag "l10n" %>
<%= javascript_include_tag "pdf" %>
<%= javascript_include_tag "pdfviewer" %>
<%= stylesheet_link_tag"viewer", :media => "all" %>
......
<div>
    <%  if not (pdf_url.nil? or pdf_url.empty?) %>
        <b>内容预览:</b>
        <%=   pdf_viewer pdf_url %>
    <%  end %>
</div>
......

如以上代码所示,pdfjs_rails提供了用于自动生成 HTML 的pdf_viewer方法,简化了代码。当要显示一个文档时,还需要判断这个文档对应的 PDF 文档有没有生成,因此需要一个类似于if pdf_url.nil?的代码。为了简单起见,我就直接把此代码写在 view 上了。

安装部署

安装 OpenOffice 到 Linux CentOS

这里有详细说明 http://www.if-not-true-then-false.com/2010/install-openoffice-org-on-fedora-centos-red-hat-rhel/

以作为后台服务的方式运行 OpenOffice

这里有详细说明 http://stackoverflow.com/questions/11591643/failed-to-connect-to-openoffice-headless-mode

下载 PyODConverter 脚本

https://github.com/concordia-publishing-house/pdfjs-rails 下载 PyODConverter 脚本,放在lib/tasks/prevew目录下面,然后我把它直接上传到我的代码库里。

演示

其它几种方案

我在方案选型的过程中研究了好几种方案,但是除了上面所描述的方案之外的其他所有方案都因为各种原因而不能满足要求。下面一一列举这些未选中的方案,以及解释为何它们满足不了要求。

FlexPaper

FlexPaper 是一个商业软件,但是有采用 GPLv3 开源协议的免费版。然而,它仅支持 ASP.NET、PHP 和 Java,而且部署相对复杂。我的系统是基于 Linux 的,开发技术是 Ruby on Rails,因此不太容易和它整合在一起。

Accusoft Document Viewers

Accusoft Document Viewers 是一个商业软件,有一些很酷的 Demos,下面列举两个 Demo 的网址。

可是这个软件的价格太高,而且不是提供一个组件,而是提供一套解决方案,对于我们的目标来说,这些解决方案太“重”了。

Google 的文档预览服务

Google 的文档预览服务 (Google Docs Viewer) 真心很不错,这里有个应用实例,但是最大的问题是经常被墙,无法稳定地工作。

CUPS-PDF

根据一个 StackOverflow 上的解答,有一个叫 CUPS-PDF 的软件可以安装在 Linux 系列(包括 Mac OS X)的操作系统上。该软件能够为系统添加一个 PDF 打印机。它是另一种文档转 PDF 的方法,通过安装 PDF 打印机将文档“打印”成 PDF 文档。用 OpenOffice 的命令行工具可以打印一个文档,并指定要使用的打印机。只要指定使用 CUPS-PDF 的打印机,就能够输出一个 PDF 文件到文件系统中的一个位置上。

这种方法可以很精确地将文档转换成 PDF 文档,可以说是“所打印即所得”。但是转换后的 PDF 文档都非常的大。比如一个 0.5M 的 Word 文档,用这种方法转换后的 PDF 竟然要 300 多 M。因此这种方法并不可行。

多年前用 java 做过类似项目,依赖于 openoffice

把 office 2 pdf 改成 sidekiq worker 上传文件后触发是不是好一点 毕竟时刻 rake 的资源消耗比 sidekiq 大多了

唉,这个轮子大家造的也够多的……

我是用了 jodconverter + tomcat 通过 http 的方式调用的

#2 楼 @zj0713001 关于 sidekiq 的 License,商用的会不会有问题?LGPL 一直不敢用

#4 楼 @lang1pal LGPL 的意思不是 基于他的软件可以选择不开源么 我理解错了?

#5 楼 @zj0713001 LGPL 好像商用不是很友好,所以一直不敢用 上次有位大哥发的图是这样的

以前用过,用来在线阅读 pdf 电子书

#6 楼 @lang1pal 是呢 他没提到对商用怎样 只是说明了对自身及其新增代码的许可证 唉 不行就买 sidekiq pro 吧 哈哈

pdf.js 显示效果 很一般把~

那请问有朋友能够提供一下在线编辑的解决方案么?!预览的倒是挺多了

有没有用过这个,api 之~ http://www.idocv.com/

#6 楼 @lang1pal #8 楼 @zj0713001 GPL/LGPL 都是不提商用的,协议里面不提就是没限制。。。

LGPL 只要你开源你修改的部分即可,其他相关系统不用公开。

#12 楼 @ninehills 3x~ 原来不限制就是不管啊 我一直也是迷迷糊糊 以为没说就是不让...

#10 楼 @ZhongWen_Zhou 直接上 Office Web App……

#13 楼 @zj0713001 LICENSE 和法律一样的,法不禁止即允许啦

flexpaper

半年前研究过,用的 crocodoc

用 DocumentConverter.py 转换为 swf 格式,用 flash 预览是不是也可以

https://docs.google.com/viewer

Image files (.JPEG, .PNG, .GIF, .TIFF, .BMP) Video files (WebM, .MPEG4, .3GPP, .MOV, .AVI, .MPEGPS, .WMV, .FLV) Text files (.TXT) Markup/Code (.CSS, .HTML, .PHP, .C, .CPP, .H, .HPP, .JS) Microsoft Word (.DOC and .DOCX) Microsoft Excel (.XLS and .XLSX) Microsoft PowerPoint (.PPT and .PPTX) Adobe Portable Document Format (.PDF) Apple Pages (.PAGES) Adobe Illustrator (.AI) Adobe Photoshop (.PSD) Tagged Image File Format (.TIFF) Autodesk AutoCad (.DXF) Scalable Vector Graphics (.SVG) PostScript (.EPS, .PS) TrueType (.TTF) XML Paper Specification (.XPS) Archive file types (.ZIP and .RAR)

如果不介意太多依赖,后台 office 文档的转换可以用 Docsplit,根据所需要的功能选择相应的依赖软件。

我这篇文章其实是为了抛砖引玉,看到有更好的方法,挺好的。

实在是太巧了,您的方案我完全认同,我个人也酝酿了一段时间的类似项目,目前解决方案是 针对浏览器分别采用 flexpaper 和 pdf.js,另外,我会重点开发 pdf.js,并引导用户使用 chrome 或者火狐来体验高级的 html5 技术

#9 楼 @xieren58 那只是你认为的,pdf.js 可以用 html5 ,node.js,css3 做出任何你想要的效果和扩展

针对可扩展性以及兼容性考虑,我 认为 flexpaper 和 pdf.js 是目前为止比较靠谱的实现方案。

#10 楼 @ZhongWen_Zhou 很难,目前没有碰到

#10 楼 @ZhongWen_Zhou 我想微软会不会提供呢 微软不是在搞 Office 365 吗?

用 jodconverter 做过类似的,依赖于 openoffice,还有 pdf2json

@Alex_Cheng google docs 和 sky dirver 除外,有无 office 在线编辑方案呢?

@Alex_Cheng @dw250100785 @chunlea 谢谢各位,本来是打算上 ms 的 sharepoint 和 web office app,但是部署成本大,并且授权费比较高,现在已经考虑使用 点聚 的 weboffice 组件,貌似还可以,连 chrome 都有兼容方案

@ZhongWen_Zhou 这东西在系统部署上有没有什么限制?

@imlcl 没有,就是一个 ActiveX 插件

#31 楼 @ZhongWen_Zhou 谢谢,有空我也尝试一下 : )

33 楼 已删除

@WaterFlowsEast 用 Docsplit 转 ppt 为 images 时,中文乱码,是什么原因呢?是否 libreoffice 打开 ppt 中文就是乱码

文档转换工具推荐 unocov 不用自己写代码,依赖 OpenOffice

#各种文档转换成pdf

#install libreoffice
sudo apt-get install libreoffice

#需要安装中文字体,不然不识别中文
sudo apt-get install ttf-wqy-zenhei

#install unoconv
sudo apt-get install unoconv

#Or install the latest version from github https://github.com/dagwieers/unoconv

#convert usage
unoconv -f pdf *.ppt *.doc *.xls

同意 2 楼,用触发来执行转化脚本,而不是轮询

38 楼 已删除
39 楼 已删除
40 楼 已删除
41 楼 已删除
42 楼 已删除
43 楼 已删除
45 楼 已删除

楼主,推荐用这个来监听文件系统,轮训太蛋疼了: https://github.com/guard/listen

49 楼 已删除

刚好要用上,除了 Viewer 在线编辑有没有好的解决方案?

怎么在这个网站里发帖啊,我怎么没法发?

匿名 #55 2014年03月02日

#6 楼 @lang1pal GPL 之类的限制的前提是对软件或衍生品的「再分发」,即使是 GPL 的软件,自己修改了自己用不公开源码,也是没问题的,但是如果要把它送给、或者卖给客户,那就必须附带源码。

一个常见案例就是国内外各大公司在自己数据中心的机器上部署自己修改过内核的 Linux。

哪位大神可以教教我 Ruby on Rails,我没有任何基础和经验,但我应该够勤奋。

用 open office 的话,会不会有很多 PPT、Excel 之类的格式乱掉?

楼主,我用你的代码测试了一下,但是只显示出工具栏,没有文件预览

楼主,不知道在解析 word 文档方面有没有什么好的建议,比如将 word 文档中的内容解析然后入库到对应字段,之前写过,但是放弃了,原因是你没法有效的分割内容

#60 楼 @naitnix 谢谢你的关注,如果有这方面的需求,我可以研究一下。

Office 毕竟是微软的,Linux 是跟微软不是一个阵营的,所以从本质上折腾是够累的。我强烈建议采用第三方的东西,www.officeweb365.com/docview.aspx,当然不是自己的孩子,总觉得心里不美,程序猿的完美人生啊

@xuse2008 赞一个,officeweb365 很棒

其实微软已经出了大杀器了,http://technet.microsoft.com/zh-cn/library/jj219437v=office.15).aspx( ,直接用这个,避免各种临时预览用文件的转换和存储,并且效果不失真。 自己搭建服务器麻烦,可以用楼上的,不过似乎 PPT 预览不行啊,出 bug 了?

可以试试这个第三方的软件预览服务 www.idocv.com

在线预览大家都搞定了?

为什么不用 libreoffice?这里能用 Libreoffice 代替 openoffice 吗?

我知道一家公司做在线预览很牛,腾讯邮箱用的也是他们的,当时想采购的,就是价格有点贵。 我这边还有他们销售的联系方式,15950597752,姓贾,大家有需要可以自取

linjunhalida 如何在网页上面展示 word 文档? 提及了此话题。 04月03日 10:57

试试这个好不好用吧。 http://dcs.hezoon.com/

需要 登录 后方可回复, 如果你还没有账号请 注册新账号