<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[古井龙王的博客]]></title><description><![CDATA[技术与生活(本博客下的所有文章都是原创博文，转载请注明来源)]]></description><link>http://www.fengshangbin.com/</link><image><url>http://www.fengshangbin.com/favicon.png</url><title>古井龙王的博客</title><link>http://www.fengshangbin.com/</link></image><generator>Ghost 3.42</generator><lastBuildDate>Sat, 14 Mar 2026 02:54:42 GMT</lastBuildDate><atom:link href="http://www.fengshangbin.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[正则匹配 HTML 嵌套元素(三)]]></title><description><![CDATA[<!--kg-card-begin: markdown--><h1 id="html">正则匹配 HTML 嵌套元素(三)</h1>
<p><a href="http://www.fengshangbin.com/match-html2/">上一篇</a> 介绍了混合条件查询, 我们再增加写入功能就是一个完整的Dom解析类库, 我已经开源如下</p>
<h1 id="fastdomparsenode">FastDomParse-node</h1>
<p>fast dom parse for node.js<br>
GitHub Pages: <a href="https://github.com/fengshangbin/FastDomParse-node">https://github.com/fengshangbin/FastDomParse-node</a></p>
<h1 id="fastdomparsejava">FastDomParse-java</h1>
<p>fast dom parse for java<br>
GitHub Pages: <a href="https://github.com/fengshangbin/FastDomParse-java">https://github.com/fengshangbin/FastDomParse-java</a></p>
<h1 id="fastdomparsenode">如何使用 FastDomParse-node</h1>
<pre><code>npm install --save fastdomparse-node
...
const fastdom = require(&quot;fastdomparse-node&quot;);

let dom = new</code></pre>]]></description><link>http://www.fengshangbin.com/match-html3/</link><guid isPermaLink="false">5ef7ef162029ae00012565e3</guid><category><![CDATA[后端]]></category><category><![CDATA[前端]]></category><category><![CDATA[开源]]></category><dc:creator><![CDATA[fengshangbin]]></dc:creator><pubDate>Sun, 28 Jun 2020 01:25:44 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><h1 id="html">正则匹配 HTML 嵌套元素(三)</h1>
<p><a href="http://www.fengshangbin.com/match-html2/">上一篇</a> 介绍了混合条件查询, 我们再增加写入功能就是一个完整的Dom解析类库, 我已经开源如下</p>
<h1 id="fastdomparsenode">FastDomParse-node</h1>
<p>fast dom parse for node.js<br>
GitHub Pages: <a href="https://github.com/fengshangbin/FastDomParse-node">https://github.com/fengshangbin/FastDomParse-node</a></p>
<h1 id="fastdomparsejava">FastDomParse-java</h1>
<p>fast dom parse for java<br>
GitHub Pages: <a href="https://github.com/fengshangbin/FastDomParse-java">https://github.com/fengshangbin/FastDomParse-java</a></p>
<h1 id="fastdomparsenode">如何使用 FastDomParse-node</h1>
<pre><code>npm install --save fastdomparse-node
...
const fastdom = require(&quot;fastdomparse-node&quot;);

let dom = new fastdom(htmlString);

//单目标查询
let element = dom.querySelector(&quot;div.page li[name=1]&quot;);

//多目标查询
let elements = dom.querySelectorAll(&quot;div.page li&quot;);

//目标子查询
let sun = element.querySelector(&quot;div.page li[name=1]&quot;);
let suns = element.querySelectorAll(&quot;div.page li[name=1]&quot;);

//element属性
let inner = element.getInnerHTML();
element.setInnerHTML(&quot;hello fast dom parse&quot;);

let outer = element.getOuterHTML();
element.setOuterHTML(&quot;&lt;div&gt;hello fast dom parse&lt;div&gt;&quot;);

let attr = element.getAttribute(&quot;id&quot;);
element.setAttribute(&quot;id&quot;, &quot;content&quot;);
let hasID = element.hasAttribute(&quot;id&quot;);

//获取整个dom内容
dom.html
</code></pre>
<h1 id="fastdomparsejava">如何使用 FastDomParse-java</h1>
<p>导入fastdomparse.jar</p>
<pre><code>FastDom dom = new FastDom(htmlString)

//单目标查询
Element element = dom.querySelector(&quot;div.page li[name=1]&quot;);

//多目标查询
ArrayList&lt;Element&gt; elements = dom.querySelectorAll(&quot;div.page li&quot;);

//目标子查询
Element sun = element.querySelector(&quot;div.page li[name=1]&quot;);
ArrayList&lt;Element&gt; suns = element.querySelectorAll(&quot;div.page li[name=1]&quot;);

//element属性
String inner = element.getInnerHTML();
element.setInnerHTML(&quot;hello fast dom parse&quot;);

String outer = element.getOuterHTML();
element.setOuterHTML(&quot;&lt;div&gt;hello fast dom parse&lt;div&gt;&quot;);

String attr = element.getAttribute(&quot;id&quot;);
element.setAttribute(&quot;id&quot;, &quot;content&quot;);
boolean hasID = element.hasAttribute(&quot;id&quot;);

//获取整个dom内容
dom.getHTML()
</code></pre>
<p>熟悉的味道，一样的配方，和原生 DOM 一样的语法，无需新学习，使用简单。</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[lazyswitch]]></title><description><![CDATA[<h1 id="lazyswitch">lazyswitch</h1>
<p>LazySwitch 让多页也可以像单页一样做页面切换动画<br>
GitHub Pages: <a href="https://github.com/fengshangbin/LazySwitch">https://github.com/fengshangbin/LazySwitch</a></p>
<h1 id>现实问题</h1>
<p>单页 VS 多页<br>
1, 单页可以在切换视图时使用酷炫的过度动画，但会所有内容放在一个页面会增加开发难度，同时会消耗很多实际没用到的网络流量，如果内容是动态生成的如博客的博文，就很难做单页了。<br>
2，多页传统模式，不支持页面切换动画。</p>
<h1 id="lazyswitch">关于 LazySwitch</h1>
<p>LazySwitch 是一个 js 插件，实现页面切换的各种动画。<br>
主要特点<br>
1, 支持多页面间的切换动画。<br>
2, 无依赖，轻量级，使用简单。</p>
<h1 id>在线示例</h1>
<p><a href="https://www.fengshangbin.com/node/lazyswitch/">https://www.fengshangbin.com/node/lazyswitch/</a></p>
<h1 id>如何使用</h1>
<h3 id="1lazyswitch">1. 引入 LazySwitch</h3>
<p>引用 lazyswitch.</p>]]></description><link>http://www.fengshangbin.com/lazyswitch/</link><guid isPermaLink="false">5eef0c892029ae00012565d7</guid><category><![CDATA[开源]]></category><category><![CDATA[前端]]></category><dc:creator><![CDATA[fengshangbin]]></dc:creator><pubDate>Sun, 21 Jun 2020 07:31:08 GMT</pubDate><content:encoded><![CDATA[<h1 id="lazyswitch">lazyswitch</h1>
<p>LazySwitch 让多页也可以像单页一样做页面切换动画<br>
GitHub Pages: <a href="https://github.com/fengshangbin/LazySwitch">https://github.com/fengshangbin/LazySwitch</a></p>
<h1 id>现实问题</h1>
<p>单页 VS 多页<br>
1, 单页可以在切换视图时使用酷炫的过度动画，但会所有内容放在一个页面会增加开发难度，同时会消耗很多实际没用到的网络流量，如果内容是动态生成的如博客的博文，就很难做单页了。<br>
2，多页传统模式，不支持页面切换动画。</p>
<h1 id="lazyswitch">关于 LazySwitch</h1>
<p>LazySwitch 是一个 js 插件，实现页面切换的各种动画。<br>
主要特点<br>
1, 支持多页面间的切换动画。<br>
2, 无依赖，轻量级，使用简单。</p>
<h1 id>在线示例</h1>
<p><a href="https://www.fengshangbin.com/node/lazyswitch/">https://www.fengshangbin.com/node/lazyswitch/</a></p>
<h1 id>如何使用</h1>
<h3 id="1lazyswitch">1. 引入 LazySwitch</h3>
<p>引用 lazyswitch.css 到页面</p>
<pre><code>&lt;link href=&quot;/css/lazyswitch.css&quot; rel=&quot;stylesheet&quot; type=&quot;text/css&quot; /&gt;
</code></pre>
<p>引用 lazyswitch.js 到页面</p>
<pre><code>&lt;script src=&quot;js/lazyswitch.js&quot;&gt;&lt;/script&gt;
</code></pre>
<p>或者 npm 导入</p>
<pre><code>npm install --save lazyswitch

...

const LazySwitch = require(&quot;lazyswitch&quot;);
</code></pre>
<p>给执行切换动画的单元添加 class=&quot;lazyswitch&quot;</p>
<pre><code>&lt;div class=&quot;lazyswitch&quot;&gt;
	...
&lt;/div&gt;
</code></pre>
<h3 id="3">3. 单页面存在多个单元</h3>
<p>如 $.html</p>
<pre><code>&lt;div class=&quot;lazyswitch in&quot; data-path=&quot;/2019.html&quot;&gt;2019&lt;/div&gt;
&lt;div class=&quot;lazyswitch out&quot; data-path=&quot;/2018.html&quot;&gt;2018&lt;/div&gt;
&lt;div class=&quot;lazyswitch out&quot; data-path=&quot;/2017.html&quot;&gt;2017&lt;/div&gt;
</code></pre>
<p>多单元只能有一个 lazyswitch 元素是展示状态 CSS 样式 in，其他 lazyswitch 的 css 样式为 out<br>
每个单元需要配置对应的路径属性 data-path=&quot;xxx&quot;<br>
同时需要把 URL domain/2019.html, domain/2019.html, domain/2019.html 的访问指向这个单页面 html 文件，这需要后台语言的支持，也可以结合 LazyPage 框架 使用，让前端也可设置访问 URL<br>
关于 LazyPage 框架, 参见 <a href="https://github.com/fengshangbin/LazyPage">https://github.com/fengshangbin/LazyPage</a></p>
<h3 id="4">4. 页面切换动画</h3>
<p>lazyswitch.css 内置了 slide、slidevertical、fade、popup 四种切换动画，您也可以自己定义自己需要的动画样式<br>
lazyswitch 默认使用 slide 进行页面切换</p>
<pre><code>&lt;div class=&quot;lazyswitch&quot; data-animate=&quot;slidevertical&quot;&gt;...&lt;/div&gt;
</code></pre>
<p>属性 data-animate 为该页面切换时的动画样式</p>
<h3 id="5">5. 页面顺序</h3>
<p>当页面动态载入时会按照属性 data-sort 的值去插入 dom 结构中<br>
如 news/2017.html 代码</p>
<pre><code>&lt;div class=&quot;lazyswitch&quot; data-sort=&quot;2017&quot;&gt;2017&lt;/div&gt;
</code></pre>
<p>news/2018.html 代码</p>
<pre><code>&lt;div class=&quot;lazyswitch&quot; data-sort=&quot;2018&quot;&gt;2018&lt;/div&gt;
</code></pre>
<p>news/2019.html 代码</p>
<pre><code>&lt;div class=&quot;lazyswitch&quot; data-sort=&quot;2019&quot;&gt;2019&lt;/div&gt;
</code></pre>
<p>当从 news/2018.html 跳转到 news/2019.html 时 dom 结构会变为</p>
<pre><code>&lt;div class=&quot;lazyswitch out&quot; data-path=&quot;/news/2018.html&quot; data-sort=&quot;2018&quot;&gt;2018&lt;/div&gt;
&lt;div class=&quot;lazyswitch in&quot; data-path=&quot;/news/2019.html&quot; data-sort=&quot;2019&quot;&gt;2019&lt;/div&gt;
</code></pre>
<p>而从 news/2018.html 跳转到 news/2017.html 时 dom 结构会变为</p>
<pre><code>&lt;div class=&quot;lazyswitch in&quot; data-path=&quot;/news/2017.html&quot; data-sort=&quot;2017&quot;&gt;2017&lt;/div&gt;
&lt;div class=&quot;lazyswitch out&quot; data-path=&quot;/news/2018.html&quot; data-sort=&quot;2018&quot;&gt;2018&lt;/div&gt;
</code></pre>
<p>lazyswitch 页面切换时，如果目标单元在 dom 中的位置是当前单元的前面，则会进行反转动画 css 类 reverse<br>
反转动画在一些切换效果中很有用如 slide，在 fade 切换时则无效</p>
<h3 id="6">6. 禁用部分页面切换动画</h3>
<p>在超链接 a 设置属性 data-direct=&quot;true&quot;<br>
如果跨域名跳转链接时会自动直接跳转的</p>
<pre><code>&lt;a href=&quot;/index&quot; data-direct=&quot;true&quot;&gt;index(direct)&lt;/a&gt;
</code></pre>
<p>a 标签还有一个属性 data-history=&quot;true&quot;,默认为 true 控制页面切换时是否产生浏览器历史纪录</p>
<h3 id="7loading">7. 禁用 loading 动画</h3>
<p>loading 动画默认开启<br>
关闭</p>
<pre><code>&lt;script&gt;
	LazySwitch.closeLoading();
&lt;/script&gt;
</code></pre>
<p>开启</p>
<pre><code>&lt;script&gt;
	LazySwitch.openLoading();
&lt;/script&gt;
</code></pre>
<h3 id="8">8. 开启多页面预加载</h3>
<p>页面预加载默认关闭<br>
开启</p>
<pre><code>&lt;script&gt;
	LazySwitch.openPreLoad();
&lt;/script&gt;
</code></pre>
<p>开启预加载后 会自动查找 DOM 中的 a 标签的超链接，预加载这些超链接对应的页面</p>
<p>关闭</p>
<pre><code>&lt;script&gt;
	LazySwitch.closePreLoad();
&lt;/script&gt;
</code></pre>
<h3 id="9">9. 监听页面切换事件</h3>
<p>LazySwitch 页面切换会有 6 种事件发出<br>
PAGE_SWITCH_BEFORE: 每次切换页面前触发<br>
PAGE_FIRST_IN: 页面第一次进场<br>
PAGE_IN_START: 页面开始进场<br>
PAGE_IN_END: 页面结束进场<br>
PAGE_OUT_START: 页面开始出场<br>
PAGE_OUT_END: 页面结束出场</p>
<pre><code>&lt;script&gt;
    LazySwitch.addEventListener(LazySwitch.PageEvent.PAGE_FIRST_IN, function test(event) {
        console.log(event);
    });
&lt;/script&gt;
</code></pre>
<p>事件 event 为相同结构<br>
event.type: 当前事件类型<br>
event.data.page 当前事件对应的页面单元<br>
event.data.animate 页面切换动画类型<br>
event.data.history 页面切换是否产生浏览器历史纪录<br>
event.data.isBack 页面切换动画是否反转<br>
对于 PAGE_SWITCH_BEFORE 事件没有 event.data.page,<br>
但是多了 event.data.from 和 event.data.to 对应将要出场和进场的页面单元</p>
<h3 id="10">10. 动态调用页面切换</h3>
<p>lazyswitch 页面切换默认在点击超链接 a 标签时触发<br>
也可以通过代码调用触发页面切换</p>
<pre><code>&lt;script&gt;
    LazySwitch.goto(url, option);
&lt;/script&gt;
</code></pre>
<p>参数:<br>
url: 跳转目标地址<br>
option: 跳转配置项<br>
默认值为<br>
{<br>
history: true,<br>
isBack: &quot;auto&quot;,<br>
animate: &quot;auto&quot;,<br>
}</p>
<h3 id="11">11. 页面标题</h3>
<p>如果一个 html 文件中只有唯一一个页面单元时，这个页面单元会自动继承该页面的 title 值，<br>
如果有多个页面单元时 需要设置每个页面单元的标题 data-title<br>
如 $.html</p>
<pre><code>&lt;div class=&quot;lazyswitch in&quot; data-path=&quot;/2019.html&quot; data-title=&quot;2019&quot;&gt;2019&lt;/div&gt;
&lt;div class=&quot;lazyswitch out&quot; data-path=&quot;/2018.html&quot; data-title=&quot;2018&quot;&gt;2018&lt;/div&gt;
&lt;div class=&quot;lazyswitch out&quot; data-path=&quot;/2017.html&quot; data-title=&quot;2017&quot;&gt;2017&lt;/div&gt;
</code></pre>
]]></content:encoded></item><item><title><![CDATA[FastDownload java队列下载]]></title><description><![CDATA[<h1 id="fastdownload">FastDownload</h1>
<p>java多线程队列下载，支持断点续传，大文件切割下载<br>
GitHub Pages: <a href="https://github.com/fengshangbin/FastDownload">https://github.com/fengshangbin/FastDownload</a></p>
<h1 id>功能</h1>
<ul>
<li>多线程队列下载</li>
<li>断点续传</li>
<li>大文件切割块下载</li>
</ul>
<h1 id>设计理念</h1>
<p>专注下载核心功能，提供UI状态回调，功能全面，使用简单</p>
<h1 id>依赖</h1>
<p>okhttp3(com.squareup.okhttp3), json(org.json.json)</p>
<h1 id>初始化</h1>
<p>实现持久化接口，以便能缓存下载进度，下面以Android为例</p>
<pre><code>CacheInterface cache = new CacheInterface() {
    @Override
    public String read() {
    	SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
        return preferences.getString(&quot;</code></pre>]]></description><link>http://www.fengshangbin.com/fastdownload/</link><guid isPermaLink="false">5e79dfaba7d4640001e87d48</guid><category><![CDATA[后端]]></category><category><![CDATA[开源]]></category><dc:creator><![CDATA[fengshangbin]]></dc:creator><pubDate>Tue, 24 Mar 2020 10:25:12 GMT</pubDate><content:encoded><![CDATA[<h1 id="fastdownload">FastDownload</h1>
<p>java多线程队列下载，支持断点续传，大文件切割下载<br>
GitHub Pages: <a href="https://github.com/fengshangbin/FastDownload">https://github.com/fengshangbin/FastDownload</a></p>
<h1 id>功能</h1>
<ul>
<li>多线程队列下载</li>
<li>断点续传</li>
<li>大文件切割块下载</li>
</ul>
<h1 id>设计理念</h1>
<p>专注下载核心功能，提供UI状态回调，功能全面，使用简单</p>
<h1 id>依赖</h1>
<p>okhttp3(com.squareup.okhttp3), json(org.json.json)</p>
<h1 id>初始化</h1>
<p>实现持久化接口，以便能缓存下载进度，下面以Android为例</p>
<pre><code>CacheInterface cache = new CacheInterface() {
    @Override
    public String read() {
    	SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
        return preferences.getString(&quot;downloadBreakPoint&quot;,&quot;&quot;);
    }

    @Override
    public void write(String data) {
    	SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
        SharedPreferences.Editor editor = preferences.edit();
        editor.putString(&quot;downloadBreakPoint&quot;, data);
        editor.commit();
    }
};
DownloadBreakPoint.setCacheInterface(cache);
</code></pre>
<h1 id>调用</h1>
<p>设置需要下载的资源列表</p>
<pre><code>ArrayList&lt;Resource&gt; resources = new ArrayList&lt;&gt;();
String sourceURL = &quot;https://csdnimg.cn/public/common/libs/jquery/jquery-1.9.1.min.js&quot;;
String savePath = IOUtils.getAppDir() + &quot;/jq.js&quot;;
String md5 = &quot;ODdx7xaSv8w/K2kXyphXeA==&quot;;
long size = 92633L;
/*
@sourceURL 资源下载地址
@savePath 资源本地保存路径
@md5 资源哈希md5值
@size 资源大小
*/
Resource resource = new Resource(sourceURL, savePath, md5, size, null);
resources.add(resource);
</code></pre>
<p>监听下载状态</p>
<pre><code>DownloadProgress downloadProgress = new DownloadProgress() {
	@Override
    protected synchronized void onDownloadChange() {
        //Message.sent(SynchronousHandler.CHANGE_LOADING, new long[]{this.getLoaded(), this.getTotal()});
    }

    @Override
    protected synchronized void onDownloadFailed(Resource resource) {
        //Log.i(&quot;c3&quot;,&quot;failed: &quot;+resource.getPath());
    }

    @Override
    protected synchronized void onDownloadSuccess(Resource resource) {
        //Log.i(&quot;c3&quot;,&quot;success: &quot;+resource.getPath());
    }

    @Override
    protected void onDownloadFinish() { 
    	//下载队列执行完成 包括成功和失败的任务
        //Log.i(&quot;c3&quot;,&quot;download all finish.&quot;);
    }
};
</code></pre>
<p>构建下载任务</p>
<pre><code>int nThreads = 5;
Long chunkSize = 5*1024*1024L;
/*
@nThreads 多线程数量上限
@chunkSize 资源切割块大小
*/
BatchDownload batchDownload = BatchDownload.build(resources, downloadProgress, nThreads, chunkSize);
</code></pre>
<p>OK, 这样就完成了调用<br>
如果需要动态执行下载任务可以</p>
<pre><code>batchDownload.download(resource);
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Java版的防抖(debounce)和节流(throttle)]]></title><description><![CDATA[<h1 id>概念</h1>
<h3 id="debounce">防抖(debounce)</h3>
<p>当持续触发事件时，一定时间段内没有再触发事件，事件处理函数才会执行一次，如果设定时间到来之前，又触发了事件，就重新开始延时</p>
<h3 id="throttle">节流(throttle)</h3>
<p>当持续触发事件时，保证在一定时间内只调用一次事件处理函数，意思就是说，假设一个用户一直触发这个函数，且每次触发小于既定值，函数节流会每隔这个时间调用一次</p>
<h3 id>区别</h3>
<p>防抖是将多次执行变为最后一次执行，节流是将多次执行变为每隔一段时间执行</p>
<h1 id="java">Java实现</h1>
<h3 id="debounce">防抖(debounce)</h3>
<pre><code>public class DebounceTask {
    private Timer timer;
    private Long delay;
    private Runnable runnable;

    public DebounceTask(Runnable runnable,  Long delay) {
        this.runnable = runnable;
        this.delay = delay;
    }

    public static DebounceTask</code></pre>]]></description><link>http://www.fengshangbin.com/debounce-throttle/</link><guid isPermaLink="false">5e74661eadfbda0001811e34</guid><category><![CDATA[后端]]></category><dc:creator><![CDATA[fengshangbin]]></dc:creator><pubDate>Fri, 20 Mar 2020 07:01:14 GMT</pubDate><content:encoded><![CDATA[<h1 id>概念</h1>
<h3 id="debounce">防抖(debounce)</h3>
<p>当持续触发事件时，一定时间段内没有再触发事件，事件处理函数才会执行一次，如果设定时间到来之前，又触发了事件，就重新开始延时</p>
<h3 id="throttle">节流(throttle)</h3>
<p>当持续触发事件时，保证在一定时间内只调用一次事件处理函数，意思就是说，假设一个用户一直触发这个函数，且每次触发小于既定值，函数节流会每隔这个时间调用一次</p>
<h3 id>区别</h3>
<p>防抖是将多次执行变为最后一次执行，节流是将多次执行变为每隔一段时间执行</p>
<h1 id="java">Java实现</h1>
<h3 id="debounce">防抖(debounce)</h3>
<pre><code>public class DebounceTask {
    private Timer timer;
    private Long delay;
    private Runnable runnable;

    public DebounceTask(Runnable runnable,  Long delay) {
        this.runnable = runnable;
        this.delay = delay;
    }

    public static DebounceTask build(Runnable runnable, Long delay){
        return new DebounceTask(runnable, delay);
    }

    public void run(){
        if(timer!=null){
            timer.cancel();
        }
        timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                timer=null;
                runnable.run();
            }
        }, delay);
    }
}
</code></pre>
<h3 id="throttle">节流(throttle)</h3>
<pre><code>public class ThrottleTask {
    private Timer timer;
    private Long delay;
    private Runnable runnable;
    private boolean needWait=false;

    public ThrottleTask(Runnable runnable,  Long delay) {
        this.runnable = runnable;
        this.delay = delay;
        this.timer = new Timer();
    }

    public static ThrottleTask build(Runnable runnable, Long delay){
        return new ThrottleTask(runnable, delay);
    }

    public void run(){
        if(!needWait){
            needWait=true;
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    needWait=false;
                    runnable.run();
                }
            }, delay);
        }
    }
}
</code></pre>
<h1 id>测试</h1>
<h3 id="debounce">防抖(debounce)</h3>
<pre><code>DebounceTask task = DebounceTask.build(new Runnable() {
    @Override
    public void run() {
        System.out.println(&quot;do task: &quot;+System.currentTimeMillis());
    }
},1000L);
long delay = 100;
while (true){
    System.out.println(&quot;call task: &quot;+System.currentTimeMillis());
    task.run();
    delay+=100;
    try {
        Thread.sleep(delay);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}
</code></pre>
<h3 id="throttle">节流(throttle)</h3>
<pre><code>ThrottleTask task = ThrottleTask.build(new Runnable() {
    @Override
    public void run() {
        System.out.println(&quot;do task: &quot;+System.currentTimeMillis());
    }
},1000L);
while (true){
    System.out.println(&quot;call task: &quot;+System.currentTimeMillis());
    task.run();
    try {
        Thread.sleep(200);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}
</code></pre>
]]></content:encoded></item><item><title><![CDATA[旧诗]]></title><description><![CDATA[<p>某天，一张沾满灰尘的纸<br>
不知何时，燃了起来<br>
闪出耀眼的白光<br>
它本来安稳的躺在桌上，沉睡在哪里很久了<br>
现在它飘了起来<br>
纸一点一点被火掩盖<br>
虽然在左右摇摆着，但是始终是向下落去<br>
终于，它掉到了地上<br>
不知为何，荡了几下<br>
当它又沉静下来时，它已经全身发黑，变为一堆灰烬<br>
只是飘出来了一缕青烟<br>
刚开始，它还是努力保持全身的完整<br>
于是，我便一脚踏了下去<br>
走了</p>
<p>古井龙王<br>
2003年</p>]]></description><link>http://www.fengshangbin.com/jiu-shi/</link><guid isPermaLink="false">5e0d5d5badfbda0001811e09</guid><category><![CDATA[诗词]]></category><category><![CDATA[生活]]></category><dc:creator><![CDATA[fengshangbin]]></dc:creator><pubDate>Thu, 02 Jan 2020 03:09:31 GMT</pubDate><content:encoded><![CDATA[<p>某天，一张沾满灰尘的纸<br>
不知何时，燃了起来<br>
闪出耀眼的白光<br>
它本来安稳的躺在桌上，沉睡在哪里很久了<br>
现在它飘了起来<br>
纸一点一点被火掩盖<br>
虽然在左右摇摆着，但是始终是向下落去<br>
终于，它掉到了地上<br>
不知为何，荡了几下<br>
当它又沉静下来时，它已经全身发黑，变为一堆灰烬<br>
只是飘出来了一缕青烟<br>
刚开始，它还是努力保持全身的完整<br>
于是，我便一脚踏了下去<br>
走了</p>
<p>古井龙王<br>
2003年</p>
]]></content:encoded></item><item><title><![CDATA[WaterFall 瀑布流排版]]></title><description><![CDATA[<h1 id="waterfall">WaterFall</h1>
<p>masonry layout JS Component 瀑布流排版<br>
GitHub Pages: <a href="https://github.com/fengshangbin/WaterFall">https://github.com/fengshangbin/WaterFall</a></p>
<h1 id>设计理念</h1>
<p>不依赖第三方框架，也无侵入，功能全面，使用简单。</p>
<h1 id>特点及优势</h1>
<p>原生脚本，不依赖第三方框架<br>
多种排版模式，支持各种实际排版需求<br>
兼容 px em rem %多种宽度单位<br>
支持下拉到底时追加下一页视图<br>
使用简单且已内置 vue 集成</p>
<h3 id>在线测试</h3>
<p><a href="https://www.fengshangbin.com/html/waterfall/">https://www.fengshangbin.com/html/waterfall/</a></p>
<h1 id>如何使用</h1>
<h3 id="1waterfall">1, 引入 waterfall 脚本</h3>
<pre><code>&lt;script src=&quot;waterfall.js&</code></pre>]]></description><link>http://www.fengshangbin.com/waterfall/</link><guid isPermaLink="false">5d74a4ab5f69b500013978bc</guid><category><![CDATA[前端]]></category><category><![CDATA[开源]]></category><dc:creator><![CDATA[fengshangbin]]></dc:creator><pubDate>Sun, 08 Sep 2019 06:51:08 GMT</pubDate><content:encoded><![CDATA[<h1 id="waterfall">WaterFall</h1>
<p>masonry layout JS Component 瀑布流排版<br>
GitHub Pages: <a href="https://github.com/fengshangbin/WaterFall">https://github.com/fengshangbin/WaterFall</a></p>
<h1 id>设计理念</h1>
<p>不依赖第三方框架，也无侵入，功能全面，使用简单。</p>
<h1 id>特点及优势</h1>
<p>原生脚本，不依赖第三方框架<br>
多种排版模式，支持各种实际排版需求<br>
兼容 px em rem %多种宽度单位<br>
支持下拉到底时追加下一页视图<br>
使用简单且已内置 vue 集成</p>
<h3 id>在线测试</h3>
<p><a href="https://www.fengshangbin.com/html/waterfall/">https://www.fengshangbin.com/html/waterfall/</a></p>
<h1 id>如何使用</h1>
<h3 id="1waterfall">1, 引入 waterfall 脚本</h3>
<pre><code>&lt;script src=&quot;waterfall.js&quot;&gt;&lt;/script&gt;

var waterFall = WaterFall.builder(container, options);
</code></pre>
<p>或</p>
<pre><code>npm i easy-waterfall -S

import {builder as waterfall} from 'easy-waterfall';

var waterFall = new waterfall(container, options);

</code></pre>
<p>container 瀑布流容器<br>
options 配置参数</p>
<p>options 完整默认值</p>
<pre><code>{
  minWidth: null,
  columns: null,
  width: null,
  gap: null,
  minGap: null,
  itemSelector: null,
  scrollParent: window,
  loading:
    '&lt;div class=&quot;water-fall-loading&quot;&gt;&lt;svg viewBox=&quot;0 0 50 50&quot; class=&quot;loading&quot;&gt;&lt;defs&gt;&lt;linearGradient id=&quot;linear&quot; x1=&quot;0%&quot; y1=&quot;0%&quot; x2=&quot;100%&quot; y2=&quot;0%&quot;&gt;&lt;stop offset=&quot;0%&quot; stop-color=&quot;#000&quot; stop-opacity=&quot;1.0&quot; /&gt;&lt;stop offset=&quot;90%&quot; stop-color=&quot;#000&quot; stop-opacity=&quot;0&quot; /&gt;&lt;/linearGradient&gt;&lt;/defs&gt;&lt;circle cx=&quot;25&quot; cy=&quot;25&quot; r=&quot;20&quot; stroke-width=&quot;5&quot; stroke=&quot;url(#linear)&quot; fill=&quot;none&quot; /&gt;&lt;/svg&gt;&lt;div&gt;'
};
</code></pre>
<p>scrollParent 如果是指定 div，应该设置成元素选择的字符串如&quot;.communitys-all&quot;</p>
<h3 id="2vue">2, 在 vue 中使用</h3>
<pre><code>&lt;template&gt;
  &lt;waterfall class=&quot;total-report&quot; :options=&quot;{minWidth:'200px', gap:'15px'}&quot;&gt;
    &lt;div class=&quot;grid-content bg-purple&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;grid-content bg-purple&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;grid-content bg-purple&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;grid-content bg-purple&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;grid-content bg-purple&quot;&gt;&lt;/div&gt;
  &lt;/waterfall&gt;
&lt;/template&gt;

&lt;script&gt;

import waterfall from &quot;easy-waterfall/dist/waterfallVue&quot;;
export default {
  components: {
    waterfall
  }
};
&lt;/script&gt;

&lt;style lang=&quot;less&quot; scoped&gt;
  .bg-purple {
    background: #d3dce6;
  }
  .grid-content {
    min-height: 150px;
  }
&lt;/style&gt;
</code></pre>
<h3 id="3">3, 三种排版模式</h3>
<p>1, 指定子项最小宽度</p>
<pre><code>{minWidth:&quot;300px&quot;, gap:&quot;10px&quot;}
</code></pre>
<p>2, 指定子项列数</p>
<pre><code>{columns:3, gap:&quot;10px&quot;}
</code></pre>
<p>3, 指定子项宽度，和最小间距</p>
<pre><code>{width:&quot;300px&quot;, minGap:&quot;10px&quot;}
</code></pre>
<h3 id="4pxemrem">4， 兼容 px em rem %多种宽度单位</h3>
<pre><code>{width:&quot;300px&quot;, minGap:&quot;10em&quot;}
</code></pre>
<pre><code>{width:&quot;300rem&quot;, minGap:&quot;1%&quot;}
</code></pre>
<h3 id="5">5, 监听滑动到底事件</h3>
<pre><code>waterFall.addEventListener(&quot;touchbottom&quot;, function(e) {})
</code></pre>
<h3 id="6loading">6, 显示隐藏 loading</h3>
<pre><code>waterFall.showLoading();
waterFall.hideLoading();
</code></pre>
<h3 id="7">7, 更新视图</h3>
<pre><code>waterFall.update();
</code></pre>
<h3 id="8">8, 使用示例</h3>
<pre><code>&lt;!DOCTYPE html&gt;
&lt;html&gt;
  &lt;head&gt;
    &lt;meta charset=&quot;utf-8&quot; /&gt;
    &lt;meta http-equiv=&quot;X-UA-Compatible&quot; content=&quot;IE=edge&quot; /&gt;
    &lt;title&gt;waterfall test&lt;/title&gt;
    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot; /&gt;
    &lt;style&gt;
      #container .water-fall-item {
        border: 1px solid #f00;
      }
    &lt;/style&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;div id=&quot;container&quot;&gt;&lt;/div&gt;
    &lt;script src=&quot;../dist/waterfall.js&quot;&gt;&lt;/script&gt;
    &lt;script&gt;
      var txt =
        &quot;因遭受该男子暴打，吴巨轮面部有血迹，头部出现明显不适，随后被送往医院进行医疗。据接诊的湖南航天医院急诊科主任黄扩军介绍，吴巨轮头部软组织损伤，有脑震荡体现，需住院观察医治。横遭此祸，吴巨轮感觉很冤枉，因为按照有关规定，他实在不能在同一个站点进行二次停泊。387路公交所属的长沙众旺有限责任公司运营科科长戴勇表示，按照行业规章，在同一个站点进行二次停泊或站外停靠都是违纪行为，因为这样会影响其他公交车进出站和一般车辆的通畅。针对年轻男子的打人行为，该公司选择了报警。20日，岳麓公安分局公布通知称，11月18日上午，一男子在搭乘387路公交时，因不满司机的服务态度，置车上乘客的性命于不顾，在车辆行进过程中暴打公交司机吴某，随后逃离现场。接到报警后，岳麓公安分局观沙岭派出所迅速展开初步调查，于11月20日8时将肇事男子夏某智归案。潇湘晨报记者了解到，夏某智是长沙本地人，也是公共交通行业的工作者，是一名的士司机。夏某智在警局内接受媒体采访时称，“我们上车看到司机的表情很不情愿，那个年纪大的乘客一直在跟他吵，司机说话也不好听”，“现在特别后悔，确实是自己太冲动了，没事找事似的”。现在夏某智涉案以危险方法危害公共安全犯罪被刑事拘留，案件还在进一步处理中。&quot;;
      function getTxt() {
        var len = Math.floor(Math.random() * txt.length);
        var start = Math.floor(Math.random() * (txt.length - len));
        return txt.substr(start, len);
      }
      var pageSize = 16;
      function initContent() {
        var container = document.getElementById(&quot;container&quot;);
        container.innerHTML = &quot;&quot;;
        addPageContent();
      }
      function addPageContent() {
        for (var i = 0; i &lt; pageSize; i++) {
          container.insertAdjacentHTML(&quot;beforeend&quot;, &quot;&lt;div&gt;&lt;/div&gt;&quot;);
          var item = container.lastChild || container.lastElementChild;
          item.innerHTML = getTxt();
        }
      }
      initContent();

      var waterFall = WaterFall.builder(document.getElementById(&quot;container&quot;), {
        minWidth: &quot;300px&quot;,
        gap: &quot;10px&quot;
      });
      waterFall.addEventListener(&quot;touchbottom&quot;, function(e) {
        waterFall.showLoading();
        setTimeout(function() {
          addPageContent();
          waterFall.update();
          waterFall.hideLoading();
        }, 1000);
      });
    &lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code></pre>
]]></content:encoded></item><item><title><![CDATA[LazyPage 整合 webpack 支持热更新]]></title><description><![CDATA[<h1 id="lazypagewebpack">LazyPage 整合 webpack 支持热更新</h1>
<h3 id="githubpages">GitHub Pages</h3>
<p><a href="https://github.com/fengshangbin/LazyPage/tree/master/examples/lazypage-webpack">GitHub Page</a></p>
<h3 id>如何运行</h3>
<p>安装依赖</p>
<pre><code>npm install
</code></pre>
<p>开始运行</p>
<pre><code>npm run server
</code></pre>
<p>打包</p>
<pre><code>npm run build
</code></pre>
<h1 id="webpack">配置 webpack 热更新</h1>
<h3 id="lazypagenode">安装 lazypage-node 和热更新 依赖</h3>
<pre><code>npm install --save-dev webpack-dev-server lazypage-node
</code></pre>
<h3 id="serverjs">新建 server.js 文件</h3>
<pre><code>const webpackDevServer = require('webpack-dev-server');
const webpack = require('webpack');
var lazypage = require('lazypage-node');

const config</code></pre>]]></description><link>http://www.fengshangbin.com/lazypage-webpack/</link><guid isPermaLink="false">5d6cde785f69b500013978b3</guid><category><![CDATA[前端]]></category><category><![CDATA[开源]]></category><dc:creator><![CDATA[fengshangbin]]></dc:creator><pubDate>Mon, 02 Sep 2019 09:19:26 GMT</pubDate><content:encoded><![CDATA[<h1 id="lazypagewebpack">LazyPage 整合 webpack 支持热更新</h1>
<h3 id="githubpages">GitHub Pages</h3>
<p><a href="https://github.com/fengshangbin/LazyPage/tree/master/examples/lazypage-webpack">GitHub Page</a></p>
<h3 id>如何运行</h3>
<p>安装依赖</p>
<pre><code>npm install
</code></pre>
<p>开始运行</p>
<pre><code>npm run server
</code></pre>
<p>打包</p>
<pre><code>npm run build
</code></pre>
<h1 id="webpack">配置 webpack 热更新</h1>
<h3 id="lazypagenode">安装 lazypage-node 和热更新 依赖</h3>
<pre><code>npm install --save-dev webpack-dev-server lazypage-node
</code></pre>
<h3 id="serverjs">新建 server.js 文件</h3>
<pre><code>const webpackDevServer = require('webpack-dev-server');
const webpack = require('webpack');
var lazypage = require('lazypage-node');

const config = require('./webpack.config.js');
config.mode = 'development';
const options = {
  contentBase: './dist',
  hot: true,
  host: 'localhost',
  before: app =&gt; {
    app.use(lazypage.response());
  }
};

webpackDevServer.addDevServerEntrypoints(config, options);
const compiler = webpack(config);
const server = new webpackDevServer(compiler, options);

compiler.hooks.emit.tap('FileListPlugin', compilation =&gt; {
  const assets = compilation.assets;
  lazypage.route(assets);
});

server.listen(8183, 'localhost', () =&gt; {
  console.log('dev server listening on port 8183');
});

</code></pre>
<h3 id="packagejsonserver">添加 package.json server 命令</h3>
<pre><code>&quot;server&quot;: &quot;node server&quot;
</code></pre>
<h3 id>开始测试</h3>
<pre><code>npm run server
</code></pre>
<h3 id="webpackjslesscsshtmlwebpackconfigjs">其他 webpack 功能 如 js 处理 less、css 处理，html 处理，版本号设置，请参考 webpack.config.js 的配置</h3>
]]></content:encoded></item><item><title><![CDATA[LazyPage 整合 gulp 支持热更新]]></title><description><![CDATA[<h1 id="lazypagegulp">LazyPage 整合 gulp 支持热更新</h1>
<h3 id="githubpages">GitHub Pages</h3>
<p><a href="https://github.com/fengshangbin/LazyPage/tree/master/examples/lazypage-gulp">GitHub Page</a></p>
<h3 id>如何运行</h3>
<p>安装依赖</p>
<pre><code>npm install
</code></pre>
<p>开始运行</p>
<pre><code>npm run server
</code></pre>
<p>打包</p>
<pre><code>npm run build
</code></pre>
<h1 id="gulp">gulp 配置</h1>
<h3 id="gulp">安装 gulp 依赖</h3>
<pre><code>npm install --save-dev gulp
</code></pre>
<h3 id="server">配置热更新 server</h3>
<p>1, 安装 lazypage-node 依赖</p>
<pre><code>npm install --save-dev lazypage-node express
</code></pre>
<p>2, 创建 server.js</p>
<pre><code>var express = require('express');
var serverFilter</code></pre>]]></description><link>http://www.fengshangbin.com/lazypage-gulp/</link><guid isPermaLink="false">5d6cddd05f69b500013978ab</guid><category><![CDATA[前端]]></category><category><![CDATA[开源]]></category><dc:creator><![CDATA[fengshangbin]]></dc:creator><pubDate>Mon, 02 Sep 2019 09:17:11 GMT</pubDate><content:encoded><![CDATA[<h1 id="lazypagegulp">LazyPage 整合 gulp 支持热更新</h1>
<h3 id="githubpages">GitHub Pages</h3>
<p><a href="https://github.com/fengshangbin/LazyPage/tree/master/examples/lazypage-gulp">GitHub Page</a></p>
<h3 id>如何运行</h3>
<p>安装依赖</p>
<pre><code>npm install
</code></pre>
<p>开始运行</p>
<pre><code>npm run server
</code></pre>
<p>打包</p>
<pre><code>npm run build
</code></pre>
<h1 id="gulp">gulp 配置</h1>
<h3 id="gulp">安装 gulp 依赖</h3>
<pre><code>npm install --save-dev gulp
</code></pre>
<h3 id="server">配置热更新 server</h3>
<p>1, 安装 lazypage-node 依赖</p>
<pre><code>npm install --save-dev lazypage-node express
</code></pre>
<p>2, 创建 server.js</p>
<pre><code>var express = require('express');
var serverFilter = require('lazypage-node');

var app = express();
app.use(serverFilter.filter('src'));
app.use(express.static('src'));

app.listen(7000, function() {});

</code></pre>
<p>3, 安装 browserSync 依赖</p>
<pre><code>npm install --save-dev browser-sync gulp-nodemon
</code></pre>
<p>4, 创建 browserSync 任务</p>
<pre><code>var browserSync = require('browser-sync').create();
gulp.task('browserSync', function(cb) {
  nodemon({
    script: 'server.js',
    //ignore: [&quot;gulpfile.js&quot;, &quot;node_modules/&quot;, &quot;public/**/*.*&quot;],
    ext: 'html',
    env: {
      NODE_ENV: 'development'
    }
  }).on('start', function() {
    browserSync.init(
      {
        proxy: 'http://localhost:7000',
        //files: ['src/**/*.less', 'src/**/*.html', 'src/**/*.js'],
        port: 8183
      },
      function() {
        console.log('browser refreshed.');
      }
    );
    cb();
  });
});
</code></pre>
<h3 id="less">编译 less</h3>
<p>安装依赖</p>
<pre><code>npm install --save-dev gulp-less
</code></pre>
<p>创建 gulp less 任务</p>
<pre><code>var less = require('gulp-less');

gulp.task('less', function() {
  return gulp
    .src(['src/**/*.less', '!src/**/_*.less'])
    .pipe(less())
    .pipe(gulp.dest('src'))
    .pipe(
      browserSync.reload({
        stream: true
      })
    );
});
</code></pre>
<h3 id="css">自动追加 css 前缀</h3>
<p>安装依赖</p>
<pre><code>npm install --save-dev gulp-autoprefixer
</code></pre>
<p>在上面的 less 任务中追加.pipe(autoprefixer())</p>
<pre><code>var autoprefixer = require('gulp-autoprefixer');
...
    .pipe(less())
    .pipe(autoprefixer())
    .pipe(gulp.dest('src'))
...
</code></pre>
<p>在 package.json 中配置</p>
<pre><code>&quot;browserslist&quot;: [
    &quot;last 2 versions&quot;,
    &quot;Android &gt;= 4.0&quot;
]
</code></pre>
<h3 id>监控文件变化</h3>
<p>安装依赖</p>
<pre><code>npm install --save-dev gulp-watch
</code></pre>
<p>创建 watch 任务</p>
<pre><code>var watch = require('gulp-watch');
gulp.task('watch', function(cb) {
  watch('src/**/*.less', gulp.series('less'));
  watch('src/**/*.html', browserSync.reload);
  watch('src/**/*.js', browserSync.reload);
  cb();
});
</code></pre>
<h3 id>创建热更新任务</h3>
<pre><code>gulp.task('server', gulp.series('browserSync', 'less', 'watch'));
</code></pre>
<h3 id>静态文件版本控制</h3>
<p>安装依赖</p>
<pre><code>npm install --save-dev gulp-rev gulp-rev-collector
var rev = require('gulp-rev');
var revCollector = require('gulp-rev-collector');
</code></pre>
<p>追加文件版本号到文件名中，版本号为文件 md5 后的值</p>
<pre><code>...
.pipe(rev())
.pipe(gulp.dest('dist'))
.pipe(rev.manifest())
...
</code></pre>
<p>替换文件引用路径为带版本号路径</p>
<pre><code>...
.pipe(
    revCollector({
        replaceReved: true
    })
)
...
</code></pre>
<h3 id>处理图片</h3>
<pre><code>gulp.task('image', function() {
  return gulp
    .src('src/**/*.{jpg,png,svg,gif}')
    .pipe(rev())
    .pipe(gulp.dest('dist'))
    .pipe(rev.manifest())
    .pipe(gulp.dest('dist/rev/image'));
});
</code></pre>
<h3 id="css">处理 css</h3>
<p>安装压缩 css 依赖</p>
<pre><code>npm install --save-dev gulp-clean-css
</code></pre>
<p>创建 css 任务</p>
<pre><code>var minifycss = require('gulp-clean-css');
gulp.task('css', function() {
  return gulp
    .src(['dist/rev/**/*.json', 'src/**/*.css'])
    .pipe(
      revCollector({
        replaceReved: true
      })
    )
    .pipe(minifycss())
    .pipe(rev())
    .pipe(gulp.dest('dist'))
    .pipe(rev.manifest())
    .pipe(gulp.dest('dist/rev/css'));
});
</code></pre>
<h3 id="js">处理 js</h3>
<p>安装压缩 js 依赖</p>
<pre><code>npm install --save-dev gulp-uglify
</code></pre>
<p>创建 js 任务</p>
<pre><code>var uglify = require('gulp-uglify');
gulp.task('js', function() {
  return gulp
    .src(['dist/rev/**/*.json', 'src/**/*.js'])
    .pipe(
      revCollector({
        replaceReved: true
      })
    )
    .pipe(uglify())
    .pipe(rev())
    .pipe(gulp.dest('dist'))
    .pipe(rev.manifest())
    .pipe(gulp.dest('dist/rev/js'));
});
</code></pre>
<h3 id="json">创建 json 任务</h3>
<pre><code>gulp.task('json', function() {
  return gulp
    .src(['dist/rev/**/*.json', 'src/**/*.json'])
    .pipe(
      revCollector({
        replaceReved: true
      })
    )
    .pipe(gulp.dest('dist'));
});
</code></pre>
<h3 id="html">处理 html</h3>
<p>安装压缩 html 依赖</p>
<pre><code>npm install --save-dev gulp-htmlmin
</code></pre>
<p>创建 html 任务</p>
<pre><code>var htmlmin = require('gulp-htmlmin');
gulp.task('html', function() {
  return gulp
    .src(['dist/rev/**/*.json', 'src/**/*.html'])
    .pipe(
      revCollector({
        replaceReved: true
      })
    )
    .pipe(
      htmlmin({
        removeComments: true,
        collapseWhitespace: true,
        removeScriptTypeAttributes: true,
        removeStyleLinkTypeAttributes: true,
        minifyJS: true,
        minifyCSS: true
      })
    )
    .pipe(gulp.dest('dist'));
});
</code></pre>
<h3 id>清理输出文件夹</h3>
<p>安装文件删除依赖</p>
<pre><code>npm install --save-dev del
</code></pre>
<p>创建删除任务</p>
<pre><code>var del = require('del');
gulp.task('clean', function(cb) {
  return del('dist', cb);
});
</code></pre>
<h3 id>拷贝其他资源</h3>
<pre><code>gulp.task('copy', function(cb) {
  gulp.src('src/**/*.{mp4,pdf,ico,woff2,woff,ttf}').pipe(gulp.dest('dist'));
  cb();
});
</code></pre>
<h3 id>创建打包任务</h3>
<pre><code>gulp.task('build', gulp.series('clean', 'less', 'image', 'css', 'js', 'json', 'html', 'copy'));
</code></pre>
]]></content:encoded></item><item><title><![CDATA[通过Docker搭建可以热部署的nodejs环境]]></title><description><![CDATA[<h1 id="dockernodejs">通过Docker搭建可以热部署的nodejs环境</h1>
<h3 id="1nodejs">1, 下载nodejs镜像</h3>
<pre><code>docker pull node
</code></pre>
<h3 id="2dockerfile">2, 创建Dockerfile文件</h3>
<pre><code>FROM node
RUN mkdir -p /home/service
WORKDIR /home/service
RUN npm install pm2 -g
CMD pm2-runtime server.js --watch
</code></pre>
<h3 id="3nodejs">3, 创建自己的nodejs镜像</h3>
<pre><code>docker build -t mynodeapp .
</code></pre>
<h3 id="4nodejs">4, 运行nodejs容器</h3>
<pre><code>docker run --name nodeapptest -d -p 8181:8181 -v /home/node-test:/home/service --restart</code></pre>]]></description><link>http://www.fengshangbin.com/docker-nodejs/</link><guid isPermaLink="false">5d22bb30bf6d3f0001a285d7</guid><category><![CDATA[后端]]></category><dc:creator><![CDATA[fengshangbin]]></dc:creator><pubDate>Mon, 08 Jul 2019 04:04:04 GMT</pubDate><content:encoded><![CDATA[<h1 id="dockernodejs">通过Docker搭建可以热部署的nodejs环境</h1>
<h3 id="1nodejs">1, 下载nodejs镜像</h3>
<pre><code>docker pull node
</code></pre>
<h3 id="2dockerfile">2, 创建Dockerfile文件</h3>
<pre><code>FROM node
RUN mkdir -p /home/service
WORKDIR /home/service
RUN npm install pm2 -g
CMD pm2-runtime server.js --watch
</code></pre>
<h3 id="3nodejs">3, 创建自己的nodejs镜像</h3>
<pre><code>docker build -t mynodeapp .
</code></pre>
<h3 id="4nodejs">4, 运行nodejs容器</h3>
<pre><code>docker run --name nodeapptest -d -p 8181:8181 -v /home/node-test:/home/service --restart always mynodeapp
</code></pre>
<p>8181为当前服务对外的端口<br>
/home/node-test为服务器任意路径，是存放nodejs代码和资源的地方，最好在ftp目录下，这样以后可以通过ftp更新程序</p>
<h3 id="5nodejs">5, 上传nodejs代码和资源到服务器上</h3>
<p>其中要包括依赖包node_modules</p>
<h3 id="6">6, 校验服务是否正常运行</h3>
<pre><code>curl -i localhost:8181
</code></pre>
<h3 id="7">7, 划重点</h3>
<p>利用pm2实现nodejs热部署<br>
创建空壳nodejs镜像，可以多个应用共用一个镜像<br>
应用的入口必须统一为server.js文件</p>
]]></content:encoded></item><item><title><![CDATA[让元素在指定区域内显示]]></title><description><![CDATA[<h1 id>让元素在指定区域内显示</h1>
<p><a href="http://www.fengshangbin.com/html/simplepin/jquery.simplepin.js">jquery.simplepin.js源代码</a></p>
<h3 id>功能特点</h3>
<p>1, 基于jquery的插件<br>
2, 区域可以是某个容器，也可以是指定顶部和底部元素<br>
3, 支持修复偏差回调<br>
4, 支持移动端，不卡顿和抖动<br>
5, 不改变原始dom结构<br>
6, 可以指定区域内贴顶或贴底显示</p>
<h3 id>调用示例</h3>
<pre><code>    $('.icons').pin({
      top: $('.topBanner'),
      bottom: $('footer'),
      snap: 'bottom'
    });
</code></pre>
<pre><code>    $('.sidemenu').pin({
      container: $('.side'),
      absoluteTranslate: function() {
        return -$('.side').offset().top;
      },
      fixedTranslate: function() {
        return $('header').height();
      }
    });
</code></pre>]]></description><link>http://www.fengshangbin.com/pin/</link><guid isPermaLink="false">5d1339930041100001ca904a</guid><category><![CDATA[前端]]></category><dc:creator><![CDATA[fengshangbin]]></dc:creator><pubDate>Wed, 26 Jun 2019 09:38:13 GMT</pubDate><content:encoded><![CDATA[<h1 id>让元素在指定区域内显示</h1>
<p><a href="http://www.fengshangbin.com/html/simplepin/jquery.simplepin.js">jquery.simplepin.js源代码</a></p>
<h3 id>功能特点</h3>
<p>1, 基于jquery的插件<br>
2, 区域可以是某个容器，也可以是指定顶部和底部元素<br>
3, 支持修复偏差回调<br>
4, 支持移动端，不卡顿和抖动<br>
5, 不改变原始dom结构<br>
6, 可以指定区域内贴顶或贴底显示</p>
<h3 id>调用示例</h3>
<pre><code>    $('.icons').pin({
      top: $('.topBanner'),
      bottom: $('footer'),
      snap: 'bottom'
    });
</code></pre>
<pre><code>    $('.sidemenu').pin({
      container: $('.side'),
      absoluteTranslate: function() {
        return -$('.side').offset().top;
      },
      fixedTranslate: function() {
        return $('header').height();
      }
    });
</code></pre>
]]></content:encoded></item><item><title><![CDATA[正则匹配 HTML 嵌套元素(二)]]></title><description><![CDATA[<h1 id="html">正则匹配 HTML 嵌套元素(二)</h1>
<p><a href="http://www.fengshangbin.com/match-html/">上一篇</a> 介绍了单一条件查询, 本篇介绍混合条件查询</p>
<p>javascript<br>
<a href="http://www.fengshangbin.com/html/queryhelp/queryhelp.js">queryhelp.js源代码</a><br>
java<br>
<a href="http://www.fengshangbin.com/html/queryhelp/QueryHelp.java">QueryHelp.java源代码</a></p>
<p>类似传统 JS 查询习惯<br>
支持# .[]混写查询<br>
空格=&gt;层级<br>
井号#=&gt;id<br>
.=&gt;class 类名<br>
[]=&gt;attribute 属性</p>
<p>多结果查询</p>
<pre><code>QueryHelp.querySelectorAll(html, queryString)
</code></pre>
<p>单结果查询</p>
<pre><code>QueryHelp.querySelector(html, queryString)
</code></pre>
<p>示例</p>
<pre><code>QueryHelp.querySelectorAll(document.body.innerHTML, &quot;div.nav #news</code></pre>]]></description><link>http://www.fengshangbin.com/match-html2/</link><guid isPermaLink="false">5d11cbc50041100001ca903d</guid><category><![CDATA[前端]]></category><category><![CDATA[后端]]></category><dc:creator><![CDATA[fengshangbin]]></dc:creator><pubDate>Tue, 25 Jun 2019 07:26:02 GMT</pubDate><content:encoded><![CDATA[<h1 id="html">正则匹配 HTML 嵌套元素(二)</h1>
<p><a href="http://www.fengshangbin.com/match-html/">上一篇</a> 介绍了单一条件查询, 本篇介绍混合条件查询</p>
<p>javascript<br>
<a href="http://www.fengshangbin.com/html/queryhelp/queryhelp.js">queryhelp.js源代码</a><br>
java<br>
<a href="http://www.fengshangbin.com/html/queryhelp/QueryHelp.java">QueryHelp.java源代码</a></p>
<p>类似传统 JS 查询习惯<br>
支持# .[]混写查询<br>
空格=&gt;层级<br>
井号#=&gt;id<br>
.=&gt;class 类名<br>
[]=&gt;attribute 属性</p>
<p>多结果查询</p>
<pre><code>QueryHelp.querySelectorAll(html, queryString)
</code></pre>
<p>单结果查询</p>
<pre><code>QueryHelp.querySelector(html, queryString)
</code></pre>
<p>示例</p>
<pre><code>QueryHelp.querySelectorAll(document.body.innerHTML, &quot;div.nav #news li[show='true']&quot;)
</code></pre>
<p><a href="http://www.fengshangbin.com/match-html3/">下一篇</a> 实现写入功能，实现一个完整的Dom解析类库</p>
]]></content:encoded></item><item><title><![CDATA[基于 webcollector 和 lucene 开发站内搜索]]></title><description><![CDATA[<h1 id="webcollectorlucene">基于 webcollector 和 lucene 开发站内搜索</h1>
<h3 id="webcollector">webcollector</h3>
<p>网站爬虫<br>
<a href="https://github.com/CrawlScript/WebCollector">https://github.com/CrawlScript/WebCollector</a></p>
<h3 id="lucene">lucene</h3>
<p>文档索引与检索<br>
<a href="https://lucene.apache.org/">https://lucene.apache.org/</a></p>
<h3 id>爬取网页信息</h3>
<pre><code>public class SiteCrawler extends RamCrawler{
	protected static final Logger LOG = Logger.getLogger(SiteCrawler.class);
	List&lt;Document&gt; docList;
	public SiteCrawler() {
        super(true);
        String domain = &quot;http://www.fengshangbin.com/&quot;</code></pre>]]></description><link>http://www.fengshangbin.com/sitesearch/</link><guid isPermaLink="false">5d0cbb670041100001ca9038</guid><category><![CDATA[后端]]></category><dc:creator><![CDATA[fengshangbin]]></dc:creator><pubDate>Fri, 21 Jun 2019 11:13:25 GMT</pubDate><content:encoded><![CDATA[<h1 id="webcollectorlucene">基于 webcollector 和 lucene 开发站内搜索</h1>
<h3 id="webcollector">webcollector</h3>
<p>网站爬虫<br>
<a href="https://github.com/CrawlScript/WebCollector">https://github.com/CrawlScript/WebCollector</a></p>
<h3 id="lucene">lucene</h3>
<p>文档索引与检索<br>
<a href="https://lucene.apache.org/">https://lucene.apache.org/</a></p>
<h3 id>爬取网页信息</h3>
<pre><code>public class SiteCrawler extends RamCrawler{
	protected static final Logger LOG = Logger.getLogger(SiteCrawler.class);
	List&lt;Document&gt; docList;
	public SiteCrawler() {
        super(true);
        String domain = &quot;http://www.fengshangbin.com/&quot;;
        docList = new ArrayList&lt;Document&gt;();
        this.addSeed(domain);

        this.addRegex(domain+&quot;.*?&quot;);
        this.addRegex(&quot;-.*\\.(jpg|png|gif|svg|css|js|pdf).*&quot;);
        this.addRegex(&quot;-.*#.*&quot;);

        setThreads(30);
        getConf().setTopN(100);
    }

    @Override
    public void visit(Page page, CrawlDatums next) {
        String url = page.url();
        String title = page.select(&quot;title&quot;).first().text();
        String content = page.select(&quot;body&quot;).text();

        Document luceneDocument = new Document();
        TextField titleFiled = new TextField(&quot;title&quot;, title, Store.YES);
        TextField contentFiled = new TextField(&quot;content&quot;, content, Store.YES);
        StringField linkFiled = new StringField(&quot;link&quot;, url, Store.YES);

        luceneDocument.add(titleFiled);
        luceneDocument.add(contentFiled);
        luceneDocument.add(linkFiled);
        docList.add(luceneDocument);
    }
}
</code></pre>
<h3 id>索引爬取结果</h3>
<p>在 SiteCrawler.java 中添加爬取结束后的方法</p>
<pre><code>    @Override
    public void afterStop(){
    	try {
    		this.dbManager.clear();
    		LuceneUtils.indexDelAll(SiteConst.LUCENE_PATH);
			LuceneUtils.indexUpdate(docList, SiteConst.LUCENE_PATH);
			docList.clear();
		} catch (Exception e) {
			//e.printStackTrace();
			LOG.error(e.getMessage());
		}
    	LOG.info(&quot;site index end&quot;);
    }
</code></pre>
<p>索引爬取结果</p>
<pre><code>    public static void indexUpdate(List&lt;Document&gt; docList, String indexSaveDir) throws IOException {
        Analyzer analyzer = new StandardAnalyzer();
        Path path = Paths.get(indexSaveDir);
        Directory directory = FSDirectory.open(path);
        IndexWriterConfig config = new IndexWriterConfig(analyzer);
        IndexWriter indexWriter = new IndexWriter(directory, config);
        for (int i = 0; i &lt; docList.size(); i++) {
        	Document luceneDocument = docList.get(i);
        	Term term = new Term(&quot;link&quot;, luceneDocument.get(&quot;link&quot;));
        	indexWriter.updateDocument(term, luceneDocument);
            if ((i + 1) % 50 == 0) {
                indexWriter.flush();
            }
        }
        indexWriter.flush();
        indexWriter.commit();
        indexWriter.close();
    }
</code></pre>
<h3 id>查询数据</h3>
<pre><code>    public static Map&lt;String, Object&gt; indexSearch(String indexDir, String queryWord, int start, int end) throws Exception {
		Map&lt;String, Object&gt; result = new HashMap&lt;String, Object&gt;();
		ArrayList&lt;Map&lt;String, String&gt;&gt; list=new ArrayList&lt;Map&lt;String, String&gt;&gt;();
		result.put(&quot;list&quot;, list);
        Analyzer analyzer = new StandardAnalyzer();
        String[] fields = {&quot;title&quot;, &quot;content&quot;};
        MultiFieldQueryParser queryParser = new MultiFieldQueryParser(fields, analyzer);
        Query query = queryParser.parse(queryWord);
        Path path = Paths.get(indexDir);
        Directory dir = FSDirectory.open(path);
        DirectoryReader directoryReader = DirectoryReader.open(dir);
        IndexSearcher indexSearcher = new IndexSearcher(directoryReader);
        TopDocs topdocs = indexSearcher.search(query, end);
        ScoreDoc[] scoreDocs = topdocs.scoreDocs;
        ScoreDoc loopScoreDoc = null;

        Formatter formatter = new SimpleHTMLFormatter(&quot;&lt;span class='hight'&gt;&quot;,&quot;&lt;/span&gt;&quot;);
        QueryScorer scorer = new QueryScorer(query);
        Highlighter highlighter = new Highlighter(formatter,scorer);
        //设置摘要
        Fragmenter fragmenter  = new SimpleSpanFragmenter(scorer);
        highlighter.setTextFragmenter(fragmenter);

        result.put(&quot;total&quot;, topdocs.totalHits.value);
        for (int i = start; i &lt; scoreDocs.length; i++) {
            loopScoreDoc = scoreDocs[i];
            int docID = loopScoreDoc.doc;
            Document document = directoryReader.document(docID);
            String content = document.get(&quot;content&quot;);
            String highlighterContent = highlighter.getBestFragment(analyzer, &quot;content&quot;, content);
            if(highlighterContent==null)highlighterContent=content.substring(0, Math.min(80, content.length()));
            Map&lt;String, String&gt; item = new HashMap&lt;String, String&gt;();
            item.put(&quot;link&quot;, document.get(&quot;link&quot;));
            item.put(&quot;title&quot;, document.get(&quot;title&quot;));
            item.put(&quot;content&quot;, highlighterContent+&quot; ...&quot;);
            list.add(item);
        }
        result.put(&quot;code&quot;, 200);
        return result;
    }
</code></pre>
<h3 id>设置每日凌晨自动运行爬虫</h3>
<pre><code>    @Override
	public void contextInitialized(ServletContextEvent sce) {
		// TODO Auto-generated method stub
		LOG.info(&quot;site serach start:&quot;);
		TimerTask task = new TimerTask() {
	      @Override
	      public void run() {
	        // task to run goes here
	    	SiteCrawler crawler = new SiteCrawler();
	        try {
				crawler.start(5);
			} catch (Exception e) {
				//e.printStackTrace();
				LOG.error(e.getMessage());
			}
	      }
	    };
	    Timer timer = new Timer();

	    long now = new Date().getTime();
        SimpleDateFormat sdfOne = new SimpleDateFormat(&quot;yyyy-MM-dd&quot;);
        long overTime=0;
		try {
			overTime = now - (sdfOne.parse(sdfOne.format(now)).getTime());
		} catch (ParseException e) {
			e.printStackTrace();
		}
	    long intevalPeriod = 24 * 60 * 60 * 1000;
	    long delay = intevalPeriod - overTime;
	    LOG.info(&quot;site serach next index:&quot;+delay);
	    timer.scheduleAtFixedRate(task, delay, intevalPeriod);
	}
</code></pre>
]]></content:encoded></item><item><title><![CDATA[H5 PhotoCrop组件]]></title><description><![CDATA[<h1 id="photocrop">PhotoCrop</h1>
<p>PhotoCrop is JS library for photo crop<br>
PhotoCrop 是一个裁剪图片的 js 组件<br>
GitHub Pages: <a href="https://github.com/fengshangbin/PhotoCrop">https://github.com/fengshangbin/PhotoCrop</a></p>
<h1 id>设计理念</h1>
<p>不依赖第三方框架，也无侵入，功能全面，使用方便</p>
<h1 id>特点及优势</h1>
<ol>
<li>支持 PC 和移动端, UI 简单清晰</li>
<li>功能全面，支持原图输出，轻界面模式，自定义宽高比例</li>
<li>图片缩放无锯齿</li>
<li>自动修正图片旋转角度</li>
<li>使用简单,支持移动端的手势</li>
</ol>
<h1 id>如何使用</h1>
<p>引入 js 文件</p>
<pre><code class="language-html">&lt;script src=&quot;c3photocrop.js&quot;&gt;</code></pre>]]></description><link>http://www.fengshangbin.com/photocrop/</link><guid isPermaLink="false">5cb046142b2cd10001cf30f0</guid><category><![CDATA[前端]]></category><category><![CDATA[开源]]></category><dc:creator><![CDATA[fengshangbin]]></dc:creator><pubDate>Fri, 12 Apr 2019 08:03:07 GMT</pubDate><content:encoded><![CDATA[<h1 id="photocrop">PhotoCrop</h1>
<p>PhotoCrop is JS library for photo crop<br>
PhotoCrop 是一个裁剪图片的 js 组件<br>
GitHub Pages: <a href="https://github.com/fengshangbin/PhotoCrop">https://github.com/fengshangbin/PhotoCrop</a></p>
<h1 id>设计理念</h1>
<p>不依赖第三方框架，也无侵入，功能全面，使用方便</p>
<h1 id>特点及优势</h1>
<ol>
<li>支持 PC 和移动端, UI 简单清晰</li>
<li>功能全面，支持原图输出，轻界面模式，自定义宽高比例</li>
<li>图片缩放无锯齿</li>
<li>自动修正图片旋转角度</li>
<li>使用简单,支持移动端的手势</li>
</ol>
<h1 id>如何使用</h1>
<p>引入 js 文件</p>
<pre><code class="language-html">&lt;script src=&quot;c3photocrop.js&quot;&gt;&lt;/script&gt;
</code></pre>
<pre><code>C3PhotoCrop.crop({
    cropSize: '300x250',
    success: function(data) {
        img.src = 'data:image/jpeg;base64,' + data.data;
    }
});
</code></pre>
<p>或</p>
<pre><code>npm i c3photocrop -S

import {crop} from &quot;c3photocrop&quot;;

crop({
    cropSize: '300x250',
    success: function(data) {
        img.src = 'data:image/jpeg;base64,' + data.data;
    }
});
</code></pre>
<p><a href="http://www.fengshangbin.com/html/c3photocrop/">示例</a><br>
示例二维码<br>
<img src="http://www.fengshangbin.com/html/c3photocrop/qrcode.png" alt="示例二维码"></p>
<h1 id>更多功能</h1>
<p>默认参数</p>
<pre><code>var defaultOption = {
  cropSize: '0x0', //裁剪尺寸
  thumbSize: '0x0', //缩图尺寸
  defaultPhoto: null, //默认需要裁剪的图片，可以为图片网址，也可以为File文件
  success: null, //回调函数
  liteMode: false, //轻模式，直接默认居中裁剪，用户看不到裁剪界面
  multiple: false, //多选模式，仅当liteMode为true并且默认图片为null时才可以开启
  photoExt: 'jpg', //输出格式（使用原图时此配置无效）
  language: 'en', //语言
  supportHiRes: true, //是否支持输出高清原图
  supportRatio: true, //是否支持自选宽高比
  ratio: ['1:1', '1:2', '2:3'], //自选宽高比的选项
  fullshow: true, //是否强制填满裁剪区域
  title: null, //标题
  openCamera: false, //直接打开摄像头拍照，只有非多选模式并且移动端生效
  gap: 20, //边距
  mimimum: 20
};
</code></pre>
<p>裁剪后返回的数据(如果启用多选模式 返回的则是一个数组)</p>
<pre><code>{
    data  //裁剪后图片base64字符串
    thumb //缩图base64
    isHiRes //是否是高清原图
    ext //输出图片的格式
    originalFile //裁剪图片源
  }
</code></pre>
<h1 id>许可</h1>
<p>MIT 许可</p>
]]></content:encoded></item><item><title><![CDATA[正则匹配HTML嵌套元素(一)]]></title><description><![CDATA[<h1 id="html">正则匹配HTML嵌套元素(一)</h1>
<p>在js, java这种不支持正则平衡组高级特性时，如何用正则匹配HTML嵌套元素？<br>
java版实现<a href="http://www.fengshangbin.com/html/queryhelp/QueryHelp.java">源码</a></p>
<h3 id>第一步</h3>
<p>查找目标元素开始位置，例根据id查找</p>
<pre><code>var id = &quot;home&quot;;
var regStr = &quot;&lt; *([^ &gt;]*)[^&gt;]*?\\bid *= *\&quot;&quot;+id+&quot;\&quot;[^&gt;]*&gt;&quot;;
var match = new RegExp(regStr, &quot;img&quot;);
var group = match.exec(html);
</code></pre>
<h3 id>第二步</h3>
<p>根据目标元素的标签，向下查找相同标签的打开和结束标签，并计数<br>
如果是打开标签，计数加1，否则计数减1。</p>]]></description><link>http://www.fengshangbin.com/match-html/</link><guid isPermaLink="false">5c99d7992b2cd10001cf30e4</guid><category><![CDATA[前端]]></category><category><![CDATA[后端]]></category><dc:creator><![CDATA[fengshangbin]]></dc:creator><pubDate>Tue, 26 Mar 2019 07:42:34 GMT</pubDate><content:encoded><![CDATA[<h1 id="html">正则匹配HTML嵌套元素(一)</h1>
<p>在js, java这种不支持正则平衡组高级特性时，如何用正则匹配HTML嵌套元素？<br>
java版实现<a href="http://www.fengshangbin.com/html/queryhelp/QueryHelp.java">源码</a></p>
<h3 id>第一步</h3>
<p>查找目标元素开始位置，例根据id查找</p>
<pre><code>var id = &quot;home&quot;;
var regStr = &quot;&lt; *([^ &gt;]*)[^&gt;]*?\\bid *= *\&quot;&quot;+id+&quot;\&quot;[^&gt;]*&gt;&quot;;
var match = new RegExp(regStr, &quot;img&quot;);
var group = match.exec(html);
</code></pre>
<h3 id>第二步</h3>
<p>根据目标元素的标签，向下查找相同标签的打开和结束标签，并计数<br>
如果是打开标签，计数加1，否则计数减1。当计数为0时结束查找</p>
<pre><code>var regStrAll = &quot;&lt; */? *&quot;+tag+&quot;[^&gt;]*&gt;&quot;;
var matchAll = new RegExp(regStrAll, &quot;img&quot;);

var regStrClose = &quot;&lt; */ *&quot;+tag+&quot; *&gt;&quot;;
var matchClose = new RegExp(regStrClose, &quot;im&quot;);

var openCount = 1;
var lastCloseIndex = 0;
while(openCount &gt; 0){
	var groupAll=matchAll.exec(html);
	if(groupAll==null){
		break;
	}else{
		if(matchClose.test(groupAll[0])){
			openCount--;
			lastCloseIndex = groupAll.index+groupAll[0].length;
		}else{
			openCount++;
		}
	}
}
</code></pre>
<p>这样就可以完成嵌套元素的匹配了<br>
接下来我们对代码进行封装扩展，以实现getElementById、getElementByTag、getElementByClass三个常用函数</p>
<pre><code>function getElementById(html, id){
	return getElementByAttr(html, &quot;id&quot;, id);
}
function getElementByTag(html, tag){
	var regStr = &quot;&lt; *(&quot;+tag+&quot;)[^&gt;]*&gt;&quot;;
	return queryElement(regStr, html);
}
function getElementByClass(html, className){
	return getElementByAttr(html, &quot;class&quot;, className);
}
function getElementByAttr(html, key, value){
	var regStr = &quot;&lt; *([^ &gt;]*)[^&gt;]*?\\b&quot;+key+&quot; *= *\&quot;&quot;+value+&quot;\&quot;[^&gt;]*&gt;&quot;;
	return queryElement(regStr, html);
}
function queryElement(regStr, html){
	var match = new RegExp(regStr, &quot;img&quot;);
	var group = match.exec(html);
	if(group == null)return null;
	var searchStart = group.index+group[0].length;
	var closeIndex = queryCloseTag(group[1], html.substring(searchStart));
	return html.substring(group.index, searchStart+closeIndex);
}
function queryCloseTag(tag, html){
	var regStrAll = &quot;&lt; */? *&quot;+tag+&quot;[^&gt;]*&gt;&quot;;
	var matchAll = new RegExp(regStrAll, &quot;img&quot;);

	var regStrClose = &quot;&lt; */ *&quot;+tag+&quot; *&gt;&quot;;
	var matchClose = new RegExp(regStrClose, &quot;im&quot;);

	var openCount = 1;
	var lastCloseIndex = 0;
	while(openCount &gt; 0){
		var groupAll=matchAll.exec(html);
		if(groupAll==null){
			break;
		}else{
			if(matchClose.test(groupAll[0])){
				openCount--;
				lastCloseIndex = groupAll.index+groupAll[0].length;
			}else{
				openCount++;
			}
		}
	}
	return lastCloseIndex;
}
</code></pre>
<h3 id>特殊标签处理</h3>
<h4 id="1">1, 自结束标签如:</h4>
<p><code>&lt;br/&gt;, &lt;input type=&quot;text&quot;/&gt;</code></p>
<pre><code>function queryElement(regStr, html){
	var match = new RegExp(regStr, &quot;img&quot;);
	var group = match.exec(html);
	if(group == null)return null;
	var searchStart = group.index+group[0].length;
	var closeIndex = 0;
	if(/\/ *&gt;$/.test(group[0])==false){
		closeIndex = queryCloseTag(group[1], html.substring(searchStart));
	}
	return html.substring(group.index, searchStart+closeIndex);
}
</code></pre>
<h4 id="2">2, 省略结束标签如:</h4>
<p><code>&lt;br&gt;, &lt;input type=&quot;text&quot;&gt;</code></p>
<pre><code>function queryCloseTag(tag, html){
	var regStrAll = &quot;&lt; */? *&quot;+tag+&quot;[^&gt;]*&gt;&quot;;
	var matchAll = new RegExp(regStrAll, &quot;img&quot;);

	var regStrClose = &quot;&lt; */ *&quot;+tag+&quot; *&gt;&quot;;
	var matchClose = new RegExp(regStrClose, &quot;im&quot;);

	var openCount = 1;
	var lastCloseIndex = 0;
	while(openCount &gt; 0){
		var groupAll=matchAll.exec(html);
		if(groupAll==null){
			break;
		}else{
			if(matchClose.test(groupAll[0])){
				openCount--;
				lastCloseIndex = groupAll.index+groupAll[0].length;
			}else{
				openCount++;
				if(new RegExp(&quot;\\b&quot;+tag+&quot;\\b&quot;, &quot;i&quot;).test(&quot;input br img&quot;))return 0;
			}
		}
	}
	return lastCloseIndex;
}
</code></pre>
<h3 id="getelementbyclass">getElementByClass改进</h3>
<p>使用时发现getElementByClass经常同时需要多个class来查找元素，如getElementByClass(document.body.innerHtml, &quot;.page.in&quot;)</p>
<pre><code>function getElementByClass(html, classNames){
	var classArr = classNames.split(&quot;.&quot;);
	var classReg = &quot;&quot;;
	for(var i=0; i&lt;classArr.length; i++){
		var className = classArr[i];
		if(className.length&gt;0){
			classReg+=&quot;(?=.*?\\b&quot;+className+&quot;\\b)&quot;;
		}
	}
	var option = {
		index: 2,
		regStr: classReg
	};
	var regStr = &quot;&lt; *([^ |&gt;]*)[^&gt;]*?\\bclass *= *\&quot;([^\&quot;]*)\&quot;[^&gt;]*&gt;&quot;;
	return queryElement(regStr, html, option);
}
function queryElement(regStr, html, option){
	var match = new RegExp(regStr, &quot;img&quot;);
	var group = match.exec(html);
	if(group == null)return null;
	if(option!=null){
		while(group!=null){
			var nextContent = group[option.index];
			var nextMatch = new RegExp(option.regStr, &quot;im&quot;);
			if(nextMatch.test(nextContent)){
				break;
			}
			group = match.exec(html);
		}
		if(group == null)return null;
	}
	var searchStart = group.index+group[0].length;
	var closeIndex = 0;
	if(/\/ *&gt;$/.test(group[0])==false){
		closeIndex = queryCloseTag(group[1], html.substring(searchStart));
	}
	return html.substring(group.index, searchStart+closeIndex);
}
</code></pre>
<h3 id="queryelement">改进queryElement</h3>
<p>很多时候我们需要查找的结果是个数组，而不只是返回一个元素</p>
<pre><code>function queryElement(regStr, html, option){
	var match = new RegExp(regStr, &quot;img&quot;);
	var result = option.multiElement ? [] : null;
	var group = match.exec(html);
	while(group!=null){
		if(option.regStr!=null){
			while(group!=null){
				var nextContent = group[option.index];
				var nextMatch = new RegExp(option.regStr, &quot;im&quot;);
				if(nextMatch.test(nextContent)){
					break;
				}
				group = match.exec(html);
			}
			if(group == null)return result;
		}
		var searchStart = group.index+group[0].length;
		var closeIndex = 0;
		if(/\/ *&gt;$/.test(group[0])==false){
			closeIndex = queryCloseTag(group[1], html.substring(searchStart));
		}
		var targetHtml = html.substring(group.index, searchStart+closeIndex);
		if(result == null){
			result = targetHtml;
			break;
		}else{
			result.push(targetHtml);
			group = match.exec(html);
		}
	}
	return result;
}
</code></pre>
<h3 id>最终代码</h3>
<pre><code>function getElementById(html, id){
	return getElementByAttr(html, &quot;id&quot;, id, false);
}
function getElementsByTag(html, tag){
	var regStr = &quot;&lt; *(&quot;+tag+&quot;)[^&gt;]*&gt;&quot;;
	return queryElement(regStr, html, {multiElement: true});
}
function getElementsByClass(html, classNames){
	var classArr = classNames.split(&quot;.&quot;);
	var classReg = &quot;&quot;;
	for(var i=0; i&lt;classArr.length; i++){
		var className = classArr[i];
		if(className.length&gt;0){
			classReg+=&quot;(?=.*?\\b&quot;+className+&quot;\\b)&quot;;
		}
	}
	var option = {
		multiElement: true,
		index: 2,
		regStr: classReg
	};
	var regStr = &quot;&lt; *([^ |&gt;]*)[^&gt;]*?\\bclass *= *\&quot;([^\&quot;]*)\&quot;[^&gt;]*&gt;&quot;;
	return queryElement(regStr, html, option);
}
function getElementByAttr(html, key, value, multiElement){
	var regStr = &quot;&lt; *([^ |&gt;]*).*?\\b&quot;+key+&quot; *= *\&quot;&quot;+value+&quot;\&quot;[^&gt;]*&gt;&quot;;
	return queryElement(regStr, html, {multiElement: multiElement});
}
function queryElement(regStr, html, option){
	var match = new RegExp(regStr, &quot;img&quot;);
	var result = option.multiElement ? [] : null;
	var group = match.exec(html);
	while(group!=null){
		if(option.regStr!=null){
			while(group!=null){
				var nextContent = group[option.index];
				var nextMatch = new RegExp(option.regStr, &quot;im&quot;);
				if(nextMatch.test(nextContent)){
					break;
				}
				group = match.exec(html);
			}
			if(group == null)return result;
		}
		var searchStart = group.index+group[0].length;
		var closeIndex = 0;
		if(/\/ *&gt;$/.test(group[0])==false){
			closeIndex = queryCloseTag(group[1], html.substring(searchStart));
		}
		var targetHtml = html.substring(group.index, searchStart+closeIndex);
		if(result == null){
			result = targetHtml;
			break;
		}else{
			result.push(targetHtml);
			group = match.exec(html);
		}
	}
	return result;
}
function queryCloseTag(tag, html){
	var regStrAll = &quot;&lt; */? *&quot;+tag+&quot;[^&gt;]*&gt;&quot;;
	var matchAll = new RegExp(regStrAll, &quot;img&quot;);

	var regStrClose = &quot;&lt; */ *&quot;+tag+&quot; *&gt;&quot;;
	var matchClose = new RegExp(regStrClose, &quot;im&quot;);

	var openCount = 1;
	var lastCloseIndex = 0;
	while(openCount &gt; 0){
		var groupAll=matchAll.exec(html);
		if(groupAll==null){
			break;
		}else{
			if(matchClose.test(groupAll[0])){
				openCount--;
				lastCloseIndex = groupAll.index+groupAll[0].length;
			}else{
				openCount++;
				if(new RegExp(&quot;\\b&quot;+tag+&quot;\\b&quot;, &quot;i&quot;).test(&quot;input br image&quot;))return 0;
			}
		}
	}
	return lastCloseIndex;
}
</code></pre>
<p><a href="http://www.fengshangbin.com/match-html2/">下一篇</a> 实现#(id)空格(层级).(类)<a href="http://www.fengshangbin.com/match-html/%E5%B1%9E%E6%80%A7"></a>混合查询</p>
]]></content:encoded></item><item><title><![CDATA[LazyPage-node.js]]></title><description><![CDATA[<h1 id="lazypagenodejs">LazyPage-node.js</h1>
<p>LazyPage server node.js version<br>
LazyPage 后端 node.js 版本<br>
GitHub Pages: <a href="https://github.com/fengshangbin/LazyPage-node.js">https://github.com/fengshangbin/LazyPage-node.js</a></p>
<h1 id="lazypage">关于 LazyPage</h1>
<p>参见 <a href="https://github.com/fengshangbin/LazyPage">https://github.com/fengshangbin/LazyPage</a></p>
<h1 id>在线测试</h1>
<p><a href="https://www.fengshangbin.com/node/lazypage/">https://www.fengshangbin.com/node/lazypage/</a></p>
<h1 id="lazypagenodejs">如何使用 LazyPage-node.js</h1>
<p>1，导入 lazypage-node.js 到你的项目</p>
<pre><code>npm install --save-dev express
npm install --save-dev</code></pre>]]></description><link>http://www.fengshangbin.com/lazypage-node-js/</link><guid isPermaLink="false">5c80ceae2b2cd10001cf30d3</guid><category><![CDATA[后端]]></category><category><![CDATA[开源]]></category><dc:creator><![CDATA[fengshangbin]]></dc:creator><pubDate>Thu, 07 Mar 2019 07:57:39 GMT</pubDate><content:encoded><![CDATA[<h1 id="lazypagenodejs">LazyPage-node.js</h1>
<p>LazyPage server node.js version<br>
LazyPage 后端 node.js 版本<br>
GitHub Pages: <a href="https://github.com/fengshangbin/LazyPage-node.js">https://github.com/fengshangbin/LazyPage-node.js</a></p>
<h1 id="lazypage">关于 LazyPage</h1>
<p>参见 <a href="https://github.com/fengshangbin/LazyPage">https://github.com/fengshangbin/LazyPage</a></p>
<h1 id>在线测试</h1>
<p><a href="https://www.fengshangbin.com/node/lazypage/">https://www.fengshangbin.com/node/lazypage/</a></p>
<h1 id="lazypagenodejs">如何使用 LazyPage-node.js</h1>
<p>1，导入 lazypage-node.js 到你的项目</p>
<pre><code>npm install --save-dev express
npm install --save-dev lazypage-node
</code></pre>
<pre><code>var express = require('express');
var app = express();
var lazypage = require('lazyPage-node');
</code></pre>
<p>2，在项目初始化时 执行 LazyPage 初始化</p>
<pre><code>app.use(lazypage.filter('test/public'));
app.use(express.static('test/public'));
</code></pre>
<h3 id="lazypagenodejs">LazyPage-node.js 使用示例</h3>
<p>test/app.js</p>
<pre><code>var express = require('express');
var app = express();

var lazypage = require('lazypage-node');
app.use(lazypage.filter('test/public'));
app.use(express.static('test/public'));

var server = app.listen(8081, function() {
  console.log('LazyPage node.js测试，访问地址为 http://localhost:8081/');
});
</code></pre>
]]></content:encoded></item></channel></rss>