分类: 开发

  • 温州地区的丧礼经济

    很多人说死也死不起。
    外婆前段时间去世了,享年 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 包,实现命令行和交互功能。