怎样为你的 Commit 加上 GPG 的签名

源码

开源赋予了每个人贡献代码的权利,要提高开源软件整体的安全性需要从一个个提交开始。
为 Commit 带上 GPG 的签名就是一个很好的开始,它能够在一定程度上保证安全性。
GPG 是一个非常出色的加密软件。当年斯诺登就是用 GPG 把大量的绝密文件发送给了记者。
本文以 Ubuntu 为例,介绍怎样为你的 Commit 加上 GPG 的签名。

环境配置

注意只有 git 2.0 及以后的版本才支持 GPG 签名。

查看 git 版本:

1
2
~$ git --version
git version 2.8.2

确保 git2.x 版本 如果需要可以执行下面的命令升级:

1
2
3
sudo add-apt-repository ppa:git-core/ppa
sudo apt-get update
sudo apt-get install git

查看 GPG 是否安装

1
2
3
4
5
6
~$ gpg --version
gpg (GnuPG) 1.4.16
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

系统里一般都已经装上了 GPG,如果没有可以 手动安装 GnuPG

1
sudo apt-get install gnupg

确保 gitGNU 都安装成功就可以开始下一步了。

生成 GPG Key

命令行输入:

1
gpg --gen-key

生成过程中需要输入一些信息:

  • 加密算法 推荐选择 RSA
  • 密匙长度 推荐选择 4096
  • 过期时间 推荐选择 1y 也就是一年
  • 名字 可以随便填
  • 邮箱 需要是 Github 认证过的邮箱
  • 注释 可以随便填

最后需要输入一个密码,类似于保护 SSH 证书的密码,可以留空,不过最好是填上。

接下来是漫长的生成过程,在等待的过程里可以多移动鼠标,多打开几个网页,以此来增加机器的随机性。

查看 GPG Key ID

GPG Key ID 是一个8位的字符串,对应着一个 GPG Key

在命令行里查看 GPG Key 列表:

1
2
3
4
5
6
~$ gpg --list-keys
/home/***/.gnupg/pubring.gpg
----------------------------
pub 4096R/6******7 2016-05-11 [expires: 2017-05-11]
uid Wang Yan <wyvernnot@gmail.com>
sub 4096R/B******E 2016-05-11 [expires: 2017-05-11]

输出结果里的 6******7 就是这个 GPG Key ID

导出 GPG 公匙

执行命令:

1
gpg --armor --export 6******7

会在命令行看到公匙的文本:

1
2
3
4
5
6
7
8
9
10
11
12
13
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v1

mQINBFczB1kBEAC3afc50p08I7knynnm4ea/8VZy42zGH/qrIWoInqT8HbgwLb9s
TrulTgLMbMTTOhl4zqFtTR52ZTwfLOtnV8tCM62ZNqO6z0nd0bFPA7HVp5t9z4a5
qBF3l3AZO5TFjn8gTsxatpVf5Ug9gmkEOCAejwSPFKNvfAun+WSLZ19LaFcorrj1
...
cOyj7VNdmx1//BMp/5A6SEqJFkkLxebaJpcrv1dm1HvKGU8vty3JAdWgOjY5VVmn
nUaAr8D0uNquASkap4dki/zscK+tEJiH6HLnQGGtIf+fR2zoHAUfBvtTuUhq8w/Q
F0R/UVgc4Sg+Vz4gyYNJKO8BdTTTfvOZMDNFw4x70vXfhCZDm4QxgfUwRs//kkIh
Z3QylTf5xSk4
=kuFS
-----END PGP PUBLIC KEY BLOCK-----

拷贝这一大段到剪贴板,然后登录 Github 添加这个公匙,添加的入口在 Settings 下的 SSH and GPG Keys 里面。

配置 Git 启用 GPG 签名

首先设置 Git 默认使用的 GPG Key ID,就是上面那个 8 位的字符串。

1
git config --global user.signingkey 6******7

提交的时候加上一个 -s 参数就可以为提交签名:

1
git commit -S -m your commit message

要想以后如果每次提交都默认都开启 GPG 签名,尤其是使用 IDE 的情况下,需要修改 git 的一个全局设置项:

1
git config --global commit.gpgsign true

验证

执行 git push 后登录到 Github, 看看提交历史的右边是不是多了一个 Verified 图标。

如果看到下面的结果,那么恭喜你已经为整个开源世界的安全迈出了一小步!

提交历史

后话

如果在 Github 上把 GPG Public Key 移除, 那么之前的 Verified 提交会变成 Unverified

参考文章

StatsD 简介

源码

介绍 StatsD 的发展,协议,最后附带有一个假想的例子。

自主开发一套监控系统可以从这三个部分分别着手:数据采集,数据存储和数据展示。

对于有代码经验的开发和运维人员,数据采集是很容易解决的问题,例如要统计一个方法的执行时间,只需要记录方法进入和退出的时间差即可。
而对于后台架构中常见的各种组件如 Redis 和 MySQL 等,它们往往各自提供了数量可观的指标接口,使得运维人员可以快速的获取组件的运行状况。

数据存储曾经是比价复杂的一块,首先就是数据结构的设计,此外还要考虑快速的检索,统计操作等。
好在最近有很多兴起的时序数据库,如 InfluxDB 和 OpenTSDB,它们都对存储时间序列型的数据有很强的适用性。
数据展示可以采用开源的方案例如 Grafana 。

StatsD 的使用场景

把数据推送到存储模块的过程中,有的时候没有必要把所有采集到的数据都存起来。
典型的场景是 Web 服务器,当请求达到每分钟上万条时,把每条请求的响应时间都存入数据库是不现实也没有必要。

这个时候可以考虑设计一个缓冲模块,来实现数据的累加,平均值,中间值计算等。
StatsD 正是这样一个实现数据中转的工具,它能够完成简单但是非常实用的统计步骤。
另一方面,由于往 StatsD 写入数据的时候使用的是 UDP 协议,所以对被检测的系统的影响可以控制的很小。

安装 StatsD 服务

StatsD 安装指南

https://github.com/etsy/statsd#installation-and-configuration

装好 StatsD 以后,还需安装 InfluxDB 或者 OpenTSDB,由于篇幅的关系不再展开。

本文推荐使用 CloudInsight 监控系统,如果已经有 OneAPM 的账号,安装只需要 1 分钟左右。

http://docs-ci.oneapm.com/quick-start/

StatsD 协议

StatsD 的协议其实非常简单,每一行就是一条数据。

1
<metric_name>:<metric_value>|<metric_type>

以监控系统负载为例,假如某一时刻系统 1分钟的负载 是 0.5,通过以下命令就可以写入 StatsD。

1
echo "system.load.1:0.5|g" | nc 127.0.0.1 8251

结合其中的 system.load.1:0.5|g,各个部分分别为:

  • 指标名称 metric_name = system.load.1
  • 指标的值 metric_value = 0.5
  • 指标类型 metric_type = g(gauge)

指标名称

指标命名没有特别的限制,但是一般的约定是使用点号分割的命名空间。

指标的值

指标的值是大于等于 0 的浮点数。

指标类型

StatsD 封装了一些最常用的操作,例如周期内指标值的累加、统计执行时间等,因此对指标的值分成以下几种:

gauge 标量

标量是任何可以测量的一维变量。例如人的身高,体重、某一时刻北京市 PM2.5 的值等。

counter 计数器

如果某个变量没有办法一下子测量出来,这时就可以使用计数器,分步累加。

例如要统计某个接口的流量

1
ent0.traffic:100|c

在每个周期里 StatsD 会自动累加流量的值,并把平均值传给后端。

ms 时间

记录执行时间。请求响应时间是 12 ms,写入到 StatsD 服务器的方法是:

1
echo "request.time:12|ms" | nc 127.0.0.1 8251

StatsD 会自动计算以下指标:

1
2
3
4
5
request.time.avg
request.time.count
request.time.max
request.time.mean
...

set 集合

统计变量不重复的个数。例如:当前在线的用户

‘echo “online.user:9587|s” | nc 127.0.0.1 8251’

‘echo “online.user:9588|s” | nc 127.0.0.1 8251’

StatsD 实例

以做一个完美的煎饼果子的过程为例,参数和类型的对应关系如下:

计数器

  • 鸡蛋
  • 薄脆
  • 面粉

标量

  • 油温
  • 辣度

集合

  • 口味

计时器

  • 做每一个煎饼果子的时间

运行代码

1
2
3
4
git clone git@github.com:wyvernnot/introduction-to-statsd.git
cd introduction-to-statsd
npm install
npm run-script pipe
1
http://localhost:8080

参考

Redux 如何单元测试

源码

redux 里有一些好玩的东西,如果与 React 一起学习各种环境搭起来速度比较慢,也比较麻烦。如果使用单元测试来学习,
并且阅读源代码,可以更快地搞懂。

TOP Level

Redux 顶层的 API 允许你创建 Store, 合并 Reducer,并通过 storeEnhancer 来引入中间件进行扩展。

  • createStore(reducer, [initialState], [enhancer])
  • combineReducers(reducers)
  • applyMiddleware(…middlewares)
  • bindActionCreators(actionCreators, dispatch)
  • compose(…functions)

Store API

Store 创建后有这些方法可供调用

  • dispatch
  • subscribe
  • getState

Redux 常用插件

redux-actions

简化 Action 的创建,使其符合 FSA 标准

  • createAction(type, payloadCreator, metaCreator)
  • handleAction(type, reducer | reducerMap)
  • handleActions(reducerMap, defaultState)

redux-promise

使用 Promise 作为 Action 的 payload,以此减少不必要的代码

redux-thunk

使用 Funtion 作为 Action, 从而延迟执行

被遗忘的 Loader

源码

专门介绍 Webpack 那些冷门的 Loader

Webpack 的官方文档上有一个 list-of-loaders
之是其中的大部分被使用到的机会非常少, 好心疼这些缺 :heart: 的模块, 所以有这个 repo 专门介绍这些冷门 Loader .

demo

raw-loader

作用:以 utf-8 编码加载文件的内容

json-loader

作用:加载一个JSON文件的内容并返回对象.

在 Node.JS 环境下可以 require 一个 .json 格式的文件:

1
require('./config.json')

但是在 wepack 打包的时候不可以直接 require 一个 .json 的文件. 这是因为 webpack 会把 .json 文件当成是 .js 文件来解析导致报错.
因此正确的做法是使用 json-loader

1
require('json!./config.json')

hson-loader

作用:加载增强版的JSON (HSON) 并返回对象.

HSON 对 JSON 的增强体现在:

  • 所有的 JSON 字符串都是有效的 HSON 字符串
  • HSON 里可以写注释
  • HSON 里属性名不一定要有引号

script-loader

有的时候不希望 webpack 在打包某些文件的时候进行解析, 而是希望在浏览器中直接运行.
一个不常见的例子是某段代码重新定义了 require , 如下:

browser_require.js

1
2
3
4
window.require=function(){
console.log(arguments);
}
require(2);

引入该文件的正确方法是:

1
require('script!./browser_require.js')

这样该文件内容里的 require 就不会被 webpack 解析了, 使用 script-loader 整个文件会被转成字符串打到包里,最终在浏览器里通过 eval 来执行.

使用 Fetch API 实现前后端共用同一个 SDK

源码

架构设计实践: 使用 Fetch API 实现前后端共用同一个 SDK.

做过一段时间的 Web 开发以后,就会有这样的感觉,前后端分离几乎和吃饭睡觉一样平常. 前端专注浏览器的一切,
通过 API 和后端通信,而后端工程师专注于系统的执行效率,可扩展性等问题.

很多优秀的网站特别是有一部分面向开发者的网站都会提供 API 的 SDK.

  • 封装底层发起请求的方法
  • 异步请求成功和出错的处理
  1. 封装

浏览器端发送 AJAX 请求底层一般采用 XMLHttpRequest 来实现, 实际工作中常使用具体的封装良好的类库,
例如 jQuery, superagent 等. 而后端 Node.js 经常用的是基于 Stream 的解决方案, 由于 API 是很常见的
工作,所以已经封装到了 Node.JS 的核心模块 http 里, 此外比较常用的第三方的类库有 request.

如果前后端公用一个 SDK, 那么必然要在上层引入一定的设计模式,来封装底层具体实现:

1
2
3
4
5
if(isBrowser){
return facade(new XMLHttpRequest(...))
}else{
return facade(http.request(...))
}

在浏览器端发起 AJAX 请求现在有了一套新的API, 那就是 Fetch. 与基于 XMLHttpRequest 的方法相比,它有几个显著地优势.

  • 更安全

虽然 Fetch 目前只在 Chrome 等比较新的浏览器上可以使用. 不过别担心, 已经有好几个垫片可以解决兼容性的问题.

  1. 异步

不管是在客户端还是在服务器端,对请求的处理都是异步的,这是由 JavaScript 本身的特性决定的.在很长的一段时间里
回调函数一直都是处理异步问题首选的方案,但是在经过了这么多年的折磨之后,人们终于想到了一个非常优秀的替代方案 - Promise.

1
2
3
4
5
6
7
8
var api=require('my-awesome-api');
api.repos('github')
.then(res=>{
// 成功
})
.catch(err=>{
// 失败
})

如果在客户端和在服务器端能够采用一致的方案,这无疑会大大提高 JavaScript 代码的质量和开发效率.

公用 SDK 有很多好处:

  • 浏览器端专注于路由,页面的设计,把请求的封装处理交给 SDK
  • 后端的开发者可以使用 SDK

所以是时候尝试和采用 Fetch API 了.

关于 Fetch API 简单的介绍

http://github.github.io/fetch/

实现该模式的 SDK

https://github.com/ringcentral/ringcentral-js

关键模块

https://github.com/matthew-andrews/isomorphic-fetch

如何使用 ES6 编写一个 React 模块, 并且编译后发布到 NPM.

源码

目录

前言

前端开发果真是发展迅猛,刚享受到由模块化,组件化和单元测试带来的种种好处,又得迅速拥抱 Grunt, Gulp, Browserify, Webpack 这类自动化工具的变革。
除了工具和生态圈,JavaScript 本身也在飞速发展着。ES2015(ES6) ,ES2016(ES7) … 照这样的节奏,几乎是一年一个标准。标准多了,为解决兼容性的问题,
竟也派生出了 源代码编译 的概念。前端开发者通过语法糖、转化器、Polyfill 等,可以享受到标准乃至尚未定稿草案里的规范的便利,大幅提升开发效率。

如果你在使用 React, 那么肯定已经撸了好多自己的组件, 并尝试着共享出来。在 OneAPM 前端开发过程中, 我们也曾遇到了一些组件共享的问题:

例如:

  • 是通过 git 直接发布还是通过 NPM 发布 ?
  • 发布的是 ES5 的代码还是 ES6 的代码 ?
  • 如何解决 Babel5 和 Babel6 的冲突 ?

这篇文章会通过编写一个叫做 MyComponet 的组件来演示发布一个 React 组件需要注意的地方, 并不涉及单元测试和代码规范等。
至于这个模块本身,它的功能特别简单, 就是显示模块自身的的属性。

源代码

我们来编写 MyComponent 组件,放到项目的 src 目录下。

src/MyComponent.jsx

1
2
3
4
5
6
7
8
9
10
import React from 'react';

const MyComponent = props=> {
return <div>
props:
<pre>{JSON.stringify(props, null, 2)}</pre>
</div>
}

export default MyComponent;

关于各种文件放在哪里, 这里是我推荐的一些约定:

  • src 下用于存放源代码
  • lib 是编译后的代码,这个目录只读
  • 所有包含 ES6 语法的文件名统一后缀为 .es6
  • 所有包含 JSX 语法的文件后统一缀名为 .jsx

假设源代码里还有另外两个文件 foo.es6bar.js,简化起见都丢到 src 的根目录下。

编译

Babel

为了把 ES6 代码编译成 ES5,需要安装 Babel,这个工具可以说野心极大,一次编译可以让 JavaScript 运行在所有地方。(听起来是不是有点 Java 的作风)

目前最常用的是 Babel5 版本,但是 Babel6 版本的设计更为精巧,已经非常推荐更新。也正是由于 Babel 有两个版本,所以开发过程中很有可能遇到这样的情况,
模块 A 的开发依赖于 Babel5 版本,而模块 B 依赖于 Babel6 版本。

解决这个问题最好的做法就是把 A 和 B 拆开,独立开发和发布。并且在发布到 NPM 的时候发布是的编译后的,也就是 ES5 版本的代码。

所以如果你的机器上的 babel 是全局安装的,是时候卸载它了,因为它的版本不是 5 就是 6 ,会导致一些不可预见的问题。

npm uninstall babel-cli --global

正确的安装方式是把 babel-cli 作为 development 依赖

npm install babel-cli --save-dev

使用的时候并不是直接调用全局的 babel 而是调用依赖里的 babel 可执行文件

./node_modules/.bin/babel

如果按照前文的约定来组织代码,src 目录结构看起来是这样的

1
2
3
4
src
├── bar.js
├── foo.es6
└── MyComponent.jsx

模块所有的代码都在一个目录下,这样编译过程就简单多了,两条命令就可以完成

1
2
./node_modules/.bin/rimraf lib
./node_modules/.bin/babel src --copy-files --source-maps --extensions .es6,.es,.jsx --out-dir lib

输出目录的结构

1
2
3
4
5
6
lib
├── bar.js
├── foo.js
├── foo.js.map
├── MyComponent.js
└── MyComponent.js.map

命令详解

具体解释一下各个命令的作用:

第一条命令 ./node_modules/.bin/rimraf lib

作用 编译前清空之前的 lib 目录,这是一个好习惯,可以杜绝对 lib 下的文件的任何手动更改。

第二条命令

./node_modules/.bin/babel src --out-dir lib --source-maps --extensions .es6,.es,.jsx --copy-files

作用 遍历 src 目录下的文件,如果后缀名是 .es/.es6/.jsx 中的一种,就编译成 ES5,否则就直接拷贝到输出目录 lib 下

参数详解:

--out-dir lib 指定输出目录为 lib

--extensions .es6,.es,.jsx 指定需要编译的文件类型

--copy-files 对于不需要编译的文件直接拷贝

--source-maps 生成 souce-map 文件

.babelrc 文件

编译过程中还隐含了一个步骤就是加载 .babelrc 文件里的配置,该文件内容如下

1
2
3
4
5
6
7
{
"presets": [
"es2015",
"stage-0",
"react"
]
}

这是因为 Babel6 采用了插件化的设计,做到了灵活配置:如果要转换 JSX 语法文件,就加上 React 的 preset,同时项目依赖里要添加
babel-preset-react

1
npm install babel-preset-react --save-dev

样例代码

开发和调试 React 模块目前最好用的打包工具还是 Webpack,在项目跟目录下,新建一个 example 目录:

example/index.html

1
2
3
4
5
6
7
8
9
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<title>Example</title>
</head>
<body></body>
<script src="bundle.js"></script>
</html>

example/src/index.jsx

1
2
3
4
5
6
7
import React from 'react';
import MyComponent,{foo,bar} from '../../';
import {render} from 'react-dom';

var element = document.createElement("div");
document.body.appendChild(element);
render(<MyComponent name="myComponent"/>, element);

webpack.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var path = require('path');

module.exports = {
entry: path.join(__dirname, 'example', 'src', 'index.jsx'),
output: {
filename: 'bundle.js'
},
module: {
loaders: [{
test: /\.jsx$/,
loader: 'babel',
include: [
path.join(__dirname, 'example')
]
}]
},
devServer: {
contentBase: path.join(__dirname, 'example')
}
}

运行样例代码

1
./node_modules/.bin/webpack-dev-server

发布

发布前,还有一件事就是为你的模块添加一个入口文件 index.js,全部指向编译后的代码

1
2
3
4
module.exports = require('./lib/MyComponent');
exports.default = require('./lib/MyComponent');
exports.bar = require('./lib/bar');
exports.foo = require('./lib/foo');

接下来就可以发布到 NPM 了。

1
npm publish

使用

别的开发者如果想使用你新发布的模块的时候可以直接通过 NPM 安装

1
npm install react-component-example --save-dev

然后在父级项目的代码里导入模块

1
import MyComponent,{foo,bat} from 'react-component-example'

此时导入的直接是 ES5 代码,跳过了组件的编译过程从而避免了出现组件 Babel 版本和父级项目 Babel 版本不一致的问题,并且速度更快,是不是很棒!

使用源码

假设你的模块包含很多组件,开发者可能只想用其中的一个或某几个,这时可以这样导入:

1
import MyComponent from 'react-component-example/src/MyComponent.jsx'

这种情况下,导入的是 ES6 代码,并且会被加入父级项目的编译过程。此外,父级项目在编译这个文件的时候会读取组件的 .babelrc 配置文件。

样式

推荐每个模块在发布的时候把样式文件 style.css 放在根目录下,如果使用的是预处理工具 LESS 或者 SASS, 同样把处理前后的样式文件一起发布。

关于

本文使用的 babel 版本

./node_modules/.bin/babel --version 6.4.5 (babel-core 6.4.5)

LICENSE

MIT