如何将博客升级http2

最近有在学习 http2 的相关知识,就想着顺便把自己的博客升级一下,其实升级后并不会带来很大的性能提升,不过当做学习用的个人实践也是不错的。

怎么升级 http2 ?

升级 https

升级的过程其实是很简单,但是因为 http2 必须需要 https 的支持,所以要想用到 http2 ,必须要先使用 https, 使用 https 的话就需要生成证书,所以其实这个步骤才是升级过程中略显复杂的一环。

生成 https 证书

证书的话,因为只是个简单的个人网站,在安全性上也并没有太大的要求,所以证书的颁发机构采用大家都喜欢用的开源证书颁发机构 let’s encrypt 就行了, 关于这个证书的生成就使用大家都喜欢的 cerbot 生成工具 就行了。推荐使用官网推荐的 cerbot-auto 脚本的方式来生成证书,你需要做的就是填写邮箱和你的域名之类的操作。

这个生成的过程会有一些小坑,我在下面的 踩坑总结: 使用 cerbot-auto 生成 Let’s Encrypt 证书遇到的问题 有提及。生成之后,可以使用 ./certbot-auto certificates 查看你生成的证书的位置。另外有一点需要注意的是,通过上面的这种方式生成的证书的有效期是 90 天,所以到时间了要记得自己去重新生成。

配置 nginx

因为我使用的是 Nginx 服务器,所以只需要简单的配置就可以使用 https , 配置的内容大致如下:

server {
    listen 443 ssl;    
    listen [::]:443 ssl ipv6only=on;
    server_name www.chengpengfei.com;
    ssl_certificate /etc/letsencrypt/live/www.chengpengfei.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/www.chengpengfei.com/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/www.chengpengfei.com/chain.pem;
}

一般情况下,再配置好 web 目录, 重启 Nginx 就可以了就可以看到效果了。但是因为我使用了 docker ,所以还不行,请接着往下看。

在 docker 中使用 Nginx

因为我是直接在 docker 中使用的 Nginx,我需要让我的证书以及我定义的 Nginx 配置对 docker 容器可见。在这里我一开始的思路有两个,第一是通过 Dockerfile 来指定 volume ,然后生成一个新的镜像,并构建容器,一种是直接使用 docker run -v 的形式去构建容器。

表面上看起来第一种的方式更为优雅些,但是在我的实践中发现,第一种方式并不可行,无法挂载到指定的文件路径,查阅了相关资料发现,当你要重新构建一个docker的image时,在Dockerfile 里指定的 volume 是无效的,因为 image 应是纯净的,不包含有指定路径的。 所以最后我选择了第二种方式,具体的命令大致如下:

docker run -d \
-p 80:80 \
-p 443:443 \
--name nginx-blog \
-v "$PWD/html":/usr/share/nginx/html \
-v /var/www/blog/:/var/www/blog/ \
-v /var/www/my_resume/:/var/www/my_resume/ \
-v /home/blog_nginx/:/etc/nginx/ \
nginx

可以看到,直接输入命令的方式可能不是很优雅,但是却有效。不过有一个推荐的做法就是将命令保存到一个脚本文件里面,通过脚本来构建也是一个不错的选择。

容器构建成功,就可以访问网站来查看效果了。下面是我的网站更换的效果,由 不安全 变成了 并非完全安全😂

Imgur

配置 http2

配置 http2 的方式更加简单,直接在 Nginx 的配置里更改就可以了(我用的是最新版的 nginx ,所以是支持 http2 的,别用太老的版本都支持的)。

server {
    listen 443 http2 ssl;
    listen [::]:443 http2 ssl ipv6only=on;
    ……
}

之后重新构建一个容器即可,来看个开启了 http2 之后的效果。

这个是之前的:

这个是 使用 http2 之后的:

踩坑总结: 使用 cerbot-auto 生成 Let’s Encrypt 证书遇到的问题

1. 提示 Problems with Python virtual environment ?

官方给出的解释是 低内存的机器会出现类似的问题 ,但是按照官方给出的 创建一个临时 swap 文件的方案并没有解决。后来查阅资料确认是国内 Python mirror 的问题,因为我用的是阿里云提供的 vps ,但是阿里的Python mirror 同步不及时,所以造成安装失败。只需要修改 /root/.pip/pip.conf 的配置即可,可以改为其他可用的 mirror (推荐清华大学的 pypi 镜像 — https://pypi.tuna.tsinghua.edu.cn/simple) ,也可以直接注释掉使用原版的。

2.生成证书时,使用了 docker 部署 nginx, 但是本机并未安装 nginx ?

cerbot 给出了两种方案来生成证书,一种是 webroot ,这种也是普遍推荐的一种,因为不需要停止你的服务,只需要在 nginx 配置里做些更改即可,生成方式可以看这里,但是因为我用的是 docker 部署,本机并没有安装 nginx ,所以这种方式对我来说并不适用。另一种是 standalone ,这种方式会在本机临时建立一个服务器,但是会默认占用你的 80 端口或者 443 端口,所以这种方式需要先解除掉 80 或者 443 端口的占用, 使用 docker 的话用这种方式就很方便,直接 docker stop containerId 停止掉容器即可,等到证书生成完成,重新启动即可。

但这里有一个大坑是:尽量不要去手动的 kill 掉 80 端口的 pid ,因为关掉容易,但是不熟悉 Linux 的话,开启就会比较麻烦,我的 阿里云 ECS 是 centos 7 的版本,但是 centos 6 和 7开启端口的方式还是有很大的差异的。

以上!

换新电脑不用慌-轻松搞定vscode配置

换新电脑不用慌-轻松搞定 vscode 配置

写这个总结的目的是因为发现很多同学并不是很了解 vscode 的隐藏功能。因为之前自己经常换电脑,换出心得了已经,遂总结下。

相信大家都曾有更换新电脑的经历,换新电脑肯定是件开心的事,但是每次换新电脑后复杂的软件配置和数据同步却是件很令人头痛的事情。

同样的,作为一名前端工程师,vscode 可能是大部分开发者的灵魂伴侣,但是这个伴侣在有些事情上却并不是那么如意,灵活的配置和丰富的插件生态是 vscode 受到大家喜爱的根本,但是在更换新电脑的时候, vscode 对于插件的管理的备份却有点捉襟见肘(如果 vscode 可以集成微软的账号同步服务那也是个不错的选择,但是作为一个开源软件,微软并不会这么干)。所幸的是 vscode 是基于配置的,所以你可以通过拷贝你的插件配置文件来达到新电脑同步的目的,但是这样一样存在配置丢失的风险,并且操作也略显复杂。不过显然会有另外一种比较优雅的方式,那就是使用 Setting Sync 来同步配置。

Setting Sync

Setting Sync 也是 vscode 的一个插件,他利用 githubgist 作为你的配置文件的存储载体,所以理论上只要你的 github 账户不丢失,你的配置文件就不会丢失。同时值得一提的就是,你的这些配置文件还具有版本管理的功能,所以你对配置文件所做的变更都一清二楚。

废话就不多说了,现在来说怎么使用吧。

前置条件

首先,你得有一个 github 的账户(别说你没有),如果你已经有了账户,那么 github 就会自动给你创建 gist 账户,简单的理解就是 github 之于 项目代码 犹如 gist 之于 代码段。你可以随意的新建你的代码段,比如这是 我的代码段

如何使用?

因为每个人的使用方式都不一样的,所以下面涉及的操作我尽量不使用 快捷键 来提示。

  1. 安装 Setting Sync 插件

    直接在 vscode 插件面板输入 Setting Sync下载安装即可。

  2. 新建一个 token

    为什么需要 token ,因为你需要 授权 Setting Sync ,这个插件可以访问并创建 gist。

    安装完成之后,需要新建一个 token ,然后勾选的权限只给一个 gist 就可以了,generate token 之后复制生成的 token。

    生成的 token 不用担心丢失,如果不记得了,重新生成一个即可,但是不要把 token 随便给别人。

  3. 新建或者配置 gist 文件

    你可以调出 vscode 的快捷命令窗口 cmd + shift + P,键入 sync 来进行 上传设置 操作。需要注意与 git 的命令区分

如果你是第一次使用,那么会提示你输入上个步骤生成的 token ,粘贴回车,第一次上传设置会自动新增一个 gist 的配置,生成成功之后会有成功的提示:

生成的 gistID 一定要记住,最好可以保存下来,如果实在记不住,可以去个人的 gist 列表里开启双目扫描查找。例如这是 我的 gist 列表

  1. 同步/下载 配置

    以上的步骤做完,就代表你的 Setting Sync 可以正常使用了,通常,你只需要 调出快捷命令窗口,然后输入 sync 就可以查看并使用 Setting Sync 的同步或者下载配置了(使用快捷键会更方便)。

    即使你更换了新电脑,只要你记得第三个步骤里保存的 token,同步也是非常方便的(不记得也没关系,重新生成一个就好)。你可以通过 cmd + shift + P –> sync 高级选项 –> 编辑本地扩展设置 ,将你的 token 粘贴在配置文件里,然后再执行同步/下载的配置即可,具体如下:

    {
      "ignoreUploadFiles": ["projects.json", "projects_cache_vscode.json", "projects_cache_git.json", "projects_cache_svn.json", "gpm_projects.json", "gpm-recentItems.json"],
      "ignoreUploadFolders": ["workspaceStorage"],
      "ignoreExtensions": [],
      "replaceCodeSettings": {},
      "gistDescription": "Visual Studio Code Settings Sync Gist",
      "version": 300,
      "token": "put token in there",
      "downloadPublicGist": true,
      "supportedFileExtensions": ["json", "code-snippets"],
      "openTokenLink": true
    }
    
    
  1. 新建用户配置的gist

    因为 Setting Sync 是针对插件的同步,所以你还可以将你的用户配置文件放到 gist 上,等到更换新电脑的时候取回即可。

插个嘴

其实 vscode 还有一个很好用但是很少人用的功能就是工作区啊,感觉我 一、二十个项目代码没有这个会疯掉的。当然这个功能好像所有的 IDE 都有,不过为啥就是很少人用呢。

以上!

换新电脑不用慌-轻松搞定 vscode 配置

写这个总结的目的是因为发现很多同学并不是很了解 vscode 的隐藏功能。因为之前自己经常换电脑,换出心得了已经,遂总结下。

相信大家都曾有更换新电脑的经历,换新电脑肯定是件开心的事,但是每次换新电脑后复杂的软件配置和数据同步却是件很令人头痛的事情。

同样的,作为一名前端工程师,vscode 可能是大部分开发者的灵魂伴侣,但是这个伴侣在有些事情上却并不是那么如意,灵活的配置和丰富的插件生态是 vscode 受到大家喜爱的根本,但是在更换新电脑的时候, vscode 对于插件的管理的备份却有点捉襟见肘(如果 vscode 可以集成微软的账号同步服务那也是个不错的选择,但是作为一个开源软件,微软并不会这么干)。所幸的是 vscode 是基于配置的,所以你可以通过拷贝你的插件配置文件来达到新电脑同步的目的,但是这样一样存在配置丢失的风险,并且操作也略显复杂。不过显然会有另外一种比较优雅的方式,那就是使用 Setting Sync 来同步配置。

Setting Sync

Setting Sync 也是 vscode 的一个插件,他利用 githubgist 作为你的配置文件的存储载体,所以理论上只要你的 github 账户不丢失,你的配置文件就不会丢失。同时值得一提的就是,你的这些配置文件还具有版本管理的功能,所以你对配置文件所做的变更都一清二楚。

废话就不多说了,现在来说怎么使用吧。

前置条件

首先,你得有一个 github 的账户(别说你没有),如果你已经有了账户,那么 github 就会自动给你创建 gist 账户,简单的理解就是 github 之于 项目代码 犹如 gist 之于 代码段。你可以随意的新建你的代码段,比如这是 我的代码段

如何使用?

因为每个人的使用方式都不一样的,所以下面涉及的操作我尽量不使用 快捷键 来提示。

  1. 安装 Setting Sync 插件

    直接在 vscode 插件面板输入 Setting Sync下载安装即可。

  2. 新建一个 token

    为什么需要 token ,因为你需要 授权 Setting Sync ,这个插件可以访问并创建 gist。

    安装完成之后,需要新建一个 token ,然后勾选的权限只给一个 gist 就可以了,generate token 之后复制生成的 token。

    生成的 token 不用担心丢失,如果不记得了,重新生成一个即可,但是不要把 token 随便给别人。

  3. 新建或者配置 gist 文件

    你可以调出 vscode 的快捷命令窗口 cmd + shift + P,键入 sync 来进行 上传设置 操作。需要注意与 git 的命令区分

如果你是第一次使用,那么会提示你输入上个步骤生成的 token ,粘贴回车,第一次上传设置会自动新增一个 gist 的配置,生成成功之后会有成功的提示:

生成的 gistID 一定要记住,最好可以保存下来,如果实在记不住,可以去个人的 gist 列表里开启双目扫描查找。例如这是 我的 gist 列表

  1. 同步/下载 配置

    以上的步骤做完,就代表你的 Setting Sync 可以正常使用了,通常,你只需要 调出快捷命令窗口,然后输入 sync 就可以查看并使用 Setting Sync 的同步或者下载配置了(使用快捷键会更方便)。

    即使你更换了新电脑,只要你记得第三个步骤里保存的 token,同步也是非常方便的(不记得也没关系,重新生成一个就好)。你可以通过 cmd + shift + P –> sync 高级选项 –> 编辑本地扩展设置 ,将你的 token 粘贴在配置文件里,然后再执行同步/下载的配置即可,具体如下:

    {
      "ignoreUploadFiles": ["projects.json", "projects_cache_vscode.json", "projects_cache_git.json", "projects_cache_svn.json", "gpm_projects.json", "gpm-recentItems.json"],
      "ignoreUploadFolders": ["workspaceStorage"],
      "ignoreExtensions": [],
      "replaceCodeSettings": {},
      "gistDescription": "Visual Studio Code Settings Sync Gist",
      "version": 300,
      "token": "put token in there",
      "downloadPublicGist": true,
      "supportedFileExtensions": ["json", "code-snippets"],
      "openTokenLink": true
    }
    
    
  1. 新建用户配置的gist

    因为 Setting Sync 是针对插件的同步,所以你还可以将你的用户配置文件放到 gist 上,等到更换新电脑的时候取回即可。

插个嘴

其实 vscode 还有一个很好用但是很少人用的功能就是工作区啊,感觉我 一、二十个项目代码没有这个会疯掉的。当然这个功能好像所有的 IDE 都有,不过为啥就是很少人用呢。

以上!

git开发规范

在团队开发中,团队协作是比埋头编程更值得去深入的东西。一套好的开发流程(规范)可以避免很多不必要的麻烦,现在大部分的公司都使用 git 来进行代码管理,这里结合个人工作中的所得以及个人使用中经常遇到的坑,做个简单的总结。

对于 git 的编程规范来说,其实 git-flow 就是一套好的编程规范,它对工作中的 git 事务性的操作做了一个封装,在团队还不具有一定的规模化的情况下,使用 git-flow 是一个不错的选择。如关于常用分支的定义,就可以参考 git-flow 的思想:

master 只能用来包括产品代码。你不能直接工作在这个 master 分支上,而是在其他指定的,独立的特性分支中(这方面我们会马上谈到)。不直接提交改动到 master 分支上也是很多工作流程的一个共同的规则。

develop 是你进行任何新的开发的基础分支。当你开始一个新的功能分支时,它将是 开发 的基础。另外,该分支也汇集所有已经完成的功能,并等待被整合到 master 分支中。

但是当团队有了一定的规模化的时候,要求所有成员再去学习 git-flow 的使用,这样的成本也是很高的,所以这时候我们倾向于使用原生的命令来操作。

下面是我总结的在工作常用的场景下,对 git 的一些操作。

开发一个新的功能时

比较好的开发规范就是,当你开发一个新的需求时,应该按照下面的流程进行开发:

第一步:基于 develop 新建一个人分支

git checkout -b "new-branch"

第二步:同步个人分支至远程仓库

git push --set-upstream origin "new-branch"

使用 --set-upstream 的目的是跟踪远程分支。后面执行命令时可以省掉指定源的操作,如:

git pull origin test => git pull 
git push origin test => git push

第三步:功能开发完成后,发送 merge 请求

master 分支和 develop 分支应该被保护,只有稳定的版本才可以允许合并操作。合并的方式因团队而异,常规的流程是找到具有develop 分支开发权限的成员,执行:

git checkout develop
git merge "new-branch"

如果你的团队使用 gitlab 进行管理,就可以发送一个由 new-branchdevelop 分支的 merge request

如何写好一个 git comment

不应当出现语义模糊或毫无意义的 comment 描述。每个团队都要有自己的 comment 规范,当然也可以直接用大家都已经接受了的

feat:新功能(feature)
fix:修补bug
docs:文档(documentation)
style: 格式(不影响代码运行的变动)
rebuild:重构(即不是新增功能,也不是修改bug的代码变动)
test:增加测试
chore:构建过程或辅助工具的变动
config: 配置

example: git commit -m "[feat] 新功能"

以上总结自: 阮一峰: Commit message 和 Change log 编写指南]

如果你很皮,想加个开源库那样 comment 的表情,你可以执行:

git commit -m ':apple: i have a apple'

苹果就出来了,更多表情代码可以点 这里

做一个 hotfix 时

Hotfix 是为了应对已上线的产品代码出现的问题出现的紧急修复,所以和开发新功能的流程略有不同。 Hotfix 显而易见是基于 master 分支的。但实际的操作步骤和上面是一样的,这里不再赘述。

分支切换遇到问题时

这里的问题往往是:使用 checkout 切换分支时遇到的冲突,或者是 分支切换的时候遇到尚未添加至暂存区的代码。对于这两个问题,前者往往很容易解决,解决掉冲突,重新 checkout 就好。后者的话就有些头疼,假如你的源分支已经有了自己大量的神仙逻辑代码,所以你不想使用 git add 将这些文件添加到暂存区,你也不想用 git reset 的方式去处理,这时相对比较好的方式就是使用下面的方式:

git stash -u    暂存,区分与 git add 的暂存
git stash list    列出暂存内容
git stash pop 取回暂存

这样就可以畅快自如的切换分支了。很显然,使用 git satge 的方式条理也会更加清晰些。

怎么去处理构建任务时

这个场景可能与这篇文章不是那么符合。

如上所说,如果使用了 gitlab 来管理团队代码,gitlab 所集成的 CI/CD 也是很不错的 devops 的选择,目前的项目团队也在使用,使用工程化的 gitlab.ci 配置文件,来自动化执行测试、编译、构建、部署等一系列的工作,原来部署过程中繁杂的工作变成了 如何去写好一个 gitlab.ci 文件?

如果没有使用 gitlab, 同样的,使用 github + travis.ci 也是比较流行的开源库的实现方案。

另外,在之前的工作中,也有使用过 TFS 的方式,这个是微软提供的服务,国内用的比较少(收费,价格还挺贵),国外很流行。这个的构建配置比起上面两个要复杂些,但是使用过程还是很舒畅的。

以上。

go入门踩坑及环境配置问题相关

go 入门踩坑及环境配置问题相关

学习网站

1.安装

直接去官网下载 go的包,下载后解压安装。

配置 gopath

gopath 可以简单理解为你的工作目录,可以自己定义位置。

echo 'export GOPATH=$HOME/go
PATH=$PATH:$HOME/.local/bin:$HOME/bin:$GOPATH/bin' >> ~/.zshrc

使配置生效

source ~/.zshrc

使用 go env 查看配置是否生效

一般目录是下面的这个样子就是对的,GOPATH="/Users/xxx/go",xxx 是当前用户名称。

2.配置 vscode 开发环境

配置 debug 环境

使用 vscode 调试 go 的话,需要安装一个go的第三方依赖:delve,可以使用下面的命令安装:

go get github.com/derekparker/delve/cmd/dlv

安装后就可以在 debug 面板添加 debug 配置文件调试了,示例如下:

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Launch",
      "type": "go",
      "request": "launch",
      "mode": "debug",
      "remotePath": "",
      "port": 7000,
      "host": "0.0.0.0",
      "program": "${workspaceRoot}",
      "env": {
        "GOPATH": "/Users/michael/Documents/go"
      },
      "args": [],
      "showLog": true
    }
  ]
}

如果启动后报下面的错误:

lldb-server needs to be installed in $PATH

可以采用下面的方法解决:

执行 xcode-select --install 解决,原因未知

配置开发语法提示

随便建一个 .go 的文件用 vscode 打开,会自动提示安装 go 的相关插件,但是一般会有一些插件安装失败,一般都是下面的几个:

Installing github.com/nsf/gocode SUCCEEDED
Installing github.com/uudashr/gopkgs/cmd/gopkgs SUCCEEDED
Installing github.com/ramya-rao-a/go-outline FAILED
Installing github.com/acroca/go-symbols FAILED
Installing golang.org/x/tools/cmd/guru FAILED
Installing golang.org/x/tools/cmd/gorename FAILED
Installing github.com/rogpeppe/godef SUCCEEDED
Installing github.com/sqs/goreturns FAILED
Installing github.com/golang/lint/golint FAILED

可以参考这个修改办法: 让你成功安装vscode中go的相关插件 修改。

注意(如果没遇到就忽略)

但是在最近的测试过程中,发现 golint 依然会报安装失败,原因是 https://github.com/golang/tools.git 这个仓库里不包含这个 lint 的工具了,所以我们需要单独的安装这个工具包。

通过查询此 issue: Where did golint go? 解决

mkdir -p $GOPATH/src/golang.org/x \
  && git clone https://github.com/golang/lint.git $GOPATH/src/golang.org/x/lint \
  && go get -u golang.org/x/lint/golint

上述命令可能会遇到文件夹已存在的问题,可以直接跳过第一步文件夹创建的操作就可以。

3.使用一些包管理工具来管理第三方包

因为 go 官方没有提供自己的包管理机制,所以包的管理是个坑。市面上各家提供的包管理都是各玩各的,但也有大家用的多的,关于 go 比较常用的包管理工具,有以下几个推荐的:

目前项目中正在使用 glide, 使用 glide 管理包的话,会生成一个 venndor 的目录,可以理解为一个跟随项目的局部 gopath, 这样子在引入第三方包的时候,实际的读取顺序就是: 局部 vendor -> 全局 gopath 。

另外,在使用第三方包的时候,有些第三方包会托管在 google 的服务上,因为墙的原因,我们没法方便的拿下来,甚至你搭了梯子还是很蛋疼。所以我们只能自己配置镜像源了,因为我们用的是 glide ,所以就拿 glide 的来做例子。

我们只需要使用 glide mirror set 命令来设置镜像配置(参考):

glide mirror set https://golang.org/x/sys https://github.com/golang/sys

后续待补……

如何用node去搭建一个项目发布系统

如何用 node 去搭建一个项目发布系统

最近在处理一个静态资源系统的发布平台,大概意思就是对于多环境的静态项目,希望可以通过系统控制来改变过去繁琐的项目的部署方式,同时对于静态页面引入的一些接口请求则通过全局的网关来控制来处理跨域。权衡利弊之后,采用最熟悉的 node 来实现这么一个功能,后端框架选用 koa@2 ,前端使用 react 来做管理界面。

项目的部署作为软件开发过程中的最后一环,往往也是最容易出问题的地方。所以,一套优雅的部署方式以及一套完善的部署监管对于项目的稳定运行也有着至关重要的作用。关于部署,有很多已经有一定规模的第三方管理平台,比如 jerkins、travis,以及微软的 TFS 等,都已经是有一套完善的机制,同时也有庞大的用户群。不过另一方面,技术服务于业务需求,所以各家公司因为需求的不一致,对以上产品的使用难以做到合适的定制化,也会选择自研一些项目发布系统。轮子该造还是得造的嘛!

有关项目发布系统,本质上是设计大于实现的,但关于系统的设计我也并不能聊什么,基本大同小异,我就聊下实现的过程的遇到的一些问题好了。项目最重要的有两个部分,一个就是全局的网关控制,另一个就是项目的部署。我就从这两个入手好了。

网关控制

在过去的一些部署方案里,对于一些轻量的静态页面,如果涉及到一些页面的跨域请求,我们通常的做法是做一层 Nginx 代理,这样做的后果是,当项目的发布一旦达到一定数量级,那么就要不停的去更新 Nginx 的配置,同时还要重启,这样的方式的确太不优雅了。

所以为了更方便的管理,我们使用一个开源的 API gateway 工具来控制。如果不了解 kong 是什么,可以先来看一下这篇文章: 微服务 api GateWay 工具: kong

kong 的使用很简单,没有什么特别大的问题,但有两点需要注意,一个是 kong 的迭代目前有些快,我使用的时候是 0.13,但是现在看已经是 0.14 了,所以可能会有更新的变化需要做兼容处理,另一个就是,kong 提供的 admin 接口在于 koa 实践的过程中,并不是那么契合,特别是在异常捕捉这里,你需要对 kong 的异常行为在 koa 里面单独处理。

项目的部署

文件的解压

对于上传的部署文件都是以压缩包的形式存在的,所以我们需要对项目文件进行解压,这里在我的实践我调研了三个库

最后我选择使用了第三种方案,过程中遇到最大的问题是,如果你要使用以 stream 的方式解压文件,这种效率最快,但当你使用 fs.createReadStream 去创建一个只读流,然后又使用 fs.createWriteStream 创建了多个写入流,这时候你无法无获取最后一个写入流结束时的状态,这个问题还是困扰了我,最后我只能用这种方式,使用 setTimeout() 函数来“保证”在写入完成之后才可以进行接下来的操作,大致的代码如下:

const compressingStream = async (ctx, source, target, options) => {
  return new Promise((resolve, reject) => {
    let isExist = fs.existsSync(source);
    if (!isExist) {
      reject(new Error('file not exist'))
      return;
    }
    let targetExist = fs.existsSync(target);
    if (!targetExist) {
      mkdirp.sync(target);
    }
    fs.createReadStream(source)
      .on('error', function (error) {
        reject(error)
      })
      .pipe(new compressing.zip.UncompressStream())
      .on('error', function (error) {
        reject(error)
      })
      .on("finish", function() {
        resolve("ok");
      })
      .on("entry", function(header, stream, next) {
        stream.on("end",next);
        <!--不是一个好的解决方案,设置延时函数-->
        setTimeout(() => {
          resolve('ok');
        }, 2000);


        let fileName = header.name;
        let type = header.type;
        if(options && !/^__MACOSX\//.test(fileName)){
          fileName = path.join(options.prefix, fileName);
        }

        let reg = /^\d|\s/;
        if (reg.test(fileName)) {
          reject(new Error(fileName + "文件名称不合法,不允许空格或者数字开头"));
        }
        if (!/^__MACOSX\//.test(fileName) && type === "file") {
          stream.pipe(fs.createWriteStream(path.join(target, fileName)));
        } else if (!/^__MACOSX\//.test(fileName) && type === "directory") {
          // directory
          mkdirp(path.join(target, fileName), err => {
            if (err) return reject(err);
            stream.resume();
          });
        }
      });
  });
};

CDN 的处理

有关于项目的部署,其实初期设想,大部分的资源都是静态资源,对于一些需要体积偏大需要缓存的文件,我们需要上传这些文件去到我们的 CDN 服务。而对于一些不需要做缓存的文件,比如项目的入口文件,我们就可以同步到我们的资源机里面。

对于第一点,我使用的是开源的 node-ftp-client,这个并没有什么操作难度,唯一需要注意的就是 node-ftp-client 的方法是异步的,所以为了兼容 koa@2 的 async/await 写法以及保证程序的执行顺序,需要对 node-ftp-client 的方法进行一次 promise 化。代码如下:

const test = async (ctx, source, target) => {

  let ftpConfig = { };
  let options = {
    logging: 'basic'
  };
  let upOption = {
    baseDir: source,
    overwrite: 'none'
  }

  let fct = new FtpClient(ftpConfig, options);
  return new Promise((resolve, reject)=> {
    fct.connect(()=>{
      fct.upload(source, target, upOption, (r) => {
        console.log(source,target,r);
        resolve(r);
      })
    })
  })
}

在docker 中使用 rsync 来进行项目发布

这个大概是耽误时间最多的一个操作。

有关于发布系统最重要的一环——发布源码到目标主机,我使用的是开源的 node-rsync。其实这个库只是对于 linux rsync 命令的一次封装,底层需要操作主机支持 rsync 命令

听起来并不是什么复杂的操作,但是在真正的执行的时候,问题还是挺多的,因为要发布的目标主机是在 docker 内部,当然这不是最大的痛点,最大的痛点是我要操作的服务机器没有外网访问权限。下面就几个遇到的问题做一些分析总结:

1. docker 中不存在 rsync 的命令?

看到这个,你可能会觉得很简单,没有 rsync 的命令,使用 linux 的 apt-get 或者类似的工具安装一下就好了啊。但是如上所说,测试主机是没有外网访问权限的,所以直接安装这种方法是不可行的。

当然,即使没有外网访问权限,对于一些常用的包,我们也有一些类似的镜像源,我们只需要在安装的时候修改一下这个源就可以了,这个应该大部分公司都是一样的。这个方法当然是可行的,但是我还是放弃了。因为项目的迭代性比较高的话,我认为每次都在打包镜像的时候,去修改 docker 内部 apt-get 的源再安装,不是很优雅的方式。所以为了以后更好的执行类似的操作,我采用构建一个 node+rsync 精简的基础镜像,构建方法如下:

第一步,新建 Dockerfile, 这里我用的基础 node 镜像是 node:8-alpine,Dockerfile 内容如下

FROM node:8-alpine
RUN apk add --no-cache rsync

第二步,进入到 Dockerfile 文件目录,本地构建镜像

docker build -t xxxx.com/node-rsync .

第三步,或者部署到内部 docker 镜像服务

部署到内部镜像服务,也可以指定版本,默认为 latest
docker push xxxx.com/node-rsync

第四步,无推送权限的情况下,推送到目标主机(非必需)

docker save -o node-rsync.docker xxx.com/node-rsync   //打包离线docker文件:node-rsync.docker
rsync -cavzP ./xxxx.docker root@host:/path    //使用 rsync 推送到目标主机
docker load -i xxxx.docker   //本地离线安装镜像

2. 在 docker 中使用 rsync 传输公钥密钥的问题

因为使用 rsync 传输需要一次密码认证,所以我们需要对 rsync 做一次免密认证。解决思路就是在构建的镜像里生成本机的公钥,然后将公钥添加到部署主机的 ~/.ssh/authorized_keys 中来实现免密登陆。当然更进一步,如果每次构建镜像的时候都生成一次公钥再添加这样的操作是很冗余的。所以我们可以生成一个通用的公钥密钥,在每次构建的时候只需要复制到镜像内部即可以解决。

以下是详细的构建脚本:

BUILD_TIME=`date "+%Y%m%d%H%M"`
SERVER_HOST=""
SERVER_PATH="/home/web"
CONTAIN_NAME='web'
IMAGE_NAME="xxx.com/web:$BUILD_TIME"
rsync -cavzP --delete-after ./ --exclude-from='.rsync-exclude' $SERVER_HOST:$SERVER_PATH
ssh $SERVER_HOST "\
  cd $SERVER_PATH; \
  echo "删除旧容器";\
  docker stop web;\
  docker rm web; \

  echo "清理过时的测试镜像"; \
  docker images | awk '/^xxx.com\/web[ ]+/ { print $3 }' | xargs docker rmi -f; \

  echo "构建docker镜像 $IMAGE_NAME"; \
  docker build -t $IMAGE_NAME . ;\

  echo "发布docker镜像"; \
  docker push $IMAGE_NAME ;\

  echo "docker start"; \
  docker run -d -p 7777:3000 -e NODE_ENV=test \
  --hostname ubuntu-14 \
  -v /data/package/:/data/package/ \
  -v /home/:/home/ \
  --name=$CONTAIN_NAME $IMAGE_NAME ; \

  echo "生成 .ssh 目录"; \
  docker exec -i $CONTAIN_NAME \
  mkdir -p  ~/.ssh/ ;\
  echo "ok"; \

  echo "复制公钥,为了ssh登陆"; \
  docker exec -i $CONTAIN_NAME \
  cp -rf ./auth/test/* ~/.ssh/ ;\
  echo "ok"; \

  echo "修改权限"; \
  docker exec -i $CONTAIN_NAME \
  chmod 0600 ~/.ssh/id_rsa ;\
  echo "ok"; \

  echo "模拟登陆主机:首次使用rsync登陆主机存在验证合法性的问题"; \
  /usr/bin/expect << EOF

    set timeout -1
    spawn docker exec -it $CONTAIN_NAME ssh $SERVER_HOST ; \
    expect { 
      "*yes/no" { send "yes\\r"; exp_continue}
      "*\#" {exit } ;\
    }

  EOF ;\
  exit; \
  "


echo "\033[40;32m\n"
echo "Sync to Server: $SERVER_HOST"
echo "Build source code path: $SERVER_PATH"
echo "Image: $IMAGE_NAME"
echo "Image deploy success."
echo "\033[0m"


以上

记一次狗血的修bug经历

记一次狗血的修 bug 经历

没什么别的目的,写这篇就是为了提醒自己是个傻子

起因是这样的,在一个风和日丽的一天,测试给我出了这么一个 bug :

hello 啊,这个页面啊,打不开啊,我的 iPad 啊,就是点了不会动了啊,其他的都可以的啊,赶紧看一下啦

在经过漫长的沟通后,我得到了以下信息:

  • ios10 的设备下某页面的登陆点击之后无法跳转。
  • 安卓和 iOS11 的设备是正常的。
  • 没有足够的设备的调试。

其实看到这儿,我的内心还是有点窃喜的。自上次自己解决 ios9浏览器无痕模式下浏览器 localStroage 存储失效 的问题后,我发现找这种类似的兼容性问题的过程还是挺爽的,我其实对我自己发现问题和解决问题的能力是很自信的,所以我想,我又可以秀操作了。

现在我来聊一下我的心路历程:

Q: 首先呢,我以为是前端页面报错!

因为正常的页面无法跳转,我怀疑大概率是 js 的某些逻辑出错,这在兼容性的问题上是经常出现的。但,ios 的设备调试是挺坑爹的,况且还是 ipad ,通过数据线来进行 safari 调试是行不通的,于是只能通过无线代理的方式来调试了。一顿操作,装证书、设代理,发请求,但是页面控制台真的是 空 空 空空如也

并没有 js 报错,看来并不是某些方法兼容性的问题。

又检查了一下请求,看出了一些端倪,竟然请求没有返回值。

Q: 那我觉得,应该是后端接口的问题了!

后端是用 node + koa 写的,我们的接口是这样的,前端发一个登陆请求,然后我们会去验证登陆,如果登陆成功,会在客户端写入唯一登陆的 accesstoken 到cookie中,后续的请求都需要用到这个登陆的 accesstoken。经过我一顿查找,发现了可能是cookie的写入出现了问题:使用 ctx.cookies.set('ss','bb') 并没有生效。

Q: 那么是所有的设备都没有生效吗?

为了不影响测试,我本地起了个服务,然后借用同事的 iOS10 的设备进行测试发现了同样不行(最后证明就是这一环节出了问题,是网络堵塞引起的不行)。当然安卓都是正常的。

于是我自以为是的得出了一个结论:这应该是 ios10 设备独有的一个 cookie 的 bug。

我疯狂的在 github issue 和 Stack Overflow 寻找相关的答案,有提到 写入 cookie 时使用中文字符造成写入失败的、也有说 iOS10 的隐私策略的问题。不过好像都与我的关系不大。

Q: 难道并不是兼容性问题?

我又重新检查了代码,最后经过一段手动测试,发现是 cookie 过期时间设置的问题。问题描述如下: 当我设置过期时间为几个小时的时间时,就不能正常写入 cookie。如果时间大于一天,就可以正常写入,也算勉强解决了。

我下意识的瞄了一下手里的iPad,时间显示是正常的(坑在此,时间是正常,但是日期不是)。我又开始怀疑是时区设置的问题,但是也是不求甚解。

Q: 好吧,估计只能抽空看下源码了……

折腾了这么个半天时间,发现了确实是 cookie 设置的问题,于是通过重新设置cookie的有效时间解决了。虽然算是勉强解决了,但是这个解决办法很让人不爽,因为我还是在迷惑的状态。就在我心如死灰的时候,我无意中打开了 ipad 的日历,发现竟然比实际多一天,但是时间却是一分一秒不差。

WTF!!!难道是日期设置的问题?

最后,测试了几次,果然是日期设置的问题。

最后,bug 终于解决了

其实问题出现的原因很简单,和什么兼容性一点关系都没有,纯粹是自己的意淫。其实就是当我设置 cookie 为一天之内的过期时间的情况,因为设备的日期时间刚好大于实际的一天,那造成的情况就是 cookie 刚写入了就因为过期被浏览器清除掉了。

真是一次令人捉急的经历!

微服务 api GateWay 工具: kong

API Gateway 工具 kong

项目需要,需要对微服务化下的 API 做统一管理,查阅相关资料,给出 kong + cassandra 的实现方案,中间也涉及到微服务以及 API Gateway 的相关知识。以下是关于研究过程中以及实施步骤中的总结笔记。

什么是 kong ?

首先呢,kong 是一个基于 Nginx_Lua 模块写的高可用,易扩展由 Mashape 公司开源的 API Gateway 项目,如果你对 API Gateway 有什么误解,可以先来了解下 什么是 API Gateway ,API Gateway 是服务于微服务架构的一种API解决方案。好像有点绕,举个例子简单来理解一下:

比如,我们的一个简单来自的购物操作,中间可能涉及到 购物车、下单、评论、快递服务等操作。在一个非微服务的架构里面,客户端想要完成整个购物流程,就需要常见的 REST 请求来获取数据,我们可能在服务层用了一些负载均衡,那么这些请求就会分发到多个应用实例中并作出响应。

但是在一个微服务的体系中,购物车、下单、评论等,这样都可以独立成一个服务出来,也就是我们说的微服务。客户端想要完成整个购物流程,可以去单独的请求某个服务来获取数据。听起来可能有点怪怪的,我为了完成一个购物操作,从之前的一次请求变成了n次请求,毕竟我们知道,客户端频繁的请求的成本是不低的。所以,直接由客户端发n次请求这种事,一般来说是没人会这么干的,那么怎么干呢,就是 API Gateway 来处理了。API Gateway 其实也是一个服务器,所有的请求首先会经过这个网关。这里做 权限控制,安全,负载均衡,请求分发,监控等操作。这里也许放个官网的图会容易解释些:

我们不去讨论微服务和 API Gateway 的好坏,但是 kong 确实就是为微服务而生,并且做着这么一件事情的。相关的 API Gateway 方案还有 nginx 自家的 API Gateway 工具:nginx plus

kong 的安装 ?

现在 kong 的版本已经迭代到 0.13.x 了,推荐使用新版来实践。

关于 kong 的安装,官网给出了基于多种平台的安装方案,可以在 这里 查看。比如我个人的平台是 macOS ,就可以按照官方给出的方案,很方便的使用 brew 来安装 kong 。不过需要注意的是,kong 是不支持 Windows平台 的安装的,不接受提问(因为我也不知道为啥没有)。但是没有不代表不可以用,因为 kong 是支持 docker 部署的,而 docker 是支持 Windows 的,所以你可以安装 docker 来部署 kong,并且,从我个人实践的过程中来看,使用 docker 是最方便的部署方式。下面就介绍使用 docker 的部署方式:

对了,首先,你得安装个 docker

第一步:创建一个 docker 私有网络

kong-net 是网络标识名字,最好个性一些。

docker network create kong-net

第二步:启动或配置一个 cassandra (or PostgreSQL) 服务器

如果你还没有 cassandra 服务,那么你可以启动一个,比如下面就是启动了一个 cassandra v3 的docker container,并且映射了9042端口,那么你最后暴露出来的 cassandra 服务就是 localhost:9042,记下这个地址,后面会用到。

docker run -d --name kong-database \
              --network=kong-net \
              -p 9042:9042 \
              cassandra:3

当然,如果你已经有了现成的 Cassandra 服务了,那么就不用这一步了,记下服务地址,后面会用到。

第三步:kong 启动准备,数据库准备

官方把这一步叫做 migrations,你可以把这一步理解为修改 kong 配置。

docker run --rm \
    --network=kong-net \
    -e "KONG_DATABASE=cassandra" \
    -e "KONG_CASSANDRA_CONTACT_POINTS=xxx" \
    -e "KONG_CASSANDRA_PORT=9042"\
    -e "KONG_CASSANDRA_KEYSPACE=kong"\
    -e "KONG_DB_UPDATE_PROPAGATION=10"\
    kong:latest kong migrations up

这一步的动作其实就是创建一个临时用来写配置的容器来进行写配置。其中 xxx 就是第二步中的 Cassandra 地址。如果你需要使用 PostgreSQL 服务,只需要设置相应的参数就可以了,具体可以参考kong configuration,官方 github 也给出了一个模板配置文件供参考:kong.conf.default。 需要注意的就是,如果使用上面参数的形式来配置,那么大概就是这么的对比关系: db_update_propagation => KONG_DB_UPDATE_PROPAGATION

第四步:启动 kong

启动 kong ,并对外暴露 8001 端口,最终的 host:8001 即 kong 对外暴露的网关 url 。

docker run -d --name kong \
    --network=kong-net \
    -e "KONG_DATABASE=cassandra" \
    -e "KONG_CASSANDRA_CONTACT_POINTS=xxx" \
    -e "KONG_CASSANDRA_PORT=9042"\
    -e "KONG_CASSANDRA_KEYSPACE=kong"\
    -e "KONG_DB_UPDATE_PROPAGATION=10"\
    -e "KONG_PROXY_ACCESS_LOG=/dev/stdout" \
    -e "KONG_ADMIN_ACCESS_LOG=/dev/stdout" \
    -e "KONG_PROXY_ERROR_LOG=/dev/stderr" \
    -e "KONG_ADMIN_ERROR_LOG=/dev/stderr" \
    -e "KONG_ADMIN_LISTEN=0.0.0.0:8001, 0.0.0.0:8444 ssl" \
    -p 8000:8000 \
    -p 8443:8443 \
    -p 8001:8001 \
    -p 8444:8444 \
    kong:latest

集群

当然,上面的 kong 只是部署在了一个结点,如果是一个集群,需要部署在多个结点的话,可以参考这里

kong 可视化界面

当成功部署了 kong 之后,我们可以使用 curl 来测试是否生效,比如我的 kong 是部署在本机,那么执行:

curl http://localhost:8001

那么就会得到当前 kong 结点的配置信息。更多操作可以可以参考这里

但是我们有一种更好的办法是,替换终端操作使用 kong 的可视化界面来管理 kong 。目前关于 kong 使用比较多的开源可视化工程模板有两个,一个是 koa-dashboard,使用 angular 和 koa ,一个是 konga,使用 sails 和 angular ,推荐使用前者。使用方法如下:

1. 下载

下载源代码: git clone https://github.com/PGBI/kong-dashboard.git

2. 安装

安装方式有两种,一种是使用 npm ,但是需要全局安装 kong-dashboard,这样对于测试生产主机不友好,所以选择第二种,打包 docker 来安装。如果我们想要直接使用,并且部署的主机可以访问 docker 官方镜像仓库,那么执行:

docker run --rm -p 8080:8080 pgbi/kong-dashboard start \
  --kong-url http://locahost:8001\
  --basic-auth admin=123456

http://locahost:8001 可以换成自己的 kong 对外暴露的 url。

但是,如果说要部署的主机存在某些限制或者我们想自己定制 dashboard 的源代码,我们可以自己打包镜像的方式来操作。在源代码工程目录下执行

docker build -t xxxx.com/kong-dashboard .

(这样做的原因是,一般各家公司都有自己的 docker registry,我们可以更改 Dockerfile 来定制我们自己的镜像),然后执行

docker push xxxx.com/kong-dashboard

这样我们部署的时候,就可以直接执行:

docker run --rm -p 8080:8080 xxxx.com/kong-dashboard start \
  --kong-url http://locahost:8001\
  --basic-auth admin=123456

最后打开浏览器打开 xxx:8080,看到的效果大概是这个样子的:

大功告成!

遇到的问题?

kong 的整体部署过程还是很和谐,并没有什么大问题,倒是在 docker 的使用出现了一些小问题。

1. kong-dashboard 版本问题

kong-dashboard docker镜像的版本并不是最新的,最新的已经支持到了 V3.3, 而 pgbi/kong-dashboard 是 V3.0 ,功能上还是差了一些的。

在部署主机没有外网访问权限的情况下,最后我使用了离线打包 docker 的方式来获取最新版,详细操作如下:

  1. 本地打包: docker save -o xxxx.docker image(镜像名称) , xxxx.docker 是输出的离线文件,
  2. 传输至远程主机: 使用 rsync 传输至远程主机: rsync -cavzP ./xxxx.docker root@host:/path
  3. 装再: 进入到 xxxx.docker 路径并执行: docker save -i xxxx.docker

之后本机就成功装在最新版本的 kong-dashboard 镜像了。

2. cassandra 可视化管理工具

在此之前,其实对 cassandra 并未耳闻,简单了解后知道是一套开源分布式 NoSQL 数据库系统。习惯了 mongo 的 studio 3T ,redis 的 RDM 的数据可视化,关于 cassandra 的可视化管理工具貌似并不多,特别是针对 macos 系统的。

查阅相关资料后,最后选择了 tableplus, 免费版虽有限制,但也够用。

一次有关于逼格的折腾笔记

iterm2 折腾笔记

以下是废话

emmm……,这其实不是一篇技术文,因为没有代码、没有逻辑、没有人气,纯粹是小部分人基于逼格的一些尝试(当然具体有没有还要另说),所以就当我胡说八道,值不值得看自己心里琢磨。

起因是这样的,浏览 v2ex 时看到一篇帖子:看看你们炫酷的命令行界面,回复不多,但是看得我春心荡漾。回复大概是这样子的,

或者是这个样子的,

再或者是这个样子的,

总之,看的我是蠢蠢欲动。在此之前,我的 terminal 的配色就是简单的 iterm2 + zsh + oh-my-zsh ,设置了简单的颜色主题,其实已经很漂亮了,大致效果是下面这个样子的:

然而,对比之下,就发现了:我为毛没有图标,简直就是高富帅和穷屌的对比啊!! 加上之前的配色方案已经用了半年之久(相当的审美疲劳),于是决定果断换掉。

划重点(以上是废话)

我喜欢对做每一件事情之前进行调研,研究其充分的可能,然后再做行动。同时,加上我实践之后得出的真知,在这里先画个重点:

  1. 只适用于 MacOS。首先 Windows 没有 iterm2(当然也不仅仅是用在 iterm2上面),其次是因为 macOS 所搭载的平台(MacBook,iMac)都是对高分辨率有强迫症的,所以才会有好的视觉效果,Windows 就暂时先放一放。
  2. 好看确实好看,不一定好用。 不好用体现在两个方面,布局和性能。在布局上,或许只有在全屏下才能看到最佳的视觉效果,因为行内有大量的内容区域被占用,大大压榨了可输入区域的大小,虽然有些主题可以设置另起一行来选择输入,但是体验是相当差的。在性能上,对于一些特殊的主题,因为会实时的显示本地的内存占用、时间时期等信息,每次执行命令都会执行一遍,不卡才怪(反正我在使用过程中发现了绝对能够影响我感官的卡顿感)。

如果你觉得以上的问题还 OK,那就继续看下去。现在开始折腾……

开始折腾

其实配置的方法很简单,比较纠结的地方在于怎么去找适合的主题,于是我开始 深入浅出 GitHub……

关于主题选择

其实 iterm 的主题有很多,我们可以随便选择一种。默认的主题是 robbyrussell ,我们可以通过 vi ~/.zshrc 命令然后找出 ZSH_THEME=”xxx” 的语句,xxx 就是默认主题的配置。

在官方给出的主题列表里,有一款主题其实很不错(其他都丑):agnoster,效果如下:

配置官方的主题方案很简单,可以参考此篇博客

emmm…… 这看起来和开篇的效果图还不太一样啊。当然,除了官方的主题,还有一些第三方的主题,比如我今天要用的 powerlevel9k,在搭配效果上还会有更多的方案。

powerlevel9k

对于 powerlevel9k,其实已经有了一个还不错的生态圈了,毕竟GitHub也是有 4k+ 的存在。在官方的说明中已经有了一份很详细的安装文档。可以戳这里查看。

这里就不赘述安装细节做无用功了。文档是英文的,但是也很容易可以看懂,大致意思就是 powerlevel9k 提供了针对不同平台的主题安装方案,对于一些特殊的主题效果,还需要安装一些特殊的字体用作图标展示,在这里都可以选择安装,比如我选择的就是 zsh 的主题安装 和 Nerd-Fonts 的字体安装方案。同时,powerlevel9k 也提供了社区分享的一些不错的配色方案,我们可以直接拿来用,嘿嘿……

自己可以选择自己喜欢的主题和字体。安装完成之后,只需要在 zsh 的配置文件中配置一下即可:

1. 打开 zsh 配置文件

vi ~/.zshrc

2. 写入配置方案, 主题选择你安装的主题,配色方案可以去上面社区分享的列表去找,比如我的

ZSH_THEME="powerlevel9k/powerlevel9k"
# ZSH_THEME="agnoster"
POWERLEVEL9K_MODE='nerdfont-complete'

POWERLEVEL9K_SHORTEN_DIR_LENGTH=2
POWERLEVEL9K_SHORTEN_STRATEGY="truncate_middle"
POWERLEVEL9K_CONTEXT_DEFAULT_BACKGROUND="000"
POWERLEVEL9K_CONTEXT_DEFAULT_FOREGROUND="007"
POWERLEVEL9K_DIR_HOME_BACKGROUND="001"
POWERLEVEL9K_DIR_HOME_FOREGROUND="000"
POWERLEVEL9K_DIR_HOME_SUBFOLDER_BACKGROUND="001"
POWERLEVEL9K_DIR_HOME_SUBFOLDER_FOREGROUND="000"
POWERLEVEL9K_NODE_VERSION_BACKGROUND="black"
POWERLEVEL9K_NODE_VERSION_FOREGROUND="007"
POWERLEVEL9K_NODE_VERSION_VISUAL_IDENTIFIER_COLOR="002"
POWERLEVEL9K_LOAD_CRITICAL_BACKGROUND="black"
POWERLEVEL9K_LOAD_WARNING_BACKGROUND="black"
POWERLEVEL9K_LOAD_NORMAL_BACKGROUND="black"
POWERLEVEL9K_LOAD_CRITICAL_FOREGROUND="007"
POWERLEVEL9K_LOAD_WARNING_FOREGROUND="007"
POWERLEVEL9K_LOAD_NORMAL_FOREGROUND="007"
POWERLEVEL9K_LOAD_CRITICAL_VISUAL_IDENTIFIER_COLOR="red"
POWERLEVEL9K_LOAD_WARNING_VISUAL_IDENTIFIER_COLOR="yellow"
POWERLEVEL9K_LOAD_NORMAL_VISUAL_IDENTIFIER_COLOR="green"
POWERLEVEL9K_RAM_BACKGROUND="black"
POWERLEVEL9K_RAM_FOREGROUND="007"
POWERLEVEL9K_RAM_VISUAL_IDENTIFIER_COLOR="001"
POWERLEVEL9K_RAM_ELEMENTS=(ram_free)
POWERLEVEL9K_TIME_BACKGROUND="black"
POWERLEVEL9K_TIME_FOREGROUND="007"
POWERLEVEL9K_TIME_FORMAT="%D{%H:%M} %F{003}\uF017"
POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=('context' 'dir' 'vcs')
POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS=('node_version' 'load' 'ram_joined' 'time')
POWERLEVEL9K_LEFT_SEGMENT_SEPARATOR=$'\uE0B0'
POWERLEVEL9K_RIGHT_SEGMENT_SEPARATOR=$'\uE0B2'

3. 使配置文件生效

source ~/.zshrc

综上,就可以略微有些逼格了,放个我的

结语

喜欢折腾的值得一试哦(内心:真的有点卡啊,不行不行,明天我就卸了)。

tcp三次握手与四次分手

理解 HTTP 协议以及 TCP 三次握手与四次分手的过程

理解 HTTP 协议

超文本传输 ​​ 协议(HTTP)是用于传输诸如 HTML 的超媒体文档的应用层协议,最顶层的协议。HTTP 是无状态协议,意味着服务器不会在两个请求之间保留任何数据(状态)。

关于无状态的理解

可以理解为 HTTP 是没有上下文的,HTTP 无法保存连接双方的状态信息。基于此,知乎上有看到一个很直观的白话例子:

参考:HTTP 是一个无状态的协议。这句话里的无状态是什么意思?

有状态 无状态 使用 cookie
A:你今天中午吃的啥? A:你今天中午吃的啥 A:你今天中午吃的啥
B:吃的大盘鸡 B:吃的大盘鸡。 B:吃的大盘鸡。
A:味道怎么样呀? A:味道怎么样呀? A:味道怎么样呀?
B:还不错,挺好吃的。 B:???啊?啥?啥味道怎么样? B:还不错,挺好吃的

TCP 三次握手与四次分手

TCP 建立连接的时候需要三次握手,断开连接需要四次分手,这个过程是比较抽象的。

整个过程简单白话一下就是:

三次握手

  1. 客户端写了一封情书: 我中意你啊(建立连接的请求)
  2. 服务端收到了这封情书的回复:哇,我也中意你啊,mua
  3. 客户端收到了服务端的 mua :好啊,那我们就在一起吧(真正建立连接)

当然上面这是正常的情况,如果遇到情书发错人(连接出错)的情况,服务端就懒得理了,然后双发就不可能在一起(建立连接)。

为什么 TCP 连接需要三次握手

当然其实会有更多的人疑问,为什么 TCP 连接需要三次握手而不是两次,因为按照上面的意思,客户端来一句:在一起, 服务端回一个:好, 就可以了啊,为什么客户端还要多此一举回复一个“我也好”呢。其实原因很简单,跟 TCP 的特性有关,TCP 通道是不可靠的,而三次握手是满足通道安全的最小握手次数。继续用上面的例子来分析下:

先假设只有两次握手的情况:

  1. 客户端写了一封情书: 我中意你啊(建立连接的请求),但是因为某些原因,邮局放假啦,你的情书被搁置在路上了
  2. 客户端没有收到回复,于是又写了一封情书:我真的好中意你啊(建立连接的请求)
  3. 服务端收到了这封情书的回复:哇,我也中意你啊,mua(建立连接)
  4. 到这时,客户端和服务端已经可以愉快的玩耍了。但是忽然,客户端写的第一封情书也到了,服务端看到了,依然回复了句: 死鬼,我知道了
  5. 因为 HTTP 是无状态的,客户端不知道服务端回复的是啥,就没理。
  6. 服务端就在一直等待中:这个死鬼的情书到底是写给谁,怎么不回复我。于是服务端憔悴至死(资源浪费)

然后是三次握手的情况:

  1. 客户端写了一封情书: 我中意你啊(建立连接的请求),但是因为某些原因,有句放假啦,你的情书被搁置在路上了
  2. 客户端没有收到回复,于是又写了一封情书:我真的好中意你啊(建立连接的请求)
  3. 服务端收到了这封情书的回复:哇,我也中意你啊,mua
  4. 客户端收到了服务端的 mua :好啊,那我们就在一起吧(真正建立连接)
  5. 到这时,客户端和服务端已经可以愉快的玩耍了。但是忽然,客户端写的第一封情书也到了,服务端看到了,依然回复了句: 死鬼,我知道了
  6. 客户端一看,这是错了: 这是情书发错了,别再等了

三次握手的基本情况都老实交代了了,就那样。

四次分手

然后是四次分手,这个就简单的多:

  1. 客户端和服务端腻歪了,就说要分手,客户端:分手吧 (关闭连接的请求)
  2. 服务端:分就分,但是还有你的一些破东西,还给你(传递向客户端待发送的数据)
  3. 客户端收到回复了,就原地待命
  4. 服务端数据发送完了:好了,都扔了(数据发送完毕)
  5. 客户端接收到数据,然后给个回复:好的,我知道了,拜拜。

以上都是抖机灵的理解。其实 TCP 的三次连接和四次分手要复杂的多,可以参考以下正经的博客:

ng2体验报告

ng2 体验报告、总结

当然,现在 angular 最新的版本已经出到 5.0 了,现在才来说 ng2 有点老套了,只是最近忽然的从 react 的项目组转到了做 angular v2 (ng2) 的项目组,同时对谷歌和微软的”孩子”也比较感兴趣,所以还是有必要好好学习一下的。

上手了一阵子,大概摸清了 ng2 的套路,其实在此之前,对于习惯了 React、 Vue 开发模式的我来说,对于 ng2 是有一定的误解的。过去的我一直以为 ng2 只是简单的视图框架,就是一个简单的模板系统,现在看来我是错了,较之于 React、 Vue 复杂的项目构建来说, ng2 才是真正的框架啊,如果你有一定的后端基础,你一定能很快的理解 ng2 知识体系, 而且你也会明白 ng2 作为一个框架对于开发效率的提升。

关于学习网站

学习 ng2 最好的就是看官网了,当然所有的语言都是一样的,以下是几个可能需要经常去逛的网站。

  1. angular-cli 脚手架工具,快速构建项目;
  2. angular 官网,最新的已经是 V5 版本了,可以继续深入学习;
  3. Rx.js 中文速查手册;
  4. javascript 的超集 typescript;
  5. 待补……

重要概念

有关于 ng2 几个重要的概念如下:

  • 脚手架 ( scaffold )
  • 指令( directive )
  • 管道 ( pipe )
  • 路由 ( router )
  • 父子组件通信 ( @input & @output )
  • 模块 ( model )
  • 服务 ( service )
  • Rxjs ( Oberverable )
  • 依赖注入 ( injectable )

当然,赘述官方的文档不是我想要的,我更希望通过我个人的理解来介绍这几个知识点(或许有错误)

脚手架 ( scaffold )

唠叨一句:一般来说,脚手架这个东西,只有新手小白或者大牛会喜欢用,往往处于中间层的大佬们是比较鄙视的,因为这让程序变得没有技术含量,或者,被一些低级的脚手架给坑到。不过话说回来,使用好的脚手架的确能让让你的开发更加幸福,与其打开另一个文件复制代码,为何不让脚手架来给你生成呢。

ng2 (angular) 使用的脚手架是官方提供的 angular-cli ,常用的几个操作如下:

生成对象 命令 注意事项
project ng new PROJECT-NAME 生成新项目
debug ng serve –host 0.0.0.0 –port 4201 启动本地服务
Component ng g component my-new-component 生成 component
Directive ng g directive my-new-directive 生成自定义指令
Pipe ng g pipe my-new-pipe 生成自定义管道
Service ng g service my-new-service 生成服务
Class ng g class my-new-class 生成类,几乎不用
Guard ng g guard my-new-guard 生成自定义路由向导,通用拦截等
Interface ng g interface my-new-interface 生成接口
Enum ng g enum my-new-enum 生成自定义枚举文件
Module ng g module my-module 生成自定义 module

以上常用的几个命令参数同样的路径模式,意思就是你使用 ng new ./test 这样的格式也是可以的。

指令( directive )

指令系统是 angular 的一大特色,当你写 react 你一定特别希望也有自己的指令系统(当然这是玩笑话,因为 angular 是没有用 vdom 的)。 在开发过程中,你一定与遇到过如下的指令:

常见结构性指令 *ngIf *ngFor *ngSwitch 的用法:

<!-- 来自官网的英雄榜例子 -->

<div *ngIf="hero" >xxx</div>

<ul>
  <li *ngFor="let hero of heroes">xxx</li>
</ul>

<div [ngSwitch]="hero?.emotion">
  <app-happy-hero    *ngSwitchCase="'happy'"    [hero]="hero"></app-happy-hero>
  <app-sad-hero      *ngSwitchCase="'sad'"      [hero]="hero"></app-sad-hero>
  <app-confused-hero *ngSwitchCase="'app-confused'" [hero]="hero"></app-confused-hero>
  <app-unknown-hero  *ngSwitchDefault           [hero]="hero"></app-unknown-hero>
</div>

ng-template ng-container

还有一些常用来和结构性指令结合使用的语法,类似 ng-template ng-container 这些。 关于这两个模板语法,其实并不是有必须要使用的必要,但在很多时候,合理的使用可以让你的代码更加语义化,或者说更加优美。关于这两个,我怕我解释不当,官网给了两个比较好的例子:

当然,ng-template 另外的一个用途是 作为动态组件加载器

自定义指令

用上面的脚手架生成最方便,也不容易出错。如下,生成一个 名为 check 的指令:

<!-- 执行 ng g directive -->
ng g directive

<!-- 自定义生成示例,并已在 当前 model 声明 -->
import { Directive } from '@angular/core';

@Directive({
  selector: '[check]'
})
export class CheckDirective {

  constructor() { }

}

<!--在html模板中使用-->
<input check="xxx" >

问题: 官文文档会看到一种 属性型指令 和 结构性指令 ,两者有什么区别?

答: 结构型指令 — 通过添加和移除 DOM 元素改变 DOM 布局的指令,专注于布局,ngIf 这种。属性型指令 — 改变元素、组件或其它指令的外观和行为的指令,专注于内部属性,ngClass 这种。两种都可以自定义。

管道 ( pipe )

如果习惯了 bash 命令的童鞋,一定对 管道 很熟悉, 你可能经常会见到 ls xxx | grep xxx 这种的写法,其实 ng 中的 管道 和这个其实是一个意思,写法都是一样的,通过 “|” 来分割,不过 ng 的明显要弱一些,ng 中管道的常见用法都是用来格式化钱币、数值精度、日期格式化这些操作。

内置管道

ng 内置了一些管道,比如 DatePipe、UpperCasePipe、LowerCasePipe、CurrencyPipe 和 PercentPipe。 它们全都可以直接用在任何模板中。

常见的内置管道

自定义管道

当内置的管道不能满足需求的时候,往往我们需要自定义自己的管道。我们可以使用 ng g pipe my-new-pipe 来生成自定义管道,如下是一个简单的 money 格式化的例子,对属于任意的数值,进行金额的精度控制,当然底层其实还是使用了内置的 DecimalPipe 。

import { Pipe } from '@angular/core';
import { DecimalPipe } from '@angular/common';

@Pipe({
  name: 'money'
})
export class MoneyPipe {

  constructor(protected decimalPipe: DecimalPipe) {

  }

  public transform(value: any, digits?: string): string | null {
    value = parseFloat(value) || 0;
    return this.decimalPipe.transform(value, digits);

  }

}

在需要格式化金额的地方,比如我们要保留两位小数,我们可以这么用,10.2222 | money:'1.2-2',具体第二个精度的使用方法可以参考

路由 ( router )

路由重定向

可以这么写:

export const routes: Routes = [
  { path: '', redirectTo: 'A', pathMatch: 'full' },
  { path: 'a', component: A },
  { path: 'b', component: B, child:{
      [
      { path: '', redirectTo: 'b-a', pathMatch: 'full' },
      { path: 'b-a', component: ba },
      { path: 'b-b', component: bb }
    ]
  } }
];

路由跳转

可以这么写:

<a [routerLink]="['/a']">a</a>

也可以这么写:

this.router.navigate(['/a']);

路由参数

比如一个通知列表,点击不同的通知可以链接到不同的通知内容。

路由配置:

export const routes: Routes = [
  { path: '', redirectTo: 'A', pathMatch: 'full' },
  { path: 'notice-list', component: A },
  { path: 'notice-content/:id', component: B }
];

在 notice-list 设置路由跳转:

this.router.navigate(['/notice-content' ,id]);
this.router.navigate(['/notice-content'],params);

读取路由参数

this.route.params.subscribe(params => {
   console.log(params['id']);
});

this.route.queryParams.subscribe(params => {
   console.log(params);
});

路由拦截

使用 ng g guard login 来快速生成。

import { CanActivate } from '@angular/router';
import { Injectable } from '@angular/core';
import { LoginService } from './login-service';

@Injectable()
export class LoginRouteGuard implements CanActivate {

  constructor(private loginService: LoginService) {}

  canActivate() {
    return this.loginService.isLoggedIn();
  }
}

父子组件通信 ( @input & @output )

父组件向子组件传值

父组件使用 [data]='{}' 向子组件接收值,子组件通过 @input() data 来接收。

子组件向父组件传值

一般用于封装的组件。

在子组件中,使用

@Output() outData = new EventEmitter<string>();
……
this.outData.emit(data);

在父组件中获取,定义一个 getData 事件

<a (outData)='getData()'></a>

模块 ( model )

其实模块这个东西现在一点都不陌生,主流的编程框架都使用了模块化的编程方式。官方的文档是这么介绍的:

Angular 模块是带有 @NgModule 装饰器函数的类。 @NgModule 接收一个元数据对象,该对象告诉 Angular 如何编译和运行模块代码。 它标记出该模块拥有的组件、指令和管道, 并把它们的一部分公开出去,以便外部组件使用它们。 它可以向应用的依赖注入器中添加服务提供商。

我们理解起来就是,一个 Angular 模块 = 接收元数据对象(metadata)+ 暴露部分便于外部组件访问。 如果不清楚什么是 元数据 的,可以看下官方的介绍

服务 ( service )

在后端编程中经常会用到 服务 ( service ), 我个人的理解是,服务就是可高度抽象且与业务逻辑耦合低的一系列操作。比如所有应用场景下的登录都需要一个公用的验证码,那么生成验证码的这个功能就可以抽象成为一个服务,服务不是必须的,但是适当写服务会让代码耦合度降低,是一种好的编程习惯。

截止到目前,我用的 ng 中的服务一般是用作某一个模块的请求封装,或者是日期的一些特殊操作,就像是一个工厂方法库一样。

Rxjs ( Oberverable )

关于异步请求,ng2 自带的 http 模块返回的就是一个 Oberverable ,所以在项目中引入 Rx.js 自然无可厚非。官方评价为 promise 的超集,使用起来的确和 promise 很像,应该说是更加强大。但是正因为强大,导致要记的方法确实不少,直接戳一个中文 api

依赖注入 ( injectable )

依赖注入其实之前也接触过一些, 依赖注入的目的在我看来有两个,一个是降低程序间的耦合性和复杂度,一个是减少复杂对象实例化带来的扩展问题。

关于依赖注入,这里有两篇不错的可以用来理解的文章(都是基于 java ):

当然 ng 中的依赖出入也差不多,而且实现方式也更优雅,东西挺多,可以看官方文档

总结

其实也是刚接触 angularv2 不久,自己也只是结合过去的知识对自己认为的 ng2 做了一个总结,认识还是比较粗鄙的,行文也比较乱。

写博客总结的这个习惯,希望自己可以继续坚持下去,即使只有自己看,hahah……