<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.7.3.1">Jekyll</generator><link href="http://yangsr.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="http://yangsr.github.io/" rel="alternate" type="text/html" /><updated>2018-08-28T02:41:48+00:00</updated><id>http://yangsr.github.io/</id><title type="html">Yang Shurui</title><subtitle>Web Developer from Somewhere</subtitle><entry><title type="html">通过线程池创建并行任务提速任务</title><link href="http://yangsr.github.io/Overmind-ThreadPoolExecutor/" rel="alternate" type="text/html" title="通过线程池创建并行任务提速任务" /><published>2018-08-27T00:00:00+00:00</published><updated>2018-08-27T00:00:00+00:00</updated><id>http://yangsr.github.io/Overmind-ThreadPoolExecutor</id><content type="html" xml:base="http://yangsr.github.io/Overmind-ThreadPoolExecutor/">&lt;p&gt;最近一年来我的全部精力都投在了Overmind的环境治理模块中，最近会抽空写一系列文章对其中涉及到的知识点由浅入深地进行梳理和回顾。&lt;/p&gt;

&lt;p&gt;今天要分享的主题是，通过线程池创建并行任务提速任务。&lt;/p&gt;

&lt;h3 id=&quot;问题背景&quot;&gt;问题背景&lt;/h3&gt;
&lt;p&gt;我们的模块中有一个功能原先的实现方式是这样的：挑选出一组合适的集群之后，调用第三方服务的接口，挨个执行“修改构建配置”、“新增实例”、“绑定服务器”操作，如下图所示:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/overmind_threadpool.png&quot; alt=&quot;线程池背景说明&quot; title=&quot;线程池背景说明&quot; /&gt;&lt;/p&gt;

&lt;p&gt;代码也很简单，把相关业务逻辑套在for循环中，挨个执行即可。&lt;/p&gt;

&lt;p&gt;当集群的数量比较少的时候，这段代码执行起来问题还不大，但是当一个环境下包含10余个集群，每个集群都要挨个调用第三方接口去操作的时候，问题出现了：代码耗时太长。&lt;/p&gt;

&lt;h3 id=&quot;并行&quot;&gt;并行&lt;/h3&gt;
&lt;p&gt;由于对于每个集群的处理逻辑相同，很容易就能想到，通过Java线程池来处理这类需求。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;线程池配置，线程池不允许使用 Executors 去创建，而是通过 ThreadPoolExecutor 的方式。这样的处理方式让写的同学更加明确线程池的运行规则, 规避资源耗尽的风险。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;上面这段话是阿里巴巴的Java开发手册里提到的，所以我们手工创建一个线程池即可。&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;@Configuration
public class ExecutorConfig {

    private static Logger logger = LoggerFactory.getLogger(ExecutorConfig.class);

    private int corePoolSize = 20;
    private int maxPoolSize = 30;

    private RejectedExecutionHandler handler = new RejectedExecutionHandler() {
        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
            if (!executor.isShutdown()) {
                try {
                    // 保证该task继续被尝试用阻塞式的put()到workQueue中。
                    executor.getQueue().put(r);
                } catch (InterruptedException e) {
                    logger.error(&quot;Executor rejectedExecution interrupted&quot;, e);
                }
            }
        }
    };

    // 用于部署的线程池
    @Bean
    public ThreadPoolTaskExecutor deployExecutor() {
        return getGeneralExecutor(corePoolSize, maxPoolSize, &quot;DeployExecutor-&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;然后将相关业务逻辑封装成一个独立的类DeployClusterTask，由于我们需要知道单个任务的执行结果，所以可以implements Callable&lt;String&gt;接口，将结果以String返回即可。&lt;/String&gt;&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;List&amp;lt;String&amp;gt; errorMessage = new ArrayList&amp;lt;&amp;gt;();
List&amp;lt;Future&amp;lt;String&amp;gt;&amp;gt; results = new ArrayList&amp;lt;&amp;gt;();

for (int i = 0; i &amp;lt; appClusterList.size(); i++) {
    DeployClusterTask task = new DeployClusterTask(params...);
    Future&amp;lt;String&amp;gt; future = deployExecutor.submit(task);
    results.add(future);
}

// 自旋，获取结果
for (int i = 0; i &amp;lt; results.size(); i++) {
    Future&amp;lt;String&amp;gt; taskHolder = results.get(i);
    if (taskHolder.isDone()) {
        String result = taskHolder.get();
        results.remove(taskHolder);
        if (result != null) {
            errorMessage.add(result);
        }
        i--;
    }

    if (i == results.size() - 1) {
        i = -1;
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;知识点&quot;&gt;知识点&lt;/h3&gt;
&lt;p&gt;相关业务改造很快说完了，还是要记录一下常见的知识点。&lt;/p&gt;

&lt;p&gt;1.应用与线程池的交互，线程池的内部工作过程示意图：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/overmind_threadpool2.png&quot; alt=&quot;线程池工作过程说明&quot; title=&quot;线程池工作过程说明&quot; /&gt;&lt;/p&gt;

&lt;p&gt;2.尽量避免在线程池中操作ThreadLocal&lt;/p&gt;

&lt;p&gt;3.避免任务堆积，如果处理速度跟不上入队速度，可能占用大量系统内存，甚至造成oom&lt;/p&gt;

&lt;p&gt;4.如果任务逻辑有问题，则会导致工作线程迟迟不能被释放，可以使用jstack排查线程栈&lt;/p&gt;</content><author><name></name></author><summary type="html">最近一年来我的全部精力都投在了Overmind的环境治理模块中，最近会抽空写一系列文章对其中涉及到的知识点由浅入深地进行梳理和回顾。</summary></entry><entry><title type="html">Docker爬坑记</title><link href="http://yangsr.github.io/Stories-about-Docker/" rel="alternate" type="text/html" title="Docker爬坑记" /><published>2017-05-17T00:00:00+00:00</published><updated>2017-05-17T00:00:00+00:00</updated><id>http://yangsr.github.io/Stories-about-Docker</id><content type="html" xml:base="http://yangsr.github.io/Stories-about-Docker/">&lt;p&gt;我们在生产环境使用Docker部署已经有段日子了，Docker为我们的部署工作提供了诸多的便利，不过同时因为经验不足我们也遇到了很多坑，因此写篇日志记录下。&lt;/p&gt;

&lt;h3 id=&quot;docker踩坑故事一&quot;&gt;Docker踩坑故事一&lt;/h3&gt;
&lt;p&gt;年初某项目收到用户反馈，打开首页登录时&lt;em&gt;偶尔&lt;/em&gt;会提示504错误。&lt;/p&gt;

&lt;p&gt;我们使用了OpenId进行登录，我观察了一下，请求正常的时候后台是会输出这么3行日志的：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;INFO -- : WARNING: making https request to https://login.xxx.com/openid/ without verifying server certificate; no CA path was specified.
INFO -- : WARNING: making https request to https://login.xxx.com/openid/xrds/ without verifying server certificate; no CA path was specified.
INFO -- : Generated checkid_setup request to https://login.xxx.com/openid/ using stateless mode.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;当我们遇到504的时候，后台只能输出1~2行日志后就卡住了，随后我在后端代码中打印了OpenId的认证结果：是missing。&lt;/p&gt;

&lt;p&gt;因为是偶现的问题，所以排查起来比较吃力。经过几次定位，发现在Docker内curl OpenId的服务器有时会卡住，而在Docker外则从未发生过这种情况，从而将问题初步定位在Docker上。&lt;/p&gt;

&lt;p&gt;联系SA抓包后发现TCP报错&lt;em&gt;Out of order&lt;/em&gt;：
&lt;img src=&quot;/images/docker-1.png&quot; alt=&quot;抓包截图&quot; title=&quot;抓包截图&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这时回想起OpenId的负责同学曾提醒过可能是MSS/MTU设置有问题。SA检查发现docker内MTU设置大于宿主机的MTU，将Docker MTU改小一些后异常消失。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;最大传输单元（英语：Maximum Transmission Unit，缩写MTU）是指一种通信协议的某一层上面所能通过的最大数据包大小（以字节为单位）。最大传输单元这个参数通常与通信接口有关（网络接口卡、串口等）。&lt;/p&gt;

  &lt;p&gt;因特网协议允许IP分片，这样就可以将数据报包分成足够小的片段以通过那些最大传输单元小于该数据报原始大小的链路了。这一分片过程发生在 IP 层（OSI模型的第三层，即网络层），它使用的是将分组发送到链路上的网络接口的最大传输单元的值。原始分组的分片都被加上了标记，这样目的主机的 IP 层就能将分组重组成原始的数据报了。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;docker踩坑故事二&quot;&gt;Docker踩坑故事二&lt;/h3&gt;
&lt;p&gt;我们在企业易信上开发了一个基于Spring boot的移动小程序，使用Docker部署后发现&lt;em&gt;偶尔&lt;/em&gt;会出现首次进入应用后白屏的问题。&lt;/p&gt;

&lt;p&gt;经过排查后发现在Docker容器外部署，一切回归正常。&lt;/p&gt;

&lt;p&gt;自己尝试在客户端抓包：
&lt;img src=&quot;/images/docker-2.png&quot; alt=&quot;抓包截图2&quot; title=&quot;抓包截图2&quot; /&gt;&lt;/p&gt;

&lt;p&gt;可以看到TCP 3次握手建立连接后，服务端忽然返回了一个RST报文，于是出现了白屏，可以初步判断网络出异常了。&lt;/p&gt;

&lt;p&gt;为了不影响线上用户使用，我们计划暂时将Docker内的服务停止，改部署在Docker外，这时我们发现了一个奇怪的进程：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;root  14360 99.9  0.0  20036   272 ?   R    May08 9583:04 /bin/bash -c nohup java -jar etest-0.0.1-SNAPSHOT.jar &amp;amp;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;我们已经将Docker内的服务停止了，怎么还有这么一个进程存在，而且是root权限的。&lt;/p&gt;

&lt;p&gt;这个进程即使SA使用root权限&lt;code class=&quot;highlighter-rouge&quot;&gt;kill -9&lt;/code&gt;也杀不掉，并且将CPU的一个内核占用了100%：
&lt;img src=&quot;/images/docker-3.png&quot; alt=&quot;CPU截图&quot; title=&quot;CPU截图&quot; /&gt;&lt;/p&gt;

&lt;p&gt;我们联系SA将云主机重启后，这个奇怪的进程消失了，偶现的白屏问题也消失了。&lt;/p&gt;

&lt;p&gt;回想一下，这个杀不掉的进程是有天晚上我们将几条Docker命令连起来跑后出现的，包括Docker暂停、Docker启动、端口映射等，没有跑成功，当时也没去管，没想到当时潜在的隐患导致了后来线上的问题。&lt;/p&gt;</content><author><name></name></author><summary type="html">我们在生产环境使用Docker部署已经有段日子了，Docker为我们的部署工作提供了诸多的便利，不过同时因为经验不足我们也遇到了很多坑，因此写篇日志记录下。</summary></entry><entry><title type="html">分布式部署静态资源预编译后fingerprint不一致问题的解决思路</title><link href="http://yangsr.github.io/Fingerprint-Inconsistent/" rel="alternate" type="text/html" title="分布式部署静态资源预编译后fingerprint不一致问题的解决思路" /><published>2016-12-12T00:00:00+00:00</published><updated>2016-12-12T00:00:00+00:00</updated><id>http://yangsr.github.io/Fingerprint-Inconsistent</id><content type="html" xml:base="http://yangsr.github.io/Fingerprint-Inconsistent/">&lt;h3 id=&quot;问题描述&quot;&gt;问题描述&lt;/h3&gt;
&lt;p&gt;为了实现线上服务高可用的目标，我们对Rails应用做了分布式部署，结果在部署服务的a和b两台服务器上，静态资源预编译后fingerprint不一致。这样的后果是我们的样式、前端js效果时好时坏，非常头疼。临时的解决方案是将一台服务器上&lt;code class=&quot;highlighter-rouge&quot;&gt;public/&lt;/code&gt;目录下的文件手动拷贝到另外一台服务器上，但显然这并非长久之道，于是我们开始着力排查原因。&lt;/p&gt;

&lt;h3 id=&quot;问题排查&quot;&gt;问题排查&lt;/h3&gt;
&lt;p&gt;为什么需要fingerprint:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;In production, Rails inserts an MD5 fingerprint into each filename so that the file is cached by the web browser&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;并且根据&lt;a href=&quot;http://guides.rubyonrails.org/asset_pipeline.html#what-is-fingerprinting-and-why-should-i-care&quot;&gt;Rails Guides&lt;/a&gt;中的介绍，fingerprint的生成是基于文件内容的，文件内容一致，则理论上来说，每次预编译后的fingerprint也应当一致。&lt;/p&gt;

&lt;p&gt;然而在排查过程中我发现，a服务器和b服务器的&lt;code class=&quot;highlighter-rouge&quot;&gt;app/assets&lt;/code&gt;目录下的文件内容完全一致，那么显然不是两边文件内容不一致的原因。&lt;/p&gt;

&lt;p&gt;那么换一个角度思考，fingerprint是由&lt;a href=&quot;https://github.com/rails/sprockets&quot;&gt;Sprockets&lt;/a&gt;这个gem生成的，会不会是gem的原因？&lt;/p&gt;

&lt;p&gt;我对比了两台服务器上的&lt;code class=&quot;highlighter-rouge&quot;&gt;Gemfile.lock&lt;/code&gt;文件，发现部分gem的版本号不一致，这其中就包括了Sprockets、&lt;a href=&quot;https://github.com/rtomayko/tilt&quot;&gt;Tilt&lt;/a&gt;这些gem。&lt;/p&gt;

&lt;p&gt;当我们将两台服务器上的&lt;code class=&quot;highlighter-rouge&quot;&gt;Gemfile.lock&lt;/code&gt;文件统一后，重新预编译，结果发现，大部分文件的fingerprint一致了，但仍旧有少部分不一致的文件。&lt;/p&gt;

&lt;p&gt;这至少说明了两台服务器上gem的版本不同是导致预编译后fingerprint不一致问题的原因之一。&lt;/p&gt;

&lt;p&gt;我分析了剩下的一小部分fingerprint仍旧不一致的文件，大多是jQuery相关的，属于第三方的js文件。这部分文件是什么原因导致fingerprint不一致呢？&lt;/p&gt;

&lt;p&gt;思考半天无果，无奈之下将整个工程整体打包，打算从a服务器拷贝到b服务器上试着预编译下看结果。结果在打包的过程中看到了&lt;code class=&quot;highlighter-rouge&quot;&gt;tmp/cache/assets&lt;/code&gt;目录，瞬间想到，会不会是cache的原因？&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;By default, Sprockets caches assets in tmp/cache/assets in development and production environments.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;于是停止打包，将工程的tmp目录下的所有cache清空，重新预编译，至此所有文件的fingerprint完全一致，问题解决。&lt;/p&gt;

&lt;h3 id=&quot;总结回顾&quot;&gt;总结回顾&lt;/h3&gt;
&lt;p&gt;首先&lt;code class=&quot;highlighter-rouge&quot;&gt;Gemfile.lock&lt;/code&gt;文件没有纳入代码仓库应当是fingerprint不一致的根本原因，关于是否应当将这个文件纳入代码仓库，这个问题在社区中被&lt;a href=&quot;https://ruby-china.org/topics/9018&quot;&gt;详细讨论过&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;其次&lt;code class=&quot;highlighter-rouge&quot;&gt;tmp/cache/assets&lt;/code&gt;目录下的缓存文件存在是fingerprint不一致的次要原因，如果文件没有修改，Rails默认就不会去重新生成fingerprint。&lt;/p&gt;</content><author><name></name></author><summary type="html">问题描述 为了实现线上服务高可用的目标，我们对Rails应用做了分布式部署，结果在部署服务的a和b两台服务器上，静态资源预编译后fingerprint不一致。这样的后果是我们的样式、前端js效果时好时坏，非常头疼。临时的解决方案是将一台服务器上public/目录下的文件手动拷贝到另外一台服务器上，但显然这并非长久之道，于是我们开始着力排查原因。</summary></entry><entry><title type="html">使用will_paginate实现下拉滚动异步加载效果</title><link href="http://yangsr.github.io/Scroll-To-Load/" rel="alternate" type="text/html" title="使用will_paginate实现下拉滚动异步加载效果" /><published>2016-03-21T00:00:00+00:00</published><updated>2016-03-21T00:00:00+00:00</updated><id>http://yangsr.github.io/Scroll-To-Load</id><content type="html" xml:base="http://yangsr.github.io/Scroll-To-Load/">&lt;h3 id=&quot;由交互变更聊起&quot;&gt;由交互变更聊起&lt;/h3&gt;
&lt;p&gt;我们网站的结果页面可能有上百条数据，原本采用的是传统的分页模式，即点击“上一页”或“下一页”按钮进行翻页操作。最近进行了一次交互改版，新版的交互变更为当鼠标滚轮滚动到页面底部时，页面上自动加载出下一页的内容。这篇博客要介绍的就是怎样实现这种交互效果。&lt;/p&gt;

&lt;h3 id=&quot;实现思路概述&quot;&gt;实现思路概述&lt;/h3&gt;
&lt;p&gt;在传统的分页模式下，当我们点击“下一页”按钮时，实际上是向后端发送了一个AJAX请求，在这个请求的URL中附带了参数&lt;code class=&quot;highlighter-rouge&quot;&gt;page&lt;/code&gt;的信息，后端接收到请求后，根据&lt;code class=&quot;highlighter-rouge&quot;&gt;page&lt;/code&gt;参数利用&lt;code class=&quot;highlighter-rouge&quot;&gt;will_paginate&lt;/code&gt;取出分页后的数据，最后更新前端页面。&lt;/p&gt;

&lt;p&gt;在新版交互模式下，其实只需要在原有的分页功能基础上做一个小更改：当鼠标滚轮滚动到页面底部时，向后端发送一个AJAX请求，请求的URL即传统的“下一页”的链接URL，后端处理逻辑几乎不需要改变，最后只需要将分页后的数据添加到原来页面的尾部即可。&lt;/p&gt;

&lt;h3 id=&quot;动手实现&quot;&gt;动手实现&lt;/h3&gt;
&lt;p&gt;首先，我们需要将页面结构重新组织一下，大致变为下面这个样子：&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;table&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 将包含数据的内容页面移入partial中 --&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;tbody&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;内容页面id&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;%=&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;partial:&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;partial&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;页面&quot;&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/tbody&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/table&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;%=&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;will_paginate&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;@分页实例变量&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;接下来按照思路实现一下鼠标滚轮滚动到页面底部的效果：&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;scroll&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(){&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;//检测鼠标滚轮是否已经滚动到页面底部&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;scrollTop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)){&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;//AJAX请求will_paginate的下一页的href&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getScript&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'.pagination .next_page'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'href'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;这样AJAX请求已经能够发送到后端了，剩下的工作是修改一下controller执行后对应的&lt;code class=&quot;highlighter-rouge&quot;&gt;.js.erb&lt;/code&gt;中的代码：&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# 注意这里是append方法&lt;/span&gt;
&lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'#内容页面id'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'&amp;lt;%= j render(partial页面)%&amp;gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 需要更新一下底部的will_paginate页面的翻页按钮&lt;/span&gt;
&lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'.pagination'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'&amp;lt;%= j will_paginate(@分页实例变量) %&amp;gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;现在测试一下，会发现当我们鼠标滚轮滑动到页面底部时，确实会向后台发送AJAX请求了，但是却会连续发送多次重复的请求。现在需要回头检查一下javascript代码：&lt;/p&gt;

&lt;p&gt;当我们通过&lt;code class=&quot;highlighter-rouge&quot;&gt;getScript&lt;/code&gt;发送请求时，仍旧在滚动滚轮，这样&lt;code class=&quot;highlighter-rouge&quot;&gt;$(window).scroll&lt;/code&gt;函数依然会被触发，当新的一页数据没有被加载出来时，if判断语句始终为true，所以会导致发送多次AJAX请求。&lt;/p&gt;

&lt;p&gt;我们需要修改一下刚才的javascript代码：&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;scroll&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(){&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;//先取出下一页的url&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;url&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'.pagination .next_page'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'href'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;//将url作为if的一个判断条件，保证当前页面滚动到底部时只会触发一次ajax请求&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;url&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;scrollTop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)){&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;//这里将pagination的url替换成文字&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'.pagination'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;加载中...&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getScript&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;再测试一下，会发现已经能够正常滚动加载了，但是当我们滚动到最后一页时，会看到&lt;code class=&quot;highlighter-rouge&quot;&gt;will_paginate&lt;/code&gt;的翻页组件露了出来，稍稍修改一下我们的&lt;code class=&quot;highlighter-rouge&quot;&gt;.js.erb&lt;/code&gt;：&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;sx&quot;&gt;% if &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;@分页实例变量&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;next_page&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;%&amp;gt;
  $('.pagination').html('&amp;lt;%= j will_paginate(@分页实例变量) %&amp;gt;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;');
&amp;lt;% else %&amp;gt;
  $('&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pagination&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;hide&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;sx&quot;&gt;% end &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;看起来差不多了。但是我们还需要考虑一种极端情况，如果页面中每页展示的数据很少，比如是下图这个样子，连滚动条都没有：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/scroll_to_load.png&quot; alt=&quot;没有滚动条页面示例&quot; title=&quot;没有滚动条页面示例&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这种情况下&lt;code class=&quot;highlighter-rouge&quot;&gt;$(window).scroll&lt;/code&gt;函数永远也不会被触发，所以我们还需要在前端javascript代码中补充一句：&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;scroll&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样，我们就实现了一个完整的下拉滚动异步加载效果。&lt;/p&gt;

&lt;h3 id=&quot;参考&quot;&gt;参考&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=PQX2fgB6y10&quot;&gt;Endless Page&lt;/a&gt;&lt;/p&gt;</content><author><name></name></author><summary type="html">由交互变更聊起 我们网站的结果页面可能有上百条数据，原本采用的是传统的分页模式，即点击“上一页”或“下一页”按钮进行翻页操作。最近进行了一次交互改版，新版的交互变更为当鼠标滚轮滚动到页面底部时，页面上自动加载出下一页的内容。这篇博客要介绍的就是怎样实现这种交互效果。</summary></entry><entry><title type="html">多列模糊搜索实现</title><link href="http://yangsr.github.io/Multi-Column-Fuzzy-Searching/" rel="alternate" type="text/html" title="多列模糊搜索实现" /><published>2016-02-16T00:00:00+00:00</published><updated>2016-02-16T00:00:00+00:00</updated><id>http://yangsr.github.io/Multi-Column-Fuzzy-Searching</id><content type="html" xml:base="http://yangsr.github.io/Multi-Column-Fuzzy-Searching/">&lt;h3 id=&quot;rails-query-interface-where&quot;&gt;Rails Query Interface: where&lt;/h3&gt;
&lt;p&gt;Rails提供了一套非常便利而又强大的查询接口（Query Interface）。例如我们经常同其打交道的&lt;code class=&quot;highlighter-rouge&quot;&gt;where&lt;/code&gt;方法，可以帮我们解决大多数查询条件（Conditions）相关的问题：&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Equality Conditions&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'is_root'&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Range Conditions&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;created_at: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;midnight&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;day&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;midnight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Subset Conditions&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;os_type: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# NOT Conditions&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;not&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;brand: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;brand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# OR Conditions(Rails5 Only)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# see https://github.com/rails/rails/pull/16052/&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'id = 1'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;or&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'id = 2'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;问题背景&quot;&gt;问题背景&lt;/h3&gt;
&lt;p&gt;今天我遇到了这么一个的查询问题：&lt;/p&gt;

&lt;p&gt;假设数据库中存在一张设备表（devices），表中有多个字段：设备品牌（brand）、系统类型（os type）、分辨率（resolution）、序列号（serial number）、内存（memory）等十多个字段。我们已经实现了一个&lt;code class=&quot;highlighter-rouge&quot;&gt;index&lt;/code&gt;页面，负责展示所有的设备，现在希望在页面上实现一个搜索框，根据搜索框中输入的文字，针对设备表中的设备品牌、分辨率、序列号这三个字段进行模糊搜索。&lt;/p&gt;

&lt;p&gt;大体上长这个样子：
&lt;img src=&quot;/images/fuzzy_search.png&quot; alt=&quot;模糊搜索示例&quot; title=&quot;模糊搜索示例&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;解决思路&quot;&gt;解决思路&lt;/h3&gt;
&lt;p&gt;根据需求我实现了一个&lt;code class=&quot;highlighter-rouge&quot;&gt;search&lt;/code&gt;方法，看起来基本可以满足需求了：&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# models/devices.rb&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;search&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;strip!&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;device_brand like ? OR serial_number like ? OR resolution like ?&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;%&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;%&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;%&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;%&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;%&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;%&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;all&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;然而深入思考一下，如果有一天需求发生了变更：不再仅仅只针对三个字段进行模糊搜索，而是将模糊搜索的范围扩大到更多字段，那是不是需要拼一个非常长的&lt;code class=&quot;highlighter-rouge&quot;&gt;where&lt;/code&gt;查询子句：&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;aaa like ? OR bbb like ? OR ccc like ? OR ddd like ? OR eee like ? OR fff like ?&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;%&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;param&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;%&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;%&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;param&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;%&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;%&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;param&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;%&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;%&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;param&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;%&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;%&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;param&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;%&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;%&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;param&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;%&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;有没有更优雅一些的实现方案呢？&lt;/p&gt;

&lt;h3 id=&quot;arel&quot;&gt;Arel&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/rails/arel&quot;&gt;Arel&lt;/a&gt;是Rails里用来管理生成AST(Abstract Syntax Tree 抽象语法树)的组件，负责将Rails里一些SQL查询的DSL转化为底层的SQL语句。&lt;/p&gt;

&lt;p&gt;我们可以使用Arel灵活地实现一些复杂的查询。&lt;/p&gt;

&lt;p&gt;Rails提供了一个叫做&lt;code class=&quot;highlighter-rouge&quot;&gt;arel_table&lt;/code&gt;的方法，用来访问底层Arel的相关接口：&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;Device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;arel_table&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# =&amp;gt; #&amp;lt;Arel::Table:0x00000004a03e18&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这里返回了一个&lt;code class=&quot;highlighter-rouge&quot;&gt;Arel::Table&lt;/code&gt;对象，我们可以把它理解成一个包含了数据库表中每一列的hash对象，可以通过正常的访问hash元素的方式来访问这些列。&lt;/p&gt;

&lt;p&gt;Arel提供了一系列的&lt;a href=&quot;https://github.com/rails/arel/blob/master/lib/arel/predications.rb&quot;&gt;方法&lt;/a&gt;作用于这些列上，用于构造SQL语句。下面是一段简单的示例：&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;devices&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;arel_table&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;devices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;devices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:brand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'HUAWEI'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# SELECT * FROM devices WHERE devices.brand = 'HUAWEI'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;回到我们的需求上，我们可以用Arel将原先的代码重构下：&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# 使用Arel实现多字段模糊查询&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;search&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;strip!&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;search_fields&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;%w(device_brand serial_number resolution)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# 每个待查询字段都会有一个对应的 matches 匹配条件，最后这些条件之间用or运算合并，语义即“device_brand matches xxx or serial_number matches xxx or ...”&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;condition&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;search_fields&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;field&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;arel_table&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;field&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;matches&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;%&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;%&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;inject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:or&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# 使用 Arel 构造的查询子句可以直接用于更高层级的 Query Method，也就是where方法&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;condition&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;all&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;这样即使模糊搜索的范围扩大到了更多的字段，我们只需要将相应的字段名赋值给&lt;code class=&quot;highlighter-rouge&quot;&gt;search_fields&lt;/code&gt;就可以了，其它代码不需要做任何修改。&lt;/p&gt;

&lt;h3 id=&quot;any-way-else&quot;&gt;Any way else?&lt;/h3&gt;
&lt;p&gt;需要注意到的一点是，使用&lt;code class=&quot;highlighter-rouge&quot;&gt;like '%keyword%'&lt;/code&gt;这种查询方式是无法使用索引的，如果数据库表中的数据非常多，那么必然会成为性能上的瓶颈。&lt;/p&gt;

&lt;p&gt;也可以考虑使用一些搜索引擎来实现模糊查询，例如ElasticSearch。&lt;/p&gt;

&lt;h3 id=&quot;参考&quot;&gt;参考&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;http://guides.rubyonrails.org/active_record_querying.html&quot;&gt;Active Record Query Interface&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://robots.thoughtbot.com/using-arel-to-compose-sql-queries&quot;&gt;Using Arel to compose SQL queries&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://railscasts-china.com/episodes/kenshin54-source-code-analysis-arel&quot;&gt;Rails 源码分析之 Arel&lt;/a&gt;&lt;/p&gt;</content><author><name></name></author><summary type="html">Rails Query Interface: where Rails提供了一套非常便利而又强大的查询接口（Query Interface）。例如我们经常同其打交道的where方法，可以帮我们解决大多数查询条件（Conditions）相关的问题：</summary></entry><entry><title type="html">使用Apache Benchmark测试网页性能</title><link href="http://yangsr.github.io/Apache-Benchmark/" rel="alternate" type="text/html" title="使用Apache Benchmark测试网页性能" /><published>2015-11-30T00:00:00+00:00</published><updated>2015-11-30T00:00:00+00:00</updated><id>http://yangsr.github.io/Apache-Benchmark</id><content type="html" xml:base="http://yangsr.github.io/Apache-Benchmark/">&lt;h3 id=&quot;写在前面&quot;&gt;写在前面&lt;/h3&gt;
&lt;blockquote&gt;
  &lt;p&gt;不要总是“我觉得”，你应该编写Benchmark测试对比&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;上面这句话是我观看RubyConf China 2015时听到的，旨在号召社区内的同学不要仅仅凭感觉，就认为这种写法比较慢，那种写法比较快。&lt;/p&gt;

&lt;p&gt;恰好今天将应用的Ruby版本从&lt;code class=&quot;highlighter-rouge&quot;&gt;2.0.0&lt;/code&gt;升级到了&lt;code class=&quot;highlighter-rouge&quot;&gt;2.2.3&lt;/code&gt;，于是就想测试一下同一个网页使用不同的ruby版本，速度会相差多少。&lt;/p&gt;

&lt;h3 id=&quot;简单安装使用&quot;&gt;简单安装使用&lt;/h3&gt;
&lt;p&gt;我选择了Apache Benchmark来做测评，因为看起来使用挺简单的。&lt;/p&gt;

&lt;p&gt;Ubuntu下可以这么安装：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sudo apt-get install apache2-utils
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;装好后我们简单试用一下：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ab -n 10 -c 10 http://www.163.com/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;然后我们就会看到长长的输出结果啦：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;This is ApacheBench, Version 2.3 &amp;lt;$Revision: 1528965 $&amp;gt;
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking www.163.com (be patient).....done


Server Software:        nginx
Server Hostname:        www.163.com
Server Port:            80

Document Path:          /
Document Length:        729047 bytes

Concurrency Level:      10
// 完成这项测试总共耗时
Time taken for tests:   6.304 seconds
Complete requests:      10
Failed requests:        0
Total transferred:      7293684 bytes
HTML transferred:       7290470 bytes
// 每秒执行了多少个请求
Requests per second:    1.59 [#/sec] (mean)
// 这一组请求所消耗的时间
Time per request:       6304.459 [ms] (mean)
// 平均下来每个请求所消耗的时间
Time per request:       630.446 [ms] (mean, across all concurrent requests)
Transfer rate:          1129.79 [Kbytes/sec] received

// 以下这段数据标志了一个请求从连接、发送数据、接收数据这三个阶段所消耗时间的最小值、平均值、中位数以及最大值
Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        8   90  87.5    150     185
Processing:  1309 3252 1599.6   3359    6155
Waiting:      238  532 222.2    619     794
Total:       1317 3342 1607.9   3544    6304

// 所有请求的消耗时间分布区间
// 50%的用户响应时间小于3544s
// 66%的用户响应时间小于3768s
// 最大响应时间小于6304s
Percentage of the requests served within a certain time (ms)
  50%   3544
  66%   3768
  75%   3941
  80%   5227
  90%   6304
  95%   6304
  98%   6304
  99%   6304
 100%   6304 (longest request)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;如果应用有登陆认证&quot;&gt;如果应用有登陆认证&lt;/h3&gt;
&lt;p&gt;像我需要测试的web 应用，是需要登陆认证的：要检查session中有没有用户信息。&lt;/p&gt;

&lt;p&gt;Rails默认的session策略是&lt;a href=&quot;http://guides.rubyonrails.org/security.html#session-storage&quot;&gt;cookie-based session&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;对于Rails应用来说，我们的session通常是这么命名的： &lt;code class=&quot;highlighter-rouge&quot;&gt;_your_site_session&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;我们可以在Chrome中按F12，在Resources这个tab标签页的Cookies下找到我们的session。（听起来有些拗口～）
&lt;img src=&quot;/images/cookie-based-session.png&quot; alt=&quot;Session示意&quot; title=&quot;Session示意图&quot; /&gt;&lt;/p&gt;

&lt;p&gt;找到了session我们就可以顺利通过登陆认证啦：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ab -C &quot;_quality_metric_session=BAh7CkkiD3Nlc3Npb25faWQGOgZFVEkiJWRmYmE5NjY5N2Y4YzM4ODgyMGU3YzQzNGVlYWQ1YzYwBjsAVEkiEF9jc3JmX3Rva2VuBjsARkkiMTZFS0RCak1lTWlTSDJZTnNCOXhwOEpPbzV3dGx0Mm96V0lGelEzSlV1V289BjsARkkiCXVzZXIGOwBGSSIlZGZjN2U4Mjk0ZmRiMTE2ZGNiOWExYWZiODkxMmQxNzIGOwBUSSINZnVsbG5hbWUGOwBGSSIO5p2o5r6N55GeBjsAVEkiCmxvZ2luBjsARkkiEWh6eWFuZ3NodXJ1aQY7AFQ%3D--e64692bcd315f83dc5b2c103b7cdc0c7e8dd713b&quot; -n 20 -c 2 localhost:3000/versions/596
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;-C参数代表了设定cookie值，格式是&lt;code class=&quot;highlighter-rouge&quot;&gt;cookie-name=value&lt;/code&gt;这种形式。&lt;/p&gt;

&lt;h3 id=&quot;参考&quot;&gt;参考&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://httpd.apache.org/docs/2.2/programs/ab.html&quot;&gt;Apache Benchmark文档&lt;/a&gt;&lt;/p&gt;</content><author><name></name></author><summary type="html">写在前面 不要总是“我觉得”，你应该编写Benchmark测试对比</summary></entry><entry><title type="html">聊聊HTML5的histroy相关API</title><link href="http://yangsr.github.io/Ajax-Histroy-State/" rel="alternate" type="text/html" title="聊聊HTML5的histroy相关API" /><published>2015-11-15T00:00:00+00:00</published><updated>2015-11-15T00:00:00+00:00</updated><id>http://yangsr.github.io/Ajax-Histroy-State</id><content type="html" xml:base="http://yangsr.github.io/Ajax-Histroy-State/">&lt;h3 id=&quot;平时没有注意到的浏览器url细节&quot;&gt;平时没有注意到的浏览器URL细节&lt;/h3&gt;
&lt;p&gt;Ajax技术能够大大提升网站的用户体验，我们的平台中也包括了诸多Ajax请求。这次遇到的需求场景是：当用户点击一个Tab标签，发送一次Ajax请求，服务器端给出响应后，浏览器的URL并不会对应地做出改变。而用户恰恰希望能够获取到最新的URL，这样就可以把这个URL发送给别人，别人点击该URL后进入平台看到对应的界面。&lt;/p&gt;

&lt;p&gt;类似的体验你一定在Github上遇到过：当我们在Github上浏览一个项目下的文件时，一层层文件夹点击进去，虽然发送的都是Ajax请求，但是如果细心观察一下，浏览器的URL也是在相应改变的。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/github-example.png&quot; alt=&quot;Github示意&quot; title=&quot;Github示意图&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;historypushstate方法&quot;&gt;history.pushState方法&lt;/h3&gt;
&lt;p&gt;要实现这个效果，其实只需要在你的javascript click事件代码中添加一句话就可以了：&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &lt;span class=&quot;nx&quot;&gt;history&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pushState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;我们来看看文档上是怎么描述这个方法的作用的吧：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;这将让浏览器的地址栏显示href，但不会加载href页面也不会检查href是否存在。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;pushState方法接收三个参数：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;第一个参数叫状态对象(state object)，任何可序列化的对象都可以，最大640K。当popstate事件被触发时，事件对象的state属性包含历史记录条目的状态对象的拷贝。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;第二个参数当前会被浏览器忽略，所以可以暂时不去关心它。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;第三个参数就是对应的URL，既可以是绝对路径，也可以是相对路径，但必须与当前URL同源，否则将抛出异常。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;popstate事件&quot;&gt;popstate事件&lt;/h3&gt;
&lt;p&gt;到目前为止，我们已经解决了发送Ajax请求URL也会相应改变的问题。我们再试试点击浏览器的回退按钮，页面并没有正常地回退，对吧？&lt;/p&gt;

&lt;p&gt;看看我们刚才提到的popstate事件，到它发挥作用的时候啦：&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;popstate&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// 需要注意的是，getScript发送的是AJAX请求&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getScript&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;location&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;每当激活的histroy entry发生变化时，都会触发popstate事件。当我们点击后退按钮的时候，浏览器的URL其实已经改变了，只是页面没有变化，于是我们只要执行getScript方法，并且把当前浏览器的URL传递给它就好啦。&lt;/p&gt;

&lt;p&gt;添加完上述代码后再试试，是不是能够正常后退了呢。&lt;/p&gt;

&lt;h3 id=&quot;余下的工作&quot;&gt;余下的工作&lt;/h3&gt;
&lt;p&gt;如果你和我一样使用Rails作为Web开发框架，那么你现在会发现还需要处理额外的工作：即在controller层相应的action中添加对HTML请求的处理。&lt;/p&gt;

&lt;p&gt;当我们通过Ajax请求进入的新页面后，按F5刷新，请求的将是更新后的URL，这时发送的就不再是Ajax请求，而是一个正常的HTML请求，在controller层找不到对应的请求处理方式的话，将会报错。&lt;/p&gt;

&lt;h3 id=&quot;参考&quot;&gt;参考&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;http://railscasts.com/episodes/246-ajax-history-state?view=asciicast&quot;&gt;Rails Cast&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/History_API&quot;&gt;mozilla History_API 官方文档&lt;/a&gt;&lt;/p&gt;</content><author><name></name></author><summary type="html">平时没有注意到的浏览器URL细节 Ajax技术能够大大提升网站的用户体验，我们的平台中也包括了诸多Ajax请求。这次遇到的需求场景是：当用户点击一个Tab标签，发送一次Ajax请求，服务器端给出响应后，浏览器的URL并不会对应地做出改变。而用户恰恰希望能够获取到最新的URL，这样就可以把这个URL发送给别人，别人点击该URL后进入平台看到对应的界面。</summary></entry><entry><title type="html">导航栏悬浮定位效果的实现思路</title><link href="http://yangsr.github.io/Affix-And-Scrollspy/" rel="alternate" type="text/html" title="导航栏悬浮定位效果的实现思路" /><published>2015-08-05T00:00:00+00:00</published><updated>2015-08-05T00:00:00+00:00</updated><id>http://yangsr.github.io/Affix-And-Scrollspy</id><content type="html" xml:base="http://yangsr.github.io/Affix-And-Scrollspy/">&lt;h3 id=&quot;需求说明&quot;&gt;需求说明&lt;/h3&gt;
&lt;p&gt;最近网站改版，需要实现一个悬浮定位导航栏，类似效果见下图（&lt;a href=&quot;http://v3.bootcss.com/components/&quot;&gt;Bootstrap页面示例&lt;/a&gt;）：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/demo.png&quot; alt=&quot;Bootstrap悬浮定位导航示例&quot; title=&quot;Bootstrap悬浮定位导航示例&quot; /&gt;&lt;/p&gt;

&lt;p&gt;本篇博文将主要讲述该效果的具体实现细节，实现依赖于Bootstrap框架。&lt;/p&gt;

&lt;h3 id=&quot;搭建框架&quot;&gt;搭建框架&lt;/h3&gt;
&lt;p&gt;首先，整个html页面的基础框架结构是这样的：&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;col-md-3&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;ul&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;nav&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;nav hidden-xs hidden-sm&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;li&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;a&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;#web-design&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Web Design&lt;span class=&quot;nt&quot;&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;li&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;a&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;#web-development&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Web Development&lt;span class=&quot;nt&quot;&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;ul&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;nav&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;li&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;a&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;#ruby&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;span&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;fa fa-angle-double-right&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/span&amp;gt;&lt;/span&gt;Ruby
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;li&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;a&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;#python&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;span&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;fa fa-angle-double-right&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/span&amp;gt;&lt;/span&gt;Python
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;&amp;lt;!--end of sub navigation--&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;&amp;lt;!-- end of main navigation --&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;col-md-9&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;section&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;web-design&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;section&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;web-development&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;section&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ruby&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;section&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;python&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;我们将页面划分为3:9的两部分，左侧为导航栏，右侧为具体内容。&lt;/p&gt;

&lt;p&gt;在这个例子中，我们希望当屏幕缩小到一定比例时，悬浮定位导航栏自动隐藏，因此我们为导航添加了&lt;code class=&quot;highlighter-rouge&quot;&gt;hidden-xs&lt;/code&gt;和&lt;code class=&quot;highlighter-rouge&quot;&gt;hidden-sm&lt;/code&gt;类。&lt;/p&gt;

&lt;p&gt;每一个&lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;section&amp;gt;&lt;/code&gt;都有一个id，对应着&lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;a&amp;gt;&lt;/code&gt;标签中的href属性，即锚点。&lt;/p&gt;

&lt;h3 id=&quot;affix控件&quot;&gt;Affix控件&lt;/h3&gt;
&lt;p&gt;Bootstrap的Affix控件在这里的主要功能是实现当页面向下滑动时导航栏的悬停效果，要使用Affix控件很容易，直接将&lt;code class=&quot;highlighter-rouge&quot;&gt;data-spy=&quot;affix&quot;&lt;/code&gt;添加到导航栏上就好。&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;ul&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;nav&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;nav hidden-xs hidden-sm&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;data-spy=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;affix&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;data-offset-top=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;380&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;注意上图中的&lt;code class=&quot;highlighter-rouge&quot;&gt;data-offset-top&lt;/code&gt;属性，它设置的是导航栏距离顶部多少像素时开始悬停。&lt;/p&gt;

&lt;p&gt;下面稍稍解释下本例中Affix控件的工作流程：&lt;/p&gt;

&lt;p&gt;1.初始状态下，我们可以注意到控件将&lt;code class=&quot;highlighter-rouge&quot;&gt;affix-top&lt;/code&gt;添加导航栏的类中：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/affix_top.png&quot; alt=&quot;affix-top示例&quot; title=&quot;affix-top示例&quot; /&gt;&lt;/p&gt;

&lt;p&gt;2.当页面滚动超过我们设定的像素距离后，控件用&lt;code class=&quot;highlighter-rouge&quot;&gt;affix&lt;/code&gt;替换了&lt;code class=&quot;highlighter-rouge&quot;&gt;affix-top&lt;/code&gt;：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/affix.png&quot; alt=&quot;affix示例&quot; title=&quot;affix示例&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这时我们可以通过设置css样式调整导航栏悬停时的效果：&lt;/p&gt;

&lt;div class=&quot;language-css highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nc&quot;&gt;.affix&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;top&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;20px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;213px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;scrollspy控件&quot;&gt;ScrollSpy控件&lt;/h3&gt;
&lt;p&gt;Bootstrap的滚动监听ScrollSpy控件在这里的作用是当页面主体内容改变时，导航条也要有对应的变化（子章节的展开、高亮等）。&lt;/p&gt;

&lt;p&gt;ScrollSpy控件依赖Bootstrap的&lt;a href=&quot;http://v3.bootcss.com/components/#nav&quot;&gt;导航控件&lt;/a&gt;用于高亮当前激活的链接。&lt;/p&gt;

&lt;p&gt;同时，需要被监听的组件是&lt;code class=&quot;highlighter-rouge&quot;&gt;position: relative;&lt;/code&gt;，即相对定位方式，大多时候是监听body元素。&lt;/p&gt;

&lt;p&gt;我们可以通过data属性调用ScrollSpy控件，将&lt;code class=&quot;highlighter-rouge&quot;&gt;data-spy=&quot;scroll&quot;&lt;/code&gt;添加到你希望监听的元素上（&lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;body&amp;gt;&lt;/code&gt;），同时将&lt;code class=&quot;highlighter-rouge&quot;&gt;data-target&lt;/code&gt;设置为本例中导航栏的class：&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;body&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;data-spy=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;scroll&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;data-target=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;.scrollspy&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;&amp;lt;!-- content here... --&amp;gt;&lt;/span&gt;

  &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;col-md-3 scrollspy&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;ul&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;nav&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;nav hidden-xs hidden-sm&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;data-spy=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;affix&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- nav items here... --&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

  &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- content here... --&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;记得设置&lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;body&amp;gt;&lt;/code&gt;元素的CSS:&lt;/p&gt;

&lt;div class=&quot;language-css highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;nt&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;relative&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;当用户滑动鼠标滚轮，页面内容发生变化时，我们可以观察到ScrollSpy控件将&lt;code class=&quot;highlighter-rouge&quot;&gt;active&lt;/code&gt;添加到对应导航栏中的&lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;li&amp;gt;&lt;/code&gt;元素的类中：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/scroll_spy.png&quot; alt=&quot;ScrollSpy示例&quot; title=&quot;ScrollSpy示例&quot; /&gt;&lt;/p&gt;

&lt;p&gt;接下来要做的就是为导航栏的各种状态添加一些css效果即可：&lt;/p&gt;

&lt;div class=&quot;language-css highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nc&quot;&gt;.nav&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;.active&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;font-weight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;bold&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;#72bcd4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nc&quot;&gt;.nav&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;.nav&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;none&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nc&quot;&gt;.nav&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;.active&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;.nav&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nc&quot;&gt;.nav&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;.nav&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;font-weight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;normal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;font-size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;.85em&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nc&quot;&gt;.nav&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;.nav&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;span&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;margin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;5px&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nc&quot;&gt;.nav&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;.nav&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;.active&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;.nav&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;.nav&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;.active&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;:hover&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;.nav&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;.nav&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;.active&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;:focus&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;font-weight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;bold&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;padding-left&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;30px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;border-left&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;5px&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;solid&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;black&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nc&quot;&gt;.nav&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;.nav&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;.active&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;span&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;.nav&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;.nav&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;.active&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;:hover&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;span&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;.nav&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;.nav&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;.active&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;:focus&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;span&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;none&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 id=&quot;参考&quot;&gt;参考&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;http://www.sitepoint.com/understanding-bootstraps-affix-scrollspy-plugins/&quot;&gt;Understanding Bootstrap’s Affix and ScrollSpy plugins&lt;/a&gt;&lt;/p&gt;</content><author><name></name></author><summary type="html">需求说明 最近网站改版，需要实现一个悬浮定位导航栏，类似效果见下图（Bootstrap页面示例）：</summary></entry><entry><title type="html">微信开放平台Omniauth探索</title><link href="http://yangsr.github.io/Wechat-Omniauth/" rel="alternate" type="text/html" title="微信开放平台Omniauth探索" /><published>2015-05-03T00:00:00+00:00</published><updated>2015-05-03T00:00:00+00:00</updated><id>http://yangsr.github.io/Wechat-Omniauth</id><content type="html" xml:base="http://yangsr.github.io/Wechat-Omniauth/">&lt;h3 id=&quot;准备&quot;&gt;准备&lt;/h3&gt;
&lt;p&gt;最近在折腾网站的微信登录功能，最终希望实现的效果是类似&lt;a href=&quot;https://passport.yhd.com/passport/login_input.do&quot;&gt;一号店这样的微信登录&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;为此首先需要在&lt;a href=&quot;https://open.weixin.qq.com/&quot;&gt;微信开放平台&lt;/a&gt;上注册开发者账号，创建网站应用，获得对应的AppID和AppSecret，并申请微信登录接口。在这个过程中，会被要求填写网站信息，包括授权回调域和官网网址。&lt;/p&gt;

&lt;p&gt;这里需要注意的是授权回调域的填写，刚开始我是这么填写的：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;example.com/auth/wechat/callback&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;然而这么填写将会在后面的OAuth认证时提示redirect_uri参数错误。微信开放平台并没有给出授权回调域的官方填写说明，我是在&lt;a href=&quot;https://mp.weixin.qq.com/&quot;&gt;微信公众平台&lt;/a&gt;的开发者文档里看到了这么一段话：&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;关于网页授权回调域名的说明&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;请注意，这里填写的是域名（是一个字符串），而不是URL，因此请勿加http://等协议头；&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;授权回调域名配置规范为全域名，比如需要网页授权的域名为：www.qq.com，配置以后此域名下面的页面http://www.qq.com/music.html 、 http://www.qq.com/login.html 都可以进行OAuth2.0鉴权。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;哦，原来改成这样就可以了：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;example.com&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;调试&quot;&gt;调试&lt;/h3&gt;
&lt;p&gt;接下来，我想的是到底该如何调试，因为授权回调域不能填成localhost，总不至于在线上调试吧。&lt;/p&gt;

&lt;p&gt;搜索后发现有人推荐一款叫ngrok的软件，只需要注册并下载ngrok，就可以通过ngrok获得一个外网域名，而这个外网域名实际访问的是本地主机。&lt;/p&gt;

&lt;p&gt;具体ngrok的注册下载流程省略，按照&lt;a href=&quot;https://ngrok.com/&quot;&gt;ngrok官网&lt;/a&gt;一步步来就可以了，这里需要自带梯子。&lt;/p&gt;

&lt;p&gt;之后本地执行./ngrok http -subdomain=example 3000，出现下面的画面就算成功啦：
&lt;img src=&quot;/images/ngrok.png&quot; alt=&quot;ngrok&quot; title=&quot;ngrok&quot; /&gt;
那么开发阶段，就把刚才提到的授权回调域改成自己设定的ngrok地址吧。&lt;/p&gt;

&lt;h3 id=&quot;oauth流程&quot;&gt;OAuth流程&lt;/h3&gt;
&lt;p&gt;整个微信登录的流程是如下图所示的：
&lt;img src=&quot;/images/oauth_process.png&quot; alt=&quot;微信登录时序图&quot; title=&quot;微信登录时序图&quot; /&gt;&lt;/p&gt;

&lt;p&gt;OAuth过程可以使用omniauth，omniauth 是一个利用 Rack 中间件实现的灵活的认证系统。&lt;/p&gt;

&lt;p&gt;omniauth需要结合具体平台的strategy使用，比如你需要做github账号的登录，那就需要&lt;a href=&quot;https://github.com/intridea/omniauth-github&quot;&gt;omniauth-github&lt;/a&gt;这个gem。omniauth官方列出了社区维护的各个平台的&lt;a href=&quot;https://github.com/intridea/omniauth/wiki/List-of-Strategies&quot;&gt;strategy list&lt;/a&gt;，我在list中找到了微信的omniauth strategy：&lt;a href=&quot;https://github.com/skinnyworm/omniauth-wechat-oauth2&quot;&gt;omniauth-wechat-oauth2&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;具体怎么使用应该首先看看README的文档，我读到了这么一句话：&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;You need to get a wechat API key at: http://mp.weixin.qq.com&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;这不是微信公众平台的地址么，可我需要的是微信开放平台的OAuth认证啊，这两个微信平台的OAuth流程是否一致，需要确定下。于是我开始打开了微信开放平台和微信公众平台的OAuth流程的文档进行对比，发现果然有些细节处不一致，比如第一步请求code时，两者请求地址的形式略有不同，微信开放平台的请求code地址是：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;https://open.weixin.qq.com/connect/qrconnect?appid=APPID&lt;span class=&quot;err&quot;&gt;&amp;amp;&lt;/span&gt;redirect_uri=REDIRECT_URI&lt;span class=&quot;err&quot;&gt;&amp;amp;&lt;/span&gt;response_type=code&lt;span class=&quot;err&quot;&gt;&amp;amp;&lt;/span&gt;scope=SCOPE&lt;span class=&quot;err&quot;&gt;&amp;amp;&lt;/span&gt;state=STATE#wechat_redirect&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;而微信公众平台的请求code地址却是：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&lt;span class=&quot;err&quot;&gt;&amp;amp;&lt;/span&gt;redirect_uri=REDIRECT_URI&lt;span class=&quot;err&quot;&gt;&amp;amp;&lt;/span&gt;response_type=code&lt;span class=&quot;err&quot;&gt;&amp;amp;&lt;/span&gt;scope=SCOPE&lt;span class=&quot;err&quot;&gt;&amp;amp;&lt;/span&gt;state=STATE#wechat_redirect&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;我去查看了omniauth-wechat-oauth2的strategy源码：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# /lib/omniauth/strategies/wechat.rb&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;option&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:client_options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;ss&quot;&gt;site:          &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://api.weixin.qq.com&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;ss&quot;&gt;authorize_url: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://open.weixin.qq.com/connect/oauth2/authorize#wechat_redirect&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;ss&quot;&gt;token_url:     &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/sns/oauth2/access_token&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;ss&quot;&gt;token_method:  :get&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;这个authorize_url的地址显然是微信公众平台请求code时的格式，这就意味着这个gem并不是我需要的。&lt;/p&gt;

&lt;p&gt;不过我发现微信公众平台和微信开放平台的基本OAuth流程是类似的，于是我参考omniauth-wechat-oauth2的源码，自己实现了微信开放平台的omniauth strategy，并托管在了&lt;a href=&quot;https://github.com/yangsr/omniauth-wechat-oauth2&quot;&gt;GitHub上&lt;/a&gt;。这里感谢一下omniauth-wechat-oauth2的原作者skinnyworm，复用了大部分他的代码。&lt;/p&gt;

&lt;h3 id=&quot;csrf错误&quot;&gt;CSRF错误&lt;/h3&gt;
&lt;p&gt;剩下的工作就和其他平台的OAuth流程差不多了，这里就不再赘述，具体可以看一下文章后的参考链接。&lt;/p&gt;

&lt;p&gt;只是这里遇到了一个CSRF错误，特别提一下：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;no&quot;&gt;Started&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;GET&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;/auth/wechat/callback?code=xxxxxxxx&amp;amp;state=xxxxxxxx&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;183.157&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;160.37&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;at&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2015&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mo&quot;&gt;04&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;28&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;22&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;26&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;48&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0800&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;I&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2015&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mo&quot;&gt;04&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;28&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;T22&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;26&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;48.017297&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#18928]  INFO -- omniauth: (wechat) Callback phase initiated.&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2015&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mo&quot;&gt;04&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;28&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;T22&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;26&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;48.017785&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#18928] ERROR -- omniauth: (wechat) Authentication failure! csrf_detected: OmniAuth::Strategies::OAuth2::CallbackError, csrf_detected | CSRF detected&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2015&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mo&quot;&gt;04&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;28&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;T22&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;26&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;48.017891&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#18928] ERROR -- omniauth: (wechat) Authentication failure! invalid_credentials: OmniAuth::Strategies::OAuth2::CallbackError, csrf_detected | CSRF detected&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;这是因为我本地调试时习惯性地打开了localhost:3000，而我又开启了ngrok，调试时应该直接打开example.ngrok.io的。&lt;/p&gt;

&lt;h3 id=&quot;参考&quot;&gt;参考&lt;/h3&gt;
&lt;p&gt;railscasts-china的&lt;a href=&quot;http://railscasts-china.com/episodes/omniauth-1&quot;&gt;Omniauth 1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;happypeter的&lt;a href=&quot;http://haoduoshipin.com/episodes/98&quot;&gt;login-with-linkedin&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;感兴趣的同学还可以拓展阅读下&lt;a href=&quot;http://www.zhihu.com/question/21074751&quot;&gt;微信开放平台和微信公众平台的区别&lt;/a&gt;&lt;/p&gt;</content><author><name></name></author><summary type="html">准备 最近在折腾网站的微信登录功能，最终希望实现的效果是类似一号店这样的微信登录。</summary></entry><entry><title type="html">Rails Mailer View层整合Bootstrap实践</title><link href="http://yangsr.github.io/Mailer-With-Bootstrap/" rel="alternate" type="text/html" title="Rails Mailer View层整合Bootstrap实践" /><published>2015-01-13T00:00:00+00:00</published><updated>2015-01-13T00:00:00+00:00</updated><id>http://yangsr.github.io/Mailer-With-Bootstrap</id><content type="html" xml:base="http://yangsr.github.io/Mailer-With-Bootstrap/">&lt;h3 id=&quot;email的局限性&quot;&gt;Email的局限性&lt;/h3&gt;
&lt;p&gt;最近平台发出去的Email界面频频被用户吐槽，”完全就是纯文本“。当初写mailer功能时，曾乐观地以为可以直接复用已有的view页面，这样也便于以后的维护。结果发现：原来Email的格式相对于HTML有诸多局限：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;无法链接外部CSS、Fonts&lt;/li&gt;
  &lt;li&gt;无法使用&lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;head&amp;gt;&lt;/code&gt;标签&lt;/li&gt;
  &lt;li&gt;Javascript被限制&lt;/li&gt;
  &lt;li&gt;部分CSS属性的跨平台跨邮件服务商的兼容性问题：比如margin在Hotmail中就被忽略&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;既然限制这么多，那应该怎样设置Email的样式呢？答案是使用&lt;strong&gt;内联样式&lt;/strong&gt;，比如：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;h1&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;style=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;color: red;&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Hello&lt;span class=&quot;nt&quot;&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;premailer&quot;&gt;Premailer&lt;/h3&gt;
&lt;p&gt;将Email页面中的每部分都加上内联样式是一项非常枯燥的工作，而且不便于维护，好在一个叫&lt;a href=&quot;https://github.com/fphilipe/premailer-rails&quot;&gt;Premailer&lt;/a&gt;的Gem包可以很好地完成这方面工作。&lt;/p&gt;

&lt;p&gt;只需要将&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'nokogiri'&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'premailer-rails'&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;添加到你的Gemfile里，然后bundle一下就可以完成安装了。&lt;/p&gt;

&lt;p&gt;这样，当你发送一封邮件的时候，Premailer会以一定的顺序(Cache,File System,Asset Pipeline,Network)去搜集CSS，并将它们嵌入Email页面中HTML元素的style属性中。&lt;/p&gt;

&lt;h3 id=&quot;整合bootstrap&quot;&gt;整合Bootstrap&lt;/h3&gt;
&lt;p&gt;我们平台的前端使用了Bootstrap-sass，为了在email中实现bootstrap中一些组件的效果，在app/assets/stylesheets目录下新建email.css.scss文件，内容如下：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-scss&quot; data-lang=&quot;scss&quot;&gt;&lt;span class=&quot;k&quot;&gt;@import&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;bootstrap-sprockets&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;@import&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;bootstrap/variables&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;@import&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;bootstrap/mixins&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;@import&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;bootstrap/scaffolding&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;@import&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;bootstrap/type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;@import&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;bootstrap/buttons&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;@import&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;bootstrap/alerts&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;@import&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'bootstrap/normalize'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;@import&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'bootstrap/tables'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;@import&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'bootstrap/progress-bars'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;然后，在Mailer的View层的&lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;head&amp;gt;&lt;/code&gt;标签中，加入：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-erb&quot; data-lang=&quot;erb&quot;&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;%=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stylesheet_link_tag&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;email&quot;&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;%&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;这样，一些Bootstrap的组件就可以在Email中显示了。&lt;/p&gt;

&lt;p&gt;要注意的额外事情是：
如果我们使用了Rails的Asset Pipeline机制，最终这个email.css.scss文件会被打包进application.css.scss文件中，而我们仅仅希望这个文件只被用在邮件发送中，因此需要把它过滤掉。&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# application.css.scss&lt;/span&gt;
&lt;span class=&quot;sr&quot;&gt;/*
*= require_self
*= require_tree .
*= stub email
*/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;测试&quot;&gt;测试&lt;/h3&gt;
&lt;p&gt;前面已经说过，Email中会遇到一些CSS属性在不同平台不同邮件服务商下的兼容性问题，当你引入Bootstrap后，同样也需要仔细测试。&lt;/p&gt;

&lt;p&gt;你需要：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;测试你的邮件在IE/Chrome/Firefox等主流浏览器下的显示效果&lt;/li&gt;
  &lt;li&gt;测试你的邮件在不同邮件服务提供商(Gmail/网易邮箱/qq邮箱)下的显示效果&lt;/li&gt;
  &lt;li&gt;测试你的邮件在Outlook/Thunderbird/网易闪电邮等邮件接收客户端上的显示效果&lt;/li&gt;
  &lt;li&gt;如果考虑到用户可能会使用手机接收邮件，你同样需要进行相应的测试&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href=&quot;https://litmus.com/pub/d831ecc/screenshots&quot;&gt;这里&lt;/a&gt;有一些别人已经测试过的截图，供参考。&lt;/p&gt;</content><author><name></name></author><summary type="html">Email的局限性 最近平台发出去的Email界面频频被用户吐槽，”完全就是纯文本“。当初写mailer功能时，曾乐观地以为可以直接复用已有的view页面，这样也便于以后的维护。结果发现：原来Email的格式相对于HTML有诸多局限：</summary></entry></feed>