<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>tkvern (Vern Brandl)</title>
    <link>https://ruby-china.org/tkvern</link>
    <description/>
    <language>en-us</language>
    <item>
      <title>让你的文字自动适配背景颜色</title>
      <description>&lt;p&gt;作者博客地址： &lt;a href="https://tkvern.com/20210224/%E8%AE%A9%E4%BD%A0%E7%9A%84%E6%96%87%E5%AD%97%E8%87%AA%E5%8A%A8%E9%80%82%E9%85%8D%E8%83%8C%E6%99%AF%E9%A2%9C%E8%89%B2/" rel="nofollow" target="_blank" title=""&gt;让你的文字自动适配背景颜色&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;网传，产品经理要求 App 开发人员，让用户 App 的主题颜色能根据手机壳自动调整。&lt;/p&gt;

&lt;p&gt;刚好笔者要做一个类似的事情，根据背景颜色自动改变文字的颜色，以便于用户识别。&lt;/p&gt;



&lt;p&gt;&lt;img src="https://user-images.githubusercontent.com/10667077/112811656-deb44f00-90ae-11eb-8f09-ad15e04eae03.png" title="" alt="image"&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://user-images.githubusercontent.com/10667077/112811719-eb38a780-90ae-11eb-8283-8529f3da83bf.jpeg" title="" alt="alicl-bwq9y"&gt;&lt;/p&gt;

&lt;hr&gt;
&lt;h2 id="正文"&gt;正文&lt;/h2&gt;
&lt;p&gt;产品设计了一个人机校验组件，大致长这个样子。背景会每次随机取不同图片，开始的时候，箭头设置为蓝色。在背景为蓝色的时候，用户就分辨箭头就有些困难了。怎么解决这个问题呢？&lt;/p&gt;

&lt;p&gt;&lt;img src="https://user-images.githubusercontent.com/10667077/112797691-5463ef00-909e-11eb-9834-7ba744f897c9.png" title="" alt="image3"&gt;&lt;/p&gt;
&lt;h2 id="思路与实现"&gt;思路与实现&lt;/h2&gt;&lt;h3 id="第一步"&gt;第一步&lt;/h3&gt;
&lt;p&gt;取到箭头底部背景的范围坐标。这个比较简单，基本运算就搞定，done&lt;/p&gt;
&lt;h3 id="第二步"&gt;第二步&lt;/h3&gt;
&lt;p&gt;要识别图片，我们需要借助 Canvas，将图片绘制到 Canvas 上，来操作图像数据。&lt;/p&gt;

&lt;p&gt;&lt;em&gt;创建 Canvas 容器&lt;/em&gt;&lt;/p&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;c4&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;canvas&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;c4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;190&lt;/span&gt;
&lt;span class="nx"&gt;c4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;190&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ctx4&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;c4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2d&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;放入图片&lt;/em&gt;&lt;/p&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 识别图片&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;image&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;Image&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;ctx4&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;drawImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;191&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;190&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// 绘制图片到 Canvas&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;analysisColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx4&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;getImageData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;191&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;190&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;// 分析颜色分布&lt;/span&gt;
    &lt;span class="nf"&gt;setFontColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// 设置字体颜色&lt;/span&gt;
    &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// 完成Promise&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;images&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`code-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;// 取本次随机图片的地址设置到 image&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="跨域问题"&gt;跨域问题&lt;/h3&gt;
&lt;p&gt;可是进展并没有那么顺利，背景图片不在同域下面，Canvas 不允许跨域的图片，怎么办呢？&lt;/p&gt;

&lt;p&gt;&lt;img src="https://user-images.githubusercontent.com/10667077/112800951-e968e700-90a2-11eb-8087-29e1783e7666.png" title="" alt="image"&gt;&lt;/p&gt;
&lt;h3 id="第三步"&gt;第三步&lt;/h3&gt;
&lt;p&gt;既然 Canvas 不允许跨域的图片，在无后端代理支持的情况下，怎么高效的解决这个问题呢？(本地是跨域，线上同域)&lt;/p&gt;

&lt;p&gt;把图片下载的本地！借助 XMLHttpRequest 将图片先缓存到本地转成 Blob 对象，Canvas 是可以访问本地 Blob 的数据。&lt;/p&gt;

&lt;p&gt;&lt;em&gt;下载图片，解决图片跨域问题&lt;/em&gt;&lt;/p&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 下载图片，解决图片跨域问题&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;xhr&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;XMLHttpRequest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nx"&gt;xhr&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;get&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;images&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`code-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;xhr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;responseType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blob&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="nx"&gt;xhr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;loaded&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;
        &lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createObjectURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;xhr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="第四步"&gt;第四步&lt;/h3&gt;
&lt;p&gt;解决了跨域问题，接下来就是分析颜色了，&lt;em&gt;getImageData&lt;/em&gt; 取到的就是图片 rgba 的数组。
这个时候就可以将计算好的坐标，代入到图片 rgba 里面计算其分布。&lt;/p&gt;

&lt;p&gt;说到这里就要补一下图像算法的知识了。&lt;/p&gt;

&lt;p&gt;许多从自然场景中拍摄的图像，其色彩分布上会给人一种和谐、一致的感觉；反过来，在许多界面设计应用中，我们也希望选择的颜色可以达到这样的效果，但对一般人来说却并不那么容易，这属于色彩心理学的范畴。从彩色图像中提取其中的主题颜色，不仅可以用于色彩设计，也可用于图像分类、搜索、识别等，本文分别总结并实现图像主题颜色提取的几种算法，包括颜色量化法（ColorQuantization）、聚类 (Clustering) 和颜色建模的方法&lt;/p&gt;
&lt;h4 id="颜色量化算法"&gt;颜色量化算法&lt;/h4&gt;
&lt;p&gt;彩色图像一般采用 RGB 色彩模式，每个像素由 RGB 三个颜色分量组成。随着硬件的不断升级，彩色图像的存储由最初的 8 位、16 位变成现在的 24 位、32 真彩色。所谓全彩是指每个像素由 8 位（$2^8$=0~255）表示，红绿蓝三原色组合共有 1677 万（256 x 256 x 256）万种颜色，如果将 RGB 看作是三维空间中的三个坐标，可以得到下面这样一张色彩空间图：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://user-images.githubusercontent.com/10667077/112802124-62b50980-90a4-11eb-94f6-32305e435e32.png" title="" alt="image"&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.pyimagesearch.com/2014/12/01/complete-guide-building-image-search-engine-python-opencv/" rel="nofollow" target="_blank" title=""&gt;RGB color cube&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;当然，一张图像不可能包含所有颜色，我们将一张彩色图像所包含的像素投射到色彩空间中，可以更直观地感受图像中颜色的分布：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://user-images.githubusercontent.com/10667077/112802295-9728c580-90a4-11eb-9837-1ae563db6e5b.png" title="" alt="image"&gt;&lt;/p&gt;

&lt;p&gt;因此颜色量化问题可以用所有矢量量化（vector quantization, VQ）算法解决。这里采用开源图像处理库 Leptonica 中用到的两种算法：中位切分法、八叉树算法。&lt;/p&gt;

&lt;p&gt;这里核心使用中位切分法（Median cut）参考项目 &lt;a href="https://github.com/lokesh/color-thief" rel="nofollow" target="_blank" title=""&gt;Github: color-thief&lt;/a&gt;&lt;/p&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 计算图片中间值&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;analysisColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rgbaArray&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Todo something，返回该区域颜色的主色&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="第五步"&gt;第五步&lt;/h3&gt;
&lt;p&gt;到这里，这个需求就算实现了基本核心的部分了，但是在运行过程中，发现性能消耗极大。大部分花在了 Canvas 绘制和图像遍历上&lt;/p&gt;

&lt;p&gt;&lt;img src="https://user-images.githubusercontent.com/10667077/112803557-02bf6280-90a6-11eb-92bd-5ec56f5fd983.png" title="" alt="image"&gt;&lt;/p&gt;

&lt;p&gt;怎么来优化这个过程呢？能不能只提取图像的特征信息进行分析呢？&lt;/p&gt;

&lt;p&gt;带着这两个问题，查阅了图像特征算法相关的文献后，找到了 方向梯度直方图（Histogram of Oriented Gradient, HOG）这个算法。&lt;/p&gt;
&lt;h3 id="基HOG特征"&gt;基 HOG 特征&lt;/h3&gt;
&lt;p&gt;方向梯度直方图（Histogram of Oriented Gradient, HOG）特征是一种在计算机视觉和图像处理中用来进行物体检测的特征描述子。它通过计算和统计图像局部区域的梯度方向直方图来构成特征。Hog 特征结合 SVM 分类器已经被广泛应用于图像识别中，尤其在行人检测中获得了极大的成功。需要提醒的是，HOG+SVM 进行行人检测的方法是法国研究人员 Dalal 在 2005 的 CVPR 上提出的，而如今虽然有很多行人检测算法不断提出，但基本都是以 HOG+SVM 的思路为主。&lt;/p&gt;
&lt;h4 id="主要思想"&gt;主要思想&lt;/h4&gt;
&lt;p&gt;在一副图像中，局部目标的表象和形状（appearance and shape）能够被梯度或边缘的方向密度分布很好地描述。（本质：梯度的统计信息，而梯度主要存在于边缘的地方）。&lt;/p&gt;
&lt;h4 id="具体的实现方法是"&gt;具体的实现方法是&lt;/h4&gt;
&lt;p&gt;首先将图像分成小的连通区域，我们把它叫细胞单元。然后采集细胞单元中各像素点的梯度的或边缘的方向直方图。最后把这些直方图组合起来就可以构成特征描述器。&lt;/p&gt;
&lt;h4 id="提高性能"&gt;提高性能&lt;/h4&gt;
&lt;p&gt;把这些局部直方图在图像的更大的范围内（我们把它叫区间或 block）进行对比度归一化（contrast-normalized），所采用的方 法是：先计算各直方图在这个区间（block）中的密度，然后根据这个密度对区间中的各个细胞单元做归一化。通过这个归一化后，能对光照变化和阴影获得更 好的效果。&lt;/p&gt;
&lt;h4 id="优点"&gt;优点&lt;/h4&gt;
&lt;p&gt;与其他的特征描述方法相比，HOG 有很多优点。首先，由于 HOG 是在图像的局部方格单元上操作，所以它对图像几何的和光学的形变都能保持很好的不 变性，这两种形变只会出现在更大的空间领域上。其次，在粗的空域抽样、精细的方向抽样以及较强的局部光学归一化等条件下，只要行人大体上能够保持直立的姿 势，可以容许行人有一些细微的肢体动作，这些细微的动作可以被忽略而不影响检测效果。因此 HOG 特征是特别适合于做图像中的人体检测的。&lt;/p&gt;

&lt;p&gt;&lt;em&gt;HOG 特征提取算法的实现过程&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://user-images.githubusercontent.com/10667077/112806597-89297380-90a9-11eb-9d06-bb303795770c.png" title="" alt="image"&gt;&lt;/p&gt;
&lt;h3 id="第六步"&gt;第六步&lt;/h3&gt;
&lt;p&gt;基于此，来做我们自己的算法实现。将原图在绘制时，按照等比平铺，一步步的绘制到 Canvas 格子上去。随着尺寸的缩小，图像的特征依然得以保留，大致效果如下。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://user-images.githubusercontent.com/10667077/112809701-da873200-90ac-11eb-8168-abd491fad785.png" title="" alt="image"&gt;&lt;/p&gt;

&lt;p&gt;在实验多个不同的压缩尺寸后，发现 16x16 这个尺寸能兼顾特征与识别性能，再小一些的格子比如 8x8 就会丢失特征值。&lt;/p&gt;

&lt;p&gt;&lt;em&gt;贴一下大致的实现过程&lt;/em&gt;&lt;/p&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;checkBack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// 计算图片中间值&lt;/span&gt;
      &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;analysisColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;rgbaArray&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Todo something，返回该区域颜色的主色&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;c4&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;canvas&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// 压缩尺寸计算用&lt;/span&gt;
      &lt;span class="nx"&gt;c4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;
      &lt;span class="nx"&gt;c4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ctx4&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;c4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2d&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="c1"&gt;// 识别图片&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;image&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;Image&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;ctx4&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;drawImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// 绘制图片到 Canvas&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;analysisColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx4&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;getImageData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;// 分析颜色分布&lt;/span&gt;
        &lt;span class="nf"&gt;setFontColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// 设置字体颜色&lt;/span&gt;
        &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// 完成Promise&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="c1"&gt;// 下载图片，解决图片跨域问题&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;xhr&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;XMLHttpRequest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="nx"&gt;xhr&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;get&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;images&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`code-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nx"&gt;xhr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;responseType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blob&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="nx"&gt;xhr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;loaded&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;
          &lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createObjectURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;xhr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="最后"&gt;最后&lt;/h3&gt;
&lt;p&gt;我们再来看看优化后，分析过程的耗时，差不多提升了 100 倍的速度！！！&lt;/p&gt;

&lt;p&gt;&lt;img src="https://user-images.githubusercontent.com/10667077/112803779-3d28ff80-90a6-11eb-82e1-c95be76b0dfc.png" title="" alt="image"&gt;&lt;/p&gt;

&lt;p&gt;最终的效果图：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://user-images.githubusercontent.com/10667077/112810638-cee83b00-90ad-11eb-8062-ac3a6b5ebda5.png" title="" alt="image"&gt;&lt;/p&gt;</description>
      <author>tkvern</author>
      <pubDate>Mon, 29 Mar 2021 17:36:28 +0800</pubDate>
      <link>https://ruby-china.org/topics/41093</link>
      <guid>https://ruby-china.org/topics/41093</guid>
    </item>
    <item>
      <title>Golang 生态是否有能与 Rails 媲美的框架？</title>
      <description>&lt;p&gt;求问，Golang 生态是否有能与 Rails 媲美的框架？&lt;/p&gt;</description>
      <author>tkvern</author>
      <pubDate>Fri, 12 Mar 2021 15:58:01 +0800</pubDate>
      <link>https://ruby-china.org/topics/41021</link>
      <guid>https://ruby-china.org/topics/41021</guid>
    </item>
    <item>
      <title>[深圳] 加推科技火热🔥招聘中 前端/小程序/Java/App (15-25K)</title>
      <description>&lt;p&gt;大家好，我是 Vern，&lt;a href="https://www.jiatui.com/about" rel="nofollow" target="_blank" title=""&gt;深圳市加推科技&lt;/a&gt;智慧城市事业部的 leader，随着公司业务的发展，我们需要更多优秀的工程师加入团队。&lt;/p&gt;
&lt;h2 id="关于我们"&gt;关于我们&lt;/h2&gt;
&lt;p&gt;加推是数字化营销的技术提供商，从成立的第一天起致力为全天下的销售做一把武器。&lt;/p&gt;

&lt;p&gt;加推研发整套系统经历 3 年时间，2017 年加推独家发布该系统的第一个组件“智能名片”，次年获得中国第七届互联网创新创业大赛冠军。截至目前，加推获得来自红杉资本、IDG 资本、仁智资本的 3 亿元投资，超过 30000 家企业购买并使用，直至今日发布了 15 个独立创新的智能销售组件并形成闭环，为全球企业提供了一套赋能销售增长的数字化营销解决方案。&lt;/p&gt;
&lt;h2 id="工作环境"&gt;工作环境&lt;/h2&gt;
&lt;p&gt;深圳总部地址：&lt;a href="https://map.baidu.com/poi/%E6%B7%B1%E5%9C%B3%E5%B8%82%E5%8A%A0%E6%8E%A8%E7%A7%91%E6%8A%80%E6%9C%89%E9%99%90%E5%85%AC%E5%8F%B8/@12697953.075,2564673.59,19z?uid=b3ac85a7dc361c64b13c81e0&amp;amp;ugc_type=3&amp;amp;ugc_ver=1&amp;amp;device_ratio=2&amp;amp;compat=1&amp;amp;querytype=detailConInfo&amp;amp;da_src=shareurl" rel="nofollow" target="_blank" title=""&gt;广东省深圳市福田区梅林街道中康路 136 号新一代产业园 1 栋 30 楼&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/tkvern/d4405d76-7258-4ace-9460-555d3c5144e6.jpg!large" title="" alt="办公区"&gt;
&lt;img src="https://l.ruby-china.com/photo/tkvern/53938067-e7fa-466e-8c6f-908d09ee0299.jpg!large" title="" alt="办公区1"&gt;
&lt;img src="https://l.ruby-china.com/photo/tkvern/9716f197-9928-482c-afa0-c10e02ffdb52.jpg!large" title="" alt="休闲区"&gt;&lt;/p&gt;

&lt;hr&gt;
&lt;h2 id="前端工程师(小程序工程师)"&gt;前端工程师 (小程序工程师)&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;岗位职责&lt;/strong&gt;：&lt;/p&gt;

&lt;p&gt;1、负责开发公司各平台系统的前端和交互功能；&lt;/p&gt;

&lt;p&gt;2、负责各项⽬的代码维护、迭代更新，保证任务质量和交付及时性；&lt;/p&gt;

&lt;p&gt;3、负责编写相关技术文档，对产品质量负责。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;任职要求&lt;/strong&gt;：&lt;/p&gt;

&lt;p&gt;1、本科以上学历，3 年及以上前端开发经验，熟练掌握小程序开发 / React 框架，能独⽴开发常用组件；&lt;/p&gt;

&lt;p&gt;2、熟悉 Git/GitHub，通讯协议 TCP/HTTP；&lt;/p&gt;

&lt;p&gt;3、熟悉 ES5/ES6/TypeScript 语法，熟悉模块化 CSS 以及 Sass、Less 语法；&lt;/p&gt;

&lt;p&gt;4、具备英语读写能⼒。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;加分项&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;1、有 Blog 的习惯，活跃技术社区，参与开源项⽬等；&lt;/p&gt;

&lt;p&gt;2、有代码洁癖，对代码精益求精，对技术有极客热情；&lt;/p&gt;

&lt;p&gt;3、有软件著作权申请，有专利发明申请。&lt;/p&gt;

&lt;p&gt;4、有 Node.js 开发经验&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;待遇&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;15 ~ 25K 月薪 + 年终奖 (根据业务收益情况而定)&lt;/p&gt;

&lt;p&gt;简历请投递至 vernzhang@aijiatui.com，注明来处，当天回复&lt;/p&gt;

&lt;hr&gt;
&lt;h2 id="iOS工程师"&gt;iOS 工程师&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;岗位职责&lt;/strong&gt;：&lt;/p&gt;

&lt;p&gt;1、负责开发公司各平台系统的 App 端和交互功能；&lt;/p&gt;

&lt;p&gt;2、负责各项⽬的代码维护、迭代更新，保证任务质量和交付及时性；&lt;/p&gt;

&lt;p&gt;3、负责编写相关技术文档，对产品质量负责。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;任职要求&lt;/strong&gt;：&lt;/p&gt;

&lt;p&gt;1、本科以上学历，3 年及以上 App 开发经验，熟练掌握 iOS 开发，能独⽴开发常用组件；&lt;/p&gt;

&lt;p&gt;2、熟悉 Git/GitHub，通讯协议 TCP/HTTP；&lt;/p&gt;

&lt;p&gt;3、掌握 Flutter/Weex/ReactNative 等混合开发模式；&lt;/p&gt;

&lt;p&gt;4、具备英语读写能⼒。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;加分项&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;1、有 Blog 的习惯，活跃技术社区，参与开源项⽬等；&lt;/p&gt;

&lt;p&gt;2、有代码洁癖，对代码精益求精，对技术有极客热情；&lt;/p&gt;

&lt;p&gt;3、有软件著作权申请，有专利发明申请。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;待遇&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;15 ~ 25K 月薪 + 年终奖 (根据业务收益情况而定)&lt;/p&gt;

&lt;p&gt;简历请投递至 vernzhang@aijiatui.com，注明来处，当天回复&lt;/p&gt;

&lt;hr&gt;
&lt;h2 id="Android工程师"&gt;Android 工程师&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;岗位职责&lt;/strong&gt;：&lt;/p&gt;

&lt;p&gt;1、负责开发公司各平台系统的 App 端和交互功能；&lt;/p&gt;

&lt;p&gt;2、负责各项⽬的代码维护、迭代更新，保证任务质量和交付及时性；&lt;/p&gt;

&lt;p&gt;3、负责编写相关技术文档，对产品质量负责。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;任职要求&lt;/strong&gt;：&lt;/p&gt;

&lt;p&gt;1、本科以上学历，3 年及以上 App 开发经验，熟练掌握 Android 开发，能独⽴开发常用组件；&lt;/p&gt;

&lt;p&gt;2、熟悉 Git/GitHub，通讯协议 TCP/HTTP；&lt;/p&gt;

&lt;p&gt;3、掌握 Flutter/Weex/ReactNative 等混合开发模式；&lt;/p&gt;

&lt;p&gt;4、具备英语读写能⼒。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;加分项&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;1、有 Blog 的习惯，活跃技术社区，参与开源项⽬等；&lt;/p&gt;

&lt;p&gt;2、有代码洁癖，对代码精益求精，对技术有极客热情；&lt;/p&gt;

&lt;p&gt;3、有软件著作权申请，有专利发明申请。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;待遇&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;15 ~ 25K 月薪 + 年终奖 (根据业务收益情况而定)&lt;/p&gt;

&lt;p&gt;简历请投递至 vernzhang@aijiatui.com，注明来处，当天回复&lt;/p&gt;

&lt;hr&gt;
&lt;h2 id="Java工程师"&gt;Java 工程师&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;职责描述&lt;/strong&gt;：&lt;/p&gt;

&lt;p&gt;1、负责建设高可靠、高并发的分布式 API 系统及服务端软件&lt;/p&gt;

&lt;p&gt;2、负责各项⽬的代码维护、迭代更新，保证任务质量和交付及时性&lt;/p&gt;

&lt;p&gt;3、负责编写相关的技术文档、单元测试，对产品质量负责。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;任职要求&lt;/strong&gt;：&lt;/p&gt;

&lt;p&gt;1、本科及以上学历，具备英语读写能⼒；&lt;/p&gt;

&lt;p&gt;2、熟悉 Git/GitHub，通信协议 TCP/HTTP 及 RESTful 标准；&lt;/p&gt;

&lt;p&gt;3、5 年以上 Java 开发经验，熟练掌握 Spring Cloud 技术栈及其各组件，有大中型互联网系统设计或开发经验者优先；&lt;/p&gt;

&lt;p&gt;4、熟悉常见的消息队列、缓存服务和存储服务，有阅读或贡献源码者优先；&lt;/p&gt;

&lt;p&gt;5、具备英语读写能⼒。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;加分项&lt;/strong&gt;：&lt;/p&gt;

&lt;p&gt;1、有 Blog 的习惯，活跃技术社区，参与开源项⽬等；&lt;/p&gt;

&lt;p&gt;2、有代码洁癖，对代码精益求精，对技术有极客热情；&lt;/p&gt;

&lt;p&gt;3、有软件著作权申请，有专利发明申请。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;待遇&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;15 ~ 25K 月薪 + 年终奖 (根据业务收益情况而定)&lt;/p&gt;

&lt;p&gt;简历请投递至 vernzhang@aijiatui.com，注明来处，当天回复&lt;/p&gt;

&lt;hr&gt;
&lt;h3 id="企业福利"&gt;企业福利&lt;/h3&gt;
&lt;p&gt;1、夜宵补助，打车报销；&lt;/p&gt;

&lt;p&gt;2、长期向员工提供免费零食饮料；&lt;/p&gt;

&lt;p&gt;3、每月组织集体活动，包括聚餐、桌游、唱 K、运动；&lt;/p&gt;

&lt;p&gt;4、生动有趣的生日会、年会趴全员；&lt;/p&gt;

&lt;p&gt;5、优秀员工享受期权。&lt;/p&gt;</description>
      <author>tkvern</author>
      <pubDate>Tue, 09 Mar 2021 14:33:24 +0800</pubDate>
      <link>https://ruby-china.org/topics/41009</link>
      <guid>https://ruby-china.org/topics/41009</guid>
    </item>
    <item>
      <title>Midway.js 最佳实践案例工程，Node.js 社区几与 Rails 媲美的 Web 框架</title>
      <description>&lt;h2 id="FSD service"&gt;FSD service&lt;/h2&gt;
&lt;p&gt;如果该项目对你有用，欢迎 star 👏
&lt;a href="https://github.com/fsd-nodejs/service-mw2" rel="nofollow" target="_blank" title=""&gt;Midway 2.x 样板工程&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://codecov.io/gh/fsd-nodejs/service-mw2" rel="nofollow" target="_blank" title=""&gt;&lt;img src="https://codecov.io/gh/fsd-nodejs/service-mw2/branch/master/graph/badge.svg" title="" alt="codecov"&gt;&lt;/a&gt;
&lt;a href="https://github.com/fsd-nodejs/service-mw2" rel="nofollow" target="_blank" title=""&gt;&lt;img src="https://github.com/fsd-nodejs/service-mw2/workflows/Node.js%20CI/badge.svg" title="" alt="GitHub Actions status"&gt;&lt;/a&gt;
&lt;a href="https://codebeat.co/projects/github-com-fsd-nodejs-service-mw2-master" rel="nofollow" target="_blank" title=""&gt;&lt;img src="https://codebeat.co/badges/ed780b5a-d9e8-41a8-8bc9-8bcb3263c6ce" title="" alt="codebeat badge"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;拓展阅读：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;配套的前端工程请移步 &lt;a href="https://github.com/fsd-nodejs/pc" rel="nofollow" target="_blank"&gt;https://github.com/fsd-nodejs/pc&lt;/a&gt; 查看这个项目&lt;/li&gt;
&lt;li&gt;全栈开发文档以及规范 &lt;a href="https://github.com/fsd-nodejs/document" rel="nofollow" target="_blank"&gt;https://github.com/fsd-nodejs/document&lt;/a&gt; 查看这个项目&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/fsd-nodejs/service-mw2/wiki/midway2.x-%E6%B7%B1%E5%BA%A6%E8%BA%BA%E5%9D%91%E8%AE%B0(%E6%8C%81%E7%BB%AD%E6%9B%B4%E6%96%B0)" rel="nofollow" target="_blank" title=""&gt;midway2.x 深度躺坑记 (持续更新)
&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="项目导览 &amp;amp; TODO"&gt;项目导览 &amp;amp; TODO&lt;/h2&gt;
&lt;p&gt;在这个项目中，你会看到以下基于 midway 的实践案例 (上层使用 eggjs )&lt;/p&gt;

&lt;p&gt;我们正在做以下工程例子，如果你有新的想法，可在 issue 留言，我们会征集你的意见，带来最干货的案例。&lt;/p&gt;

&lt;p&gt;帮你扫清学习障碍，让你用起 midway 来更加得心应手，提升能效，找回编码的乐趣。&lt;/p&gt;
&lt;h3 id="框架特性及能力应用"&gt;框架特性及能力应用&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;[x] 中间件&lt;/li&gt;
&lt;li&gt;[x] 依赖注入&lt;/li&gt;
&lt;li&gt;[x] 参数校验和转换（DTO 层）&lt;/li&gt;
&lt;li&gt;[x] 测试（Controller &amp;amp; Service 单元测试）&lt;/li&gt;
&lt;li&gt;[x] swagger&lt;/li&gt;
&lt;li&gt;[x] Database&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="功能"&gt;功能&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;基础&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;[x] Admin 登录&lt;/li&gt;
&lt;li&gt;[ ] 普通用户登录 - 账户密码&lt;/li&gt;
&lt;li&gt;[ ] OAuth 2.0&lt;/li&gt;
&lt;li&gt;[ ] 日志监控&lt;/li&gt;
&lt;li&gt;[ ] 本地上传文件服务&lt;/li&gt;
&lt;li&gt;[x] 鉴权中间件&lt;/li&gt;
&lt;li&gt;[ ] 接口响应统计中间件&lt;/li&gt;
&lt;li&gt;[x] 统一错误处理&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;超级管理&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;[x] 权限&lt;/li&gt;
&lt;li&gt;[x] 角色&lt;/li&gt;
&lt;li&gt;[x] 管理员&lt;/li&gt;
&lt;li&gt;[x] 菜单&lt;/li&gt;
&lt;li&gt;[ ] 日志 (操作日志，记录管理用户的实际操作)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="快速开始"&gt;快速开始&lt;/h2&gt;


&lt;p&gt;see &lt;a href="https://midwayjs.org" rel="nofollow" target="_blank" title=""&gt;midway docs&lt;/a&gt; for more detail.&lt;/p&gt;
&lt;h3 id="Development"&gt;Development&lt;/h3&gt;
&lt;p&gt;先将 database 目录下到 sql 文件迁移到数据库，修改默认的 config 配置文件&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npm i
&lt;span class="nv"&gt;$ &lt;/span&gt;npm run dev
&lt;span class="nv"&gt;$ &lt;/span&gt;open http://localhost:7001/
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="Deploy"&gt;Deploy&lt;/h3&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npm start
&lt;span class="nv"&gt;$ &lt;/span&gt;npm stop
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="npm scripts"&gt;npm scripts&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;npm run lint&lt;/code&gt; to check code style.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;npm test&lt;/code&gt; to run unit test.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="Redis"&gt;Redis&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;使用 Redis 作为用户登录凭证存取的地方&lt;/li&gt;
&lt;li&gt;RTS 收集统计数据 (开发中)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="Redis划分"&gt;Redis 划分&lt;/h3&gt;
&lt;p&gt;建议使用 Redis 的时候，对所有 key 做好命名空间划分，便于管理。可把 scope 写到对照表中。&lt;/p&gt;

&lt;p&gt;借助 jwt 插件做签名校验，管理员的 token 中会包含 id 字段。&lt;/p&gt;

&lt;p&gt;所有 admin 相关的缓存数据都放在 &lt;code&gt;admin:xxxx&lt;/code&gt; 下面。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;admin:accessToken:${id}&lt;/code&gt; 缓存管理员 Token 信息&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;admin:userinfo:${id}&lt;/code&gt; 缓存管理员基本信息&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="数据库"&gt;数据库&lt;/h2&gt;
&lt;p&gt;所有实体表均有 deleted_at 字段 (目前基础模块不使用软删除)，用于软删除。&lt;/p&gt;

&lt;p&gt;如果要关闭软删除，将 deletedAt 字段注释即可&lt;/p&gt;

&lt;p&gt;进行软删除的时候，关系表的数据不做改动。&lt;/p&gt;

&lt;p&gt;后期根据需要，用脚本定期清理软删除的数据。&lt;/p&gt;

&lt;p&gt;以下模块未使用软删除：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;权限管理&lt;/li&gt;
&lt;li&gt;角色管理&lt;/li&gt;
&lt;li&gt;菜单管理&lt;/li&gt;
&lt;li&gt;管理员管理&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="查询注意事项"&gt;查询注意事项&lt;/h3&gt;
&lt;p&gt;业务软删除单独写一个 BaseModel，其他实体继承该 Model 即可&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;实体查询，继承 &lt;code&gt;BaseModel&lt;/code&gt; 的实体会自带软删除判断，例子查看&lt;code&gt;src/app/model/base.ts&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;在做关系查询的时候，关系表需要手动加软删除判断 IS NULL，如下:
&lt;code&gt;typescript
/**
 * 根据菜单id获取数据
 * @param id 菜单id
 */
async getAdminMenuById(id: string) {
  const row = await this.adminMenuModel
    .createQueryBuilder()
    .select()
    .leftJoinAndSelect(
      'AdminMenuModel.roles',
      'role',
      'role.deletedAt IS NULL'
    )
    .where({ id: id })
    .getOne();
  return row;
}
&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="接口响应统计中间件(设计)"&gt;接口响应统计中间件 (设计)&lt;/h2&gt;
&lt;p&gt;做接口响应数据统计的出发点，有两点（即使有类似的第三方包，但还是自己实现以下）:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;帮助排查线上接口响应问题&lt;/li&gt;
&lt;li&gt;监控系统实时状态&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;虽然框架本身已经有日志功能，但是很多场景下，我们可能需要看下各个接口服务的响应状态&lt;/p&gt;

&lt;p&gt;是在正常服务，还是已经出现问题。在有监控的帮助下，可以快速帮我们定位日志排查问题。&lt;/p&gt;

&lt;p&gt;是对应统计实时数据而言，这里我们会使用 RTS 的技术方案，会用到 RabbitMQ 和 Redis &lt;/p&gt;

&lt;p&gt;RabbitMQ 作用在于把统计的计算异步化，从而不影响正常的业务请求处理&lt;/p&gt;

&lt;p&gt;（消费者的逻辑代码，需要写在单独一个工程，独立部署）&lt;/p&gt;

&lt;p&gt;大致流程如下，手绘的，工具简陋，姑且看一下。
&lt;img src="https://user-images.githubusercontent.com/10667077/101478900-55a4cb00-398c-11eb-97c3-4a41195c572d.JPG" title="" alt="IMG_5365 HEIC"&gt;&lt;/p&gt;
&lt;h2 id="迁移API"&gt;迁移 API&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;home.ts&lt;/li&gt;
&lt;li&gt;- [x] /&lt;/li&gt;
&lt;li&gt;&lt;ul&gt;
&lt;li&gt;[x] /ping&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;auth.ts&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;ul&gt;
&lt;li&gt;[x] /auth/login&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;ul&gt;
&lt;li&gt;[x] /auth/logout&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;ul&gt;
&lt;li&gt;[x] /auth/currentUser&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;admin/menu.ts&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;ul&gt;
&lt;li&gt;[x] /admin/menu/query&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;ul&gt;
&lt;li&gt;[x] /admin/menu/show&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;ul&gt;
&lt;li&gt;[x] /admin/menu/create&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;ul&gt;
&lt;li&gt;[x] /admin/menu/update&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;ul&gt;
&lt;li&gt;[x] /admin/menu/remove&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;ul&gt;
&lt;li&gt;[x] /admin/menu/order&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;admin/permission.ts&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;ul&gt;
&lt;li&gt;[x] /admin/permission/query&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;ul&gt;
&lt;li&gt;[x] /admin/permission/show&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;ul&gt;
&lt;li&gt;[x] /admin/permission/create&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;ul&gt;
&lt;li&gt;[x] /admin/permission/update&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;ul&gt;
&lt;li&gt;[x] /admin/permission/remove&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;admin/role.ts&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;ul&gt;
&lt;li&gt;[x] /admin/role/query&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;ul&gt;
&lt;li&gt;[x] /admin/role/show&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;ul&gt;
&lt;li&gt;[x] /admin/role/create&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;ul&gt;
&lt;li&gt;[x] /admin/role/update&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;ul&gt;
&lt;li&gt;[x] /admin/role/remove&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;admin/user.ts&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;ul&gt;
&lt;li&gt;[x] /admin/user/query&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;ul&gt;
&lt;li&gt;[x] /admin/user/show&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;ul&gt;
&lt;li&gt;[x] /admin/user/create&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;ul&gt;
&lt;li&gt;[x] /admin/user/update&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;ul&gt;
&lt;li&gt;[x] /admin/user/remove&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>tkvern</author>
      <pubDate>Tue, 22 Dec 2020 17:14:17 +0800</pubDate>
      <link>https://ruby-china.org/topics/40729</link>
      <guid>https://ruby-china.org/topics/40729</guid>
    </item>
    <item>
      <title>【水贴】脱离 Ruby 这些年</title>
      <description>&lt;p&gt;脱离 Ruby 这几年，在 PHP、Nodejs、Go 社区混迹。撸了各种项目。&lt;/p&gt;

&lt;p&gt;回过头来发现&lt;/p&gt;

&lt;p&gt;&lt;em&gt;ROR&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;写起来还是蛮爽！&lt;/p&gt;</description>
      <author>tkvern</author>
      <pubDate>Fri, 12 Jun 2020 09:53:55 +0800</pubDate>
      <link>https://ruby-china.org/topics/39953</link>
      <guid>https://ruby-china.org/topics/39953</guid>
    </item>
    <item>
      <title>传播正能量，健康生活。996.ICU</title>
      <description>&lt;h2 id=" 996.ICU "&gt; &lt;a href="https://996.icu" rel="nofollow" target="_blank" title=""&gt; 996.ICU &lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;“996”工作制，即每天早 9 点到岗，一直工作到晚上 9 点，每周工作 6 天。&lt;/p&gt;

&lt;p&gt;“996”工作制的周工作时间为最低 12x6=72 小时。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;中国大陆工时规管现况（标准工时）：&lt;/strong&gt;
一天工作时间为 8 小时，平均每周工时不超过 40 小时；加班上限为一天 3 小时及一个月 36 小时，逾时工作薪金不低于平日工资的 150%。而一周最高工时则为 48 小时。平均每月计薪天数为 21.75 天。  &lt;/p&gt;
&lt;h2 id="相关法律法规"&gt;相关法律法规&lt;/h2&gt;&lt;h3 id="《中华人民共和国宪法》"&gt;&lt;a href="http://www.npc.gov.cn/npc/xinwen/2018-03/22/content_2052489.htm" rel="nofollow" target="_blank" title=""&gt;《中华人民共和国宪法》&lt;/a&gt;&lt;/h3&gt;&lt;h4 id="第二章第四十三条："&gt;第二章第四十三条：&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;中华人民共和国劳动者有休息的权利。&lt;br&gt;
国家发展劳动者休息和休养的设施，规定职工的工作时间和休假制度。  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="《中华人民共和国劳动法》"&gt;&lt;a href="http://www.npc.gov.cn/npc/xinwen/2019-01/07/content_2070261.htm" rel="nofollow" target="_blank" title=""&gt;《中华人民共和国劳动法》&lt;/a&gt;&lt;/h3&gt;&lt;h4 id="第一章第三条："&gt;第一章第三条：&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;劳动者享有平等就业和选择职业的权利、&lt;strong&gt;取得劳动报酬的权利&lt;/strong&gt;、&lt;strong&gt;休息休假的权利&lt;/strong&gt;、获得劳动安全卫生保护的权利、接受职业技能培训的权利、享受社会保险和福利的权利、提请劳动争议处理的权利以及法律规定的其他劳动权利。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="第四章第三十六条："&gt;第四章第三十六条：&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;国家实行劳动者每日工作时间不超过八小时、平均每周工作时间不超过四十四小时的工时制度。  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="第四章第三十九条："&gt;第四章第三十九条：&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;企业因生产特点不能实行本法第三十六条、第三十八条规定的，经劳动行政部门批准，可以实行其他工作和休息办法。  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="第四章第四十一条："&gt;第四章第四十一条：&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;用人单位由于生产经营需要，经与工会和劳动者协商后可以延长工作时间，一般每日不得超过一小时；因特殊原因需要延长工作时间的，在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时，但是每月不得超过三十六小时。  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="第四章第四十三条："&gt;第四章第四十三条：&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;用人单位不得违反本法规定延长劳动者的工作时间。  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="第四章第四十四条："&gt;第四章第四十四条：&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;有下列情形之一的，用人单位应当按照下列标准支付高于劳动者正常工作时间工资的工资报酬：&lt;br&gt;
（一）安排劳动者延长工作时间的，支付不低于工资的百分之一百五十的工资报酬；&lt;br&gt;
（二）休息日安排劳动者工作又不能安排补休的，支付不低于工资的百分之二百的工资报酬；&lt;br&gt;
（三）法定休假日安排劳动者工作的，支付不低于工资的百分之三百的工资报酬。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="第十二章第九十条："&gt;第十二章第九十条：&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;用人单位违反本法规定，延长劳动者工作时间的，由劳动行政部门给予警告，责令改正，并可以处以罚款。  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="第十二章第九十一条："&gt;第十二章第九十一条：&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;用人单位有下列侵害劳动者合法权益情形之一的，由劳动行政部门责令支付劳动者的工资报酬、经济补偿，并可以责令支付赔偿金：&lt;br&gt;
 ……&lt;br&gt;
 （二）拒不支付劳动者延长工作时间工资报酬的；&lt;br&gt;
……  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="《中华人民共和国劳动合同法》"&gt;&lt;a href="http://www.npc.gov.cn/wxzl/gongbao/2013-04/15/content_1811058.htm" rel="nofollow" target="_blank" title=""&gt;《中华人民共和国劳动合同法》&lt;/a&gt;&lt;/h3&gt;&lt;h4 id="第三章第三十一条："&gt;第三章第三十一条：&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;用人单位应当严格执行劳动定额标准，不得强迫或者变相强迫劳动者加班。用人单位安排加班的，应当按照国家有关规定向劳动者支付加班费。  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="第五章第六十二条："&gt;第五章第六十二条：&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;用工单位应当履行下列义务：&lt;br&gt;
……&lt;br&gt;
（三）支付加班费、绩效奖金，提供与工作岗位相关的福利待遇；&lt;br&gt;
……  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="第七章第八十五条："&gt;第七章第八十五条：&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;用人单位有下列情形之一的，由劳动行政部门责令限期支付劳动报酬、加班费或者经济补偿；劳动报酬低于当地最低工资标准的，应当支付其差额部分；逾期不支付的，责令用人单位按应付金额百分之五十以上百分之一百以下的标准向劳动者加付赔偿金：&lt;br&gt;
 （一）未按照劳动合同的约定或者国家规定及时足额支付劳动者劳动报酬的；&lt;br&gt;
 （二）低于当地最低工资标准支付劳动者工资的；&lt;br&gt;
 （三）安排加班不支付加班费的；&lt;br&gt;
……  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="《国务院关于职工工作时间的规定》"&gt;&lt;a href="http://www.mohrss.gov.cn/SYrlzyhshbzb/zcfg/flfg/xzfg/201604/t20160412_237909.html" rel="nofollow" target="_blank" title=""&gt;《国务院关于职工工作时间的规定》&lt;/a&gt;&lt;/h3&gt;&lt;h4 id="第三条："&gt;第三条：&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;职工每日工作 8 小时、每周工作 40 小时。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="第六条："&gt;第六条：&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;任何单位和个人不得擅自延长职工工作时间。因特殊情况和紧急任务确需延长工作时间的，按照国家有关规定执行。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="《劳动部贯彻〈国务院关于职工工作时间的规定〉的实施办法》"&gt;&lt;a href="https://duxiaofa.baidu.com/detail?cid=dd3870dde31884c992ce6c617b248e99_law&amp;amp;searchType=statute" rel="nofollow" target="_blank" title=""&gt;《劳动部贯彻〈国务院关于职工工作时间的规定〉的实施办法》&lt;/a&gt;&lt;/h3&gt;&lt;h4 id="第三条："&gt;第三条：&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;职工每日工作 8 小时、每周工作 40 小时。实行这一工时制度，应保证完成生产和工作任务，不减少职工的收入。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="第六条："&gt;第六条：&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;任何单位和个人不得擅自延长职工工作时间。企业由于生产经营需要而延长职工工作时间的，应按《中华人民共和国劳动法》第四十一条的规定执行。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="第八条："&gt;第八条：&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;根据本办法第六条、第七条延长工作时间的，企业应当按照《中华人民共和国劳动法》第四十四条的规定，给职工支付工资报酬或安排补休。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="相关事件报道"&gt;相关事件报道&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;2016 年 9 月初起，陆续有网友爆料称，&lt;strong&gt;58 同城&lt;/strong&gt;实行全员 996 工作制，且周末加班没有工资。公司方面回应称，为应对业务量高峰期，公司每年 9、10 月份都会有动员，属常规性活动，而本次“996 动员”并非强制。（&lt;a href="http://finance.cnr.cn/gs/20160901/t20160901_523105136.shtml" rel="nofollow" target="_blank" title=""&gt;58 同城实行全员 996 工作制 被指意图逼员工主动辞职&lt;/a&gt;. 央广网。2016-09-01. ）&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;del&gt;2018 年 11 月，&lt;strong&gt;聚美优品&lt;/strong&gt;在成都研发部实行 996 工作制，并称之为正常工作时间，拒绝支付加班工资和调休。员工称正常请假 1 天会被计以 11 个小时的请假时间。周六请假需要提交邮件后经由部门总经理审核。2019 年 1 月旗下新零售&lt;strong&gt;汪汪集团&lt;/strong&gt;员工，被迫放弃一切调休时间和工龄，并签署到聚美优品，否则将被无情裁员。&lt;/del&gt;（{{来源请求}}）&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;2019 年 1 月，杭州电商公司&lt;strong&gt;有赞&lt;/strong&gt;在公司年会宣布未来执行 996 工作制，CEO 白鸦回应“几年后回看，这次绝对是好事”。（&lt;a href="http://www.linkshop.com.cn/web/archives/2019/418163.shtml" rel="nofollow" target="_blank" title=""&gt;年会成了“鸿门宴”，这家公司“强制 996”被员工举报&lt;/a&gt;. 联商网。2019-01-22. ）&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;2019 年 3 月，曝&lt;strong&gt;京东&lt;/strong&gt;开始实行分部门的 996 或 995 工作制，京东公关在脉脉平台上表示：“全情投入”。（&lt;a href="http://tech.163.com/19/0312/13/EA2QGIOK00097U7R.html" rel="nofollow" target="_blank" title=""&gt;京东回应 995 工作制：不会强制要求 但要全情投入&lt;/a&gt;. 网易科技。2019-03-12. ）&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;

&lt;p&gt;按照劳动法规定，996 工作制下只有拿到当前工资的 &lt;strong&gt;2.275&lt;/strong&gt; 倍，才在经济账上不吃亏。&lt;/p&gt;

&lt;p&gt;什么是 996.ICU？工作 996，生病 ICU。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Developers' lives matter.&lt;/strong&gt;&lt;/p&gt;</description>
      <author>tkvern</author>
      <pubDate>Sat, 30 Mar 2019 15:47:26 +0800</pubDate>
      <link>https://ruby-china.org/topics/38325</link>
      <guid>https://ruby-china.org/topics/38325</guid>
    </item>
    <item>
      <title>学习 Rails 有感</title>
      <description>&lt;p&gt;较前文 &lt;a href="https://ruby-china.org/topics/30594" title=""&gt;Rails 从入门到完全放弃&lt;/a&gt;，已有两年。&lt;/p&gt;

&lt;p&gt;在用过 Rails 往后的时光里，使用其他语言的 Web 框架时，让我一个前端时时刻刻无法忘记 Rails 的思想以及艺术性。&lt;/p&gt;
&lt;h2 id="我如何选择框架和构建框架"&gt;我如何选择框架和构建框架&lt;/h2&gt;
&lt;p&gt;Rails 对我编程生涯的影响&lt;code&gt;深旷长远&lt;/code&gt;，&lt;code&gt;约定优于配置&lt;/code&gt;这一思想深入人心。&lt;/p&gt;

&lt;p&gt;在我选择以何种 Web 开发框架来支撑业务开发时，首要考量的是框架的设计思想。&lt;/p&gt;
&lt;h3 id="框架选择优先级"&gt;框架选择优先级&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;继承 Rails 思想的&lt;/li&gt;
&lt;li&gt;像 Rails 的&lt;/li&gt;
&lt;li&gt;Rails&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="继承Rails思想的"&gt;继承 Rails 思想的&lt;/h4&gt;
&lt;p&gt;这个我称为神似，不单单徒具其表，还有其思想。&lt;/p&gt;
&lt;h4 id="像Rails的"&gt;像 Rails 的&lt;/h4&gt;
&lt;p&gt;样子做的像 Rails，实际上千差万别的&lt;/p&gt;
&lt;h4 id="Rails"&gt;Rails&lt;/h4&gt;
&lt;p&gt;为什么把 Rails 列在普通优先级。一方面是生态的问题。其实项目立项的时候，首先想到的是 Rails，但是迫于种种原因，不得不选择其他的 Web 框架。
首要是人才招聘，找到一个熟悉 Web 开发的工程师比较难，找到一个熟悉 Web 开发的 Rails 工程师更难，而国内 Rails 开发的工程师群众基础不够强。&lt;/p&gt;

&lt;p&gt;由此，我并没有选择 Rails 做开发，而是能继承 Rails 思想并且容易上手、群众基础强的 Web 框架，如 PHP Laravel。&lt;/p&gt;
&lt;h3 id="构建框架"&gt;构建框架&lt;/h3&gt;
&lt;p&gt;用的轮子多了，也会想着造轮子，谁年轻的时候没这么造过呢。&lt;/p&gt;

&lt;p&gt;然而，比起造轮子，我更倾向于造车——把优秀的轮子组装起来。
过去的两年的，我把 Rails 的思想应用到了前端。做了一套开发实践《&lt;a href="https://ruby-china.org/topics/32231" title=""&gt;Dva + Ant Design 前后端分离之 React 应用实践&lt;/a&gt;》&lt;/p&gt;

&lt;p&gt;有了这一套实践，后续的前后端分离项目，企业中台、H5 应用中。
有了约定和规范，码起来飞快。再结合 CI、日志、监控 (全部基于阿里云)。省时省心。&lt;/p&gt;
&lt;h2 id="Nodejs的抉择"&gt;Nodejs 的抉择&lt;/h2&gt;
&lt;p&gt;进入编程世界已经有几年了，我也不再是懵懂新手。但是对于整个编程大海而言，我还是在沙滩边拾贝的少年。
在我使用过的语言中，JavaScript 无疑是我使用过的诸多语言中最频繁的。这也使得我在编程倾向性上，偏向 JavaScript。
而在 Nodejs 生态中，eggjs 无疑是最得 Rails 真传的企业级 Web 框架。&lt;a href="https://eggjs.org/" rel="nofollow" target="_blank" title=""&gt;Egg 为企业级框架和应用而生&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;当我选择 Node 技术栈的时候，Eggjs 是和 Rails 一样舒服的 Web 框架。而 Eggjs 的作者也曾言，使用 Eggjs 能让 P5、P6 达到 P7～P8 的水准。
让 P7～P8 能玩出更多花样。&lt;/p&gt;
&lt;h2 id="[未完结......]"&gt;[未完结......]&lt;/h2&gt;</description>
      <author>tkvern</author>
      <pubDate>Wed, 03 Oct 2018 19:24:14 +0800</pubDate>
      <link>https://ruby-china.org/topics/37588</link>
      <guid>https://ruby-china.org/topics/37588</guid>
    </item>
    <item>
      <title>RC UI 上的小问题</title>
      <description>&lt;p&gt;发现一个小问题。有劳管理员修复一下。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2018/584ae00c-b94a-4c47-9b59-04fb161bd346.png!large" title="" alt=""&gt;&lt;/p&gt;</description>
      <author>tkvern</author>
      <pubDate>Tue, 26 Jun 2018 17:26:50 +0800</pubDate>
      <link>https://ruby-china.org/topics/37054</link>
      <guid>https://ruby-china.org/topics/37054</guid>
    </item>
    <item>
      <title>程序员发生劳动纠纷该如何处理？</title>
      <description>&lt;h2 id="资料准备"&gt;资料准备&lt;/h2&gt;
&lt;p&gt;下面是提交劳动仲裁和司法起诉的材料&lt;/p&gt;

&lt;p&gt;1.劳动合同 (如果签订劳动合同时有出现 A、B 合同的情况，请警惕)&lt;/p&gt;

&lt;p&gt;2.考勤或工作在岗证据 (如果有更好)&lt;/p&gt;

&lt;p&gt;3.公积金或社保缴费记录 (如果有更好，可在公司所在市的社保或公积金大厅自助打印)&lt;/p&gt;

&lt;p&gt;4.工资卡到账记录 (前往对应银行自助打印即可)&lt;/p&gt;

&lt;p&gt;5.身份证复印件&lt;/p&gt;

&lt;p&gt;6.就职公司基本信息 (纳税人识别号、法人代表、所在地址等等)&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;例如：小 A 在深圳市南山区工作，公司注册地址为南山区。即向南山区劳动局申请仲裁即可。&lt;/p&gt;

&lt;p&gt;远程工作者提交仲裁申请的方式，如上，需向公司注册地辖区提交申请。&lt;/p&gt;
&lt;h2 id="额外"&gt;额外&lt;/h2&gt;
&lt;p&gt;最好是委托专业律师提供法律援助。&lt;/p&gt;

&lt;p&gt;在深圳，劳动仲裁只要证据资料&lt;code&gt;真实齐全&lt;/code&gt;，处理是很快的。&lt;/p&gt;

&lt;p&gt;唯一需要担心的就是法人资产转移，可以在仲裁期间申请财产冻结。&lt;/p&gt;</description>
      <author>tkvern</author>
      <pubDate>Mon, 25 Jun 2018 17:06:43 +0800</pubDate>
      <link>https://ruby-china.org/topics/37044</link>
      <guid>https://ruby-china.org/topics/37044</guid>
    </item>
    <item>
      <title>Dva + Ant Design 前后端分离之 React 应用实践</title>
      <description>&lt;h2 id="Dva + Ant Design 前后端分离之 React 应用实践"&gt;Dva + Ant Design 前后端分离之 React 应用实践&lt;/h2&gt;
&lt;p&gt;源站链接&lt;a href="https://tkvern.com/20170204/Dva%20+%20Ant%20Design%20%E5%89%8D%E5%90%8E%E7%AB%AF%E5%88%86%E7%A6%BB%E4%B9%8B%20React%20%E5%BA%94%E7%94%A8%E5%AE%9E%E8%B7%B5/" rel="nofollow" target="_blank" title=""&gt;https://tkvern.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;继 &lt;a href="https://ruby-china.org/topics/30594" title=""&gt;Rails 从入门到完全放弃 拥抱 Elixir + Phoenix + React + Redux&lt;/a&gt; 这篇文章被喷之后，笔者很长一段时候没有上社区逛了。现在 &lt;a href="tkvern.com" title=""&gt;tkvern&lt;/a&gt; 又回归了，给大家带来 React 实践的一些经验，一些踩坑的经验。&lt;/p&gt;

&lt;p&gt;Rails 嘛，很好用，Laravel 也好用。Phoenix 也好用。都好，哪个方便用哪个。&lt;/p&gt;

&lt;p&gt;还有关于&lt;em&gt;Turbolinks&lt;/em&gt;之争，不能单从页面渲染时间去对比，要综合考虑。&lt;/p&gt;
&lt;h2 id="Why Dva？"&gt;Why Dva?&lt;/h2&gt;
&lt;p&gt;Dva 是基于 Redux 做了一层封装，对于 React 的 state 管理，有很多方案，我选择了轻量、简单的 Dva。至于 Mobx，还没应用到项目中来。先等友军踩踩坑，再往里面跳。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/dvajs/dva/issues/1" rel="nofollow" target="_blank" title=""&gt;Why dva and what's dva&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.github.com/sorrycc/blog/issues/6" rel="nofollow" target="_blank" title=""&gt;支付宝前端应用架构的发展和选择&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;顺便贴下 Dva 的特性：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;易学易用&lt;/strong&gt;：仅有 5 个 api，对 redux 用户尤其友好&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;elm 概念&lt;/strong&gt;：通过 &lt;code&gt;reducers&lt;/code&gt;, &lt;code&gt;effects&lt;/code&gt; 和 &lt;code&gt;subscriptions&lt;/code&gt; 组织 model&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;支持 mobile 和 react-native&lt;/strong&gt;：跨平台 (&lt;a href="https://github.com/sorrycc/dva-example-react-native" rel="nofollow" target="_blank" title=""&gt;react-native 例子&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;支持 HMR&lt;/strong&gt;：目前基于 &lt;a href="https://github.com/dvajs/babel-plugin-dva-hmr" rel="nofollow" target="_blank" title=""&gt;babel-plugin-dva-hmr&lt;/a&gt; 支持 components 和 routes 的 HMR&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;动态加载 Model 和路由&lt;/strong&gt;：按需加载加快访问速度 (&lt;a href="https://github.com/dvajs/dva/tree/master/examples/dynamic-load" rel="nofollow" target="_blank" title=""&gt;例子&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;插件机制&lt;/strong&gt;：比如 &lt;a href="https://github.com/dvajs/dva-loading" rel="nofollow" target="_blank" title=""&gt;dva-loading&lt;/a&gt; 可以自动处理 loading 状态，不用一遍遍地写 showLoading 和 hideLoading&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;完善的语法分析库 &lt;a href="https://github.com/dvajs/dva-ast" rel="nofollow" target="_blank" title=""&gt;dva-ast&lt;/a&gt;&lt;/strong&gt;：&lt;a href="https://github.com/dvajs/dva-cli" rel="nofollow" target="_blank" title=""&gt;dva-cli&lt;/a&gt; 基于此实现了智能创建 model, router 等&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;支持 TypeScript&lt;/strong&gt;：通过 d.ts (&lt;a href="https://github.com/sorrycc/dva-boilerplate-typescript" rel="nofollow" target="_blank" title=""&gt;例子&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="Why Ant Design?"&gt;Why Ant Design?&lt;/h2&gt;
&lt;p&gt;做为传道士，这么好的 UI 设计语言，肯定不会藏着掖着啦。蚂蚁金服的东西，确实不错，除了 Ant Design 外，还有 Ant Design Mobile、AntV、AntMotion、G2。&lt;/p&gt;
&lt;h2 id="Why yarn?"&gt;Why yarn?&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;npm install&lt;/code&gt; 太慢，试试&lt;a href="https://yarnpkg.com/" rel="nofollow" target="_blank" title=""&gt;yarn&lt;/a&gt;吧。建议用&lt;code&gt;npm install yarn -g&lt;/code&gt;进行安装。&lt;/p&gt;
&lt;h2 id="开发过程中的前后端分离"&gt;开发过程中的前后端分离&lt;/h2&gt;
&lt;p&gt;项目开始了，前端视图写完，要开始数据交互了，后端提供的 API 还没好。&lt;/p&gt;

&lt;p&gt;那么问题来了，如何在不依靠后端提供 API 的情况下，实现数据交互？&lt;/p&gt;

&lt;p&gt;使用&lt;a href="http://mockjs.com/" rel="nofollow" target="_blank" title=""&gt;Mock.js&lt;/a&gt;可以解决这个问题。先对接好 API 数据格式，然后使用 Mockjs 拦截 Ajax 请求，模拟后端真实数据。&lt;/p&gt;

&lt;p&gt;在 Mockjs 官方提供的 API 不够用的情况下，还可以使用正则产生模拟数据。&lt;/p&gt;
&lt;h3 id="如何对模拟做数据持久化处理？"&gt;如何对模拟做数据持久化处理？&lt;/h3&gt;
&lt;p&gt;这里给出一个模拟用户数据并持久化的实例实例：&lt;a href="https://github.com/tkvern/dva-passport/blob/develop/resources/assets/ant_passport/mock/users.js" rel="nofollow" target="_blank" title=""&gt;mock/users.js&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;代码摘要：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use strict&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;qs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;qs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mockjs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mockjs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Random&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mockjs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Random&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// 数据持久化&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;tableListData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;global&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tableListData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mockjs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data|100&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&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;id|+1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cname&lt;/span&gt;&lt;span class="p"&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;mobile&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;57&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;01678&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;,
&lt;/span&gt;      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;avatar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;125x125&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&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;status|1-2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;visiondk.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&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;isadmin|0-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;created_at&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;yyyy-MM-dd HH:mm:ss&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&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;updated_at&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;yyyy-MM-dd HH:mm:ss&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;total&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;current&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;tableListData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nb"&gt;global&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tableListData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tableListData&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;tableListData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;global&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tableListData&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="模拟API怎么写？"&gt;模拟 API 怎么写？&lt;/h3&gt;
&lt;p&gt;完成持久化处理后，就可以像操作数据库一样进行增、删、改、查&lt;/p&gt;

&lt;p&gt;下面是一个删除用户的 API&lt;/p&gt;

&lt;p&gt;参见&lt;a href="https://github.com/tkvern/dva-passport/blob/develop/resources/assets/ant_passport/mock/users.js#L106" rel="nofollow" target="_blank" title=""&gt;mock/users.js#L106&lt;/a&gt;：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DELETE /api/users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&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="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;deleteItem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;qs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="nx"&gt;tableListData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tableListData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;deleteItem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="nx"&gt;tableListData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tableListData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="nb"&gt;global&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tableListData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tableListData&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;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tableListData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tableListData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="还有一步"&gt;还有一步&lt;/h3&gt;
&lt;p&gt;模拟数据和 API 写好了，还需要拦截 Ajax 请求&lt;/p&gt;

&lt;p&gt;修改&lt;code&gt;package.json&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dora --plugins &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;proxy,webpack,webpack-hmr&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"atool-build -o ../../../public"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"atool-test-mocha ./src/**/*-test.js"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果与&lt;code&gt;dora&lt;/code&gt;有端口冲突可修改&lt;code&gt;dora&lt;/code&gt;的端口号&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dora --port 8888 --plugins &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;proxy,webpack,webpack-hmr&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;完成这些基本工作就做好了&lt;/p&gt;
&lt;h3 id="友情提示"&gt;友情提示&lt;/h3&gt;
&lt;p&gt;在模拟数据环境，&lt;code&gt;services&lt;/code&gt;下的模块这么写就好了，真实 API 则替换为真实 API 的地址。可将地址前缀写到统一配置中去。&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../utils/request&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;qs&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;qs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/api/users?&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;qs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;post&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;qs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;delete&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;qs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;put&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;qs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;真实 API 参考实例：&lt;a href="https://github.com/tkvern/dva-passport/blob/develop/resources/assets/ant_passport/src/services/users.js" rel="nofollow" target="_blank" title=""&gt;src/services/users.js&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="如何保持登录状态"&gt;如何保持登录状态&lt;/h2&gt;
&lt;p&gt;在看 dva 的引导手册时，并没有介绍登录相关的内容。因为不同的项目，对于登录这块的实现会有所不同，并不是唯一的。通常我们会使用 Cookie 的方式保持登录状态，或者 Auth 2.0 的技术。&lt;/p&gt;

&lt;p&gt;这里介绍 Cookie 的方式。&lt;/p&gt;

&lt;p&gt;登录成功之后服务器会设置一个当前域可以使用的 Cookie，例如&lt;code&gt;token&lt;/code&gt;啥的。然后在每次数据请求的时候在&lt;code&gt;Request Headers&lt;/code&gt;中携带&lt;code&gt;token&lt;/code&gt;，后端会基于这个&lt;code&gt;token&lt;/code&gt;进行权限验证。思路清晰了，来看看具体实现吧。（注：在这次项目中使用了统一登录模块，通过 Header 中的&lt;code&gt;Authorization&lt;/code&gt;进行验证，将只介绍拿到&lt;code&gt;token&lt;/code&gt;之后的数据处理）&lt;/p&gt;
&lt;h3 id="准备工作"&gt;准备工作&lt;/h3&gt;
&lt;p&gt;对于操作 Cookie 的一些操作，建议先封装到工具类模块下。同时我把操作&lt;code&gt;LocalStrage&lt;/code&gt;的一些操作也写进来了。&lt;/p&gt;

&lt;p&gt;参见&lt;a href="https://github.com/tkvern/dva-passport/blob/develop/resources/assets/ant_passport/src/utils/helper.js" rel="nofollow" target="_blank" title=""&gt;src/utils/helper.js&lt;/a&gt;&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="c1"&gt;// Operation Cookie&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getCookie&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reg&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;RegExp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;(^| )&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;=([^;]*)(;|$)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;arr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;decodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;delCookie&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getCookie&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookie&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;=; expires=Thu, 01-Jan-70 00:00:01 GMT; path=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; 
                      &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;; domain=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; 
                      &lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Header 的预处理我放在了&lt;a href="https://github.com/tkvern/dva-passport/blob/develop/resources/assets/ant_passport/src/utils/auth.js#L5" rel="nofollow" target="_blank" title=""&gt;src/utils/auth.js#L5&lt;/a&gt;，这里后端返回的数据都是 JSON 格式，所以在 Header 里面需要添加&lt;code&gt;application/json&lt;/code&gt;进去，而&lt;code&gt;Authorization&lt;/code&gt;是后端用来验证用户信息的。变量&lt;code&gt;sso_token&lt;/code&gt;为了方便代码阅读就没有按照规范命名了。&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getAuthHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sso_token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&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;Accept&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;application/json&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;Authorization&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;Bearer &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;sso_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&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;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="修改Request"&gt;修改 Request&lt;/h3&gt;
&lt;p&gt;这里没有使用自带的 catch 机制来处理请求错误，在开发过程中，最开始打算使用统一错误处理，但是发现请求失败后，不能在&lt;code&gt;models&lt;/code&gt;层处理&lt;code&gt;components&lt;/code&gt;，所以就换了一种方式处理，后面会讲到。&lt;/p&gt;

&lt;p&gt;参见&lt;a href="https://github.com/tkvern/dva-passport/blob/develop/resources/assets/ant_passport/src/utils/request.js#L29" rel="nofollow" target="_blank" title=""&gt;src/utils/request.js#L29&lt;/a&gt;&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sso_token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getCookie&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sso_token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authHeader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getAuthHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sso_token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;authHeader&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;checkStatus&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parseJSON&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
    &lt;span class="c1"&gt;// .catch((err) =&amp;gt; ({ err }));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;完成这些配置之后，每次向服务器发送的请求就都携带了用户&lt;code&gt;token&lt;/code&gt;了。在&lt;code&gt;token&lt;/code&gt;无效时，服务器会抛出&lt;code&gt;401&lt;/code&gt;错误，这时就需要在中间件中处理&lt;code&gt;401&lt;/code&gt;错误。&lt;/p&gt;

&lt;p&gt;参见&lt;a href="https://github.com/tkvern/dva-passport/blob/develop/resources/assets/ant_passport/src/utils/request.js#L10" rel="nofollow" target="_blank" title=""&gt;src/utils/request.js#L10&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;redirectLogin&lt;/code&gt;是工具类&lt;a href="https://github.com/tkvern/dva-passport/blob/develop/resources/assets/ant_passport/src/utils/auth" rel="nofollow" target="_blank" title=""&gt;src/utils/auth.js&lt;/a&gt;中的重定向登录方法。&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;checkStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;redirectLogin&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;error&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;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusText&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;到此为止，登录状态的配置基本完成。&lt;/p&gt;
&lt;h2 id="Router"&gt;Router&lt;/h2&gt;
&lt;p&gt;我们的应用中会有多个页面，而且有的需要登录才可见，那么如何控制呢？&lt;/p&gt;

&lt;p&gt;React 的路由控制是比较灵活的，来看看下面这个例子：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/tkvern/dva-passport/blob/develop/resources/assets/ant_passport/src/router.jsx" rel="nofollow" target="_blank" title=""&gt;src/router.jsx&lt;/a&gt;&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Route&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dva/router&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;authenticated&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./utils/auth&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Dashboard&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./routes/Dashboard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Users&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./routes/Users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./routes/User&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Password&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./routes/Password&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Roles&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./routes/Roles&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Permissions&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./routes/Permissions&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;history&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Router&lt;/span&gt; &lt;span class="nx"&gt;history&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;history&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Route&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Dashboard&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;onEnter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;authenticated&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Route&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;onEnter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;authenticated&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Route&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Password&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;onEnter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;authenticated&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Route&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/users&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Users&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;onEnter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;authenticated&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Route&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/roles&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Roles&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;onEnter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;authenticated&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Route&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/permissions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Permissions&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;onEnter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;authenticated&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Router&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于路由的验证配置在&lt;code&gt;onEnter&lt;/code&gt;属性中，&lt;code&gt;authenticated&lt;/code&gt;方法可统一进行路由验证，要注意每一个&lt;code&gt;Route&lt;/code&gt;节点的验证都需要配置相应的&lt;code&gt;onEnter&lt;/code&gt;属性。如果权限较为复杂需对每一个&lt;code&gt;Route&lt;/code&gt;单独验证。其实这种基于客户端渲染的应用，如果页面限制有遗漏也关系不太，后端提供的 API 会对数据进行验证，即使前端访问到没有权限的页面，也同样不用担心，做好客户端错误处理即可。&lt;/p&gt;
&lt;h2 id="数据缓存"&gt;数据缓存&lt;/h2&gt;
&lt;p&gt;对于一个 React 应用来说，缓存是很重要的一步。前后端分离后，频繁的 Ajax 请求会消耗大量的服务器资源，如果一些不长变动的持久化数据不做缓存的话，会浪费许多资源。所以，比较常见的方法就是将数据缓存在&lt;code&gt;LocalStorage&lt;/code&gt;中。针对一些敏感信息可适当进行加密混淆处理，我这里就不介绍了。&lt;/p&gt;
&lt;h3 id="什么时候做数据缓存?"&gt;什么时候做数据缓存？&lt;/h3&gt;
&lt;p&gt;例：用户信息缓存&lt;/p&gt;

&lt;p&gt;参见&lt;a href="https://github.com/tkvern/dva-passport/blob/develop/resources/assets/ant_passport/src/models/auth.js#L64" rel="nofollow" target="_blank" title=""&gt;src/models/auth.js#L64&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;在&lt;code&gt;subscriptions&lt;/code&gt;中配置了&lt;code&gt;setup&lt;/code&gt;检测&lt;code&gt;LocalStorage&lt;/code&gt;中的&lt;code&gt;user&lt;/code&gt;是否存在。不存在时会去&lt;code&gt;query&lt;/code&gt;用户信息，然后保存到&lt;code&gt;user&lt;/code&gt;中，如果存在就将&lt;code&gt;user&lt;/code&gt;中的数据添加到&lt;code&gt;state&lt;/code&gt;的&lt;code&gt;user: {}&lt;/code&gt;中。当然在进行请求时，已经在&lt;code&gt;src/utils/auth.js&lt;/code&gt;验证用户信息是否正确，同时做了相应的限制&lt;a href="https://github.com/tkvern/dva-passport/blob/develop/resources/assets/ant_passport/src/utils/auth.js#L20" rel="nofollow" target="_blank" title=""&gt;src/utils/auth.js#L20&lt;/a&gt;&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;parse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;qs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;antd&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../services/auth&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getLocalStorage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setLocalStorage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../utils/helper&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auth&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
    &lt;span class="na"&gt;isLogined&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;currentMenu&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;reducers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;querySuccess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;isLogined&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;effects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;put&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;err_msg&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SUCCESS&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;setLocalStorage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;querySuccess&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nl"&gt;subscriptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;dispatch&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getLocalStorage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;query&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;querySuccess&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;简单来说，就是没有缓存的时候缓存。&lt;/p&gt;
&lt;h3 id="什么时候更新数据缓存？"&gt;什么时候更新数据缓存？&lt;/h3&gt;
&lt;p&gt;例如，&lt;code&gt;roles&lt;/code&gt;中&lt;code&gt;添加&lt;/code&gt;和&lt;code&gt;修改&lt;/code&gt;功能都需要用到&lt;code&gt;permissions&lt;/code&gt;的数据，哪我怎么拿到最新的&lt;code&gt;permissions&lt;/code&gt;数据呢。首先，我在加载&lt;code&gt;roles&lt;/code&gt;列表页面时就需要将&lt;code&gt;permissions&lt;/code&gt;的数据缓存，这样，在每次点&lt;code&gt;添加&lt;/code&gt;或&lt;code&gt;修改&lt;/code&gt;功能时就不需要再去拉取已缓存的数据了。&lt;/p&gt;

&lt;p&gt;参见&lt;a href="https://github.com/tkvern/dva-passport/blob/develop/resources/assets/ant_passport/src/models/roles.js#L166" rel="nofollow" target="_blank" title=""&gt;src/models/roles.js#L166&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;在监听路由到&lt;code&gt;roles&lt;/code&gt;时查询&lt;code&gt;permissions&lt;/code&gt;是否缓存，将其更新到缓存中去。&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;
  &lt;span class="nx"&gt;subscriptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;history&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;history&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pathToRegexp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/roles&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getLocalStorage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;permissions&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
              &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;permissions/updateCache&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;query&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="什么时候删除数据缓存？"&gt;什么时候删除数据缓存？&lt;/h3&gt;
&lt;p&gt;删除缓存的配置是比较灵活的，这里的业务场景并不复杂所以，我用了比较简单的处理方式。&lt;/p&gt;

&lt;p&gt;参见&lt;a href="https://github.com/tkvern/dva-passport/blob/develop/resources/assets/ant_passport/src/models/permissions.js#L112" rel="nofollow" target="_blank" title=""&gt;src/models/permissions.js#L112&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;在执行新增或更新操作成功后，将本地原有的缓存删除。加上数据联动的特性，当再次回到&lt;code&gt;roles&lt;/code&gt;操作时，缓存已经更新了。&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;
    &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;select&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;put&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hideModal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;showLoading&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;permissions&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentItem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newRole&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;newRole&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;err_msg&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SUCCESS&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;updateSuccess&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;newRole&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;permissions&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;更新成功!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="State的临时缓存"&gt;State 的临时缓存&lt;/h3&gt;
&lt;p&gt;state 的中的数据是变化的，刷新页面之后会重置掉，也可以将部分&lt;code&gt;models&lt;/code&gt;中的&lt;code&gt;state&lt;/code&gt;存到&lt;code&gt;Localstorage&lt;/code&gt;中，让 state 的数据从&lt;code&gt;Localstorage&lt;/code&gt;读取，但不是必要的。而&lt;code&gt;list&lt;/code&gt;数据的更新，是直接操作 state 中的数据的。&lt;/p&gt;

&lt;p&gt;如下 (这样就不用更新整个 list 的数据了)。&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;
    &lt;span class="nf"&gt;grantSuccess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;grantUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;list&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="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;grantUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;grantUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roles&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;newList&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="视图组件运用"&gt;视图组件运用&lt;/h2&gt;
&lt;p&gt;Ant 提供的组件非常多，但用起来还是需要一些学习成本的，同时多个组件组合使用时也需要有很多地方注意的。&lt;/p&gt;
&lt;h3 id="Modal注意事项"&gt;Modal 注意事项&lt;/h3&gt;
&lt;p&gt;在使用 Modal 组件时，难免会出现一个页面多个 Modal 的情况，首先要注意的就是 Modal 的命名，在多 Modal 情况下，命名不注意很容易出现分不清用的是哪个 Modal。建议命名时能望名知意。然后就是 Modal 需要用到别的 Models 的数据时，如果在弹窗时通过 Ajax 获取需要的数据再显示 Modal，这样就会出现 Modal 延迟，而且 Modal 的动画也无法加载出来。所以，我的处理方式是，在进入这一级&lt;code&gt;Route&lt;/code&gt;的时候就将需要的数据&lt;code&gt;预缓存&lt;/code&gt;，这样调用时就可随用随取，不会出现延迟了。&lt;/p&gt;

&lt;p&gt;参见&lt;a href="https://github.com/tkvern/dva-passport/blob/develop/resources/assets/ant_passport/src/components/user/UserModalGrant.jsx#L33" rel="nofollow" target="_blank" title=""&gt;src/components/user/UserModalGrant.jsx#L33&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="Form注意"&gt;Form 注意&lt;/h3&gt;
&lt;p&gt;Ant 的 form 组件很完善，需要注意的就是表单的多条件查询。如果单单是一个条件查询的处理比较简单，将查询关键词设成&lt;code&gt;string&lt;/code&gt;类型存到相应的 Models 中的 state 即可，多条件的话，稍微麻烦一点，需存成 Hash 对象。灵活处理即可。&lt;/p&gt;
&lt;h3 id="其他"&gt;其他&lt;/h3&gt;
&lt;p&gt;官方文档的描述很清楚，我就不充大头了。注意写法规范即可，直接复制粘贴官方例子代码会很难看。&lt;/p&gt;
&lt;h2 id="跨域问题"&gt;跨域问题&lt;/h2&gt;
&lt;p&gt;终于说到点子上了，前后端分离遇到跨域问题很正常，而这种基于 RESTful API 的前后端分离就更好弄了。我这以 Fetch + PHP + Laravel 为例，这种并不是最有解决方案！仅供参考！&lt;/p&gt;

&lt;p&gt;在&lt;code&gt;header&lt;/code&gt;中进行如下配置&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Access-Control-Allow-Origin&lt;/code&gt;配置允许的域&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Access-Control-Allow-Methods&lt;/code&gt;配置允许的请求方式&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Access-Control-Allow-Headers&lt;/code&gt;配置允许的请求头&lt;/p&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Http\Request&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/&lt;/span&gt;

&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'middleware'&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'auth:api'&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Access-Control-Allow-Origin: *"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Access-Control-Allow-Methods: GET, HEAD, POST, PUT, PATCH, DELETE"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Access-Control-Allow-Headers: Access-Control-Allow-Headers, Origin, Accept, Authorization, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;require&lt;/span&gt; &lt;span class="nf"&gt;base_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'routes/common.php'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;基于其他编程语言的处理类似。&lt;/p&gt;
&lt;h2 id="结语"&gt;结语&lt;/h2&gt;
&lt;p&gt;了解前端、熟悉前端、精通前端、熟悉前端、不懂前端&lt;/p&gt;

&lt;p&gt;了解 X X、熟悉 X X、精通 X X、熟悉 X X、不懂 X X &lt;/p&gt;</description>
      <author>tkvern</author>
      <pubDate>Sat, 04 Feb 2017 17:36:01 +0800</pubDate>
      <link>https://ruby-china.org/topics/32231</link>
      <guid>https://ruby-china.org/topics/32231</guid>
    </item>
    <item>
      <title>Rails 从入门到完全放弃再到重新找回</title>
      <description>&lt;h2 id="Rails 从入门到完全放弃再到重新找回"&gt;Rails 从入门到完全放弃再到重新找回&lt;/h2&gt;
&lt;p&gt;转载地址：&lt;a href="https://tkvern.com/20160721/Rails%E4%BB%8E%E5%85%A5%E9%97%A8%E5%88%B0%E5%AE%8C%E5%85%A8%E6%94%BE%E5%BC%83/" rel="nofollow" target="_blank" title=""&gt;Rails 从入门到完全放弃再到重新找回&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;这是一篇关于 Rails 的开发经历的文章，旨在将 Rails 中遇到的各种问题分享给还未接触 Rails 或是已经上路的朋友。虽说做 Rails 的开发时间不长，刚好一年多。但是，在这一年的时间中，该使用的技术架构，&lt;code&gt;Ruby-China&lt;/code&gt; 推荐的 Gem 包，都尝试过使用过了，也为业务开发了一些 Gem 包。谈不上精通 Rails，如果把 Rails 作者定为最高等级，他是 F1 赛车手，我该是个跑出租的老司机。&lt;/p&gt;
&lt;h2 id="背景"&gt;背景&lt;/h2&gt;
&lt;p&gt;早前有做过 Java，PHP，.Net 的开发，相信玩 Rails 的朋友多多少少也都有写过，不过主要还是以前端为主。早在 IE7/IE8 时代做前端开发，那时&lt;code&gt;NodeJS&lt;/code&gt;还没火起来，前端成了低技术含量又耗体力又没地位的活。不过，还好有&lt;code&gt;NodeJS&lt;/code&gt;，让我赶上了这个时代。&lt;/p&gt;
&lt;h2 id="怎么接触到Rails"&gt;怎么接触到 Rails&lt;/h2&gt;
&lt;p&gt;当公司的一个 PHP 的多人即时聊天项目接近尾声时，我们在思考能不能将程序员生产力解放出来？是不是可以尝试一些其他的技术架构。很快，经过多方研究，发现 Rails 是单兵作战的神器。相比 PHP，可以达到 Rails : PHP = 1 : 4 的效率。但对于一个技术架构成熟的技术团队来说，放弃原有的技术架构去使用一个从未接触过新技术，时间成本和决心是很重要的。但挑战往往会带来意想不到的收获。&lt;/p&gt;
&lt;h2 id="在深大图书馆的 Rails之道"&gt;在深大图书馆的 Rails 之道&lt;/h2&gt;
&lt;p&gt;学习新技术的第一件事就是去找学习资料。在 google 上找了很久，发现深大图书馆有各种各样的技术书籍，果不其然，在这里找到了&lt;code&gt;Ruby元编程&lt;/code&gt;，&lt;code&gt;Rails之道&lt;/code&gt;，&lt;code&gt;敏捷开发之道&lt;/code&gt;这些书籍，但是版本比较老。为了能够掌握最新版本的知识，下载了相应的英文版 PDF，一起结合。修炼 Rails 的过程是痛并快乐着的，因为要转变思维模式，去接受新的思想，去了解诸多的语法糖因何而生。学累了就躺会，饿了就上个外卖，脑袋成浆糊了就洗把脸。其实接触一门新语言并不是多难，这是一个循序渐进的过程。好在前端底子厚，学习&lt;code&gt;ERB&lt;/code&gt;，&lt;code&gt;UJS&lt;/code&gt;，&lt;code&gt;RJS&lt;/code&gt;的过程比较轻松，但是&lt;code&gt;Turbolinks&lt;/code&gt;对于前端工程师来说就是噩梦，一直到现在我都用的 Pjax。不喜欢&lt;code&gt;Turbolinks&lt;/code&gt;的做法，&lt;code&gt;Pjax&lt;/code&gt;显得很机智。关于&lt;code&gt;Turbolinks&lt;/code&gt;和&lt;code&gt;Pjax&lt;/code&gt;我并不是挑起战争，仁者见仁，智者见智。&lt;/p&gt;
&lt;h2 id="用Rails对电商的探索"&gt;用 Rails 对电商的探索&lt;/h2&gt;
&lt;p&gt;在构建电商系统的时候，很自然就 pull 了&lt;code&gt;ECShop&lt;/code&gt;的源码来学习。
业务上的问题并不大，有现成案例，结合需求来订制开发很快。
同时在开发过程中&lt;code&gt;Ruby-China&lt;/code&gt;社区也提供了许多帮助。类似查询 &lt;code&gt;N + 1&lt;/code&gt;问题，&lt;code&gt;CanCanCan&lt;/code&gt;权限问题.....&lt;/p&gt;
&lt;h3 id="文件上传"&gt;文件上传&lt;/h3&gt;&lt;h4 id="上传图片"&gt;上传图片&lt;/h4&gt;
&lt;p&gt;对于图片等资源的处理，最开始没有选用&lt;code&gt;Carrierwave&lt;/code&gt;的方案，而是使用&lt;code&gt;七牛云存储JS SDK&lt;/code&gt;，开始接触的时候，发现并没有多少参考文档，于是想是不是这个东西比较简单也比较少人用，还是&lt;code&gt;Ruby-China&lt;/code&gt; 社区的朋友太懒。后面深入研究后发现，这类云存储的方法还是用得比较多，也比较便捷，但对于新手还是有一定门槛，所以做完之后顺带写了相应的&lt;a href="https://ruby-china.org/topics/29010" title=""&gt;教程&lt;/a&gt;造福社会。&lt;/p&gt;
&lt;h4 id="富文本编辑器上传图片"&gt;富文本编辑器上传图片&lt;/h4&gt;
&lt;p&gt;在富文本编辑器中&lt;a href="https://www.froala.com/wysiwyg-editor" rel="nofollow" target="_blank" title=""&gt;Froala&lt;/a&gt;可以说是佼佼者，我们选用了 Froala。但是遇到一个问题，Froala 中的图片上传仅支持&lt;code&gt;Amazon云&lt;/code&gt;，因此不得不改造&lt;code&gt;Froala&lt;/code&gt;的源码。幸运的是这个过程并不困难，我将改造后的 Froala 用策略模式做成了一个 Gem: &lt;a href="https://github.com/tkvern/wysiwyg-rails-qiniu" rel="nofollow" target="_blank" title=""&gt;wysiwyg-rails-qiniu&lt;/a&gt;，又一次造福社会。&lt;/p&gt;
&lt;h3 id="猴子补丁"&gt;猴子补丁&lt;/h3&gt;
&lt;p&gt;在使用&lt;code&gt;will_paginate&lt;/code&gt;的时候，分页的结构与样式与&lt;code&gt;Materia UI&lt;/code&gt;的风格并不相符，并且没有找到合适的 Gem，所以大胆的用起了&lt;code&gt;打开类&lt;/code&gt;的法术，并且纪录了这一过程《 &lt;a href="https://tkvern.com/20160507/%E5%85%83%E7%BC%96%E7%A8%8B%E4%B9%8B%E9%87%8D%E5%86%99will-paginate/" rel="nofollow" target="_blank" title=""&gt;为什么重写 will_paginate&lt;/a&gt; 》&lt;/p&gt;
&lt;h3 id="Pjax"&gt;Pjax&lt;/h3&gt;
&lt;p&gt;使用 Pjax 的过程相对比较顺利，在听完&lt;code&gt;Rei&lt;/code&gt;大神对&lt;code&gt;Turbolinks&lt;/code&gt;的讲解之后，还是坚定不移的使用&lt;code&gt;Pjax&lt;/code&gt;，值得注意的是在使用&lt;code&gt;WiceGrid&lt;/code&gt;的时候，会存在初始化组件问题，当时是使用&lt;code&gt;data-skip-pjax&lt;/code&gt;解决。不过现在前后端分离，前端使用 React ＋ Redux 操作 DOM 比以往轻松多了。事实上&lt;code&gt;WiceGrid&lt;/code&gt;的筛选方式对于用户并不友好。&lt;/p&gt;
&lt;h3 id="Devise 和 OmniAuth"&gt;Devise 和 OmniAuth&lt;/h3&gt;
&lt;p&gt;这两个 Gem 的使用不多，在尝试过&lt;code&gt;Devise&lt;/code&gt;之后，还是得自己手写一遍登录等功能，第三方登录开始有考虑用，后面发现还用不上就没有研究了。&lt;/p&gt;
&lt;h3 id="china_city"&gt;china_city&lt;/h3&gt;
&lt;p&gt;在使用 china_city 的时候发现一个小问题。&lt;/p&gt;
&lt;pre class="highlight coffeescript"&gt;&lt;code&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;china_city&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="vi"&gt;@&lt;/span&gt;&lt;span class="na"&gt;each&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="err"&gt;下面这一行选择&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;select&lt;/span&gt;&lt;span class="err"&gt;的时候没有限制为&lt;/span&gt;&lt;span class="nx"&gt;select&lt;/span&gt;
      &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="err"&gt;如果&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="err"&gt;有冲突会出现&lt;/span&gt;&lt;span class="nx"&gt;bug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
      &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="err"&gt;所以更正为&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="na"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'select.city-select'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nx"&gt;selects&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="na"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'.city-select'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nx"&gt;selects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;change&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="nx"&gt;jQuery&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="前端css框架"&gt;前端 css 框架&lt;/h3&gt;
&lt;p&gt;在开发中多次切换了前端技术栈。只想告诉大家，&lt;code&gt;Materia UI&lt;/code&gt;并不适合后台使用，而且与诸多的 Gem 包存在兼容问题，Rails 中大部分跟前端有关的 Gem 都是基于&lt;code&gt;Bootstrap&lt;/code&gt;。所以觉得&lt;code&gt;Bootstrap&lt;/code&gt;审美疲劳的朋友，还是继续用着吧。&lt;/p&gt;
&lt;h3 id="前端JS处理"&gt;前端 JS 处理&lt;/h3&gt;
&lt;p&gt;随着 JS 的增多，维护起来会越来越难，在 Rails 的项目中并没有做 JS 模块化，而是将 JS 用工厂模式汇集到了一起，新的功能代码会放到工厂车间去，在使用的时候 new 一个工厂，调用需要的功能即可，同时保证了可复用性。&lt;/p&gt;
&lt;h3 id="部署"&gt;部署&lt;/h3&gt;
&lt;p&gt;其实 Rails 的应用部署相对比较容易，没有太多的内容。只要注意配置文件加后缀防止被新的&lt;code&gt;commit&lt;/code&gt;覆盖就好了，一般来说，写好 shell 脚本实现一键部署也并非难事。&lt;/p&gt;
&lt;h3 id="微信支付"&gt;微信支付&lt;/h3&gt;
&lt;p&gt;现今主流的是微信支付和支付宝支付，银联的太蛋疼了。相比与微信支付，支付宝的文档真心不友好，看到吐，而且申请流程繁琐。如果你有打算在项目中使用支付宝支付，最好提前两个月做申请。虽然我不太喜欢马化腾，但是微信支付的文档我给 32 个赞，使用起来也方便。微信支付的申请流程更加透明一些，每个节点都很快。使用下面的 Gem&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'wechat'&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'wx_pay'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是也有一个问题待解决，就是在支付时取消订单，数据库状态更新，而微信支付的数据状态未更新，再进行支付的时候就会出现订单号已存在的&lt;code&gt;error&lt;/code&gt;。&lt;/p&gt;
&lt;h4 id="微信支付虚拟键盘"&gt;微信支付虚拟键盘&lt;/h4&gt;
&lt;p&gt;在便利店用过微信支付的朋友应该知道， &lt;code&gt;好近&lt;/code&gt;这样的第三方支付商的虚拟键盘。开始做虚拟键盘的时候想扒一下&lt;code&gt;好近&lt;/code&gt;的源码，奈何用微信开发调试工具根本拿不到。所以只能自己写，遇到的第一个问题就是点击事件延迟 300ms，虽说可用&lt;code&gt;Tap&lt;/code&gt;事件，被搞得不要不要的。先后尝试了&lt;code&gt;JqueryMobile.Tap&lt;/code&gt;，&lt;code&gt;FastClick&lt;/code&gt;等解决方法，仍然是在&lt;code&gt;Android&lt;/code&gt;上延迟超高，&lt;code&gt;IOS&lt;/code&gt;流畅。后面灵感闪现，我为什么要给用户一个完整的点击事件呢？一碰到就触发键盘不是可以让用户得到的反馈跟好么。索性偷懒了一把。&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;touchstart&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;&lt;span class="cm"&gt;/* do something */&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="Rails 的问题"&gt;Rails 的问题&lt;/h2&gt;
&lt;p&gt;Rails 从诞生到现在，已有经年。开发过程中最拖慢开发进度的不是需求变动，也不是技术点，使用了&lt;code&gt;assets pipeline&lt;/code&gt;的话，在调试页面的时候资源加载总是很慢。实在受不了的时候尝试了结合&lt;code&gt;NodeJS&lt;/code&gt;，用&lt;code&gt;Gulp&lt;/code&gt; &lt;code&gt;browser sync&lt;/code&gt;，来代理资源，虽说速度快超多，但不是官方集成的方案，多多少少让强迫症的人很难受。对于业务复杂的电商系统来说，Rails 标准的&lt;code&gt;Action&lt;/code&gt;肯定不够用，而自定义的写出来感觉不伦不类，可能是功夫不到家，但是没有找到更好的编程参考。其他的就是性能问题了，了解&lt;code&gt;Elixir&lt;/code&gt;的朋友应该就知道了。&lt;/p&gt;
&lt;h2 id="跟着Peter学Meteor"&gt;跟着 Peter 学 Meteor&lt;/h2&gt;
&lt;p&gt;响应&lt;code&gt;Peter&lt;/code&gt;的号召，我也全情的投入到了&lt;code&gt;Meteor&lt;/code&gt; ＋ &lt;code&gt;React&lt;/code&gt; ＋ &lt;code&gt;Redux&lt;/code&gt; 的大军中去了。虽说没用&lt;code&gt;Meteor&lt;/code&gt;做过大型项目，但是小应用做起来是得新应手了。好像也没有看到有多少大型项目用&lt;code&gt;Meteor&lt;/code&gt; + &lt;code&gt;React&lt;/code&gt; + &lt;code&gt;Redux&lt;/code&gt; 技术栈的。用上&lt;code&gt;React&lt;/code&gt;前端代码思路和结构变得清晰多了。也可以使用诸多的&lt;code&gt;React&lt;/code&gt;组件了。类似于&lt;a href="http://amazeui.org/" rel="nofollow" target="_blank" title=""&gt;Amazeui&lt;/a&gt;，&lt;a href="http://ant.design/" rel="nofollow" target="_blank" title=""&gt;Ant Design&lt;/a&gt;，这些优秀的设计，连 UI 的费用都省了。&lt;/p&gt;
&lt;h2 id="我与Elixir 和 Phoenix 不能说的秘密"&gt;我与 Elixir 和 Phoenix 不能说的秘密&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Elixir&lt;/code&gt;不用我说，相信大家都有耳闻了，函数式编程是未来。一个专业前端的 Rails 工程师切换到&lt;code&gt;Elixir&lt;/code&gt;的过程没有第一次经历的痛苦，当你接受了函数式的思想之后相当顺畅。社区里面有的人说&lt;code&gt;Phoenix&lt;/code&gt;抄&lt;code&gt;Rails&lt;/code&gt;的，我并不认同，&lt;code&gt;Phoenix&lt;/code&gt;传承了敏捷开发的思想，也为开发者提供了诸多的便利，像&lt;code&gt;Hot load&lt;/code&gt;的技术也被集成进来，对于&lt;code&gt;Socket&lt;/code&gt;的支持也是相当的好。融合&lt;code&gt;Elixir&lt;/code&gt;的特性，让多线程成为利器，利好多多，如果可以，你应该像我一样去深入研究下&lt;code&gt;Phoenix&lt;/code&gt;，还有你们常用的&lt;code&gt;Devise&lt;/code&gt;也是&lt;code&gt;Phoenix&lt;/code&gt;的作者写的。当&lt;code&gt;Rails&lt;/code&gt;老了，你还有&lt;code&gt;Phoenix&lt;/code&gt;&lt;/p&gt;
&lt;h2 id="结束语"&gt;结束语&lt;/h2&gt;
&lt;p&gt;AD：你错过了房地产，错过了网购，错过了炒股，别再错过&lt;code&gt;Elixir&lt;/code&gt; &lt;code&gt;Phoenix&lt;/code&gt; &lt;code&gt;React&lt;/code&gt; &lt;code&gt;Redux&lt;/code&gt;。
作者：本猿不才，文采平平，且读切珍惜。&lt;/p&gt;</description>
      <author>tkvern</author>
      <pubDate>Thu, 21 Jul 2016 23:40:26 +0800</pubDate>
      <link>https://ruby-china.org/topics/30594</link>
      <guid>https://ruby-china.org/topics/30594</guid>
    </item>
    <item>
      <title>[转载] 元编程之重写 will_paginate</title>
      <description>&lt;p&gt;转载地址：&lt;a href="https://tkvern.com/20160507/%E5%85%83%E7%BC%96%E7%A8%8B%E4%B9%8B%E9%87%8D%E5%86%99will-paginate/" rel="nofollow" target="_blank" title=""&gt;https://tkvern.com/20160507/元编程之重写will_paginate/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://o5zglbuyp.qnssl.com/will-paginate-bg.png" title="" alt="will-paginate-bg"&gt;&lt;/p&gt;
&lt;h2 id="为什么重写will_paginate"&gt;为什么重写 will_paginate&lt;/h2&gt;
&lt;p&gt;相信很多同学在使用&lt;code&gt;will_paginate&lt;/code&gt;的时候都会遇到这样一个问题：
自带分页样式太 LOW 了，有木有好看一点的，能不能自己定制呢。于是我们在&lt;a href="https://rubygems.org/search?utf8=%E2%9C%93&amp;amp;query=will_paginate" rel="nofollow" target="_blank" title=""&gt;RubyGems&lt;/a&gt;搜索 will_paginate 的主题 gem 包。发现有各种各样主题的，但却找不到你想要的，怎么办？&lt;/p&gt;



&lt;p&gt;本着自己动手丰衣足食的理念，我们开始动手改造&lt;code&gt;will_paginate&lt;/code&gt;。
（注：笔者使用的是&lt;code&gt;Materialize&lt;/code&gt;的前端框架，下文将以&lt;code&gt;Materialize&lt;/code&gt;的分页为例）&lt;/p&gt;
&lt;h2 id="预览效果"&gt;预览效果&lt;/h2&gt;
&lt;p&gt;先来看看&lt;code&gt;will_paginate&lt;/code&gt;默认的效果是怎么样？为了方便后续区分，默认效果叫&lt;code&gt;Old&lt;/code&gt;，修改后效果叫&lt;code&gt;New&lt;/code&gt;
&lt;img src="https://o5zglbuyp.qnssl.com/will-paginate-pagelist.png" title="" alt="will-paginate-pagelist"&gt;
上图中的&lt;code&gt;Old&lt;/code&gt;分页稍显简陋。&lt;/p&gt;

&lt;p&gt;下图是修改后需要&lt;code&gt;New&lt;/code&gt;的效果
&lt;img src="https://o5zglbuyp.qnssl.com/will-paginate-materiaizepg.png" title="" alt="will-paginate-materiaizepg"&gt;&lt;/p&gt;
&lt;h2 id="分析结构"&gt;分析结构&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Old&lt;/code&gt;代码结构&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pagination"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"previous_page"&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"prev"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/admins/admins?page=5"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;← Previous&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"start"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/admins/admins?page=1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;1&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/admins/admins?page=2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;2&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/admins/admins?page=3"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;3&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt; 
    &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/admins/admins?page=4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;4&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"prev"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/admins/admins?page=5"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;5&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;em&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"current"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;6&lt;span class="nt"&gt;&amp;lt;/em&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"next"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/admins/admins?page=7"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;7&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/admins/admins?page=8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;8&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/admins/admins?page=9"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;9&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/admins/admins?page=10"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;10&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"gap"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;…&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/admins/admins?page=24"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;24&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/admins/admins?page=25"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;25&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"next_page"&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"next"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/admins/admins?page=7"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Next →&lt;span class="nt"&gt;&amp;lt;/a&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;从代码结构中可以知道，共有 5 种形式 DOM:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;previous_page&lt;/li&gt;
&lt;li&gt;next_page&lt;/li&gt;
&lt;li&gt;current&lt;/li&gt;
&lt;li&gt;gap&lt;/li&gt;
&lt;li&gt;default&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;了解结构后，需要将&lt;code&gt;Old&lt;/code&gt;修改成下面的结构才能有&lt;code&gt;New&lt;/code&gt;的效果&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt; &lt;span class="nt"&gt;&amp;lt;ul&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pagination"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"disabled"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#!"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;i&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"material-icons"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;chevron_left&lt;span class="nt"&gt;&amp;lt;/i&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"active"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#!"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;1&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"waves-effect"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#!"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;2&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"waves-effect"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#!"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;3&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"waves-effect"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#!"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;4&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"waves-effect"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#!"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;5&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"waves-effect"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#!"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;i&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"material-icons"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;chevron_right&lt;span class="nt"&gt;&amp;lt;/i&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="分析will_paginate源码"&gt;分析&lt;code&gt;will_paginate&lt;/code&gt;源码&lt;/h2&gt;
&lt;p&gt;将&lt;a href="https://github.com/mislav/will_paginate" rel="nofollow" target="_blank" title=""&gt;will_paginate&lt;/a&gt;的源码&lt;code&gt;Clone&lt;/code&gt;到本地。
进入&lt;em&gt;&lt;code&gt;lib&lt;/code&gt;&lt;/em&gt;目录下，这里就不介绍&lt;code&gt;will_paginate&lt;/code&gt;到源码结构了，有时间自己看看。我们直奔主题，打开&lt;em&gt;&lt;code&gt;link_renderer.rb&lt;/code&gt;&lt;/em&gt;文件。我在里面添加了部分代码中文解释，对于修改结构已经够用了
&lt;em&gt;&lt;code&gt;lib/will_paginate/view_helpers/link_renderer.rb&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'cgi'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'will_paginate/core_ext'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'will_paginate/view_helpers'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'will_paginate/view_helpers/link_renderer_base'&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;WillPaginate&lt;/span&gt;
  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;ViewHelpers&lt;/span&gt;
    &lt;span class="c1"&gt;# This class does the heavy lifting of actually building the pagination&lt;/span&gt;
    &lt;span class="c1"&gt;# links. It is used by +will_paginate+ helper internally.&lt;/span&gt;
    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LinkRenderer&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;LinkRendererBase&lt;/span&gt;

      &lt;span class="c1"&gt;# * +collection+ is a WillPaginate::Collection instance or any other object&lt;/span&gt;
      &lt;span class="c1"&gt;#   that conforms to that API&lt;/span&gt;
      &lt;span class="c1"&gt;# * +options+ are forwarded from +will_paginate+ view helper&lt;/span&gt;
      &lt;span class="c1"&gt;# * +template+ is the reference to the template being rendered&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="vi"&gt;@template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;template&lt;/span&gt;
        &lt;span class="vi"&gt;@container_attributes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@base_url_params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="c1"&gt;# Process it! This method returns the complete HTML string which contains&lt;/span&gt;
      &lt;span class="c1"&gt;# pagination links. Feel free to subclass LinkRenderer and change this&lt;/span&gt;
      &lt;span class="c1"&gt;# method as you see fit.&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;to_html&lt;/span&gt;
        &lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pagination&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&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;item&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
          &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_a?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Fixnum&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt;
            &lt;span class="n"&gt;page_number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nb"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;end&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="vi"&gt;@options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:link_separator&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

        &lt;span class="vi"&gt;@options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:container&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;html_container&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="c1"&gt;# Returns the subset of +options+ this instance was initialized with that&lt;/span&gt;
      &lt;span class="c1"&gt;# represent HTML attributes for the container element of pagination links.&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;container_attributes&lt;/span&gt;
        &lt;span class="vi"&gt;@container_attributes&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="vi"&gt;@options.except&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ViewHelpers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pagination_options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:renderer&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:class&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="kp"&gt;protected&lt;/span&gt;

      &lt;span class="c1"&gt;# page_number方法显示分页元素&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;page_number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;current_page&lt;/span&gt;
          &lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:rel&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;rel_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;
          &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:em&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:class&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'current'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="c1"&gt;# gap方法在页数超过设定值时用...代替&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;gap&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@template.will_paginate_translate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:page_gap&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s1"&gt;'&amp;amp;hellip;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="sx"&gt;%(&amp;lt;span class="gap"&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sx"&gt;&amp;lt;/span&amp;gt;)&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="c1"&gt;# previous_page方法显示上一页&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;previous_page&lt;/span&gt;
        &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@collection.current_page&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="vi"&gt;@collection.current_page&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;previous_or_next_page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:previous_label&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s1"&gt;'previous_page'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="c1"&gt;# next_page方法显示下一页&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;next_page&lt;/span&gt;
        &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@collection.current_page&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;total_pages&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="vi"&gt;@collection.current_page&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;previous_or_next_page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:next_label&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s1"&gt;'next_page'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="c1"&gt;# 边界值按钮&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;previous_or_next_page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;classname&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;page&lt;/span&gt;
          &lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:class&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;classname&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;
          &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:span&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:class&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;classname&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;' disabled'&lt;/span&gt;&lt;span class="p"&gt;)&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;def&lt;/span&gt; &lt;span class="nf"&gt;html_container&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:div&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;container_attributes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="c1"&gt;# Returns URL params for +page_link_or_span+, taking the current GET params&lt;/span&gt;
      &lt;span class="c1"&gt;# and &amp;lt;tt&amp;gt;:params&amp;lt;/tt&amp;gt; option into account.&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;NotImplementedError&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="kp"&gt;private&lt;/span&gt;

      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;param_name&lt;/span&gt;
        &lt;span class="vi"&gt;@options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:param_name&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="c1"&gt;# 私有方法, 构建a标签&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attributes&lt;/span&gt; &lt;span class="o"&gt;=&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;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_a?&lt;/span&gt; &lt;span class="no"&gt;Fixnum&lt;/span&gt;
          &lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:rel&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rel_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
        &lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:href&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;
        &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="c1"&gt;# 私有方法, 包裹标签&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attributes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
        &lt;span class="n"&gt;string_attributes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inject&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="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pair&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
          &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;pair&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nil?&lt;/span&gt;
            &lt;span class="n"&gt;attrs&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sx"&gt;%( &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;pair&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sx"&gt;="&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;CGI&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;escapeHTML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pair&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sx"&gt;")&lt;/span&gt;
          &lt;span class="k"&gt;end&lt;/span&gt;
          &lt;span class="n"&gt;attrs&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
        &lt;span class="s2"&gt;"&amp;lt;&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}#{&lt;/span&gt;&lt;span class="n"&gt;string_attributes&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;"&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;rel_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;
        &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="vi"&gt;@collection.current_page&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="s1"&gt;'prev'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;' start'&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="k"&gt;when&lt;/span&gt; &lt;span class="vi"&gt;@collection.current_page&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="s1"&gt;'next'&lt;/span&gt;
        &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="s1"&gt;'start'&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;def&lt;/span&gt; &lt;span class="nf"&gt;symbolized_update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;other&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;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
          &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_sym&lt;/span&gt;
          &lt;span class="n"&gt;existing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&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;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_a?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_a?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nil?&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;symbolized_update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;existing&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}),&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="k"&gt;else&lt;/span&gt;
            &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&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;end&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="打开类"&gt;打开类&lt;/h2&gt;
&lt;p&gt;通过分析我们已经了解需要修改哪些方法&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;page_number&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;previous_page&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;next_page&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;previous_or_next_page&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;同时我们还将使用两个私有方法 &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;link(text, target, attributes = {})&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tag(name, value, attributes = {})&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;回到工作项目，新建文件。下面使用了元编程的法术——打开类。这也是作为动态语言的优点。修改过的地方我加了注释。
&lt;em&gt;&lt;code&gt;lib/materialize_renderer.rb&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'cgi'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'will_paginate/core_ext'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'will_paginate/view_helpers'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'will_paginate/view_helpers/link_renderer_base'&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;WillPaginate&lt;/span&gt;
  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;ViewHelpers&lt;/span&gt;
    &lt;span class="c1"&gt;# This class does the heavy lifting of actually building the pagination&lt;/span&gt;
    &lt;span class="c1"&gt;# links. It is used by +will_paginate+ helper internally.&lt;/span&gt;
    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LinkRenderer&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;LinkRendererBase&lt;/span&gt;

      &lt;span class="c1"&gt;# * +collection+ is a WillPaginate::Collection instance or any other object&lt;/span&gt;
      &lt;span class="c1"&gt;#   that conforms to that API&lt;/span&gt;
      &lt;span class="c1"&gt;# * +options+ are forwarded from +will_paginate+ view helper&lt;/span&gt;
      &lt;span class="c1"&gt;# * +template+ is the reference to the template being rendered&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="vi"&gt;@template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;template&lt;/span&gt;
        &lt;span class="vi"&gt;@container_attributes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@base_url_params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="c1"&gt;# Process it! This method returns the complete HTML string which contains&lt;/span&gt;
      &lt;span class="c1"&gt;# pagination links. Feel free to subclass LinkRenderer and change this&lt;/span&gt;
      &lt;span class="c1"&gt;# method as you see fit.&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;to_html&lt;/span&gt;
        &lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pagination&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&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;item&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
          &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_a?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Fixnum&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt;
            &lt;span class="n"&gt;page_number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nb"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;end&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="vi"&gt;@options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:link_separator&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

        &lt;span class="vi"&gt;@options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:container&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;html_container&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="c1"&gt;# Returns the subset of +options+ this instance was initialized with that&lt;/span&gt;
      &lt;span class="c1"&gt;# represent HTML attributes for the container element of pagination links.&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;container_attributes&lt;/span&gt;
        &lt;span class="vi"&gt;@container_attributes&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="vi"&gt;@options.except&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ViewHelpers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pagination_options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:renderer&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:class&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="kp"&gt;protected&lt;/span&gt;

      &lt;span class="c1"&gt;# 修改后，我使用私有方法tag，在link外面套了一层li，同时修改了class属性&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;page_number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;current_page&lt;/span&gt;
          &lt;span class="c1"&gt;# link(page, page, :rel =&amp;gt; rel_value(page))&lt;/span&gt;
          &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="ss"&gt;:li&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:rel&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;rel_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="ss"&gt;:class&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'waves-effect'&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;
          &lt;span class="c1"&gt;# tag(:em, page, :class =&amp;gt; 'current')&lt;/span&gt;
          &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:li&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&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="ss"&gt;:rel&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;rel_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="ss"&gt;:class&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'active'&lt;/span&gt;&lt;span class="p"&gt;)&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;def&lt;/span&gt; &lt;span class="nf"&gt;gap&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@template.will_paginate_translate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:page_gap&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s1"&gt;'&amp;amp;hellip;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="sx"&gt;%(&amp;lt;span class="gap"&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sx"&gt;&amp;lt;/span&amp;gt;)&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="c1"&gt;# 这里没有修改全局变量@options，使用打开类最好不要修改全局变量。所以直接改了icon&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;previous_page&lt;/span&gt;
        &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@collection.current_page&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="vi"&gt;@collection.current_page&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="c1"&gt;# previous_or_next_page(num, @options[:previous_label], 'previous_page')&lt;/span&gt;
        &lt;span class="n"&gt;previous_or_next_page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'chevron_left'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'previous_page'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;


      &lt;span class="c1"&gt;# 这里没有修改全局变量@options，使用打开类最好不要修改全局变量。所以直接改了icon&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;next_page&lt;/span&gt;
        &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@collection.current_page&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;total_pages&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="vi"&gt;@collection.current_page&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="c1"&gt;# previous_or_next_page(num, @options[:next_label], 'next_page')&lt;/span&gt;
        &lt;span class="n"&gt;previous_or_next_page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'chevron_right'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'next_page'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="c1"&gt;# 修改了边界值的按钮，增加了局部变量icon_item用于google icon&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;previous_or_next_page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;classname&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;icon_item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="ss"&gt;:i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:class&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'material-icons'&lt;/span&gt; 
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;
          &lt;span class="c1"&gt;# link(text, page, :class =&amp;gt; classname)&lt;/span&gt;
          &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:li&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;icon_item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="ss"&gt;:class&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'waves-effect'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;
          &lt;span class="c1"&gt;# tag(:span, text, :class =&amp;gt; classname + ' disabled')&lt;/span&gt;
          &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:li&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;icon_item&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="ss"&gt;:class&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'disabled'&lt;/span&gt;&lt;span class="p"&gt;)&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;def&lt;/span&gt; &lt;span class="nf"&gt;html_container&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:div&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;container_attributes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="c1"&gt;# Returns URL params for +page_link_or_span+, taking the current GET params&lt;/span&gt;
      &lt;span class="c1"&gt;# and &amp;lt;tt&amp;gt;:params&amp;lt;/tt&amp;gt; option into account.&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;NotImplementedError&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="kp"&gt;private&lt;/span&gt;

      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;param_name&lt;/span&gt;
        &lt;span class="vi"&gt;@options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:param_name&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;to_s&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;link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attributes&lt;/span&gt; &lt;span class="o"&gt;=&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;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_a?&lt;/span&gt; &lt;span class="no"&gt;Fixnum&lt;/span&gt;
          &lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:rel&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rel_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
        &lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:href&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;
        &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;)&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;tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attributes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
        &lt;span class="n"&gt;string_attributes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inject&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="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pair&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
          &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;pair&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nil?&lt;/span&gt;
            &lt;span class="n"&gt;attrs&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sx"&gt;%( &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;pair&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sx"&gt;="&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;CGI&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;escapeHTML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pair&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sx"&gt;")&lt;/span&gt;
          &lt;span class="k"&gt;end&lt;/span&gt;
          &lt;span class="n"&gt;attrs&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
        &lt;span class="s2"&gt;"&amp;lt;&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}#{&lt;/span&gt;&lt;span class="n"&gt;string_attributes&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;"&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;rel_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;
        &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="vi"&gt;@collection.current_page&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="s1"&gt;'prev'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;' start'&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="k"&gt;when&lt;/span&gt; &lt;span class="vi"&gt;@collection.current_page&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="s1"&gt;'next'&lt;/span&gt;
        &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="s1"&gt;'start'&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;def&lt;/span&gt; &lt;span class="nf"&gt;symbolized_update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;other&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;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
          &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_sym&lt;/span&gt;
          &lt;span class="n"&gt;existing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&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;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_a?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_a?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nil?&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;symbolized_update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;existing&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}),&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="k"&gt;else&lt;/span&gt;
            &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&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;end&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="加入到initializers"&gt;加入到&lt;code&gt;initializers&lt;/code&gt;
&lt;/h2&gt;
&lt;p&gt;完成上面的修改后，是不起作用的，还需要加入到&lt;code&gt;initializers&lt;/code&gt;中，才会加载我们的打开类，新建文件
&lt;em&gt;&lt;code&gt;config/initializers/will_pagination_materialize.rb&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'materialize_renderer'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="完成"&gt;完成&lt;/h2&gt;
&lt;p&gt;完成这些操作之后，重启服务器。恭喜你，完成了对&lt;code&gt;will_paginate&lt;/code&gt;的修改。看看&lt;code&gt;New&lt;/code&gt;吧
&lt;img src="https://o5zglbuyp.qnssl.com/will-paginate-done.png" title="" alt="will-paginate-done"&gt;&lt;/p&gt;</description>
      <author>tkvern</author>
      <pubDate>Sun, 08 May 2016 17:13:47 +0800</pubDate>
      <link>https://ruby-china.org/topics/29943</link>
      <guid>https://ruby-china.org/topics/29943</guid>
    </item>
    <item>
      <title>Rails + PostgreSQL 常见问题及解决办法</title>
      <description>&lt;h2 id="No pg_config..."&gt;No pg_config...&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;问题重现&lt;/em&gt;: 在&lt;code&gt;bundle&lt;/code&gt;的时候出现 gem 包&lt;code&gt;pg-0.18.4&lt;/code&gt;安装出错的情况，错误代码如下：&lt;/p&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;bundle
&lt;span class="c"&gt;.
.
.
&lt;/span&gt;&lt;span class="go"&gt;Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

&amp;nbsp; &amp;nbsp; current directory: /home/vagrant/.rvm/gems/ruby-2.3.0/gems/pg-0.18.4/ext
/home/vagrant/.rvm/rubies/ruby-2.3.0/bin/ruby -r ./siteconf20160415-7139-1cu08ba.rb extconf.rb
checking for pg_config... no
No pg_config... trying anyway. If building fails, please try again with
&amp;nbsp;--with-pg-config=/path/to/pg_config
checking for libpq-fe.h... no
Can't find the 'libpq-fe.h header
*** extconf.rb failed ***
Could not create Makefile due to some reason, probably lack of necessary
libraries and/or headers.&amp;nbsp; Check the mkmf.log file for more details.&amp;nbsp; You may
need configuration options.

Provided configuration options:
 --with-opt-dir
 --without-opt-dir
 --with-opt-include
&lt;/span&gt;&lt;span class="gp"&gt; --without-opt-include=$&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;opt-dir&lt;span class="o"&gt;}&lt;/span&gt;/include
&lt;span class="go"&gt; --with-opt-lib
&lt;/span&gt;&lt;span class="gp"&gt; --without-opt-lib=$&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;opt-dir&lt;span class="o"&gt;}&lt;/span&gt;/lib
&lt;span class="go"&gt; --with-make-prog
 --without-make-prog
 --srcdir=.
 --curdir
&lt;/span&gt;&lt;span class="gp"&gt; --ruby=/home/vagrant/.rvm/rubies/ruby-2.3.0/bin/$&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;RUBY_BASE_NAME&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="go"&gt; --with-pg
 --without-pg
 --enable-windows-cross
 --disable-windows-cross
 --with-pg-config
 --without-pg-config
 --with-pg_config
 --without-pg_config
 --with-pg-dir
 --without-pg-dir
 --with-pg-include
&lt;/span&gt;&lt;span class="gp"&gt; --without-pg-include=$&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;pg-dir&lt;span class="o"&gt;}&lt;/span&gt;/include
&lt;span class="go"&gt; --with-pg-lib
&lt;/span&gt;&lt;span class="gp"&gt; --without-pg-lib=$&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;pg-dir&lt;span class="o"&gt;}&lt;/span&gt;/lib
&lt;span class="go"&gt;
To see why this extension failed to compile, please check the mkmf.log which can be found here:

&amp;nbsp; /home/vagrant/.rvm/gems/ruby-2.3.0/extensions/x86_64-linux/2.3.0/pg-0.18.4/mkmf.log

extconf failed, exit code 1

Gem files will remain installed in /home/vagrant/.rvm/gems/ruby-2.3.0/gems/pg-0.18.4 for inspection.
Results logged to /home/vagrant/.rvm/gems/ruby-2.3.0/extensions/x86_64-linux/2.3.0/pg-0.18.4/gem_make.out
&lt;/span&gt;&lt;span class="c"&gt;.
.
.
&lt;/span&gt;&lt;span class="go"&gt;An error occurred while installing pg (0.18.4), and Bundler cannot continue.
Make sure that `gem install pg -v '0.18.4'` succeeds before bundling.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;解决方案&lt;/em&gt;: 先不要急着按提示去执行，出现这个问题可能是你没有安装 PostgreSQL 或是没有指定 pgsql 的路径。如果没有安装 PostgreSQL，点击&lt;a href="http://www.postgresql.org/download/" rel="nofollow" target="_blank" title=""&gt;传送门&lt;/a&gt;去安装 (注意：执行&lt;code&gt;yum install postgresql-server&lt;/code&gt;后继续往下看文档安装 pgsql 的扩展，建议最好安装的 PostgreSQL 是 9.X 以上版本，否则许多新特性无法使用)。英文不太好的同学可以到这篇博客&lt;a href="http://www.ruanyifeng.com/blog/2013/12/getting_started_with_postgresql.html" rel="nofollow" target="_blank" title=""&gt;PostgreSQL 新手入门&lt;/a&gt;看看。
步骤 1:
安装&lt;code&gt;libpq-dev&lt;/code&gt;包
Ubuntu 执行以下命令：&lt;/p&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;apt-get &lt;span class="nb"&gt;install &lt;/span&gt;libpq-dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;CentOS/RetH执行以下命令：&lt;/p&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;yum &lt;span class="nb"&gt;install &lt;/span&gt;postgresql-devel
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;步骤 2:
找到你的 pgsql 安装目录
我的是在&lt;code&gt;/usr/pgsql-9.4&lt;/code&gt;，也有可能会在&lt;code&gt;/usr/local/pgsql&lt;/code&gt;，因系统而异。&lt;/p&gt;

&lt;p&gt;步骤 3:
&lt;code&gt;with-pg-config&lt;/code&gt;后面接的就是 pgsql 目录下的 pg_config 文件，注意&lt;code&gt;--with-pg-config&lt;/code&gt;前面还有两个&lt;code&gt;-&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ gem install pg -v '0.18.4' -- --with-pg-config=/usr/pgsql-9.4/bin/pg_config
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;步骤 4:
重新执行&lt;code&gt;bundle&lt;/code&gt;命令&lt;/p&gt;
&lt;h2 id="无法连接pgsql"&gt;无法连接 pgsql&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;问题重现&lt;/em&gt;: 运行&lt;code&gt;rails s -b 0.0.0.0 -p 3000&lt;/code&gt;后，在浏览器打开项目首页出现下面问题&lt;/p&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;PG::ConnectionBad (FATAL:&amp;nbsp; Ident authentication failed for user "postgres"
):
&amp;nbsp; activerecord (4.2.3) lib/active_record/connection_adapters/postgresql_adapter.rb:655:in `initialize'
&amp;nbsp; activerecord (4.2.3) lib/active_record/connection_adapters/postgresql_adapter.rb:655:in `new'
&amp;nbsp; activerecord (4.2.3) lib/active_record/connection_adapters/postgresql_adapter.rb:655:in `connect'
&amp;nbsp; activerecord (4.2.3) lib/active_record/connection_adapters/postgresql_adapter.rb:242:in `initialize'
&amp;nbsp; activerecord-postgis-adapter (3.1.0) lib/active_record/connection_adapters/postgis_adapter.rb:51:in `initialize'
&lt;/span&gt;&lt;span class="c"&gt;.
.
.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;解决方案&lt;/em&gt;: 
找到 &lt;code&gt;pg_hba.conf&lt;/code&gt;文件，一般是在&lt;code&gt;/var/lib/pgsql/data&lt;/code&gt;目录下，如果修改后不生效看看&lt;code&gt;/var/lib/pgsql&lt;/code&gt;目录下是否还有其他的数据目录。因系统环境而异。
使用&lt;code&gt;vim&lt;/code&gt;或&lt;code&gt;vi&lt;/code&gt;打开
步骤 1:&lt;/p&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vim /var/lib/pgsql/data/pg_hba.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;步骤 2:
按住&lt;code&gt;shift&lt;/code&gt; + &lt;code&gt;g&lt;/code&gt; 将光标定位的文件底部，按&lt;code&gt;i&lt;/code&gt;进入编辑模式，修改 METHOP 为 md5 验证。
完成后按&lt;code&gt;shift&lt;/code&gt; + &lt;code&gt;:&lt;/code&gt; 进入命令模式，输入&lt;code&gt;wq&lt;/code&gt;完成编辑。
下面给出修改后效果&lt;/p&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;.
.
.
&lt;span class="c"&gt;# TYPE&amp;nbsp; DATABASE&amp;nbsp; &amp;nbsp; USER&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; CIDR-ADDRESS&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; METHOD
&lt;/span&gt;
&lt;span class="c"&gt;# "local" is for Unix domain socket connections only
&lt;/span&gt;&lt;span class="n"&gt;local&lt;/span&gt; &amp;nbsp; &lt;span class="n"&gt;all&lt;/span&gt; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &lt;span class="n"&gt;all&lt;/span&gt; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &lt;span class="n"&gt;md5&lt;/span&gt;
&lt;span class="c"&gt;# IPv4 local connections:
&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&amp;nbsp; &amp;nbsp; &lt;span class="n"&gt;all&lt;/span&gt; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &lt;span class="n"&gt;all&lt;/span&gt; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &lt;span class="m"&gt;127&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;1&lt;/span&gt;/&lt;span class="m"&gt;32&lt;/span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &lt;span class="n"&gt;md5&lt;/span&gt;
&lt;span class="c"&gt;# IPv6 local connections:
&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&amp;nbsp; &amp;nbsp; &lt;span class="n"&gt;all&lt;/span&gt; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &lt;span class="n"&gt;all&lt;/span&gt; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; ::&lt;span class="m"&gt;1&lt;/span&gt;/&lt;span class="m"&gt;128&lt;/span&gt; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &lt;span class="n"&gt;md5&lt;/span&gt;
&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;步骤 3:
重启&lt;code&gt;postgresql&lt;/code&gt;服务&lt;/p&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;service postgresql restart
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="type "&gt;type "json" does not exist&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;问题重现&lt;/em&gt;: 执行&lt;code&gt;rake db:migrate&lt;/code&gt;时出现错误，错误代码如下：&lt;/p&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;rake db:migrate
&lt;span class="c"&gt;.
.
.
&lt;/span&gt;&lt;span class="go"&gt;== 20151208044806 CreateShops: migrating ======================================
-- create_table(:shops)
rake aborted!
StandardError: An error has occurred, this and all later migrations canceled:

PG::UndefinedObject: ERROR:&amp;nbsp; type "json" does not exist
LINE 1: ...ying NOT NULL, "logo" character varying, "images" json, "reg...
&lt;/span&gt;&lt;span class="c"&gt;.
.
.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;解决方案&lt;/em&gt;：出现这种问题大多是因为安装了老版的 PostgreSQL，在 CentOS 上面执行&lt;code&gt;yum install postgresql&lt;/code&gt;默认是 8.X 版本。升级版本即可。&lt;/p&gt;

&lt;p&gt;步骤 1:删除旧版 postgresql&lt;/p&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;yum remove postgresql&lt;span class="k"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;步骤 2:更新 yum&lt;/p&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;yum update
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;步骤 3: 到&lt;a href="http://yum.pgrpms.org/reporpms/" rel="nofollow" target="_blank" title=""&gt;http://yum.pgrpms.org/reporpms/&lt;/a&gt;选择 9.X 以上版本下载相应的 rpm 包&lt;/p&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;wget https://download.postgresql.org/pub/repos/yum/9.4/redhat/rhel-6-x86_64/pgdg-centos94-9.4-2.noarch.rpm
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;步骤 4:使用下载好的 rpm 包&lt;/p&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;rpm &lt;span class="nt"&gt;-ivh&lt;/span&gt; pgdg-centos94-9.4-2.noarch.rpm
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;步骤 5:安装&lt;code&gt;postgresql94-server&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;yum -y install postgresql94-server
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;步骤 6:重新启动&lt;code&gt;postgresql-94&lt;/code&gt;服务&lt;/p&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;service postgresql-9.4 start
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="type "&gt;type "geography" does not exist&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;问题重现&lt;/em&gt;:执行&lt;code&gt;rake db:migrate&lt;/code&gt;时出现错误，错误代码如下：&lt;/p&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;rake db:migrate
&lt;span class="c"&gt;.
.
.
&lt;/span&gt;&lt;span class="go"&gt;rake aborted!
StandardError: An error has occurred, this and all later migrations canceled:

PG::UndefinedObject: ERROR:&amp;nbsp; type "geography" does not exist
LINE 1: ... "address" character varying NOT NULL, "location" geography(...
&lt;/span&gt;&lt;span class="c"&gt;.
.
.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;解决方案&lt;/em&gt;: 这是由于没有安装支持&lt;code&gt;geography&lt;/code&gt;类型数据的扩展，笔者使用的是 PostgreSQL-9.4 版本，这里给出 9.X 版本的解决方案。为了后续用到其他扩展方便，这里也就一起安装了。&lt;/p&gt;

&lt;p&gt;步骤 1: &lt;code&gt;list&lt;/code&gt;命令查看&lt;code&gt;postgresql&lt;/code&gt;有哪些扩展，当你看到下面效果说明你的 yum 库中有这些扩展，如果没有请到&lt;a href="http://yum.pgrpms.org/reporpms/" rel="nofollow" target="_blank" title=""&gt;http://yum.pgrpms.org/reporpms/&lt;/a&gt;选择 9.X 以上版本下载相应的 rpm 包安装。如果不需要请跳过步骤 1，步骤 2&lt;/p&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;yum list postgresql94-&lt;span class="k"&gt;*&lt;/span&gt;
&lt;span class="go"&gt;已加载插件：fastestmirror
Repository pgdg94 is listed more than once in the configuration
Repository pgdg94-source is listed more than once in the configuration
Loading mirror speeds from cached hostfile
&amp;nbsp;* base: mirrors.yun-idc.com
&amp;nbsp;* extras: mirrors.yun-idc.com
&amp;nbsp;* updates: mirrors.yun-idc.com
已安装的软件包
postgresql94.x86_64 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 9.4.7-1PGDG.rhel6&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; @pgdg94
postgresql94-libs.x86_64&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 9.4.7-1PGDG.rhel6&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; @pgdg94
postgresql94-server.x86_64&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 9.4.7-1PGDG.rhel6&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; @pgdg94
可安装的软件包
postgresql94-contrib.x86_64 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 9.4.7-1PGDG.rhel6&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; pgdg94&amp;nbsp;
postgresql94-debuginfo.x86_64 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 9.4.7-1PGDG.rhel6&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; pgdg94&amp;nbsp;
postgresql94-devel.x86_64 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 9.4.7-1PGDG.rhel6&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; pgdg94&amp;nbsp;
postgresql94-docs.x86_64&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 9.4.7-1PGDG.rhel6&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; pgdg94&amp;nbsp;
postgresql94-jdbc.x86_64&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 9.3.1101-1PGDG.rhel6 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; pgdg94&amp;nbsp;
postgresql94-jdbc-debuginfo.x86_64&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 9.3.1101-1PGDG.rhel6 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; pgdg94&amp;nbsp;
postgresql94-odbc.x86_64&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 09.03.0400-1PGDG.rhel6 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; pgdg94&amp;nbsp;
postgresql94-odbc-debuginfo.x86_64&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 09.03.0400-1PGDG.rhel6 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; pgdg94&amp;nbsp;
postgresql94-plperl.x86_64&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 9.4.7-1PGDG.rhel6&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; pgdg94&amp;nbsp;
postgresql94-plpython.x86_64&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 9.4.7-1PGDG.rhel6&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; pgdg94&amp;nbsp;
postgresql94-pltcl.x86_64 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 9.4.7-1PGDG.rhel6&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; pgdg94&amp;nbsp;
postgresql94-python.x86_64&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 4.2-1PGDG.rhel6&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; pgdg94&amp;nbsp;
postgresql94-python-debuginfo.x86_64&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 4.2-1PGDG.rhel6&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; pgdg94&amp;nbsp;
postgresql94-tcl.x86_64 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 2.1.1-1.rhel6&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; pgdg94&amp;nbsp;
postgresql94-tcl-debuginfo.x86_64 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 2.1.1-1.rhel6&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; pgdg94&amp;nbsp;
postgresql94-test.x86_64 &amp;nbsp; &amp;nbsp; &amp;nbsp;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;步骤 2:安装扩展&lt;/p&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;yum &lt;span class="nb"&gt;install &lt;/span&gt;postgresql94-&lt;span class="k"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;步骤 3: 前往&lt;a href="http://postgis.net/source/" rel="nofollow" target="_blank" title=""&gt;postgis&lt;/a&gt;安装扩展。
友情提示：自行编译源码的话，如果系统编译环境不完全，会折腾很久，建议直接用 yum 安装。推荐一篇文章&lt;a href="http://www.icanx.cn/p/13" rel="nofollow" target="_blank" title=""&gt;&lt;/a&gt;&lt;a href="http://www.icanx.cn/p/13" rel="nofollow" target="_blank" title=""&gt;centos 安装 postgis
&lt;/a&gt;。
作者是为 PostgreSQL 源加上 EPEL 源，直接 yum 安装，无痛解决依赖问题。抓狂的同学速度 get。如果依然报错，请执行&lt;code&gt;rake db:drop&lt;/code&gt;，然后再创建一次数据库就行了。&lt;/p&gt;</description>
      <author>tkvern</author>
      <pubDate>Sun, 17 Apr 2016 18:32:38 +0800</pubDate>
      <link>https://ruby-china.org/topics/29750</link>
      <guid>https://ruby-china.org/topics/29750</guid>
    </item>
    <item>
      <title>wysiwyg-rails 编辑器支援七牛云存储</title>
      <description>&lt;h2 id="wysiwyg-rails-qiniu"&gt;wysiwyg-rails-qiniu&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/tkvern/wysiwyg-rails-qiniu" rel="nofollow" target="_blank" title=""&gt;wysiwyg-rails-qiniu&lt;/a&gt; 是基于 &lt;a href="https://rubygems.org/gems/wysiwyg-rails" rel="nofollow" target="_blank" title=""&gt;wysiwyg-rails&lt;/a&gt;制作，上传的资源直接支持七牛云存储
在使用该 gem 包前，你得会使用七牛云存储
传送门：&lt;a href="https://ruby-china.org/topics/29010" title=""&gt;Rails 进阶—— 云存储实战&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="安装"&gt;安装&lt;/h2&gt;
&lt;p&gt;添加下面代码到你的 Gemfile:&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;"wysiwyg-rails-qiniu"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后运行 &lt;code&gt;bundle install&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="将资源文件加入到你的 assets目录"&gt;将资源文件加入到你的 &lt;code&gt;assets&lt;/code&gt;目录&lt;/h2&gt;
&lt;p&gt;在你的 &lt;code&gt;application.css&lt;/code&gt; 文件中，引入下列文件：&lt;/p&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/*
 *= require froala_editor.min.css
 *= require froala_style.min.css
 *= require font-awesome
 */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果你想使用黑色主题，你可以加入 &lt;code&gt;themes/dark.min.css&lt;/code&gt; 到&lt;code&gt;application.css&lt;/code&gt;文件中。&lt;/p&gt;

&lt;p&gt;在你的 &lt;code&gt;application.js&lt;/code&gt;, 引入以下文件：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;//&lt;/span&gt;
&lt;span class="c1"&gt;//= require froala_editor.min.js&lt;/span&gt;
&lt;span class="c1"&gt;//&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果你想使用更多功能插件 &lt;a href="https://froala.com/wysiwyg-editor/docs/plugins" rel="nofollow" target="_blank" title=""&gt;Available Plugins&lt;/a&gt;, 你应该将下面的这些文件加入的 &lt;code&gt;application.js&lt;/code&gt; 和 &lt;code&gt;application.css&lt;/code&gt;.
其中&lt;code&gt;support_qiniu.min.js&lt;/code&gt;是必须要引入的，否则无法使用七牛云存储&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Include other plugins.&lt;/span&gt;
&lt;span class="c1"&gt;//= require plugins/support_qiniu.min.js&lt;/span&gt;
&lt;span class="c1"&gt;//= require plugins/align.min.js&lt;/span&gt;
&lt;span class="c1"&gt;//= require plugins/char_counter.min.js&lt;/span&gt;
&lt;span class="c1"&gt;//= require plugins/code_beautifier.min.js&lt;/span&gt;
&lt;span class="c1"&gt;//= require plugins/code_view.min.js&lt;/span&gt;
&lt;span class="c1"&gt;//= require plugins/colors.min.js&lt;/span&gt;
&lt;span class="c1"&gt;//= require plugins/emoticons.min.js&lt;/span&gt;
&lt;span class="c1"&gt;//= require plugins/entities.min.js&lt;/span&gt;
&lt;span class="c1"&gt;//= require plugins/file.min.js&lt;/span&gt;
&lt;span class="c1"&gt;//= require plugins/font_family.min.js&lt;/span&gt;
&lt;span class="c1"&gt;//= require plugins/font_size.min.js&lt;/span&gt;
&lt;span class="c1"&gt;//= require plugins/fullscreen.min.js&lt;/span&gt;
&lt;span class="c1"&gt;//= require plugins/image.min.js&lt;/span&gt;
&lt;span class="c1"&gt;//= require plugins/image_manager.min.js&lt;/span&gt;
&lt;span class="c1"&gt;//= require plugins/inline_style.min.js&lt;/span&gt;
&lt;span class="c1"&gt;//= require plugins/line_breaker.min.js&lt;/span&gt;
&lt;span class="c1"&gt;//= require plugins/link.min.js&lt;/span&gt;
&lt;span class="c1"&gt;//= require plugins/lists.min.js&lt;/span&gt;
&lt;span class="c1"&gt;//= require plugins/paragraph_format.min.js&lt;/span&gt;
&lt;span class="c1"&gt;//= require plugins/paragraph_style.min.js&lt;/span&gt;
&lt;span class="c1"&gt;//= require plugins/quick_insert.min.js&lt;/span&gt;
&lt;span class="c1"&gt;//= require plugins/quote.min.js&lt;/span&gt;
&lt;span class="c1"&gt;//= require plugins/save.min.js&lt;/span&gt;
&lt;span class="c1"&gt;//= require plugins/table.min.js&lt;/span&gt;
&lt;span class="c1"&gt;//= require plugins/url.min.js&lt;/span&gt;
&lt;span class="c1"&gt;//= require plugins/video.min.js&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/*
 *= require plugins/char_counter.min.css
 *= require plugins/code_view.min.css
 *= require plugins/colors.min.css
 *= require plugins/emoticons.min.css
 *= require plugins/file.min.css
 *= require plugins/fullscreen.min.css
 *= require plugins/image_manager.min.css
 *= require plugins/image.min.css
 *= require plugins/line_breaker.min.css
 *= require plugins/quick_insert.min.css
 *= require plugins/table.min.css
 *= require plugins/video.min.css
 */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;同样的，如果你要使用中文语言包，请加入响应的 js&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Include Language if needed&lt;/span&gt;
&lt;span class="c1"&gt;//= require languages/zh_cn.js&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;到目前位置你已经配置好编辑的基本资源，你需要重启 rails 服务器&lt;/p&gt;
&lt;h2 id="初始化编辑器"&gt;初始化编辑器&lt;/h2&gt;
&lt;p&gt;详细文档请参见作者官网，提供了初始化代码 &lt;a href="https://www.froala.com/wysiwyg-editor/docs" rel="nofollow" target="_blank" title=""&gt;Froala WYSIWYG Editor official documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;这里提供部分示例代码，在使用下面代码之前你应该需要知道如何生成七牛上传凭证&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&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;#edit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;froalaEditor&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;zh_cn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;900&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;heightMin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;heightMax&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;requestWithCORS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;//图片上传配置(必须)&lt;/span&gt;
    &lt;span class="na"&gt;imageUploadDomain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;%= Rails.application.config.qiniu_domain %&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;//七牛云存储空间域名地址&lt;/span&gt;
    &lt;span class="na"&gt;imageUploadParam&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;file&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;imageUploadURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://upload.qiniu.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                            &lt;span class="c1"&gt;//七牛上传服务器, 如果是海外服务器为 http://up.qiniu.com&lt;/span&gt;
    &lt;span class="na"&gt;imageUploadParams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;%= @uptoken %&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;                       &lt;span class="c1"&gt;//上传凭证, 详细规则查看七牛官方文档&lt;/span&gt;
    &lt;span class="na"&gt;imageUploadMethod&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;imageMaxSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;imageAllowedTypes&lt;/span&gt;&lt;span class="p"&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;jpeg&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;jpg&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;png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;

    &lt;span class="c1"&gt;//文件上传配置(必须)&lt;/span&gt;
    &lt;span class="na"&gt;fileUploadDomain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;%= Rails.application.config.qiniu_domain %&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;//七牛云存储空间域名地址&lt;/span&gt;
    &lt;span class="na"&gt;fileUploadParam&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;file&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;fileUploadURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://upload.qiniu.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                             &lt;span class="c1"&gt;//同上&lt;/span&gt;
    &lt;span class="na"&gt;fileUploadParams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;%= @uptoken %&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;                        &lt;span class="c1"&gt;//同上&lt;/span&gt;
    &lt;span class="na"&gt;fileUploadMethod&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;fileMaxSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;fileAllowedTypes&lt;/span&gt;&lt;span class="p"&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;*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="配置代码视图"&gt;配置代码视图&lt;/h2&gt;
&lt;p&gt;使用代码视图加高亮效果，可加入以下代码，也可以使用&lt;a href="https://rubygems.org/gems/codemirror-rails" rel="nofollow" target="_blank" title=""&gt;codemirror-rails&lt;/a&gt;这个 gem 包&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"//cdnjs.cloudflare.com/ajax/libs/codemirror/5.3.0/codemirror.min.css"&lt;/span&gt; &lt;span class="na"&gt;media=&lt;/span&gt;&lt;span class="s"&gt;"screen"&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"//cdnjs.cloudflare.com/ajax/libs/codemirror/5.3.0/codemirror.min.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"//cdnjs.cloudflare.com/ajax/libs/codemirror/5.3.0/mode/xml/xml.min.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="许可"&gt;许可&lt;/h2&gt;
&lt;p&gt;wyg-rails 项目是在麻省理工学院的许可。然而，为了使用 Froala WYSIWYG HTML 编辑器插件你应该购买一个许可证。
下面是许可链接，请支持源作者
Froala Editor has &lt;a href="https://froala.com/wysiwyg-editor/pricing" rel="nofollow" target="_blank" title=""&gt;3 different licenses&lt;/a&gt;.
For details please see &lt;a href="https://froala.com/wysiwyg-editor/terms" rel="nofollow" target="_blank" title=""&gt;License Agreement&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="致谢"&gt;致谢&lt;/h2&gt;
&lt;p&gt;感谢 Froala 项目作者的贡献，我在项目的原基础上增加了对七牛云存储的支持.
如果你需要使用其它的云存储平台，请使用&lt;a href="https://rubygems.org/gems/wysiwyg-rails" rel="nofollow" target="_blank" title=""&gt;wysiwyg-rails&lt;/a&gt;进行自定义配置&lt;/p&gt;</description>
      <author>tkvern</author>
      <pubDate>Thu, 25 Feb 2016 16:13:08 +0800</pubDate>
      <link>https://ruby-china.org/topics/29099</link>
      <guid>https://ruby-china.org/topics/29099</guid>
    </item>
    <item>
      <title>[教程] Rails 进阶—— 云存储实战</title>
      <description>&lt;h2 id="七牛云存储使用教程(Ruby on Rails + JavaScript)"&gt;七牛云存储使用教程 (Ruby on Rails + JavaScript)&lt;/h2&gt;
&lt;p&gt;标签（空格分隔）：Rails 七牛&lt;/p&gt;

&lt;p&gt;欢迎关注我的&lt;a href="https://github.com/tkvern" rel="nofollow" target="_blank" title=""&gt;Github&lt;/a&gt;(tkvern)
(博客服务器很久没维护，已经挂掉了，上 github 吧)&lt;/p&gt;

&lt;p&gt;[TOC]&lt;/p&gt;

&lt;hr&gt;
&lt;h2 id="1.吐槽"&gt;1.吐槽&lt;/h2&gt;
&lt;p&gt;写教程之前不得不吐槽一下&lt;a href="http://developer.qiniu.com/docs/v6/sdk/index.html" rel="nofollow" target="_blank" title=""&gt;七牛&lt;/a&gt;的官方文档，API 的说明是很全面，但是读起来超蛋疼。为什么这么说呢，按照我以往的看 API 的文档都会有示例代码跑起来帮助理解，而七牛的 API 文档对于刚接触这种第三方服务商 SDK 的开发者来说不是太友好。建议七牛借鉴下&lt;a href="http://developer.baidu.com/map/index.php?title=jspopular" rel="nofollow" target="_blank" title=""&gt;百度地图 SDK&lt;/a&gt;的文档写法。&lt;/p&gt;

&lt;hr&gt;
&lt;h2 id="2.初识"&gt;2.初识&lt;/h2&gt;
&lt;p&gt;七牛云存储是一家专注云存储领域的技术公司，提供云存储、云处理、云加速分发一站式服务，持续追求高可靠、高可用、高性能、高响应速度，推动客户健康稳定地快速发展。（前面是广告）&lt;/p&gt;

&lt;p&gt;以往开发项目时总会遇到上传文件的问题，譬如上传速度、断点续传、资源服务器部署等等。存在自己的服务器上面，维护成本高。那如何解决这些问题呢，有需求就有市场，云存储应运而生，把这些处理统统放到云存储上，只需要进行 API 调用即可，而这次我们选中了七牛云存储，便宜，好用，快速。（广告打了一大堆，进入正题吧）&lt;/p&gt;

&lt;hr&gt;
&lt;h2 id="3.使用"&gt;3.使用&lt;/h2&gt;
&lt;p&gt;教程正式开始&lt;/p&gt;
&lt;h3 id="3.1注册"&gt;3.1 注册&lt;/h3&gt;
&lt;p&gt;使用前得先注册一个七牛云存储的账号，&lt;a href="https://portal.qiniu.com/signup" rel="nofollow" target="_blank" title=""&gt;--&amp;gt;注册传送门&amp;lt;--&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="3.2登录认证"&gt;3.2 登录认证&lt;/h3&gt;
&lt;p&gt;登录之后查看你的账号身份认证，完成认证解锁更多功能&lt;/p&gt;
&lt;h3 id="3.3创建空间"&gt;3.3 创建空间&lt;/h3&gt;&lt;h3 id="3.4获取AK, SK,空间域名"&gt;3.4 获取 AK, SK，空间域名&lt;/h3&gt;&lt;h3 id="3.5查看上传文件"&gt;3.5 查看上传文件&lt;/h3&gt;&lt;h3 id="3.6配置服务器环境"&gt;3.6 配置服务器环境&lt;/h3&gt;
&lt;p&gt;在使用七牛云存储前我们先要配置好&lt;code&gt;上传凭证&lt;/code&gt;的生成环境&lt;/p&gt;

&lt;p&gt;代码清单 3.6.1: 导入七牛云存储的&lt;code&gt;gem&lt;/code&gt;包
&lt;em&gt;&lt;code&gt;Gemfile&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="s1"&gt;'https://rubygems.org'&lt;/span&gt;


&lt;span class="c1"&gt;# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'rails'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'~&amp;gt;4.2.3'&lt;/span&gt;
&lt;span class="c1"&gt;# Use postgresql as the database for Active Record&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="nf"&gt;.&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="nf"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'qiniu'&lt;/span&gt; &lt;span class="c1"&gt;#加入七牛云存储gem包&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="nf"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;代码清单 3.6.2: 然后重新&lt;code&gt;bundle&lt;/code&gt;我们的项目会看到安装了很多&lt;code&gt;gem&lt;/code&gt;包&lt;/p&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;bundle &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;span class="c"&gt;.
.
.
&lt;/span&gt;&lt;span class="go"&gt;Using qiniu 6.5.1
&lt;/span&gt;&lt;span class="c"&gt;.
.
.
&lt;/span&gt;&lt;span class="go"&gt;Bundle complete! 17 Gemfile dependencies, 66 gems now installed.
Use `bundle show [gemname]` to see where a bundled gem is installed.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果有提示安装失败或是无法安装一些国外的镜像可修改&lt;code&gt;Gemfile&lt;/code&gt;的源到淘宝源或是自行翻墙，推荐一款翻墙工具&lt;a href="https://shadowsocks.com/" rel="nofollow" target="_blank" title=""&gt;Shadowsocks&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="3.7配置AccessKey/SecretKey"&gt;3.7 配置 AccessKey/SecretKey&lt;/h3&gt;
&lt;p&gt;代码清单 3.7.1: 新建&lt;code&gt;qiniu_sdk.rb&lt;/code&gt;文件，将 3.3 节获取到的 AK,SK，空间域名地址输入下面代码
&lt;em&gt;&lt;code&gt;config/initializers/qiniu_sdk.rb&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env ruby&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'qiniu'&lt;/span&gt;

&lt;span class="no"&gt;Qiniu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;establish_connection!&lt;/span&gt; &lt;span class="ss"&gt;:access_key&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;输入你的AK&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="ss"&gt;:secret_key&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;输入你的SK&amp;gt;'&lt;/span&gt;

&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;qiniu_domain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;空间域名地址&amp;gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="3.8生成上传凭证"&gt;3.8 生成上传凭证&lt;/h3&gt;
&lt;p&gt;代码清单 3.8.1: 这个方法我会包装到 Helper 里面
&lt;em&gt;&lt;code&gt;app/helpers/application_helper.rb&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;ApplicationHelper&lt;/span&gt;
  &lt;span class="kp"&gt;private&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;uptoken&lt;/span&gt;

      &lt;span class="n"&gt;put_policy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Qiniu&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Auth&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PutPolicy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;"&amp;lt;你的空间名称&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                    &lt;span class="c1"&gt;# 存储空间&lt;/span&gt;
        &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                           &lt;span class="c1"&gt;# 最终资源名，可省略，即缺省为“创建”语义&lt;/span&gt;
        &lt;span class="mi"&gt;1800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                          &lt;span class="c1"&gt;# 相对有效期，可省略，缺省为3600秒后 uptoken 过期&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt;  &lt;span class="c1"&gt;# 绝对有效期，可省略，指明 uptoken 过期期限（绝对值），通常用于调试，这里表示半小时&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="n"&gt;uptoken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Qiniu&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_uptoken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;put_policy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;#生成凭证&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;p&gt;到目前为止我们以及完成了服务端的所有配置，当我们的文件上传时，是不经过我们自己的服务器的，由客户端通过 Ajax 请求七牛的 API 再将返回的文件名存入我们的服务器。接下来就是客户端的配置&lt;/p&gt;
&lt;h3 id="3.9导入七牛JavaScriptSDK"&gt;3.9 导入七牛 JavaScriptSDK&lt;/h3&gt;
&lt;p&gt;&lt;a href="http://developer.qiniu.com/docs/v6/sdk/javascript-sdk.html" rel="nofollow" target="_blank" title=""&gt;--&amp;gt;下载 JavaScriptSDK 传送门&amp;lt;--&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;代码清单 3.9.1: 为了代码易于管理我们将七牛的 JavaScriptSDK 文件放到&lt;em&gt;&lt;code&gt;app/assets/javascripts/plugins&lt;/code&gt;&lt;/em&gt;目录&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- app
  - assets
    + fonts
    + images
    - javascript
      - plugins
        - plupload
            moxie.js
            plupload.dev.js
            plupload.full.min.js
        - qiniu
            qiniu.js
            qiniu.min.js
      application.js
    + stylesheets
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;代码清单 3.9.2: 引用 SDK 到&lt;code&gt;application.js&lt;/code&gt;
&lt;em&gt;&lt;code&gt;app/assets/javascripts/application.js&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// # Place all the behaviors and hooks related to the matching controller here.&lt;/span&gt;
&lt;span class="c1"&gt;// # All this logic will automatically be available in application.js.&lt;/span&gt;
&lt;span class="c1"&gt;// # You can use CoffeeScript in this file: http://coffeescript.org/&lt;/span&gt;

&lt;span class="c1"&gt;//= require plugins/plupload/moxie&lt;/span&gt;
&lt;span class="c1"&gt;//= require plugins/plupload/plupload.dev&lt;/span&gt;
&lt;span class="c1"&gt;//= require plugins/qiniu/qiniu&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="3.91初始化javascript"&gt;3.91 初始化 javascript&lt;/h3&gt;
&lt;p&gt;在完成以上操作之后我们就可以正式使用七牛云存储了&lt;/p&gt;

&lt;p&gt;代码清单 3.9.3: 在&lt;code&gt;controller&lt;/code&gt;中引入&lt;code&gt;helper&lt;/code&gt;方法，传入上传凭证
&lt;em&gt;&lt;code&gt;app/controllers/uploads_controller.rb&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UploadsController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActionController&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;BaseController&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;ApplicationHelper&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
    &lt;span class="c1"&gt;# 获取上传凭证&lt;/span&gt;
    &lt;span class="vi"&gt;@uptoken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;uptoken&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;p&gt;代码清单 3.9.4: 代码清单在视图文件中使用
&lt;em&gt;&lt;code&gt;app/views/upload/index.html.erb&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- 初始化按钮 start --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"col-md-12"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"container"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"position: absolute; top: 50px;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn btn-default btn-lg "&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"pickfiles"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"position: relative; z-index: 1;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;i&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"fa fa-plus"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/i&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;选择文件&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- 初始化按钮 end --&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- 初始化配置 start --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text/javascript"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;uploader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Qiniu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uploader&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;runtimes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;html5,html4&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;//上传模式,依次退化&lt;/span&gt;
    &lt;span class="na"&gt;browse_button&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pickfiles&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="c1"&gt;//上传选择的点选按钮，**必需**&lt;/span&gt;
    &lt;span class="na"&gt;uptoken&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;%= @uptoken %&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;//若未指定uptoken_url,则必须指定 uptoken ,uptoken由其他程序生成&lt;/span&gt;
    &lt;span class="na"&gt;unique_names&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;// 默认 false，key为文件名。若开启该选项，SDK会为每个文件自动生成key（文件名）&lt;/span&gt;
    &lt;span class="na"&gt;save_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;// 默认 false。若在服务端生成uptoken的上传策略中指定了 `sava_key`，则开启，SDK在前端将不对key进行任何处理&lt;/span&gt;
    &lt;span class="na"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;%= Rails.application.config.qiniu_domain %&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;//bucket 域名，下载资源时用到，**必需**&lt;/span&gt;
    &lt;span class="na"&gt;container&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;container&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="c1"&gt;//上传区域DOM ID，默认是browser_button的父元素，&lt;/span&gt;
    &lt;span class="na"&gt;max_file_size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;5mb&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="c1"&gt;//最大文件体积限制&lt;/span&gt;
    &lt;span class="c1"&gt;// flash_swf_url: 'js/plupload/Moxie.swf',  //引入flash,相对路径&lt;/span&gt;
    &lt;span class="na"&gt;max_retries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                   &lt;span class="c1"&gt;//上传失败最大重试次数&lt;/span&gt;
    &lt;span class="na"&gt;dragdrop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                   &lt;span class="c1"&gt;//开启可拖曳上传&lt;/span&gt;
    &lt;span class="na"&gt;drop_element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;container&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;//拖曳上传区域元素的ID，拖曳文件或文件夹后可触发上传&lt;/span&gt;
    &lt;span class="na"&gt;chunk_size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1mb&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                &lt;span class="c1"&gt;//分块上传时，每片的体积&lt;/span&gt;
    &lt;span class="na"&gt;auto_start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                 &lt;span class="c1"&gt;//选择文件后自动上传，若关闭需要自己绑定事件触发上传&lt;/span&gt;
    &lt;span class="na"&gt;init&lt;/span&gt;&lt;span class="p"&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;FilesAdded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;up&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;plupload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// 文件添加进队列后,处理相关的事情&lt;/span&gt;
            &lt;span class="p"&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;BeforeUpload&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;up&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
               &lt;span class="c1"&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;UploadProgress&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;up&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
               &lt;span class="c1"&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;FileUploaded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;up&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
               &lt;span class="c1"&gt;// 每个文件上传成功后,处理相关的事情&lt;/span&gt;
               &lt;span class="c1"&gt;// 其中 info 是文件上传成功后，服务端返回的json，形式如&lt;/span&gt;
               &lt;span class="c1"&gt;// {&lt;/span&gt;
               &lt;span class="c1"&gt;//    "hash": "Fh8xVqod2MQ1mocfI4S4KpRL6D98",&lt;/span&gt;
               &lt;span class="c1"&gt;//    "key": "gogopher.jpg"&lt;/span&gt;
               &lt;span class="c1"&gt;//  }&lt;/span&gt;
               &lt;span class="c1"&gt;// 参考http://developer.qiniu.com/docs/v6/api/overview/up/response/simple-response.html&lt;/span&gt;
               &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;domain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;up&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getOption&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;domain&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
               &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parseJSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
               &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;sourceLink&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;domain&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;//获取上传成功后的文件的Url&lt;/span&gt;
               &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
               &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sourceLink&lt;/span&gt;&lt;span class="p"&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;Error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;up&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;errTip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
               &lt;span class="c1"&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;UploadComplete&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
               &lt;span class="c1"&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;Key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;up&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// 若想在前端对每个文件的key进行个性化处理，可以配置该函数&lt;/span&gt;
            &lt;span class="c1"&gt;// 该配置必须要在 unique_names: false , save_key: false 时才生效&lt;/span&gt;
            &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="c1"&gt;// do something with key here&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- 初始化配置 end --&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;代码清单 3.9.5: 完成了这么多复杂的操作后终于可以试试效果了，启动 Rails 服务器看看吧&lt;/p&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;rails server &lt;span class="nt"&gt;-b&lt;/span&gt; &lt;span class="nv"&gt;$IP&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="nv"&gt;$PORT&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="4.作者"&gt;4.作者&lt;/h2&gt;
&lt;p&gt;如果教程里面有什么纰漏的地方请给我留言&lt;/p&gt;</description>
      <author>tkvern</author>
      <pubDate>Wed, 17 Feb 2016 11:05:16 +0800</pubDate>
      <link>https://ruby-china.org/topics/29010</link>
      <guid>https://ruby-china.org/topics/29010</guid>
    </item>
  </channel>
</rss>
