Category Archives: 开发

温州地区的丧礼经济

很多人说死也死不起。
外婆前段时间去世了,享年 89 岁。
前前后后花费了20多万元。
寿衣 1400元。
灵堂,800元。
做法事,3000元。
殡仪馆火化,3000元。殡仪馆提供军乐吹唱接送服务,接送分阶段收费,从殡仪馆门口接送到祷告室和后面火化完送出到门口,收费 400元,从祷告室接送到火化室收费 200元。
租车,1100元;租车包括两部分,一部分是花圈车,外婆去世的时候租了23俩车,其中1俩为放大照片的头车。还有一部分是接送送葬亲友的小面包车,总共有 270人,10辆车。
鲜花,560元;鲜花15篮。
纸扎品,500元。
骨灰盒,5200元。
请亲友吃饭,80000元;吃饭有两顿,第一顿23桌,第二顿27桌,每桌大概1400元。
墓地,118800元,墓地在很早之前修缮过,后面政府发布条例禁止重新翻修墓地,今年过去看是有大面积破损,但是没有进行修复。

NODEJS 命令行工具开发简介

SHELL可执行脚本

写一个 shell 命令行脚本。

# test001.sh
echo "Hello World !"

如何运行这个脚本?

sh test001.sh

这还不是一个命令行工具,这里命令行工具是 sh,test001.sh 是这个工具的一个参数。

如何转转换成命令行工具?

# test001.sh
#!/bin/bash
echo "Hello World !"
chmod +x test001.sh

以命令行工具的形式执行。

./test001.sh

和平常的命令行工具有点不一样?

重命名成 test001,放入系统路径中,就可以直接用 test001 全局执行命令。

export PATH="/usr/local/opt/ruby/bin:$PATH"

复杂多文件的情况?

test001
|--bin/
|----test001
|--lib/
|----...
export PATH="/path/to/test001/bin:$PATH"

扩展一

cat /etc/shells
# List of acceptable shells for chpass(1).
# Ftpd will not allow users to connect who are not using
# one of these shells.
# /bin/false was added for FTP users that do not have a home directory.
/bin/bash
/bin/csh
/bin/ksh
/bin/sh
/bin/tcsh
/bin/zsh
/usr/bin/false

扩展二

#!/usr/bin/env shell

1、兼容性,/usr/bin/env 从系统路径中查找脚本解释器路径

2、/usr/bin/env 有 -S -P 参数,可以指定其他查找路径

#!/usr/bin/env -S -P /custom/search/path:${PATH} bash

NODEJS 可执行脚本

同理

# test002.js
console.log('hello world');
node test002.js
# test002.js
#!/usr/bin/env node
console.log('hello world');
chmod +x test002.js 
./test002.js

去扩展名,复杂多文件的情况?

# aveng/meili-all-fwtool-aides Aides 3 项目
.
├── bin
│   ├── aides
│   ├── aides_application
│   ├── aides_component
│   ├── aides_fwtool
│   ├── enums
│   └── tools
├── jsconfig.json
├── lib
│   ├── application
│   ├── commons
│   ├── component
│   ├── fwtool
│   └── index.js
├── node_modules
|   |...
├── package-lock.json
├── package.json
├── src
│   ├── application
│   ├── commons
│   ├── component
│   ├── fwtool
│   └── index.js
├── README.md
└── yarn.lock

扩展一

对 nodejs 命令行工具,不需要手动设置系统路径

{
  "name": "test002",
  "bin": {
    "test002": "path/to/entry\ file"
  }
}
npm link

NODEJS 命令行工具开发

获取命令行参数

1、普通方式

在 nodejs 脚本中,通过 process.argv 获取命令行参数,process.argv 是个数组:

#!/usr/bin/env node
console.log('process.argv: ', process.argv);
node test001.js -a -b -c -d 123 --aaa --bbb --ccc --ddd 123 456
process.argv:  [ '/usr/local/Cellar/node/11.3.0_1/bin/node',
  '/Users/eqielb/Test/test/test001.js',
  '-a',
  '-b',
  '-c',
  '-d',
  '123',
  '--aaa',
  '--bbb',
  '--ccc',
  '--ddd',
  '123',
  '456' ]
./test001.js -a -b -c -d 123 --aaa --bbb --ccc --ddd 123 456

输出结果是一样的。

2、commander 模块

手动分析管理参数非常麻烦,可以使用 commander 模块

https://www.npmjs.com/package/commander

3、 yargs 模块

https://www.npmjs.com/package/yargs

4、 minimist 模块

https://www.npmjs.com/package/minimist

5、模块对比

调用系统其他命令

1、基础模块

可以通过 nodejs 的 child_process 模块,新建子进程执行其他命令

#!/usr/bin/env node
var name = process.argv[2];
var exec = require('child_process').exec;

var child = exec('echo hello ' + name, function(err, stdout, stderr) {
    if (err) throw err;
    console.log(stdout);
});
2、通用模块

如:安装 shelljs 模块

#!/usr/bin/env node
var name = process.argv[2];
var shell = require("shelljs");

shell.exec("echo hello " + name);

如:安装 execa 模块

const execa = require('execa');

(async () => {
    const {stdout} = await execa('echo', ['unicorns']);
    console.log(stdout);
    //=> 'unicorns'
})();

模块对比:

3、特定模块

如: 安装 nodegit 模块

var Git = require("nodegit");
// Clone a given repository into the ./tmp folder.
Git.Clone("https://github.com/nodegit/nodegit", "./tmp")
    // Look up this known commit.
    .then(function(repo) {
        // Use a known commit sha from this repository.
        return repo.getCommit("59b20b8d5c6ff8d09518454d4dd8b7b30f095ab5");
    })

命令行交互

1、inquirer 包

美化控制台输出

1、chalk、colors 包

2、ora 包、cli-spinners

3、blessed-contrib 包

https://github.com/yaronn/blessed-contrib

4、命令行字体 figlet

 _ __ ___    ___    __ _  _   _
| '_  _ \  / _ \  / _ || | | |
| | | | | || (_) || (_| || |_| |
|_| |_| |_| \___/  \__, | \__,_|
                   |___/

其他事项

1、返回值

根据 Unix 传统,程序执行成功返回 0,否则返回 1 。

if (err) {
    process.exit(1);
} else {
    process.exit(0);
}
process.exitCode = 1; 
process.exit(); // 默认情况下 exitCode 是 0

扩展一

process.exit 有回调方法

process.on('exit', (code) => {
   // 同步方法 
    console.log(About to exit with code: ${</span><span class="cm-variable-2">code</span><span class="cm-string-2">});
});

扩展二

一般情况下,不建议直接调用 process.exit() 方法,直接调用会导致异步方法中断。需要手动异常退出的话,可以设置 process.exitCode ,并抛出 uncaught error,让进程根据异常自动退出。

2、重定向
ps aux | grep 'node'

nodejs 中实现这种功能

process.stdin.resume();
process.stdin.setEncoding('utf8');
process.stdin.on('data', function(data) {
    process.stdout.write(data);
});
3、系统信号

接受系统信号

process.on('SIGINT', function () {
  console.log('Got a SIGINT');
  process.exit(0);
});

发送系统信号

kill -s SIGINT [process_id]

扩展

这些功能开发不太常用,运维会用到,看公司发布系统的脚本

发布

命令行工具就是一个 npm 包,执行 npm publish 进行发布,npm i 安装后就可以使用。

NODEJS 命令行工具应用举例

1、主机管理

aws、google cloud、aliyun、qcloud 等都支持 oauth 2.0认证,可以命令行下做验证通过命令行管理服务。

2、命令行爬虫

3、自动提交表单

Ansible 简单介绍

最近迁移一个公益网站 http://www.tongdelove.com 到一台新的 VPS 上。原来安装 nignx,php,mysql 等繁琐的操作已经被 docker 取代了,但是麻烦的还是需要手动连上服务器,安装 docker 服务,rsync 项目到服务器上,还是麻烦。所以查询了下有没有什么解决方案。
了解到现在比较常用的是 Ansible 工具。Ansible是—基于 Python paramiko 开发,分布式,无需客户端,轻量级,配置语法使用 YMAL 及 Jinja2模板语言,更强的远程命令执行操作。在了解 Ansible 之前,我也听过 Puppet 、fabric等工具,但看起来像是比较专业的运维管理工具,也比较复杂,没有深入去研究。但是 Ansible 一看非常简单,几行命令就跑了环境。
Ansible 直接基于 ssh 协议 和 python,不需要在服务器上安装特定的客户端。一般开发者个人用的 VPS 已经和开发机配置有 ssh 密钥自动登录,直接在开发机上安装个 ansible 就可以了。如果是苹果电脑,执行下 brew install ansible 就完成了。
Ansible 有 3 个配置文件 /etc/ansible/hosts、/etc/ansible/roles、/etc/ansible/ansible.cfg
有command、cron、user、group、file、ping、service、shell、script、yum、setup等几个常用模块。
执行 ansible <host-pattern> [-f forks] [-m module_name] [-a args]  传入 module_name 模块名就可以调用到服务器上对应的各个功能,非常方便。host-pattern 还可以按分组或者单台服务器去执行命令。
当然初次尝试也不是一帆风顺的,我把遇到的一些问题记录在语雀上了,遇到问题的话,可以参考下看有没有相同的问题:https://www.yuque.com/wuwb/ops/ansible
具体如何使用,本文就不展开讲了,官方文档,或者社区有很多教程,ansible 比较简单,看下就差不多了。

普吉岛旅游小记

理想是每年能去一次长途旅行的,这样活到70岁也不过去了40来个地方。40个地方,和那么多国家,那么多美丽的地方比,有些微不足道。只能加倍努力赚更多的钱啦。
去年和前年,借着公司的 outing,去了柬埔寨和日本。去年和今年,公司业绩不好,原本的长途旅行换成了郊游,去年去了舟山。而今年还没决定去哪里,公司补贴已经从 3500 元砍到了 1000 元。这么少的补贴下,那些不愿意自己出打钱的同事都选择了杭州周边的郊游。最多的是选择去千岛湖希尔顿酒店躺尸。
我对公司的Outing已经不报什么希望了,所以自力更生筹划了自己的长途旅游。
本来以为出去玩还是挺麻烦的,自己这样的流程走下来,发现真的是非常方便。
1、淘宝办理签证,加急,318 元一个人,另外送一张有效期 7 天的泰国手机卡。正好可以用来在境外打电话上网。
2、深圳航空官网预定机票。临时决定的出游,没有提前预定便宜的机票,预定的时候机票已经没有多少折扣了。为了便宜几块钱,选择了转机票,到广州转机。另外两张票一起定竟然比分开分别定要贵。还好我机智看出来其中的差别,选择了分开预定,省下了 200 块钱。
3、携程预订酒店,出去玩 8 天,定了 7 晚的酒店。第一天到酒店是凌晨 2 点多,12点就要退房了,就几个小时,也要三百多块钱,感觉非常不值,但是这么晚也没地方去,只能先定了。最终那天由于飞机晚点等原因,到酒店时候将近 4 点钟,那个时间点,要是每个床睡一觉,笔者这样的老年人也是受不了。
4、马蜂窝预定游玩项目。预定了个皮皮岛浮潜一日游。
5、预定机场接机。普吉岛机场到酒店还是有些距离的,人生地不熟,到站后再找出租车的话比较麻烦,而且是半夜的飞机,查了看机场已经没有合法营运的出租车了,一些黑车价格非常高。提前预定一个接机可以省很多事情。在马蜂窝预定皮皮岛项目的时候送了个加价购的权益,加 18 元送接机服务。顺手加购了一个。接机的是一辆小轿车,不限人数和行李,只要塞得下都可以。
准备的就这些啦。行李什么的就比较简单了
1、穿。泳装、夏装、帽子、雨伞,防嗮手臂护套,防晒围巾,拖鞋。
2、清洁。沐浴露、洗发露、防晒霜、老婆的各种化妆品、防蚊液。
3、吃。坐车坐飞机酒店吃的零食等。
4、手机充电器充电宝。
普吉岛玩的总体上是非常悠闲的。就是沙滩,逛街,夜市,游泳,浮潜。没有像去柬埔寨那要可以看到很多壮观靓丽的景色,没有像日本一样琳琅满目的商品。体验是比较平平,和国内的三亚也差不多。
一天的行程基本是睡到差不多自然醒,下楼吃酒店最后一班早餐。然后出门小镇里逛下一只逛到沙滩。蹚一会儿海水,再回镇上逛一会儿,吃午饭。接着再逛一会儿。等到脚差不多有点累了,就回酒店躺一会儿,然后下楼酒店泳池游泳。游泳完出门吃完饭。吃完逛夜市。差不多就是这样,非常悠闲。
下次去旅游的话,肯定不会再选择这种海边度假的方式了。虽然非常轻松,但笔者这样喜欢新鲜的事物的人来说,这样的游玩显得比较单调。

position sticky 失效问题

写了个图墙组件,其中用到了 sticky 做滚动固定。测试发现部分使用情况下会失效。搜了下,看到下面情况会导致 sticky 失效:
1、具有 sticky 属性的元素,其父级高度必须大于sticky元素的高度。
2、sticky 元素的底部,不能和父级底部重叠。
3、sticky 元素的父级不能含有 overflow:hidden 和 overflow:auto 属性。
4、必须具有 top,或 bottom 属性。
对应排查后,发现父元素上使用了 overflow:hidden,去除后问题修复了。

设置 debian 服务器虚拟内存

服务器虚拟内存只有 512M,安装 X 服务的时候提示虚拟内存太小了,搜了把,找到如下设置调整虚拟内存大小的方法:
cd /var
touch swap.img # 创建虚拟内存文件
chmod 600 swap.img # 设置权限
dd if=/dev/zero of=/var/swap.img bs=1024k count=1000
mkswap /var/swap.img # 设置文件为虚拟内存
swapon /var/swap.img # 启用虚拟内存
free # 释放内存
echo "/var/swap.img none swap sw 0 0" >> /etc/fstab # 设置启动自动加载

通过 rel=noopener 属性进行性能优化

当你通过打开新标签的方式跳转页面时,最好给 a 标签加上 rel="noopener" 属性,特别是在调往其他域的页面的时候。
代码如下:
<a href="http://example.com" target="_blank" rel="noopener">
Example site
</a>
从安全上看:
如果没有添加这个属性,那么新页面可以通过 window.opener 变量访问原来的页面,虽然受限于跨域安全机制,新页面不能读取老页面信息,但是新页面可以通过 window.opener.location = newURL 来修改老页面连接,让其跳转到其他页面。
从性能上看:
另外一个重要的问题是,如果仅仅通过 target="_blank" 标签从新标签打开页面。新老页面将运行在同一个进程上,如果新老页面上有运行大量计算任务的脚本时,新老页面的性能都会受到影响。
还有要注意的点,在老的浏览器中,rel=noreferrer 除了禁用 window.opener 外,还一并禁用了 HTTP 头部 的 Referer 属性,会对页面打点等功能造成影响,可以通过

var otherWindow = window.open();
otherWindow.opener = null;
otherWindow.location = url;

的方式进行规避。但是这样的话又会引入新的问题。这个方法在 safari 下会因为弹出机制被拦截。
参考文章:
https://developers.google.com/web/tools/lighthouse/audits/noopener?hl=zh-cn
https://jakearchibald.com/2016/performance-benefits-of-rel-noopener/
https://mathiasbynens.github.io/rel-noopener/#hax
http://lists.w3.org/Archives/Public/public-whatwg-archive/2015Jan/0002.html
http://www.jdon.com/idea/target-blank.html
https://my.oschina.net/dawd/blog/816023

webpack 3 脚手架 tapable 依赖错误问题修复

负责公司 vue 项目脚手架维护,有段时间没改过脚手架,最近对脚手架进行依赖升级,防止依赖腐化导致后面升级维护困难。
结果发现项目直接跑不起来了。排查后发现遇到了 webpack 升级 4 后升级了依赖的 tapable 组件的版本,并且和老版本不兼容,而 webpack 3 中依赖 tapable 没有指定版本号,导致依赖 webpack 3 的很多项目都跪了的问题。强制指定 tapable 版本号到 0.2.5 后问题修复。

前端用户行为打点方案

背景
网站访问量大了以后,运营希望收集部分用户行为记录,用来分析预测用户偏好,进行针对性的优化。
方案
1、先要确定要收集哪些行为
根据我们公司的情况,这里确定了要收集用户鼠标点击,手指移动起始位置,和表单获取焦点的行为。除此之外,还有用户键盘输入等行为可以记录,可以选择性的增加。
2、确定上报的方式
上报是通过将行为数据提交到一个后端接口进行记录。为了避免实时的提交数据,造成无必要的性能损耗,可以将行为数据存储到一个列表中,每隔一段时间,从列表中获取一定数量的数据进行提交。
用户行为数据可能会比较多,选择间隔时间提交固定数量的行为数据后,可能会有部分数据在用户离开页面之前,无法有效的提交到后端。这时候,需要在用户离开页面之前,将剩下未提交的数据全部都一起提交了。但是有个问题,用户离开页面的时候,如果再触发 ajax 去提交数据,ajax 很大概率会被中断,导致数据提交失败。所以这里采用的方案是,在 window 的 unload 事件里把剩下的行为数据存储到 localStorage 之中。然后再访问下一个页面,或者下次访问同域的页面的时候,在用户第一次触发点击行为的时候合并 localStorage 里的数据一次性提交到后端。
不过存放到 localStorage 的方式也无法保障能提交所有的数据。在用户关闭浏览器 tab,或者浏览器 crash 的时候,window 的 unload 是不会触发的。并且不是所有浏览器都支持 window unload 事件。支持请见 http://w3help.org/zh-cn/causes/SD9026
为了通用性,提交数据可以使用跨域支持的 new Image() 方式
3、确定数据存储格式
前面提到要将数据存储到列表中,所以需要确定下数据存储格式。
点击和移动需要记录点击事件的 screenX, screenY, pageX, pageY 还有时间和事件名。
基本是这样的,逻辑确定好后,实现也比较简单了,实际写下来 200 行左右的代码。

IOS 模拟器命令行使用方法

背景
在公司写完页面之后要先自己测试页面的兼容性,作为非测试的同学,手头没有那么多的测试机可以用,所以很多情况下需要使用模拟器进行测试。
默认情况下使用 IOS 模拟器比较麻烦,在使用模拟器之前
1、首先需要安装 Xcode 编辑器,模拟器是作为 xcode 的一个子组件出现的。
2、装好后,打开 Xcode,在首选项里选择安装模拟器。
安装好模拟器后,通过下面的命令进行启动调试
1、查看设备列表 simctl list devices 命令
2、启动模拟器, open -a Simulator –args -CurrentDeviceUDID $设备ID
3、安装程序包 simctl install booted $程序安装包绝对路径
4、启动程序 simctl launch booted $程序包名
5、打开需要调试的页面, simctl openurl booted $唤起短链
6、电脑上打开 safari 浏览器,点击开发目中的模拟器,选择模拟器中的运行的页面
7、关闭模拟器,killall Simulator 命令
下面是模拟器中一些常用的快捷:
1、回到桌面:⌘+⇧+H
2、模拟器缩放:⌘+1、⌘+2、⌘+3、⌘+4、⌘+5,对应缩放100%、75%、50%、33%、25% 大小
3、锁屏&解锁屏:⌘+L
另外一个注意的要点是,模拟器中的代理与 mac 系统的代理是同步的。
代码溜的同学,可以对这些命令做一个封装,比如用前端常用的 nodejs 来处理。
写起来也比较简单,主要使用 commander 和 prompt 包,实现命令行和交互功能。