markdown @ shopify /主题产品

@ shopify /主题产品

product.js
this.Shopify = this.Shopify || {};
this.Shopify.theme = this.Shopify.theme || {};
this.Shopify.theme.product = (function (exports) {
  'use strict';

  /**
   * Find a match in the project JSON (using a ID number) and return the variant (as an Object)
   * @param {Object} product Product JSON object
   * @param {Number} value Accepts Number (e.g. 6908023078973)
   * @returns {Object} The variant object once a match has been successful. Otherwise null will be return
   */
  function getVariantFromId(product, value) {
    _validateProductStructure(product);

    if (typeof value !== 'number') {
      throw new TypeError(value + ' is not a Number.');
    }

    var result = product.variants.filter(function(variant) {
      return variant.id === value;
    });

    return result[0] || null;
  }

  /**
   * Convert the Object (with 'name' and 'value' keys) into an Array of values, then find a match & return the variant (as an Object)
   * @param {Object} product Product JSON object
   * @param {Object} collection Object with 'name' and 'value' keys (e.g. [{ name: "Size", value: "36" }, { name: "Color", value: "Black" }])
   * @returns {Object || null} The variant object once a match has been successful. Otherwise null will be returned
   */
  function getVariantFromSerializedArray(product, collection) {
    _validateProductStructure(product);

    // If value is an array of options
    var optionArray = _createOptionArrayFromOptionCollection(product, collection);
    return getVariantFromOptionArray(product, optionArray);
  }

  /**
   * Find a match in the project JSON (using Array with option values) and return the variant (as an Object)
   * @param {Object} product Product JSON object
   * @param {Array} options List of submitted values (e.g. ['36', 'Black'])
   * @returns {Object || null} The variant object once a match has been successful. Otherwise null will be returned
   */
  function getVariantFromOptionArray(product, options) {
    _validateProductStructure(product);
    _validateOptionsArray(options);

    var result = product.variants.filter(function(variant) {
      return options.every(function(option, index) {
        return variant.options[index] === option;
      });
    });

    return result[0] || null;
  }

  /**
   * Creates an array of selected options from the object
   * Loops through the project.options and check if the "option name" exist (product.options.name) and matches the target
   * @param {Object} product Product JSON object
   * @param {Array} collection Array of object (e.g. [{ name: "Size", value: "36" }, { name: "Color", value: "Black" }])
   * @returns {Array} The result of the matched values. (e.g. ['36', 'Black'])
   */
  function _createOptionArrayFromOptionCollection(product, collection) {
    _validateProductStructure(product);
    _validateSerializedArray(collection);

    var optionArray = [];

    collection.forEach(function(option) {
      for (var i = 0; i < product.options.length; i++) {
        if (product.options[i].name.toLowerCase() === option.name.toLowerCase()) {
          optionArray[i] = option.value;
          break;
        }
      }
    });

    return optionArray;
  }

  /**
   * Check if the product data is a valid JS object
   * Error will be thrown if type is invalid
   * @param {Array} product Product JSON object
   */
  function _validateProductStructure(product) {
    if (typeof product !== 'object') {
      throw new TypeError(product + 'is not an object.');
    }

    if (Object.keys(product).length === 0 && product.constructor === Object) {
      throw new Error(product + 'is empty.');
    }
  }

  /**
   * Validate the structure of the array
   * It must be formatted like jQuery's serializeArray()
   * @param {Array} collection Array of object [{ name: "Size", value: "36" }, { name: "Color", value: "Black" }]
   */
  function _validateSerializedArray(collection) {
    if (!Array.isArray(collection)) {
      throw new TypeError(collection + 'is not an array.');
    }

    if (collection.length === 0) {
      throw new Error(collection + 'is empty.');
    }

    if (collection[0].hasOwnProperty('name')) {
      if (typeof collection[0].name !== 'string') {
        throw new TypeError(
          'Invalid value type passed for name of option ' +
            collection[0].name +
            '. Value should be string.'
        );
      }
    } else {
      throw new Error(collection[0] + 'does not contain name key.');
    }
  }

  /**
   * Validate the structure of the array
   * It must be formatted as list of values
   * @param {Array} collection Array of object (e.g. ['36', 'Black'])
   */
  function _validateOptionsArray(options) {
    if (Array.isArray(options) && typeof options[0] === 'object') {
      throw new Error(options + 'is not a valid array of options.');
    }
  }

  exports.getVariantFromId = getVariantFromId;
  exports.getVariantFromSerializedArray = getVariantFromSerializedArray;
  exports.getVariantFromOptionArray = getVariantFromOptionArray;

  return exports;

}({}));
readme.md
getVariantFromId(product, value)
Find a variant with a matching ID in the project JSON and return it.

product: Product JSON object. Usually it is the Product object generated from Liquid. Note: The JSON generated from Liquid is different from the conventional {{ json }} filter due to some properties not being exposed.

value: Product ID (e.g. 6908023078973)

getVariantFromSerializedArray(product, collection)
Find a variant which matches a collection of name and values (like what is returned by jQuery's serializeArray() method) and return it.

product: Product JSON object. Usually it is the Product object generated from Liquid. Note: The JSON generated from Liquid is different from the conventional {{ json }} filter due to some properties not being exposed.

collection: Object with 'name' and 'value' keys

[
  {
    name: 'Size',
    value: '36'
  },
  {
    name: 'Color',
    value: 'Black'
  }
];
getVariantFromOptionArray(product, options)
Find a matching variant using an array of option values and return it.

product: Product JSON object. Usually it is the Product object generated from Liquid. Note: The JSON generated from Liquid is different from the conventional {{ json }} filter due to some properties not being exposed.

options: List of submitted values

['36', 'Black'];

markdown keybase.md

keybase.md
### Keybase proof

I hereby claim:

  * I am faizaand on github.
  * I am sirfaizdat (https://keybase.io/sirfaizdat) on keybase.
  * I have a public key ASD8WVv7RFc134E-Im3i6JlAQJhJQYjvy7dOk8yW5UnKdAo

To claim this, I am signing this object:

```json
{
  "body": {
    "key": {
      "eldest_kid": "0120fc595bfb445735df813e226de2e899404098494188efcbb74e93cc96e549ca740a",
      "host": "keybase.io",
      "kid": "0120fc595bfb445735df813e226de2e899404098494188efcbb74e93cc96e549ca740a",
      "uid": "79d6026f97db896aac1cf4b0f176c819",
      "username": "sirfaizdat"
    },
    "merkle_root": {
      "ctime": 1540741309,
      "hash": "a7bd7ec5d48f858bd5569334fffcf349acd5151ff351d11e16371e04af694fe1a113410099960aa28b353e2a987c2e4edfd6327e9214c0a8421842469069e763",
      "hash_meta": "b5e0ea0be4f3f8dc280058c5e03898de1141c031967bf0fa582a814db02b1d8f",
      "seqno": 3861161
    },
    "service": {
      "entropy": "wYezsss/97j2Bsm+ZjH0w8ss",
      "name": "github",
      "username": "faizaand"
    },
    "type": "web_service_binding",
    "version": 2
  },
  "client": {
    "name": "keybase.io go client",
    "version": "2.8.0"
  },
  "ctime": 1540741343,
  "expire_in": 504576000,
  "prev": "3640246c6e0a6b42550e7c80475b48b8e2c637ab3c3b3c4fa670761d13864eb7",
  "seqno": 29,
  "tag": "signature"
}
```

with the key [ASD8WVv7RFc134E-Im3i6JlAQJhJQYjvy7dOk8yW5UnKdAo](https://keybase.io/sirfaizdat), yielding the signature:

```
hKRib2R5hqhkZXRhY2hlZMOpaGFzaF90eXBlCqNrZXnEIwEg/Flb+0RXNd+BPiJt4uiZQECYSUGI78u3TpPMluVJynQKp3BheWxvYWTESpcCHcQgNkAkbG4Ka0JVDnyAR1tIuOLGN6s8OzxPpnB2HROGTrfEIOPrmLfTfRcrHuUN+wrMtdvkeNlsGrL4C1qzkcl9huEsAgHCo3NpZ8RA+x1LIYpqtsIAp6sSygQVQXPPot7caZVt7siCRrmzU5sKE5Jsom2nZ2EW4+gDCKoqWvQzeQRpizFsoYuO8qY9A6hzaWdfdHlwZSCkaGFzaIKkdHlwZQildmFsdWXEII5CPs7Zi23fuF62QVx8flydBs7YmtSfl06kucnveYlXo3RhZ80CAqd2ZXJzaW9uAQ==

```

And finally, I am proving ownership of the github account by posting this as a gist.

### My publicly-auditable identity:

https://keybase.io/sirfaizdat

### From the command line:

Consider the [keybase command line program](https://keybase.io/download).

```bash
# look me up
keybase id sirfaizdat
```

markdown vLans的多播代理

使Sonos和Chromcast在新的CentOS 7最小安装上与vlans一起工作

centos7-setup
```
yum update -y
yum install net-tools nano git -y
yum install epel-release -y
yum install python-pip -y
pip install --upgrade pip
pip install netifaces
git clone https://github.com/alsmith/multicast-relay.git
systemctl disable firewalld
systemctl stop firewalld
```
Check that its working by running it in the foreground
```
/usr/bin/python /root/multicast-relay/multicast-relay.py --interfaces eth0 eth1 eth2 --verbose --foreground
```
If you don't see traffic being relayed something is wrong. If its working lets make it run at boot.
```
crontab -e
```
Add and save the following, change the interfaces if neccesary
```
@reboot    /usr/bin/python /root/multicast-relay/multicast-relay.py --interfaces eth0 eth1 eth2 --wait
```

Reboot and check that its running after you reboot
```
ps aux | grep python
```

markdown 脚手架

md
## 背景
从事中后台开发已经将近两年了,在日常的工作中,经常会听到类似的抱怨:"XXX写的代码真垃圾,完全看不懂", "业内明明提倡用tab缩进,你为啥要用空格?", "你这个项目要怎么再本地调试啊?为啥这么麻烦?连文档都没有?","我去,这哥们到低有没有用ESLint,这么多报错?"等等,我们会发现近几年我们已经将中后台的开发收拢到React+Webpack的技术栈,但是100个人心中有100份webpack配置,在开发中依然会存在编码习惯差异,在合作中依然会存在大量沟通成本,在项目交接时依然会存在各种吐槽。

面对上面的问题,我们小组(由于组织变动,当年的小组已经不在了)首先想到的是通过脚手架的方案来形成组内的统一开发规范,从而提高开发效率,降低一些不必要的沟通成本。what?天猫不是有tap了吗,你们又要重新造轮子?现在市面上那么多开发规范,你们又要搞一套?在这里我们只想做以下澄清:
* tap在面向消费者领域已经支持的很好了,但是在面向中后台开发有很多不足之处,我们希望能够提供一个更贴合中后台的脚手架方案(其实在本地开发中我们使用了不少tap现有的功能);
* 我们不生产规范,我们只是规范的搬运工,我们不去讨论语句后面跟分号还是不跟分号好,我们尊崇“约定优于配置原则”,只要组内成员达成一致就好;
* 我们更多的是希望把日常开发过程的一些经验以及最佳实践能够沉淀下来,降低大家的试错及调优成本;

因此大家不用纠结这是不是一个新的轮子,如果你一定要纠结,那我就只有承认它是了,但是我写这篇文章的主要目的并不是为了炫耀我们又造了一个新轮子,而是希望把我们制作这个脚手架的过程记录下来,给予那些刚刚开始考虑前端工程化的同学一点借鉴,同时将我们的一些最佳实践以及思考沉淀下来,方便随时自省。

## 目录结构
目录结构是我们在开始构建一个项目时首先要考虑的东西,但是我们发现,在进行项目目录结构设计时,大家往往有不同的想法,例如工具库是应该放在common文件夹还是应该放在utils文件夹里面?组件文件夹是应该叫widgets还是应该叫components?构建产出文件是应该放在build还是dist文件夹下?这些问题其实都不重要,但是,如果我们不形成一个统一的目录结构,就会给合作方啊带来一定的理解成本或是文件查找成本,因此我们对目录结构做了最基本的约定:
* 应用维度
```bash
build/                    webpack构建完成输出目录
src/                      源文件目录
  constants/              常量定义
  data/                   数据
  widgets/                纯样式组件库
  components/             全局业务组件库
    xxx/
      index.jsx
      index.less
  pages/                  页面目录
    xxx/                  子页面
      components/        
        index.jsx         
        index.less
      index.jsx
      index.less      
  entries/                入口文件
  utils/                  通用功能组件库 
lib/                      babel编译输出目录
docs/                     文档
test/                     测试用例
demo/                     演示页面文件
.babelrc                  babel配置文件
.eslintrc.js              eslint配置文件
.prettierrc               prettier格式化配置
.yo-rc.json               yeoman配置文件
.gitignore                gitignore文件
.storybook/               storybook相关配置
README.md                 项目说明
tap.conf.js               资源代理配置
webpack.config.dev.js     开发环境webpack配置
webpack.config.dll.js     打包第三方依赖包webpack配置
webpack.config.js         生产环境微博pack配置
package.json              package文件
postcss.config.js         postcss配置
```
* 组件维度

```bash
build/                    webpack构建完成输出目录
src/                      源文件目录
lib/                      babel编译输出目录
docs/                     文档
test/                     测试用例
demo/                     演示页面文件
.babelrc                  babel配置文件
.eslintrc.js              eslint配置文件
.prettierrc               prettier格式化配置
.yo-rc.json               yeoman配置文件
.gitignore                gitignore文件
.storybook/               storybook相关配置
README.md                 项目说明
webpack.config.js         生产环境微博pack配置
package.json              package文件
postcss.config.js         postcss配置
```

PS: 通过上面的目录结构我们可以看到,对于一些工具的配置,例如 .babalrc、.postcss.config.js等等,我们尽量放到对应的配置中,而不是放到js script或者webpack配置中,这样方面我们快速查看对应工具配置。
## 初始化
我们这里说到的初始化主要包含两类:一类为项目初始化,这类初始化是在项目创建之初用于快速完成项目框架创建,这种初始化在整个项目周期中往往只会执行一次;另一类是模块初始化,这类初始化将伴随项目的整个生命周期,它可以帮我们快速初始化诸如页面、组件、demo等项目需要使用的模块,尤其是像React这种模板代码特别多的框架,可以帮我们节省很大一部分开发成本。

初始化的思路和实现方案其实再简单不过了,主要包含以下三步:
* 提前制作好模板;
* 根据用户在初始化时的输入结合模板产出真实代码;
* 将真实代码拷贝到指定目录;

在我们的脚手架中使用到[Yeoman](http://yeoman.io/)来实现初始化功能,它很好的封装了模板制作、代码生成、用户交互、代码复制等功能,可以帮助我们快速实现脚手架的初始化功能,但是工具的使用不在本篇文章的讨论范围之内,有兴趣的同学可以自行了解。

## 代码规范
JS其实只是提供了最基本的语法要求,它并没有规定开发者要如果去书写,所以才有了要不要分号的争论,所谓的代码规范无非是大家在日常开发中的最佳实践以及一些共识的沉淀,为了更好的编写代码以及日后维护。业内比较有名的前端代码规范主要有[Google JS代码规范](https://google.github.io/styleguide/jsguide.html)和[Airbnb JS代码规范](https://github.com/airbnb/javascript),其实集团前端委员会也定义了一套代码规范,作为集团的一部分,因此我们首选集团的代码规范。

有了代码规范,我们最先想到的是如果在组内统一这个代码规范,最基本的就是要求大家把集团的代码规范都通读一遍(虽然读了一遍也不一定记得住,记住了也不一定会用),另外就是在初始化时根据代码规范来初始化项目及模块代码,除了上面两种方式,我们还有下面两件法宝:

### [ESLint](https://eslint.org/)
ESLint是当下非常流行的一款JS代码检测工具,通过合理的配置,在配合IDE的ESlint插件能够方便快捷的检测出代码上不符合规范的地方,并给出响应级别的提示,开发同学要做到就是将IDE中的ESLint插件打开就可以了,ESLint的检测主要包含以下两种类型:
* 语法检测 - 对代码的语法进行检测,可以避免用户在编码过程中出现低级的语法错误,从而导致代码运行失败;
* 风格检测 - 对代码的风格进行检测,可以帮助用户约束代码风格,保证项目中代码风格统一;

具体ESLint的配置&使用方法不在本文的介绍范围之内,大家可以去官网进行查看,B3提供了一份比较通用的[ESLint配置文件](http://gitlab.alibaba-inc.com/B3/b3-func-init/blob/master/src/generators/app/templates/.eslintrc.js)。
### [prettier](https://prettier.io/)
上面介绍ESLint的时候提到了代码风格检测,检测虽易,但是执行起来就没有那么容易了,比如在我们的代码规范中约定代码缩进使用空格而不是tab,但是或多同学已经习惯使用tab来进行缩进,如果强行要求他使用空格真的是一件非常艰难的事情,写起代码来也会非常别扭。面对这种场景,prettier这款神器就能发挥它的作用了,妈妈再也不用担心我的项目代码风格五花八门了。prettier可以通过一份配置来规定代码的统一风格(其实prettier的默认配置已经基本满足我们的需求,几乎不用做太大变更就可以直接使用),在开发过程中,用户并不需要遵守这份风格规范,甚至你可以任性地把所有代码都写在同一行(开玩笑,应该没人会这么做),我们仅仅需要在代码构建的时候执行一下prettier将所有的源码都格式化一遍,再凌乱的代码也能边得井井有条。

具体prettier的配置&使用方法不在本文的介绍范围之内,大家可以去官网进行查看,B3提供了一份通过约定的[prettier配置文件](http://gitlab.alibaba-inc.com/B3/b3-func-init/blob/master/src/generators/app/templates/.prettierrc)


PS:虽然上面两款法宝可以大大降低我们进行代码规范约束的成本,但是类似函数命名、变量声明位置等更加细节的规范约束还是需要大家的共同努力,才会出现读别人的代码如看自己的代码一般顺滑。

## 本地开发
本地开发是整个项目开发周期中非常重要的一环,我们不可能开发完代码就直接发布上线,肯定要进行本地验证调试,如何让本地调试更加接近线上真实环境,将决定整个项目的开发效率及联调成本,下面将通过项目调试及组件调试两个方面介绍B3在本地开发中使用的方案:
### [tap](http://tap.alibaba-inc.com/)
从前,我们看到过这样的本地调试方式,就是在本地起一个server,然后将异步接口数据拉下来放到mock文件夹下,然后在本地访问页面请求这些接口时,从mock文件夹下拿响应的数据来返回,这种方案带来了以下几个问题:
* 需要手动将所有的接口都拉到本地,开发效率较低;
* 调用的是mock接口并不是线上真实接口,并不能体现线上真实情况,尤其是在写操作的时候;
* 页面模板(即VM)也是使用本地mock,无法真实拿到线上真实模板数据;

tap工具为我们本地项目调试提供了更加便捷的方案,它可以让我们在尽可能模拟线上真实环境的情况下,调试本地代码:

上图的主要思路是:
* 访问线上或者预发真实页面,保证页面模板和异步接口更接近真实环境;
* 将页面中的cdn资源全部代理到本地;
* 在本地通过webpack dev server 启动9898端口服务,用于访问本地调试代码;
* 在本地通过tap assets 启动8000端口服务,用于通过cdn链接去请求cdn资源;
* 在本地通过tap moniter 启动80/433端口服务,通过正则方式进行cdn资源匹配,将符合本地调试代码的cdn资源代理到9898端口,用于读取调试代码,将不符合本地调试代码的cdn资源代理到8000获取线上cdn资源;

具体tap的配置&使用方法不在本文的介绍范围之内,大家可以去官网进行查看,B3提供了一份通过约定的[tap配置文件](http://gitlab.alibaba-inc.com/B3/b3-func-init/blob/master/src/generators/app/templates/tap.conf.js)

### [storybook](https://storybook.js.org/)
上面tap的方案解决了本地应用代码调试的问题,可是,我们有时候会面临另外一种调试问题,就是组件的调试,按照通常的情况,我们需要建立一些测试页面来进行组件的调试,这样成本较高,而且不便于维护,还有就是在业务上线时,一定要记得将这些测试页面删掉,否则用户如果通过特殊渠道拿到了测试链接进行访问,会认为是平台的bug。

针对上面的问题,我们就要请出组件开发利器——storybook。storybook是当下非常流行的一款组件开发工具,在组件开发过程中,它可以方便的提供组件demo实例管理,组件本地开发工具,在项目开发过程中我们可以将组件开发和页面开发进行分离,在组件开发过程中创建组件demo,使用storybook来进行本地调试及验证,当组件完成验证后在页面开发过程中再使用已经开发完成的组件就可以高枕无忧了。B3 提供应用维度的组件demo初始化以及组件维度的组件demo初始化方案来快速建立组件demo进行本地调试及功能验证。

具体storybook的配置&使用方法不在本文的介绍范围之内,大家可以去官网进行查看,B3提供了一套完整的组件开发流程,大家可以前往试用。

## 项目构建
当整个项目完成开发以后,我们就需要考虑项目构建了,通常对于不同类型的代码文件我们可能需试用不同的方式来进行构建,但最终我们需要使用webpack来构建整个项目代码。

### [Babel](https://babeljs.io/)
Babel为我们使用新的ES语法提供了可能,我们可以尽情享受新的ES语法为我们开发带来的便利,而不用担忧兼容性问题。

具体babel的配置&使用方法不在本文的介绍范围之内,大家可以去官网进行查看,B3提供了一份通过约定的[babel配置文件](http://gitlab.alibaba-inc.com/B3/b3-func-init/blob/master/src/generators/app/templates/.babelrc)
### [postCss](https://postcss.org/)
postCss提供丰富的插件来帮助我们解决浏览器的兼容性问题,或者为我们的开发提供遍历。

具体postCss的配置&使用方法不在本文的介绍范围之内,大家可以去官网进行查看,B3提供了一份通过约定的[postcss配置文件](http://gitlab.alibaba-inc.com/B3/b3-func-init/blob/master/src/generators/app/templates/postcss.config.js)

PS:通过上面的配置可以看到,我们为PC和无线端提供了不同的postCss配置,另外在这里要安利一下postCss的pxToViewport插件在解决无线端的适配问题上非常强大,远胜百分比及em/rem方案;
### [webpack](https://webpack.js.org/)
webpack大家肯定并不陌生,进行中后台开发的同学基本都会用到,但是webpack的配置确实让人头痛,正如我前面说到的,100个人心中有100个webpack配置,之前我也写过一篇关于[webpack配置调优](https://www.atatech.org/articles/105165)的文章,因此在这里就不加累述了。

具体webpack的配置&使用方法不在本文的介绍范围之内,大家可以去官网进行查看,B3提供了3个webpack配置供大家参考[webpack.config.dll.js](http://gitlab.alibaba-inc.com/B3/b3-func-init/blob/master/src/generators/app/templates/webpack.config.dll.js)、[webpack.config.dev.js](http://gitlab.alibaba-inc.com/B3/b3-func-init/blob/master/src/generators/app/templates/webpack.config.dev.js)、[webpack.config.js](http://gitlab.alibaba-inc.com/B3/b3-func-init/blob/master/src/generators/app/templates/webpack.config.js)
## 项目发布
项目发布作为项目周期的最后一环,关系到项目能否正常上线,说来惭愧,B3在这一环做的并不好,现在我们还是在使用本地构建并通过gitlab assets推送cdn的方案。下面只能简单列举一下现在集团内其他团队在项目发布环节的主要方案。
### 云构建
云构建主要用于解决以下两个问题:
* 各个项目成员本地环境不一致的问题,本地环境的不一致往往会导致最终打包结果的不一致,最终会导致整个项目的不稳定,因此纯净且一致的打包环境对于项目构建和发布非常重要;
* 如果将build后的代码推送到gitlab会导致线上代码diff变得非常艰难,在云构建场景下只需要提交源码;
* 如果将每次打包后的结果都推送到gitlab会导致项目的体积呈指数级上升,到最后可能因为项目体积过大而不得不切换新的项目空间(这并不能从根本上解决问题),在云构建场景下只需要提交源码;

集团内的云构建方案有: [DEF](http://def.alibaba-inc.com/)
### 持续集成

## 其他

## 总结

markdown [什么是Java中的InputStream和输出流?我们为什么以及何时使用它们?] #java

[什么是Java中的InputStream和输出流?我们为什么以及何时使用它们?] #java

streams.md
The goal of InputStream and OutputStream is to abstract different ways to input and output: whether the stream is a file, a web page, or the screen shouldn't matter. All that matters is that you receive information from the stream (or send information into that stream.)

InputStream is used for many things that you read from.

OutputStream is used for many things that you write to.

Here's some sample code. It assumes the InputStream instr and OutputStream osstr have already been created:

```Java
int i;
while ((i = instr.read()) != -1) {
    osstr.write(i);
}

instr.close();
osstr.close();
```

Ref:
- [Stackoverflow answers](https://stackoverflow.com/questions/1830698/what-is-inputstream-output-stream-why-and-when-do-we-use-them)

markdown Ubuntu配置Aria2服务器+ AriaNG web面板

.md
# Ubuntu 配置Aria2服务器 + AriaNG web面板

## 安装nginx和aria2

```
sudo apt-get install nginx aria2
```

## 下载AriaNG面板

下载地址 https://github.com/mayswind/AriaNg/releases 

解压出来上传到`/var/www/html`下,刷新服务器地址可以看到AriaNG面板

## 配置Aria2

创建一个目录存放aria2配置终端输入:

```
sudo mkdir /etc/aria2    #新建文件夹
sudo touch /etc/aria2/aria2.session    #新建session文件
sudo chmod 777 /etc/aria2/aria2.session    #设置aria2.session可写
```

```
sudo vi /etc/aria2/aria2.conf    #创建并编辑配置文件
```

输入以下内容(注意修改下载目录等)

```
#允许rpc
enable-rpc=true
#允许所有来源, web界面跨域权限需要
rpc-allow-origin-all=true
#允许非外部访问
rpc-listen-all=true
#RPC端口, 仅当默认端口被占用时修改
#rpc-listen-port=6800

#最大同时下载数(任务数), 路由建议值: 3
max-concurrent-downloads=5
#断点续传
continue=true
#同服务器连接数
max-connection-per-server=10
#最小文件分片大小, 下载线程数上限取决于能分出多少片, 对于小文件重要
min-split-size=1M
#单文件最大线程数, 路由建议值: 5
split=100
#下载速度限制
max-overall-download-limit=0
#单文件速度限制
max-download-limit=0
#上传速度限制
max-overall-upload-limit=0
#单文件速度限制
max-upload-limit=0
#断开速度过慢的连接
#lowest-speed-limit=0
#验证用,需要1.16.1之后的release版本
#referer=*

#从会话文件中读取下载任务
input-file=/etc/aria2/aria2.session
#在Aria2退出时保存`错误/未完成`的下载任务到会话文件
save-session=/etc/aria2/aria2.session
#定时保存会话,需要1.16.1之后的release版
#save-session-interval=60

#文件保存路径, 默认为当前启动位置
dir=/home/你的用户名/下载
禁用IPv6, 默认:false
disable-ipv6=true
#文件缓存, 使用内置的文件缓存, 如果你不相信Linux内核文件缓存和磁盘内置缓存时使用, 需要1.16及以上版本
#disk-cache=0
#另一种Linux文件缓存方式, 使用前确保您使用的内核支持此选项, 需要1.15及以上版本(?)
#enable-mmap=true
#文件预分配, 能有效降低文件碎片, 提高磁盘性能. 缺点是预分配时间较长
#所需时间 none < falloc ? trunc << prealloc, falloc和trunc需要文件系统和内核支持
file-allocation=prealloc # 建议关闭,否则大文件很慢

#启用本地节点查找
bt-enable-lpd=true
#添加额外的tracker
#bt-tracker=<URI>,…
#单种子最大连接数
#bt-max-peers=55
#强制加密, 防迅雷必备
#bt-require-crypto=true
#当下载的文件是一个种子(以.torrent结尾)时, 自动下载BT
follow-torrent=true
#BT监听端口, 当端口屏蔽时使用
#listen-port=6881-6999

#不确定是否需要,为保险起见,need more test
enable-dht=false
bt-enable-lpd=false
enable-peer-exchange=false
#修改特征
user-agent=uTorrent/2210(25130)
peer-id-prefix=-UT2210-
#修改做种设置, 允许做种
seed-ratio=0
#保存会话
force-save=false
bt-hash-check-seed=true
bt-seed-unverified=true
bt-save-metadata=true
#定时保存会话,需要1.16.1之后的某个release版本(比如1.16.2)
#save-session-interval=60
```

## 测试运行

运行

`sudo aria2c --conf-path=/etc/aria2/aria2.conf`

另外: 加-D后台运行

有错误是aria2.conf配置错误,进行修正.

如果没有提示错误,按 ctrl+c 停止运行

4.做成系统服务
新建与配置服务文件

`sudo vi /etc/init.d/aria2c`

新建与配置服务

在vi里面输入下面内容保存.

```
#!/bin/sh
### BEGIN INIT INFO
# Provides:          aria2
# Required-Start:    $remote_fs $network
# Required-Stop:     $remote_fs $network
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Aria2 Downloader
### END INIT INFO
 
case "$1" in
start)
 
echo -n "Starting aria2c"
sudo -u hsli aria2c --conf-path=/etc/aria2/aria2.conf -D
#sudo -u后面的是你正在使用的用户名,因为我用的XBian,用debian的是pi(没改用户的话)
;;
stop)
 
echo -n "Shutting down aria2c "
killall aria2c
;;
restart)
 
killall aria2c
sudo -u hsli aria2c --conf-path=/etc/aria2/aria2.conf -D
#同上面的一样,根据自己的用户名改xbian。
;;
esac
exit
```

然后修改文件权限

```
sudo chmod 755 /etc/init.d/aria2c
```

添加Aria2c服务到开机启动

`sudo update-rc.d aria2c defaults`

测试服务

```
sudo service aria2c start #启动Aria2c
sudo service aria2c restart #重启Aria2c
sudo service aria2c stop #关闭Aria2c
```
.md
# Caddy面板

装好caddy之后

```
yum install unzip  #CentOS系统
apt-get install unzip  #Debian和Ubuntu系统
cd /var/www/caddy && rm -rf index.html
wget https://github.com/mayswind/AriaNg/releases/download/0.3.0/aria-ng-0.3.0.zip
unzip aria-ng-0.3.0.zip
```

配置caddyfile即可

markdown Windows Aria2服务器设置

.md
# Windows Aria2服务器设置

> 下载 Aria2 及 Web 管理面板

> 废话

> Aria2 是一个命令行下载工具,所以使用的时候要敲命令,可是每下载一个文件敲一条命令,太麻烦了,那咋办?Aria2 支持远程接口调用,只需要配置一个 Web 管理面板就可以在浏览器管理 Aira2 了

1.下载并解压 Aria2 主程序

进入 https://github.com/aria2/aria2/releases 下载 Aria2 主程序

![Aria2 下载](https://cdn.mivm.cn/www.mivm.cn/archives/windows-aria2/01.jpg)

这里有不同平台的压缩包,Windows下载 [win-32bit] 或者 [win-64bit],下载后解压,解压到剩余空间比较大的地方,不要解压在桌面。

2.下载并解压 Web 管理面板

进入 https://github.com/mayswind/AriaNg/releases 下载 Web 管理面板,下载第一个 ZIP 压缩包,下载后解压。

3.下载 Web 服务器

点击 https://cdn.mivm.cn/www.mivm.cn/archives/windows-aria2/EasyWebSvr.zip 下载 Web 服务器,下载后解压。

> 安装/配置 Aria2 及 Web 管理面板

1.点击 https://cdn.mivm.cn/www.mivm.cn/archives/windows-aria2/aria2_conf.zip 下载 Aria2 配置文件,解压至 Aria2 目录里。

![Aria2 配置文件](https://cdn.mivm.cn/www.mivm.cn/archives/windows-aria2/02.jpg)

> 废话

> 默认已经配置好了,如果需要更改配置,用记事本等文本编辑器,打开 aria2.conf ,里面有对应的注释,这里说几个比较重要的参数。

```
dir=Download
文件保存目录 ,默认下载到 Aria2 目录的 Download 文件夹。

disk-cache=32M
硬盘缓存,默认 32M,作用是将数据缓存到内存中。

file-allocation=none
文件预分配方式,配置文件有速度比较,具体看你的硬盘,机械硬盘用默认的none不进行预分配就好,固态硬盘可以选择falloc。

enable-rpc=true
是否启用 RPC,RPC 是远程调用接口,开启:true,关闭:false。

#rpc-secret=mivm.cn
RPC 授权令牌,如果启用授权令牌,远程管理会要求输入令牌,去掉 # 即可启用,默认授权令牌:mivm.cn.
```

剩下的参数配置文件有注释,更多参数可以查阅官方文档(英文)。

修改完记得重启 Aria2

2.点击 https://cdn.mivm.cn/www.mivm.cn/archives/windows-aria2/aria2_bat.zip 下载Aria2 控制脚本,解压至 Aria2 目录里。

```
Start.bat
带命令行窗口输出启动 Aria2

Start.vbs
不带命令行窗口启动 Aria2

Stop.bat
停止 Aria2

Status.bat
查看 Aria2 进程状态

Restart.bat
重启 Aria2

Boot.bat
开启或关闭 Aria2 开机启动
```

运行 Start.vbs 或 Start.bat 启动 Aria2

首次运行会出现防火墙警告,允许即可。

3.打开 Web 服务器目录,打开 EasyWebSvr.exe → 点击底部的锤子图标 → [设置] → [主目录] 设置为 Web 管理面板目录 → [确定] → 点击底部的锤子图标 → [启动服务器]

如果不想搭建Web服务器的话,直接访问 http://yaaw.ghostry.cn/ 或 http://webui-aria2.ghostry.cn/ 也可以 。

如果出现404等无法访问的情况,请检查[主目录]是否正确或重启 Web 服务器。

[开机自动运行] 需要使用管理员权限运行

关闭服务器:右键托盘图标选择[关闭服务器]

配置完服务器第二次运行不会显示界面,后台运行。

![Web 服务器](https://cdn.mivm.cn/www.mivm.cn/archives/windows-aria2/03.jpg)

4.Web服务器开启后,浏览器访问 http://localhost/ 就可以访问 Web 管理面板。

![Aria2 Web 管理面板](https://cdn.mivm.cn/www.mivm.cn/archives/windows-aria2/04.jpg)

设置中文方法:点击 [AriaNg Settings] → [Language] 选择 [简体中文]

5. [Aria2 状态] 如果显示 已连接,恭喜你,Aria2 搭建成功,如果显示 未连接,请检查 Aria2 是否正常开启,或者重启 Aria2。

> Aria2 使用方法

Web 管理面板点击 [新建],可以添加 HTTP、FTP、BT 任务等,同时添加多个任务每行一个 URL,添加镜像 URL 用空格分割,点击文件夹图标可以打开种子文件等。

如果你使用的是 Chrome 浏览器,可以使用 https://chrome.google.com/webstore/detail/llhdoolhgigbnppanegcohafahjgbpek 这个扩展程序来让 Aria2 下载文件,其他浏览器请自行查找扩展。

markdown 懒加载

md
## 背景
做中后台的前端开发已经有一段时间了,在过往的业务开发中很少会关注到页面性能,因为随着硬件的发展当下电脑及浏览器已经足以支撑起一个较为复杂的前台页面,在中后台的业务中,我们更多的会关注组件的复用以及页面的快速实现。

可是,最近我遇到了一个场景使我不得不开始关注页面性能,具体来说是这样的,我们做数据报表型页面的时候,往往一个数据可视化模块就对应一个后端数据源,也相当于要对接一个接口,这样一来,如果一个较为复杂的页面,我们可能需要对接很多数据源,这样就带来一个问题,一旦我们访问页面,会在加载完页面后一下子发送大量的异步请求,导致页面会卡顿一会,当然这样的体验不是很好。

面对上面的问题,我们首先想到的解决方案就是推动后端同学进行接口合并,尽量减少异步请求的数量,来缓解前端的卡顿问题,这是一个很好的思路,但是新的问题接踵而至,如果把所有的后端数据库查询操作都放到一异步接口中势必会导致整个接口的RT变长,很有可能会有超时风险,而且前端页面在等待后端接口返回过程中,也会出现较长的loading状态,另外,对于实时数据的实时报表,我们需要每个一段时间来刷新数据,而且页面中不同部分的刷新率也不太一样,这就要求我们在页面首次请求完合并接口以后,然后再单个调用接口进行实时刷新,这无形中增加了前后端的复杂度。

最后我们想到了懒加载,这是在消费者端经常会考虑的页面性能方案,但是在商家端同样适用,我们可以在页面加载完成以后只拉取首屏的数据展示给用户,然后当页面滚动时再加载下面的内容,同时我们还可以做到当模块进入视区时才进行模块的实时刷新,当模块离开视区了则停止实时刷新,进一步降低前后端性能耗损。

## 技术调研
既然确立了方向,我们就要开始调研方案的可实施性,在网上搜了一轮,发现关于前端懒加载的文章特别多,大致思路基本一致,主要就是监听模块是否在视窗范围内可见,如果判断模块出现在视窗中了就开始执行加载或者是渲染。关于如何判断模块是否在视窗中可见,业界主要有两种方案:一种是通过监听页面的scroll及resize事件,然后根据模块和视窗的相对位置来确定模块是否在视窗内,另一种则是通过IntersectionObserver API来进行判断,接下来我会大致介绍一下以上两种方案。

### [IntersectionObserver](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API)
按照通常的顺序,我们应该是先介绍传统的方式,再引入新的API,但是我迫不及待的想先介绍新的API,因为新的API一定会成为我们的第一优先方案,当新的API无法满足时,我们才会考虑传统的方案。

IntersectionObserver是Chrome提出的一种快速确定指定元素是否在祖先元素或者是整个视窗可见的方案,并在chrome51版本中实现,现在互联网上有大量对[IntersectionObserver’s Coming into View](https://developers.google.com/web/updates/2016/04/intersectionobserver)的翻译,因此看到下面这个demo效果,大家一定不会陌生。

![](http://www.ruanyifeng.com/blogimg/asset/2016/bg2016110201.gif)

IntersectionObserver的使用非常简单,主要包含以下几个API, 大家可以详细阅读[官方文档](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver),这里只做简单介绍:

#### 实例化监听器

我们可以通过下面的方式实例化一个监听器:
```js
var observer = new IntersectionObserver(callback[, options]);
```
其中callback是一个回调函数,当指定节点的可视比例达到一个我们提前设置的阈值(后面会讲到阈值的设置)时,会触发回调,callback的参数数主要包含两个:
* entries - 一个[IntersectionObserverEntry](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry)列表,可以查看命中的目标节点的各种属性,例如位置,可视比例等等;
* observer - 是当前使用的IntersectionObserver实例;

options则是监听器的一些预设属性,主要包含:

* root - 需要进行监听的目标节点的祖先节点,通过设置这个属性来指定监听特定的范围,默认情况下监听整个视窗;
* rootMargin - 类似css中margin配置,用于扩大或者缩小上面设置的监听范围的区域,默认值为 '0px 0px 0px 0px';
* threshold - 可以是一个0-1之间的数字,也可以是一个数组,用于指定当指定元素的可视比例达到多少是触发callback回调,如果是0则是当指定节点一出现在root中就触发,如果是1则是当指定节点全部进入root时触发,我们也可以设置为[0, 0.5, 1]来制定多种触发时机;

#### 监听指定节点
完成监听器的实例化以后,我们就要开始监听目标节点了,这个API更加简单,我们只用调用下面的接口就完成了:
```js
IntersectionObserver.observe(targetElement);
```

#### 停止监听指定节点
当我们不需要再监听目标节点时,我们可以通过下面的API来取消对目标节点的监听:
```js
IntersectionObserver.unobserve(target);
```

#### 停止整个监听器
当我们不需要再进行监听或者整个页面销毁时,我们可以通过下面的API来停止整个监听器:
```js
IntersectionObserver.disconnect();
```

#### 查看所有监听节点
除了上面的API,IntersectionObserver还提供另外一个API来查看上一次监听结束后可视区域发生改变的目标节点列表,这个函数和构造函数的callback功能一致,主要的区别则是这个API是用户主动调用,callback为被动执行:
```js
intersectionObserverEntries = IntersectionObserver.takeRecords();
```
PS: 如果我们使用callback来监听目标元素可视范围改变则不需要用到这个API,当调用这个接口会导致callback函数不被执行。

是不是so easy?有了这个API妈妈再也不用担心我们的懒加载方案了,到目前为止,这似乎是一个非常完美的解决方案,但是我们高兴太早了,虽然这个API已经发布2年多了,但是浏览器的兼容性真的是不够好,这也导致如果单纯的使用这个API,我们的页面将面临很大的可访问性问题。

![](https://img.alicdn.com/tfs/TB1Ss_6lXYqK1RjSZLeXXbXppXa-1265-499.png)
### 传统方案
尽管监听页面的scroll及resize来判断目标节点是否在视窗可见的方案会对页面性能有一定影响,但是不得否则,这种传统的做法的确拥有良好的兼容性,因此我们不得不把这个方案也考虑进来,github上有不少类似的实现,大多都是提供一个高阶组件或者一个容器组件来包裹我们真正需要渲染的组件,当组件在视窗范围外时,用占位符进行占位,当组件出现在视窗之后执行我们真正需要展示的组件渲染周期,[react-lazyload](https://github.com/jasonslyvia/react-lazyload)算是众多的实现中比较典型的一个,因此下面我会以react-lazyload作为例子简单介绍原理(高手请绕过直接阅读源码),整个懒加载分为下面四步: 

#### 事件监听
要实时判断目标节点的可视状态,我们需要先注册监听器,通常我们需要注册以下几个监听器:
* 页面scroll:首先我们需要监听整个页面的滚动,当页面滚动时触发我们的可视判断机制来判断目标节点是否可见;
* 页面resize:对于PC页面,页面的resize会改变可视区域的大小,因此我们需要监听resize来触发可视判断机制,不过在无线场景下,可以不用监听页面的resize,因此是否监听页面resize往往会作为组件的可选配置;
* 祖先scroll:对于一些特殊的情况,滚动条不一定会出现在页面级别,因此,我们还需要定位拥有滚动条的祖先节点,然后通过监听祖先节点的滚动来触发可视判断;

事件绑定就不多讲了,一般拥有前端开发经验的同学都是信手拈来,主要讲一下如何找到拥有滚动条的祖先节点:
```js
function scrollParent(node) {
  if (!(node instanceof HTMLElement)) {
    return document.documentElement;
  }

  const excludeStaticParent = node.style.position === 'absolute';
  const overflowRegex = /(scroll|auto)/;
  let parent = node;

  while (parent) {
    if (!parent.parentNode) {
      return node.ownerDocument || document.documentElement;
    }

    const style = window.getComputedStyle(parent);
    const position = style.position;
    const overflow = style.overflow;
    const overflowX = style['overflow-x'];
    const overflowY = style['overflow-y'];

    if (position === 'static' && excludeStaticParent) {
      parent = parent.parentNode;
      continue;
    }

    if (overflowRegex.test(overflow) && overflowRegex.test(overflowX) && overflowRegex.test(overflowY)) {
      return parent;
    }

    parent = parent.parentNode;
  }

  return node.ownerDocument || node.documentElement || document.documentElement;
};
```
通过上面的代码我们可以看到,作者主要用到了css中[overflow](http://www.w3school.com.cn/cssref/pr_pos_overflow.asp)的特征来寻找拥有滚动条的祖先表单。

另外我们知道上面的事件监听会导致回调函数的频繁调用,如果每次回调我们都需要重新计算目标节点的可视状态将会给浏览器带来非常大的计算压力,因此作者用到了throttle和debounce来优化性能了,有兴趣的同学可以阅读[理解 Debouncing 与 Throttling 的区别](https://www.jianshu.com/p/e91775195608)或是阅读loadash的源码来深入了解这两个方案,这里就不详细介绍了。

最后,对于大多数场景,一旦目标节点出现到视窗范围内触发了组件的渲染就不在需要在监听目标节点的可视状态,因此作者增加了一个once属性,当组件完成渲染以后就不再继续监听事件判断组件是否可见,这样一定程度上也优化了页面性能。
#### 目标节点可视判断
事件绑定完成了以后,我们要做的重要一点,就是判断目标节点是否在可视范围之内,getBoundingClientRect来判断组件和视窗的相对位置则是最常见的方式:
```js
function checkNormalVisible(component) {
  const node = ReactDom.findDOMNode(component);
  // If this element is hidden by css rules somehow, it's definitely invisible
  if (!(node.offsetWidth || node.offsetHeight || node.getClientRects().length)) return false;
  let top;
  let elementHeight;
  try {
    ({ top, height: elementHeight } = node.getBoundingClientRect());
  } catch (e) {
    ({ top, height: elementHeight } = defaultBoundingClientRect);
  }
  const windowInnerHeight = window.innerHeight || document.documentElement.clientHeight;
  const offsets = Array.isArray(component.props.offset) ?
                component.props.offset :
                [component.props.offset, component.props.offset]; // Be compatible with previous API

  return (top - offsets[0] <= windowInnerHeight) &&
         (top + elementHeight + offsets[1] >= 0);
};
```

另外,我们刚刚也说到,有可能目标节点所处的滚动区域并不是整个页面的滚动区域,而是某一个祖先节点的滚动范围,因此我们需要先找到目标节点拥有滚动条的祖先节点,如果找到的祖先节点就是当前document,我们可以直接使用上面的方法,如果不是,我们需要先计算祖先节点与视窗的交叉范围,如果没有交叉我们就认为目标节点不存在,如果有,我们就需要通过上面的方式来判断目标节点是否在交叉范围而不是整个视窗范围可见。
#### 占位&渲染
这部分就相对简单了,对于一个组件,我们往往会在最开始有一个占位符来占位,当组件出现在视窗范围内了之后,我们才开始组件内部的子组件渲染周期,这个占位符可以是组件外部传入,也可以是默认值:
```js
render() {
    return this.visible ?
           this.props.children :
             this.props.placeholder ?
                this.props.placeholder :
                <div style={{ height: this.props.height }} className="lazyload-placeholder" />;
}
```

#### 销毁监听
最后,我们需要考虑监听事件的销毁,前面说到,如果懒加载组件配置了once属性,则会在组件第一次进入可视区域之后就会销毁事件监听函数,但是如果没有设置once,一定记得要在组件卸载时完成事件销毁监听事件噢!


到这里整个react-lazyload的方案就介绍完了,这个方案实现中规中矩,但是也会存在一些问题:
* 尽管做了优化,但是频繁的事件回调依然是我们无法回避的问题;
* 通过上面的代码我们可以看到,在判断目标节点是否可见时,只判断了目标节点的上边界是否在可视区域,这种实现在支持横划或者页面从下往上滑动的过程中会存在一定的问题;
* 作者只考虑了组件渲染的场景,但是忽略了另外一个场景,就是在我们上面说到的实时报表的场景中,我们只是希望在组件只有在可视范围内的时候才执行实时刷新;

## 方案结合
上面两个方案各有优劣,是否有可能将两种方案结合?我相信之前肯定有人干过这件事情,只是我没有发现而已,因此我决定结合以上两种方案自己实现一个的懒加载方案。大致思路非常简单,我们将所有懒加载的逻辑抽离到单独的工具文件,然后根据当前环境是否支持IntersectionObserver来判断使用IntersectionObserver还是传统的事件监听方式,并对外提供一致的API,整个方案的使用包含以下几步:
### 构造
```js
const lazy = new Lazy({
  node: this.node,
  options: {
    once,
    lazyType,
    lazyDuration,
    scroll,
    resize,
    offset
  }
});
```
通过上面的代码可以看出在构造函数中指定了目标节点以及一些配置选项,其中配置选项基本和react-lazyload一致,这里就不详细介绍。

### 监听
我们提供监听机制来监听构造函数中设置的节点的可视状态, 用户可以使用visibleChange事件来完成监听任务,并当目标节点的可视状态发生改变是进行相应的操作:
```js
lazy.on('visibleChange', visible => {
  ...
});
```

### 初始化
当完成事件绑定以后,我们提供一个init函数来启动整个懒加载机制,init函数要做的就是确定使用哪种监听方案,并完成相应的事件监听:
```js
lazy.init();
```

### 销毁
最后当我们不在需要懒加载的时候可以调用destroy销毁整个懒加载实例:
```js
lazy.destroy();
```

接下来我们要做的就是运用上面提供的工具组件来提供react组件或者提供高阶组件实现,具体方式非常简单,这个地方就不再啰嗦,有兴趣的同学可以查看[源代码](http://gitlab.alibaba-inc.com/B3/b3-lazy),整个方案就给用户提供了三种使用方式:
* 容器组件 - 用户可以将需要展示的组件放入容器组件已实现懒加载功能;
* 高阶组件(装饰器) - 用户可以将需要展示的组件使用高阶组件包裹已实现懒加载功能;
* 工具库 - 用户也可以直接使用上面提到的工具文件来进行一些懒加载处理,例如我们之前提到的当模块出现在视窗范围内时才进行实时数据更新;

## 总结
方案虽然简单,但是重要的是过程,从现有的方案中吸取经验发现问题并有所沉淀才是我书写本文的主要目的,最终的实现肯定有不完善的地方,也希望大家能够用包容的心态看待,并能够多多交流。

markdown NuiTrack SDK实例JSON规范

NuiTrack SDK实例JSON规范

json.md
NuiTrack SDK Instances JSON Specification
=========================================

Pattern
-------
1. Not Found Human
   ```json
   {
       "Timestamp": "0000000000000000",
       "Instances":
       [
       ]
   }
   ```
   
2. Found Human, But Not Detect Face (e.g. Found 2 Humans)
   ```json
   {
       "Timestamp": "0000000000000000",
       "Instances":
       [
           {
               "id": "1",
               "class": "human"
           },
           {
               "id": "2",
               "class": "human"
           }
       ]
   }
   ```
   
3. Detect Face (e.g. Found 2 Humans, But Detect Face Only 1 Human)
   ```json
   {
       "Timestamp": "0000000000000000",
       "Instances":
       [
           {
               "id": "1",
               "class": "human",
               "face":
               {
                   "rectangle":
                   {
                      "left": "0.0000",
                      "top": "0.0000",
                      "width": "0.0000",
                      "height": "0.0000"
                    },
                    "landmark":
                    [
                        {
                            "x": "0.0000",
                            "y": "0.0000"
                        },
                        {
                            "x": "0.0000",
                            "y": "0.0000"
                        },
                        {
                            "x": "0.0000",
                            "y": "0.0000"
                        },
                        {
                            "x": "0.0000",
                            "y": "0.0000"
                        },
                        {
                            "x": "0.0000",
                            "y": "0.0000"
                        },
                        {
                            "x": "0.0000",
                            "y": "0.0000"
                        },
                        {
                            "x": "0.0000",
                            "y": "0.0000"
                        },
                        {
                            "x": "0.0000",
                            "y": "0.0000"
                        },
                        {
                            "x": "0.0000",
                            "y": "0.0000"
                        },
                        {
                            "x": "0.0000",
                            "y": "0.0000"
                        },
                        {
                            "x": "0.0000",
                            "y": "0.0000"
                        },
                        {
                            "x": "0.0000",
                            "y": "0.0000"
                        },
                        {
                            "x": "0.0000",
                            "y": "0.0000"
                        },
                        {
                            "x": "0.0000",
                            "y": "0.0000"
                        },
                        {
                            "x": "0.0000",
                            "y": "0.0000"
                        },
                        {
                            "x": "0.0000",
                            "y": "0.0000"
                        },
                        {
                            "x": "0.0000",
                            "y": "0.0000"
                        },
                        {
                            "x": "0.0000",
                            "y": "0.0000"
                        },
                        {
                            "x": "0.0000",
                            "y": "0.0000"
                        },
                        {
                            "x": "0.0000",
                            "y": "0.0000"
                        },
                        {
                            "x": "0.0000",
                            "y": "0.0000"
                        },
                        {
                            "x": "0.0000",
                            "y": "0.0000"
                        },
                        {
                            "x": "0.0000",
                            "y": "0.0000"
                        },
                        {
                            "x": "0.0000",
                            "y": "0.0000"
                        },
                        {
                            "x": "0.0000",
                            "y": "0.0000"
                        },
                        {
                            "x": "0.0000",
                            "y": "0.0000"
                        },
                        {
                            "x": "0.0000",
                            "y": "0.0000"
                        },
                        {
                            "x": "0.0000",
                            "y": "0.0000"
                        },
                        {
                            "x": "0.0000",
                            "y": "0.0000"
                        },
                        {
                            "x": "0.0000",
                            "y": "0.0000"
                        },
                        {
                            "x": "0.0000",
                            "y": "0.0000"
                        }
                    ],
                    "left_eye":
                    {
                        "x": "0.0000",
                        "y": "0.0000"
                    },
                    "right_eye":
                    {
                        "x": "0.0000",
                        "y": "0.0000"
                    },
                    "angles":
                    {
                        "yaw": "0.0000",
                        "pitch": "0.0000",
                        "roll": "0.0000"
                    },
                    "emotions":
                    {
                        "happy": "0.0000",
                        "neutral": "0.0000",
                        "angry": "0.0000",
                        "surprise": "0.0000"
                    },
                    "age":
                    {
                        "type": "kid",
                        "years": "0.0000"
                    },
                    "gender": "male" 
               }
           },
           {
               "id": "2",
               "class": "human"
           }
       ]
   }
   ```

markdown Python unittest`setUp`继承

Python unittest`setUp`继承

README.md
In some cases for Python unit tests, we want to automatically perform `setUp` methods in as declared in a base class. However, we still want `setUp` to work as per normal in the subclass. The following code will proxy the new `setUp` function to run it's base class' and the new one.

```python
# Define a common test base for starting servers
class MyBaseTestCase(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        """On inherited classes, run our `setUp` method"""
        # Inspired via http://stackoverflow.com/questions/1323455/python-unit-test-with-base-and-sub-class/17696807#17696807
        if cls is not MyBaseTestCase and cls.setUp is not MyBaseTestCase.setUp:
            orig_setUp = cls.setUp
            def setUpOverride(self, *args, **kwargs):
                MyBaseTestCase.setUp(self)
                return orig_setUp(self, *args, **kwargs)
            cls.setUp = setUpOverride

    def setUp(self):
        """Do some custom setup"""
        self.abc = True


class ItemCreateTest(MyBaseTestCase):
    def setUp(self):
        """Do more custom setup"""
        self.def = True

    def test_verify_both_setups_run(self):
        """Test for our current usage"""
        self.assertEqual(self.abc, True)
        self.assertEqual(self.def, True)
    
```