-
Notifications
You must be signed in to change notification settings - Fork 0
/
atom.xml
476 lines (284 loc) · 247 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>刘先玉</title>
<subtitle>知易行难</subtitle>
<link href="/atom.xml" rel="self"/>
<link href="https://liuxianyu.cn/"/>
<updated>2024-11-26T09:42:38.000Z</updated>
<id>https://liuxianyu.cn/</id>
<author>
<name>liuxy0551</name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>53k star! 高颜值的开源相册工具 —— immich</title>
<link href="https://liuxianyu.cn/article/immich-docker.html"/>
<id>https://liuxianyu.cn/article/immich-docker.html</id>
<published>2024-11-26T09:42:38.000Z</published>
<updated>2024-11-26T09:42:38.000Z</updated>
<content type="html"><![CDATA[<p> 最早之前使用的是百度网盘备份视频和照片,近两年用的是不怎么限速的阿里网盘。近期想从百度网盘下载点东西,限速后太过费劲,所以准备弃用,开了个 SVIP,将所有文件全部下载到了硬盘。同理,担心后续阿里网盘限速,虽然视频照片这类资源备份后基本不怎么再次下载,但本着我不下载但你不能限速的原则,找到了一个开源的相册备份应用 <code>immich</code>。</p><a id="more"></a><p> 也对比过一些其他的开源软件,比如 NextCloud,但是个人觉得还是 immich 更好用。最近用了一段时间,体验还不错,内网下上传照片极快,也有对应手机平台的 APP 支持,体验较好。这里记录下通过 docker compose 安装 immich 的过程和一些改动。</p><h3 id="一-环境与链接"><a class="header-anchor" href="#一-环境与链接">¶</a>一、环境与链接</h3><p> <strong>最好能有科学上网</strong>,<a href="https://liuxianyu.cn/article/http-proxy.html">远程服务器使用本地代理</a></p><ul><li>硬件:中柏 n100 pro Ⅱ 16G + 512G</li><li>系统:Ubuntu 24.04</li><li>官网:<a href="https://immich.app/" target="_blank" rel="noopener">https://immich.app/</a></li></ul><h3 id="二-安装"><a class="header-anchor" href="#二-安装">¶</a>二、安装</h3><h4 id="2-1-安装-docker"><a class="header-anchor" href="#2-1-安装-docker">¶</a>2.1 安装 docker</h4><p> 我是通过 docker compose 安装的,简单记录下如何安装 docker compose:</p><figure class="highlight shell"><figcaption><span>安装 docker</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> Add Docker<span class="string">'s official GPG key:</span></span></span><br><span class="line">sudo apt update -y</span><br><span class="line">sudo apt install ca-certificates curl -y</span><br><span class="line">sudo install -m 0755 -d /etc/apt/keyrings</span><br><span class="line">sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc</span><br><span class="line">sudo chmod a+r /etc/apt/keyrings/docker.asc</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> Add the repository to Apt sources:</span></span><br><span class="line">echo \</span><br><span class="line"> "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \</span><br><span class="line"><span class="meta"> $</span><span class="bash">(. /etc/os-release && <span class="built_in">echo</span> <span class="string">"<span class="variable">$VERSION_CODENAME</span>"</span>) stable<span class="string">" | \</span></span></span><br><span class="line"> sudo tee /etc/apt/sources.list.d/docker.list > /dev/null</span><br><span class="line">sudo apt update -y</span><br><span class="line">sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -y</span><br><span class="line">docker -v</span><br></pre></td></tr></table></figure><figure class="highlight shell"><figcaption><span>安装 docker compose</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">sudo apt update -y</span><br><span class="line">sudo apt install docker-compose-plugin -y</span><br><span class="line">docker compose version</span><br></pre></td></tr></table></figure><h4 id="2-2-docker-compose-yml"><a class="header-anchor" href="#2-2-docker-compose-yml">¶</a>2.2 docker-compose.yml</h4><figure class="highlight shell"><figcaption><span>存储相关文件</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">mkdir -p /mnt/docker/immich-app</span><br><span class="line">cd /mnt/docker/immich-app</span><br></pre></td></tr></table></figure><p> 官网的安装步骤在这里:<a href="https://immich.app/docs/install/docker-compose/%E3%80%82" target="_blank" rel="noopener">https://immich.app/docs/install/docker-compose/。</a> 下载 <code>docker-compose.yml</code> 和配置文件 <code>.env</code>,如果有硬件加速的能力(N 卡之类的硬件)可以点开官网文档查看,这里不进行。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">wget -O docker-compose.yml https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml</span><br><span class="line">wget -O .env https://github.com/immich-app/immich/releases/latest/download/example.env</span><br></pre></td></tr></table></figure><p> <code>vim .env</code> 追加时区:<code>TZ=Asia/Shanghai</code></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker compose -p immich up -d</span><br></pre></td></tr></table></figure><p> 稍等片刻即可访问,默认端口是 <code>2283</code>。我这里是在局域网其他电脑访问的:<a href="http://192.168.31.101:2283" target="_blank" rel="noopener">http://192.168.31.101:2283</a>。第一次进入会有一些初始化的设置。</p><h4 id="2-3-大模型"><a class="header-anchor" href="#2-3-大模型">¶</a>2.3 大模型</h4><p> immich 强大在可以支持大模型处理,能够标注人脸和按文字搜索照片。上传一定量图片后,如果左侧 探索 功能中还是没有人脸,可能是由于服务器端存在网络问题,此时需要在服务器端准备好大模型。在 <code>docker-compose.yaml</code> 文件同级新建 <code>model_cache</code> 文件夹,然后执行:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">cd /mnt/docker/immich-app</span><br><span class="line">mkdir -p model-cache/clip model-cache/facial-recognition</span><br></pre></td></tr></table></figure><p> 这里我们会用到两个大模型 <a href="https://huggingface.co/immich-app/XLM-Roberta-Large-Vit-B-16Plus/tree/main" target="_blank" rel="noopener">XLM-Roberta-Large-Vit-B-16Plus</a> 和 <a href="https://huggingface.co/immich-app/buffalo_l/tree/main" target="_blank" rel="noopener">buffalo_l</a>,分布用于中文搜索(以文搜图)和人脸识别。用到的的相关文件我都放到了 <a href="https://pan.quark.cn/s/f623f75acd2a" target="_blank" rel="noopener">夸克云盘 immich-docker</a>,也可以自行下载。</p><p> 我们需要分别下载并解压到 <code>model-cache/clip</code> 和 <code>model-cache/facial-recognition</code> 目录下。因为仓库文件较大,需要借助 <a href="https://github.com/git-lfs/git-lfs/releases" target="_blank" rel="noopener">git-fls</a>:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">tar -zxvf git-lfs-linux-amd64-v3.6.0.tar.gz</span><br><span class="line">cd git-lfs-3.6.0</span><br><span class="line">sudo ./install.sh</span><br><span class="line">git lfs install</span><br></pre></td></tr></table></figure><blockquote><p><strong>注意:</strong> 大模型的路径是有讲究的。</p></blockquote><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">cd /mnt/docker/immich-app/model-cache/clip</span><br><span class="line">git clone https://huggingface.co/immich-app/XLM-Roberta-Large-Vit-B-16Plus</span><br></pre></td></tr></table></figure><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">cd /mnt/docker/immich-app/model-cache/facial-recognition</span><br><span class="line">git clone https://huggingface.co/immich-app/buffalo_l</span><br></pre></td></tr></table></figure><p><img src="https://images-hosting.liuxianyu.cn/posts/immich-docker/1.png" alt></p><p> <code>vim .env</code> 定义大模型的路径:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># model cache path</span><br><span class="line">MODEL_CACHE=./model-cache</span><br></pre></td></tr></table></figure><p> <code>vim docker-compose.yml</code> 使用大模型的路径:</p><p><img src="https://images-hosting.liuxianyu.cn/posts/immich-docker/2.png" alt></p><p> 分别使用两个大模型,然后 <a href="https://liuxianyu.cn/article/immich-docker.html#2-4-%E9%87%8D%E5%90%AF-immich">在服务器端重启 immich</a>。</p><p><img src="https://images-hosting.liuxianyu.cn/posts/immich-docker/3.png" alt><br><img src="https://images-hosting.liuxianyu.cn/posts/immich-docker/4.png" alt></p><h4 id="2-4-重启-immich"><a class="header-anchor" href="#2-4-重启-immich">¶</a>2.4 重启 immich</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">docker compose down</span><br><span class="line">docker compose -p immich up -d</span><br></pre></td></tr></table></figure><p> 重启完成后,重新拉起相关的任务。</p><p><img src="https://images-hosting.liuxianyu.cn/posts/immich-docker/5.png" alt></p><p> 等待运行完成,搜索功能就有人脸分组了。</p><p><img src="https://images-hosting.liuxianyu.cn/posts/immich-docker/6.png" alt></p><h4 id="2-5-更新-immich"><a class="header-anchor" href="#2-5-更新-immich">¶</a>2.5 更新 immich</h4><p> 进入 immich 时如果有更新提示,可以在服务器端执行:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker compose pull && docker compose -p immich up -d</span><br></pre></td></tr></table></figure><h3 id="三-卸载"><a class="header-anchor" href="#三-卸载">¶</a>三、卸载</h3><p> 如果体验后不想使用,可以按以下方式卸载:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker stop immich_server immich_machine_learning immich_redis immich_postgres</span><br></pre></td></tr></table></figure><p> 自行清理对应的容器和镜像,可以酌情删除 <code>/mnt/docker/immich-app</code> 文件夹。</p>]]></content>
<summary type="html">
<p> 最早之前使用的是百度网盘备份视频和照片,近两年用的是不怎么限速的阿里网盘。近期想从百度网盘下载点东西,限速后太过费劲,所以准备弃用,开了个 SVIP,将所有文件全部下载到了硬盘。同理,担心后续阿里网盘限速,虽然视频照片这类资源备份后基本不怎么再次下载,但本着我不下载但你不能限速的原则,找到了一个开源的相册备份应用 <code>immich</code>。</p>
</summary>
<category term="Linux" scheme="https://liuxianyu.cn/categories/Linux/"/>
<category term="Linux" scheme="https://liuxianyu.cn/tags/Linux/"/>
</entry>
<entry>
<title>远程服务器使用本地代理</title>
<link href="https://liuxianyu.cn/article/http-proxy.html"/>
<id>https://liuxianyu.cn/article/http-proxy.html</id>
<published>2024-06-16T23:07:38.000Z</published>
<updated>2024-06-16T23:07:38.000Z</updated>
<content type="html"><![CDATA[<p> 在服务器端经常遇到下载速度慢的问题,在服务端安装 clash 或者 v2ray 等又比较麻烦,可以临时使用本地的代理达到科学上网的目的。这里<strong>需要区分 <code>局域网内服务器</code> 和 <code>公网服务器</code></strong>,记录下细节。</p><a id="more"></a><h3 id="一-局域网内服务器"><a class="header-anchor" href="#一-局域网内服务器">¶</a>一、局域网内服务器</h3><p> 之前将一台放在家里旧笔记本电脑安装了 CentOS 作为服务器学习用,属于局域网内的机器,原则上公网无法访问。家里还有一台 Windows 台式机,可以通过 clash 科学上网,安装了远程开机卡和 ToDesk 自启动,方便在公司远程开机。笔记本和 Windows 台式机都是通过 WiFi 接入网络的,因此处于同一个局域网。公司的 Mac 偶尔带回家,这里也设置下。需要开启 clash 的 <code>允许局域网连接</code>:</p><p><img src="https://images-hosting.liuxianyu.cn/posts/http-proxy/1.png" alt></p><p> 将旧笔记本电脑的别名改为 <code>lenovo</code>,在 <code>lenovo</code> 上 <code>vim ~/.zshrc</code> 添加以下命令:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">MACIP='192.168.31.23'</span><br><span class="line">WINIP='192.168.31.108'</span><br><span class="line"><span class="meta">#</span><span class="bash"> 使用代理服务器(MacOS)</span></span><br><span class="line">alias proxym='export https_proxy=http://$MACIP:7890;export http_proxy=http://$MACIP:7890;export all_proxy=socks5://$MACIP:7890'</span><br><span class="line"><span class="meta">#</span><span class="bash"> 使用代理服务器(Windows)</span></span><br><span class="line">alias proxyw='export https_proxy=http://$WINIP:7890;export http_proxy=http://$WINIP:7890;export all_proxy=socks5://$WINIP:7890'</span><br><span class="line"><span class="meta">#</span><span class="bash"> 取消使用代理服务器</span></span><br><span class="line">alias unproxy='unset http_proxy;unset https_proxy;unset all_proxy'</span><br><span class="line"><span class="meta">#</span><span class="bash"> 测试服务器是否可用</span></span><br><span class="line">alias proxy_test='curl -v google.com'</span><br></pre></td></tr></table></figure><p> 在 <code>lenovo</code> 上添加完上述命令后执行 <code>source ~/.zshrc</code> 就可以生效了。此时可以执行 <code>proxy_test</code> 测试代理是否可用,发现一直是 Trying;执行 <code>proxy</code> 后再执行 <code>proxy_test</code> 就发现有具体内容返回了;可以通过执行 <code>unproxy</code> 取消代理。</p><p><img src="https://images-hosting.liuxianyu.cn/posts/http-proxy/2.png" alt></p><h3 id="二-公网服务器"><a class="header-anchor" href="#二-公网服务器">¶</a>二、公网服务器</h3><p> 公网服务器可以借助 <code>ssh -R</code> 命令创建一个反向代理通道(<strong>下方 ssh 命令在本地机器执行,本地机器需要科学上网</strong>):</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ssh -R 6990:localhost:7890 [email protected] -N</span><br></pre></td></tr></table></figure><ul><li><code>ssh -R</code> 表示建立一个反向 SSH 隧道</li><li><code>6990</code> 是在公网服务器上监听的端口</li><li><code>localhost</code> 本地机器的地址</li><li><code>7890</code> 本地机器代理服务的地址,这里使用的是 clash</li><li><code>[email protected]</code> 公网服务器的用户名和 IP 地址</li><li><code>-N</code> 表示不执行远程命令,仅建立隧道连接,这里必需</li></ul><p> 因此上述命令的作用是:将本地的 <code>7890</code> 端口映射到公网服务器 <code>47.65.55.62</code> 的 <code>6990</code> 端口,当公网服务器有请求发送到公网服务器的 <code>6990</code> 端口时,它会通过 SSH 隧道转发到本地机器的 <code>7890</code> 端口。</p><p> 在本地机器执行上述命令前先在公网服务器上确认配置了允许端口转发,我这里的机器是 CentOS7.6,使用 <code>cat /etc/os-release</code> 可以查看系统信息。<code>vim /etc/ssh/sshd_config</code> 后找到 <code>AllowTcpForwarding</code> 和 <code>GatewayPorts</code>,这两个配置需要都设置为 <code>yes</code>,一般是注释了,可以在这两者的下方添加如下配置,并执行 <code>systemctl restart sshd</code> 命令重启 SSH 服务:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">AllowTcpForwarding yes</span><br><span class="line">GatewayPorts yes</span><br></pre></td></tr></table></figure><p> 类似的,在公网服务器上也添加下方的 <code>proxy</code> 命令,然后执行 <code>source ~/.zshrc</code> 就可以生效了。在公网服务器执行 <code>proxy_test</code> 测试代理是否可用,发现一直是 Trying;执行 <code>proxy</code> 后再执行 <code>proxy_test</code> 就发现有具体内容返回了;可以通过执行 <code>unproxy</code> 取消代理。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> 代理到本机的指定端口(公网服务器)</span></span><br><span class="line">alias proxy='export https_proxy=http://localhost:6990;export http_proxy=http://localhost:6990;export all_proxy=socks5://localhost:6990'</span><br><span class="line"><span class="meta">#</span><span class="bash"> 取消使用代理服务器</span></span><br><span class="line">alias unproxy='unset http_proxy;unset https_proxy;unset all_proxy'</span><br><span class="line"><span class="meta">#</span><span class="bash"> 测试服务器是否可用</span></span><br><span class="line">alias proxy_test='curl -v google.com'</span><br></pre></td></tr></table></figure><p><img src="https://images-hosting.liuxianyu.cn/posts/http-proxy/3.png" alt></p><blockquote><p><strong>注意</strong></p><ul><li><strong>先在本地机器执行 <code>ssh -R</code> 命令,再在公网服务器执行 <code>proxy</code> 使用代理</strong></li><li><strong><code>ssh -R</code> 命令的 <code>-N</code> 参数需要有,否则就会直接登录公网服务器的终端了</strong></li><li><strong><code>ssh -R</code> 命令执行后会要求终端活跃,关闭终端或停止 <code>ssh -R</code> 命令都会停止代理</strong></li><li><strong>当公网服务器上的端口被占用时,<code>ssh -R</code> 命令执行会有 Warning 提示,代理会失败</strong></li></ul></blockquote><h3 id="三-其他"><a class="header-anchor" href="#三-其他">¶</a>三、其他</h3><ul><li>当 <code>ssh</code> 连接断开后,<code>proxy</code> 也就失效了,自启动或全局生效等可以自行研究;</li><li><code>ssh -R</code> 命令的作用是将远程服务器的端口映射到本地机器的端口,这也可以实现局域网内的服务在公网访问,不仅仅是代理的作用;</li></ul>]]></content>
<summary type="html">
<p> 在服务器端经常遇到下载速度慢的问题,在服务端安装 clash 或者 v2ray 等又比较麻烦,可以临时使用本地的代理达到科学上网的目的。这里<strong>需要区分 <code>局域网内服务器</code> 和 <code>公网服务器</code></strong>,记录下细节。</p>
</summary>
<category term="Cent OS" scheme="https://liuxianyu.cn/categories/Cent-OS/"/>
<category term="Linux" scheme="https://liuxianyu.cn/tags/Linux/"/>
<category term="Cent OS" scheme="https://liuxianyu.cn/tags/Cent-OS/"/>
</entry>
<entry>
<title>用 inquirer 写交互式的脚本</title>
<link href="https://liuxianyu.cn/article/inquirer-script.html"/>
<id>https://liuxianyu.cn/article/inquirer-script.html</id>
<published>2024-05-29T21:39:46.000Z</published>
<updated>2024-05-29T21:39:46.000Z</updated>
<content type="html"><![CDATA[<p> 最近开发 npm 包,发现基于 <a href="https://github.com/SBoudrias/Inquirer.js" target="_blank" rel="noopener">inquirer.js</a> 写交互式脚本很方便,尤其是一些部署脚本和打版本号之类的脚步,记录一下。</p><a id="more"></a><h3 id="一-打版本号"><a class="header-anchor" href="#一-打版本号">¶</a>一、打版本号</h3><p><img src="https://images-hosting.liuxianyu.cn/posts/inquirer-script/1.gif" alt></p><p> 前置依赖:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm i inquirer standard-version -D</span><br></pre></td></tr></table></figure><p> 参考 package.json:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"name"</span>: <span class="string">"release-script"</span>,</span><br><span class="line"> <span class="attr">"version"</span>: <span class="string">"0.0.3"</span>,</span><br><span class="line"> <span class="attr">"scripts"</span>: {</span><br><span class="line"> <span class="attr">"release"</span>: <span class="string">"node ./scripts/release.js"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"devDependencies"</span>: {</span><br><span class="line"> <span class="attr">"cz-conventional-changelog"</span>: <span class="string">"^3.3.0"</span>,</span><br><span class="line"> <span class="attr">"inquirer"</span>: <span class="string">"^9.2.22"</span>,</span><br><span class="line"> <span class="attr">"standard-version"</span>: <span class="string">"^9.5.0"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"module"</span>,</span><br><span class="line"> <span class="attr">"config"</span>: {</span><br><span class="line"> <span class="attr">"commitizen"</span>: {</span><br><span class="line"> <span class="attr">"path"</span>: <span class="string">"./node_modules/cz-conventional-changelog"</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><img src="https://images-hosting.liuxianyu.cn/posts/inquirer-script/2.png" alt></p><p> 对应的 <a href="https://gitee.com/liuxy0551/release-script/blob/master/scripts/release.js" target="_blank" rel="noopener">release.js</a></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> inquirer <span class="keyword">from</span> <span class="string">"inquirer"</span>;</span><br><span class="line"><span class="keyword">import</span> { spawn } <span class="keyword">from</span> <span class="string">"child_process"</span>;</span><br><span class="line"><span class="comment">// const inquirer = require('inquirer');</span></span><br><span class="line"><span class="comment">// const { spawn } = require('child_process');</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> runCommand = <span class="function">(<span class="params">command, args</span>) =></span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =></span> {</span><br><span class="line"> <span class="keyword">const</span> executedCommand = spawn(command, args, {</span><br><span class="line"> stdio: <span class="string">"inherit"</span>,</span><br><span class="line"> shell: <span class="literal">true</span>,</span><br><span class="line"> });</span><br><span class="line"> executedCommand.on(<span class="string">"error"</span>, (error) => {</span><br><span class="line"> reject({ error, <span class="attr">message</span>: <span class="literal">null</span>, <span class="attr">code</span>: <span class="literal">null</span> });</span><br><span class="line"> });</span><br><span class="line"> executedCommand.on(<span class="string">"exit"</span>, (code) => {</span><br><span class="line"> <span class="keyword">if</span> (code === <span class="number">0</span>) {</span><br><span class="line"> resolve({ <span class="attr">error</span>: <span class="literal">null</span>, <span class="attr">message</span>: <span class="literal">null</span>, code });</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> reject({ <span class="attr">error</span>: <span class="literal">null</span>, <span class="attr">message</span>: <span class="literal">null</span>, code });</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> executedCommand.on(<span class="string">"message"</span>, (message) => {</span><br><span class="line"> resolve({ <span class="attr">error</span>: <span class="literal">null</span>, <span class="attr">message</span>: message, <span class="attr">code</span>: <span class="literal">null</span> });</span><br><span class="line"> });</span><br><span class="line"> });</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">execStandardVersion</span>(<span class="params">res</span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> { bumpType, isPrerelease, prereleaseType, tagPrefix } = res;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">let</span> cmd = <span class="string">`standard-version --release-as <span class="subst">${bumpType}</span> `</span>;</span><br><span class="line"> <span class="keyword">if</span> (isPrerelease) {</span><br><span class="line"> cmd += <span class="string">` --prerelease <span class="subst">${prereleaseType}</span> `</span>;</span><br><span class="line"> }</span><br><span class="line"> cmd += <span class="string">` --tag-prefix <span class="subst">${tagPrefix}</span> `</span>;</span><br><span class="line"> cmd += <span class="string">" --infile CHANGELOG.md "</span>;</span><br><span class="line"></span><br><span class="line"> <span class="built_in">console</span>.info(<span class="string">`\nExecuting: <span class="subst">${cmd}</span> \n`</span>);</span><br><span class="line"></span><br><span class="line"> runCommand(cmd)</span><br><span class="line"> .then(<span class="function">(<span class="params">{ message }</span>) =></span> {</span><br><span class="line"> <span class="built_in">console</span>.info(<span class="string">"\nPlease checkout recent commit, and then"</span>);</span><br><span class="line"> <span class="built_in">console</span>.info(<span class="string">"Push branch and new tag to git repository, publish package to npm"</span>);</span><br><span class="line"> <span class="comment">// message && console.info(message)</span></span><br><span class="line"> })</span><br><span class="line"> .catch(<span class="function">(<span class="params">{ error, code }</span>) =></span> {</span><br><span class="line"> code && <span class="built_in">console</span>.error(<span class="string">"Error: process exit code"</span> + code);</span><br><span class="line"> error && <span class="built_in">console</span>.error(error);</span><br><span class="line"> });</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">inquirer</span><br><span class="line"> .prompt([</span><br><span class="line"> {</span><br><span class="line"> type: <span class="string">"list"</span>,</span><br><span class="line"> name: <span class="string">"bumpType"</span>,</span><br><span class="line"> message: <span class="string">"Which type you want bump"</span>,</span><br><span class="line"> choices: [<span class="string">"major"</span>, <span class="string">"minor"</span>, <span class="string">"patch"</span>],</span><br><span class="line"> loop: <span class="literal">false</span>,</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> type: <span class="string">"confirm"</span>,</span><br><span class="line"> name: <span class="string">"isPrerelease"</span>,</span><br><span class="line"> message: <span class="string">"Is a prerelease? Default is no"</span>,</span><br><span class="line"> <span class="keyword">default</span>: <span class="literal">false</span>,</span><br><span class="line"> loop: <span class="literal">false</span>,</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> type: <span class="string">"list"</span>,</span><br><span class="line"> name: <span class="string">"prereleaseType"</span>,</span><br><span class="line"> message: <span class="string">"What is the current stage"</span>,</span><br><span class="line"> choices: [<span class="string">"alpha"</span>, <span class="string">"beta"</span>],</span><br><span class="line"> when: <span class="function">(<span class="params">answer</span>) =></span> {</span><br><span class="line"> <span class="keyword">return</span> answer.isPrerelease;</span><br><span class="line"> },</span><br><span class="line"> loop: <span class="literal">false</span>,</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> type: <span class="string">"input"</span>,</span><br><span class="line"> name: <span class="string">"tagPrefix"</span>,</span><br><span class="line"> message: <span class="string">"Input git tag prefix, default is v"</span>,</span><br><span class="line"> <span class="keyword">default</span>: <span class="string">"v"</span>,</span><br><span class="line"> loop: <span class="literal">false</span>,</span><br><span class="line"> },</span><br><span class="line"> ])</span><br><span class="line"> .then(execStandardVersion);</span><br></pre></td></tr></table></figure><h3 id="二-部署脚步"><a class="header-anchor" href="#二-部署脚步">¶</a>二、部署脚步</h3><p> 待补充</p>]]></content>
<summary type="html">
<p> 最近开发 npm 包,发现基于 <a href="https://github.com/SBoudrias/Inquirer.js" target="_blank" rel="noopener">inquirer.js</a> 写交互式脚本很方便,尤其是一些部署脚本和打版本号之类的脚步,记录一下。</p>
</summary>
<category term="前端" scheme="https://liuxianyu.cn/categories/%E5%89%8D%E7%AB%AF/"/>
<category term="git" scheme="https://liuxianyu.cn/tags/git/"/>
</entry>
<entry>
<title>使用 pnpm pack 命令本地调试 npm 包</title>
<link href="https://liuxianyu.cn/article/pnpm-pack.html"/>
<id>https://liuxianyu.cn/article/pnpm-pack.html</id>
<published>2024-05-29T21:01:50.000Z</published>
<updated>2024-05-29T21:01:50.000Z</updated>
<content type="html"><![CDATA[<p> 最近本地开发 npm 包,发现 <code>pnpm pack</code> 可以很方便的验证效果,比 <code>pnpm link</code> 好用点,不需要频繁的发 beta 包,记录一下。</p><a id="more"></a><h3 id="一-npm-包"><a class="header-anchor" href="#一-npm-包">¶</a>一、npm 包</h3><p> 在 npm 包的根目录<strong>先执行构建命令</strong>,一般是 <code>pnpm build</code>,再执行 <code>pnpm pack</code> 命令,会出现一个包名加版本号的 tgz 压缩文件,压缩包的内容和 npm publish 发布的包内容一致,这样就可以更准确的验证效果了。建议执行命令前修改下 npm 包 <code>package.json</code> 文件的 <code>version</code> 属性,方便区分。</p><h3 id="二-目标项目"><a class="header-anchor" href="#二-目标项目">¶</a>二、目标项目</h3><p> 删除 node_modules,复制上述 tgz 压缩文件的绝对路径,在目标项目的根目录执行下方命令,建议执行后再补一次 <code>pnpm i</code>。<code>-w</code> 是 workspace 的标识,可以忽略。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pnpm install /Users/liuyi/Desktop/Projects/dtstack/monaco-sql-languages/monaco-sql-languages-0.12.3-beta.0.tgz -w</span><br></pre></td></tr></table></figure><p><img src="https://images-hosting.liuxianyu.cn/posts/pnpm-pack/1.png" alt></p><h3 id="三-其他"><a class="header-anchor" href="#三-其他">¶</a>三、其他</h3><p> 推荐一个好用的 VSCode 插件 <code>Search node_modules</code>,自行设置快捷键后,可以快速的找到 node_modules 中依赖的目录结构和路径。</p><p><img src="https://images-hosting.liuxianyu.cn/posts/pnpm-pack/2.png" alt></p>]]></content>
<summary type="html">
<p> 最近本地开发 npm 包,发现 <code>pnpm pack</code> 可以很方便的验证效果,比 <code>pnpm link</code> 好用点,不需要频繁的发 beta 包,记录一下。</p>
</summary>
<category term="前端" scheme="https://liuxianyu.cn/categories/%E5%89%8D%E7%AB%AF/"/>
<category term="npm" scheme="https://liuxianyu.cn/tags/npm/"/>
<category term="pnpm" scheme="https://liuxianyu.cn/tags/pnpm/"/>
</entry>
<entry>
<title>ProxyNT 安装与使用</title>
<link href="https://liuxianyu.cn/article/proxynt.html"/>
<id>https://liuxianyu.cn/article/proxynt.html</id>
<published>2024-04-21T22:12:39.000Z</published>
<updated>2024-04-21T22:12:39.000Z</updated>
<content type="html"><![CDATA[<p> 记录一下如何将闲置电脑借助 <a href="https://github.com/sazima/proxynt" target="_blank" rel="noopener">ProxyNT</a> 改造成可以公网访问的服务器,原理是反向代理。</p><a id="more"></a><p> ProxyNT:<a href="https://github.com/sazima/proxynt" target="_blank" rel="noopener">https://github.com/sazima/proxynt</a> 。后续操作需要有公网服务器,假设公网 IP 为 <code>43.25.35.231</code>。</p><h3 id="公网服务器-服务端"><a class="header-anchor" href="#公网服务器-服务端">¶</a>公网服务器(服务端)</h3><p> 1. 先安装 python pip:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yum install python3-pip -y</span><br></pre></td></tr></table></figure><p> 2. 然后通过清华源安装 <code>proxynt</code>:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip3 install -U -i https://pypi.tuna.tsinghua.edu.cn/simple proxynt</span><br></pre></td></tr></table></figure><p> 3. 在常用目录下新建 <code>nt_server_config.json</code> 配置文件:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">mkdir /mnt/proxynt</span><br><span class="line">cd /mnt/proxynt</span><br><span class="line">vim nt_server_config.json</span><br></pre></td></tr></table></figure><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"port"</span>: <span class="number">6999</span>,</span><br><span class="line"> <span class="attr">"path"</span>: <span class="string">"/proxynt"</span>,</span><br><span class="line"> <span class="attr">"password"</span>: <span class="string">"helloworld"</span>,</span><br><span class="line"> <span class="attr">"log_file"</span>: <span class="string">"/mnt/proxynt/logs/nt_server.log"</span>,</span><br><span class="line"> <span class="attr">"admin"</span>: {</span><br><span class="line"> <span class="attr">"enable"</span>: <span class="literal">true</span>,</span><br><span class="line"> <span class="attr">"admin_password"</span>: <span class="string">"new_password"</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p> 4. 在公网服务器提供方的控制台页面新增安全组:6990-7010 范围端口备用。</p><p> 5. 执行以下命令在前台运行服务端程序:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">nt_server -c nt_server_config.json</span><br></pre></td></tr></table></figure><h3 id="闲置电脑-客户端"><a class="header-anchor" href="#闲置电脑-客户端">¶</a>闲置电脑(客户端)</h3><p> 安装 CentOS 系统的方式自行搜索,这里用的是 <code>CentOS 7.9</code>。</p><p> 1. 先安装 python pip:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yum install python3-pip -y</span><br></pre></td></tr></table></figure><p> 2. 然后通过清华源安装 proxynt:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip3 install -U -i https://pypi.tuna.tsinghua.edu.cn/simple proxynt</span><br></pre></td></tr></table></figure><p> 3. 在常用目录下新建 <code>nt_client_config.json</code> 配置文件:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">mkdir /mnt/proxynt</span><br><span class="line">cd /mnt/proxynt</span><br><span class="line">vim nt_client_config.json</span><br></pre></td></tr></table></figure><p> 注意下方 URL 中的 IP 为公网服务器 IP,端口为 <code>proxynt</code> 服务端配置文件设置的端口:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"server"</span>: {</span><br><span class="line"> <span class="attr">"url"</span>: <span class="string">"ws://43.25.35.231:6999/proxynt"</span>,</span><br><span class="line"> <span class="attr">"password"</span>: <span class="string">"helloworld"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"client_name"</span>: <span class="string">"home_pc"</span>,</span><br><span class="line"> <span class="attr">"log_file"</span>: <span class="string">"/mnt/proxynt/logs/nt_client.log"</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p> 4. 执行以下命令在前台运行客户端程序:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">nt_client -c nt_client_config.json</span><br></pre></td></tr></table></figure><p> 如果安全组设置正确并且服务端已经正常启动,则可以看到 <code>success</code> 提示。</p><h3 id="新增配置"><a class="header-anchor" href="#新增配置">¶</a>新增配置</h3><p> 在浏览器访问以下地址,输入上方服务端配置文件中设置的 admin 密码(<code>new_password</code>)即可进入管理页面。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">http://43.25.35.231:6999/proxynt/admin</span><br></pre></td></tr></table></figure><p> 可以在已经有的 nginx 配置里添加一段,以通过域名访问管理页面:</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">location</span> /proxynt {</span><br><span class="line"> <span class="attribute">proxy_pass</span> http://127.0.0.1:6999;</span><br><span class="line"> <span class="attribute">proxy_http_version</span> <span class="number">1</span>.<span class="number">1</span>;</span><br><span class="line"> <span class="attribute">proxy_set_header</span> Upgrade <span class="variable">$http_upgrade</span>;</span><br><span class="line"> <span class="attribute">proxy_set_header</span> Connection <span class="string">"upgrade"</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p> 如果服务端和客户端服务都正常运行,则可以看到 <code>online</code>:</p><p><img src="https://images-hosting.liuxianyu.cn/posts/proxynt/1.png" alt></p><p> 后续闲置电脑上启服务新增配置时,远程端口为公网服务器刚刚设置范围安全组的一个值,可以递增的转发出去,闲置电脑上启动的服务端口填在本地端口,本地 ip 默认 <code>127.0.0.1</code> 即可。</p><p><img src="https://images-hosting.liuxianyu.cn/posts/proxynt/2.png" alt></p><h3 id="ssh-连接"><a class="header-anchor" href="#ssh-连接">¶</a>SSH 连接</h3><p> 按上述新增配置的截图添加一个闲置电脑的 22 端口用于 SSH 远程连接。</p><p> 使用以下命令在输入密码后即可远程连接到闲置电脑,后续在公司也可以远程连家里的闲置电脑。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ssh [email protected] -p 7000</span><br></pre></td></tr></table></figure><p> 添加秘钥到闲置电脑,后续 ssh 连接时不再需要输入密码:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ssh-copy-id -oPort=7001 [email protected]</span><br></pre></td></tr></table></figure><h3 id="服务自启动"><a class="header-anchor" href="#服务自启动">¶</a>服务自启动</h3><p> 接下来的内容涉及到 <code>Linux systemd</code> 的知识,可以自行搜索补充。</p><p> 由于刚刚都是在前台运行验证效果,日常使用中我们可以将刚刚启动服务端和客户端程序的命令设置为自启动。</p><h4 id="公网服务器-服务端-v2"><a class="header-anchor" href="#公网服务器-服务端-v2">¶</a>公网服务器(服务端)</h4><p> 1. 使用以下命令查看 <code>nt_server</code> 的路径,要和下方的 <code>ExecStart</code> 开头保持一致。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">which nt_server</span><br></pre></td></tr></table></figure><p> 2. 进入自启动脚本的默认目录:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">cd /lib/systemd/system</span><br><span class="line">vim proxynt_server.service</span><br></pre></td></tr></table></figure><p> 3. 填入以下内容:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">[Unit]</span><br><span class="line">Description=nat_traversal server</span><br><span class="line">After=network.target</span><br><span class="line"></span><br><span class="line">[Service]</span><br><span class="line">Type=simple</span><br><span class="line">User=root</span><br><span class="line">Restart=on-failure</span><br><span class="line">RestartSec=5s</span><br><span class="line">ExecStart=/usr/local/bin/nt_server -c /mnt/proxynt/nt_server_config.json</span><br><span class="line"></span><br><span class="line">[Install]</span><br><span class="line">WantedBy=multi-user.target</span><br></pre></td></tr></table></figure><p> 4. 加载上述自启动文件:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">systemctl daemon-reload</span><br></pre></td></tr></table></figure><p> 5. 允许开机自启:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">systemctl enable proxynt_server.service</span><br></pre></td></tr></table></figure><p> 6. 立即启动:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">systemctl start proxynt_server.service</span><br></pre></td></tr></table></figure><h4 id="闲置电脑-客户端-v2"><a class="header-anchor" href="#闲置电脑-客户端-v2">¶</a>闲置电脑(客户端)</h4><p> 1. 使用以下命令查看 <code>nt_client</code> 的路径,要和下方的 <code>ExecStart</code> 开头保持一致。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">which nt_client</span><br></pre></td></tr></table></figure><p> 2. 进入自启动脚本的默认目录:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">cd /lib/systemd/system</span><br><span class="line">vim proxynt_client.service</span><br></pre></td></tr></table></figure><p> 3. 填入以下内容:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">[Unit]</span><br><span class="line">Description=nat_traversal client</span><br><span class="line">After=network.target</span><br><span class="line"></span><br><span class="line">[Service]</span><br><span class="line">Type=simple</span><br><span class="line">User=root</span><br><span class="line">Restart=on-failure</span><br><span class="line">RestartSec=5s</span><br><span class="line">ExecStart=/usr/local/bin/nt_client -c /mnt/proxynt/nt_client_config.json</span><br><span class="line"></span><br><span class="line">[Install]</span><br><span class="line">WantedBy=multi-user.target</span><br></pre></td></tr></table></figure><p> 4. 加载上述自启动文件:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">systemctl daemon-reload</span><br></pre></td></tr></table></figure><p> 5. 允许开机自启:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">systemctl enable proxynt_client.service</span><br></pre></td></tr></table></figure><p> 6. 立即启动:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">systemctl start proxynt_client.service</span><br></pre></td></tr></table></figure><h3 id="更新"><a class="header-anchor" href="#更新">¶</a>更新</h3><p> 有更新时,服务端和客户端都需要进行升级:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip3 install -U -i https://pypi.tuna.tsinghua.edu.cn/simple proxynt</span><br></pre></td></tr></table></figure><h3 id="写在最后"><a class="header-anchor" href="#写在最后">¶</a>写在最后</h3><p> 至此,你又有了一台公网服务器,性能用于学习足够了。</p>]]></content>
<summary type="html">
<p> 记录一下如何将闲置电脑借助 <a href="https://github.com/sazima/proxynt" target="_blank" rel="noopener">ProxyNT</a> 改造成可以公网访问的服务器,原理是反向代理。</p>
</summary>
<category term="Cent OS" scheme="https://liuxianyu.cn/categories/Cent-OS/"/>
<category term="Linux" scheme="https://liuxianyu.cn/tags/Linux/"/>
<category term="Cent OS" scheme="https://liuxianyu.cn/tags/Cent-OS/"/>
</entry>
<entry>
<title>React 中使用 AntD Upload 组件时手动触发文件选择框</title>
<link href="https://liuxianyu.cn/article/react-antd-upload.html"/>
<id>https://liuxianyu.cn/article/react-antd-upload.html</id>
<published>2024-01-24T15:58:58.000Z</published>
<updated>2024-01-24T15:58:58.000Z</updated>
<content type="html"><![CDATA[<p> 最近有个需求,需要在点击上传按钮时先调用接口判断是否满足上传的条件,而不是在上传后校验失败后进行报错,将校验前置。过程中没查到其他有用的资料,这里记录下。</p><a id="more"></a><h3 id="一-实现效果"><a class="header-anchor" href="#一-实现效果">¶</a>一、实现效果</h3><p><img src="https://images-hosting.liuxianyu.cn/posts/react-antd-upload/1.gif" alt></p><h3 id="二-实现代码"><a class="header-anchor" href="#二-实现代码">¶</a>二、实现代码</h3><h4 id="html"><a class="header-anchor" href="#html">¶</a>html</h4><p> 先将 <code>Upload</code> 组件的 <code>openFileDialogOnClick</code> 属性置为 false,点击不打开文件对话框;<code>Upload</code> 的父级 div 加上 ref。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">ref</span>=<span class="string">{(el:</span> <span class="attr">any</span>) =></span> (this.uploadEle = el)}></span><br><span class="line"> <span class="tag"><<span class="name">Upload</span> {<span class="attr">...upLoadProps</span>} <span class="attr">openFileDialogOnClick</span>=<span class="string">{false}</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">Button</span></span></span><br><span class="line"><span class="tag"> <span class="attr">type</span>=<span class="string">"primary"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">loading</span>=<span class="string">{loading}</span></span></span><br><span class="line"><span class="tag"> <span class="attr">onClick</span>=<span class="string">{this.handleImport}</span></span></span><br><span class="line"><span class="tag"> ></span></span><br><span class="line"> 导入文件</span><br><span class="line"> <span class="tag"></<span class="name">Button</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">Upload</span>></span></span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br></pre></td></tr></table></figure><h4 id="javascript"><a class="header-anchor" href="#javascript">¶</a>JavaScript</h4><p> 点击按钮,先进行接口校验,校验成功则弹出文件选择框,失败则弹提示。通过父级 div 去拿文件上传 input 的 dom,然后手动触发 <code>click</code> 事件。</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">handleImport = <span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> <span class="keyword">this</span>.setState({ loading: <span class="literal">true</span> });</span><br><span class="line"> API.checkImport().then(<span class="function">(<span class="params">{ data }</span>) =></span> {</span><br><span class="line"> <span class="keyword">if</span> (data) {</span><br><span class="line"> (<span class="keyword">this</span>.uploadEle <span class="keyword">as</span> <span class="built_in">any</span>)?.querySelector(<span class="string">'input[type=file]'</span>)?.click?.();</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> message.warning(<span class="string">'校验失败'</span>);</span><br><span class="line"> }</span><br><span class="line"> }).finally(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> <span class="keyword">this</span>.setState({ loading: <span class="literal">false</span> }</span><br><span class="line"> });</span><br><span class="line">};</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<p> 最近有个需求,需要在点击上传按钮时先调用接口判断是否满足上传的条件,而不是在上传后校验失败后进行报错,将校验前置。过程中没查到其他有用的资料,这里记录下。</p>
</summary>
<category term="前端" scheme="https://liuxianyu.cn/categories/%E5%89%8D%E7%AB%AF/"/>
<category term="antd" scheme="https://liuxianyu.cn/tags/antd/"/>
</entry>
<entry>
<title>Linux 定时备份 MySQL (内有 docker 版)</title>
<link href="https://liuxianyu.cn/article/linux-crontab.html"/>
<id>https://liuxianyu.cn/article/linux-crontab.html</id>
<published>2024-01-08T14:02:17.000Z</published>
<updated>2024-01-08T14:02:17.000Z</updated>
<content type="html"><![CDATA[<p> Linux 中的定时任务一般通过 <code>crontab</code> 命令来管理,这里记录下常用的一些任务。</p><a id="more"></a><h3 id="一-常用命令"><a class="header-anchor" href="#一-常用命令">¶</a>一、常用命令</h3><ul><li><code>crontab -l</code> 查看当前用户的定时任务列表</li><li><code>crontab -e</code> 编辑当前用户的定时任务列表</li><li><code>service crond status</code> 查看服务状态</li><li><code>service crond start</code> 启动服务</li><li><code>service crond stop</code> 关闭服务</li><li><code>service crond restart</code> 重启服务</li><li><code>service crond reload</code> 重新载入配置</li></ul><h3 id="二-命令脚本"><a class="header-anchor" href="#二-命令脚本">¶</a>二、命令脚本</h3><p> 先创建存放脚本的文件夹:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mkdir -p /mnt/mysqldump</span><br></pre></td></tr></table></figure><p> 再编辑脚本文件(<code>docker 版</code> 在下方):</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vim /mnt/mysqldump/my-database.sh</span><br></pre></td></tr></table></figure><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 数据库用户名</span></span><br><span class="line">username=root</span><br><span class="line"><span class="comment"># 数据库密码</span></span><br><span class="line">password=123456</span><br><span class="line"><span class="comment"># 要备份的数据库名称</span></span><br><span class="line">database_name=my-database</span><br><span class="line"><span class="comment"># 保存备份个数,备份30天数据</span></span><br><span class="line">number=30</span><br><span class="line"><span class="comment"># 日期格式</span></span><br><span class="line">day=`date +%Y%m%d`</span><br><span class="line"><span class="comment"># 备份保存路径</span></span><br><span class="line">backup_dir=/mnt/mysqldump/<span class="variable">$day</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># 如果文件夹不存在则创建</span></span><br><span class="line"><span class="keyword">if</span> [ ! -d <span class="variable">$backup_dir</span> ];</span><br><span class="line"><span class="keyword">then</span></span><br><span class="line"> mkdir -p <span class="variable">$backup_dir</span>;</span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 简单写法 mysqldump -uroot -p123456 my-database > /mnt/my-database.sql</span></span><br><span class="line">mysqldump -u<span class="variable">$username</span> -p<span class="variable">$password</span> <span class="variable">$database_name</span> > <span class="variable">$backup_dir</span>/<span class="variable">$database_name</span>.sql</span><br><span class="line"></span><br><span class="line"><span class="comment"># 写创建备份日志</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">"create <span class="variable">$backup_dir</span>/<span class="variable">$database_name</span>.dump"</span> >> <span class="variable">$backup_dir</span>/log.txt</span><br><span class="line"></span><br><span class="line"><span class="comment"># 删除超期的备份</span></span><br><span class="line">delfile=`ls -l -crt <span class="variable">$backup_dir</span>/*.sql | awk <span class="string">'{print $9 }'</span> | head -1`</span><br><span class="line"></span><br><span class="line"><span class="comment"># 判断现在的备份数量是否大于 $number</span></span><br><span class="line">count=`ls -l -crt <span class="variable">$backup_dir</span>/*.sql | awk <span class="string">'{print $9 }'</span> | wc -l`</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> [ <span class="variable">$count</span> -gt <span class="variable">$number</span> ]</span><br><span class="line"><span class="keyword">then</span></span><br><span class="line"> <span class="comment"># 删除更早生成的备份,只保留 $number 数量的备份</span></span><br><span class="line"> rm <span class="variable">$delfile</span></span><br><span class="line"> <span class="comment"># 写删除文件日志</span></span><br><span class="line"> <span class="built_in">echo</span> <span class="string">"delete <span class="variable">$delfile</span>"</span> >> <span class="variable">$backup_dir</span>/log.txt</span><br><span class="line"><span class="keyword">fi</span></span><br></pre></td></tr></table></figure><p> 上述脚本仅需关注 <code>username</code>、<code>password</code>、<code>database_name</code>、<code>number</code> 等变量;通过 ls 命令获取第 9 列(文件名列),再通过实现定义操作时间最晚的那个需要删除的文件。</p><blockquote><p><strong>注意</strong><br> 执行命令可能提示 <code>You have new mail.</code>,输入 <code>mail</code> 查看 crontab 返回的信息,输入数字查看对应的信息</p></blockquote><p> <code>mail</code> 交互式界面中的常用命令:</p><ul><li><code>n</code> 查看下一封邮件</li><li><code>p</code> 查看上一封邮件</li><li><code>d 1 2</code> 按编号删除邮件,不填编号时删除当前邮件</li><li><code>d *</code> 删除所有邮件</li><li><code>u 1 2</code> 按编号恢复删除的邮件</li><li><code>q</code> 退出 mail 界面</li><li><code>h</code> 显示邮件列表的头部信息,包括邮件的发件人、主题等</li><li><code>?</code> 显示帮助信息</li></ul><p><img src="https://images-hosting.liuxianyu.cn/posts/linux-crontab/1.png" alt></p><p> 给脚本文件授权,否则会在 mail 中看到 <code>Permission denied</code>:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">chmod +x /mnt/mysqldump/*.sh</span><br></pre></td></tr></table></figure><h3 id="crontab"><a class="header-anchor" href="#crontab">¶</a>crontab</h3><p> cron 读取一个或多个配置文件,这些配置文件中包含了命令行及其调用时间。cron 的配置文件称为 <code>crontab</code>,是 <code>cron table</code> 的简写。</p><h4 id="crontab-语法"><a class="header-anchor" href="#crontab-语法">¶</a>crontab 语法</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"> minute hour day-of-month month-of-year day-of-week commands</span><br><span class="line">合法值 00-59 00-23 01-31 01-12 0-6 (0 is sunday)</span><br></pre></td></tr></table></figure><p> 除了数字还有几个个特殊的符号就是 <code>*</code> <code>/</code> 和 <code>-</code> <code>,</code>,<code>*</code> 代表所有的取值范围内的数字,<code>/</code> 代表每的意思,<code>/5</code> 表示每 5 个单位,<code>-</code> 代表从某个数字到某个数字,<code>,</code> 分开几个离散的数字。</p><h4 id="添加-cron-定时任务"><a class="header-anchor" href="#添加-cron-定时任务">¶</a>添加 cron 定时任务</h4><p> <code>crontab -e</code> 编辑任务文件,添加以下内容:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">50 23 * * * /mnt/mysqldump/my-database.sh</span><br></pre></td></tr></table></figure><p> 可以使用 <code>crontab -l</code> 查看任务是否已添加成功。</p><h3 id="docker-版"><a class="header-anchor" href="#docker-版">¶</a>docker 版</h3><p> 当通过 docker 安装 MySQL 时,也可以进行备份,比常规版新增了 <code>container_name</code>、<code>container_tmp_dir</code> 参数,需要注意 <code>container_name</code> 的值。使用下方脚本:</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 数据库用户名</span></span><br><span class="line">username=root</span><br><span class="line"><span class="comment"># 数据库密码</span></span><br><span class="line">password=123456</span><br><span class="line"><span class="comment"># 要备份的数据库名称</span></span><br><span class="line">database_name=my-database</span><br><span class="line"><span class="comment"># docker 容器名</span></span><br><span class="line">container_name=mysql-5.7</span><br><span class="line"><span class="comment"># 容器内临时存放的路径</span></span><br><span class="line">container_tmp_dir=/tmp</span><br><span class="line"><span class="comment"># 保存备份个数,备份30天数据</span></span><br><span class="line">number=30</span><br><span class="line"><span class="comment"># 日期格式</span></span><br><span class="line">day=`date +%Y%m%d`</span><br><span class="line"><span class="comment"># 宿主机备份保存路径</span></span><br><span class="line">backup_dir=/mnt/mysqldump/<span class="variable">$day</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># 如果文件夹不存在则创建</span></span><br><span class="line"><span class="keyword">if</span> [ ! -d <span class="variable">$backup_dir</span> ];</span><br><span class="line"><span class="keyword">then</span></span><br><span class="line"> mkdir -p <span class="variable">$backup_dir</span>;</span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line">container_sql_path=<span class="variable">$container_tmp_dir</span>/<span class="variable">$database_name</span>.sql</span><br><span class="line"><span class="comment"># docker exec mysql-5.7 bash -c "mysqldump -uroot -pMysql..1234 $database_name > /tmp/my-database.sql"</span></span><br><span class="line">docker <span class="built_in">exec</span> <span class="variable">$container_name</span> bash -c <span class="string">"mysqldump -u<span class="variable">$username</span> -p<span class="variable">$password</span> <span class="variable">$database_name</span> > <span class="variable">$container_sql_path</span>"</span></span><br><span class="line"><span class="comment"># 将容器内的文件挪到宿主机</span></span><br><span class="line">docker cp <span class="variable">$container_name</span>:<span class="variable">$container_sql_path</span> <span class="variable">$backup_dir</span></span><br><span class="line"><span class="comment"># 删除容器内的临时文件</span></span><br><span class="line">docker <span class="built_in">exec</span> <span class="variable">$container_name</span> rm -rf <span class="variable">$container_sql_path</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 写创建备份日志</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">"create <span class="variable">$backup_dir</span>/<span class="variable">$database_name</span>-<span class="variable">$dd</span>.dump"</span> >> <span class="variable">$backup_dir</span>/log.txt</span><br><span class="line"></span><br><span class="line"><span class="comment"># 删除超期的备份</span></span><br><span class="line">delfile=`ls -l -crt <span class="variable">$backup_dir</span>/*.sql | awk <span class="string">'{print $9 }'</span> | head -1`</span><br><span class="line"></span><br><span class="line"><span class="comment"># 判断现在的备份数量是否大于 $number</span></span><br><span class="line">count=`ls -l -crt <span class="variable">$backup_dir</span>/*.sql | awk <span class="string">'{print $9 }'</span> | wc -l`</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> [ <span class="variable">$count</span> -gt <span class="variable">$number</span> ]</span><br><span class="line"><span class="keyword">then</span></span><br><span class="line"> <span class="comment"># 删除更早生成的备份,只保留 $number 数量的备份</span></span><br><span class="line"> rm <span class="variable">$delfile</span></span><br><span class="line"> <span class="comment"># 写删除文件日志</span></span><br><span class="line"> <span class="built_in">echo</span> <span class="string">"delete <span class="variable">$delfile</span>"</span> >> <span class="variable">$backup_dir</span>/log.txt</span><br><span class="line"><span class="keyword">fi</span></span><br></pre></td></tr></table></figure><h3 id="参考资料:"><a class="header-anchor" href="#参考资料:">¶</a>参考资料:</h3><p> <a href="https://segmentfault.com/a/1190000040642688#item-4" target="_black">编写BASH维护固定数量备份文件</a></p>]]></content>
<summary type="html">
<p> Linux 中的定时任务一般通过 <code>crontab</code> 命令来管理,这里记录下常用的一些任务。</p>
</summary>
<category term="Linux" scheme="https://liuxianyu.cn/categories/Linux/"/>
<category term="Linux" scheme="https://liuxianyu.cn/tags/Linux/"/>
</entry>
<entry>
<title>MySQL 备份 —— 导出导入 sql 文件</title>
<link href="https://liuxianyu.cn/article/mysqldump.html"/>
<id>https://liuxianyu.cn/article/mysqldump.html</id>
<published>2024-01-07T21:46:05.000Z</published>
<updated>2024-01-07T21:46:05.000Z</updated>
<content type="html"><![CDATA[<p> 最近在学习 MySQL 的备份和恢复,我的实际应用场景是正式服数据同步到测试服用于测试、备份生产环境的数据库。</p><a id="more"></a><p> 我的 MySQL 是通过 docker 安装的,非容器的可以执行双引号中的命令;可以通过 <code>ls -lhS</code> 命令在目录下查看是否已存在文件。下方实例中的 <code>123456</code> 为数据库密码,<code>my-database</code> 是数据库名。</p><h3 id="一-数据备份"><a class="header-anchor" href="#一-数据备份">¶</a>一、数据备份</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker exec mysql-5.7-backup bash -c "mysqldump -uroot -p123456 my-database > /tmp/my-database.sql"</span><br></pre></td></tr></table></figure><blockquote><p><strong>注意</strong><br> <code>-p</code> 后面跟密码,加空格会被识别为 database;也可以不跟密码,回车后输入密码</p></blockquote><p> 导出后的文件还在 docker 容器中,可以通过 <code>docker cp</code> 命令将备份的 sql 文件拷贝到宿主机,便于后面在其他 docker 容器内使用。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker cp mysql-5.7:/tmp/my-database.sql /opt/</span><br></pre></td></tr></table></figure><p> 同时备份多个数据库</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker exec mysql-5.7-backup bash -c "mysqldump -uroot -p123456 --databases dbname2 dbname2 > Backup.sql"</span><br></pre></td></tr></table></figure><h3 id="二-数据还原"><a class="header-anchor" href="#二-数据还原">¶</a>二、数据还原</h3><p> 还原使用 <code>mysqldump</code> 命令备份的数据库,先将导出的 sql 文件通过 <code>docker cp</code> 命令拷贝到备份的 mysql 容器内。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker cp /opt/my-database.sql mysql-5.7-backup:/tmp/</span><br></pre></td></tr></table></figure><p> 再进行导入:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker exec mysql-5.7-backup bash -c "mysql -uroot -p123456 my-database < /tmp/my-database.sql"</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<p> 最近在学习 MySQL 的备份和恢复,我的实际应用场景是正式服数据同步到测试服用于测试、备份生产环境的数据库。</p>
</summary>
<category term="MySQL" scheme="https://liuxianyu.cn/categories/MySQL/"/>
<category term="MySQL" scheme="https://liuxianyu.cn/tags/MySQL/"/>
</entry>
<entry>
<title>npm 包相关命令</title>
<link href="https://liuxianyu.cn/article/npm-commands.html"/>
<id>https://liuxianyu.cn/article/npm-commands.html</id>
<published>2023-04-02T18:43:56.000Z</published>
<updated>2023-04-02T18:43:56.000Z</updated>
<content type="html"><![CDATA[<p> 整理下 <code>npm</code> 包相关的一些命令,主要包括 <code>npm</code>、<code>yarn</code>、<code>pnpm</code>。</p><a id="more"></a><h3 id="一-npm"><a class="header-anchor" href="#一-npm">¶</a>一、npm</h3><h4 id="npm-publish"><a class="header-anchor" href="#npm-publish">¶</a>npm publish</h4><p> 可以通过 `nrm`` 管理源,方便切换。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm config set registry https://registry.npmjs.org</span><br></pre></td></tr></table></figure><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm whoami</span><br></pre></td></tr></table></figure><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm login</span><br></pre></td></tr></table></figure><p> npm 包可以带上 tag 标识</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm publish</span><br></pre></td></tr></table></figure><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm publish --tag=beta</span><br></pre></td></tr></table></figure><p> 当发布私有包(如 <code>@liuxy0551/dt-sql-parser</code>)时,npm 会要求付费,报错:<code>You must sign up for private package</code>,此时需要带上 <code>--access public</code> 标识</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm publish --tag=beta --access public</span><br></pre></td></tr></table></figure><p> npm 包版本切换 tag</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm dist-tag add [email protected] latest</span><br></pre></td></tr></table></figure><p> npm 包撤销某个版本,仅在发布 24 小时内有效,撤销后该版本不可再用。如果非必须撤销,可以考虑废弃该版本</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm unpublish [email protected]</span><br></pre></td></tr></table></figure><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm deprecate [email protected] 'This version is deprecated. Please upgrade to later.'</span><br></pre></td></tr></table></figure><p> npm 撤销某个包,也可以废弃某个包</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm unpublish my-package-demo -f</span><br></pre></td></tr></table></figure><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm deprecate my-package-demo 'This version is deprecated. Please use other packages to instead.'</span><br></pre></td></tr></table></figure><h4 id="npm-link"><a class="header-anchor" href="#npm-link">¶</a>npm link</h4><p> 被依赖的包</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm link</span><br></pre></td></tr></table></figure><p> 使用依赖的包</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm link my-package-demo</span><br></pre></td></tr></table></figure><p> 去除依赖</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm unlink</span><br></pre></td></tr></table></figure><p> 发布后的包可以同步到淘宝源,建议页面上点击 <code>进行同步</code> 按钮:<code>https://npmmirror.com/package/my-package-demo</code></p><h3 id="yarn"><a class="header-anchor" href="#yarn">¶</a>yarn</h3><p> 待补充</p><h3 id="pnpm"><a class="header-anchor" href="#pnpm">¶</a>pnpm</h3><h4 id="pnpm-link"><a class="header-anchor" href="#pnpm-link">¶</a>pnpm link</h4><p> 被依赖的包</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pnpm link --global</span><br></pre></td></tr></table></figure><p> 使用依赖的包</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pnpm link --global my-package-demo</span><br></pre></td></tr></table></figure><p> 去除依赖</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pnpm unlink</span><br></pre></td></tr></table></figure><p><a href="https://pnpm.io/zh/cli/link" target="_blank" rel="noopener">官方文档</a></p><blockquote><p><strong>注意</strong><br> 如果使用的是 clashx pro,出现连接不上 npm 官方源时可以打开增强模式,如果本地联调中,记得开关增强模式前后重启联调服务</p></blockquote>]]></content>
<summary type="html">
<p> 整理下 <code>npm</code> 包相关的一些命令,主要包括 <code>npm</code>、<code>yarn</code>、<code>pnpm</code>。</p>
</summary>
<category term="npm" scheme="https://liuxianyu.cn/categories/npm/"/>
<category term="npm" scheme="https://liuxianyu.cn/tags/npm/"/>
</entry>
<entry>
<title>在服务器上自建图床</title>
<link href="https://liuxianyu.cn/article/images-hosting.html"/>
<id>https://liuxianyu.cn/article/images-hosting.html</id>
<published>2023-03-10T22:37:22.000Z</published>
<updated>2023-03-10T22:37:22.000Z</updated>
<content type="html"><![CDATA[<p> 至此,博客的图片资源已经经历 github -> 阿里云服务器(1M 带宽) -> 七牛云 -> gitee -> 腾讯云服务器(4M 带宽),一直没有一个稳定的方案,上一次使用的还是 Gitee 图床,可以点击 <a href="https://liuxianyu.cn/article/gitee-image-hosting.html" target="_black">Gitee 图床【已不可用】</a> 查看当时的过程。</p><a id="more"></a><p> 接下来通过把图片等静态资源放到服务器上,再通过 nginx 转发 + 域名生成固定格式的图片链接,记录下过程。</p><h4 id="1-解析域名"><a class="header-anchor" href="#1-解析域名">¶</a>1、解析域名</h4><p> 在对应的服务器控制台解析域名,我这里使用的是 <code>images-hosting.liuxianyu.cn</code>。</p><h4 id="2-申请并下载-ssl-证书"><a class="header-anchor" href="#2-申请并下载-ssl-证书">¶</a>2、申请并下载 SSL 证书</h4><p> 因为博客使用的是 <code>https</code>,所以需要申请一个 SSL 证书,我的域名是在阿里云购买的,每年赠送 20个 免费的证书。申请完成后下载 Nginx 版本的 SSL 证书,重命名并放到服务器指定路径下。</p><h4 id="3-服务器创建文件夹"><a class="header-anchor" href="#3-服务器创建文件夹">¶</a>3、服务器创建文件夹</h4><p> 需要进行的操作:创建 deploy 用户、添加本机的 SSH 公钥到服务器、创建文件夹等</p><ul><li><p>创建 deploy 用户并设置密码,把 deploy 用户添加到 sudo 用户组中 - 参考 <a href="https://liuxianyu.cn/article/cent-os-base.html#%E4%BA%8C-%E6%B7%BB%E5%8A%A0%E6%9C%AC%E6%9C%BA%E7%9A%84-ssh-%E5%88%B0%E6%9C%8D%E5%8A%A1%E5%99%A8">Cent OS 基础环境搭建 - 添加 deploy 用户</a></p></li><li><p>添加本机的 SSH 公钥到服务器 - 参考 <a href="https://liuxianyu.cn/article/cent-os-base.html#%E4%BA%8C-%E6%B7%BB%E5%8A%A0%E6%9C%AC%E6%9C%BA%E7%9A%84-ssh-%E5%88%B0%E6%9C%8D%E5%8A%A1%E5%99%A8">Cent OS 基础环境搭建 - 添加本机的 SSH 到服务器</a></p></li><li><p>在服务器上新建一个 <code>images-hosting</code> 文件夹,用来存储图片等静态资源,并授权给 deploy 用户。</p></li></ul><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">sudo mkdir -p /mnt/projects/images-hosting</span><br><span class="line">cd /mnt/projects/</span><br><span class="line">sudo chown -R deploy:deploy images-hosting</span><br></pre></td></tr></table></figure><h4 id="4-本地环境变量"><a class="header-anchor" href="#4-本地环境变量">¶</a>4、本地环境变量</h4><p> 在本地添加远程服务器的环境变量,避免服务器 IP 暴露在公网。<code>vim ~/.zshrc</code> 添加以下内容后执行 <code>source ~/.zshrc</code>:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># 腾讯云服务器</span><br><span class="line">export tencent="43.139.139.139"</span><br></pre></td></tr></table></figure><h4 id="5-本地图床仓库"><a class="header-anchor" href="#5-本地图床仓库">¶</a>5、本地图床仓库</h4><p> 在 gitee 新建了一个仓库用来做备份,但是由于包含图床内容,gitee 不允许公开仓库。在本地图床仓库中添加 <code>deploy.command</code> 文件:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">echo -e '1、本地压缩资源中...'</span><br><span class="line">gtar -czf images-hosting.tar.gz *</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">echo -e '2、上传压缩包到远程服务器'</span><br><span class="line">scp -P 22 -r images-hosting.tar.gz deploy@$tencent:/mnt/projects/images-hosting/</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">echo -e '3、在远程服务器解压中...'</span><br><span class="line">ssh deploy@$tencent "cd /mnt/projects/images-hosting/; tar -xzf images-hosting.tar.gz; rm -rf images-hosting.tar.gz; ls"</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">echo -e '4、删除本地压缩包'</span><br><span class="line">rm -rf images-hosting.tar.gz</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">echo -e '\n资源已经成功上传到远程服务器啦~'</span><br></pre></td></tr></table></figure><p> 在本地图床仓库中添加 <code>package.json</code> 文件:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> "name": "images-hosting",</span><br><span class="line"> "version": "1.0.0",</span><br><span class="line"> "description": "在服务器上自建图床 https://liuxianyu.cn/article/images-hosting.html",</span><br><span class="line"> "scripts": {</span><br><span class="line"> "deploy": "bash deploy.command",</span><br><span class="line"> "push": "git add . && git commit -m 'new images' && git push"</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>执行 <code>npm run deploy</code> 可将本地图床仓库中的图片等静态资源上传到远程服务器指定的地址了。<br>执行 <code>npm run push</code> 可以提交到远程 git 仓库。</p><h4 id="6-配置-nginx"><a class="header-anchor" href="#6-配置-nginx">¶</a>6、配置 nginx</h4><p> 配置完成后重启 nginx。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"># images-hosting 的 nginx 配置</span><br><span class="line"># http</span><br><span class="line">#server {</span><br><span class="line"># listen 80;</span><br><span class="line"># server_name images-hosting.liuxianyu.cn;</span><br><span class="line"># root /mnt/projects/images-hosting;</span><br><span class="line">#</span><br><span class="line"># location / {</span><br><span class="line"># try_files $uri $uri/ /index.html;</span><br><span class="line"># }</span><br><span class="line">#}</span><br><span class="line"></span><br><span class="line"># https</span><br><span class="line">server {</span><br><span class="line"> listen 443 ssl;</span><br><span class="line"> server_name images-hosting.liuxianyu.cn;</span><br><span class="line"> root /mnt/projects/images-hosting;</span><br><span class="line"> ssl_certificate cert/images-hosting.liuxianyu.cn.pem;</span><br><span class="line"> ssl_certificate_key cert/images-hosting.liuxianyu.cn.key;</span><br><span class="line"> ssl_session_timeout 5m;</span><br><span class="line"> ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;</span><br><span class="line"> ssl_protocols TLSv1 TLSv1.1 TLSv1.2;</span><br><span class="line"> ssl_prefer_server_ciphers on;</span><br><span class="line"></span><br><span class="line"> location / {</span><br><span class="line"> try_files $uri $uri/ /index.html;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong>图片资源可以访问啦</strong><br><img src="https://images-hosting.liuxianyu.cn/comment-bg.png" alt></p><h4 id="7-替换博客原有的链接地址"><a class="header-anchor" href="#7-替换博客原有的链接地址">¶</a>7、替换博客原有的链接地址</h4><p> 全局替换 <code>https://gitee.com/liuxy0551/image-hosting/</code> -> <code>https://images-hosting.liuxianyu.cn/</code>,重新部署博客即可恢复图片资源的访问啦。</p><h4 id="8-缺点"><a class="header-anchor" href="#8-缺点">¶</a>8、缺点</h4><ul><li>每次都是上传全量资源,耗时较长</li></ul>]]></content>
<summary type="html">
<p> 至此,博客的图片资源已经经历 github -&gt; 阿里云服务器(1M 带宽) -&gt; 七牛云 -&gt; gitee -&gt; 腾讯云服务器(4M 带宽),一直没有一个稳定的方案,上一次使用的还是 Gitee 图床,可以点击 <a href="https://liuxianyu.cn/article/gitee-image-hosting.html" target="_black">Gitee 图床【已不可用】</a> 查看当时的过程。</p>
</summary>
<category term="blog" scheme="https://liuxianyu.cn/categories/blog/"/>
<category term="blog" scheme="https://liuxianyu.cn/tags/blog/"/>
</entry>
<entry>
<title>CentOS 最小化安装</title>
<link href="https://liuxianyu.cn/article/centos7-minimal.html"/>
<id>https://liuxianyu.cn/article/centos7-minimal.html</id>
<published>2022-12-09T12:43:09.000Z</published>
<updated>2022-12-09T12:43:09.000Z</updated>
<content type="html"><![CDATA[<p> 记录一下将闲置电脑改造成远程服务器的过程。记录一下将闲置电脑改造成远程服务器的过程。</p><a id="more"></a><h3 id="连接网络"><a class="header-anchor" href="#连接网络">¶</a>连接网络</h3><p> 开机后登录,输入以下命令测试网络情况,会报错:<code>Name or service not know</code>。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ping www.baidu.com</span><br></pre></td></tr></table></figure><h4 id="有线网络"><a class="header-anchor" href="#有线网络">¶</a>有线网络</h4><p> 建议笔记本电脑使用下方 <code>WiFi 设置的第一种方案</code> 先连上网络。</p><p> **请插上网线,执行 <code>reboot</code> 命令重启设备。**执行以下命令查看网卡状态:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">nmcli d</span><br></pre></td></tr></table></figure><p><img src="https://images-hosting.liuxianyu.cn/posts/centos7-minimal/1.png" alt></p><p> 一般会显示已经连接上网络,此时执行 <code>ip addr</code> 或 <code>ip route</code> 命令查看 IP 地址:</p><p><img src="https://images-hosting.liuxianyu.cn/posts/centos7-minimal/2.png" alt></p><h4 id="wifi"><a class="header-anchor" href="#wifi">¶</a>WiFi</h4><p> 后续会通过 WIFI 连接网络,输入以下命令连接 WiFi:</p><h5 id="第一种-不推荐"><a class="header-anchor" href="#第一种-不推荐">¶</a>第一种(不推荐)</h5><p> 以下命令重启设备时不会主动再次连接 WiFi,建议仅在初次使用时进行联网,联网后按照第二种方案设置。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">ip link set wlp2s0 up</span><br><span class="line">wpa_supplicant -B -i wlp2s0 -c <(wpa_passphrase your_wifi_name your_wifi_password)</span><br><span class="line">dhclient wlp2s0</span><br></pre></td></tr></table></figure><p> <code>wlp2s0</code> 是上述 <code>nmcli d</code> 命令查询出来的 WiFi 网卡名称,完成后 ping 网络测试。</p><h5 id="第二种-推荐"><a class="header-anchor" href="#第二种-推荐">¶</a>第二种(推荐)</h5><p> 输入以下命令连接 WiFi:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">nmcli --ask dev wifi connect your_wifi_name password your_wifi_password</span><br></pre></td></tr></table></figure><p> 会报错:<code>No Wi-Fi device found</code>,执行 <code>yum install NetworkManager-wifi -y</code> 后执行 <code>reboot</code> 命令重启设备。</p><p> 然后再执行上述命令即可连接成功,查看 WIFI 自动分配的局域网 IP,可以通过 SSH 工具连接设备了(如果连不上可以重启下设备)。</p><h3 id="忽略合盖-电源键"><a class="header-anchor" href="#忽略合盖-电源键">¶</a>忽略合盖、电源键</h3><p> 闲置设备可能是笔记本,此时忽略笔记本的合盖休眠很有必要,同时将电源键忽略。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vi /etc/systemd/logind.conf</span><br></pre></td></tr></table></figure><p> 将其中的 <code>HandlePowerKey</code> 的值改为 <code>ignore</code>,<code>HandleLidSwitch</code> 的值改为 <code>lock</code> 并去除两者最前方的 <code>#</code> 注释。</p><p> 执行 <code>reboot</code> 命令重启生效,这样就可以忽略电源键,并且合盖时仅锁屏,设备不会休眠。</p><h3 id="常用工具"><a class="header-anchor" href="#常用工具">¶</a>常用工具</h3><p><a href="https://liuxianyu.cn/article/cent-os-base.html">Cent OS 基础环境搭建 | 刘先玉</a></p><h4 id="更新所有包"><a class="header-anchor" href="#更新所有包">¶</a>更新所有包</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yum update -y</span><br></pre></td></tr></table></figure><h4 id="ifconfig"><a class="header-anchor" href="#ifconfig">¶</a>ifconfig</h4><p> 执行 <code>yum search ifconfig</code>,然后安装查询到的结果:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yum install net-tools.x86_64 -y</span><br></pre></td></tr></table></figure><h4 id="其他常用工具"><a class="header-anchor" href="#其他常用工具">¶</a>其他常用工具</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yum install wget vim lsof -y</span><br></pre></td></tr></table></figure><h4 id="git"><a class="header-anchor" href="#git">¶</a>git</h4><p> 通过 yum 安装 git <code>yum install git -y</code>,版本会比较老(不推荐)。</p><p> <strong>通过下载最新版的源文件并编译(推荐)</strong>:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">cd /mnt</span><br><span class="line">wget https://mirrors.edge.kernel.org/pub/software/scm/git/git-2.44.0.tar.gz</span><br><span class="line">tar -zxvf git-2.44.0.tar.gz</span><br><span class="line">cd git-2.44.0</span><br><span class="line">yum install -y gcc-c++ curl-devel zlib-devel autoconf</span><br></pre></td></tr></table></figure><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">./configure --prefix=/usr/local</span><br><span class="line">make && make install</span><br></pre></td></tr></table></figure><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git --version</span><br></pre></td></tr></table></figure><h3 id="查看电池状态"><a class="header-anchor" href="#查看电池状态">¶</a>查看电池状态</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yum install upower -y</span><br></pre></td></tr></table></figure><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">upower -i $(upower -e | grep BAT) | grep --color=never -E "state|to\ full|to\ empty|percentage"</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<p> 记录一下将闲置电脑改造成远程服务器的过程。记录一下将闲置电脑改造成远程服务器的过程。</p>
</summary>
<category term="Cent OS" scheme="https://liuxianyu.cn/categories/Cent-OS/"/>
<category term="Linux" scheme="https://liuxianyu.cn/tags/Linux/"/>
<category term="Cent OS" scheme="https://liuxianyu.cn/tags/Cent-OS/"/>
</entry>
<entry>
<title>安装 on-my-zsh 和常用插件,配置常用命令</title>
<link href="https://liuxianyu.cn/article/on-my-zsh.html"/>
<id>https://liuxianyu.cn/article/on-my-zsh.html</id>
<published>2022-11-11T20:31:23.000Z</published>
<updated>2024-06-06T11:32:35.000Z</updated>
<content type="html"><![CDATA[<p> 最近之前买的三年服务器到期了,新买了腾讯云的服务器,Cent OS 7.6,记录下安装<code>oh-my-zsh</code>及常用插件的过程,MacOS 通用。</p><a id="more"></a><h3 id="一-安装"><a class="header-anchor" href="#一-安装">¶</a>一、安装</h3><h4 id="1-查看当前-shell"><a class="header-anchor" href="#1-查看当前-shell">¶</a>1、查看当前 shell</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">echo $SHELL</span><br></pre></td></tr></table></figure><h4 id="2-安装-zsh"><a class="header-anchor" href="#2-安装-zsh">¶</a>2、安装 zsh</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yum install zsh -y</span><br></pre></td></tr></table></figure><h4 id="3-查看已经安装的-shell"><a class="header-anchor" href="#3-查看已经安装的-shell">¶</a>3、查看已经安装的 shell</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cat /etc/shells</span><br></pre></td></tr></table></figure><h4 id="4-切换-zsh"><a class="header-anchor" href="#4-切换-zsh">¶</a>4、切换 zsh</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">chsh -s /bin/zsh</span><br></pre></td></tr></table></figure><h4 id="5-安装-git"><a class="header-anchor" href="#5-安装-git">¶</a>5、安装 git</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yum install git -y</span><br></pre></td></tr></table></figure><h4 id="6-安装-oh-my-zsh"><a class="header-anchor" href="#6-安装-oh-my-zsh">¶</a>6、安装 oh-my-zsh</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"</span><br></pre></td></tr></table></figure><p> 由于网络原因,上述命令可能无法执行,此时新建一个<code>install.sh</code>脚本文件,复制 <a href="https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh" target="_blank" rel="noopener">ohmyzsh/install.sh</a> 的内容,然后执行<code>bash install.sh</code>命令即可。</p><h3 id="二-配置"><a class="header-anchor" href="#二-配置">¶</a>二、配置</h3><h4 id="1-下载插件"><a class="header-anchor" href="#1-下载插件">¶</a>1、下载插件</h4><p> <code>oh-my-zsh</code> 有很多的插件:<a href="https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins" target="_blank" rel="noopener">plugins</a>,我常用的有 z、cp、zsh-autosuggestions、zsh-syntax-highlighting,它们的功能分别是跳转常用目录、cp 通过<code>rsync</code>命令提供带进度条的复制文件命令、自动提示、错误命令高亮。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git clone https://github.com/zsh-users/zsh-autosuggestions $ZSH_CUSTOM/plugins/zsh-autosuggestions</span><br><span class="line">git clone https://github.com/zsh-users/zsh-syntax-highlighting $ZSH_CUSTOM/plugins/zsh-syntax-highlighting</span><br></pre></td></tr></table></figure><p> 自带的插件:git、<a href="https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/z" target="_blank" rel="noopener">z</a>、<a href="https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/cp" target="_blank" rel="noopener">cp</a></p><blockquote><p>z 查看历史记录<br>–add 添加目录到记录里<br>-c 只查看当前目录<br>-t 按最近使用的匹配</p></blockquote><blockquote><p>rsync<br>-r 递归文件夹<br>-h 显示常见的文件大小单位<br>-P 显示进度条</p></blockquote><h4 id="2-编辑配置文件"><a class="header-anchor" href="#2-编辑配置文件">¶</a>2、编辑配置文件</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vim ~/.zshrc</span><br></pre></td></tr></table></figure><p> 我常用的主题是 ys,日常配置如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">export ZSH="$HOME/.oh-my-zsh"</span><br><span class="line"></span><br><span class="line">ZSH_THEME="ys"</span><br><span class="line">plugins=(git z cp zsh-autosuggestions zsh-syntax-highlighting)</span><br><span class="line"></span><br><span class="line">source $ZSH/oh-my-zsh.sh</span><br><span class="line">source ~/.bash_profile</span><br><span class="line"></span><br><span class="line"># node n 第三方镜像</span><br><span class="line">export N_NODE_MIRROR=https://npmmirror.com/mirrors/node</span><br><span class="line"></span><br><span class="line"># pnpm</span><br><span class="line">alias p="pnpm"</span><br><span class="line"></span><br><span class="line"># 腾讯云服务器地址</span><br><span class="line">tencent="122.122.122.122"</span><br><span class="line"></span><br><span class="line"># 删除当前目录的 node_modules</span><br><span class="line">alias remove="pwd; echo 'Deleting all node_modules in the current directory ...'; find . -name 'node_modules' -type d -prune -exec rm -rf '{}' +; echo 'Delete done!'; "</span><br></pre></td></tr></table></figure><p> 可能执行<code>source ~/.zshrc</code>后会报错:<code>/etc/bashrc "command not found: shopt"</code>,编辑<code>~/.bashrc</code>,将<code>. /etc/bashrc</code>替换成<code>bash -c ". /etc/bashrc"</code>,保存后退出,再次执行<code>source ~/.zshrc</code>即可。</p><h4 id="3-卸载-oh-my-zsh"><a class="header-anchor" href="#3-卸载-oh-my-zsh">¶</a>3、卸载 oh-my-zsh</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">uninstall_oh_my_zsh</span><br></pre></td></tr></table></figure><h3 id="三-常用别名"><a class="header-anchor" href="#三-常用别名">¶</a>三、常用别名</h3><h4 id="1-查看电池状态"><a class="header-anchor" href="#1-查看电池状态">¶</a>1、查看电池状态</h4><p> 笔记本电脑安装了 CentOS 7.6,使用 <code>upower</code> 命令可以查看电池状态,这里配置一个 <code>power</code> 快捷命令,方便查看电池状态。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> 查看笔记本电脑电量</span></span><br><span class="line">alias power='upower -i $(upower -e | grep BAT) | grep --color=never -E "state|to\ full|to\ empty|percentage"'</span><br></pre></td></tr></table></figure><h4 id="2-删除-node-modules"><a class="header-anchor" href="#2-删除-node-modules">¶</a>2、删除 node_modules</h4><p> 使用 Monorepo 架构后,工程内很多地方都有了 <code>node_modules</code> 文件夹,在不希望有缓存干扰调试时,快速清理所有 node_modules 是有必要的,可以借助 <code>npkill</code> 这类工具包,可以配置下方的命令,通过执行 <code>remove</code> 命令快速删除当前路径下所有 node_modules。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> 删除当前目录的 node_modules</span></span><br><span class="line">alias remove="pwd; echo 'Deleting all node_modules in the current directory ...'; find . -name 'node_modules' -type d -prune -exec rm -rf '{}' +; echo 'Delete done!'; "</span><br></pre></td></tr></table></figure><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">find . -name 'node_modules' -type d -prune -exec rm -rf '{}' +</span><br></pre></td></tr></table></figure><ul><li><code>find .</code> 表示从当前目录(<code>.</code>)开始搜索</li><li><code>-name 'node_modules'</code> 表示搜索名为 <code>node_modules</code> 的文件或目录</li><li><code>-type d</code> 限定查找类型为目录 (<code>d</code>)</li><li><code>-prune</code> 跳过当前目录下的匹配项及其子目录。这意味着当 <code>find</code> 发现一个名为 <code>node_modules</code> 的目录时,不会再进入这个目录递归查找</li><li><code>-exec</code> 对匹配到的每一个文件或目录执行指定的命令</li><li><code>rm -rf</code> 递归强制删除文件或目录</li><li><code>{}</code> 代表当前匹配到的文件或目录</li><li><code>+</code> 表示以批处理方式执行命令,而不是对每个匹配项分别执行一次 <code>rm -rf</code>,这在有大量匹配项时可以提高效率</li></ul><h3 id="四-自定义主题"><a class="header-anchor" href="#四-自定义主题">¶</a>四、自定义主题</h3><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> ~/.oh-my-zsh/themes</span><br><span class="line">cp ys.zsh-theme ys1.zsh-theme</span><br><span class="line">vim ys1.zsh-theme</span><br></pre></td></tr></table></figure><p> 进入 <code>~/.oh-my-zsh/themes</code> 目录,以 <code>ys</code> 主题为模板复制一个自定义的主题文件,暂且命名为 <code>ys1</code>,文件名以<code>.zsh-theme</code>结尾,内容如下:</p><p><img src="https://images-hosting.liuxianyu.cn/posts/on-my-zsh/1.png" alt></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br></pre></td><td class="code"><pre><span class="line"># Clean, simple, compatible and meaningful.</span><br><span class="line"># Tested on Linux, Unix and Windows under ANSI colors.</span><br><span class="line"># It is recommended to use with a dark background.</span><br><span class="line"># Colors: black, red, green, yellow, *blue, magenta, cyan, and white.</span><br><span class="line">#</span><br><span class="line"># Mar 2013 Yad Smood</span><br><span class="line"></span><br><span class="line"># VCS</span><br><span class="line">YS_VCS_PROMPT_PREFIX1=" %{$reset_color%}on%{$fg[blue]%} "</span><br><span class="line">YS_VCS_PROMPT_PREFIX2=":%{$fg[cyan]%}"</span><br><span class="line">YS_VCS_PROMPT_SUFFIX="%{$reset_color%}"</span><br><span class="line">YS_VCS_PROMPT_DIRTY=" %{$fg[red]%}x"</span><br><span class="line"># YS_VCS_PROMPT_CLEAN=" %{$fg[green]%}o"</span><br><span class="line">YS_VCS_PROMPT_CLEAN=""</span><br><span class="line"></span><br><span class="line"># node version</span><br><span class="line">local node_info='$(ys_node_prompt_info)'</span><br><span class="line">ys_node_prompt_info() {</span><br><span class="line">if command -v node &>/dev/null; then</span><br><span class="line">echo " %{$fg[green]%}$(node -v)"</span><br><span class="line">fi</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"># Git info</span><br><span class="line">local git_info='$(git_prompt_info)'</span><br><span class="line">ZSH_THEME_GIT_PROMPT_PREFIX="${YS_VCS_PROMPT_PREFIX1}%{$fg[cyan]%}"</span><br><span class="line">ZSH_THEME_GIT_PROMPT_SUFFIX="$YS_VCS_PROMPT_SUFFIX"</span><br><span class="line">ZSH_THEME_GIT_PROMPT_DIRTY="$YS_VCS_PROMPT_DIRTY"</span><br><span class="line">ZSH_THEME_GIT_PROMPT_CLEAN="$YS_VCS_PROMPT_CLEAN"</span><br><span class="line"></span><br><span class="line"># SVN info</span><br><span class="line">local svn_info='$(svn_prompt_info)'</span><br><span class="line">ZSH_THEME_SVN_PROMPT_PREFIX="${YS_VCS_PROMPT_PREFIX1}svn${YS_VCS_PROMPT_PREFIX2}"</span><br><span class="line">ZSH_THEME_SVN_PROMPT_SUFFIX="$YS_VCS_PROMPT_SUFFIX"</span><br><span class="line">ZSH_THEME_SVN_PROMPT_DIRTY="$YS_VCS_PROMPT_DIRTY"</span><br><span class="line">ZSH_THEME_SVN_PROMPT_CLEAN="$YS_VCS_PROMPT_CLEAN"</span><br><span class="line"></span><br><span class="line"># HG info</span><br><span class="line">local hg_info='$(ys_hg_prompt_info)'</span><br><span class="line">ys_hg_prompt_info() {</span><br><span class="line"># make sure this is a hg dir</span><br><span class="line">if [ -d '.hg' ]; then</span><br><span class="line">echo -n "${YS_VCS_PROMPT_PREFIX1}hg${YS_VCS_PROMPT_PREFIX2}"</span><br><span class="line">echo -n $(hg branch 2>/dev/null)</span><br><span class="line">if [[ "$(hg config oh-my-zsh.hide-dirty 2>/dev/null)" != "1" ]]; then</span><br><span class="line">if [ -n "$(hg status 2>/dev/null)" ]; then</span><br><span class="line">echo -n "$YS_VCS_PROMPT_DIRTY"</span><br><span class="line">else</span><br><span class="line">echo -n "$YS_VCS_PROMPT_CLEAN"</span><br><span class="line">fi</span><br><span class="line">fi</span><br><span class="line">echo -n "$YS_VCS_PROMPT_SUFFIX"</span><br><span class="line">fi</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"># Virtualenv</span><br><span class="line">local venv_info='$(virtenv_prompt)'</span><br><span class="line">YS_THEME_VIRTUALENV_PROMPT_PREFIX=" %{$fg[green]%}"</span><br><span class="line">YS_THEME_VIRTUALENV_PROMPT_SUFFIX=" %{$reset_color%}%"</span><br><span class="line">virtenv_prompt() {</span><br><span class="line">[[ -n "${VIRTUAL_ENV:-}" ]] || return</span><br><span class="line">echo "${YS_THEME_VIRTUALENV_PROMPT_PREFIX}${VIRTUAL_ENV:t}${YS_THEME_VIRTUALENV_PROMPT_SUFFIX}"</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">local time=" %{$reset_color%}[%*]"</span><br><span class="line">local exit_code="%(?,,C:%{$fg[red]%}%?%{$reset_color%})"</span><br><span class="line"></span><br><span class="line"># Prompt format:</span><br><span class="line">#</span><br><span class="line"># PRIVILEGES USER @ MACHINE in DIRECTORY on git:BRANCH STATE [TIME] C:LAST_EXIT_CODE</span><br><span class="line"># $ COMMAND</span><br><span class="line">#</span><br><span class="line"># For example:</span><br><span class="line">#</span><br><span class="line"># % ys @ ys-mbp in ~/.oh-my-zsh on git:master x [21:47:42] C:0</span><br><span class="line"># $</span><br><span class="line">PROMPT="</span><br><span class="line">%{$terminfo[bold]$fg[blue]%}#%{$reset_color%} \</span><br><span class="line">%(#,%{$bg[yellow]%}%{$fg[black]%}%n%{$reset_color%},%{$fg[cyan]%}%n) \</span><br><span class="line">%{$reset_color%}@ \</span><br><span class="line">%{$fg[green]%}%m \</span><br><span class="line">%{$reset_color%}in \</span><br><span class="line">%{$terminfo[bold]$fg[yellow]%}%~%{$reset_color%}\</span><br><span class="line">${hg_info}\</span><br><span class="line">${git_info}\</span><br><span class="line">${svn_info}\</span><br><span class="line">${venv_info}\</span><br><span class="line">${node_info}\</span><br><span class="line">${time}\</span><br><span class="line"> \</span><br><span class="line">$exit_code</span><br><span class="line">%{$terminfo[bold]$fg[red]%}$ %{$reset_color%}"</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<p> 最近之前买的三年服务器到期了,新买了腾讯云的服务器,Cent OS 7.6,记录下安装<code>oh-my-zsh</code>及常用插件的过程,MacOS 通用。</p>
</summary>
<category term="Linux" scheme="https://liuxianyu.cn/categories/Linux/"/>
<category term="Linux" scheme="https://liuxianyu.cn/tags/Linux/"/>
</entry>
<entry>
<title>前端学习 Docker 之旅(七)—— Docker 中安装 MongoDB</title>
<link href="https://liuxianyu.cn/article/docker-f.html"/>
<id>https://liuxianyu.cn/article/docker-f.html</id>
<published>2022-08-04T10:51:23.000Z</published>
<updated>2022-08-04T10:51:23.000Z</updated>
<content type="html"><![CDATA[<p> 最近在公司中,有个项目需要用到数据库,选择了尝试下 MongoDB,记录下在 Docker 中安装 MongoDB 的过程。</p><a id="more"></a><h3 id="一-安装-mongodb"><a class="header-anchor" href="#一-安装-mongodb">¶</a>一、安装 MongoDB</h3><h4 id="1-查找镜像"><a class="header-anchor" href="#1-查找镜像">¶</a>1、查找镜像</h4><p> 在 Docker Hub 官网查找自己需要的版本 <a href="https://hub.docker.com/_/mongo?tab=tags" target="_blank" rel="noopener">https://hub.docker.com/_/mongo?tab=tags</a></p><h4 id="2-下载镜像"><a class="header-anchor" href="#2-下载镜像">¶</a>2、下载镜像</h4><p> 我选择的是 6.0.2:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker pull mongo:6.0.2</span><br></pre></td></tr></table></figure><h4 id="3-创建挂载文件夹"><a class="header-anchor" href="#3-创建挂载文件夹">¶</a>3、创建挂载文件夹</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">mkdir -p /mnt/docker/mongo</span><br><span class="line">cd /mnt/docker/mongo</span><br></pre></td></tr></table></figure><h3 id="二-运行-mongodb"><a class="header-anchor" href="#二-运行-mongodb">¶</a>二、运行 MongoDB</h3><h4 id="1-使用镜像创建容器并运行"><a class="header-anchor" href="#1-使用镜像创建容器并运行">¶</a>1、使用镜像创建容器并运行</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run -v /mnt/docker/mongo:/data/db --name mongodb -p 27019:27017 -e MONGO_INITDB_ROOT_USERNAME=root -e MONGO_INITDB_ROOT_PASSWORD='123456' -d mongo:6.0.2 --auth</span><br></pre></td></tr></table></figure><p>指令解释:</p><ul><li><code>-v</code> 挂载本地文件夹,存储数据</li><li><code>--name</code> 表示给容器指定的名称</li><li><code>-p</code> 表示端口映射,<code>-p 宿主机port:容器port</code>,这里不使用相同端口是为了防止攻击</li><li><code>-e</code> 携带密码等参数</li><li><code>-d</code> 表示后台启动</li><li><code>--auth</code> MongoDB 进行权限校验</li></ul><h4 id="2-进入容器"><a class="header-anchor" href="#2-进入容器">¶</a>2、进入容器</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker exec -it mongodb mongosh admin</span><br></pre></td></tr></table></figure><blockquote><p><strong>注意</strong><br>使用 <code>docker exec -it mongodb mongo admin</code> 可能如果出现以下报错,mongodb 5.0 以上的版本需要使用<code>mongosh</code>来代替<code>mongo</code><br>rpc error: code = 2 desc = oci runtime error: exec failed: container_linux.go:235: starting container process caused “exec: “mongo”: executable file not found in $PATH”</p></blockquote><h4 id="3-验证用户名密码登录"><a class="header-anchor" href="#3-验证用户名密码登录">¶</a>3、验证用户名密码登录</h4><p> 返回 1 代表登录成功。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">db.auth('root', '123456')</span><br></pre></td></tr></table></figure><p><img src="https://images-hosting.liuxianyu.cn/posts/docker-f/1.png" alt></p><h4 id="4-使用数据库"><a class="header-anchor" href="#4-使用数据库">¶</a>4、使用数据库</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">use landingDB</span><br></pre></td></tr></table></figure><h4 id="5-创建数据库的管理员"><a class="header-anchor" href="#5-创建数据库的管理员">¶</a>5、创建数据库的管理员</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">db.createUser({ user: "landing-user", pwd: "landing-admin.1234", roles: [{ role: "readWrite", db: "landingDB" }] })</span><br></pre></td></tr></table></figure><p> MongoDB 不允许同一窗口有多个用户登录,退出再次进入终端:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">use landingDB</span><br><span class="line">db.auth('landing-user', 'landing-admin.1234')</span><br></pre></td></tr></table></figure><h4 id="6-创建表"><a class="header-anchor" href="#6-创建表">¶</a>6、创建表</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">db.createCollection('article')</span><br><span class="line">db.createCollection('tag')</span><br></pre></td></tr></table></figure><h4 id="7-测试插入数据"><a class="header-anchor" href="#7-测试插入数据">¶</a>7、测试插入数据</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">db.article.insert({ id: 1, title: '测试文章标题' })</span><br></pre></td></tr></table></figure><h4 id="8-通过-mongodb-compass-连接数据库"><a class="header-anchor" href="#8-通过-mongodb-compass-连接数据库">¶</a>8、通过 MongoDB Compass 连接数据库</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mongodb://landing-user:[email protected]:27019/landingDB</span><br></pre></td></tr></table></figure><p><img src="https://images-hosting.liuxianyu.cn/posts/docker-f/2.png" alt><br><img src="https://images-hosting.liuxianyu.cn/posts/docker-f/3.png" alt></p><h3 id="三-在-node-中使用"><a class="header-anchor" href="#三-在-node-中使用">¶</a>三、在 node 中使用</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> { MongoClient } = <span class="built_in">require</span>(<span class="string">'mongodb'</span>)</span><br><span class="line"><span class="keyword">const</span> { host, port, dbName, username, password } = configObj</span><br><span class="line"><span class="keyword">const</span> url = <span class="string">`mongodb://<span class="subst">${username}</span>:<span class="subst">${password}</span>@<span class="subst">${host}</span>:<span class="subst">${port}</span>/<span class="subst">${dbName}</span>`</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> client = <span class="keyword">new</span> MongoClient(url)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 初始化数据库链接</span></span><br><span class="line"><span class="keyword">const</span> initDB = <span class="keyword">async</span> () => {</span><br><span class="line"> <span class="keyword">await</span> client.connect()</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'Connected successfully to mongodb'</span>)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 新增查询到的标签列表</span></span><br><span class="line"><span class="keyword">const</span> insertTags = <span class="keyword">async</span> (tagList) => {</span><br><span class="line"> <span class="keyword">const</span> db = client.db(dbName)</span><br><span class="line"> <span class="keyword">const</span> collection = db.collection(<span class="string">'tag'</span>)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">const</span> updateResult = <span class="keyword">await</span> collection.updateMany({ <span class="attr">isDelete</span>: <span class="number">0</span> }, { <span class="attr">$set</span>: { <span class="attr">isDelete</span>: <span class="number">1</span>, <span class="attr">updateTime</span>: getDateStr() } })</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'updateTags documents =>'</span>, updateResult)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">const</span> insertResult = <span class="keyword">await</span> collection.insertMany(tagList)</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'insertTags documents =>'</span>, insertResult)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 查询标签列表</span></span><br><span class="line"><span class="keyword">const</span> getTagList = <span class="keyword">async</span> () => {</span><br><span class="line"> <span class="keyword">const</span> db = client.db(dbName)</span><br><span class="line"> <span class="keyword">const</span> collection = db.collection(<span class="string">'tag'</span>)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">const</span> data = <span class="keyword">await</span> collection.find({ <span class="attr">isDelete</span>: <span class="number">0</span> }).toArray()</span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> code: <span class="number">200</span>,</span><br><span class="line"> data,</span><br><span class="line"> message: <span class="string">'成功'</span>,</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = {</span><br><span class="line"> initDB,</span><br><span class="line"> insertTags,</span><br><span class="line"> getTagList,</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>附:<a href="https://liuxianyu.cn/article/docker-b.html" target="_black">前端学习 Docker 之旅(二)—— 常用指令</a></p>]]></content>
<summary type="html">
<p> 最近在公司中,有个项目需要用到数据库,选择了尝试下 MongoDB,记录下在 Docker 中安装 MongoDB 的过程。</p>
</summary>
<category term="Docker" scheme="https://liuxianyu.cn/categories/Docker/"/>
<category term="Docker" scheme="https://liuxianyu.cn/tags/Docker/"/>
</entry>
<entry>
<title>规范 git commit、设置版本号、自动生成 changelog</title>
<link href="https://liuxianyu.cn/article/git-commit-version-changelog.html"/>
<id>https://liuxianyu.cn/article/git-commit-version-changelog.html</id>
<published>2022-04-12T15:49:25.000Z</published>
<updated>2022-04-12T15:49:25.000Z</updated>
<content type="html"><![CDATA[<p> 在开源项目中,规范的 commit message 可以让修改记录更简洁明了,记录下借助工具实现规范的 git commit。这里主要介绍 <a href="https://github.com/commitizen/cz-cli" target="_black">commitizen</a>、<a href="https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-cli" target="_black">conventional-changelog-cli</a>、<a href="https://github.com/conventional-changelog/standard-version" target="_black">standard-version</a> 这三款工具。</p><a id="more"></a><p> 全局安装是为了本地方便运行,<code>-D</code> 安装是为了方便 CI。</p><h3 id="一-commitizen"><a class="header-anchor" href="#一-commitizen">¶</a>一、commitizen</h3><h4 id="1-1-安装"><a class="header-anchor" href="#1-1-安装">¶</a>1.1、安装</h4><p> <a href="https://github.com/commitizen/cz-cli" target="_black">commitizen</a> 是一款标准化 git commit 的工具,在没有规范的情况下,开发人员的 commit message 通常是随意的,这就导致 commit message 显得有些无用。可是当你在做git log、code review、编写 changelog 等情况时,良好的 commit 规范就显得尤为重要。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install commitizen -g</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install commitizen -D</span><br></pre></td></tr></table></figure><p> 使用 commitizen 来安装<code>cz-conventional-changelog</code>,commitizen 安装 cz-conventional-changelog 后会自动在 package.json 中添加如下配置:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">commitizen init cz-conventional-changelog --save-dev --save-exact</span><br></pre></td></tr></table></figure><figure class="highlight"><figcaption><span>package.json</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">"config": {</span><br><span class="line"> "commitizen": {</span><br><span class="line"> "path": "./node_modules/cz-conventional-changelog"</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p> 安装并添加完成后,便可以使用<code>git cz</code>命令来替换<code>git commit</code>了。修改一个文件并<code>git add</code>后,通过<code>git cz</code>试一下:</p><p><img src="https://images-hosting.liuxianyu.cn/posts/git-commit-version-changelog/1.png" alt></p><table><thead><tr><th>类型</th><th>描述</th></tr></thead><tbody><tr><td>feat</td><td>新增功能</td></tr><tr><td>fix</td><td>bug 修复</td></tr><tr><td>docs</td><td>文档更新</td></tr><tr><td>style</td><td>不影响程序逻辑的代码修改(修改空白字符,格式缩进,补全缺失的分号等,没有改变代码逻辑)</td></tr><tr><td>refactor</td><td>重构代码(既没有新增功能,也没有修复 bug)</td></tr><tr><td>perf</td><td>性能, 体验优化</td></tr><tr><td>test</td><td>新增测试用例或是更新现有测试</td></tr><tr><td>build</td><td>主要目的是修改项目构建系统(例如 glup,webpack,rollup 的配置等)的提交</td></tr><tr><td>ci</td><td>主要目的是修改项目继续集成流程(例如 Travis,Jenkins,GitLab CI,Circle等)的提交</td></tr><tr><td>chore</td><td>不属于以上类型的其他类型</td></tr><tr><td>revert</td><td>回滚某个更早之前的提交</td></tr></tbody></table><blockquote><p><strong>注意</strong><br>如果想修改已经打好的 commit message,我们可以通过<code>git reset HEAD~</code>命令来修改</p></blockquote><h4 id="1-2-commit-message-的格式规范"><a class="header-anchor" href="#1-2-commit-message-的格式规范">¶</a>1.2、commit message 的格式规范</h4><p> commit message 包含三个部分:Header、Body、Footer,一般 Header 是必需的,Body 和 Footer 可以省略。<code>Header</code>部分只有一行,包括三个字段:<code>type</code>(必需)、<code>scope</code>(可选,用于定义 type 的影响范围)和<code>subject</code>(必需,commit 的简短描述)。</p><h3 id="二-conventional-changelog-cli"><a class="header-anchor" href="#二-conventional-changelog-cli">¶</a>二、conventional-changelog-cli</h3><h4 id="2-1-安装"><a class="header-anchor" href="#2-1-安装">¶</a>2.1、安装</h4><p> <a href="https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-cli" target="_black">conventional-changelog-cli</a> 默认推荐的 commit 标准是来自 angular,还有其他可选值:atom, codemirror, ember, eslint, express, jquery。可用来生成<code>CHANGELOG.md</code>。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install conventional-changelog-cli -g</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install conventional-changelog-cli -D</span><br></pre></td></tr></table></figure><p> 在项目根目录执行以下命令,不会覆盖以前的<code>CHANGELOG.md</code>,只会在<code>CHANGELOG.md</code>的头部加上自从上次<code>git push</code>以来的变动。<code>-s</code>表示读写同一文件,<code>-r</code>表示生成 changelog 所需要使用的发布版本数量,默认为 1,全部则是 0。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">conventional-changelog -p angular -i CHANGELOG.md -s -r 0</span><br></pre></td></tr></table></figure><h4 id="2-2-自定义参数"><a class="header-anchor" href="#2-2-自定义参数">¶</a>2.2、自定义参数</h4><p> 生成的 changelog 中有些常用内容可以通过自定义参数来根据需求更改,例如版本号、commit 地址等等。</p><ul><li>版本号是从 package.json 中获取的 version 字段值;</li><li>commit 地址是从 package.json 中获取的 repository 字段值;</li><li>issue 地址是从 package.json 中获取的 repository 字段值;</li></ul><p> 如果你使用了第三方的协作系统,那么在生成 changelog 后可以使用 replace 工具(<code>--quiet</code> 表示不输出 replace 日志)来处理文本中的原有地址:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">replace <span class="string">'https://github.com/myproject/issues/'</span> <span class="string">'https://redmine.example.com'</span> CHANGELOG.md --quiet</span><br></pre></td></tr></table></figure><p> <a href="https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-core" target="_black">点此查看</a> 更多配置选项。</p><h3 id="三-standard-version"><a class="header-anchor" href="#三-standard-version">¶</a>三、standard-version</h3><h4 id="3-1-安装"><a class="header-anchor" href="#3-1-安装">¶</a>3.1、安装</h4><p> <a href="https://github.com/conventional-changelog/standard-version" target="_black">standard-version</a> 是一款遵循语义化版本 <a href="https://semver.org/" target="_black">semver</a> 和 <a href="https://conventionalcommits.org/" target="_black">commit message 标准规范</a> 的版本和 changelog 自动化工具。通常情况下,我们会在 master 分支进行如下的版本发布操作:</p><blockquote><p>1、git pull origin master<br>2、根据 package.json 中的 version 更新版本号,更新 changelog<br>3、git add ., 然后 git commit<br>4、git tag 打版本操作<br>5、push 版本 tag 和 master 分支到仓库</p></blockquote><p> 其中 2,3,4 是 standard-version 工具会自动完成的工作,配合本地的 shell 脚本,就可以完成一系列版本发布的工作了。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install standard-version -g</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install standard-version -D</span><br></pre></td></tr></table></figure><h4 id="3-2-命令"><a class="header-anchor" href="#3-2-命令">¶</a>3.2、命令</h4><p> 在项目根目录执行 standard-version 命令,可以在控制台看到整个执行流程的 log 信息,在这里几个常用的参数需要注意下:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">standard-version</span><br></pre></td></tr></table></figure><p>1、<strong>–release-as, -r 指定版本号</strong></p><p> 默认情况下,工具会自动根据 主版本(major)、次版本( minor)、修订版(patch)规则生成版本号,例如如果你 package.json 中的 version 为 1.0.0, 那么执行后版本号则是:1.0.1。自定义可以通过:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">standard-version -r minor // 1.1.0</span><br><span class="line">standard-version -r 2.0.0 // 2.0.0</span><br><span class="line">standard-version -r 2.0.0-test // 2.0.0-test</span><br></pre></td></tr></table></figure><p>2、<strong>–prerelease, -p 预发版本命名</strong></p><p> 用来生成预发版本, 如果当期的版本号是 2.0.0,例如</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">standard-version --prerelease alpha // 2.0.0-alpha.0</span><br></pre></td></tr></table></figure><p>3、<strong>–tag-prefix, -t 版本 tag 前缀</strong></p><p> 用来给生成 tag 标签添加前缀,例如如果前版本号为 2.0.0,则:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">standard-version --tag-prefix <span class="string">"stable-"</span> // tag: stable-v2.0.0</span><br></pre></td></tr></table></figure><p> 以上几个参数用得会多一些,还有其他选项可以通过<code>standard-version --help</code>查看。</p><h4 id="3-3-集成-npm"><a class="header-anchor" href="#3-3-集成-npm">¶</a>3.3、集成 npm</h4><p> 把命令集成到 package.json 的 scripts 中, 并配合 shell 脚本使用, 如下:</p><figure class="highlight"><figcaption><span>package.json</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">"scripts": {</span><br><span class="line"> "release": "./scripts/release.sh",</span><br><span class="line"> "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0 && git add CHANGELOG.md && npm run change-issue-url",</span><br><span class="line"> "change-issue-url": "replace 'https://github.com/myproject/issues/' 'https://redmine.example.com/' CHANGELOG.md"</span><br><span class="line">}</span><br></pre></td></tr></table></figure><figure class="highlight sh"><figcaption><span>release.sh</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Release branch</span></span><br><span class="line">branch=<span class="string">"master"</span></span><br><span class="line">prefix=<span class="string">"v"</span></span><br><span class="line"></span><br><span class="line">git pull origin <span class="variable">$branch</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">"Current pull origin <span class="variable">$branch</span>."</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Auto generate version number and tag</span></span><br><span class="line">standard-version -r <span class="variable">$release</span> --tag-prefix <span class="variable">$prefix</span></span><br><span class="line"></span><br><span class="line">git push --follow-tags origin <span class="variable">$branch</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">"Git push origin <span class="variable">$branch</span>"</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">"Release finished."</span></span><br></pre></td></tr></table></figure><h3 id="四-使用-husky-校验"><a class="header-anchor" href="#四-使用-husky-校验">¶</a>四、使用 husky 校验</h3><p> <a href="https://github.com/typicode/husky" target="_black">husky</a> 主用功能是为 git 添加钩子,它允许我们在 git 的一些重要动作发生时触发动作(npm script), 比如我们可以在 git push 之前执行特定的自定义脚本对代码进行单元测试、又或者在 git commit 之前执行 eslint 校验。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install husky -D</span><br></pre></td></tr></table></figure><figure class="highlight"><figcaption><span>package.json</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">"husky": {</span><br><span class="line"> "hooks": {</span><br><span class="line"> "pre-commit": "echo commit 之前的动作",</span><br><span class="line"> "commit-msg": "echo $HUSKY_GIT_PARAMS $HUSKY_GIT_STDIN",</span><br><span class="line"> "pre-push": "echo push 之前的动作"</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<p> 在开源项目中,规范的 commit message 可以让修改记录更简洁明了,记录下借助工具实现规范的 git commit。这里主要介绍 <a href="https://github.com/commitizen/cz-cli" target="_black">commitizen</a>、<a href="https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-cli" target="_black">conventional-changelog-cli</a>、<a href="https://github.com/conventional-changelog/standard-version" target="_black">standard-version</a> 这三款工具。</p>
</summary>
<category term="git" scheme="https://liuxianyu.cn/categories/git/"/>
<category term="git" scheme="https://liuxianyu.cn/tags/git/"/>
</entry>
<entry>
<title>通过 Lerna 创建自己的 eslint 配置包</title>
<link href="https://liuxianyu.cn/article/my-eslint-config-by-lerna.html"/>
<id>https://liuxianyu.cn/article/my-eslint-config-by-lerna.html</id>
<published>2022-03-23T17:24:04.000Z</published>
<updated>2022-03-23T17:24:04.000Z</updated>
<content type="html"><![CDATA[<p> 最近在写一个<code>eslint config</code>的整合包,因为有不同语言,会发多个 npm 包,通过 <a href="https://github.com/lerna/lerna" target="_black">Lerna</a> 来管理多包发布,它优化了使用 git 和 npm 管理多包存储库的工作流,Vue、Babel、React 都有使用 Lerna。这里记录下使用过程中的一些点。</p><a id="more"></a><h3 id="一-两种工作模式"><a class="header-anchor" href="#一-两种工作模式">¶</a>一、两种工作模式</h3><h4 id="1-1-固定模式"><a class="header-anchor" href="#1-1-固定模式">¶</a>1.1、固定模式</h4><p> Fixed/Locked mode,Vue,Babel 都是用这种,在 publish 的时候,会依据<code>lerna.json</code>文件里面的<code>"version": "0.0.1"</code>进行增加,只选择一次,其他有改动的包自动更新版本号。</p><h4 id="1-2-独立模式"><a class="header-anchor" href="#1-2-独立模式">¶</a>1.2、独立模式</h4><p> Independent mode,执行<code>lerna init --independent</code>命令初始化项目,<code>lerna.json</code>文件里面会设置<code>"version": "independent"</code>。每次 publish 时,会得到一个提示符,提示每个已更改的包,以指定是补丁、次要更改、主要更改还是自定义更改(<code>x.y.z</code>)。</p><h3 id="二-初始化"><a class="header-anchor" href="#二-初始化">¶</a>二、初始化</h3><p> 新建一个文件夹<code>eslint-config-liuxy0551</code>,并进入该文件夹;为了方便 github action,安装 lerna 到开发环境:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">npm init -y</span><br><span class="line">yarn add lerna -D</span><br></pre></td></tr></table></figure><p> 因为 lerna 经常需要用到,我们全局安装下:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn global add lerna</span><br></pre></td></tr></table></figure><p> 安装完成后输入<code>lerna -v</code>查看版本号:</p><p><img src="https://images-hosting.liuxianyu.cn/posts/my-eslint-config-by-lerna/1.png" alt></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">lerna init</span><br></pre></td></tr></table></figure><p> 我们使用固定模式,然后进入<code>packages</code>文件夹初始化几个不同语言对应的 eslint config 包:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> packages</span><br><span class="line">mkdir basic prettier typescript</span><br><span class="line"></span><br><span class="line"><span class="built_in">cd</span> basic</span><br><span class="line">npm init -y</span><br><span class="line"><span class="built_in">cd</span> ../prettier</span><br><span class="line">npm init -y</span><br><span class="line"><span class="built_in">cd</span> ../react</span><br><span class="line">npm init -y</span><br><span class="line"><span class="built_in">cd</span> ../typescript</span><br><span class="line">npm init -y</span><br></pre></td></tr></table></figure><p> 项目结构如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">eslint-config-liuxy0551</span><br><span class="line">├─lerna.json</span><br><span class="line">├─package.json</span><br><span class="line">├─result.txt</span><br><span class="line">├─yarn.lock</span><br><span class="line">├─packages</span><br><span class="line">| ├─typescript</span><br><span class="line">| | └package.json</span><br><span class="line">| ├─react</span><br><span class="line">| | └package.json</span><br><span class="line">| ├─prettier</span><br><span class="line">| | └package.json</span><br><span class="line">| ├─basic</span><br><span class="line">| | └package.json</span><br></pre></td></tr></table></figure><p> 按照约定 <a href="https://eslint.org/docs/developer-guide/shareable-configs" target="_black">Shareable Configs</a>,包名应该以<code>eslint-config-</code>开头,例如:<code>eslint-config-liuxy0551-basic</code>。依次将 packages 下的几个 package.json 中的 name 改成如:@liuxy0551/eslint-config-liuxy0551-basic,version 改成 0.0.0。</p><p> 每个子 package 都有自己的 node_modules,通过如下设置,就可以只在根目录创建 node_modules,只有开启了 private 的项目才能使用 workspaces。依次修改根目录的 package.json 和 lerna.json,添加以下配置项:</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">"private": true,</span><br><span class="line">"workspaces": [</span><br><span class="line"> <span class="string">"packages/*"</span></span><br><span class="line">]</span><br></pre></td></tr></table></figure><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">"useWorkspaces": true,</span><br><span class="line">"npmClient": "yarn"</span><br></pre></td></tr></table></figure><h3 id="三-绑定-git-和-npm"><a class="header-anchor" href="#三-绑定-git-和-npm">¶</a>三、绑定 git 和 npm</h3><p> 接下来与远程仓库绑定,并登录 npm:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git remote add origin [email protected]:liuxy0551/eslint-config-liuxy0551.git</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm whoami</span><br></pre></td></tr></table></figure><blockquote><p><strong>注意</strong><br>如果上述命令报错,排查 npm 源: <code>npm config ls</code><br>设置 npm 官方源: <code>npm config set registry https://registry.npmjs.org/</code><br>如果未登录则执行<code>npm login</code>登录</p></blockquote><h3 id="四-配置内容"><a class="header-anchor" href="#四-配置内容">¶</a>四、配置内容</h3><p> 在 packages/basic 文件夹下新建 index.js,内容如下:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">module</span>.exports = {</span><br><span class="line"> extends: [</span><br><span class="line"> <span class="string">'airbnb'</span>,</span><br><span class="line"> <span class="string">'plugin:import/errors'</span>,</span><br><span class="line"> <span class="string">'plugin:import/warnings'</span>,</span><br><span class="line"> <span class="string">'plugin:eslint-comments/recommended'</span>,</span><br><span class="line"> <span class="string">'plugin:yml/recommended'</span>,</span><br><span class="line"> <span class="string">'prettier'</span>,</span><br><span class="line"> ],</span><br><span class="line"> plugins: [<span class="string">'html'</span>],</span><br><span class="line"> rules: {</span><br><span class="line"> </span><br><span class="line"> },</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p> <code>rules</code>对象就是我们可以自己改动的配置项,在<code>Airbnb/JavaScript</code>仓库中的 <a href="https://github.com/airbnb/javascript/issues/1089" target="_black">https://github.com/airbnb/javascript/issues/1089</a>,告诉了我们有哪些规则可以被修改。</p><h3 id="五-lerna-命令"><a class="header-anchor" href="#五-lerna-命令">¶</a>五、lerna 命令</h3><h4 id="5-1-创建一个包"><a class="header-anchor" href="#5-1-创建一个包">¶</a>5.1、创建一个包</h4><p><code>lerna create <包名> [存放的目录]</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">lerna create packageName</span><br></pre></td></tr></table></figure><h4 id="5-2-查看当前列表"><a class="header-anchor" href="#5-2-查看当前列表">¶</a>5.2、查看当前列表</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">lerna list</span><br></pre></td></tr></table></figure><p><img src="https://images-hosting.liuxianyu.cn/posts/my-eslint-config-by-lerna/2.png" alt></p><h4 id="5-3-增加本地或者远程-package-作为当前项目-packages-里面的依赖"><a class="header-anchor" href="#5-3-增加本地或者远程-package-作为当前项目-packages-里面的依赖">¶</a>5.3、增加本地或者远程 package 作为当前项目 packages 里面的依赖</h4><p><code>lerna add [@version] [--scope=localPackageName] [-D] [--exact]</code></p><ul><li>-D 表示安装到 devDependencies</li><li>–exact 表示安装准确版本,不带 ^</li></ul><blockquote><p><strong>注意</strong><br>以下基于<code>node 12</code>版本安装插件,需要兼容低版本的可以在插件后加上版本号</p></blockquote><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">lerna add eslint</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">lerna add --scope=@liuxy0551/eslint-config-liuxy0551-basic eslint-loader</span><br><span class="line">lerna add --scope=@liuxy0551/eslint-config-liuxy0551-basic eslint-config-airbnb</span><br><span class="line">lerna add --scope=@liuxy0551/eslint-config-liuxy0551-basic eslint-plugin-eslint-comments</span><br><span class="line">lerna add --scope=@liuxy0551/eslint-config-liuxy0551-basic eslint-plugin-html</span><br><span class="line">lerna add --scope=@liuxy0551/eslint-config-liuxy0551-basic eslint-plugin-import</span><br><span class="line">lerna add --scope=@liuxy0551/eslint-config-liuxy0551-basic eslint-plugin-node</span><br><span class="line">lerna add --scope=@liuxy0551/eslint-config-liuxy0551-basic eslint-plugin-promise</span><br><span class="line">lerna add --scope=@liuxy0551/eslint-config-liuxy0551-basic eslint-plugin-sort-requires</span><br></pre></td></tr></table></figure><p> 加上<code>--scope=</code>表示给本地指定的包安装依赖,也可以 cd 到这个包的文件夹下安装,就不用加<code>--scope=</code>了;不加则是给所有子包都安装该依赖。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">lerna add --scope=@liuxy0551/eslint-config-liuxy0551-typescript @liuxy0551/eslint-config-liuxy0551-basic</span><br><span class="line">lerna add --scope=@liuxy0551/eslint-config-liuxy0551-typescript @typescript-eslint/eslint-plugin</span><br><span class="line">lerna add --scope=@liuxy0551/eslint-config-liuxy0551-typescript @typescript-eslint/parser</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">lerna add --scope=@liuxy0551/eslint-config-liuxy0551-react @liuxy0551/eslint-config-liuxy0551-typescript</span><br><span class="line">lerna add --scope=@liuxy0551/eslint-config-liuxy0551-react eslint-plugin-jest</span><br><span class="line">lerna add --scope=@liuxy0551/eslint-config-liuxy0551-react eslint-plugin-jsx-a11y</span><br><span class="line">lerna add --scope=@liuxy0551/eslint-config-liuxy0551-react eslint-plugin-react</span><br><span class="line">lerna add --scope=@liuxy0551/eslint-config-liuxy0551-react eslint-plugin-react-hooks</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">lerna add --scope=@liuxy0551/eslint-config-liuxy0551-prettier @liuxy0551/eslint-config-liuxy0551-react</span><br><span class="line">lerna add --scope=@liuxy0551/eslint-config-liuxy0551-prettier prettier</span><br><span class="line">lerna add --scope=@liuxy0551/eslint-config-liuxy0551-prettier prettier-plugin-jsdoc</span><br><span class="line">lerna add --scope=@liuxy0551/eslint-config-liuxy0551-prettier eslint-config-prettier</span><br><span class="line">lerna add --scope=@liuxy0551/eslint-config-liuxy0551-prettier eslint-plugin-prettier</span><br></pre></td></tr></table></figure><h4 id="5-4-安装依赖"><a class="header-anchor" href="#5-4-安装依赖">¶</a>5.4、安装依赖</h4><p> 因为我们指定过使用 yarn,直接执行<code>yarn install</code>就会把所有包的依赖安装到根目录的 node_modules。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">lerna bootstrap</span><br></pre></td></tr></table></figure><h4 id="5-5-删除依赖"><a class="header-anchor" href="#5-5-删除依赖">¶</a>5.5、删除依赖</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">lerna clean --scope=特定的某个包</span><br></pre></td></tr></table></figure><p> 和<code>rm -rf node_modules</code>功能一致,<code>--scope=</code>表示指定包,不会移除根目录的 node_modules。</p><h4 id="5-6-建立软链接"><a class="header-anchor" href="#5-6-建立软链接">¶</a>5.6、建立软链接</h4><p><code>lerna link [--force-local]</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">lerna link --force-local</span><br></pre></td></tr></table></figure><p> 类似<code>npm link</code>的使用,–force-local 表示不论本地的版本是否符合,都使用本地的版本。</p><h4 id="5-7-列出更新的包"><a class="header-anchor" href="#5-7-列出更新的包">¶</a>5.7、列出更新的包</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">lerna changed</span><br></pre></td></tr></table></figure><p> 列出改动过的包,发布时只更新改动过的包。</p><p><img src="https://images-hosting.liuxianyu.cn/posts/my-eslint-config-by-lerna/3.png" alt></p><h4 id="5-8-指定版本号"><a class="header-anchor" href="#5-8-指定版本号">¶</a>5.8、指定版本号</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">lerna version 0.0.2 -y</span><br></pre></td></tr></table></figure><p> 需要本地分支和远程分支无差别。</p><h4 id="5-9-发布"><a class="header-anchor" href="#5-9-发布">¶</a>5.9、发布</h4><p><code>lerna publish [--conventional-commits -y]</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">lerna publish</span><br></pre></td></tr></table></figure><p> 需要先执行 git commit,会打 tag,<code>--conventional-commits</code>表示生成 changelog。如果包名是带 scope 的格式,如:@liuxy0551/eslint-config-liuxy0551,则需要在 package.json 中添加配置项,packages 下的每个包都需要加:</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">"publishConfig": {</span><br><span class="line"> "access": "public"</span><br><span class="line">}</span><br></pre></td></tr></table></figure><div class="group-picture"><div class="group-picture-container"><div class="group-picture-row"><div class="group-picture-column" style="width: 50%;"><img src="https://images-hosting.liuxianyu.cn/posts/my-eslint-config-by-lerna/4.png" alt></div><div class="group-picture-column" style="width: 50%;"><img src="https://images-hosting.liuxianyu.cn/posts/my-eslint-config-by-lerna/5.png" alt></div></div><div class="group-picture-row"></div></div></div><h3 id="六-发布整合包"><a class="header-anchor" href="#六-发布整合包">¶</a>六、发布整合包</h3><blockquote><p>lerna publish 只会发布 packages 下的包,当前文件夹并不会作为一个包发布</p></blockquote><p> 在 packages 文件夹下新建一个<code>main</code>,作为入口:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> packages</span><br><span class="line">mkdir main</span><br><span class="line"></span><br><span class="line"><span class="built_in">cd</span> main</span><br><span class="line">npm init -y</span><br></pre></td></tr></table></figure><p> 将 main 下 package.json 中的 name 改成如:@liuxy0551/eslint-config-liuxy0551,version 改成 0.0.0。添加依赖:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">lerna add --scope=@liuxy0551/eslint-config-liuxy0551 eslint@^7.0.0</span><br><span class="line">lerna add --scope=@liuxy0551/eslint-config-liuxy0551 @liuxy0551/eslint-config-liuxy0551-react</span><br></pre></td></tr></table></figure><p> 在 packages/main 文件夹下新建 index.js,内容如下:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/** Export all */</span></span><br><span class="line"><span class="built_in">module</span>.exports = {</span><br><span class="line"> extends: [</span><br><span class="line"> <span class="string">'@liuxy0551/eslint-config-liuxy0551-react'</span>,</span><br><span class="line"> ]</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p> lerna 不会发布标记私有的项目,需要修改根目录 package.json 中的配置<code>"private": false</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm whoami</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">lerna publish</span><br></pre></td></tr></table></figure><p> 访问<a href="https://www.npmjs.com/search?q=@liuxy0551/eslint-config-liuxy0551" target="_black">https://www.npmjs.com/search?q=@liuxy0551/eslint-config-liuxy0551</a> 可以看到发布的包。</p><blockquote><p><strong>常见错误</strong><br>第一次发布失败后出现 Current HEAD is already released<br>执行<code>lerna publish from-package</code></p></blockquote><p> <code>可选步骤</code>删除测试发布的 npm 包:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">npm unpublish @liuxy0551/eslint-config-liuxy0551-react --force</span><br><span class="line">npm unpublish @liuxy0551/eslint-config-liuxy0551-typescript --force</span><br><span class="line">npm unpublish @liuxy0551/eslint-config-liuxy0551-prettier --force</span><br><span class="line">npm unpublish @liuxy0551/eslint-config-liuxy0551-basic --force</span><br><span class="line">npm unpublish @liuxy0551/eslint-config-liuxy0551 --force</span><br></pre></td></tr></table></figure><h3 id="参考资料"><a class="header-anchor" href="#参考资料">¶</a>参考资料</h3><p>1、<a href="https://juejin.cn/post/6844903856153821198" target="_black">Lerna 中文教程详解</a><br>2、<a href="https://www.ahwgs.cn/ruhechuangjianzijideeslintpeizhibao.html" target="_black">如何创建自己的ESLint配置包</a><br>3、<a href="https://segmentfault.com/a/1190000023954051" target="_black">Lerna --多包存储管理工具(一)</a></p>]]></content>
<summary type="html">
<p> 最近在写一个<code>eslint config</code>的整合包,因为有不同语言,会发多个 npm 包,通过 <a href="https://github.com/lerna/lerna" target="_black">Lerna</a> 来管理多包发布,它优化了使用 git 和 npm 管理多包存储库的工作流,Vue、Babel、React 都有使用 Lerna。这里记录下使用过程中的一些点。</p>
</summary>
<category term="前端" scheme="https://liuxianyu.cn/categories/%E5%89%8D%E7%AB%AF/"/>
<category term="eslint" scheme="https://liuxianyu.cn/tags/eslint/"/>
</entry>
<entry>
<title>Node.js 中操作 Redis</title>
<link href="https://liuxianyu.cn/article/node-redis.html"/>
<id>https://liuxianyu.cn/article/node-redis.html</id>
<published>2022-03-12T22:25:55.000Z</published>
<updated>2022-03-12T22:25:55.000Z</updated>
<content type="html"><![CDATA[<p> 之前写了个爬取 Github Trending 的服务 <a href="https://github.com/liuxy0551/github-trending-api" target="_black">github-trending-api</a>,因为网络原因,失败率比较高,最近在进行优化,会每个小时请求一次 Github,失败则重试5次,然后把成功的结果存到 Redis 中。记录下 Node.js 中操作 Redis 的一些方法。</p><a id="more"></a><h3 id="一-安装-redis"><a class="header-anchor" href="#一-安装-redis">¶</a>一、安装 Redis</h3><p> 我这里是通过 Docker 来安装 Redis 的,具体可参考:<a href="https://liuxianyu.cn/article/docker-d.html" target="_black">前端学习 Docker 之旅(五)—— 安装 Redis 并启动、连接</a>。</p><h3 id="二-缓存类型"><a class="header-anchor" href="#二-缓存类型">¶</a>二、缓存类型</h3><p> 主要分为三种:数据库、本地应用缓存(内存等)、远程缓存(Redis),这里不展开细讲。</p><h3 id="三-缓存模式"><a class="header-anchor" href="#三-缓存模式">¶</a>三、缓存模式</h3><div class="group-picture"><div class="group-picture-container"><div class="group-picture-row"><div class="group-picture-column" style="width: 33.333333333333336%;"><img src="https://images-hosting.liuxianyu.cn/posts/node-redis/1.png" alt></div><div class="group-picture-column" style="width: 33.333333333333336%;"><img src="https://images-hosting.liuxianyu.cn/posts/node-redis/2.png" alt></div><div class="group-picture-column" style="width: 33.333333333333336%;"><img src="https://images-hosting.liuxianyu.cn/posts/node-redis/3.png" alt></div></div><div class="group-picture-row"></div></div></div><h3 id="四-node-js-与-redis"><a class="header-anchor" href="#四-node-js-与-redis">¶</a>四、Node.js 与 Redis</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> { createClient } <span class="keyword">from</span> <span class="string">'redis'</span>;</span><br><span class="line"></span><br><span class="line">(<span class="keyword">async</span> () => {</span><br><span class="line"> <span class="keyword">const</span> client = createClient({</span><br><span class="line"> url: <span class="string">'redis://username:password@host:port/db-number'</span></span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> client.on(<span class="string">'error'</span>, (err) => <span class="built_in">console</span>.log(<span class="string">'Redis Client Error'</span>, err));</span><br><span class="line"></span><br><span class="line"> <span class="keyword">await</span> client.connect();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">await</span> client.set(<span class="string">'key'</span>, <span class="string">'value'</span>);</span><br><span class="line"> <span class="keyword">const</span> value = <span class="keyword">await</span> client.get(<span class="string">'key'</span>);</span><br><span class="line">})();</span><br></pre></td></tr></table></figure><p> 语法:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">createClient({</span><br><span class="line"> url: <span class="string">'redis://alice:[email protected]:6380'</span></span><br><span class="line">})</span><br></pre></td></tr></table></figure><h3 id="五-redis-value-类型"><a class="header-anchor" href="#五-redis-value-类型">¶</a>五、Redis value 类型</h3><p> Redis 的 key 是唯一的,如果 key 所对应的 value 是 string 类型,则不能再次覆盖修改为 hash 类型。<a href="https://github.com/redis/node-redis/blob/be51abe347/packages/client/lib/client/commands.ts#L85" target="_black">点此查看其他方法</a></p><h4 id="5-1-string"><a class="header-anchor" href="#5-1-string">¶</a>5.1、string</h4><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">await</span> redis.set(<span class="string">'key'</span>, <span class="string">'value'</span>)</span><br><span class="line"><span class="keyword">const</span> value = <span class="keyword">await</span> redis.get(<span class="string">'key'</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">await</span> redis.setEx(<span class="string">'key'</span>, <span class="number">60</span>, <span class="string">'value'</span>) <span class="comment">// 设置缓存,单位秒</span></span><br></pre></td></tr></table></figure><p> 不建议赋值后再设置过期时间,这样不能保证原子性。</p><h4 id="5-2-hash"><a class="header-anchor" href="#5-2-hash">¶</a>5.2、hash</h4><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> obj = { <span class="attr">name</span>: <span class="string">'Tom'</span> }</span><br><span class="line"><span class="comment">// await redis.hSet('key', obj, 'EX', 60)</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> obj = <span class="keyword">await</span> redis.hGetAll(<span class="string">'key'</span>) <span class="comment">// { name: 'Tom' }</span></span><br><span class="line"><span class="keyword">const</span> name = <span class="keyword">await</span> redis.hGet(<span class="string">'key'</span>, <span class="string">'name'</span>) <span class="comment">// 'Tom'</span></span><br><span class="line"><span class="keyword">const</span> value = <span class="keyword">await</span> redis.hVals(<span class="string">'key'</span>) <span class="comment">// ['Tom']</span></span><br></pre></td></tr></table></figure><p> 取出存入 Redis 的对象时,每个 key 的值会被转成 string。</p><h4 id="5-3-lists"><a class="header-anchor" href="#5-3-lists">¶</a>5.3、lists</h4><h4 id="5-4-sets"><a class="header-anchor" href="#5-4-sets">¶</a>5.4、sets</h4><h4 id="5-4-事务"><a class="header-anchor" href="#5-4-事务">¶</a>5.4、事务</h4><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">redis.multi()</span><br><span class="line"> .set(<span class="string">'key'</span>, <span class="string">'value'</span>)</span><br><span class="line"> .get(<span class="string">'key'</span>)</span><br><span class="line"> .exec(<span class="function">(<span class="params">error, replies</span>) =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(replies) <span class="comment">// ['OK', 'value']</span></span><br><span class="line"> })</span><br></pre></td></tr></table></figure><h3 id="参考资料"><a class="header-anchor" href="#参考资料">¶</a>参考资料</h3><p>1、<a href="https://www.cnblogs.com/zhaowinter/p/10776868.html" target="_black">nodejs操作redis总结</a><br>2、<a href="https://github.com/redis/node-redis" target="_black">node-redis</a><br>3、<a href="https://redis.js.org/" target="_black">https://redis.js.org/</a><br>4、涉及代码:<a href="https://github.com/liuxy0551/github-trending-api" target="_black">https://github.com/liuxy0551/github-trending-api</a></p>]]></content>
<summary type="html">
<p> 之前写了个爬取 Github Trending 的服务 <a href="https://github.com/liuxy0551/github-trending-api" target="_black">github-trending-api</a>,因为网络原因,失败率比较高,最近在进行优化,会每个小时请求一次 Github,失败则重试5次,然后把成功的结果存到 Redis 中。记录下 Node.js 中操作 Redis 的一些方法。</p>
</summary>
<category term="node" scheme="https://liuxianyu.cn/categories/node/"/>
<category term="Redis" scheme="https://liuxianyu.cn/categories/node/Redis/"/>
<category term="node" scheme="https://liuxianyu.cn/tags/node/"/>
<category term="Redis" scheme="https://liuxianyu.cn/tags/Redis/"/>
</entry>
<entry>
<title>iframe 跨域时的通信方式 postMessage</title>
<link href="https://liuxianyu.cn/article/iframe-postMessage.html"/>
<id>https://liuxianyu.cn/article/iframe-postMessage.html</id>
<published>2022-03-10T23:02:41.000Z</published>
<updated>2022-03-10T23:02:41.000Z</updated>
<content type="html"><![CDATA[<p> 最近有个公司客户对接了公司的用户中心,客户期望自己的系统在退出登录时,公司的用户中心也退出登录。提出了 iframe 的技术方案,并给出了实现方式。<br> 因为客户系统和公司的用户中心必然跨域,这里只记录一下跨域时的处理方法。</p><a id="more"></a><h3 id="一-如何实现"><a class="header-anchor" href="#一-如何实现">¶</a>一、如何实现</h3><h4 id="postmessage"><a class="header-anchor" href="#postmessage">¶</a>postMessage</h4><blockquote><p>window.postMessage() 方法可以安全地实现跨源通信。通常,对于两个不同页面的脚本,只有当执行它们的页面位于具有相同的协议(通常为https),端口号(443为https的默认值),以及主机 (两个页面的模数 Document.domain设置为相同的值) 时,这两个脚本才能相互通信。window.postMessage() 方法提供了一种受控机制来规避此限制,只要正确的使用,这种方法就很安全。</p></blockquote><p> 以上是摘自 MDN 的原文,<a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Window/postMessage" target="_black">window.postMessage</a></p><p><strong>语法</strong></p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">otherWindow.postMessage(message, targetOrigin, [transfer]);</span><br></pre></td></tr></table></figure><ul><li><code>otherWindow</code> 其他窗口的引用对象</li><li><code>message</code> 将要传递的消息,字符串或对象</li><li><code>targetOrigin</code> 目标窗口,* 代表所有</li><li><code>transfer</code> 可选,是一串和 message 同时传递的对象</li></ul><p> <code>postMessage</code>非常强大,既可以父传子,也可以子传父,并且是可以跨域传输的。</p><h4 id="业务场景"><a class="header-anchor" href="#业务场景">¶</a>业务场景</h4><p> 客户的系统退出登录后,公司的用户中心前端页面无法感知到已经退出登录,也就无法清除 cookie,如果此时进入公司的用户中心页面,依旧可以访问,所以需要实现同步退出登录。而且因为客户的系统和公司的用户中心不同源,存在跨域,这里通过<code>postMessage</code>来解决。</p><h4 id="父传子"><a class="header-anchor" href="#父传子">¶</a>父传子</h4><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">// 子页面</span><br><span class="line"><span class="tag"><<span class="name">script</span>></span></span><br><span class="line"><span class="javascript"> <span class="built_in">window</span>.addEventListener(<span class="string">"message"</span>, (e) => {</span></span><br><span class="line"><span class="javascript"> <span class="built_in">console</span>.log(<span class="string">'这里是公司的用户中心,客户系统发来消息: '</span>, e.data)</span></span><br><span class="line"><span class="javascript"> <span class="keyword">if</span> (e.data !== <span class="string">'logout'</span>) <span class="keyword">return</span></span></span><br><span class="line"><span class="javascript"> <span class="comment">// TODO 清除 cookie</span></span></span><br><span class="line"> })</span><br><span class="line"><span class="tag"></<span class="name">script</span>></span></span><br><span class="line"></span><br><span class="line">// 父页面</span><br><span class="line"><span class="tag"><<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">iframe</span> <span class="attr">src</span>=<span class="string">"子页面"</span> <span class="attr">id</span>=<span class="string">"dtstack-logout"</span> /></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">onclick</span>=<span class="string">"logoutFunc()"</span>></span>退出登录<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br><span class="line"><span class="tag"><<span class="name">script</span>></span></span><br><span class="line"><span class="javascript"> <span class="function"><span class="keyword">function</span> <span class="title">logoutFunc</span> (<span class="params"></span>) </span>{</span></span><br><span class="line"><span class="javascript"> <span class="built_in">document</span>.getElementById(<span class="string">'dtstack-logout'</span>).contentWindow.postMessage(<span class="string">'logout'</span>, <span class="string">'*'</span>)</span></span><br><span class="line"> }</span><br><span class="line"><span class="tag"></<span class="name">script</span>></span></span><br></pre></td></tr></table></figure><p> 上面的代码表示:在父页面引入 iframe,src 指向子页面,父页面点击<code>退出登录</code>按钮时,向子页面发送一个消息,子页面监听到事先和父页面约定好的信息后,清除 cookie,并给父页面一个反馈。</p><h4 id="子传父"><a class="header-anchor" href="#子传父">¶</a>子传父</h4><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">// 子页面</span><br><span class="line"><span class="tag"><<span class="name">script</span>></span></span><br><span class="line"><span class="javascript"> <span class="built_in">window</span>.addEventListener(<span class="string">"message"</span>, (e) => {</span></span><br><span class="line"><span class="javascript"> <span class="built_in">console</span>.log(<span class="string">'这里是公司的用户中心,客户系统发来消息: '</span>, e.data)</span></span><br><span class="line"><span class="javascript"> <span class="keyword">if</span> (e.data !== <span class="string">'logout'</span>) <span class="keyword">return</span></span></span><br><span class="line"><span class="javascript"> <span class="comment">// TODO 清除 cookie</span></span></span><br><span class="line"><span class="javascript"> setTimeout(<span class="function"><span class="params">()</span> =></span> {</span></span><br><span class="line"><span class="javascript"> <span class="comment">// 向父页面传递消息</span></span></span><br><span class="line"><span class="javascript"> <span class="built_in">window</span>.parent.postMessage(<span class="string">'clear done'</span>, <span class="string">'*'</span>)</span></span><br><span class="line"> }, 2500)</span><br><span class="line"> })</span><br><span class="line"><span class="tag"></<span class="name">script</span>></span></span><br><span class="line"></span><br><span class="line">// 父页面</span><br><span class="line"><span class="tag"><<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">iframe</span> <span class="attr">src</span>=<span class="string">"子页面"</span> <span class="attr">id</span>=<span class="string">"dtstack-logout"</span> /></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">onclick</span>=<span class="string">"logoutFunc()"</span>></span>退出登录<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br><span class="line"><span class="tag"><<span class="name">script</span>></span></span><br><span class="line"><span class="javascript"> <span class="built_in">window</span>.addEventListener(<span class="string">"message"</span>, (e) => {</span></span><br><span class="line"><span class="javascript"> <span class="built_in">console</span>.log(<span class="string">'这里是客户系统,公司的用户中心发来消息: '</span>, e.data)</span></span><br><span class="line"><span class="javascript"> <span class="keyword">if</span> (!e.data.includes(<span class="string">'clear'</span>)) <span class="keyword">return</span></span></span><br><span class="line"><span class="javascript"> <span class="comment">// TODO 清除公司用户中心的 cookie 后需要进行的动作</span></span></span><br><span class="line"> })</span><br><span class="line"><span class="tag"></<span class="name">script</span>></span></span><br></pre></td></tr></table></figure><p> 上面的代码表示:在父页面引入 iframe,src 指向子页面,子页面在接收到父页面清除 cookie 的消息后,清除 cookie,并在完成后给父页面发送消息,父页面接收到后进行下一步动作。</p><h3 id="二-实现效果"><a class="header-anchor" href="#二-实现效果">¶</a>二、实现效果</h3><p align="center"> <img src="https://images-hosting.liuxianyu.cn/posts/iframe-postMessage/1.gif"></p><img src="https://images-hosting.liuxianyu.cn/posts/iframe-postMessage/2.png"><img src="https://images-hosting.liuxianyu.cn/posts/iframe-postMessage/3.png"><p> 以上截图的代码是临时 demo。</p><h3 id="参考资料"><a class="header-anchor" href="#参考资料">¶</a>参考资料</h3><p> 1、<a href="https://www.runoob.com/tags/tag-iframe.html" target="_black">HTML iframe 标签</a></p>]]></content>
<summary type="html">
<p> 最近有个公司客户对接了公司的用户中心,客户期望自己的系统在退出登录时,公司的用户中心也退出登录。提出了 iframe 的技术方案,并给出了实现方式。<br>
因为客户系统和公司的用户中心必然跨域,这里只记录一下跨域时的处理方法。</p>
</summary>
<category term="HTML" scheme="https://liuxianyu.cn/categories/HTML/"/>
<category term="HTML" scheme="https://liuxianyu.cn/tags/HTML/"/>
</entry>
<entry>
<title>Node.js 中的进程和线程</title>
<link href="https://liuxianyu.cn/article/node-process-thread.html"/>
<id>https://liuxianyu.cn/article/node-process-thread.html</id>
<published>2022-02-22T20:43:37.000Z</published>
<updated>2022-02-22T20:43:37.000Z</updated>
<content type="html"><![CDATA[<p> 线程和进程是计算机操作系统的基础概念,在程序员中属于高频词汇,那如何理解呢?Node.js 中的进程和线程又是怎样的呢?</p><a id="more"></a><p align="center"> <img src="https://images-hosting.liuxianyu.cn/posts/node-process-thread/1.png"></p><h3 id="一-进程和线程"><a class="header-anchor" href="#一-进程和线程">¶</a>一、进程和线程</h3><h4 id="1-1-专业性文字定义"><a class="header-anchor" href="#1-1-专业性文字定义">¶</a>1.1、专业性文字定义</h4><ul><li>进程(Process),进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础,进程是线程的容器。</li><li>线程(Thread),线程是操作系统能够进行运算调度的最小单位,被包含在进程之中,是进程中的实际运作单位。</li></ul><h4 id="1-2-通俗理解"><a class="header-anchor" href="#1-2-通俗理解">¶</a>1.2、通俗理解</h4><p> 以上描述比较硬,看完可能也没看懂,还不利于理解记忆。那么我们举个简单的例子:</p><p> 假设你是某个快递站点的一名小哥,起初这个站点负责的区域住户不多,收取件都是你一个人。给张三家送完件,再去李四家取件,事情得一件件做,这叫单线程,所有的工作都得按顺序执行。</p><p> 后来这个区域住户多了,站点给这个区域分配了多个小哥,还有个小组长,你们可以为更多的住户服务了,这叫多线程,小组长是主线程,每个小哥都是一个线程。</p><p> 快递站点使用的小推车等工具,是站点提供的,小哥们都可以使用,并不仅供某一个人,这叫多线程资源共享。</p><p> 站点小推车目前只有一个,大家都需要使用,这叫冲突。解决的方法有很多,排队等待或者等其他小哥用完后的通知,这叫线程同步。</p><p><img src="https://images-hosting.liuxianyu.cn/posts/node-process-thread/2.png" alt></p><p> 总公司有很多站点,各个站点的运营模式几乎一模一样,这叫多进程。总公司叫主进程,各个站点叫子进程。</p><p> 总公司和站点之间,以及各个站点互相之间,小推车都是相互独立的,不能混用,这叫进程间不共享资源。各站点间可以通过电话等方式联系,这叫管道。各站点间还有其他协同手段,便于完成更大的计算任务,这叫进程间同步。</p><p> 还可以看看阮一峰对 <a href="http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html" target="_black">进程与线程的一个简单解释</a>。</p><h3 id="二-node-js-中的进程和线程"><a class="header-anchor" href="#二-node-js-中的进程和线程">¶</a>二、Node.js 中的进程和线程</h3><p> Node.js 是单线程服务,事件驱动和非阻塞 I/O 模型的语言特性,使得 Node.js 高效和轻量。优势在于免去了频繁切换线程和资源冲突;擅长 I/O 密集型操作(底层模块 libuv 通过多线程调用操作系统提供的异步 I/O 能力进行多任务的执行),但是对于服务端的 Node.js,可能每秒有上百个请求需要处理,当面对 CPU 密集型请求时,因为是单线程模式,难免会造成阻塞。</p><h4 id="2-1-node-js-阻塞"><a class="header-anchor" href="#2-1-node-js-阻塞">¶</a>2.1、Node.js 阻塞</h4><p> 我们利用 Koa 简单地搭建一个 Web 服务,用斐波那契数列方法来模拟一下 Node.js 处理 CPU 密集型的计算任务:</p><blockquote><p>斐波那契数列,也称黄金分割数列,这个数列从第三项开始,每一项都等于前两项只和:0、1、1、2、3、5、8、13、21、…</p></blockquote><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// app.js</span></span><br><span class="line"><span class="keyword">const</span> Koa = <span class="built_in">require</span>(<span class="string">'koa'</span>)</span><br><span class="line"><span class="keyword">const</span> router = <span class="built_in">require</span>(<span class="string">'koa-router'</span>)()</span><br><span class="line"><span class="keyword">const</span> app = <span class="keyword">new</span> Koa()</span><br><span class="line"></span><br><span class="line"><span class="comment">// 用来测试是否被阻塞</span></span><br><span class="line">router.get(<span class="string">'/test'</span>, (ctx) => {</span><br><span class="line"> ctx.body = {</span><br><span class="line"> pid: process.pid,</span><br><span class="line"> msg: <span class="string">'Hello World'</span></span><br><span class="line"> }</span><br><span class="line">})</span><br><span class="line">router.get(<span class="string">'/fibo'</span>, (ctx) => {</span><br><span class="line"> <span class="keyword">const</span> { num = <span class="number">38</span> } = ctx.query</span><br><span class="line"> <span class="keyword">const</span> start = <span class="built_in">Date</span>.now()</span><br><span class="line"> <span class="comment">// 斐波那契数列</span></span><br><span class="line"> <span class="keyword">const</span> fibo = <span class="function">(<span class="params">n</span>) =></span> {</span><br><span class="line"> <span class="keyword">return</span> n > <span class="number">1</span> ? fibo(n - <span class="number">1</span>) + fibo(n - <span class="number">2</span>) : <span class="number">1</span></span><br><span class="line"> }</span><br><span class="line"> fibo(num)</span><br><span class="line"></span><br><span class="line"> ctx.body = {</span><br><span class="line"> pid: process.pid,</span><br><span class="line"> duration: <span class="built_in">Date</span>.now() - start</span><br><span class="line"> }</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line">app.use(router.routes())</span><br><span class="line">app.listen(<span class="number">9000</span>, () => {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'Server is running on 9000'</span>)</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p> 执行<code>node app.js</code>启动服务,用 Postman 发送请求,可以看到,计算 38 次耗费了 617ms,换而言之,因为执行了一个 CPU 密集型的计算任务,所以 Node.js 主线程被阻塞了六百多毫秒。如果同时处理更多的请求,或者计算任务更复杂,那么在这些请求之后的所有请求都会被延迟执行。</p><p><img src="https://images-hosting.liuxianyu.cn/posts/node-process-thread/3.png" alt></p><p> 我们再新建一个 axios.js 用来模拟发送多次请求,此时将 app.js 中的 fibo 计算次数改为 43,用来模拟更复杂的计算任务:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// axios.js</span></span><br><span class="line"><span class="keyword">const</span> axios = <span class="built_in">require</span>(<span class="string">'axios'</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> start = <span class="built_in">Date</span>.now()</span><br><span class="line"><span class="keyword">const</span> fn = <span class="function">(<span class="params">url</span>) =></span> {</span><br><span class="line"> axios.get(<span class="string">`http://127.0.0.1:9000/<span class="subst">${ url }</span>`</span>).then(<span class="function">(<span class="params">res</span>) =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(res.data, <span class="string">`耗时: <span class="subst">${ <span class="built_in">Date</span>.now() - start }</span>ms`</span>)</span><br><span class="line"> })</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">fn(<span class="string">'test'</span>)</span><br><span class="line">fn(<span class="string">'fibo?num=43'</span>)</span><br><span class="line">fn(<span class="string">'test'</span>)</span><br></pre></td></tr></table></figure><p><img src="https://images-hosting.liuxianyu.cn/posts/node-process-thread/4.png" alt></p><p> 可以看到,当请求需要执行 CPU 密集型的计算任务时,后续的请求都被阻塞等待,这类请求一多,服务基本就阻塞卡死了。对于这种不足,Node.js 一直在弥补。</p><h4 id="2-2-master-worker"><a class="header-anchor" href="#2-2-master-worker">¶</a>2.2、master-worker</h4><p> master-worker 模式是一种并行模式,核心思想是:系统有两个及以上的进程或线程协同工作时,master 负责接收和分配并整合任务,worker 负责处理任务。</p><p><img src="https://images-hosting.liuxianyu.cn/posts/node-process-thread/5.png" alt></p><h4 id="2-3-多线程"><a class="header-anchor" href="#2-3-多线程">¶</a>2.3、多线程</h4><p> 线程是 CPU 调度的一个基本单位,只能同时执行一个线程的任务,同一个线程也只能被一个 CPU 调用。如果使用的是多核 CPU,那么将无法充分利用 CPU 的性能。</p><p> 多线程带给我们灵活的编程方式,但是需要学习更多的 Api 知识,在编写更多代码的同时也存在着更多的风险,线程的切换和锁也会增加系统资源的开销。</p><ul><li><a href="http://nodejs.cn/api-v14/worker_threads.html" target="_black">worker_threads 工作线程</a>,给 Node.js 提供了真正的多线程能力。</li></ul><p> worker_threads 是 Node.js 提供的一种多线程 Api。对于执行 CPU 密集型的计算任务很有用,对 I/O 密集型的操作帮助不大,因为 Node.js 内置的异步 I/O 操作比 worker_threads 更高效。worker_threads 中的 Worker,parentPort 主要用于子线程和主线程的消息交互。</p><p> 将 app.js 稍微改动下,将 CPU 密集型的计算任务交给子线程计算:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// app.js</span></span><br><span class="line"><span class="keyword">const</span> Koa = <span class="built_in">require</span>(<span class="string">'koa'</span>)</span><br><span class="line"><span class="keyword">const</span> router = <span class="built_in">require</span>(<span class="string">'koa-router'</span>)()</span><br><span class="line"><span class="keyword">const</span> { Worker } = <span class="built_in">require</span>(<span class="string">'worker_threads'</span>)</span><br><span class="line"><span class="keyword">const</span> app = <span class="keyword">new</span> Koa()</span><br><span class="line"></span><br><span class="line"><span class="comment">// 用来测试是否被阻塞</span></span><br><span class="line">router.get(<span class="string">'/test'</span>, (ctx) => {</span><br><span class="line"> ctx.body = {</span><br><span class="line"> pid: process.pid,</span><br><span class="line"> msg: <span class="string">'Hello World'</span></span><br><span class="line"> }</span><br><span class="line">})</span><br><span class="line">router.get(<span class="string">'/fibo'</span>, <span class="keyword">async</span> (ctx) => {</span><br><span class="line"> <span class="keyword">const</span> { num = <span class="number">38</span> } = ctx.query</span><br><span class="line"> ctx.body = <span class="keyword">await</span> asyncFibo(num)</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> asyncFibo = <span class="function">(<span class="params">num</span>) =></span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =></span> {</span><br><span class="line"> <span class="comment">// 创建 worker 线程并传递数据</span></span><br><span class="line"> <span class="keyword">const</span> worker = <span class="keyword">new</span> Worker(<span class="string">'./fibo.js'</span>, { <span class="attr">workerData</span>: { num } })</span><br><span class="line"> <span class="comment">// 主线程监听子线程发送的消息</span></span><br><span class="line"> worker.on(<span class="string">'message'</span>, resolve)</span><br><span class="line"> worker.on(<span class="string">'error'</span>, reject)</span><br><span class="line"> worker.on(<span class="string">'exit'</span>, (code) => {</span><br><span class="line"> <span class="keyword">if</span> (code !== <span class="number">0</span>) reject(<span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">`Worker stopped with exit code <span class="subst">${code}</span>`</span>))</span><br><span class="line"> })</span><br><span class="line"> })</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">app.use(router.routes())</span><br><span class="line">app.listen(<span class="number">9000</span>, () => {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'Server is running on 9000'</span>)</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p> 新增 fibo.js 文件,用来处理复杂计算任务:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> { workerData, parentPort } = <span class="built_in">require</span>(<span class="string">'worker_threads'</span>)</span><br><span class="line"><span class="keyword">const</span> { num } = workerData</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> start = <span class="built_in">Date</span>.now()</span><br><span class="line"><span class="comment">// 斐波那契数列</span></span><br><span class="line"><span class="keyword">const</span> fibo = <span class="function">(<span class="params">n</span>) =></span> {</span><br><span class="line"> <span class="keyword">return</span> n > <span class="number">1</span> ? fibo(n - <span class="number">1</span>) + fibo(n - <span class="number">2</span>) : <span class="number">1</span></span><br><span class="line">}</span><br><span class="line">fibo(num)</span><br><span class="line"></span><br><span class="line">parentPort.postMessage({</span><br><span class="line"> pid: process.pid,</span><br><span class="line"> duration: <span class="built_in">Date</span>.now() - start</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p> 执行上文的 axios.js,此时将 app.js 中的 fibo 计算次数改为 43,用来模拟更复杂的计算任务:</p><p><img src="https://images-hosting.liuxianyu.cn/posts/node-process-thread/6.png" alt></p><p> 可以看到,将 CPU 密集型的计算任务交给子线程处理时,主线程不再被阻塞,只需等待子线程处理完成后,主线程接收子线程返回的结果即可,其他请求不再受影响。<br> 上述代码是演示创建 worker 线程的过程和效果,实际开发中,请使用线程池来代替上述操作,因为频繁创建线程也会有资源的开销。</p><blockquote><p>线程是 CPU 调度的一个基本单位,只能同时执行一个线程的任务,同一个线程也只能被一个 CPU 调用。</p></blockquote><p> 我们再回味下,本小节开头提到的线程和 CPU 的描述,此时由于是新的线程,可以在其他 CPU 核心上执行,可以更充分的利用多核 CPU。</p><h4 id="2-4-多进程"><a class="header-anchor" href="#2-4-多进程">¶</a>2.4、多进程</h4><p> Node.js 为了能充分利用 CPU 的多核能力,提供了 cluster 模块,cluster 可以通过一个父进程管理多个子进程的方式来实现集群的功能。</p><ul><li><a href="http://nodejs.cn/api-v14/child_process.html" target="_black">child_process 子进程</a>,衍生新的 Node.js 进程并使用建立的 IPC 通信通道调用指定的模块。</li><li><a href="http://nodejs.cn/api-v14/cluster.html" target="_black">cluster 集群</a>,可以创建共享服务器端口的子进程,工作进程使用 child_process 的 fork 方法衍生。</li></ul><p> cluster 底层就是 child_process,master 进程做总控,启动 1 个 agent 进程和 n 个 worker 进程,agent 进程处理一些公共事务,比如日志等;worker 进程使用建立的 IPC(Inter-Process Communication)通信通道和 master 进程通信,和 master 进程共享服务端口。</p><p><img src="https://images-hosting.liuxianyu.cn/posts/node-process-thread/7.png" alt></p><p> 新增 fibo-10.js,模拟发送 10 次请求:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// fibo-10.js</span></span><br><span class="line"><span class="keyword">const</span> axios = <span class="built_in">require</span>(<span class="string">'axios'</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> url = <span class="string">`http://127.0.0.1:9000/fibo?num=38`</span></span><br><span class="line"><span class="keyword">const</span> start = <span class="built_in">Date</span>.now()</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i < <span class="number">10</span>; i++) {</span><br><span class="line"> axios.get(url).then(<span class="function">(<span class="params">res</span>) =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(res.data, <span class="string">`耗时: <span class="subst">${ <span class="built_in">Date</span>.now() - start }</span>ms`</span>)</span><br><span class="line"> })</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p> 可以看到,只使用了一个进程,10 个请求慢慢阻塞,累计耗时 15 秒:</p><p><img src="https://images-hosting.liuxianyu.cn/posts/node-process-thread/8.png" alt></p><p> 接下来,将 app.js 稍微改动下,引入 cluster 模块:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// app.js</span></span><br><span class="line"><span class="keyword">const</span> cluster = <span class="built_in">require</span>(<span class="string">'cluster'</span>)</span><br><span class="line"><span class="keyword">const</span> http = <span class="built_in">require</span>(<span class="string">'http'</span>)</span><br><span class="line"><span class="keyword">const</span> numCPUs = <span class="built_in">require</span>(<span class="string">'os'</span>).cpus().length</span><br><span class="line"><span class="comment">// const numCPUs = 10 // worker 进程的数量一般和 CPU 核心数相同</span></span><br><span class="line"><span class="keyword">const</span> Koa = <span class="built_in">require</span>(<span class="string">'koa'</span>)</span><br><span class="line"><span class="keyword">const</span> router = <span class="built_in">require</span>(<span class="string">'koa-router'</span>)()</span><br><span class="line"><span class="keyword">const</span> app = <span class="keyword">new</span> Koa()</span><br><span class="line"></span><br><span class="line"><span class="comment">// 用来测试是否被阻塞</span></span><br><span class="line">router.get(<span class="string">'/test'</span>, (ctx) => {</span><br><span class="line"> ctx.body = {</span><br><span class="line"> pid: process.pid,</span><br><span class="line"> msg: <span class="string">'Hello World'</span></span><br><span class="line"> }</span><br><span class="line">})</span><br><span class="line">router.get(<span class="string">'/fibo'</span>, (ctx) => {</span><br><span class="line"> <span class="keyword">const</span> { num = <span class="number">38</span> } = ctx.query</span><br><span class="line"> <span class="keyword">const</span> start = <span class="built_in">Date</span>.now()</span><br><span class="line"> <span class="comment">// 斐波那契数列</span></span><br><span class="line"> <span class="keyword">const</span> fibo = <span class="function">(<span class="params">n</span>) =></span> {</span><br><span class="line"> <span class="keyword">return</span> n > <span class="number">1</span> ? fibo(n - <span class="number">1</span>) + fibo(n - <span class="number">2</span>) : <span class="number">1</span></span><br><span class="line"> }</span><br><span class="line"> fibo(num)</span><br><span class="line"></span><br><span class="line"> ctx.body = {</span><br><span class="line"> pid: process.pid,</span><br><span class="line"> duration: <span class="built_in">Date</span>.now() - start</span><br><span class="line"> }</span><br><span class="line">})</span><br><span class="line">app.use(router.routes())</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (cluster.isMaster) {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">`Master <span class="subst">${process.pid}</span> is running`</span>)</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 衍生 worker 进程</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i < numCPUs; i++) {</span><br><span class="line"> cluster.fork()</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> cluster.on(<span class="string">'exit'</span>, (worker, code, signal) => {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">`worker <span class="subst">${worker.process.pid}</span> died`</span>)</span><br><span class="line"> })</span><br><span class="line">} <span class="keyword">else</span> {</span><br><span class="line"> app.listen(<span class="number">9000</span>)</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">`Worker <span class="subst">${process.pid}</span> started`</span>)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p> 执行<code>node app.js</code>启动服务,可以看到,cluster 帮我们创建了 1 个 master 进程和 4 个 worker 进程:</p><p><img src="https://images-hosting.liuxianyu.cn/posts/node-process-thread/9.png" alt><br><img src="https://images-hosting.liuxianyu.cn/posts/node-process-thread/10.png" alt></p><p> 通过 fibo-10.js 模拟发送 10 次请求,可以看到,四个进程处理 10 个请求耗时近 9 秒:</p><p><img src="https://images-hosting.liuxianyu.cn/posts/node-process-thread/11.png" alt></p><p> 当启动 10 个 worker 进程时,看看效果:</p><p><img src="https://images-hosting.liuxianyu.cn/posts/node-process-thread/12.png" alt></p><p> 仅需不到 3 秒,不过进程的数量也不是无限的。在日常开发中,worker 进程的数量一般和 CPU 核心数相同。</p><h4 id="2-5-多进程说明"><a class="header-anchor" href="#2-5-多进程说明">¶</a>2.5、多进程说明</h4><p> 开启多进程不全是为了处理高并发,而是为了解决 Node.js 对于多核 CPU 利用率不足的问题。<br> 由父进程通过 fork 方法衍生出来的子进程拥有和父进程一样的资源,但是各自独立,互相之间资源不共享。通常根据 CPU 核心数来设置进程数量,因为系统资源是有限的。</p><h3 id="三-总结"><a class="header-anchor" href="#三-总结">¶</a>三、总结</h3><p> 1、大部分通过多线程解决 CPU 密集型计算任务的方案都可以通过多进程方案来替代;<br> 2、Node.js 虽然异步,但是不代表不会阻塞,CPU 密集型任务最好不要在主线程处理,保证主线程的畅通;<br> 3、不要一味的追求高性能和高并发,达到系统需要即可,高效、敏捷才是项目需要的,这也是 Node.js 轻量的特点。<br> 4、Node.js 中的进程和线程还有很多概念在文章中提到了但没展开细讲或没提到的,比如:Node.js 底层 I/O 的 libuv、IPC 通信通道、多进程如何守护、进程间资源不共享如何处理定时任务、agent 进程等;<br> 5、以上代码可在 <a href="https://github.com/liuxy0551/node-process-thread" target="_black">https://github.com/liuxy0551/node-process-thread</a> 查看。</p>]]></content>
<summary type="html">
<p> 线程和进程是计算机操作系统的基础概念,在程序员中属于高频词汇,那如何理解呢?Node.js 中的进程和线程又是怎样的呢?</p>
</summary>
<category term="node" scheme="https://liuxianyu.cn/categories/node/"/>
<category term="node" scheme="https://liuxianyu.cn/tags/node/"/>
</entry>
<entry>
<title>Apache Bench(ab) 的使用方法</title>
<link href="https://liuxianyu.cn/article/apache-bench.html"/>
<id>https://liuxianyu.cn/article/apache-bench.html</id>
<published>2022-02-22T16:41:45.000Z</published>
<updated>2022-02-22T16:41:45.000Z</updated>
<content type="html"><![CDATA[<p> ApacheBench 是 Apache 服务器自带的一个 web 压力测试工具,简称 ab。ab 是一个命令行工具,对发起负载的本机要求很低,根据 ab 命令可以创建很多的并发访问线程,模拟多个访问者同时对某一 URL 地址进行访问,因此可以用来测试目标服务器的负载压力。总的来说 ab 工具小巧简单,可以提供需要的基本性能指标,但是没有图形化结果,不能监控。这里记录下参数含义。</p><a id="more"></a><p> 格式:ab [options] [http://]hostname[:port]/path</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ab -c 10 -n 10 http://baidu.com/</span><br></pre></td></tr></table></figure><p>指令解释:</p><ul><li><code>-n</code> 本次测试发起的总请求数</li><li><code>-c</code> 一次产生的请求数(或并发数)</li><li><code>-t</code> 测试所进行的最大秒数,默认没有时间限制</li><li><code>-r</code> 抛出异常继续执行测试任务</li><li><code>-p</code> 包含了需要 POST 的数据的文件,文件格式如<code>p1=1&p2=2</code>,使用方法是<code>-p 111.txt</code></li><li><code>-T</code> POST 数据所使用的 Content-type 头信息,如<code>-T “application/x-www-form-urlencoded”</code>,配合<code>-p</code>使用</li><li><code>-v</code> 设置显示信息的详细程度 – 4或更大值会显示头信息, 3或更大值可以显示响应代码(404, 200等), 2或更大值可以显示警告和其他信息。 -V 显示版本号并退出</li><li><code>-c</code> <code>-C cookie-name=value</code>对请求附加一个 Cookie:行。其典型形式是<code>name=value</code>的一个参数对。此参数可以重复,用逗号分割。</li><li><code>-w</code> 以 HTML 表的格式输出结果,默认是白色背景的两列宽度的一张表。</li></ul><p> 参数很多,一般我们用<code>-c</code>和<code>-n</code>参数就可以了,<code>ab -c 并发数 -n 请求数 URL地址</code>。</p><p><code>-c</code>后面的 10 表示采用 10 个并发(模拟 10 个人同时访问),<code>-n</code>后面的 10 代表总共发出 10 个请求;后面的网址表示测试的目标 URL。</p><p><img src="https://images-hosting.liuxianyu.cn/posts/apache-bench/1.png" alt></p><ul><li>Document Path 测试页面</li><li>Document Length 页面大小</li><li>Concurrency Level 测试的并发数</li><li>Time taken for tests 整个测试持续的时间</li><li>Complete requests 完成的请求数量</li><li>Failed requests 失败的请求数量</li><li>Write errors: 0</li><li>Total transferred 整个过程中的网络传输量</li><li>HTML transferred 整个过程中的HTML内容传输量</li><li>Requests per second 最重要的指标之一,相当于LR中的每秒事务数,后面括号中的mean表示这是一个平均值</li><li>Time per request 最重要的指标之二,相当于LR中的平均事务响应时间,后面括号中的mean表示这是一个平均值</li><li>Time per request 每个连接请求实际运行时间的平均值</li><li>Transfer rate 平均每秒网络上的流量,可以帮助排除是否存在网络流量过大导致响应时间延长的问题</li></ul>]]></content>
<summary type="html">
<p> ApacheBench 是 Apache 服务器自带的一个 web 压力测试工具,简称 ab。ab 是一个命令行工具,对发起负载的本机要求很低,根据 ab 命令可以创建很多的并发访问线程,模拟多个访问者同时对某一 URL 地址进行访问,因此可以用来测试目标服务器的负载压力。总的来说 ab 工具小巧简单,可以提供需要的基本性能指标,但是没有图形化结果,不能监控。这里记录下参数含义。</p>
</summary>
<category term="Linux" scheme="https://liuxianyu.cn/categories/Linux/"/>
<category term="Linux" scheme="https://liuxianyu.cn/tags/Linux/"/>
</entry>
<entry>
<title>React 项目中同时引入 antd3.x 和 antd4.x</title>
<link href="https://liuxianyu.cn/article/antd3-antd4-together.html"/>
<id>https://liuxianyu.cn/article/antd3-antd4-together.html</id>
<published>2022-02-13T11:33:16.000Z</published>
<updated>2022-03-31T16:23:02.000Z</updated>
<content type="html"><![CDATA[<p> 公司的项目中一直使用的是<code>antd3.x</code>,最近有个需求,<code>TreeSelect</code>支持多选和模糊搜索的时候,模糊搜索后选中某个选项,不清除搜索条件,点击组件外其他地方才清除搜索条件。思路是如下:</p><ul><li>设置 autoClearSearchValue 为 false,选择选项后不清除搜索框</li><li>监听 onBlur 事件,触发时清除搜索框</li></ul><a id="more"></a><h3 id="一-使用-antd3-x"><a class="header-anchor" href="#一-使用-antd3-x">¶</a>一、使用 antd3.x</h3><p> 由于年久失修,antd3.x 有很多 api 有错误,会影响业务场景,这里就有一个坑:</p><p> <code>antd3.x TreeSelect</code>,在第一次获得焦点时,会依次触发<code>onFocus</code>、<code>onBlur</code>、<code>onFocus</code>,此时会清除一下搜索框,第一次还没有输入搜索条件,清除也无伤大雅。</p><p><img src="https://images-hosting.liuxianyu.cn/posts/antd3-antd4-together/1.gif" alt></p><p> 输入搜索内容后,选择某个选项,此时会发现,第一次选择选项,会再次出触发<code>onBlur</code>事件,这就让人很尴尬了,这样会让开发者无法区分<code>onBlur</code>到底是搜索后第一次选择选项触发的,还是点击组件外触发的,也就不能在<code>onBlur</code>事件中清除搜索条件,否则与需求不符。</p><p><img src="https://images-hosting.liuxianyu.cn/posts/antd3-antd4-together/2.gif" alt></p><blockquote><p><strong>思考</strong><br>我们去<code>antd4.x TreeSelect</code>尝试了一下,发现没有这个问题,所以着手引入 antd4.x;<br>但是项目中目前暂时不能全部升级 antd4.x,否则改动太大;<br>最终考虑同时引入 antd3.x 和 antd4.x。</p></blockquote><h3 id="二-使用-antd4-x"><a class="header-anchor" href="#二-使用-antd4-x">¶</a>二、使用 antd4.x</h3><h4 id="2-1-安装"><a class="header-anchor" href="#2-1-安装">¶</a>2.1、安装</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn add antd-v4@npm:antd@^4</span><br></pre></td></tr></table></figure><p> 安装完成后可以在 package.json 和 yarn.lock 中看到安装的依赖:</p><div class="group-picture"><div class="group-picture-container"><div class="group-picture-row"><div class="group-picture-column" style="width: 50%;"><img src="https://images-hosting.liuxianyu.cn/posts/antd3-antd4-together/3.png" alt></div><div class="group-picture-column" style="width: 50%;"><img src="https://images-hosting.liuxianyu.cn/posts/antd3-antd4-together/4.png" alt></div></div><div class="group-picture-row"></div></div></div><h4 id="2-2-配置-css-loader"><a class="header-anchor" href="#2-2-配置-css-loader">¶</a>2.2、配置 css loader</h4><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">'path'</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>: {</span><br><span class="line"> rules: [</span><br><span class="line"> {</span><br><span class="line"> test: <span class="regexp">/\.less$/</span>,</span><br><span class="line"> include: [</span><br><span class="line"> path.resolve(__dirname, <span class="string">"../../node_modules/antd-v4"</span>),</span><br><span class="line"> ],</span><br><span class="line"> use: [</span><br><span class="line"> <span class="string">'style-loader'</span>,</span><br><span class="line"> {</span><br><span class="line"> loader: <span class="string">'css-loader'</span>,</span><br><span class="line"> options: {</span><br><span class="line"> importLoaders: <span class="number">1</span>,</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> loader: <span class="string">'less-loader'</span>,</span><br><span class="line"> options: {</span><br><span class="line"> javascriptEnabled: <span class="literal">true</span>,</span><br><span class="line"> modifyVars: {</span><br><span class="line"> <span class="string">'@ant-prefix'</span>: <span class="string">'ant-v4'</span>,</span><br><span class="line"> <span class="string">'@primary-color'</span>: <span class="string">'#237804'</span>, <span class="comment">// 主题色</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line">},</span><br></pre></td></tr></table></figure><ul><li><code>style-loader</code>将打包后的 css 代码添加到页面头部</li><li><code>css-loader</code>加载 css 文件,<code>importLoaders</code>的含义可参考:<a href="https://zhuanlan.zhihu.com/p/94706976" target="_black">css-loader中importLoaders的理解</a></li><li><code>less-loader</code>将 less 转成 css,<code>modifyVars</code>可以修改 less 中变量的值,我们再配合 antd4.x ConfigProvider 的 prefixCls 属性,搭配 ant-prefix 将样式前缀修改为<code>antd-v4</code>,故意修改下主题色,可以更显眼的看到是否成功</li></ul><p> 如果已经配置了 less loader,建议给之前的规则添加 exclude:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">exclude: [</span><br><span class="line"> path.resolve(__dirname, <span class="string">"../../node_modules/antd-v4"</span>),</span><br><span class="line">],</span><br></pre></td></tr></table></figure><blockquote><p><strong>注意</strong><br><code>path.resolve</code> 处理的是 node_modules 中的 antd-v4,注意路径<br>修改 webpack 配置后需要重启项目</p></blockquote><h4 id="2-3-引入样式-组件"><a class="header-anchor" href="#2-3-引入样式-组件">¶</a>2.3、引入样式、组件</h4><p> 在<code>/src/components</code>下新建一个文件夹 <code>TreeSelectV4</code>,添加 index.tsx:</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 封装 antd4.x 的 TreeSelect</span></span><br><span class="line"><span class="comment"> * 使用的地方可以直接使用 TreeSelectV4,不再需要引入各种 antd4.x 的依赖</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> React <span class="keyword">from</span> <span class="string">'react'</span></span><br><span class="line"><span class="keyword">import</span> { ConfigProvider, TreeSelect } <span class="keyword">from</span> <span class="string">'antd-v4'</span>;</span><br><span class="line"><span class="keyword">import</span> zhCN <span class="keyword">from</span> <span class="string">'antd-v4/es/locale/zh_CN'</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="string">'antd-v4/dist/antd.less'</span>;</span><br><span class="line"><span class="comment">// import './style.scss'</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">class</span> TreeSelectV4 <span class="keyword">extends</span> React.Component<<span class="built_in">any</span>, <span class="built_in">any</span>> {</span><br><span class="line"> render () {</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <ConfigProvider locale={zhCN} prefixCls=<span class="string">"ant-v4"</span>></span><br><span class="line"> <TreeSelect {...this.props} /></span><br><span class="line"> <<span class="regexp">/ConfigProvider></span></span><br><span class="line"><span class="regexp"> )</span></span><br><span class="line"><span class="regexp"> }</span></span><br><span class="line"><span class="regexp">}</span></span><br></pre></td></tr></table></figure><ul><li>prefixCls 是样式前缀,这里统一写成<code>ant-v4</code>。</li></ul><p><img src="https://images-hosting.liuxianyu.cn/posts/antd3-antd4-together/5.gif" alt></p><blockquote><p><strong>注意</strong><br>建议完成以上内容后执行 lint、check-types、test 等命令<br>建议升级<code>typescript</code>等相关依赖的版本</p></blockquote><h3 id="参考资料"><a class="header-anchor" href="#参考资料">¶</a>参考资料</h3><p>1、<a href="https://3x.ant.design/components/tree-select-cn/" target="_black">antd3.x 的 TreeSelect</a><br>2、<a href="https://ant.design/components/tree-select-cn/" target="_black">antd4.x 的 TreeSelect</a></p>]]></content>
<summary type="html">
<p> 公司的项目中一直使用的是<code>antd3.x</code>,最近有个需求,<code>TreeSelect</code>支持多选和模糊搜索的时候,模糊搜索后选中某个选项,不清除搜索条件,点击组件外其他地方才清除搜索条件。思路是如下:</p>
<ul>
<li>设置 autoClearSearchValue 为 false,选择选项后不清除搜索框</li>
<li>监听 onBlur 事件,触发时清除搜索框</li>
</ul>
</summary>
<category term="前端" scheme="https://liuxianyu.cn/categories/%E5%89%8D%E7%AB%AF/"/>
<category term="antd" scheme="https://liuxianyu.cn/tags/antd/"/>
</entry>
</feed>