Becomin' Charles

算法 | LNMP | Flutter | Mac

Becomin' Charles

在移动支付领域,经常会听到这个术语,I 类户,II 类户,III 类户,这个称法,据说最早开始于 2015 年,中国人民银行的发文(银发[392]号文),内容是《关于改进个人银行账户服务加强账户管理的通知》。

里面规定了各类各级银行,要对客户账户施行实名制管理,以及账户分类管理。通知里,规范了三类账上的名称,功能和限制。

I 类户II 类户III 类户
主要功能全功能储蓄存款和投资理财
消费支付
绑定支付账户
消费支付
绑定支付账户
账户余额无限制无限制少于1000元
使用限额无限额储蓄存款和投资理财无限额
消费支付每日 < 10000 元
消费支付每日 < 5000 元
账户形式借记卡及储蓄存折电子账户电子账户
开户柜面开设
银行工作人员现场核验下,自助机具也可以开设
柜面开设
自助机具不需要现场核验
电子渠道(需要绑定 I 类户 作为身份核验)
柜面开设
自助机具不需要现场核验
电子渠道(需要绑定 I 类户作为身份核验)
转账限额无限制非绑定账户,日 存入/转入 资金累计限额 1 万,年累计限额 20 万;消费、缴费和向非绑定账户转出资金,日累计限额1万,年累计限额20 万非绑定账户,日 存入/转入 资金累计限额 5000 元,年累计限额 10 万;消费、缴费和向非绑定账户转出资金,日累计限额 5000 元,年累计限额 10 万

此外,关于三种账户的发文还有《关于加强支付结算管理防范电信网络新型违法犯罪有关事项的通知》(银发 2016 [261] 号文),以及 《关于落实个人银行账户分类管理制度的通知》(银发 2016 [302] 号文)。进一步规范了三类账户的使用限制和限额。

新增了诸如 I 类户一个客户在一家银行只能开设一个,II 类户年累计限额 20 万,III 类户年累计限额 10 万,日累计限额 5000 元,等规定。

三类账户的限制是从小到大,使用的便捷程度则是从困难到简单。基本可以认为是为移动支付时代定制的规则。

II 类户的一个例子

下面通过一个小例子说明一下 II 类户的现实运用情况。微众银行是中国第一批的互联网银行,是由腾讯牵头成立的,于 2014 年 12 月 16 日成立。

在微众银行的手机 App 中,每个人可以申请一张虚拟银行卡。其实这张卡,就是上文提到的 II 类户,这个账户的开户过程,不需要用户亲自到银行柜面核验,但是开设过程中,需要绑定一个 I 类户才能进行身份验证。

微众银行的储蓄卡,上面有II类户标记

根据上面 II 类户的说明,这个账户可以用于理财投资,所以,微众银行 App 里面是可以使用微众银行卡,购买理财产品的。不过将资金转入到微众银行,是比较麻烦的,如果你尝试往里面转账,你会注意到,有两个不同的转账功能。一个是从绑定卡直接扣转,这个显然要符合 II 类户的转账限额,也就是 1 万元一次的限制。这边可能会有另一个限制,就是代扣的单次限额限制。

另一个转账进入的途径是大额转账,是从你的其他银行的 I 类户的手机银行,直接给微众的卡号转账汇款。但是,你发现,如果没有事先将出账卡登记绑定到微众银行 App 的话,转账也会失败的。这也是来源于 II 类户的限制。如果未进行事先与 I 类户绑定的操作,也会受到单次 1 万,累计 20 万的限制。

如何查询自己的卡是否 II 类户?

  • 工行-中国工商银行app-我的账户-查询明细(1类卡没有提示,2类卡会标明是2类卡账户)
  • 中行-中国银行app-账户管理(1类卡标明1类账户,2类卡会标明是2类卡账户)
  • 建行-中国建设银行app-登陆后点击首页卡号下方,进去之后会有提示1类卡还是2类卡
  • 农行-中国农业银行app–我的账户-借记卡-人名币可用余额-进去后卡号前方有提示1类卡还是2类卡。
  • 交行-交通银行app-搜索“银行卡管理”,(1类卡标明1类账户,2类卡会标明是2类卡账户)
  • 招行-招商银行app-我的-银行卡(1类卡没有提示,2类卡会标明是2类卡账户)
  • 兴业-兴业银行app-我的-银行卡-选择银行卡后在账户类型可查看

在看一些印度支付相关的文档时候,发现各种英文缩写出现得自然而然,仿佛人尽皆知一般,可惜对我们来说,还是太过陌生,本文收集一批,便于查阅。

缩写全称备注
RBIReserve Bank of India印度储备银行(印度的央行),于 1935 年 4 月 1 日作为私人实体设立,于 1949 年国有化,使用货币政策来维持国家金融稳定,负责监管国家货币和信用系统。机构位于孟买。
IBAIndian Banks’ Association印度银行联合会,成立于 1946 年 9 月 26 日,位于孟买。目前有 237 家银行业机构成员。致力于发展、合作和强化印度的银行业,在新系统实现和标准采纳方面给予成员协助。
NPCINational Payments Corporation of India印度国家支付公司,零售支付和结算系统组成的伞形组织。由 RBI 和 IBA 在支付和结算系统法案(Payment and Settlement Systems Act, 2007)要求下设立,致力于建设一个健壮的支付和结算基础架构。
IMPSImmediate Payment Service即时支付服务,于 2010 年 11 月 22 日发布,在多种渠道上可供访问的,健壮的跨银行实时全天候资金传输服务。目前,该服务有 243 个成员,包括银行和 PPIs。由 NPCI 提供。致力于构建基于移动设备的银行业务基础。
UPIUnified Payments Interface在一个移动 App 中使用多家银行账户的统一支付接口。首次发布于 2016 年 4 月 11 日,有 21 家成员银行,由 NPCI 开发。使用支持 UPI 的 App,可以绑定多家银行的卡片。UPI 支持双因素(2FA)和一次性密码(OTP)。是 P2P 的生命线。支持代付和代扣。IMPS 之上的接口封装
PPIsPrepaid Payment Instruments
PSPPayment Service Provider支付服务提供商
VPAVirtual Payment Address虚拟支付地址

本文内容是研究印度网络支付行业的一些笔记,主要是便于自己记忆和理解,内容不一定准确,请自行甄别。

什么是数字钱包?

数字钱包(Digital Wallet)也被称为是电子钱包(e-Wallet),从概念上来说,就是一个字面意义的钱包,只不过是电子版的。不像普通钱包,存储的是“物理意义上的”的纸币和硬币,数字钱包使用数字表示货币。

对比中国的概念来看,更像是支付宝的余额,微信支付的钱包零钱。里面的数字就代表现金。

可以使用信用卡、贷记卡,网络银行,或者 UPI 往数字钱包里充钱,但是停留在数字钱包里的钱没有任何利息。这和国内的情况也是一样的,支付宝发明了余额宝,微信支付发明了零钱宝,来解决没有利息的问题。

电子钱包里面,内置了很多账单支付功能。对比支付宝,例如“生活缴费”功能,就是支付宝内置的账单支付功能。数字钱包里,存储的金额是有限的,在印度的限制是 20,000 卢比。

什么是普通银行?

这个概念其实很简单,放在这里主要是对比另外两个概念的。受到高度监管的金融机构,除了能储蓄以外,还可以给个人和企业提供信贷服务。对储蓄的现金支付固定或者浮动的利息。

银行可以发行 ATM卡、借记卡(Debit Card)、信用卡(Credit Card),支票(Cheque),可以开设网络银行(Internet Banking),移动银行(Mobile Banking),可以进行 UPI 交易。存钱最安全的地方,是一国经济的核心构成。

什么是支付银行?(Payments Bank)

是一种由 RBI(Reserve Bank of India)开发的一种新型银行模型。基本就是银行,但是不能进行信用和贷款服务,此外还有一些限制。依据 RBI 最新的条例,支付银行可以存储 Rs. 1 lakh 的现金(10万卢比)。可以依据银行的业绩表现来进行升级。

支付银行可以发行 ATM、借记卡,本票,但是不能发行信用卡。很快也会放开网络银行,移动银行和 UPI 交易业务。你可以往你的账户上放钱,也会收到利息。目前有八家机构可以开设支付银行(拥有牌照):

  • Paytm(马上)
  • Airtel M Commerce Services(已经开设)
  • Vodafone M-Pesa
  • Reliance Industries(把 JioMoney 钱包,转化成支付银行)
  • Aditya Birla Nuvo
  • National Securities Depository
  • Department of Posts
  • FINO PayTech

「注」以上内容大部分翻译自:

Digital Wallet vs Normal Bank vs Payments Bank – Clearing The Confusion

当年,刚开始玩网站的时候, SSL 证书还是很难获得的,只有少数几个机构支持颁发免费的证书,所以我们只能自己签名一个证书,来部署 HTTPS,但是,自签名的证书不会被浏览器承认,12306 刚上线的时候像个骗子网站,就是因为它用了自签名的证书。

后来,有了救世主,就是 Let’s Encrypt,面向全网颁发免费证书,无条件的。截止目前,全网已经有 1.9 亿网站部署了 Let’s Encrypt 的证书。我以为,我这辈子再也用不到自签名证书了。哪知道,还是让我碰到了,用自签名证书更方便的场景。

场景

这里也顺便说说我的场景好了。第一次遇到非要自签名场景是公司里部署企业内网安全的时候。咱们访问一个网站的时候,一般是服务提供商,需要部署证书,我们作为个人用户,需要通过公信力机构,认定我们在访问的服务,使用的是合法获得的证书,且我们的通信是加密的。在公司,内网网站,还需要对访问者进行验证,不是谁都能看公司内网的,必须是公司的员工才行,怎么确认这一点呢?一般我们使用密码,更进一步,只有登记在册的设备才能访问公司的内网网站,那么就需要用到客户端证书。大家,平日里用的客户端证书的场景很少,一般只有访问网银的时候,才用客户端证书,也就是 UKey,原理是一样的。

颁发客户端证书,目前,全网免费的服务非常少,既要免费,又要可以信任的,就更难找了。所以,我们当年采用这一方案的时候,不得不使用自签名证书。

第二次就是最近,我是一个个人开发者,想要做一个 Telegram 机器人,这就需要跟服务器通信,比如,设置 Webhook 之类的,现在这类需要去服务端注册的回调,都要求强制 HTTPS 了,微信的公众号也是需要 HTTPS 的。这里,其实 Let’s Encrypt 是可以解决的,不过我想在本地电脑搭建一个开发环境,让服务器的回调发到我的本地,这样,我开发调试起来非常容易,那就不那么容易解决证书的问题。所以,我想到了,还是弄个自签名证书方便,反正只是在开发环境用一下。

EasyRSA

EasyRSA 是 OpenVPN 社区旗下的一个自项目,专门用来搭建一个 CA 的全部命令行套件。其本质是一套 shell 脚本,里面真正起作用的是 OpenSSL 的命令。如果,你非常熟悉的话,直接用 OpenSSL 也是可以的。

现在进入主题了,接下来本文介绍如何使用 EasyRSA 创建一个 SSL 证书。

下载安装 EasyRSA

因为是一款 shell 软件,本质上下载后解压就是安装完毕了:

1
2
3
4
5
# 去 GitHub 下载最新 Release 现在是 v3.0.6
curl -OL https://github.com/OpenVPN/easy-rsa/releases/download/v3.0.6/EasyRSA-unix-v3.0.6.tgz
# 解压缩
tar xf EasyRSA-unix-v3.0.6.tgz
cd EasyRSA-v3.0.6

然后,我们可以看到一个 easyrsa 可执行文件在目录里,已经安装完毕了。

创建证书颁发机构 CA

接下来就要创建证书颁发机构了,这里有个小技巧,介绍一下,就是等下可能会在目录下生成很多文件,你可能会搞不清楚,生成了哪些,可以现在提前把整个目录变成本地 git 库,提交所有文件,然后一旦有新文件生成了,就很容易分辨。

1
2
# 第一个命令,初始化公钥基础设施(Public Key Infrastructure)
./easyrsa init-pki

公钥基础设施(PKI),是一个安全体系,也是咱们整个互联网的安全基础,我们接下来要讨论的一些概念,都是这个体系内的角色,所以,无论要扮演哪个角色,都需要先拥有一个 PKI。

PKI 简易版流程

图注:**PKI 的简易版流程**

初始化好 PKI 后,我们会发现,多了一个目录,叫 pki/ 里面有两个目录,一个是 private 显然,里面将要存储私钥了,另一个目录叫 reqs/ 看起来,要存储申请(request)了。

第一个,要扮演的角色是证书颁发机构(Certificate Authority)也就是我们说的 CA:

1
2
# 创建 CA 的时候,可以不使用选项,这里开启了一个 nopass 的选项,作用是,创建的 CA 不使用密码保护自己最重要的私钥,因为咱们是在玩开发环境嘛,如果是用于生产,还是需要密码保护的。
./easyrsa build-ca nopass

创建过程中,会问你要 Common Name,你可以输入一个英文句号 . 来表示留空,我输入了自己的名字 Charles,哈哈 :D

创建好 CA 后,我们在 pki/ 目录下,看到一个 ca.crt 这是 CA 的公钥,在 private/ 目录下,有 ca.key 这是 CA 的私钥,也是一个 CA 最要命的东西了。还有其他一大堆的文件,先不要管了。

创建申请表 CSR

刚才也简单介绍了 PKI 是怎么回事,也说到,在这个体系内,会有好多个角色,第一个角色,就是证书颁发机构 CA,然后,我们的目的是得到一张服务器证书,现在就要扮演另一个角色,就是证书申请人

1
2
3
4
# 作为证书申请人,也是需要先初始化 PKI 的,当然,因为咱们全在同一台电脑上,就没必要这么做了,因为上一个步骤,已经有初始化好了一个 PKI 了
./easyrsa init-pki
# 接下来,要创建证书签名申请 CSR(Certificate Signing Request),咱们这里又用了一个 nopass 的参数,开发环境噢。Demo 只是一个名字而已,无所谓是什么。
./easyrsa gen-req DEMO nopass

这里应该比较容易理解吧,有一个证书颁发机构,申请人,先填写一个申请表,接下来拿去机构盖章(签名),于是就签发了一个证书出来。

这个脚本的执行过程中,会问及你的 Common Name,这个时候,如果证书是要用于网站的域名证书的时候,应该在这里填写上网站的域名。

导入申请

继续咱们的角色扮演游戏,咱们现在又要扮演回 CA 的角色了。申请人已经填写了申请表,接下来,提交了申请。作为 CA,我们收到了申请人的申请,需要将申请导入系统:

1
2
# 还记得上面初始化 PKI 的时候,我们说了一个 reqs/ 的目录吗?申请表在那个目录里
./easyrsa import-req ./pki/reqs/DEMO.req HelloDemo

上一个步骤,我们把 CSR 取名为 DEMO 了,所以,这次我们把第二个参数取名为 HelloDemo,为了跟刚才那个名字不一样,然后你就能看到比较好玩的,pki/ 目录下,除了 DEMO.req,又多了一个文件叫 HelloDemo.req,我有一个大胆的想法不知道当讲不当讲,哈哈哈,你猜对了,这两个文件一模一样,因为一般来说,证书申请的时候,CA 都不会是你自己的电脑,现在同一台服务器,扮演两个角色,就搞笑了,左手导入到右手,根本一回事嘛,所以,上面这步,在同一台电脑上是多余的。可以根本不做。只要你记得,你想签名的那份申请是叫 DEMO 就可以了。

签发证书

既然演戏,我们就演到底,咱们现在继续扮演 CA,需要给刚刚导入的 HelloDemo 签名了:

1
./easyrsa sign-req server HelloDemo

命令倒是简洁而且干脆啊…… 执行完毕后,证书就签发完毕了,在 pki/ 目录下,多了一个叫 issued/ 的子目录,里面存放了签发的证书,HelloDemo.crt,有效期默认是 1080 天。私钥就是前面生成 CSR 的时候,在 private/ 目录里的 DEMO.key。到此,我们需要的自签发网站证书就生成完毕了。

证书部署

生成好证书以后,我们就要把证书部署到本地的 Web 服务器上了。

Apache 证书部署

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
SSLEngine on

# Uncomment the next line if Apache should not accept SSLv3 connections, to learn more google for "POODLE SSLv3".
# SSLCipherSuite ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:-SSLv3:+EXP:+eNULL

# Comment the next line (and uncomment the line above) if Apache should not accept SSLv3 connections, to learn more google for "POODLE SSLv3".
SSLCipherSuite ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP:+eNULL

SSLProtocol -all +TLSv1.2 +TLSv1.1 +TLSv1
SSLCipherSuite ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256
SSLHonorCipherOrder on
SSLCompression off

SSLCertificateFile "/path/to/cert.crt"
SSLCertificateKeyFile "/path/to/cert.key"
SSLSessionTickets on

<VirtualHost/> 区段内,添加上述 SSL 有关的配置,关键是 14,15 行,证书文件和私钥的配置。因为,这个例子里面是顶级 CA 直接签发,所以,也不需要配置 Chain 了,否则真正的 SSL 证书还需要配置 SSLCertificateChainFile。因为一般颁发证书的,都不会是顶级 CA,所以,需要 Chain 来描述整个证书链,让浏览器可以确认安全。

Nginx 证书配置

在 nginx 服务器上,配置大概是这样的:

1
2
3
4
5
6
7
8
9
10
11
ssl on;
ssl_certificate /path/to/cert.crt;
ssl_certificate_key /path/to/cert.key;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

ssl_ciphers ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:AES128-GCM-SHA256:AES256-GCM-SHA384:DES-CBC3-SHA;

ssl_prefer_server_ciphers on;

上面这一段,配在 server 区段里。咱们的例子里,生成的证书对是 HelloDemo.crt 和 DEMO.key 一个是公钥,一个是私钥,我重命名为 cert.crt 和 cert.key,主要是为了好看,这里说明一下。

根证书导入

使用了私自签发的证书后,用浏览器访问本地的站点,会发现浏览器报不安全,要点好几次,才可以真的进入,还会有一个红色的警告。如果嫌麻烦的话,可以把跟证书,导入到系统的信任证书中。就是咱们在创建 CA 的时候,生成的 CA.crt ,在各种操作系统里,基本双击就可以激活导入程序了,询问的时候,都点“信任”就可以了。你自己生成的证书,也没什么可怕的了。

总结

以上这些,其实都可以使用 OpenSSL 里面自带的命令完成,不过,那个命令的参数非常多,也非常不友好,比较起来就好像是用 C 语言编程和用 Python 语言编程的区别。后者对用户更加友好,隐藏了更多的底层细节,生产力更加高。

本文介绍了 EasyRSA 的基本使用方法,以及穿插介绍了 PKI 的基本流程和几个关键角色。如果想要深入的了解,还是敬请阅读《HTTPS权威指南》by Ivan Ristic。

—全文完—

Update:

今天用了这个自签名证书,部署 Telegram 和 Potato 的机器人的 Webhook,发现,土豆是正常能用的,但是电报竟然一直说证书验证错误。这里贴一个电报官方给的生成自签名的方法:

1
openssl req -newkey rsa:2048 -sha256 -nodes -keyout YOURPRIVATE.key -x509 -days 365 -out YOURPUBLIC.pem -subj "/C=US/ST=New York/L=Brooklyn/O=Example Brooklyn Company/CN=YOURDOMAIN.EXAMPLE"

注意一下,它用的是 openssl 套件,调用了 req 命令,根据上面的介绍,这个命令本质上是生成签名请求用的,文档里说,也可以用于生成自签名证书,场景是生成 root 证书的时候。

使用隧道搭建开发环境数据库

最近,我在写一个小的项目,用到了 MySQL 数据库,然后我发现一个非常烦恼的问题,我在家里电脑的本地环境改了数据库,到了办公室,代码里的变更通过 GitHub 同步过来了,但是数据库里的变更则完全没有。虽然,也有类似 Migration 之类的解决方案,但是,毕竟是密集开发的阶段,都使用 Migration 太过麻烦了。每次记住自己的操作,重放一遍,或者导出整个数据库,也一并提交到 GitHub 未尝不可,可比起一个真正的两边都能连接的开发环境数据库服务器来说,体验还是差多了。

我自己有腾讯云和阿里云的 VPS,于是就想到了何不搭一个开发环境的数据库来用呢?因为腾讯云服务器上部署了本博客,本来就有数据库,于是我就直接选择了使用腾讯云。别的云原理也是一样的。

安全起见,数据库一般都是侦听本地端口,如果在公网开放端口,则非常不安全。但是,想要从家里和办公室都能连到云端的数据库,则必须解决网络联通性的问题。

于是我想到了隧道。

通过构建隧道,可以将服务器的端口映射到本地,用起来就像在本地开了侦听端口一样。能做到这个效果的非常多,比如 SSH 就是最常见的一种隧道软件。Google 搜索能找到很多的 SSH 建立隧道的范例,本文就不赘述了。

这次我使用的是 KCP 协议隧道。我使用了 kcptun 这个软件。首先在服务器下载软件最新版本,然后,使用如下命令,开启 KCP 服务器侦听:

1
screen -dmS KCPSERVER ./server -l :60331 -t 127.0.0.1:3306 -key "pwd@123456" -mtu 1400 -sndwnd 2048 -rcvwnd 2048 -mode fast2

然后,在客户端本地,同样下载好,使用如下命令开启客户端连接:

1
screen -dmS KCPCLIENT ./client -l :3306 -r 128.142.11.11:60331 -key "pwd@123456" -mtu 1400 -sndwnd 2048 -rcvwnd 2048 -mode fast2

如此,就建立了一个从本地到服务器的连接,并将服务器的 3306 端口,映射到本地的 3306 端口,中间使用的隧道协议还是加密的,这样就不用担心被黑客扫到 MySQL 服务器的端口了。

本地数据库的配置时候,Host 填写 127.0.0.1,端口 3306,这里比较需要注意的是服务器的用户授权:

1
GRANT ALL ON db_dev.* TO dev@localhost IDENTIFIED BY "Dev@demo1";

这样写就不对了,因为一般来说,我们都在本地连接,使用 localhost 作为连接来源的 host 是没问题的,现在通过隧道转发到 server 来的连接,来源 host 到底是哪里呢?

在腾讯云的 CVM 上,我实测下来,应该使用 CVM 服务器的内网 IP 地址,怎么确认这一点呢?其实用客户端尝试连接时候,看报错就知道了。如果我们选的 host 不对,那么连接时候鉴权无法通过,从报错信息里,可以看到应该授权的 host IP 地址。

这样我就相对安全地搭建了为两个本地环境共享的云端开发环境数据库。

这里有个小的技巧,就是善用同步网盘的功能。刚才说到了 KCP 隧道需要在本地安装客户端,启动命令里是有 key 的,密钥不能提交到 GitHub 去的,这是基本常识,包括服务器的 IP 地址也不能暴露到 GitHub,那样就被黑客看到了。

所以,我用的是平时同步文件用的网盘。因为我是 Mac 系统,实际上我用的就是 iCloud,把启动客户端的命令,写在一个 shell 文件里,然后放在 iCloud 网盘上,去了办公室,直接执行就行了,可以直接连接到服务器,在本地映射出服务器上的 MySQL 端口。

另外,在服务器上,可以把启动 Server 的命令也写入一个 shell 脚本,然后配置到 crontab,每 10 分钟重复执行一次,如果 KCP 的服务器端意外退出,就会被 crontab 拉起。非常简单粗暴地实现了可靠安全的 C/S 架构隧道。

前两天写了如何给 Python 项目设置 Virtualenv,然后又发现了一个新的小问题,于是,干脆把文章也改了,改成小集锦了,以后各种小问题,都记录到这篇文章里面好了,省得开新的文章了。

Mac 上如何给 Python 项目设置 Virtualenv

这两天,写了个小小的 Python 代码,基本功能实现了,需要完善一下的时候,突然想起来,VS Code 那么好用,应该也支持 Python 吧,我就不用在命令行用 vim 写那么辛苦了,主要是对中文字符的渲染不好看。

没想到,相当尴尬了,用 VS Code 打开以后,无法识别我在项目文件夹设置的 virtualenv 环境。而 Python 扩展的官方帮助赫然写着是支持的。介绍的设置方法什么的,完全都是扯淡,毫无帮助,还说会什么自动识别之类的,都是不起作用的。想起来了著名的直升机飞行员笑话。

最后,救世主还是 SO(StackOverflow),只有一个老哥提到了一句,在用 VS Code 打开一个文件夹的时候,就创建了一个 Workspace ,然后,你的目录下多了一个隐藏目录 .vscode ,这个目录里有一个 settings.json 文件,这个文件里,设定了一个 Workspace 级别的配置。

如果你使用 ⌘ + ⇧ + P 来设置 Python: Select Interpreter 的时候,是无论如何都没法发现你的项目目录下的 bin 目录的。这就很尴尬了。所以,方法是直接在 Workspace 的 settings.json 中,手动编辑加入使用的解释器路径:

1
2
3
4
5
{
"python.linting.pylintEnabled": true,
"python.linting.enabled": true,
"python.pythonPath": "./bin/python3.7"
}

然后,重启 VS Code,就可以发现完全兼容了现在这个项目目录下的 virtualenv 环境了。又可以愉快的 Python 了。

我解释一下,我一般是进入一个项目目录,然后执行 virtualenv . ,这个项目目录会变成一个 virtualenv 环境,只会影响这一个项目,不会影响别的项目,这样我可以在每个不同的项目里,使用不同的 Python 环境。这会在项目根目录下多出来一些目录,bin,include,lib 等,加入到 .gitignore 文件就好了。

调整左侧目录树的缩进

连续用了几天的 VS Code,发现左侧目录树的展开折叠后,子目录的缩进实在是太小了,目录层级多了,展开以后真的看着很难受,很累眼睛,经常有迷路的感觉。

VS Code 左侧目录树的缩进感觉

放狗搜索了一下,在知乎找到了一个答案,原来可以在设置里面,通过设置 Tree:Indent 选项的值来控制缩进。

在设置里搜索 Tree:Indent 选项 可以找到

在这里,我们看到 VS Code 的缩进控制是用像素做单位的,真是吐血。调整为 24 或者 32 比较好,另外,下面那个选项,说的是,展开一个目录时候,绘制一条垂直线,就像上一张图 alt 目录下面有一条竖线一样,把 onHover 改成 always ,总是显示竖线,这样,目录树就会显得好辨认很多了。

如何左边展示目录树,右边展示 Outline

这个是困扰了我很久的问题,因为一直以来,目录树和大纲都缩在左边,好难受。今天终于知道了。

首先调出终端,然后终端可以停靠在右侧,终端所在的容器,可以停靠大纲的面板,把大纲拖去终端的面板就行了。

感觉上,这些面板有两种,一种是可以让别人停靠的,另一种是只能是停靠到某个面板的。想要左边一个 bar,右边一个 bar 的话,你需要两个可以停靠的容器面板。

不过,如果我想左边目录树,右边大纲,下面是 Console ,又怎么操作呢?

好久没有写代码提交 GitHub 了,真是惭愧!回到正题,今天提交了一个代码,冷不丁发现,在我的 Commit 记录里面,有一条被打上了 Verified 标记。原来 GitHub 的 Commit 支持签名验证了,我可以对每一个我的 Commit 进行签名(Commit Signature),这样,GitHub 的其他用户就知道这个“提交”来自一个可以信任的来源。我举个例子,如果有人设定了我的头像,我的名字,往我的版本库里 Push 了一个 Commit(我的 WordPress 插件官方仓库,被黑客提交过恶意代码,因为我不小心被钓鱼了,自爆一下黑历史),那么,有没有 Verified 就成为一个识别真伪的依据(虽然,我位微言轻,可能不太会有人假冒我,我也知道,杠头请退散)。

给每个 Commit 签名

作为个人开发者,给自己的每个 Commit 签名,可能有点多此一举(或许会有这么想的人),但是在一个多人合作开发的项目里,管理员可以要求所有的项目成员,都必须签名自己的 Commit,不接受未经签名的 PR,那就产生了一定的意义(虽然是什么意义我还没想得太明白)。

上面图里个 Verified 标记,是 GitHub 自动给打上的,因为一个项目的第一个 Commit 是在我生成项目的时候,由 GitHub 的 Web 站点自动提交的,是项目的初始化 Commit。这种情况由 GitHub 方面利用你在网站可信的登录态进行签名。但是我们知道,这是一个分布式版本控制,一般来说,Commit 都是在本地完成的,然后 Push 到云端,所以,要想让每个 Commit 都能够带有签名验证,需要在本地部署签名的过程。怎么做呢?

在 GitHub 验证一个邮箱

我们在 git 里面,进行 commit 操作的时候,都需要设定一个 user.name 和 user.email,所以,对 commit 的签名,是基于 email 的。第一个步骤就是在 GitHub 的 Settings 里面,设置一个用于关联签名算法的 email。你可能已经有了多个 email,在本地,需要设置那个关联了 GPG 签名的 email 来提交代码。

生成一个 GPG 的 key

刚才已经说了,签名的原理是使用 GPG,其实 GitHub 还支持 S/MIME,我不知道那是什么,所以就选 GPG了。

GitHub 支持的加密算法有:

  • RSA
  • ElGamal
  • DSA
  • ECDH
  • ECDSA
  • EdDSA

如果加密算法不在上述之中,可能无法被 GitHub 所验证。如果,你的环境没有安装 GPG,第一步你可能需要安装一下:

1
brew install gpg2 # 我的环境是 Mac 就用 Homebrew 安装了

我在 Mac 上安装的是 GPG 2.x,其实有 GPG 1.x 和 2.x 两个版本,显然大一点的更新一些,支持了很多新功能,不过,有可能你所在的系统环境只能选择 1.x。2.1.17 版本以上的可以使用如下命令来生成 key:

1
gpg --full-generate-key

虽然官方说了支持上面 6 种算法,但是在 GPG 指南这里说,必须选择 RSA,我不知道这个矛盾是为什么,以后再来探究。上面的命令会开启一个命令行交互式创建 key pair 的过程,问及算法的时候,用默认的就行了,我用的 GnuPG 2.2.19,默认选项是 RSA and RSA。当问到 key 的长度的时候,要填写 4096,因为官方指南要求这样,而 GnuPG 的默认值是 2048,这里需要注意。

接下来是过期时间,个人使用选择 does not expire,永不过期就可以了。如果是团队使用,看整个团队的安全策略如何。

接下来要求填写 ID 相关信息,会填写名字,邮箱,注释,这里邮箱是比较关键的,在第 1 步里,咱们预先准备了要关联的邮箱地址。就填上那个。然后是要求键入密码。这个密码的用途是保护你的私钥。如果你自信不会有人入侵你的个人电脑,那么你可以不填写密码,GnuPG 会很贴(fan)心(ren)地走两遍这个要求密码保护的过程,请耐心回车。(注:假如很不幸,一个黑客已经黑到你个人电脑了,用你的身份打开了你的 Term,这时候,如果你的私钥是有密码保护的,每当程序需要使用你的私钥的时候,都必须输入密码,这个情况下,私钥保护密码就是最后一道屏障。)

1
2
3
4
5
6
gpg --list-secret-keys --keyid-format LONG
/Users/hubot/.gnupg/secring.gpg
------------------------------------
sec 4096R/3AA5C34371567BD2 2016-03-10 [expires: 2017-03-10]
uid Hubot (Comment) <Hubot@a.com>
ssb 4096R/42B317FD4BA89E7A 2016-03-10

使用上述命令就可以查看你 LONG 格式的私钥了。这个私钥就是要用来对你的每个 Commit 进行签名的。

在 GitHub 登记你的公钥

然后,咱们需要在命令行打印出来自己的公钥,使用如下命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
gpg --armor --export 3AA5C34371567BD2
# Prints the GPG key ID, in ASCII armor format

-----BEGIN PGP PUBLIC KEY BLOCK-----

mQINBF5bciABEADJ/FQa+ymIuKuZycgtPmLMPHEwOtYY21k7zU9ddDZH6ZGIxeN0
N9ljc6zp8/3cQDRrbrirHv/9WqaqSRb+EFcUog5LOfR/C6NeiGNW8AgUuFxGGFXK
s6VrAmzhIxDmKDkRdX9sHf7myiwBhtkFM0/8AGUdR8pjHKw+vA8IJzMhowWkiX1O
F1ZW81gKUYCLSfkty1HccGr4kFpE6r1R/w18hYH2zcr0dll0ox2LHfSHuuQzamew
hdR7B6S5Xi+EJjv7rujHaRWzLoPXktxUFme9LxdVblp6FD/lP79AkPhqSPAwzee+
ShlO9AScCCbsm8p3/KhmUn2yigbfd0eWvh4wm5HvbTCJ3/SLspMYrsF/VMHAJFRW
pmULevI5bdVR3fm7t/IgkjFasmbOZ9EqZNf43ljVi3SOyTmRX9GbxtvHXKL8tgL1
q7do0cArc/cKigEfssHe6gXChLZ6nDEzj/aNgOEcKo/cPVVCH4yzldEMvCB4aMYW
PET+7Io+FM1b69yOtFvKmJnGNpDbtySn1b6E0gWk/3uqzcspAzZMb6aIdZ6BcaXE
wU8zqRqcMXVnI6s2gvrMYrFCUB71ujzdGO9LWIu/y/FOdrzmrjXofOmdQom9Z+dW
cCo7LaTCE994HhLbqacsUROhjFCSzisH1yi0T0rD6oWSzsjFdewpEtjJGwARAQAB
tENDaGFybGVzIChHUEcga2V5IHVzZWQgZm9yIEdpdEh1YiBjb21taXQuKSA8Y2hh
cmxlc3RhbmdAZm94bWFpbC5jb20+iQJOBBMBCAA4FiEE6zd9skJwGmhOg6epkcp5
trRrrvQFAl5bciACGwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQkcp5trRr
rvQ…… …… ……
-----END PGP PUBLIC KEY BLOCK-----

大概长上面那个样子的,很长很长,整段拷贝下来,然后去 GitHub 个人的 Settings 界面 SSH and GPG keys 标签里,登记这个公钥。登记完毕后,你能看到这个 key 关联的 email 地址显示出来了。

在 git 里面设定使用签名

咱们的公钥私钥对已经生成了,然后就是需要告诉 git 命令每次 Commit 的时候,都要使用私钥进行签名。

1
git config --global user.signingkey 3AA5C34371567BD2

上面的命令设定了全局的签名密钥,如果你使用多个身份在多个项目提交代码,那么不要使用 –global 参数,但是,要记得去每个项目里设置单个项目范围的签名私钥。然后,你要告诉 git 命令,以后每次 Commit 都要签名:

1
git config commit.gpgsign true

也可以手动控制,那么记得执行 git commit 的时候带上 -S 参数。

总结

到此,我们需要的设置已经全部设定完毕了,设定完毕后,执行一个 Commit,然后 Push,就可以在你的项目记录里看到,此次的 Commit 是带有 Verified 标记的了。

此外,有个关于隐私保护的话题。你在 GitHub 验证了一个邮箱,只有你的 Commit 关联了这个验证过的邮箱,在你贡献 PR 的时候,GitHub 才能在贡献人清单里显示出来你的帐号 ID。这也意味着,所有人,都可以看到你的这个电子邮箱,即隐私暴露。

在 GitHub 的 Settings 里面的 Emails 面板,有一个 keep my emails private 的选项,如果勾选了的话,那么关联了你的验证邮箱的 Commit 是无法被 Push 的,因为服务器知道你的隐私设定,会保护你不让你暴露你的私人邮箱。

听起来,如果你非要保护自己的隐私,就死锁了一样。不过,你可以选择,使用 GitHub 提供的匿名 noreply 邮箱,只是一个邮箱地址,并不能收发邮件。你在使用 GPG 进行签名的时候,可以把邮箱地址填成 GitHub 后台给你提供的匿名邮箱。这样,才能同时做到,既签名你的每个 Commit,又在你贡献 PR 的时候,追踪到你的 GitHub 帐号。

文首截图里的项目主页在此:https://github.com/charlestang/trip-table-parser 可以去 commits 标签页里看看带验证的 commit 的效果。

全文完。

附:

今天看了一篇文章,分析讲解了技术人员面试中,现场编写代码的意义,我觉得说得非常在理。

其实我在本科的时候,就有一次有机会面试谷歌的实习生。但是因为在徒手写代码的时候,失败了,成了我毕生的遗憾。也造成了我在这类面试中的恐惧心理。当时,我只是一名大三学生,我 C 语言课程成绩优秀,算法课成绩一般,但是我很有动手能力,做过不少网站和项目,还在学校的网站设计大赛拿奖。我认真做了学过课程的大作业,都是照着最高要求做的,比如实现数据库引擎,实现编译器等等。我自我感觉不错,可是,仍然在面试官让我写出围棋提子算法的时候,一脸茫然。

我觉得这非常不公平,只是因为我对这种冷僻的问题没有研究,对算法的套路不熟悉,就遭逢此让人遗憾一生的失败,实在难以服气。

工作多年后,我也成为了一名面试官,我就非常反对在面试中考徒手编程,但是后来我发现,识别一个优秀程序员,着实没有什么好的方法,如果不用徒手编程的话,我们识别一个好的程序员的概率更加低下。所以,我做出了退让,候选人必须过手写代码这一关,但是我不会考算法套路,我们都知道,很多人朗朗上口的深度优先,广度优先,回溯法,线性规划等等,在你实现一个电商业务系统的时候,或者在你实现一个对账系统的时候,或者在你实现一个网站管理后台的时候,用到的概率微乎其微,就算要用,也不会自己去写,因为经常会写出漏洞百出的代码,使用已经存在的类库要稳妥得多。所以对于真实工作来说,这些套路被一些同学喷成是屠龙之术,我觉得也无可厚非。

所以,我当时要求候选人写这样一种代码,仅仅根据直觉,就一下子知道解法是什么,只要智力正常,有初中数学水平就可以随口讲得头头是道。越是这样的题目,越能进入我惯用的题库。我举个例子,计算两个数字之和。这个看似很简单的题目,是很多 ACM 比赛的入门题,加法是任何编程语言内嵌的方法。如果候选人想得太过简单,我就会提示,编程语言的变量都有取值范围,我出的这道题目,不限制两数的取值范围。

这样一来,就不如表面那么简单,但是仍然知道怎么做。首先要选取合适的变量作为容器,其次要按步就班来计算求和。最后可能要考虑很多的情况,比如正负数,浮点数等等。但是,想不出来这个怎么做的人,少之又少。但是写不出来的人,比比皆是。

我此前的想法是,能用嘴说得头头是道,说明候选人智力正常,能用笔写出来,说明候选人是个熟手,不怕繁琐,是个耐烦的人,因为我们的日常工作琐事,往往就是需要细致耐心,有时候甚至是个重复的工作,如果不耐烦,畏惧繁琐,可能就不是很好的候选人。另外不能把自己的想法写出来,说明对自己惯用的语言并不熟练,不一定是个熟手。

我秉持这样的理念很多年。类似这样的题目,我知道还有不少。我有个同事也秉承类似的理念,跟我不同他坚持需要有一点绕弯的题目。他想考察候选人会不会奋力争取,能不能与面试官充分沟通,在提示之下到底能不能被循循善诱找出正确答案。一方面考察沟通,一方面考察智力,一方面还考察候选人的性格。我觉得这也是不错的方式。

有一次,我在拉钩或者知乎上回答别人对编码面试的质疑,我解释过我的理念,我觉得自己还是比较理直气壮的,我说,编码面试确实不能证明一个候选人有多好,但是,测试出了这个候选人的下限有多低。最不济,候选人还是一个可以熟练写出代码的人。

今天看到的文章是这篇《为什么时至今日编码面试依然这么糟糕?》,这篇文章从另一个角度解释了这个问题。我觉得这个作者解释得也比较好。

我们当然会在面试中看走眼,不过,这种看走眼,有两种情况,一个是放进来错误的人,另一个是拒绝了优秀的人。这两种情况来看,放进来错误的人,成本是非常高的,但是拒绝了优秀的人,成本则很低(对于优秀的顶级企业来说尤其如此)。

通过艰难的徒手编码测试,对一个人的心性,毅力,能力都是一种有效地证明,所以,抬高了录用的门槛,极大降低了“错放”的概率,哪怕增加了误拒的概率,但是综合来看成本是降低了。再加上企业有很好的品牌,就更不惧没有人来面试了。

最后,我也想说跟文章作者一样的话。作为候选人,你已经足够优秀了,这个世界上一定有适合你的岗位。一场面试什么都说明不了,因为面试官背后的种种想法,都是希望在统计意义上去优化成本和收益,对于个体的候选人来说,不具备什么绝对的说服力,运气成分很大。所以,千万不要因为过不了编码面试就气馁。

之前,在北京体验了一次智能门锁,可以用密码开门,不用钥匙,觉得好方便,一冲动,就把家里的锁换了,选了京造智能锁(当时随便找了个最便宜的,买下来 789),可以用指纹、密码、ID 卡、钥匙多种手段开门。更换异常顺利,从此少了一把很大的钥匙,感觉特别方便。这时候,我发现楼下单元门的门锁,还是需要使用钥匙或者 ID 卡才能开。我出门还是不能空手。

不过,楼下单元门上有个对讲门铃,按了房间号码以后,会唤响室内安装的一部对讲话机,接听后,按开锁键,就可以打开楼下的门锁。我就想到,能不能按一下我自己家的门铃,然后,用手机 App,通过网络控制,完成开锁的动作。这样,我出门只要有手机,就可以了,不需要钥匙或者 ID 卡。

机械方案,误入歧途

楼宇门禁室内机的样子

先给大家看一下,楼宇门禁系统的室内机的样子,看起来就像一个电话分机一样,一般安装在家里门口的位置。上面有一个按钮,开锁键。这个东西的淘宝名称叫做“楼宇门禁非可视对讲室内机”,这个名字我也是折腾了很久才搞明白的,免费赠送给大家了,这年头搞清楚搜什么很重要。

想要手机控制开锁,就要想办法按下那个按钮,我想起以前在微博上看过一个人发的小视频。

在微博上看到过一次这个小视频

这个视频里展示了一个手机控制的机械杆,机械杆转动,形成了一个按压按钮的效果。显然,我遇到的问题有人遇到过,也有人解决过。当我想到要开门的时候,刚好想起了这个视频。废了一番力气,从历史记录里翻出来了。

这个方案,是一个通过网络控制一个机械的外挂的方案。想到这个方案以后,就开始研究人家怎么做的。其实,玩这一套,是一个领域,叫 IoT,意思是 Internet of Things,中文叫“物联网”。图里使用到的那个机械设备,核心部分是一个电机,这个电机的淘宝名称叫做“舵机”,更加正式的名字叫“步进伺服电机”,本质的原理是,通过脉冲信号的数量,来控制电机转动的角度,实现比较精确的控制。因为用在飞机模型,或者船只模型里面,用来控制舵板之类的装置,得名为舵机。

图里的方案,大概使用了一个叫 Node-RED 的解决方案,大概是一个嵌入式开发板,封装了比较高级的语言(NodeJS)和开发环境,方便玩家快速开发出如是视频中的解决方案。

这个方案看起来轻巧,无侵入(不破坏原来的设备),轻便。但是,并不是全无缺点。对我来说,最大的缺点就是门槛比较高,光是搞清楚视频里的这些东西叫什么名字,淘宝怎么买,就花了不少精力,看得见,叫不出名字,越来越普遍了。另外,视频里可以看出来,电机带动的机械杆,是水平转动的,但是按钮是垂直按压的,那么水平扫动,怎么才能转换成垂直按压呢?

力学原理示意图

上面的那个方案,如果想要同样效果,需要一个前提条件,就是你要按压的按钮是带有导角的,不然的话,就不能用视频里的方案,然而,非常不幸的,我家的那部室内机,开锁按钮是圆柱形的,侧面看上去,就像图里左边的形状,没有导角,也就无法通过水平扫动得到一个垂直方向的作用力。

我能想的办法,只能直接产生一个垂直方向的作用力,一开始我思维定势在舵机上面,就变成了如何把舵机输出的扭矩转换成垂直按压的作用力,可能需要一套连杆齿轮之类的经典机械结构,简直痛苦,我并不懂这个。

空想方案一

空想方案二

我想了各种空想方案,但是,发给小伙伴,他们都看不懂我在说什么,所以,读者你看不懂,也没什么,不是你的问题:D

后来一个对机械有点知识的同学告诉我,还有一种设备,是集成好的电动液压杆,才打开了一个新的大门,原来还可以用液压杆,直接能产生一个方向的作用力,还足够大,没有扭动转换按压的问题,也没有力矩杠杆问题,不用考虑功率问题。

但是终究还是太过麻烦了,想想就头皮发麻。乱七八糟一堆,怎么安装?怎么固定?怎么供电?主控怎么连接?全都没有头绪,需要太多知识了。所以,这个阶段,全部停留在空想,我没有任何行动,仅限于搜淘宝和思维试验,以及群里和小朋友讨论。

弱电电子方案,看到了希望曙光

外挂一套机械装置可能还是太过麻烦了,而且稳定性实在是堪忧,以至于,整个项目陷入了僵局。有一天,我一个人待在家里,实在无聊,我就开始拆卸门上的那个室内话机,打开一看,竟然也没有那么难以理解。

非可视对讲室内机1

非可视对讲室内机-电路板正面

非可视对讲室内机-电路板背面

看到这个电路板,我惊呆了,没想到如此之简单,以前我严重高估了困难性。从电路板正面,我们三个元器件和 6 根线,很好理解,信号进入线,顶部两根蓝色的,(震惊它的精巧,竟然只有两根),下面 4 股线是听筒,可以想见,里面是麦克风和扬声器。右上角是开关,左边是挂机的按钮,右下角是一个二极管小灯。

就算我电路知识全部还给老师,我仍然能理解到,开关的本质是一种短路,那么输入信号线只有两根,又直接连接了开关,从背面可以看到(这是一个单层 PCB 电路板,非常简单,看到的就是实际的情况)。可见,开锁的原理,极其简单,就是短路输入的两股信号线即可。也就是说,我们需要的东西是“通过网络控制的开关”,或者类似的东西。一个同事告诉我,叫“继电器”。

于是开始了新的搜索旅程,开始在淘宝找 Wi-Fi 门禁开关,或者继电器。于是,我找到了这么一个东西:

淘宝找到的智能 Wi-Fi 门禁开关

其实,继电器我也不是没有找到过,大概长什么样子呢,看下面的图:

Wi-Fi 智能继电器模块

我为什么买了上面的那个集成好的开关,而没有买下面的继电器模块呢,原因可能是我的个人洁癖。这个模块上面的蓝色的东西,就是继电器,从参数看,能支撑 250V 电压 10A 电流,而我想开关的线,无非是那么细的两根线,可以猜到,上面根本不会有什么电压或者电流的。那我为什么要用一个强电的继电器,去控制一个弱电电路呢?(这种洁癖是病,得治)

上面那个集成好的开关,显然也是一个 Wi-Fi 芯片加一个继电器,不过显然是一个弱点继电器,就显得非常小巧。而且,一体化集成方案,也非常紧凑。就是价格差距有点大,上面的要 98 块,下面的模块只要 30 多。

要想解决问题,还是要穿透很多迷雾

一个插曲,我买那个开关的时候,店家问我要怎么玩,我说了我的想法。淘宝小二告诉我,说不行的。我说,你估计不懂电子电路吧,你就告诉我一下,图里的几根线,分别干什么,以及怎么给这个电路板供电就行了。她很负责的要电话联系我,电话里,非跟我说不行的,说那个开关,只能安装在主机的附近,不能安装在室内机,肯定不起作用的,她亲自安装过。我问她,你安装过不起作用的原因是什么,你是否真的懂呢?她斩钉截铁说,她当然懂,她卖这个吃这口饭的,很了解。然而,我问不起作用的原因,终究是没说出来。

你要干一个事情,人家告诉你不行,然后,你可能要冒一些损失的风险,比如退换货的成本,时间浪费等等。但是,我想,这个东西的原理我应该是看懂了的,不太会错,她虽然说不行,拼着损失 100 块,我也要试一试好死心。

卖家终究没讲明白,这个东西怎么供电,但是给我发来了安装图,配合宝贝页面的图示,果然这个东西都是只介绍怎么安装在主机附近。很多公司玻璃门,都用的这种按钮,进门的时候刷工卡,出门的时候按一下按钮,门就开了。

所以,我只能去查一般的楼宇门禁系统的规格。对,关键词是“楼宇门禁”,这也是一个要命的关键词,你说不对,很难搜。我发现,大多数楼宇门禁的直流电供电箱,规格都是 12V1.5A,我就猜那个开关既然设计为和门禁主机共享电源,可能接受的是 12V 电源。想想,这东西还是能解决的,我要想办法使用 12V DC 供电。

东西买到手了,我才发现,我并找不到一个 12V 的直流电源。我手上最多的东西是充电宝,USB 线之类的东西,这类如无例外都是 5V 的直流电供电。我剪断了一根 USB 线,抽出正负两极,尝试驱动这个开关,发现根本无法点亮,可见 5V 是肯定不行的。

正好,我有一个喜欢玩嵌入式的小伙伴同事,他很热心帮我分析我买的这个 Wi-Fi 开关,我们分析电路板上的元器件,发现 Wi-Fi 主控芯片在网上非常流行,几乎是这类设备的事实标准,这个 MCU 芯片的标准电压是 3.3V,电路板上的继电器芯片,也搜索到了,供电电压是 5V,那么为什么 5V 的直流电拖不动呢?

我们又发现了上面有两个 DC-to-DC 的电压转换芯片。我们猜测,第一个是把 12V 转成 5V 的,第二个是把 5V 转成 3.3V 的。所以,只接受 12V。到这里,我就问高手,我说,这个电压转化,是什么原理,是把高于 5V 的都降低到 5V,还是只能把正正好好 12V 变成 5V?他回答我说,是把高于 5V 降低到正好 5V。当然,高出的部分是有极限的,太高的电压就直接击穿了。那么就有了一个大胆的猜测,只要供电高于 5V,这个东西就能跑起来。高手说:善。

然而我还是没有高于 5V 的直流电。你看,走了那么远,最后被这种事情绊住。结果,高手突然跑过来雪中送炭,接济了我一块 9V 的干电池。这个我也见过,竟然从来没想到过!(后面再说高手为啥这么热心)

我于是用 9V 电池直接测试了那个开关,点亮了!让我无比兴奋!虽然,离成功还很遥远,但是我觉得已经超过 50% 的概率了。

首次测试成功,我高兴得跳起来了

得知可以用 9V 电池点亮以后,怎么安装,以及怎么确保稳定运转就成为提上日程的事情了。从上面的图里,可以看到,我买的是一个 86 盒,就是家里一般的那种开关插座面板的规格。如果家里事先掏好一个洞的话,安装就简单了。只剩下了走线的问题了,如果没有洞,就比较麻烦。

同事建议我安装明盒,这个大家去淘宝搜“86 明盒”,就知道我说的是什么,明盒的麻烦是,要凸起一大坨。而且,可能还要在墙上打眼,膨胀螺丝等等问题。

我也是犹豫了很久,后来觉得,还是先试验成功再做决定。回了家里,我打算接线,发现这个电路板其实很小,就想,能不能干脆塞进那个话机的外壳里面,舍弃这个 86 的面板。反正我要的只是遥控开关功能,实体按钮话机上本来就有一个了,再多一个也不是特别有必要。

拧下来一比划,真的可行。于是我就开始了安装。

将电路板拆下来直接放入室内机外壳

将电路板拆下来直接放入室内机外壳

反正 9V 电池可以驱动,我就想到,把电池也放进去,图里就是我的安装过程,电池用双面胶贴上,然后电路板塞进。

[embed]https://v.youku.com/v_show/id_XNDMwNTY1MjMwOA==.html[/embed]

上面的视频就是我塞进去以后的效果,然后,我热血沸腾地跑到楼下去按门铃试验了。

[embed]https://v.youku.com/v_show/id_XNDMwNTY1OTc4MA==.html[/embed]

成功了!我觉得无比振奋,高兴得手舞足蹈。整个事情到这里其实就告一段落了,从想到这个点,到痛苦的寻找,最终还是被我做出来了,而且安装到了话机的里面,简直完美!虽然从 IoT 这个领域,或者从智能家居这个领域来说,我这个东西还是很简单的一个小东西,不知道为什么就是很有成就感。

最后的困难,供电

到上面那一步,我高兴得睡不着觉,四点多才睡下。心里唯一的忐忑,就是那个电池能用多久。想起来专家跟我说过,Wi-Fi 模块比较费电,恐怕难以为继。我睡前就在思谋这个事情,如果一节电池可以撑很久,我以后就用电池方案了。

然后我就想算个账,一个电池大概能用多久。已知,Wi-Fi 模块待机 0.1W,Wi-Fi 芯片的电压 3.3V,电池设计容量是 400mAh。根据公式 Q = It,电量等于电流乘以时间,再根据功率公式 W = IV,可以算出来待机电流是 30mA 左右,那么理论待机时间是 400mAh 除以 30mA,才 13个小时!

算出来这个又阴云密布了,早上 9 点我起床一看,已经没电了,毫不意外,因为就算达到理论值,也只有 13 小时,我还没算电路和服务器相连心跳的耗电,还有之前点亮后,反复配对几次的耗电,撑到早上没电,实属正常啊。

搞了半天还是要解决供电。

后来我去淘宝买了一个 12V 的直流电源插头,是以前斐讯盒子的电源转换器,然后又买了10米的延长线,从我的弱电箱一直延伸到门口的室内话机里面,用电烙铁在话机的外壳上烫了个洞,把电线伸进去,直接从交流转直流给这个智能模块供电,总算是全部搞定了。

[embed]https://v.youku.com/v_show/id_XNDMwNTgwMTQzMg==.html[/embed]

软件部分,智能助手的连接

整体解决方案完整原理图

折腾了很久,总算全部搞定了,最后在家里走了5米多的明线有点不够完美。不过第一次从调研,设计到最后实现,能完全搞定,并且稳定可靠,我已经很满意了。

一直没有提及软件部分,这里简单提一下。我买的这个开关,用的 Wi-Fi 芯片,是圈里非常流行的芯片,很多 IoT 设备都是用的这款,这家开关集成的芯片背后的厂商叫易微联,也生产很多其他智能设备。我只是把一个智能门禁开关改造了一下和家里的门禁室内机相连了。

然后,很惊喜发现,易微联作为三方厂商,和小米的米家智能也可以联合,就赶快把这个开关绑定到了米家。虽然在米家里没法直接操作,但是可以通过语音来操控,我可以喊“小爱同学!打开门铃开关”,这样,就可以响应门铃,自动开门了,以后人在家的时候,只要喊一声就可以了,不用跑过去门口开门。

而从外面回来,可以用手机遥控给自己开门。非常方便。

总结,以及还有什么可能

这次的项目完结,我感觉是对我这么多年来知道的各种电子,计算机,软件知识的一次大盘活,还有动手能力,设计能力等等,非常有成就感。

中间我做的过程种,给我支招的那个高手同事,听了我的想法,他自己也心动想搞一个,不过最后他用了跟我不同的方案,一看他就更有经验一点。他买了一个 433 遥控弱电继电器(整套方案 20 以内)。433 本质上是一个射频的频率,433MHz,常见的遥控器使用的频段,比如遥控车库门,单位会议室的遥控投影幕布,遥控电风扇之类的,可能是红外的,也可能就是这种 433 的。

这个模块非常小,比一元硬币还小,相应的其功耗也极其低,不支持 Wi-Fi 和智能,这带来的好处是,一节 9V 电池,可以供电几个月到半年。而且方案简单可靠。很难坏掉。也不担心停电。

怎么解决 Wi-Fi 控制呢,可以用一个树莓派之类的小型机器来遥控那个 433 模块的遥控器。这样,也可以解决智能的问题,后来他买到了一个可以和天猫精灵结合的设备,通过天猫精灵控制,也可以语音控制。

如果计算完整功能的成本来看,我的整套方案在 130 左右,他的方案算上天猫精灵的部分也差不多 100 多,成本差距不大。我的方案集成度高一点,但是没有他的方案简洁。他把遥控和智能拆解成两部分来实现,遥控那一侧尽可能简单可靠,低功耗。智能那一侧就有各种办法。各有所长。不过我个人反思一下,可能他的方案更好一点。毕竟是专家,经验丰富了。

因为老板想搞 K8S,但是我连 Docker 都不懂,就觉得还是要学一点点 Docker 的,之前还是看了一点点的,甚至折腾过一个开发环境的方案,但是,很长时间不弄了以后,就全都还回去了。

这次我又想自己搭建一个基于 Docker 的开发环境,以前只是把 Docker 当成一个易于分发的开发环境来思考,所以,我记得以前费了很大的力气,做出了一个单一的 image,把 PHP + nginx + Redis + Memcahed 全部压到一个 image 里面了,然后用 Volume 映射代码,MySQL 连接本地网络公用的实例,形成一个开箱即用的开发环境。

这次,因为稍微看了点编排的概念,开始纠结,这些东西真的应该压到一个 image 里面么?为啥不是多个 docker image,然后,怎么想办法编排一下子?不是传说有个最优实践是一个 container 里面只放一个服务嘛?

第一个纠结的就是 nginx 和 PHP 到底应该放在一个 image 里面还是不同的 image 里面呢?

网上搜了一下,发现还是有不少文章讲 nginx 和 PHP 分开放无法访问的问题。看来,显然有人做过尝试了,而且遇到了问题。就看看他们遇到了什么问题。经过一番分析,我感觉我想明白了这个事情,到底应该放在一起,还是分开放。

分开或者合并的原理

其实,经常配置 nginx 和 PHP 的话,就会知道,这俩在原理上,分开和合并都是完全可能的。而且,从提供的接口层面,我们看不出来到底鼓励怎么做。

常见的配置方法是,使用 fastcgi 的方式来配合 nginx 和 PHP,我这两年的经验,用 debian 的 apt-get 安装的默认配置看,nginx 和 PHP 的连接方式,是用的 UNIX sock 文件,这种情况下显然是必须在一台机器上了。不过,显然 fastcgi 是支持 TCP 协议的,就是大家很熟悉的 9000 端口,流行的配置文件都是 tcp://127.0.0.1:9000 这样的编写方式。这个本地 IP 地址,看起来也是部署在一台机器的。

不过呢,既然支持 TCP,就必然可以分布在不同的机器上面,原理上完全成立的。

网上流行的问题是什么?

那么那些把 nginx 和 PHP 放到不同 image 的同学遇到了什么问题呢?其实,是路径问题。

其实,我想,因为部署在一起的方式太过于流行了(可能的根本原因是互联网的绝大部分网站的规模很小,都在单台服务器上),以至于很多人没有注意过路径这个问题。

nginx 是一个服务器应用程序,每次要伺服的时候,都要从一个文件根目录出发,寻找需要伺服的文件路径。而 PHP 的 FPM 进程,也是一个服务器应用程序,它也有一个问题,就是需要从一个文件根目录出发,去寻找需要解释的文件路径。

因为最为流行的部署方式是放在一起的,往往也包含了静态文件和动态文件部署在一起的问题(前后端不分离是更为流行的做法),所以,用到的文件根目录,都是在一起的,所以,很显然,如果分开部署 nginx 和 PHP 的话,一定会遇到文件路径寻址的问题。

nginx 配置文件里,会用 root 变量指定一个 server 寻址的根目录,合并部署的时候,和 PHP 的根目录是一样的,用 document_root 变量(就是 root 的别名)传递给 fastcgi,但是,分开部署的时候,一个 server 的 root 变量,指的 nginx 所在的计算机的路径,但是 fastcgi 需要使用的 SCRIPT_FILENAME 参量,里面的路径,要用的是 PHP 所在的计算机的路径。既然是两台计算机,路径可以吻合,也可以不吻合,所以,分开部署的话,还能正确使用,是有一定概率的。你怎么知道 nginx 的 image 和 PHP 的 image 正好基于一个发型版?在 Docker 的世界下,两个 image 来自天南海北的两个人制作的可能性很高。

怎么解决路径问题?

要说怎么解决这个问题,其实,说到这里,知道了原理,就非常好解决,梳理好两个服务器程序应该使用的路径参数就好了。

document_root 这个变量,一般会继承 server 段落的 root 变量的配置,或者 http 段落的 root 的配置。如果这个 root 和 PHP 所在的机器,驴唇不对马嘴,那么可以猜测一定跑不起来。

解决方法是,把 PHP 所在机器的 root 在 location 段落里重新设定。或者,设置 SCRIPT_FILENAME 这个 fastcgi_param 的时候,用绝对路径直接写,不要用 $document_root$script_name 这种变量的写法。

然而,像我这么纠结的人,还是很不满意的,因为这种写法让我觉得恶心。为什么呢?因为耦合了。

nginx 在一台机器上,以服务的面貌提供自己的服务,而 PHP 在另一台机器上,也以服务的面貌提供自己的服务。但是,如果 nginx 的配置,必须知道 PHP 那台机器的文件路径,我想,这就是它知道了它不应该知道的事实,这就是耦合,这就是丑陋。

其实,nginx 作为一个服务,从客户端那里得到了 script_name,当然,它自己解释不了,也不拥有这个文件,所以,用 fastcgi 把 script_name 传递给 PHP 所在的服务就行了。这是最最必要的操作了。能不能不用搞清楚 PHP 所在的计算机的路径呢?当然可以,只要使用相对路径就行了。

那就需要 PHP 的 fastcgi 启动的时候,知道自己的根目录在什么地方,然后传过来相对路径,都可以自己找到正确的位置,从而解决了一个耦合。PHP 的 FPM 当然可以这么配置,只是因为一起部署的缺省配置太过流行,咱们从没注意过这个可能性而已。

到底应该放在一个 image 里还是分开?

答案是:视情况而定。(KAO!跟没说一样)

其实,PHP 的 FPM 是支持一个叫 pool 的特性的,我们可以在一个 pool 里面通过 chroot 和 chdir 之类的特性来把访问限制在一个特定的路径里,就是代码所在的根目录。

但是,那样的话,如果你一台机器上有多个网站的源代码,你就必须把根路径指向多个网站的共同根目录,不然的话,PHP 就只能伺服其中一个。

我们知道,世界上绝大多数网站的规模很小,所以,一台 Linux 可以同时支持很多网站的使用,所以,绝大多数缺省配置,FPM 只配置了一个 pool。这种情况下,nginx 传递相对路径的时候,必须加一个网站名的前缀。懂道理的话,会很简单啦,怎么都不会搞混。但是,显然增加了这套架构的学习成本,不是每个人都能很快搞那么明白的。

所以,详细回答一下“到底应该放一个 image 还是分开?”这个问题。

如果,你只是在本地,做一个给自己用的开发环境,我强烈建议放在一个 image 里面。一个程序员,往往会开发 N 多个网站的代码,放在一个里面,最省资源。配置也最为熟悉和简单,网上随手一搜,搜出来的配置很大概率可以部署成功。

如果,在线上环境,部署一个流量弹性范围很广,或者增长可能性很高的服务的时候,分开部署的优势比较大。因为,nginx 的性能是非常好的,远远好于 PHP。分开部署后,PHP 的 FPM 进程不够用了以后,可以不断扩容,增加 container 数量就行了。但是,这种方案的话,学习成本较高,需要程序员对这几个服务的配置有比较深的理解,就算自动扩容,执行动作感觉也不是单纯增加一个 container 就行的,毕竟一个 container 就有一个入口 IP,还要把扩容出来的入口 IP 告诉 nginx 所在的 container。

结论

其实吧,最流行的方案,恰恰是最正确的方案。比如,你可以直接下载到 LNMP 完备的 image,这种东西需求量最大,所以最流行。因为都是单个程序员用来解决自己开发环境的。就算拿去用在生产,问题也不大,小流量的服务和网站,才是这个世界的主流。不过想明白为什么是这个样子,就要花点心思。