<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>alex_cheng (Alex Cheng)</title>
    <link>https://ruby-china.org/alex_cheng</link>
    <description>keep hungry, keep foolish</description>
    <language>en-us</language>
    <item>
      <title>现在上海 Ruby 程序员好找吗？</title>
      <description>&lt;p&gt;思考系统选型，想用 Ruby on Rails，觉得 Java Spring 虽然好但是还是不够精简。但是一个考量点是 Java 程序员相对较多，好招。不知道上海的 Ruby 程序员容不容易招。&lt;/p&gt;</description>
      <author>alex_cheng</author>
      <pubDate>Fri, 17 May 2019 13:18:31 +0800</pubDate>
      <link>https://ruby-china.org/topics/38543</link>
      <guid>https://ruby-china.org/topics/38543</guid>
    </item>
    <item>
      <title>Ruby on Rails 或者 Sinatra 是否可以开发 P2P 的平台</title>
      <description>&lt;p&gt;最近在考虑做一个 P2P 的平台，因为我之前一直是 Ruby 相关技术的粉丝，所以考虑是不是可以用 Ruby on Rails 或者 Sinatra 做这种 P2P 平台。但是一般的做法好像是用 Java 作为核心服务器，外围用 PHP。我的问题是有人用 RoR 或者 Sinatra 搭建过 P2P 平台吗？如何做？怎么做？&lt;/p&gt;

&lt;p&gt;希望朋友们能够给与指导，帮到我的必有酬谢。&lt;/p&gt;</description>
      <author>alex_cheng</author>
      <pubDate>Mon, 08 Jun 2015 16:07:19 +0800</pubDate>
      <link>https://ruby-china.org/topics/25928</link>
      <guid>https://ruby-china.org/topics/25928</guid>
    </item>
    <item>
      <title>开发 app 的后台，用 ror 或者 Sinatra 能否合适？</title>
      <description>&lt;p&gt;开放性问题，希望大家分享点经验。&lt;/p&gt;</description>
      <author>alex_cheng</author>
      <pubDate>Tue, 30 Dec 2014 16:25:36 +0800</pubDate>
      <link>https://ruby-china.org/topics/23480</link>
      <guid>https://ruby-china.org/topics/23480</guid>
    </item>
    <item>
      <title>如何做静态文件的 URL Rewriting</title>
      <description>&lt;p&gt;有静态文件 graph.html，我希望对于这样的请求"//graph.html;sessionid="，可以得到信息，并且可以返回静态文件 graph.html 的结果，但不希望直接使用 io 操作（即要用 file.read）。&lt;/p&gt;

&lt;p&gt;谢谢！&lt;/p&gt;</description>
      <author>alex_cheng</author>
      <pubDate>Tue, 21 Oct 2014 16:59:26 +0800</pubDate>
      <link>https://ruby-china.org/topics/22173</link>
      <guid>https://ruby-china.org/topics/22173</guid>
    </item>
    <item>
      <title>如何介绍 Ruby on Rails？</title>
      <description>&lt;p&gt;我在 Autodesk 工作，在部门里推荐 Ruby on Rails，将要做一个讲座。我的目的是让其他人了解到 RoR 的长处和短处（但主要是介绍长处）。让大家对这个技术有信心和兴趣。&lt;/p&gt;

&lt;p&gt;请问各位大牛，有没有什么建议和资料分享？&lt;/p&gt;

&lt;p&gt;比如有哪些比较好的讲座可以参考，有哪些资料可以参考。&lt;/p&gt;

&lt;p&gt;谢谢！&lt;/p&gt;</description>
      <author>alex_cheng</author>
      <pubDate>Wed, 13 Aug 2014 13:55:49 +0800</pubDate>
      <link>https://ruby-china.org/topics/21000</link>
      <guid>https://ruby-china.org/topics/21000</guid>
    </item>
    <item>
      <title>如何将我自己的 WordPress 里的文章导入到 Ruby China 论坛</title>
      <description>&lt;p&gt;如题。&lt;/p&gt;</description>
      <author>alex_cheng</author>
      <pubDate>Wed, 25 Jun 2014 13:56:07 +0800</pubDate>
      <link>https://ruby-china.org/topics/20157</link>
      <guid>https://ruby-china.org/topics/20157</guid>
    </item>
    <item>
      <title>用 JSHint 帮助提高代码质量</title>
      <description>&lt;p&gt;（转自 &lt;a href="http://www.alex-cheng.me/?p=110" rel="nofollow" target="_blank"&gt;http://www.alex-cheng.me/?p=110&lt;/a&gt;）
JSHint 是社区发起的开源项目。目的是用来检查 JavaScript 中代码的错误和潜在问题，以及不符合既定代码规范的地方。代码规范是可定制的，也就是说，开发组织可以根据需要在 JSHint 中定制自己的规范。JSHint 也提供在线版本。它的官网是：&lt;a href="http://www.jshint.com/" rel="nofollow" target="_blank"&gt;http://www.jshint.com/&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;JSHint 可以检查出一些简单的语法错误，比如少一个;号的情况，也比如引用了一个未定义的变量的情况。如果要自定义代码规范，只要修改.jshintrc 这个文件就行了。&lt;/p&gt;

&lt;p&gt;要安装 JSHint，可以有很多种途径，命令行运行 jshint 是一种方法，集成到编辑器中或者 IDE 中也是一种很好的使用 jshint 的途径。目前 JSHint 可以集成的编辑器或 IDE 有：VIM, Emacs, Sublime Text, TextMate, Visual Studio, Brackets 等等。还有一种在线的使用方式，就是访问网址&lt;a href="http://www.jshint.com/" rel="nofollow" target="_blank"&gt;http://www.jshint.com/&lt;/a&gt;，然后把代码贴到左边（删除已有的示例代码）。&lt;/p&gt;

&lt;p&gt;除此之外还有一些基于 JSHint 的有用的工具，例如：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;jshintr 只要在 js 代码的网址后面的.js 替换成.jshint，就能够看到此 js 代码的 JSHint 的检查结果。但是我没用过，因为需要部署 jshintr 的服务器端 node 代码。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;FixMyJS 用于自动修复 JavasScript 的简单错误，例如少了个;号，变量定义了多次等。
但我试用了一下，有个大问题是 FixMyJS 总是删除空行，但是代码之间的空行是我故意留出来的。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;jshint-gem JSHint on Rails &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;虽然 JSHint 检查的是一些简单的代码缺陷，但并不能检查出程序的逻辑错误。比如以下代码错误用 JSHint 就检查不出来。
for(var i=1; i &amp;lt; 103; ++i) {
     for(var i=1; i &amp;lt; 100; ++i) {
          console.log(i);
     }
}&lt;/p&gt;

&lt;p&gt;但是不管怎样，JSHint 把程序员从繁琐低级的代码检查工作中解脱出来，这样可以有更多的精力做创新的事情。作为程序员，花时间去加一个;号这种没意义的事情最好就让计算机做吧。&lt;/p&gt;</description>
      <author>alex_cheng</author>
      <pubDate>Wed, 25 Jun 2014 12:26:59 +0800</pubDate>
      <link>https://ruby-china.org/topics/20155</link>
      <guid>https://ruby-china.org/topics/20155</guid>
    </item>
    <item>
      <title>使用 HTTP 缓存机制提升系统性能</title>
      <description>&lt;p&gt;分享自&lt;a href="http://www.alex-cheng.me/" rel="nofollow" target="_blank"&gt;http://www.alex-cheng.me/&lt;/a&gt; &lt;/p&gt;
&lt;h2 id="摘要"&gt;摘要&lt;/h2&gt;
&lt;p&gt;HTTP 缓存机制定义在 HTTP 协议标准中，被现代浏览器广泛支持，同时也是一个用于提升基于 Web 的系统性能的广泛使用的工具。本文讨论如何使用 HTTP 缓存机制提升基于 Web 的系统，以及如何避免误用。同时也讨论了几种常见的应用场景。&lt;/p&gt;
&lt;h2 id="一般性的缓存机制"&gt;一般性的缓存机制&lt;/h2&gt;
&lt;p&gt;抛开具体的技术实现，一般性来说在服务器 - 客户端模式下，缓存机制通常都是这么工作的，如图所示：
&lt;img src="https://l.ruby-china.com/photo/2014/fabaea82e072e9aea4716a1dfc543020.png" title="" alt=""&gt;
当客户端需要获取某个资源的时候，首先查看客户端本地缓存中是否存在此资源。如果缓存中不存在这个资源，则必须要从服务器端把资源拉到本地。如果存在，则还需要判断缓存的资源是否过期。如果没过期，则从缓存中直接获取资源，不需要再从服务器端获取。如果过期了，则必须从服务器端获取最新资源。还有一种很可能出现的情况是客户端不知道服务器端的资源更新了没有，即不知道缓存中的资源是否过期。这种情况下，客户端还是要向服务器端发请求的，不过在请求中附加了一些信息。这些信息用于服务器判断客户端缓存中的资源是否过期，例如最后修改日期。服务器端如果发现缓存的资源并未过期，那么只需要返回一个信号，告诉客户端从缓存获取即可，并不需要返回资源的内容。&lt;/p&gt;
&lt;h2 id="HTTP协议定义的缓存机制"&gt;HTTP 协议定义的缓存机制&lt;/h2&gt;
&lt;p&gt;当 Web 浏览器向服务器请求资源时（如下图所示），服务器可以在响应中包含缓存相关的 HTTP 头（HTTP Header）。这些 HTTP Header 会告诉浏览器是否以及如何缓存资源。
&lt;img src="https://l.ruby-china.com/photo/2014/2bcc7632a19cabe328f34bc1e398eec1.png" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="关于缓存的HTTP Headers"&gt;关于缓存的 HTTP Headers&lt;/h2&gt;
&lt;p&gt;下面列出的是关于缓存的 HTTP Headers，它们有些是出现在响应（Response）中，有些在请求（Request）中。&lt;/p&gt;
&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th&gt;HTTP Header&lt;/th&gt;
&lt;th&gt;出现在&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cache-Control&lt;/td&gt;
&lt;td&gt;响应&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Expires&lt;/td&gt;
&lt;td&gt;响应&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Last-Modified&lt;/td&gt;
&lt;td&gt;响应&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;If-Modified-Since&lt;/td&gt;
&lt;td&gt;请求&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ETag&lt;/td&gt;
&lt;td&gt;响应&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;If-None-Match&lt;/td&gt;
&lt;td&gt;请求&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;下面会一一介绍这些 Header 的用法，有一些是要配套使用的。&lt;/p&gt;
&lt;h2 id="Cache-Control"&gt;Cache-Control&lt;/h2&gt;
&lt;p&gt;在 Chrome 调试器中，可以在 Response 中找到 Cache-Control，例如下图中的服务响应就包含一个 Cache-Control 头，其值为"public, max-age=30"
&lt;img src="https://l.ruby-china.com/photo/2014/f9b8f48f95d62b2bee1bf15595112205.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;Cache-Control 的值一般为 [public | private | no-cache, no-store ], [max-age=n] （[A | B] 表示可以从 A 和 B 中取一个），"max-age=n"表示 n 秒后资源失效。&lt;/p&gt;

&lt;p&gt;public 是指资源应该被缓存，并且中间经过的代理服务器（假如有的话）也应该缓存这个资源。隐含的意思是，其他用户可能也能分享这个资源的缓存。
private 是指资源应该被缓存，但是只能被客户端的浏览器缓存。
no-cache, no-store 是指示资源不应该被缓存。
max-age 是指缓存多长时间，单位是秒。&lt;/p&gt;

&lt;p&gt;下面举几个常用的例子：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Cache-Control : public, max-age=60&lt;br&gt;
告诉浏览器，当前资源应该被缓存，同时也告诉代理服务器（假如有的话）也可以缓存这个资源。缓存应该在 60 秒后过期。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cache-Control : private, max-age=60&lt;br&gt;
告诉浏览器，当前资源应该被缓存，同时也告诉代理服务器（假如有的话）不要缓存这个资源。缓存应该在 60 秒后过期。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cache-Control : no-cache, no-store
告诉浏览器以及代理服务器，不要缓存这个资源。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="Expires"&gt;Expires&lt;/h2&gt;
&lt;p&gt;是相对于 max-age 的另一种指示过期时间的方式。max-age 表示多少时间后过期，而 Expires 表示在某个日期时间点后过期。换种通俗的说法，max-age 相当于说“保质期六个月”，而 Expires 是说“在此日期之前”饮用。&lt;/p&gt;

&lt;p&gt;注意：如果 max-age 和 Expires 同时存在，应该以 max-age 为准。但是通常可以把两个都设置成一个时间点。例如下面的 JavaScript 代码所示：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Set the max age to 1 year.&lt;/span&gt;
&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cache-Control&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public, max-age=31536000&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Set expire date to 1 year later.&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;currentDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;365&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Expires&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;currentDate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toUTCString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;限制：max-age 和 Expires 设置的缓存过期时间最多为一年（365 天），如果多于这个值则浏览器有可能会忽略。&lt;/p&gt;
&lt;h2 id="Last-Modified 和 If-Modified-Since"&gt;Last-Modified 和 If-Modified-Since&lt;/h2&gt;
&lt;p&gt;Last-Modified 出现在响应中，告诉浏览器当前资源的最后修改时间，例如：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Last-Modified: Mon, 03 Jan 2011 17:45:57 GMT
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;而 If-Modified-Since 出现在下次请求中，询问服务器当前缓存的资源是否已经过期，例如：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;If-Modified-Since: Mon, 03 Jan 2011 17:45:57 GMT
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果资源没有过期，则服务器应该返回 304，不需要返回资源的内容。如果已过期，则服务器应该返回资源的内容到浏览器，并且返回 200 HTTP 状态码。&lt;/p&gt;

&lt;p&gt;Last-Modified 和 If-Modified-Since 配套使用，可以在保证不会误用缓存里的过期资源的前提下，减少服务器向浏览器发送的数据量，以及由于服务器在缓存未过期的情况下只需要返回 304 HTTP 状态码，而不需要返回整个资源的内容，也给了服务器优化的机会。&lt;/p&gt;
&lt;h2 id="ETag 和 If-None-Match"&gt;ETag 和 If-None-Match&lt;/h2&gt;
&lt;p&gt;Last-Modified 和 If-Modified-Since 是用日期时间判断一个缓存的资源是否有效。ETag 和 If-None-Match 则是用内容摘要作为判定的依据。内容摘要是指为一个资源的内容产生一串比较短的数字，当内容变化时，产生的数字串也会改变。内容摘要的算法有很多种，较常见的是 SHA-1 哈希算法、CRC32 等。&lt;/p&gt;

&lt;p&gt;ETag 包含在服务器的响应中，为内容摘要。在下一次请求中，If-None-Match 的值为上次的 ETag 的值。服务器根据 If-None-Match 的值（即内容摘要）判断缓存的资源是否有效。&lt;/p&gt;
&lt;h2 id="304状态码"&gt;304 状态码&lt;/h2&gt;
&lt;p&gt;304 状态码（"Not Modified"）表示资源（相对于缓存过的）没有被修改过。&lt;/p&gt;
&lt;h2 id="检查请求与响应以及缓存工作情况"&gt;检查请求与响应以及缓存工作情况&lt;/h2&gt;
&lt;p&gt;这里仅以 Google Chrome 为例，介绍如何检查请求与响应，以及检查浏览器缓存是否在发挥作用。其他的浏览器平台也是类似的。&lt;/p&gt;

&lt;p&gt;打开 Google Chrome，按 F12，出现调试界面，切换到 Network 页。
&lt;img src="https://l.ruby-china.com/photo/2014/6b1f1a94e654ca2216417540eef4c67a.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;你会看到很多请求，包括 URL、方法、状态等等，注意到上图有一栏显示的是"(from cache)"，已经用红框标出。这表示这些资源都是从缓存里获取的，没有经过服务器。清除 Cache 之后，看到的会是浏览器从服务器端获取资源，如下图所示：
&lt;img src="https://l.ruby-china.com/photo/2014/5d1c1bc6177fd1d09de0e15ad8fd7971.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;点击其中的一项，显示出请求和响应的细节信息。
&lt;img src="https://l.ruby-china.com/photo/2014/ea68cfc78fb451767abeb9ae43bd809c.png" title="" alt=""&gt;
跟 HTTP 缓存相关的 Header 已经被红框标出，这里真正发挥作用 Cache-Control 和 Expires，这两个让浏览器根本不用发请求。浏览器只要发请求就会产生一定的延时，因为即使服务器返回 304 这样简单的数据，在底层也还需要 TCP/IP 层的握手等各种操作，而且 HTTP 协议也会有额外开销。&lt;/p&gt;
&lt;h2 id="应用场景"&gt;应用场景&lt;/h2&gt;
&lt;p&gt;下面罗列了几种场景，并讨论如何设定缓存策略。&lt;/p&gt;
&lt;h2 id="静态资源"&gt;静态资源&lt;/h2&gt;
&lt;p&gt;对于那些不经常改变的静态资源，比如 CSS、图片、动画等，应尽可能地利用缓存。因为这些资源通常很大而且几乎每个页面可能都会用到，缓存会大大提高系统效率。对于这些资源，响应中应该包含如下内容：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Cache-Control:public&lt;span class="p"&gt;;&lt;/span&gt; max-age&lt;span class="o"&gt;=&lt;/span&gt;31536000
Expires: Mon, 25 Jun 2013 21:31:12 GMT
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;max-age=31536000 意味着 31536000 秒（也就是一年）后缓存失效。这里尤为注意不能设置成多于一年，因为 RFC 上限制了最大只能是一年，超过一年的情况不同的浏览器处理策略不同，有些直接就忽略了 Cache-Control。&lt;/p&gt;
&lt;h2 id="动态资源"&gt;动态资源&lt;/h2&gt;
&lt;p&gt;对于动态内容，需要依据内容的实际情况，定义合适的 max-age。例如对于 SNS 网络中的时间线通常可以设置成几秒。&lt;/p&gt;
&lt;h2 id="私有内容"&gt;私有内容&lt;/h2&gt;
&lt;p&gt;对于需要登录才能访问到资源，Cache-Control 应该设置成 private 以禁止代理服务器缓存这些资源，否则会威胁信息安全。&lt;/p&gt;
&lt;h2 id="禁用缓存"&gt;禁用缓存&lt;/h2&gt;
&lt;p&gt;某些情况下需要禁止使用缓存，则应该把 Cache-Control 设置成"no-cache, no-store"，如下所示。&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Cache-Control:no-cache, no-store
&lt;/code&gt;&lt;/pre&gt;</description>
      <author>alex_cheng</author>
      <pubDate>Thu, 19 Jun 2014 17:55:58 +0800</pubDate>
      <link>https://ruby-china.org/topics/20058</link>
      <guid>https://ruby-china.org/topics/20058</guid>
    </item>
    <item>
      <title>如何用一种简洁的方法求出给定数组中相邻元素之差</title>
      <description>&lt;p&gt;给定 包含整数的数组 A = [a_0, a_1, a_2, ..., a_n]，求 [a_1 - a_0, a_2 - a_1, ..., a_n - a_n-1]&lt;/p&gt;

&lt;p&gt;我尝试用 Array 的 rotate 方法，得到 A.rotate = [a_1, a_2, ..., a_n, a_0]，但是我找不到一种方法对 A.rotate 和 A 做向量减运算。&lt;/p&gt;</description>
      <author>alex_cheng</author>
      <pubDate>Thu, 13 Mar 2014 17:08:04 +0800</pubDate>
      <link>https://ruby-china.org/topics/17857</link>
      <guid>https://ruby-china.org/topics/17857</guid>
    </item>
    <item>
      <title>由哥伦布的故事想到的</title>
      <description>&lt;p&gt;前两天跟别人聊天时提到了哥伦布发现新大陆的事情。哥伦布本来要去亚洲的，而且还把地球的周长算错了，结果阴错阳差地到了美洲，才有了“哥伦布发现新大陆”的故事。&lt;/p&gt;

&lt;p&gt;哥伦布的故事其实可以看成是一个创业故事。他有自己的目标和计划，想方设法筹集到了资金、人员和设备等各种资源，经历了千辛万苦，甚至中途船员叛变，但是仍然坚持自己一路向西的信念。可是细细一想，他的目标是到印度，计划也是按照他计算错误的地球周长来做的。假如没有美洲在中间，他一定会死在海洋上。假如他再错一点，方向由向西改成向南，那么他也一定会死在海洋上。也就是说，他虽然错了，但是运气好歪打正着发现了美洲，也算是创业成功了。&lt;/p&gt;

&lt;p&gt;创业一定是有风险的，我们永远不知道目标是否可达，计划是否准确。我们不知道我们的向西去印度的航程中有没有美洲，我们不知道能否坚持一直向西。我们是否能看到新大陆，很大程度上取决于运气。&lt;/p&gt;

&lt;p&gt;我猜想在哥伦布之前应该也有航海家试图从大西洋东岸向西航行。网上查了半天，查不到了。人们只能记住第一个成功者，但是却忽略了之前的失败者。但是谁能保证自己不是前面的失败者呢？&lt;/p&gt;

&lt;p&gt;因此我觉得创业就是一场赌博，我们能做的只是提高胜率，却不能够保证一定能赢。那些赢了的创业者是应该感谢运气，而那些失败的创业者，也一样值得尊重。&lt;/p&gt;</description>
      <author>alex_cheng</author>
      <pubDate>Fri, 21 Feb 2014 23:52:28 +0800</pubDate>
      <link>https://ruby-china.org/topics/17406</link>
      <guid>https://ruby-china.org/topics/17406</guid>
    </item>
    <item>
      <title>用 Ruby on Rails 实现适应各种平台的在线 Office 文档预览</title>
      <description>&lt;h2 id="前言"&gt;  前言&lt;/h2&gt;
&lt;p&gt;在许多 Web 应用中都需要预览文档的功能。而用户可能用不同的设备访问 Web 应用，可能是装有 Windows 系统的 PC 台式机，也有可能是 iOS 系统的 iPad。一般来说，要预览的文档通常是主流的 Office 文档，包括.doc、.ppt、.docx、.pptx 文档，也包括 Adobe 的.pdf 文档。&lt;/p&gt;

&lt;p&gt;因此开发一个能够在不同客户端上使用的，支持主流 Office 文档类型的在线文档预览系统就显得非常重要。&lt;/p&gt;
&lt;h2 id="目标"&gt;目标&lt;/h2&gt;
&lt;p&gt;实现基于 Web 的跨平台的在线预览功能，支持主流 Office 文档。另外我们的服务器是 Linux 系统，不能够安装 Microsoft Office 软件。&lt;/p&gt;
&lt;h2 id="我的解决方案"&gt;我的解决方案&lt;/h2&gt;
&lt;p&gt;先大致地说一下我是如何实现的。为了实现这个功能，我用了下面的组件：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PDF.js&lt;/li&gt;
&lt;li&gt;OpenOffice&lt;/li&gt;
&lt;li&gt;PyODConverter&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;PDF.js&lt;/strong&gt; 是 Mozilla 实验室开发的关于 HTML5 技术的一个开源项目，用于在网页上显示 PDF 文档，而不需要任何浏览器插件等原生代码。目前这个开源项目放在了 GitHub 上，网址是&lt;a href="https://github.com/andreasgal/pdf.js" rel="nofollow" target="_blank" title=""&gt;https://github.com/andreasgal/pdf.js&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;演示地址：&lt;a href="http://mozilla.github.io/pdf.js/web/viewer.html" rel="nofollow" target="_blank" title=""&gt;http://mozilla.github.io/pdf.js/web/viewer.html&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;PyODConverter&lt;/strong&gt;是一个 Python 脚本，用于自动化的文档格式转换，依赖于 LibreOffice or OpenOffice。我用它把 Office 文档转成 PDF 文档。&lt;/p&gt;

&lt;p&gt;PyODConverter 代码托管在 &lt;a href="https://github.com/jamayka/barberry-plugin-openoffice/tree/master/externals/pyodconverter" rel="nofollow" target="_blank" title=""&gt;https://github.com/jamayka/barberry-plugin-openoffice/tree/master/externals/pyodconverter&lt;/a&gt; 上。&lt;/p&gt;
&lt;h2 id="实现过程"&gt;实现过程&lt;/h2&gt;
&lt;p&gt;整个实现分为&lt;strong&gt;“Office 文档转 PDF”&lt;/strong&gt;和&lt;strong&gt;“PDF 文档在 Web 端显示”&lt;/strong&gt;两部分。
，后端负责将各种 Office 文档通过 OpenOffice 服务转换成 PDF 文档，而前端通过 pdf.js 显示 PDF 的内容。由于是采用了 pdf.js 显示 PDF 文档，浏览器端不需要安装任何插件，但是需要支持部分 HTML5 的功能特性。因此旧浏览器例如 IE8，IE7,IE6 就不能够使用 pdf.js 了。&lt;/p&gt;
&lt;h2 id="Office文档转PDF"&gt;Office 文档转 PDF&lt;/h2&gt;
&lt;p&gt;我利用了 OpenOffice 服务将各种 Office 文档转换成了 PDF 文档。写的脚本是&lt;em&gt;rake task&lt;/em&gt;脚本，这样我可以用&lt;em&gt;rake&lt;/em&gt;命令运行此脚本。这个脚本作为一个守护进程运行，它不断地检查系统里是否有新的上传文件。如果有新上传的文件，则启动转换过程，步骤如下：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;启动 OpenOffice 的服务&lt;/li&gt;
&lt;li&gt;检查是否有新的 Office 文档上传&lt;/li&gt;
&lt;li&gt;调用 PyODConverter 转换文档&lt;/li&gt;
&lt;li&gt;更新 Model 上的字段，指向生成的用于预览的 PDF&lt;/li&gt;
&lt;li&gt;重复步骤 2 到 4&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;整个 rake task 脚本的代码如下：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# -*- encoding : utf-8 -*-&lt;/span&gt;
    &lt;span class="no"&gt;PYTHON_PATH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'/opt/openoffice4/program/python'&lt;/span&gt;
    &lt;span class="no"&gt;CONVERTER_PATH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expand_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'../DocumentConverter.py'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;__FILE__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;gen_preview&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;beDaemon&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;begin&lt;/span&gt;
        &lt;span class="n"&gt;no_preview_materials&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;CoursewareMaterial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;preview_file_name: &lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'doc'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'docx'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ppt'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'pptx'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="s2"&gt;"upload_file_name LIKE '%.&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;'"&lt;/span&gt;&lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;' or '&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;no_preview_materials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
          &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Prepare to process &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;no_preview_materials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; items."&lt;/span&gt;
          &lt;span class="n"&gt;no_preview_materials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;material&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
            &lt;span class="k"&gt;begin&lt;/span&gt;
              &lt;span class="n"&gt;generate_preview&lt;/span&gt; &lt;span class="n"&gt;material&lt;/span&gt;
            &lt;span class="k"&gt;rescue&lt;/span&gt;
              &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"[Exception] &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vg"&gt;$!&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;red&lt;/span&gt;
            &lt;span class="k"&gt;end&lt;/span&gt;
          &lt;span class="k"&gt;end&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;

        &lt;span class="k"&gt;break&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;beDaemon&lt;/span&gt;

        &lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;


    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_preview&lt;/span&gt; &lt;span class="n"&gt;material&lt;/span&gt;
      &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Generating preview PDF for [&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;material&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upload_file_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;]..."&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;material&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt; &lt;span class="o"&gt;=~&lt;/span&gt; &lt;span class="sr"&gt;/\.(doc|docx|ppt|pptx|pdf)$/&lt;/span&gt;
        &lt;span class="n"&gt;tmpPdfPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Dir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tmpdir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;material&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'.*'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;'.pdf'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# try to delete existing temporary file if it exists.&lt;/span&gt;
        &lt;span class="k"&gt;begin&lt;/span&gt;
          &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt; &lt;span class="n"&gt;tmpPdfPath&lt;/span&gt;
        &lt;span class="k"&gt;rescue&lt;/span&gt;
          &lt;span class="c1"&gt;# do nothing&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;

        &lt;span class="c1"&gt;# relaunch openoffice if it quit.&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sb"&gt;`pgrep soffice`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
          &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s1"&gt;'Relaunch OpenOffice service.'&lt;/span&gt;
          &lt;span class="n"&gt;spawn&lt;/span&gt; &lt;span class="s1"&gt;'soffice "-accept=socket,host=localhost,port=8100;urp;StarOffice.ServiceManager" -norestore -nofirststartwizard -nologo -headless &amp;amp;'&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;

        &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;
        &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;PYTHON_PATH&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;CONVERTER_PATH&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; '&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;material&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;' '&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;tmpPdfPath&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;'"&lt;/span&gt;
        &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Execute command: &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tmpPdfPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="n"&gt;material&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preview&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tmpPdfPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'rb'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="n"&gt;material&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;
          &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt; &lt;span class="n"&gt;tmpPdfPath&lt;/span&gt;
          &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Preview PDF for [&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;material&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upload_file_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;] is generated."&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;green&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;
          &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"An error occured when invoke DocumentConverter.py. &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;red&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"The material [&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;material&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upload_file_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;] is not available for generating preview PDF."&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;yellow&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="PDF文档在Web端显示"&gt;PDF 文档在 Web 端显示&lt;/h2&gt;
&lt;p&gt;首先在 asset pipeline 中添加 pdf.js，这一步可以通过引入&lt;em&gt;pdfjs-rails&lt;/em&gt;这个 Gem 实现。然后在页面中引用需要的 js 和 css 资源即可，而这些资源包含在&lt;em&gt;pdfjs-rails&lt;/em&gt;中。&lt;/p&gt;

&lt;p&gt;Gem &lt;em&gt;pdfjs-rails&lt;/em&gt;代码托管在&lt;a href="https://github.com/concordia-publishing-house/pdfjs-rails" rel="nofollow" target="_blank" title=""&gt;https://github.com/concordia-publishing-house/pdfjs-rails&lt;/a&gt;中。&lt;/p&gt;

&lt;p&gt;然后在 view 上添加前端代码。我用的是 ERB，因此对应的文件是 preview.html.erb。此文件中的代码如下所示：&lt;/p&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;javascript_include_tag&lt;/span&gt; &lt;span class="s2"&gt;"compatibility"&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;javascript_include_tag&lt;/span&gt; &lt;span class="s2"&gt;"l10n"&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;javascript_include_tag&lt;/span&gt; &lt;span class="s2"&gt;"pdf"&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;javascript_include_tag&lt;/span&gt; &lt;span class="s2"&gt;"pdfviewer"&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;stylesheet_link_tag&lt;/span&gt;&lt;span class="s2"&gt;"viewer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:media&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"all"&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
......
&lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt;  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdf_url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nil?&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;pdf_url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;empty?&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;b&amp;gt;&lt;/span&gt;内容预览：&lt;span class="nt"&gt;&amp;lt;/b&amp;gt;&lt;/span&gt;
        &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt;   &lt;span class="n"&gt;pdf_viewer&lt;/span&gt; &lt;span class="n"&gt;pdf_url&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt;  &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
......
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如以上代码所示，&lt;em&gt;pdfjs_rails&lt;/em&gt;提供了用于自动生成 HTML 的&lt;em&gt;pdf_viewer&lt;/em&gt;方法，简化了代码。当要显示一个文档时，还需要判断这个文档对应的 PDF 文档有没有生成，因此需要一个类似于&lt;code&gt;if pdf_url.nil?&lt;/code&gt;的代码。为了简单起见，我就直接把此代码写在 view 上了。&lt;/p&gt;
&lt;h2 id="安装部署"&gt;安装部署&lt;/h2&gt;&lt;h3 id="安装OpenOffice到Linux CentOS"&gt;安装 OpenOffice 到 Linux CentOS&lt;/h3&gt;
&lt;p&gt;这里有详细说明 &lt;a href="http://www.if-not-true-then-false.com/2010/install-openoffice-org-on-fedora-centos-red-hat-rhel/" rel="nofollow" target="_blank" title=""&gt;http://www.if-not-true-then-false.com/2010/install-openoffice-org-on-fedora-centos-red-hat-rhel/&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="以作为后台服务的方式运行OpenOffice"&gt;以作为后台服务的方式运行 OpenOffice&lt;/h3&gt;
&lt;p&gt;这里有详细说明 &lt;a href="http://stackoverflow.com/questions/11591643/failed-to-connect-to-openoffice-headless-mode" rel="nofollow" target="_blank" title=""&gt;http://stackoverflow.com/questions/11591643/failed-to-connect-to-openoffice-headless-mode&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="下载PyODConverter脚本"&gt;下载 PyODConverter 脚本&lt;/h3&gt;
&lt;p&gt;从 &lt;a href="https://github.com/concordia-publishing-house/pdfjs-rails" rel="nofollow" target="_blank" title=""&gt;https://github.com/concordia-publishing-house/pdfjs-rails&lt;/a&gt; 下载 PyODConverter 脚本，放在&lt;code&gt;lib/tasks/prevew&lt;/code&gt;目录下面，然后我把它直接上传到我的代码库里。&lt;/p&gt;
&lt;h2 id="演示"&gt;演示&lt;/h2&gt;&lt;h2 id="其它几种方案"&gt;其它几种方案&lt;/h2&gt;
&lt;p&gt;我在方案选型的过程中研究了好几种方案，但是除了上面所描述的方案之外的其他所有方案都因为各种原因而不能满足要求。下面一一列举这些未选中的方案，以及解释为何它们满足不了要求。&lt;/p&gt;
&lt;h2 id="FlexPaper "&gt;FlexPaper &lt;/h2&gt;
&lt;p&gt;&lt;a href="http://flexpaper.devaldi.com/" rel="nofollow" target="_blank" title=""&gt;FlexPaper&lt;/a&gt; 是一个商业软件，但是有采用 GPLv3 开源协议的免费版。然而，它仅支持 ASP.NET、PHP 和 Java，而且部署相对复杂。我的系统是基于 Linux 的，开发技术是 Ruby on Rails，因此不太容易和它整合在一起。&lt;/p&gt;
&lt;h2 id="Accusoft Document Viewers"&gt;Accusoft Document Viewers&lt;/h2&gt;
&lt;p&gt;&lt;a href="http://prizmdemos.accusoft.com/" rel="nofollow" target="_blank" title=""&gt;Accusoft Document Viewers&lt;/a&gt; 是一个商业软件，有一些很酷的 Demos，下面列举两个 Demo 的网址。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://prizmdemos.accusoft.com/flash.php" rel="nofollow" target="_blank"&gt;http://prizmdemos.accusoft.com/flash.php&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://prizmdemos.accusoft.com/html5.php" rel="nofollow" target="_blank"&gt;http://prizmdemos.accusoft.com/html5.php&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;可是这个软件的价格太高，而且不是提供一个组件，而是提供一套解决方案，对于我们的目标来说，这些解决方案太“重”了。&lt;/p&gt;
&lt;h2 id="Google的文档预览服务"&gt;Google 的文档预览服务&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://docs.google.com/viewer/" rel="nofollow" target="_blank" title=""&gt;Google 的文档预览服务 (Google Docs Viewer)&lt;/a&gt; 真心很不错，这里有个&lt;a href="http://jsfiddle.net/7ueuU/" rel="nofollow" target="_blank" title=""&gt;应用实例&lt;/a&gt;，但是最大的问题是经常被墙，无法稳定地工作。&lt;/p&gt;
&lt;h2 id="CUPS-PDF"&gt;CUPS-PDF&lt;/h2&gt;
&lt;p&gt;根据一个 StackOverflow 上的解答，有一个叫 CUPS-PDF 的软件可以安装在 Linux 系列（包括 Mac OS X）的操作系统上。该软件能够为系统添加一个 PDF 打印机。它是另一种文档转 PDF 的方法，通过安装 PDF 打印机将文档“打印”成 PDF 文档。用 OpenOffice 的命令行工具可以打印一个文档，并指定要使用的打印机。只要指定使用 CUPS-PDF 的打印机，就能够输出一个 PDF 文件到文件系统中的一个位置上。&lt;/p&gt;

&lt;p&gt;这种方法可以很精确地将文档转换成 PDF 文档，可以说是“所打印即所得”。但是转换后的 PDF 文档都非常的大。比如一个 0.5M 的 Word 文档，用这种方法转换后的 PDF 竟然要 300 多 M。因此这种方法并不可行。 &lt;/p&gt;</description>
      <author>alex_cheng</author>
      <pubDate>Mon, 17 Feb 2014 15:53:51 +0800</pubDate>
      <link>https://ruby-china.org/topics/17309</link>
      <guid>https://ruby-china.org/topics/17309</guid>
    </item>
    <item>
      <title>有没有人做过论坛爬虫？</title>
      <description>&lt;p&gt;最近我想做一个论坛爬虫，但是觉得自己从头开始做有点违背 DRY 原则，因此过来问问各位老师有没有做过类似的东西，分享一些经验和介绍一些类似的开源项目。&lt;/p&gt;

&lt;p&gt;谢谢！&lt;/p&gt;</description>
      <author>alex_cheng</author>
      <pubDate>Mon, 25 Nov 2013 15:07:13 +0800</pubDate>
      <link>https://ruby-china.org/topics/15775</link>
      <guid>https://ruby-china.org/topics/15775</guid>
    </item>
    <item>
      <title>Capybara 貌似无法处理中文</title>
      <description>&lt;p&gt;本人在用 RSpec+Capybara 写自动化测试。有如下 HTML 页面：&lt;/p&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;table&lt;/span&gt; &lt;span class="na"&gt;cellpadding=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt; &lt;span class="na"&gt;cellspacing=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"dataTable dataTable2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;thead&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;th&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;160&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;作业名称&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;th&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;80&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;开始时间&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;th&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;80&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;截止时间&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;th&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;80&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;目标群组&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;th&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;40&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;状态&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
      &lt;span class="c"&gt;&amp;lt;!-- &amp;lt;th width=80&amp;gt;操作&amp;lt;/th&amp;gt; --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/thead&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;tbody&amp;gt;&lt;/span&gt;
.........
  &lt;span class="nt"&gt;&amp;lt;/tbody&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/table&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我运行代码 &lt;code&gt;all("th").first.text&lt;/code&gt;，发现结果是空字符串，但是&lt;code&gt;all("th").first.native.text&lt;/code&gt;却能得到正确的结果“作业名称”。&lt;/p&gt;

&lt;p&gt;这是不是 capybara 无法处理中文的原因呢？&lt;/p&gt;</description>
      <author>alex_cheng</author>
      <pubDate>Sun, 06 Oct 2013 20:53:37 +0800</pubDate>
      <link>https://ruby-china.org/topics/14555</link>
      <guid>https://ruby-china.org/topics/14555</guid>
    </item>
    <item>
      <title>如何在 rails 上整合 discuz!?</title>
      <description>&lt;p&gt;本人用 rails 做了一个 web 应用，需要加个论坛，自然地就采用了 discuz!。但是我希望用户在 rails 上登录后，可以直接进入 discuz!。我的 rails 上的用户信息在 discuz! 中也能够看到，总之希望将两者整合在一起，共享用户注册信息。&lt;/p&gt;</description>
      <author>alex_cheng</author>
      <pubDate>Thu, 25 Apr 2013 13:59:46 +0800</pubDate>
      <link>https://ruby-china.org/topics/10500</link>
      <guid>https://ruby-china.org/topics/10500</guid>
    </item>
  </channel>
</rss>
