让Chrome显示完整网址

Chrome 76+版本以后默认隐藏 https:// , www无关紧要的标识符

作为开发者,这些信息是非常有价值的。因此需要手动再将其重新开启

在flags页面中将其重新开启

地址栏chrome://flags, 进入flags页面

Chrome 版本小于83

Omnibox UI Hide Steay-State URL Scheme and Trivial Subdomains设置为禁用

Chrome 版本大于等于83

Context menu show full URLs设置为启用, 并右键地址栏选中总是显示完整网址

adb devices无法获取夜神模拟器的解决方案

在开启模拟器的前提下。在终端输入

1
$ adb devices

如果没有看到夜神模拟器的设备。则将${ANDROID_HOME}/platform-tools/adb.exe复制并覆盖夜神模拟器根目录下的adb.exenox_adb.exe

重启后查看效果(重启前确保后台的adb.exenox_adb.exe进程已被关闭)

新服务器必做二三事

主要是给自己一个提醒。一个新服务器需要做哪些必备项目

增加新用户用于常用用户

1
2
$ useradd xxxx
$ passwd xxx

切换到用户 设置为秘钥登录

1
$ su xxx
1
2
3
4
5
$ cd ~
$ mkdir .ssh
$ chmod 700 .ssh
$ vim authorized_keys # 插入登录公钥
$ chmod 600 authorized_keys

关闭密码登录和root用户登录 仅允许密码登录

1
$ exit # 退出到root用户
1
2
$ vim /etc/ssh/sshd_config
$ service sshd restart

设定以下参数:

1
2
PermitRootLogin no # 不允许root用户直接登录
PasswordAuthentication no # 不允许通过密码登录(即仅允许秘钥登录)

可选项目

修改主机名

1
2
$ vim /etc/hostname
$ hostname $(cat /etc/hostname)

快速构建软件文档——docusaurus v2

背景

来源是想要给我的应用做一个首页。需要有文档、首页两项。如果文档中能插入一些比较复杂的内容(如在线预览)这样的功能的话就更好了

开始使用的是docusaurus v1, 后来发现他无法在markdown文档中插入iframe。 会触发一个诡异的bug,导致渲染中断,无法正常渲染后面的内容,因此就中断了。后来发现docusaurus开始开发了v2版。虽然处于Beta阶段但还是能进行初步的使用了。

环境

  • docusaurus v2.0.0-alpha.50

特性

  • 使用React开发。可以体会到现代语言的优势。
  • 内置文档、博客,集成Algolia DocSearch。开箱即用。
  • 完全可定制的组件和样式, 增加完全的可定制化能力
  • MDX实现。在Markdown中也能使用React组件

自定义独立页面

略过初始化内容。我们直接进入自定义的阶段。

docusaurus 的独立页面在src/pages文件夹中, 就和写React组件一样我们可以利用React很轻松的构建属于自己的页面, 首页就是index.js。

对于非模板化的页面。比如首页、showcase、问卷调查这些,都可以通过独立页面来实现。

自定义渲染组件

docusaurus 提供了很强的自定义能力,使用@theme/*引用组件。提供一套预设的组件用于渲染网站的各个部位。其内容在https://github.com/facebook/docusaurus/tree/master/packages/docusaurus-theme-classic

对于该项目中的每一个组件,我们都能进行定制。使用docusaurus swizzle <theme name> [component name]命令将样式包中的组件复制到自己的src/theme文件夹中。可以指定的替换相应组件的实现

如:

1
npm run swizzle @docusaurus/theme-classic Footer

来重写页脚。

@theme/* 引用会以此查找本地的src/theme文件夹的组件,主题包里的组件。

自定义markdown渲染

Markdown是我们写文档重要的工具,有时候Markdown基本的语法无法满足我们的需要,此时就可以自己编写渲染逻辑。这样在不丧失Markdown简洁的前提下给予我们文档更加强大的表现力

比如我就自己写了一个组件, 用于将代码与预览相互转化: https://github.com/TRPGEngine/Server/blob/master/services/Website/website/src/plugins/remark-template-previewer.js

当然还有一种方法是利用MDX的特性在Markdown中引用React组件

成果

更多docusaurus的特性请查阅官方文档

wsl2入坑指南

背景

WSL2支持了docker环境,这给了我一个入坑wsl的理由。在一段时间的配置和踩坑以后我将wsl配置需求记录下来

任务目标:

  • 配置wsl2
  • 在wsl安装docker环境
  • 搭建基本环境

安装

更新Windows10的版本以安装wsl2

WSL 2 仅适用于 Windows 10 版本 18917 或更高版本

因此首先我们要window10版本升级到匹配的版本。目前来说正式发行版无法升级到相应版本,因此需要启用开发者预览版。启用方式是搜索insider打开Windows 预览体验计划的设置页面(在设置的 更新和安全 的最后一项)

根据提示将自己的微软账号注册为开发者账号。在获取预览版本的频率中选择 慢(Slow) 因为我们需要一个相对稳定的版本。

然后手动检测windows更新即可升级到符合条件的版本

PS: 注意, 更新版本以后可能会丢失一些windows的系统设置。需要手动检查并重新设置回来

启用wsl2

管理员身份打开Powershell

首先启用wsl

1
Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux

重启计算机

以管理员身份打开Powershell

1
2
dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart

重启计算机

Microsoft Store搜索wsl下载自己喜欢的Linux系统

注意!!!!
如果你使用wsl2的目的是为了docker。请务必选择Ubuntu 18.04系统! 因为Debian无法正常启动docker服务(其他系统没有测试过)!

在Powershell中查看自己当前已安装的Linux系统与使用的版本

1
2
3
wsl -l -v
# 或
wsl --list --verbose

设置linux系统使用的wsl版本。wsl1和wsl2据开发者所说会一直共存下去。因此要手动分配

1
wsl --set-version <Distro> 2

<Distro> 替换为上面列出的系统名称

将wsl2设置为默认的wsl体系

1
wsl --set-default-version 2

这会使你安装的任何新发行版均初始化为 WSL 2 发行版。

将软件源更换为清华源(可选)

  • 清华源
    1
    2
    sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak
    sudo vim /etc/apt/sources.list

将软件源设置为国内源的话操作会更加流畅

安装Docker

进入wsl, 输入uname -a可以检测版本

快速安装Docker

1
2
3
4
$ curl -fsSL https://get.docker.com -o get-docker.sh
$ sudo sh get-docker.sh
$ sudo service docker start
$ sudo usermod -aG docker $USER

使用service docker status查看docker服务的状态

安装zsh作为常用的shell

1
2
3
4
5
# 安装zsh
sudo apt-get install zsh

# 安装oh-my-zsh
sh -c "$(wget https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh -O -)"

相互访问网络应用

WSL 2 做了架构的巨大变更,使用了虚拟化技术,并仍在努力改进网络支持。由于 WSL 2 现在运行在虚拟机中,因此你从 Windows 访问 Linux 网络应用程序需要使用该 VM 的 IP 地址,反之亦然,你需要 Windows 主机的 IP 地址才能从 Linux 中访问 Windows 网络应用程序。 WSL 2 的目标是尽可能使用 localhost 访问网络应用程序!可以在文档中找到有关如何执行此操作的完整详细信息和步骤。

参考资料

微软: WSL 2 的安装说明
知乎: WSL 2中安装Docker

浅谈节流(throttle)与防抖动(debounce)

简介

节流与防抖是性能优化常见的方式,用于限制一些高频操作导致对cpu无意义的负担。
我搜索了网络上很多相关的文章,都没有办法直观表现出两者的区别。因此我自己总结了一下两者的区别,并用几个例子来解释一下两者区别

一下内容均以 lodash 的实现为例

防抖动debounce

防抖动的核心概念是当停止调用函数一段时间后。调用该函数

example:

1
2
3
4
5
6
const d = _.debounce(() => console.log('call func'), 1000, {leading: true, trailing: true});

const dt = setInterval(() => {
console.log('call timer');
d();
}, 200);

输出结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
call func # 如果 leading 为true
call timer
call timer
call timer
call timer
call timer
...
call timer
call timer
call timer
(执行clearInterval(dt))
(等待1s后)
call func # 如果 trailing 为true

即如果在限定时间(本例为1s)内不断调用防抖函数,则永远不会触发1s后的trailing的输出

节流throttle

节流的核心概念是一段时间内,函数最多只能被调用一次。

example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const t1 = _.throttle(() => console.log('call func'), 1000, {leading: true, trailing: true});
t1(); // 立即输出call func

const t2 = _.throttle(() => console.log('call func'), 1000, {leading: false, trailing: true});
t2(); // 一秒后输出call func

const t3 = _.throttle(() => console.log('call func'), 1000, {leading: false, trailing: false});
t3(); // 无反应

const t4 = _.throttle(() => console.log('call func'), 1000, {leading: true, trailing: true});
const t4t = setInterval(t4, 200); // 立即输出call func, 之后每秒输出一次

const t5 = _.throttle(() => console.log('call func'), 1000, {leading: false, trailing: true});
const t5t = setInterval(t5, 200); // 一秒后输出call func, 之后每秒输出一次

const t6 = _.throttle(() => console.log('call func'), 1000, {leading: false, trailing: false});
const t6t = setInterval(t6, 200); // 一秒后输出call func, 之后每秒输出一次 因为内部是通过 debounce 的 maxWait来实现的

如果在 throttle 规定的时间内有多次触发的话。必定会在时间段结束时触发函数调用
如果只有一次触发的话。如果 leadingtrailing 都为 true 。只会触发开始的那次函数调用

共同点

对于lodash来说
throttle的实现是通过debounce来实现的

lodash 实现

1
2
3
4
5
6
7
8
9
throttle(func, wait, options);

// 等价于

debounce(func, wait, {
options.leading || true,
options.trailing || true,
'maxWait': wait
});

参考文章

https://css-tricks.com/debouncing-throttling-explained-examples/

Docker Swarm 模型配合 traefik 实现集群sticky实践

背景

为什么要用Docker Swarm, 相比 Kubernetes 有什么好处

Docker Swarm可以看作是docker自带的一个简化版的Kubernetes, 拥有Kubernetes的基本功能如:

  • 更新
  • 回滚
  • 动态扩容
  • 分布式部署

Docker Swarm 的优势在于:

  • Docker 自带, 无需另外安装。学习成本低
  • 单机也能很好的使用,也可以很方便的进行实例扩容与设备扩容
  • Swarm 只有2层网络封装,而 Kubernetes 有5层网络封装
  • Swarm 本身占用的内存只有100M左右,而 Kubernetes 的简化版 k3s 也需要512M的内存空间,对小资源机器友好

Traefik 是什么, 为什么要使用它

Traefik 是一个反向代理软件,类似 Nginx但对于微服务有很好的优化。可以搭配各种分布式发现服务而无需另外配置。在本篇文章中我们会使用 Docker Provider 作为服务发现。

虽然 Docker Swarm 自带了http请求的分发,但是无法实现 sticky 功能(即同一用户的请求会分发到同一后端实例),因此需要Traefik 作为请求的中间件来分发请求

搭建 Docker Swarm 环境

Docker Swarm 环境非常好搭建,因为已经集成到Docker中了。我们安装好Docker以后就可以直接使用了

依赖:

  • Docker 1.12+
1
2
# 初始化swarm, 并将当前节点作为manager
$ docker swarm init
1
2
3
# 或者使用--advertise-addr 和 --listen-addr参数来指定使用哪个IP作为沟通IP
# 两个参数一般保持一致即可
$ docker swarm init --advertise-addr 10.0.0.1:2377 --listen-addr 10.0.0.1:2377

使用Docker快速搭建Traefik

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
version: '3'

services:
reverse-proxy:
# The official v2.0 Traefik docker image
image: traefik:v2.0
# Enables the web UI and tells Traefik to listen to docker
command: --api.insecure=true --providers.docker.endpoint=tcp://127.0.0.1:2377 --providers.docker.swarmMode=true
ports:
# The HTTP port
- "80:80"
# The Web UI (enabled by --api.insecure=true)
- "8080:8080"
volumes:
# So that Traefik can listen to the Docker events
- /var/run/docker.sock:/var/run/docker.sock
  • api.insecure参数用于打开WEB UI. 可以访问127.0.0.1:8080/api/rawdata获取当前可以连接到的服务的相关信息
  • providers.docker.endpoint指向docker的沟通端口。默认端口为2377
  • providers.docker.swarmMode 表示为swarm模式

创建测试服务

此处使用 whoami 镜像提供的HTTP服务用于打印出集群连接相关信息

将以下文件保存为docker-compose.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
version: '3'

services:
whoami:
# A container that exposes an API to show its IP address
image: containous/whoami
deploy:
replicas: 3
labels:
- "traefik.http.routers.whoami.rule=Host(`whoami.docker.localhost`)"
- "traefik.http.services.whoami.loadbalancer.server.port=80"
- "traefik.http.services.whoami.loadbalancer.sticky=true"
- "traefik.http.services.whoami.loadbalancer.sticky.cookie.name=foosession"

启动集群:

1
docker stack deploy -c ./docker-compose.yml whoami

测试命令:

1
$ curl -vs -c cookie.txt -b cookie.txt -H "Host: whoami.docker.localhost" http://127.0.0.1

注意需要带上 header Host 这样才能成功反向代理

Unicode编码解明

简介

本文简述了Unicode编码解码相关知识。假设读者已经对此有一定的基本了解

从Base64开始

为了更好说明变长字符编码,我们从Base64编码开始。因为Base64是每个字节进行编码

编码如下字符串:

1
你好, 世界!

得到如下编码:

1
5L2g5aW9LCDkuJbnlYwh

将这段编码放入浏览器控制台中进行不进行任何处理的解密

1
atob('5L2g5aW9LCDkuJbnlYwh');

得到如下结果:

1
你好, 世界!

可以阴影约约看到逗号和感叹号已经被解释出来,而中文没有被正确处理。因为英文的逗号和感叹号在128位ascii表中。而其他的中文字符就无法正确翻译了。

为了能正确翻译中文。我们需要对数据进行一些特殊处理。即实现utf8的编码规范来将二进制转换成一个unicode码,使其能对应上一个具体的字符。(unicode表是一个巨大的map, 每个数字都能对应一个具体的字符, 至于字符的具体渲染由系统提供的字体显示)

atob
atob 是一个很基础的base64转二进制的方法。他只会单独的去处理每个字节而不管其具体的编码实现

UTF8解码

我们先处理第一个字符ä
很明显。这是因为浏览器错误的处理了这个字节的翻译。我们需要将其转换成二进制

1
2
3
4
'ä'.charCodeAt(0); // 228

// 将其转换成二进制
(228).toString(2); // 11100100

我们查看一下 utf8的编码规范 。首字符前n个1表示由n个字节组成.以0表示收尾与分割。因此我们提取出其描述的二进制位为1110表示这个字由3个字节组成。那么我们继续提取接下来的2个字符

1
2
'½'.charCodeAt(0).toString(2); // 10111101
' '.charCodeAt(0).toString(2); // 10100000

其中最前的二位10是描述位,是无效的。我们将这三个字节的有效的二进制位提取出来可得: 0100 111101 100000。将其转换成16进制

1
(0b0100111101100000).toString(16); // 4f60

简单验证一下结果。直接在控制台输入:

1
'\u4f60' // 你

我们成功提取出了第一个中文字符。那么剩下的中文字符也很简单了

而编码就是其逆过程

UTF8编码

1
2
3
4
5
6
7
8
// 将 你好, 世界! 编码成16进制字符
const str = '你好, 世界!'

str
.split("")
.map(char => char.charCodeAt(0).toString(16))
.map(hex => "\\u" + hex.padStart(4, "0"))
.join(""); // \u4f60\u597d\u002c\u0020\u4e16\u754c\u0021

直接将输出的字符串复制到控制台可以看到自动转换出的中文

\u表示的是只后面跟的是一个unicode。长度为4字节

Git骚操作之从一个分支中批量将离散的commit 迁移到另一个分支

背景

因为某些原因。分支A与分支B在某个点分叉了。且分叉出来的分支B拥有很多乱七八糟的commit。现在希望将分支B中的代码迁移到分支A中。但因为分支B中有很多其他的commit。因此希望把分支B舍弃,只保留想要的一些commit。

本例是指仅作者为我自己的commit

方案

使用git log将自己的commit选出来。然后切换到分支A上。将选出来的commit cherry-pick。

首先根据某些条件选出想要迁移的commit。最终输出成空格分割的hash号

bash语句如下:

1
2
3
# 本例假设起始commit为4524cb34ea4
# 当前所在分支: 分支B
git log --author moonrailgun --oneline 4524cb34ea4^1...HEAD | awk '{print $1}' | sed '1!G;h;$!d' | xargs echo

语句解释:

  • git log --author moonrailgun --oneline 4524cb34ea4^1...HEAD 获取范围4524cb34ea4(包含该commit)~当前commit的commit中作者是(包含)moonrailgun的列并用单行显示
  • awk '{print $1}' 获取每行中以空格分割的第一列
  • sed '1!G;h;$!d' 将输入按行倒序输出(因为git log输出的最后一行在最上面)
  • xargs echo 将输入的行变成一行

由此可以得到一串用空格分割的hash字符串

然后git checkout A切换到分支A。执行git cherry-pick <此处输入刚刚得到的字符串>
如有冲突,解决冲突后git cherry-pick --continue即可

结论

  • 不会丢失代码
  • 若有冲突能马上解决
  • 保留commit细节
  • 解放生产力