forked from cestr-forks/xiaomo1992
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathatom.xml
441 lines (235 loc) · 693 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
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>小莫的博客-fighting(技术分享、生活随笔)</title>
<subtitle>行百里者半九十</subtitle>
<link href="https://blog.xiaomo.info/atom.xml" rel="self"/>
<link href="https://blog.xiaomo.info/"/>
<updated>2022-08-10T23:53:17.655Z</updated>
<id>https://blog.xiaomo.info/</id>
<author>
<name>小莫</name>
</author>
<generator uri="https://hexo.io/">Hexo</generator>
<entry>
<title>2021年年终总结(又是跨界的一年)</title>
<link href="https://blog.xiaomo.info/2021/mySummary2021/"/>
<id>https://blog.xiaomo.info/2021/mySummary2021/</id>
<published>2021-12-31T00:00:00.000Z</published>
<updated>2022-08-10T23:53:17.655Z</updated>
<content type="html"><![CDATA[<p>不管愿不愿意,2022年还是如期而至。对于我来说今年是个特殊的一年,从之前的奔三到了实实在在的到了30岁,心态还是有很多变化的。年底工作上非常的忙,导致2022年都过了半个月还没有把总结写法,迟到了一个月的年终总结终于出炉了,不过截至到今天完全写完正好是农历除夕,也不算迟到吧。</p><a id="more"></a><h4 id="建议直接看视频高清版"><a href="#建议直接看视频高清版" class="headerlink" title="建议直接看视频高清版"></a>建议直接看视频<a href="https://www.bilibili.com/video/BV1Sm4y1o7aZ" target="_blank" rel="noopener">高清版</a></h4><iframe src="//player.bilibili.com/player.html?aid=678786061&bvid=BV1Sm4y1o7aZ&cid=501656401&page=1" scrolling="no" height="768" width="1024" border="0" frameborder="no" framespacing="0" allowfullscreen="true"> </iframe><h2 id="工作"><a href="#工作" class="headerlink" title="工作"></a>工作</h2><h3 id="参与的项目"><a href="#参与的项目" class="headerlink" title="参与的项目"></a>参与的项目</h3><h4 id="游戏平台管理系统"><a href="#游戏平台管理系统" class="headerlink" title="游戏平台管理系统"></a>游戏平台管理系统</h4><ul><li>后端<code>java</code>,前端<code>vue2</code></li><li>系统新功能的开发和维护</li><li>新游戏上线流程的优化</li><li>即存Bug的修改和UI/UX的优化</li><li>支付授权、支付回调、支付方式、订单等功能的开发</li><li>添加google sso登陆方式<br><img src="https://image.xiaomo.info/blog/2022-01-30-092009.png" alt="image-20220130182004894"></li></ul><h4 id="游戏平台"><a href="#游戏平台" class="headerlink" title="游戏平台"></a>游戏平台</h4><ul><li>前端<code>React</code>,后端<code>java</code></li><li>功能的开发</li><li>线上bug的修复<br><img src="https://image.xiaomo.info/blog/2022-01-30-091942.png" alt="image-20220130181941842"><br><br><br></li></ul><h3 id="管理相关"><a href="#管理相关" class="headerlink" title="管理相关"></a>管理相关</h3><ul><li>面试</li><li>Intern发表评价</li><li>Project Tracking</li><li><a href="https://www.bilibili.com/video/BV1vy4y1x7xd/" target="_blank" rel="noopener">技术分享:游戏开发浅析:科普向</a><iframe src="//player.bilibili.com/player.html?aid=802515835&bvid=BV1vy4y1x7xd&cid=321456692&page=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true" height="768" width="1024"> </iframe><br><br></li></ul><h3 id="PIC的项目"><a href="#PIC的项目" class="headerlink" title="PIC的项目"></a>PIC的项目</h3><h4 id="把公司官网从ctw-inc-net迁移到ctw-inc上"><a href="#把公司官网从ctw-inc-net迁移到ctw-inc上" class="headerlink" title="把公司官网从ctw-inc.net迁移到ctw.inc上"></a>把公司官网从<a href="https://ctw-inc.net/" target="_blank" rel="noopener">ctw-inc.net</a>迁移到<a href="https://ctw.inc/" target="_blank" rel="noopener">ctw.inc</a>上</h4><ul><li>技术栈是 <code>wordpress</code>,服务放在了<code>AWS</code>上</li><li>整个网站打包迁移到新的域名</li><li>配置了<code>Google Analytics</code>和<code>Google SSO</code>登陆</li><li>配套的一些<code>SEO优化设置</code></li><li>维护着官网上的一些内容更新<br><img src="https://image.xiaomo.info/blog/2022-01-30-091908.png" alt="image-20220130181907797"><br><br><br></li></ul><h4 id="根据开源博客hexo二次开发了公司技术部的博客网站"><a href="#根据开源博客hexo二次开发了公司技术部的博客网站" class="headerlink" title="根据开源博客hexo二次开发了公司技术部的博客网站"></a>根据开源博客hexo二次开发了公司技术部的博客网站</h4><ul><li>从0搭建了一个技术博客</li><li>根据UI设计对博客主题进行二次开发</li><li>维护博客的一些内容更新<br><img src="https://image.xiaomo.info/blog/2022-01-30-084948.png" alt="image-20220130174948507"><br><br><br></li></ul><h4 id="上线了短连接服务"><a href="#上线了短连接服务" class="headerlink" title="上线了短连接服务"></a>上线了短连接服务</h4><ul><li>后端API用go语言开发,管理界面用vue开发,项目打docker镜像放在k8s集群上</li><li>主要功能就是在host后成生成一个随机串,比如 <a href="https://domain/mblghox8%EF%BC%8C%E8%AE%BF%E9%97%AE%E8%BF%99%E4%B8%AA%E5%9C%B0%E5%9D%80%E5%8F%AF%E4%BB%A5%E8%B7%B3%E5%88%B0%E5%B8%A6%E6%9C%89%E5%BE%88%E5%A4%9A%E5%8F%82%E6%95%B0%E7%9A%84%E5%8E%9F%E5%A7%8B%E9%93%BE%E6%8E%A5" target="_blank" rel="noopener">https://domain/mblghox8,访问这个地址可以跳到带有很多参数的原始链接</a></li><li>在平台管理工具上添加了对短连接的管理UI<br><br><br></li></ul><h4 id="开发了平台三周年庆典的活动网站"><a href="#开发了平台三周年庆典的活动网站" class="headerlink" title="开发了平台三周年庆典的活动网站"></a>开发了平台三周年庆典的活动网站</h4><ul><li>前端<code>React</code>,后端<code>java</code>,数据库<code>mysql</code></li><li>API接口管理</li><li>接入<code>Twitter API</code>拉取最新的数量<br><br><br></li></ul><h4 id="搭建前端React-vite-react-tailwind-mui-和后端Golang-go-gin-gorm-模板"><a href="#搭建前端React-vite-react-tailwind-mui-和后端Golang-go-gin-gorm-模板" class="headerlink" title="搭建前端React(vite+react+tailwind+mui)和后端Golang(go+gin+gorm)模板"></a>搭建前端React(vite+react+tailwind+mui)和后端Golang(go+gin+gorm)模板</h4><ul><li>公司项目越来越多,技术负债也越来越重。为了统一技术栈,搭建了前后端开发的模板,方便新起项目的时候快速搭建</li><li>golang项目模板搭建的时候参考了<code>springboot</code>开发的结构,现在实际项目中己经修改和原来结构完全不同的结构<br><img src="https://image.xiaomo.info/blog/2022-01-30-092536.png" alt="image-20220130182536394"><br><br><br></li></ul><h4 id="翻译平台"><a href="#翻译平台" class="headerlink" title="翻译平台"></a>翻译平台</h4><ul><li>前端<code>React</code>,后端<code>Golang</code>,其他技术栈:<code>mysql</code>+<code>redis</code>+<code>elasticsearch</code>+<code>kafka</code></li><li>因为平台开始做全球化,需要把游戏翻译成各个国家的语言版本,原来的三方工具<a href="https://crowdin.com/" target="_blank" rel="noopener">crowdin</a>无法满足需求,所以自己写了一个翻译平台</li><li>前后端分离,项目放在了<code>k8s</code>上<br><img src="https://image.xiaomo.info/blog/2022-01-30-084915.png" alt="image-20220130174755461"><br></li></ul><h3 id="工作上的变动"><a href="#工作上的变动" class="headerlink" title="工作上的变动"></a>工作上的变动</h3><p>一开始参加工作的时候一直做的后端开发,有的时候有一些好的想法想做一个小项目出来,但是因为只会后端所以把API都写出来了却没有前端页面,导致想法也不了了之。后来为了解决这个困扰开始学习前端,把主流的<code>Angular</code>、<code>React</code>、<code>Vue</code>都玩了一遍,2019年从国内游戏公司离职到日本做前端写<code>angular</code>算是真正的开始在商业项目中参与前端的工作。2020年还是回到了心心念念的游戏行业,不过开始是<code>fullstack</code>的角色参与开发。主要使用<code>java</code>和<code>vue</code>,在年底的时候开始陆续在一些微服务上用<code>golang</code>写API,在基础设施搭建方面和同事接触了解了一些<code>aws</code>、<code>terraform</code>、<code>k8s</code>的知识,开始产生兴趣不断的学习。于是在年底的时候从<code>App</code>开发组转到了<code>Cloud</code>组做系统架构,期间也在用<code>golang</code>写翻译平台的API,用React写一些前端项目。<br><br><br></p><h2 id="技术"><a href="#技术" class="headerlink" title="技术"></a>技术</h2><h3 id="前端方面"><a href="#前端方面" class="headerlink" title="前端方面"></a>前端方面</h3><ul><li>彻底放弃<code>Vue</code>系列,开始全身心关注React技术栈相关内容,因为精力实在有限(Vue非常简单,如果以后工作有需要稍微看下最新文档就可以写业务),把之前用<code>React Class Component</code>全部换到了<code>React Hook</code>的写法</li><li>学习了<code>MUI</code>的使用,虽然它只是一个UI框架但是对于CSS的写法和原生React写css样式有着巨大的差异</li><li>在项目中使用<code>Tailwindcss</code>来写css,不断的在和同事讨论最佳实践,引用了<code>tailwindcss-classnames</code>来对<code>class</code>进行检查</li><li>构建工具从<code>webpack</code>换到了<code>vite</code>,后端<code>esbuild</code>前端<code>vite</code>来打包项目。但是因为要多环境部署使用<code>runtimeConfig</code>来打包项目变量</li><li>TS的用法在前端和后端Node项目中得到了大量的练习,对于基本使用己没有太大问题。</li></ul><p><br><br></p><h3 id="后端方面"><a href="#后端方面" class="headerlink" title="后端方面"></a>后端方面</h3><ul><li>用Node(<code>Koa2</code> + <code>sequlize</code>)写了一个订单相关的项目,感觉只是在API层面上使用的话边写边查文档不用很熟写功能也问题不大。但是和熟练者相比效率会低,而且有些坑不知道会有些问题。</li><li>从开始排斥用<code>Golang</code>写新项目到积极的拥抱<code>Golang</code>,感受到用新东西需要有一个接受过程而且如果有可以一起交流的小伙伴会更加有动力坚持下去。</li><li>写了7年的Java,照目前的计划的话以后写Java的机会会非常少,不过一点也没有觉得可惜。技术在不断的更新,人也得往前走,一直待在舒适区是一件很恐怖的事情。</li></ul><p><br><br></p><h3 id="Infra方面"><a href="#Infra方面" class="headerlink" title="Infra方面"></a>Infra方面</h3><ul><li>2015年开始使用阿里云在上面买服务器折腾一些内容,直到今年10月份接触到AWS才了解到AWS的强大,因此组建了一个AWS学习小组一起学习AWS并把内容直播放在<a href="https://www.youtube.com/playlist?list=PL5W70sCpELWAtXDglmoEAfLhnEVWOxfwX" target="_blank" rel="noopener">Youtube</a> 和 <a href="https://www.bilibili.com/video/BV1nb4y1q7r3/" target="_blank" rel="noopener">bilibili</a> 上,有兴趣的朋友可以看看我们在学习过程中如何成长,有没有踩过你踩到的坑。</li><li>IaC是近些年非常流行的一个思想,使用写代码的方式代替在网页上点点点的操作好处显而易见。比如代码的复用性,可维护性等等都是很大的优势,因此学习了Terraform如何管理我的AWS资源并以考取terraform证书为目标。当然IaC工具不光这一个,比如AWS的服务<code>CloudFormation</code>和用不同的编程语言都可以管理你的资源的<a href="https://www.pulumi.com/" target="_blank" rel="noopener">pulumi</a>,根据自己的需要进行学习。目前考到了Terraform的证书,HashCrop家还有2个证书后续有时间也想一起考了。<br><img src="https://tva1.sinaimg.cn/large/008i3skNgy1gyvryraxfsj31wq0su0xo.jpg" alt="image-20220130172402266"></li><li>前几年使用Docker也仅限于用Docker起一个本地测试的数据库之类的,今年系统的学习了Docker相关的知识,包括基础用法、DockerFile、Compose、网络、原理等,计划春节期间花一周的时间来开一个Docker专题当一下Docker的布道师,也巩固一下自己的Docker知识。(Podman用法基本一致,后面学下原理感觉问题不大)</li><li>使用Docker来打包项目好处很多,但是如果Docker镜像一旦多起来了如何编排呢?手工维护是一件非常头疼的事情,因此开始学习k8s如何使用,以考取<code>CKA</code>+<code>CKS</code>为目标来学习k8s</li><li>其他的一些监控系统的搭建,比如日志收集系统<code>ELK</code>,k8s资源监控<code>grafana</code>,项目部署系统<code>ArgoCD</code>等等</li></ul><h2 id="语言"><a href="#语言" class="headerlink" title="语言"></a>语言</h2><h4 id="日语方面"><a href="#日语方面" class="headerlink" title="日语方面"></a>日语方面</h4><p>2021年12月份的N1依旧没有通过考试,最初目标是为了想拿永驻的签证可以加分所以卯着劲想要考过,但是结果证明我的日语还只是刚刚达到N2的水平,甚至N2的语法大部分都没有掌握,勉强去考N1意义不大,因此后续计划把N2的课程系统的从头再学一遍。等基础打牢了之后再继续以N1为目标吧,经历了这几次考试我深刻的体会到结果不会欺骗你,你在它上面付出了多少心血,它就会拿同等的结果来回报你,想要顺利通过就一定得付出努力才行。</p><p><img src="https://image.xiaomo.info/blog/2022-01-30-090734.jpg" alt="3.pic"></p><h4 id="英语方面"><a href="#英语方面" class="headerlink" title="英语方面"></a>英语方面</h4><p>在这次的Terraform的备考和考试的经历来说,不会英语会成为技术进步的一大阻碍。本来是想着工作中需要大量的读到英语的文档,坚持看英语很快就能提高。但是理想很美好现实不如意,平时在公司工作比较忙想静下心来细读英文文档的次数非常少,为了更快的理解经常读到一半的时候直接翻译成中文快速的过一遍然后就去写代码了。个人觉得还有一个非常重要的原因是对英语的恐惧感,目前我读日语的文章己经没有之前的那种恐惧感了,但是英语还是会有这种感觉。不是说我的英语水平理解不了文章的内容,而是看着看着突然有个不认识的词卡住再去查觉得挺难受,而且看技术文档本来就比较耗费精力,再分一部分脑细胞给理解英语上就很抵触。这是我目前最大的问题,为了克服这种障碍我计划开始从简单的非技术文章开始看起,养成看英语文章的习惯而不是打开一个网页是英文的条件反射的就给翻译成中文。以托业750+为目标来好好学习英语。<br><img src="https://image.xiaomo.info/blog/2022-01-30-092946.jpg" alt="1371643534969_.pic"></p><h2 id="兴趣"><a href="#兴趣" class="headerlink" title="兴趣"></a>兴趣</h2><h4 id="游戏"><a href="#游戏" class="headerlink" title="游戏"></a>游戏</h4><p>2015年进入游戏行业后对游戏的原理和套路了解的透彻之后对游戏反而没有那么大的兴趣了。还有一种可能应该是年龄越来越大能力却配不上这个年纪的时候心里对玩游戏也没有了那么大的兴致。但是作为一个玩游戏长大的人来说,生活中如果没有了游戏生活的乐趣一定会少很多。当初一起玩网游(WOW、剑灵、DNF、LOL、PUBG)的朋友和同事一个一个的从身上消失,我也就没有了继续玩下去的动力。尝试着玩一玩别的类型的游戏,于是买了Switch OLED和一些游戏卡,尽量让自己的生活不那么无趣。</p><p><img src="https://image.xiaomo.info/blog/2022-01-30-094220.jpg" alt="1381643535723_.pic"></p><h4 id="钢琴"><a href="#钢琴" class="headerlink" title="钢琴"></a>钢琴</h4><p>学习钢琴是我这十几年来一直想要付诸实践的事情,但是受限于各种原因一直没有机会,趁着过年的机会买了一台kawari es110。虽然可能短期并没有机会让自己能够比较熟练的学会如何弹奏,但是也算是满足了这么多年的一个想要学习的愿望吧。<br><img src="https://image.xiaomo.info/blog/2022-01-30-094748.png" alt="image-20220130184747945"></p><h4 id="滑雪"><a href="#滑雪" class="headerlink" title="滑雪"></a>滑雪</h4><p>在很偶然的机会下,同事邀请着一起去滑雪。在杭州的时候曾经有一次去滑雪玩过一次双板,但是具体什么感觉己经完全记不住了。本来并不打算去,但是后来想要挑战一下自我,是一个难得的机会于是和同事一起去了越后汤泽的滑雪场。己经数不清摔了有多少次,但是作为真正的第一次滑雪体验,成长速度还算是比较满意。后续元旦和三连休的时候又连续去了两次,目前己经算是我众多爱好里在不断追求进步的爱好之一了。毕竟花了那么多钱置办装备,如果不坚持下去怎么对得起自己的钱包,希望今年冬天我能够飘逸的滑完全程不摔跤。</p><img src="https://image.xiaomo.info/blog/2022-01-30-095449.png" alt="image-20220130185449574" style="zoom:50%;"><iframe src="//player.bilibili.com/player.html?aid=465546934&bvid=BV1EL41157Ua&cid=482457665&page=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true" height="768" width="1024"> </iframe><h4 id="动漫"><a href="#动漫" class="headerlink" title="动漫"></a>动漫</h4><p>小时候虽然看过各种动漫,但是电视上的动漫都汉化成了中文,体会不到看原版动漫那种感觉。应该来说真正爱上看动漫是在上大学的时候,追着当时的热门番一直就这么追了十几年,这应该是我有生之年坚持的最久且热情不减的爱好了。如果看到这里的你还怎么看过动漫,一定建议去看一看,它远比各种偶像剧、综艺那些带给我们的感触要多的多。我在Notion上维护着一个动漫列表,收集着这些年来非常推荐的一些动漫,有兴趣的可以<a href="https://xiaomoinfo.notion.site/f3cd200d379740efbd381f57e0714d5d?v=8541fff72dff464e8564acb26ef46a83" target="_blank" rel="noopener">打开看一看</a>。</p><p><img src="https://image.xiaomo.info/blog/2022-01-30-100236.png" alt="image-20220130190235938"></p><h4 id="旅游"><a href="#旅游" class="headerlink" title="旅游"></a>旅游</h4><p>我的梦想就是环游世界,做着自由的工作。背着电脑在不同的国家边体会各国的风情边工作,但是近两年持续没有好转别说去别的国家旅游,连回国都变的不可能。但好的一点是很多公司己经开始接受永久远程办公了,这也算是间接的离梦想更近了一步吧。有认识的朋友在公司允许永久远程工作之后很快就从东京搬到了福冈,不必在拥挤的东京和大家抢资源是一件非常值得开心的事情。后面等疫情过去之后至少一年去一个国家旅游,领略一下不同的风景。</p><h2 id="生活"><a href="#生活" class="headerlink" title="生活"></a>生活</h2><h4 id="记忆力"><a href="#记忆力" class="headerlink" title="记忆力"></a>记忆力</h4><p>发现自己近两年记忆力越来越差,不仅表现在学习记东西没有之前有效率,而且很多事情头天决定的事情第二天就完全没有印象了,因此我做了一些应对方案。</p><h5 id="笔记"><a href="#笔记" class="headerlink" title="笔记"></a>笔记</h5><p>我从笔记从原来的onenote迁移到了Notion,从一个youtuber(老石谈芯)那里了解到第二大脑的概念,把人不擅长长久保存在大脑的东西转到笔记里,在大脑里只有一个索引,把自己的大脑用来做思考、分析、做决策、学东西而不是记忆。</p><img src="https://image.xiaomo.info/blog/2022-01-30-103001.png" alt="image-20220130193001167" style="zoom:50%;"><h5 id="todo-list"><a href="#todo-list" class="headerlink" title="todo list"></a>todo list</h5><p>想到有什么事情需要做的时候第一件事情就是先掏出手机把记下来,然后把时间、地点、要做的事情给记下来。到点之前手机会自动给我推送消息提醒我,而不是完全靠自己的大脑去记。</p><img src="https://image.xiaomo.info/blog/2022-01-30-103243.png" alt="image-20220130193243018" style="zoom:50%;"><p>开始记账</p><p>不管是在上学的时候还是开始工作之后,对于每个月要用多少钱是一点规划都没有的。想买什么一冲动就买了,月底的时候一看账单一大堆,总怀疑哪里有错,为什么会花这么多。曾经有短暂的记账,但是没坚持多久就放弃了。2021年一整年坚持下来,己经养成了记账的好习惯。个人认为记账的目的不是为了省钱,而是当看到账单时经过自己手的每一笔账,哪些是真正的对生活的提高起到了作用,哪些是冲动消费,买了从来基本上不用的东西,考虑一下以后是不是可以把把这些东西的钱用在别的地方能够让自己的生活过的更舒服。</p><h5 id="学习"><a href="#学习" class="headerlink" title="学习"></a>学习</h5><p>从出生到现在,我们每个人都对学习这件事情上从没有停止过,甚至学习着如何学习,学习是推动我们生活的动力。提升自我能够得到更多的报酬,可以让自己的生活变好,生活变好之后就更有动力去学习继续提升自我。如果每天只是重复的、机械性的工作,下班了就刷短视频、葛优躺追剧,那么年复一年除了年龄增长、身体越来越差之外基本上得不到别的收获。我之前几年学技术一直是自己埋头苦学,今年偶然间认识了一些网络上的朋友,每天早上一起学习Terrafrom(己经拿到证书),然后也发起组建了AWS学习会晚上在zoom上一直学习。感觉这种方式非常的不错,毕竟程序员的圈子特别小平时工作和别人沟通也不多,能够通过这种方式认识一些朋友感觉是一件非常值得高兴的事情。而且大家住在不同的国家,接触着不同的东西,能够互相学习,互相鼓励,打算把这种模式一直持续下去。</p><h5 id="当UP主"><a href="#当UP主" class="headerlink" title="当UP主"></a>当UP主</h5><p>这几年在学习的过程中把自己需要学习的视频搬运到B站上己经积攒了近2万的关注,但是没有太大成就感 。于是开始自己录制一些视频,目前开了三个系列,后面还打算有一个<code>莫读书系列</code>,分享看过的一些比较好的书。</p><ol><li><a href="https://www.bilibili.com/video/BV1nb4y1q7r3/" target="_blank" rel="noopener">莫学习</a>:AWS学习小组直播录屏</li><li><a href="https://www.bilibili.com/video/BV1fS4y1D77f/" target="_blank" rel="noopener">莫分享</a>:分享一些工作生活中接触到的觉得非常好的东西</li><li><a href="https://www.bilibili.com/video/BV15g411A7qJ/" target="_blank" rel="noopener">莫生活</a>:在家写代码、整理笔记或者一些碎碎念,记录日常生活的一些内容</li></ol><p>录这些视频让我认识到我是非常喜欢分享自己知道的内容的,所以后面还是会坚持做这件事情。后面可能会花时间专门学一学<code>final cut pro</code>,对录制的内容做一些剪辑给观看者一个更好的体验。后面也计划出一些免费的技术类教程,给想要学习相关技术的朋友一些帮助,私心是教别人的时候自己能够对这些内容的理解更加深刻。</p><h2 id="心态"><a href="#心态" class="headerlink" title="心态"></a>心态</h2><h5 id="越学越菜"><a href="#越学越菜" class="headerlink" title="越学越菜"></a>越学越菜</h5><p>倒不是说自己的能力退步了,而是觉得不知者无畏,刚毕业的时候学会了什么东西之后就感觉自己非常的厉害,走路都能走出六亲不认的步伐。现在东西懂的越来越多,反而觉得自己和技术好的人差距很大,需要走的路还很远。</p><h5 id="开始变的佛系"><a href="#开始变的佛系" class="headerlink" title="开始变的佛系"></a>开始变的佛系</h5><p>不太爱去争强好胜,看明白了有很多时间并不是争赢了就是胜利,找到一个大家都舒服的点很重要。</p><h5 id="要稍微慢一点"><a href="#要稍微慢一点" class="headerlink" title="要稍微慢一点"></a>要稍微慢一点</h5><p>现在这个时代什么都图快,快餐文化、短视频成为了目前的主流,连电影都只能看解说版,以免浪费时间。但是有很多事情快了味道就变了,只有慢慢的沉浸下去才能够体会到其中的精髓。看视频、听语音确实比看文字的速度更快一些,但是我一直有一个观点,书籍从写到校正再到发布到广为推荐,那么这么书的价值是显而易见的。对于一些非常好的书,一定要花时间去看一看,光学技术是狭隘的。技术只是工具,用工具的是人,生活中要和别人接触,学一学为人处事之道,如何高效沟通,如何让自己的时间花的更有价值,如何平衡和工作和生活。不要焦虑,找好自己的节奏,慢慢的沉下心来去思考自己未来怎么走。</p><h2 id="反思"><a href="#反思" class="headerlink" title="反思"></a>反思</h2><p>不管生活和工作多忙,有没有时间,我总会想办法抽出一些时间写一个年末总结。从毕业开始我坚持了有6年时间了,我觉得写总结也是一个反思的过程,给过去一年画上一个句号开始一段新的旅程。想一想这一年自己有哪些进步,有哪些做的不好,接下来一年要怎么改进,新的一年又有一些什么样的计划,个人觉得花费这个时间是值得的。因为我们的每一步行动都是大脑去控制的,如果你有合理的规划,能够对事情有一个比较好的认识,那么我们的行为一定是趋向于好的方向。在公司组织的合宿分享会上,需要我们每个人回顾自己这一年有哪些变化,如果躲在舒适区不肯踏出来,那么整个人不是活了一年,而是把上一年的生活重复了一遍,我讨厌这种一成不变。</p><h2 id="内在"><a href="#内在" class="headerlink" title="内在"></a>内在</h2><p>俗话说内外兼修,技术好不好能力强不强我觉得这是外在。从毕业开始这么多年,我一直在外在上面下足了功夫,我一直觉得我不是聪明的那一类人,甚至还有点拖后腿。但是我要强,越是觉得我做不成,我越是要证明给他们看我可以。另外我觉得自己能够大致按时既定计划在执行,虽然也有些计划一直在拖延,但是大方向上行动力还是很强的,所以我不能说比别人优秀,但是同一个地方出生的同一届毕业的人中我绝对不算差的。这也归功于毕业后这些年我一直把时间花在技术提升上,虽然和现在的同事相比不算优秀,但也算是能跟上大家的脚步。今年也30了,虽然不能算30而立但是需要有一些变化。不能埋头于学习技术,也要学着去提高一下自己的内在涵养。如果做好自己,如何提高情商,如何换位思考,如何与不同的人打交道都是我需要去提升的地方,因此我买了很多跟说话、情商、心理学相关的书籍,多去看看这些书让自己的内在也变的扎实起来。</p><p><img src="https://image.xiaomo.info/blog/2022-01-30-130237.jpg" alt="1341643533603_.pic"></p>]]></content>
<summary type="html"><p>不管愿不愿意,2022年还是如期而至。对于我来说今年是个特殊的一年,从之前的奔三到了实实在在的到了30岁,心态还是有很多变化的。年底工作上非常的忙,导致2022年都过了半个月还没有把总结写法,迟到了一个月的年终总结终于出炉了,不过截至到今天完全写完正好是农历除夕,也不算迟到吧。</p></summary>
<category term="Japan" scheme="https://blog.xiaomo.info/categories/Japan/"/>
<category term="summary" scheme="https://blog.xiaomo.info/tags/summary/"/>
</entry>
<entry>
<title>从0搭建一个react开发的脚手架</title>
<link href="https://blog.xiaomo.info/2021/reactProject/"/>
<id>https://blog.xiaomo.info/2021/reactProject/</id>
<published>2021-09-07T00:00:00.000Z</published>
<updated>2022-08-10T23:53:17.655Z</updated>
<content type="html"><![CDATA[<p>最近这一年一直在用vue写前端,虽然一直很想用react,但是因为业务所限不可能花大量的时候去用react重写,只有在新项目的时候才有可能重新做技术选项。公司之前的技术类型比较多,最近开始统一成前端react,后端go/node,这样互相交流起来比较容易。</p><a id="more"></a><p>为了快捷的开发新项目,我维护了一套<a href="https://github.com/reactZone/vite-react-ts-template" target="_blank" rel="noopener">react脚手架模板</a>,开新项目的时候可以快速的以此模板开始业务逻辑,它的特性有:</p><ul><li>vite作为构建工具,复杂度比webpack低,但速度却比webpack快很多</li><li>支持hmr,修改代码页面无缝更新</li><li>以typescript为开发语言,有类型约束不会写出难以查找的bug</li><li>支持别名@到根目录,@@到components目录</li><li>支持国际化,默认写了中文、英文、日语3种语言</li><li>配置了eslint,使用<code>yarn fix</code>可以自动修复代码格式问题</li><li>采用了tailwind作为开发的css样式框架,外加tailwind-classnames约束样式名字。</li><li>配置了多环境,默认为local、stg、prod3个环境,如果有特殊需求可根据需要扩展</li><li>状态管理采用redux-tool-kit,没有繁琐的redux流程(action、reducer等),一个slice文件就是一个业务模块。</li><li>带一个todoList的demo,列举了react-router-dom相关用法</li></ul><p>目录结构</p><figure class="highlight routeros"><table><tr><td class="code"><pre><span class="line"></span><br><span class="line">├── envs 多环境配置文件</span><br><span class="line">└── src 工程核心代码</span><br><span class="line"> ├── assets 资源文件</span><br><span class="line"> │ ├── i18n 国际化翻译文件</span><br><span class="line"> │ ├── images 图片资源</span><br><span class="line"> │ └── styles 全局样式</span><br><span class="line"> ├── components 组件</span><br><span class="line"> │ ├── CHeader 头部</span><br><span class="line"> │ └── Common 通用组件</span><br><span class="line"> │ ├── CButton</span><br><span class="line"> │ ├── CContainer</span><br><span class="line"> │ ├── CFullLoading</span><br><span class="line"> │ ├── CInput</span><br><span class="line"> │ ├── CLink</span><br><span class="line"> │ ├── CNavLink</span><br><span class="line"> │ └── CPartialLoading</span><br><span class="line"> ├── i18n 国际化代码实现</span><br><span class="line"> ├── redux 状态管理(slice和store)</span><br><span class="line"> ├──<span class="built_in"> service </span> 业务服务(http交互)</span><br><span class="line"> ├── utils 通用方法</span><br><span class="line"> └── views 页面</span><br><span class="line"> ├── <span class="builtin-name">Add</span> 添加todo</span><br><span class="line"> ├── Home todoList</span><br><span class="line"> ├── Login 登陆页面</span><br><span class="line"> ├── NotFound 404</span><br><span class="line"> └── Task taskList</span><br><span class="line">├── tsconfig.json ts配置</span><br><span class="line">├── vite.config.ts vite配置</span><br><span class="line">├── .eslintrc.js eslint配置</span><br><span class="line">├── README.md 项目说明文件</span><br><span class="line">├── index.html 项目入口文件</span><br><span class="line">├── node_modules 项目依赖包</span><br><span class="line">├── package.json 项目依赖声明文件</span><br><span class="line">├── .env 本地环境(如果没有的话会使用envs/.env.local)</span><br><span class="line">└── yarn.lock yarn文件锁</span><br></pre></td></tr></table></figure><h2 id="依赖库介绍"><a href="#依赖库介绍" class="headerlink" title="依赖库介绍"></a>依赖库介绍</h2><h4 id="核心框架"><a href="#核心框架" class="headerlink" title="核心框架"></a>核心框架</h4><figure class="highlight 1c"><table><tr><td class="code"><pre><span class="line"><span class="string">"react"</span>: <span class="string">"^17.0.2"</span>,</span><br><span class="line"><span class="string">"react-dom"</span>: <span class="string">"^17.0.2"</span>,</span><br><span class="line"><span class="string">"react-router-dom"</span>: <span class="string">"^5.2.0"</span>,</span><br></pre></td></tr></table></figure><h4 id="国际化"><a href="#国际化" class="headerlink" title="国际化"></a>国际化</h4><figure class="highlight 1c"><table><tr><td class="code"><pre><span class="line"><span class="string">"react-i18next"</span>: <span class="string">"^11.11.4"</span>,</span><br><span class="line"><span class="string">"i18next"</span>: <span class="string">"^20.4.0"</span>,</span><br><span class="line"><span class="string">"i18next-browser-languagedetector"</span>: <span class="string">"^6.1.2"</span>,</span><br></pre></td></tr></table></figure><h4 id="UI相关"><a href="#UI相关" class="headerlink" title="UI相关"></a>UI相关</h4><figure class="highlight perl"><table><tr><td class="code"><pre><span class="line"><span class="string">"react-loading"</span>: <span class="string">"^2.0.3"</span>,</span><br><span class="line"><span class="string">"@fortawesome/fontawesome-svg-core"</span>: <span class="string">"^1.2.36"</span>,</span><br><span class="line"><span class="string">"@fortawesome/free-solid-svg-icons"</span>: <span class="string">"^5.15.4"</span>,</span><br><span class="line"><span class="string">"@fortawesome/react-fontawesome"</span>: <span class="string">"^0.1.15"</span>,</span><br></pre></td></tr></table></figure><h4 id="环境相关"><a href="#环境相关" class="headerlink" title="环境相关"></a>环境相关</h4><figure class="highlight perl"><table><tr><td class="code"><pre><span class="line"><span class="string">"vite"</span>: <span class="string">"^2.5.0"</span>,</span><br><span class="line"><span class="string">"@vitejs/plugin-react-refresh"</span>: <span class="string">"^1.3.6"</span>,</span><br><span class="line"><span class="string">"typescript"</span>: <span class="string">"^4.3.5"</span>,</span><br><span class="line"><span class="string">"cross-env"</span>: <span class="string">"^7.0.3"</span>,</span><br><span class="line"><span class="string">"@types/node"</span>: <span class="string">"^16.7.12"</span>,</span><br><span class="line"><span class="string">"@types/react"</span>: <span class="string">"^17.0.18"</span>,</span><br><span class="line"><span class="string">"@types/react-dom"</span>: <span class="string">"^17.0.9"</span>,</span><br><span class="line"><span class="string">"@typescript-eslint/eslint-plugin"</span>: <span class="string">"^4.29.2"</span>,</span><br><span class="line"><span class="string">"@typescript-eslint/parser"</span>: <span class="string">"^4.29.2"</span>,</span><br><span class="line"><span class="string">"@types/react-router-dom"</span>: <span class="string">"^5.1.8"</span>,</span><br></pre></td></tr></table></figure><h4 id="状态管理"><a href="#状态管理" class="headerlink" title="状态管理"></a>状态管理</h4><figure class="highlight perl"><table><tr><td class="code"><pre><span class="line"><span class="string">"@reduxjs/toolkit"</span>: <span class="string">"^1.6.1"</span>,</span><br><span class="line"><span class="string">"redux"</span>: <span class="string">"^4.1.1"</span>,</span><br><span class="line"><span class="string">"react-redux"</span>: <span class="string">"^7.2.4"</span>,</span><br><span class="line"><span class="string">"redux-thunk"</span>: <span class="string">"^2.3.0"</span>,</span><br><span class="line"><span class="string">"redux-devtools-extension"</span>: <span class="string">"^2.13.9"</span></span><br></pre></td></tr></table></figure><h4 id="css框架"><a href="#css框架" class="headerlink" title="css框架"></a>css框架</h4><figure class="highlight 1c"><table><tr><td class="code"><pre><span class="line"><span class="string">"tailwindcss-classnames"</span>: <span class="string">"^2.2.3"</span>,</span><br><span class="line"><span class="string">"autoprefixer"</span>: <span class="string">"^10.3.3"</span>,</span><br><span class="line"><span class="string">"postcss"</span>: <span class="string">"^8.3.6"</span>,</span><br><span class="line"><span class="string">"tailwindcss"</span>: <span class="string">"^2.2.9"</span>,</span><br><span class="line"><span class="string">"variables"</span>: <span class="string">"^1.0.1"</span></span><br></pre></td></tr></table></figure><h4 id="eslint相关"><a href="#eslint相关" class="headerlink" title="eslint相关"></a>eslint相关</h4><figure class="highlight 1c"><table><tr><td class="code"><pre><span class="line"><span class="string">"eslint-config-airbnb"</span>: <span class="string">"^18.2.1"</span>,</span><br><span class="line"><span class="string">"eslint-config-prettier"</span>: <span class="string">"^8.3.0"</span>,</span><br><span class="line"><span class="string">"eslint-plugin-import"</span>: <span class="string">"^2.24.0"</span>,</span><br><span class="line"><span class="string">"eslint-plugin-jsx-a11y"</span>: <span class="string">"^6.4.1"</span>,</span><br><span class="line"><span class="string">"eslint-plugin-react"</span>: <span class="string">"^7.24.0"</span>,</span><br><span class="line"><span class="string">"eslint-plugin-prettier"</span>: <span class="string">"^3.4.0"</span>,</span><br><span class="line"><span class="string">"eslint-plugin-react-hooks"</span>: <span class="string">"^4.2.0"</span>,</span><br><span class="line"><span class="string">"prettier"</span>: <span class="string">"^2.3.2"</span>,</span><br></pre></td></tr></table></figure><h4 id="网络相关"><a href="#网络相关" class="headerlink" title="网络相关"></a>网络相关</h4><figure class="highlight 1c"><table><tr><td class="code"><pre><span class="line"><span class="string">"redaxios"</span>: <span class="string">"^0.4.1"</span>,</span><br><span class="line"><span class="string">"qs"</span>: <span class="string">"^6.10.1"</span>,</span><br></pre></td></tr></table></figure><h2 id="scripts"><a href="#scripts" class="headerlink" title="scripts"></a>scripts</h2><figure class="highlight 1c"><table><tr><td class="code"><pre><span class="line"><span class="string">"dev"</span>: <span class="string">"cross-env NODE_ENV=local vite --host"</span>, </span><br><span class="line"><span class="string">"dev:stg"</span>: <span class="string">"cross NODE_ENV=stg vite"</span>,</span><br><span class="line"><span class="string">"build"</span>: <span class="string">"vite build"</span>,</span><br><span class="line"><span class="string">"build:stg"</span>: <span class="string">"cross-env vite build --mode test"</span>,</span><br><span class="line"><span class="string">"build:prod"</span>: <span class="string">"cross-env vite build --mode production"</span>,</span><br><span class="line"><span class="string">"serve"</span>: <span class="string">"vite preview"</span>,</span><br><span class="line"><span class="string">"lint"</span>: <span class="string">"eslint --ext .jsx,.js,.ts,.tsx ."</span></span><br></pre></td></tr></table></figure><h2 id="完整package-json"><a href="#完整package-json" class="headerlink" title="完整package.json"></a>完整package.json</h2><figure class="highlight json"><table><tr><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"name"</span>: <span class="string">"vite-react-teamplate"</span>,</span><br><span class="line"> <span class="attr">"version"</span>: <span class="string">"0.0.1"</span>,</span><br><span class="line"> <span class="attr">"scripts"</span>: {</span><br><span class="line"> <span class="attr">"dev"</span>: <span class="string">"cross-env NODE_ENV=local vite --host"</span>,</span><br><span class="line"> <span class="attr">"dev:stg"</span>: <span class="string">"cross NODE_ENV=stg vite"</span>,</span><br><span class="line"> <span class="attr">"build"</span>: <span class="string">"vite build"</span>,</span><br><span class="line"> <span class="attr">"build:stg"</span>: <span class="string">"cross-env vite build --mode test"</span>,</span><br><span class="line"> <span class="attr">"build:prod"</span>: <span class="string">"cross-env vite build --mode production"</span>,</span><br><span class="line"> <span class="attr">"serve"</span>: <span class="string">"vite preview"</span>,</span><br><span class="line"> <span class="attr">"lint"</span>: <span class="string">"eslint --ext .jsx,.js,.ts,.tsx ."</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"dependencies"</span>: {</span><br><span class="line"> <span class="attr">"react"</span>: <span class="string">"^17.0.2"</span>,</span><br><span class="line"> <span class="attr">"react-dom"</span>: <span class="string">"^17.0.2"</span>,</span><br><span class="line"> <span class="attr">"react-router-dom"</span>: <span class="string">"^5.2.0"</span>,</span><br><span class="line"> <span class="attr">"react-i18next"</span>: <span class="string">"^11.11.4"</span>,</span><br><span class="line"> <span class="attr">"i18next"</span>: <span class="string">"^20.4.0"</span>,</span><br><span class="line"> <span class="attr">"i18next-browser-languagedetector"</span>: <span class="string">"^6.1.2"</span>,</span><br><span class="line"> <span class="attr">"react-loading"</span>: <span class="string">"^2.0.3"</span>,</span><br><span class="line"> <span class="attr">"@fortawesome/fontawesome-svg-core"</span>: <span class="string">"^1.2.36"</span>,</span><br><span class="line"> <span class="attr">"@fortawesome/free-solid-svg-icons"</span>: <span class="string">"^5.15.4"</span>,</span><br><span class="line"> <span class="attr">"@fortawesome/react-fontawesome"</span>: <span class="string">"^0.1.15"</span>,</span><br><span class="line"> <span class="attr">"redaxios"</span>: <span class="string">"^0.4.1"</span>,</span><br><span class="line"> <span class="attr">"qs"</span>: <span class="string">"^6.10.1"</span>,</span><br><span class="line"> <span class="attr">"@reduxjs/toolkit"</span>: <span class="string">"^1.6.1"</span>,</span><br><span class="line"> <span class="attr">"redux"</span>: <span class="string">"^4.1.1"</span>,</span><br><span class="line"> <span class="attr">"react-redux"</span>: <span class="string">"^7.2.4"</span>,</span><br><span class="line"> <span class="attr">"redux-thunk"</span>: <span class="string">"^2.3.0"</span>,</span><br><span class="line"> <span class="attr">"tailwindcss"</span>: <span class="string">"^2.2.9"</span>,</span><br><span class="line"> <span class="attr">"variables"</span>: <span class="string">"^1.0.1"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"devDependencies"</span>: {</span><br><span class="line"> <span class="attr">"vite"</span>: <span class="string">"^2.5.0"</span>,</span><br><span class="line"> <span class="attr">"@vitejs/plugin-react-refresh"</span>: <span class="string">"^1.3.6"</span>,</span><br><span class="line"> <span class="attr">"typescript"</span>: <span class="string">"^4.3.5"</span>,</span><br><span class="line"> <span class="attr">"cross-env"</span>: <span class="string">"^7.0.3"</span>,</span><br><span class="line"> <span class="attr">"@types/node"</span>: <span class="string">"^16.7.12"</span>,</span><br><span class="line"> <span class="attr">"@types/react"</span>: <span class="string">"^17.0.18"</span>,</span><br><span class="line"> <span class="attr">"@types/react-dom"</span>: <span class="string">"^17.0.9"</span>,</span><br><span class="line"> <span class="attr">"@typescript-eslint/eslint-plugin"</span>: <span class="string">"^4.29.2"</span>,</span><br><span class="line"> <span class="attr">"@typescript-eslint/parser"</span>: <span class="string">"^4.29.2"</span>,</span><br><span class="line"> <span class="attr">"@types/react-router-dom"</span>: <span class="string">"^5.1.8"</span>,</span><br><span class="line"> <span class="attr">"redux-devtools-extension"</span>: <span class="string">"^2.13.9"</span>,</span><br><span class="line"> <span class="attr">"tailwindcss-classnames"</span>: <span class="string">"^2.2.3"</span>,</span><br><span class="line"> <span class="attr">"autoprefixer"</span>: <span class="string">"^10.3.3"</span>,</span><br><span class="line"> <span class="attr">"postcss"</span>: <span class="string">"^8.3.6"</span>,</span><br><span class="line"> <span class="attr">"prettier"</span>: <span class="string">"^2.3.2"</span>,</span><br><span class="line"> <span class="attr">"dotenv"</span>: <span class="string">"^10.0.0"</span>,</span><br><span class="line"> <span class="attr">"eslint"</span>: <span class="string">"^7.32.0"</span>,</span><br><span class="line"> <span class="attr">"eslint-config-airbnb"</span>: <span class="string">"^18.2.1"</span>,</span><br><span class="line"> <span class="attr">"eslint-config-prettier"</span>: <span class="string">"^8.3.0"</span>,</span><br><span class="line"> <span class="attr">"eslint-plugin-import"</span>: <span class="string">"^2.24.0"</span>,</span><br><span class="line"> <span class="attr">"eslint-plugin-jsx-a11y"</span>: <span class="string">"^6.4.1"</span>,</span><br><span class="line"> <span class="attr">"eslint-plugin-react"</span>: <span class="string">"^7.24.0"</span>,</span><br><span class="line"> <span class="attr">"eslint-plugin-prettier"</span>: <span class="string">"^3.4.0"</span>,</span><br><span class="line"> <span class="attr">"eslint-plugin-react-hooks"</span>: <span class="string">"^4.2.0"</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="多环境"><a href="#多环境" class="headerlink" title="多环境"></a>多环境</h2><p><img src="https://image.xiaomo.info/blog/image-20210907151025364.png" alt="image-20210907151025364"></p><figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="attr">NODE_ENV</span> = local</span><br><span class="line"><span class="attr">VITE_BUILD_ENV</span> = development</span><br><span class="line"><span class="attr">VITE_HOST</span> = portal.local.xiaomo.info</span><br><span class="line"><span class="attr">VITE_PORT</span> = <span class="number">10086</span></span><br><span class="line"><span class="attr">VITE_BASE_URL</span> = ./</span><br><span class="line"><span class="attr">VITE_OUTPUT_DIR</span> = dist</span><br><span class="line"><span class="attr">VITE_APP_BASE_API</span> = https://mock.local.xiaomo.info</span><br></pre></td></tr></table></figure><h2 id="vite配置"><a href="#vite配置" class="headerlink" title="vite配置"></a>vite配置</h2><figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> { defineConfig</span><br><span class="line">} <span class="keyword">from</span> <span class="string">'vite'</span>;</span><br><span class="line"><span class="keyword">import</span> reactRefresh <span class="keyword">from</span> <span class="string">'@vitejs/plugin-react-refresh'</span>;</span><br><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> path <span class="keyword">from</span> <span class="string">'path'</span>;</span><br><span class="line"><span class="keyword">import</span> { existsSync } <span class="keyword">from</span> <span class="string">'fs'</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Dotenv 是一个零依赖的模块,它能将环境变量中的变量从 .env 文件加载到 process.env 中</span></span><br><span class="line"><span class="keyword">const</span> dotenv = <span class="built_in">require</span>(<span class="string">'dotenv'</span>);</span><br><span class="line">dotenv.config({</span><br><span class="line"> path: existsSync(<span class="string">'.env'</span>) ?</span><br><span class="line"> <span class="string">'.env'</span> : path.resolve(<span class="string">'envs'</span>, <span class="string">`.env.<span class="subst">${process.env.NODE_ENV}</span>`</span>)</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="comment">// https://vitejs.dev/config/</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> defineConfig({</span><br><span class="line"> plugins: [reactRefresh()],</span><br><span class="line"> resolve: {</span><br><span class="line"> alias: {</span><br><span class="line"> <span class="string">'@@'</span>: path.resolve(__dirname),</span><br><span class="line"> <span class="string">'@'</span>: path.resolve(__dirname, <span class="string">'src'</span>),</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> server: {</span><br><span class="line"> cors: <span class="literal">true</span>,</span><br><span class="line"> port: process.env.VITE_PORT <span class="keyword">as</span> unknown <span class="keyword">as</span> <span class="built_in">number</span>,</span><br><span class="line"> hmr: {</span><br><span class="line"> host: <span class="string">'localhost'</span>,</span><br><span class="line"> protocol: <span class="string">'ws'</span>,</span><br><span class="line"> port: process.env.VITE_PORT <span class="keyword">as</span> unknown <span class="keyword">as</span> <span class="built_in">number</span>,</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">});</span><br></pre></td></tr></table></figure><h2 id="状态管理-1"><a href="#状态管理-1" class="headerlink" title="状态管理"></a>状态管理</h2><p><img src="https://image.xiaomo.info/blog/image-20210907151509301.png" alt="image-20210907151509301"></p><h4 id="slice"><a href="#slice" class="headerlink" title="slice"></a>slice</h4><figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> { createAsyncThunk, createSlice } <span class="keyword">from</span> <span class="string">'@reduxjs/toolkit'</span>;</span><br><span class="line"><span class="keyword">import</span> fileService <span class="keyword">from</span> <span class="string">'@/service/fileService'</span>;</span><br><span class="line"><span class="keyword">import</span> { File } <span class="keyword">from</span> <span class="string">'@/models/File'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> getFiles = createAsyncThunk(</span><br><span class="line"> <span class="string">'files/getFiles'</span>,</span><br><span class="line"> <span class="keyword">async</span> () => {</span><br><span class="line"> <span class="keyword">const</span> response = <span class="keyword">await</span> fileService.getFiles();</span><br><span class="line"> <span class="keyword">const</span> result = response.data;</span><br><span class="line"> <span class="keyword">return</span> result.data <span class="keyword">as</span> File[];</span><br><span class="line"> }</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> FileState {</span><br><span class="line"> loading: <span class="built_in">boolean</span>,</span><br><span class="line"> files: File[]</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> initialState: FileState = {</span><br><span class="line"> loading: <span class="literal">false</span>,</span><br><span class="line"> files: []</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> fileSlice = createSlice({</span><br><span class="line"> name: <span class="string">'files'</span>,</span><br><span class="line"> initialState: initialState,</span><br><span class="line"> reducers: {},</span><br><span class="line"> extraReducers: <span class="function">(<span class="params">builder</span>) =></span> {</span><br><span class="line"> builder.addCase(getFiles.fulfilled, <span class="function">(<span class="params">state: FileState, action</span>) =></span> {</span><br><span class="line"> state.loading = <span class="literal">false</span>;</span><br><span class="line"> state.files = action.payload;</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 class="keyword">export</span> <span class="keyword">default</span> fileSlice.reducer;</span><br></pre></td></tr></table></figure><h4 id="store"><a href="#store" class="headerlink" title="store"></a>store</h4><figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> { configureStore } <span class="keyword">from</span> <span class="string">'@reduxjs/toolkit'</span>;</span><br><span class="line"><span class="keyword">import</span> fileReducer <span class="keyword">from</span> <span class="string">'@/redux/fileSlice'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> store = configureStore({</span><br><span class="line"> reducer: {</span><br><span class="line"> files: fileReducer,</span><br><span class="line"> }</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">type</span> RootState = ReturnType<<span class="keyword">typeof</span> store.getState></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> store;</span><br></pre></td></tr></table></figure><h4 id="使用store"><a href="#使用store" class="headerlink" title="使用store"></a>使用store</h4><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">ReactDOM.render(</span><br><span class="line"> <React.StrictMode></span><br><span class="line"> <BrowserRouter></span><br><span class="line"> <Provider store={store}></span><br><span class="line"> <App/></span><br><span class="line"> </Provider></span><br><span class="line"> </BrowserRouter></span><br><span class="line"> </React.StrictMode>,</span><br><span class="line"> document.getElementById('root')</span><br><span class="line">);</span><br></pre></td></tr></table></figure><h4 id="fileService"><a href="#fileService" class="headerlink" title="fileService"></a>fileService</h4><figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> { Response } <span class="keyword">from</span> <span class="string">'redaxios'</span>;</span><br><span class="line"><span class="keyword">import</span> api <span class="keyword">from</span> <span class="string">'@/utils/api'</span>;</span><br><span class="line"><span class="keyword">import</span> { Result } <span class="keyword">from</span> <span class="string">'@/models/Result'</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> fileService {</span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">async</span> getFiles(): <span class="built_in">Promise</span><Response<Result>> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">await</span> api.get(<span class="string">'/api/v1/files'</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>UI中获取数据</p><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">export const CtwFiles = (): JSX.Element => {</span><br><span class="line"> const dispatch = useDispatch();</span><br><span class="line"> useEffect(() => {</span><br><span class="line"> dispatch(getFiles());</span><br><span class="line"> }, [dispatch]);</span><br><span class="line"></span><br><span class="line"> const state = useSelector((state: RootState) => {</span><br><span class="line"> return state.files;</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> return <CContainer</span><br><span class="line"> loading={state.loading}</span><br><span class="line"> classes={classnames('h-80')}</span><br><span class="line"> component={<FileList files={state.files}/>}</span><br><span class="line"> />;</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>详细代码请查看工程 <a href="https://github.com/reactZone/vite-react-ts-template" target="_blank" rel="noopener">https://github.com/reactZone/vite-react-ts-template</a></p><h2 id="国际化-1"><a href="#国际化-1" class="headerlink" title="国际化"></a>国际化</h2><h2 id="在assets-i18n下新建翻译文件"><a href="#在assets-i18n下新建翻译文件" class="headerlink" title="在assets/i18n下新建翻译文件"></a>在<code>assets/i18n</code>下新建翻译文件</h2><p><img src="https://image.xiaomo.info/blog/image-20210907152148011.png" alt="image-20210907152148011"></p><p>ja-JP.json</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"home"</span>: <span class="string">"ホーム"</span>,</span><br><span class="line"> <span class="attr">"welcome"</span>: <span class="string">"ホームへようこそ!"</span>,</span><br><span class="line"> <span class="attr">"Login"</span>: <span class="string">"ログイン"</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="国际化核心实现"><a href="#国际化核心实现" class="headerlink" title="国际化核心实现"></a>国际化核心实现</h4><figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> LanguageDetector <span class="keyword">from</span> <span class="string">'i18next-browser-languagedetector'</span>;</span><br><span class="line"><span class="keyword">import</span> i18n <span class="keyword">from</span> <span class="string">'i18next'</span>;</span><br><span class="line"><span class="keyword">import</span> { initReactI18next } <span class="keyword">from</span> <span class="string">'react-i18next'</span>;</span><br><span class="line"><span class="keyword">import</span> enUsTrans <span class="keyword">from</span> <span class="string">'@/assets/i18n/en-us.json'</span>;</span><br><span class="line"><span class="keyword">import</span> zhCnTrans <span class="keyword">from</span> <span class="string">'@/assets/i18n/zh-cn.json'</span>;</span><br><span class="line"><span class="keyword">import</span> jaJpTrans <span class="keyword">from</span> <span class="string">'@/assets/i18n/ja-jp.json'</span>;</span><br><span class="line"></span><br><span class="line">i18n</span><br><span class="line"> .use(LanguageDetector) <span class="comment">// 嗅探当前浏览器语言</span></span><br><span class="line"> .use(initReactI18next) <span class="comment">// init i18next</span></span><br><span class="line"> .init({</span><br><span class="line"> <span class="comment">// 引入资源文件</span></span><br><span class="line"> resources: {</span><br><span class="line"> en: {</span><br><span class="line"> translation: enUsTrans,</span><br><span class="line"> },</span><br><span class="line"> zh: {</span><br><span class="line"> translation: zhCnTrans,</span><br><span class="line"> },</span><br><span class="line"> ja: {</span><br><span class="line"> translation: jaJpTrans,</span><br><span class="line"> },</span><br><span class="line"> },</span><br><span class="line"> <span class="comment">// 选择默认语言,选择内容为上述配置中的key,即en/zh/ja</span></span><br><span class="line"> fallbackLng: <span class="string">'ja'</span>,</span><br><span class="line"> debug: <span class="literal">false</span>,</span><br><span class="line"> interpolation: {</span><br><span class="line"> escapeValue: <span class="literal">false</span>, <span class="comment">// not needed for react as it escapes by default</span></span><br><span class="line"> },</span><br><span class="line"> })</span><br><span class="line"> .then(<span class="function">(<span class="params">r</span>) =></span> <span class="built_in">console</span>.log(r));</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> i18n;</span><br></pre></td></tr></table></figure><h4 id="在入口文件加载i18n"><a href="#在入口文件加载i18n" class="headerlink" title="在入口文件加载i18n"></a>在入口文件加载i18n</h4><p><img src="https://image.xiaomo.info/blog/image-20210907152327173.png" alt="image-20210907152327173"></p><h2 id="路由"><a href="#路由" class="headerlink" title="路由"></a>路由</h2><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">const Home = lazy(() => import('@/views/Home'));</span><br><span class="line">const Task = lazy(() => import('@/views/Task'));</span><br><span class="line">const Add = lazy(() => import('@/views/Add'));</span><br><span class="line">const Login = lazy(() => import('@/views/Login'));</span><br><span class="line"></span><br><span class="line">const App = (): JSX.Element => {</span><br><span class="line"> return (</span><br><span class="line"> <Suspense fallback={<CFullLoading/>}></span><br><span class="line"> <Switch></span><br><span class="line"> <Route path="/" component={Home} exact/></span><br><span class="line"> <Route path="/home" component={Home}/></span><br><span class="line"> <Route path="/task" component={Task}/></span><br><span class="line"> <Route path="/add" component={Add}/></span><br><span class="line"> <Route path="/login" component={Login}/></span><br><span class="line"> <Route path="/404" component={NotFound}/></span><br><span class="line"> <Redirect to="/404"/></span><br><span class="line"> </Switch></span><br><span class="line"> </Suspense></span><br><span class="line"> );</span><br><span class="line">};</span><br></pre></td></tr></table></figure><h2 id="如何使用"><a href="#如何使用" class="headerlink" title="如何使用"></a>如何使用</h2><p><a href="https://github.com/reactZone/vite-react-ts-template/generate" target="_blank" rel="noopener">https://github.com/reactZone/vite-react-ts-template/generate</a> 点击此链接创建属于自己的项目(需要事先登陆github账号)</p><img src="https://image.xiaomo.info/blog/blogblogblogblogimage-20210907142954421.png" alt="image-20210907142954421" style="zoom: 50%;"><p>以此项目为模板创建一个新的项目,然后clone到本地进行开发</p>]]></content>
<summary type="html"><p>最近这一年一直在用vue写前端,虽然一直很想用react,但是因为业务所限不可能花大量的时候去用react重写,只有在新项目的时候才有可能重新做技术选项。公司之前的技术类型比较多,最近开始统一成前端react,后端go/node,这样互相交流起来比较容易。</p></summary>
<category term="web" scheme="https://blog.xiaomo.info/categories/web/"/>
<category term="react" scheme="https://blog.xiaomo.info/tags/react/"/>
</entry>
<entry>
<title>日本医疗制度之限度額適用認定証</title>
<link href="https://blog.xiaomo.info/2021/japanNyuyin2/"/>
<id>https://blog.xiaomo.info/2021/japanNyuyin2/</id>
<published>2021-06-28T00:00:00.000Z</published>
<updated>2022-08-10T23:53:17.655Z</updated>
<content type="html"><![CDATA[<p>在中国做手术,从选医院到找大夫,不但要托关系送礼,还要照顾病人,一场手术下来,病人和家人都身心疲惫。在中国做个手术,绝对是个大事。但在日本做手术,心里会很踏实,日本先进的医疗水平和完善的医保制度,会让人省心。在日本看病,首先是去家附近的私人诊所。医生会根据你的病情,向你说明医治方案和可选择的医院。选用哪个方案和哪家医院,要由你自己决定,医生只是给你建议,毕竟没有最好的,只有适合自己的。一旦你决定下来,医生就会介绍你到对口的综合大医院,有了他的“介绍信和诊断资料”,不但大大缩短待诊时间,而且省去了抽血、化验和拍片等二次诊断时间,只需说明自己希望的医治方案,直接就可以预约手术时间了。</p><a id="more"></a><h2 id="住院要办的证"><a href="#住院要办的证" class="headerlink" title="住院要办的证"></a>住院要办的证</h2><p><strong>在日本住院前,有一件事千万不要忘记,就是办理“限度額適用認定証”。</strong>参加健保的工薪族,向自己所在的健保组合申请,参加国保的自由人,向区市役所的“保险年金课”申请。申请手续也非常简单,填个申请表,就可以领到这个证了。比如我加入的是<a href="https://www.its-kenpo.or.jp/hoken/situation/case_07/kougaku/shinsei.html" target="_blank" rel="noopener">関東IT保険</a></p><p><img src="https://image.xiaomo.info//blog/825.png" alt="825"></p><p>为什么要办这个证呢?不管得了什么大病,单月超过自己医疗费上限额度的部分全部报销。但报销还是手续繁杂,而且还需要你垫付医疗费。假如事先办好“限度額適用認定証”,就可以省去申请报销的过程。出院时,只需支付自己额度内的医疗费即可,超出部分医院会代你申请报销,万一自己要负担的部分还是出不起可以申请分期,如果分期都出不起就说没钱最后这部分钱会由政府负担,但是对于个人的信用的话会有很大影响。不像中国住院前需要交住院费,不交费就不给安排病房,后续如果费用续不上还有可能会被赶出病房,生命得不到保障。</p><h3 id="个人负担额度"><a href="#个人负担额度" class="headerlink" title="个人负担额度"></a>个人负担额度</h3><p><strong>个人负担额度标准(69岁以下):</strong></p><img src="https://image.xiaomo.info//blog/image-20210629103914261.png" alt="image-20210629103914261" style="zoom:50%;"><p>如何知道自己的医疗负担上限额度呢?<strong>“限度額適用認定証”的“適用区分”栏,就是你的认定额度了。</strong>我的认定额度档次为“<strong>イ</strong>”。这次手术费估算为120万円左右,所以我的费用负担9万多点。<strong>而最低档“オ”只需负担35,400円!</strong></p><p>“负担额度”的认定月为每年的8月,根据上一年度的所得标准,来确定下个周期个人负担额度(本年8月-次年7月)。同理,8月之前的额度认可,以上一个周期为准,即上上年的所得标准认定。有点绕吧?还是举例说明:我的申请月在2021年7月,因此认定周期为上一个周期,即2021年7月~2022年6月,它对应的年收入为2020年1月-12月。那时是有工资收入的工薪族,所以被认定为中档的“ウ”!可见在日本“脱畜”后的一两年内,还是要面对各种税负的挑战!</p><h4 id="床位费和饭费"><a href="#床位费和饭费" class="headerlink" title="床位费和饭费"></a>床位费和饭费</h4><p><strong>在日本“住院”和国内很不同,它不准许护工和家属陪护!</strong><br>一日三餐等生活问题全部由医院负责,家属只准在规定时间段内探望,假如是行动不便的病人,甚至连洗脸、洗脚都会由护士代劳,这极大的减轻了病人家属的负担,日式服务不服不行呀。</p><p><strong>1)床位费</strong></p><p><strong>4人或6人的病房为普通病房,多人病房的床位费是免费的!</strong><br>但遇上床位紧张,不得不住单人或双人病房就有额外费用“差額ベッド代”产生了。床位费各家医院略有不同,平均每天约6000円。</p><p><strong>2)饭费</strong></p><p><strong>医院根据病情调整病人三餐的饮食机构,家属完全不用操心。</strong><br>之前饭费固定为每餐360円,2018年起调整为每餐460円。假如是低收入家庭“市民税非課税世帯”,或长期卧床的还有饭费的减免。</p><p><img src="https://image.xiaomo.info//blog/1735.png" alt="1735"></p><p><strong>申请住院饭费减免同样是在市役所的“保险年金课”,领到的是另外一个证“標準負担額減額認定証”。</strong><br>记得在申请“国保限度額適用認定証”的同时申请该证。</p><p><strong>低收入家庭<a href="https://www.city.fuchu.tokyo.jp/kurashi/hoken/kokuminkenko/kyufu/nyuinji.html" target="_blank" rel="noopener">住院饭费减免</a>:</strong></p><table><thead><tr><th><strong>对象人群</strong></th><th><strong>住院饭费(2018年)</strong></th></tr></thead><tbody><tr><td><strong>一般</strong></td><td><strong>460円/餐</strong></td></tr><tr><td><strong>市民税非課税世帯</strong></td><td><strong>210円/餐</strong></td></tr><tr><td><strong>市民税非課税世帯(一年内住院超过90日)</strong></td><td><strong>160円/餐</strong></td></tr><tr><td><strong>市民税非課税世帯(70~74岁)</strong></td><td><strong>100円/餐</strong></td></tr></tbody></table><h5 id="手术住院账单"><a href="#手术住院账单" class="headerlink" title="手术住院账单"></a>手术住院账单</h5><p><img src="https://image.xiaomo.info//blog/858.jpg" alt="858"></p><p><strong>1)</strong>本次住院10天,4人房,每天7700日元,如果是6-8人间的话是房间是免费的;<br><strong>2)</strong>总费用共计120多万,医保报销70%,自己负担费用36万;<br><strong>3)</strong>高额医疗费制度保障下,实际支付账单只有9万多!</p><p><strong>“限度額適用認定証”连复杂的报销手续都省了,不得不感叹日本高效完善的医疗保险体系!</strong>需要注意的是:<strong>“限度額認定証”是对“月度”医疗费的限定。</strong></p><p>假如这次住院“跨月”,那么报销方面就比较吃亏了。例如同样医疗费11万円,一旦跨月就会被分为两个部分。假设2月份医疗费是5万,3月是6万,这样的报销结果就是:两个月都没有超过“红线”,“限度額認定証”完全失去作用,11万円医疗费全部需要个人负担。可见,<strong>对于那些不太紧急的疾病,在同医生预约手术时间时,需要考虑“限度額認定証”的跨月问题,尽量避免月底住院。</strong></p><p>最后总结<br>1)利用高额医疗费制度,可报销个人每月负担额度之外全部的医疗费用。<br>2)最低“オ”档,个人每月负担只需35,400円!普通工薪的“ウ”档,也只有每月8万円。<br>3)事先申请“限度額適用認定証”,可以省去繁琐的医疗费报销手续。<br>4)“负担额度”的认定为每年的8月,认定周期为每年8月至次年7月。<br>5)医院的饭费、床位费是属于医保范围外的费用,需要自己负担。<br>6)注意报销的“跨月”问题,尽量避免月底住院。</p>]]></content>
<summary type="html"><p>在中国做手术,从选医院到找大夫,不但要托关系送礼,还要照顾病人,一场手术下来,病人和家人都身心疲惫。在中国做个手术,绝对是个大事。但在日本做手术,心里会很踏实,日本先进的医疗水平和完善的医保制度,会让人省心。在日本看病,首先是去家附近的私人诊所。医生会根据你的病情,向你说明医治方案和可选择的医院。选用哪个方案和哪家医院,要由你自己决定,医生只是给你建议,毕竟没有最好的,只有适合自己的。一旦你决定下来,医生就会介绍你到对口的综合大医院,有了他的“介绍信和诊断资料”,不但大大缩短待诊时间,而且省去了抽血、化验和拍片等二次诊断时间,只需说明自己希望的医治方案,直接就可以预约手术时间了。</p></summary>
<category term="生活" scheme="https://blog.xiaomo.info/categories/生活/"/>
<category term="japan" scheme="https://blog.xiaomo.info/tags/japan/"/>
</entry>
<entry>
<title>我的tmux之旅(tmux使用详解)</title>
<link href="https://blog.xiaomo.info/2021/ohmytmux/"/>
<id>https://blog.xiaomo.info/2021/ohmytmux/</id>
<published>2021-05-17T00:00:00.000Z</published>
<updated>2022-08-10T23:53:17.655Z</updated>
<content type="html"><![CDATA[<p>之前在中期学习计划中将tmux列入其中,但是一直没有花时间研究。周末看番的时候有一个<a href="https://www.bilibili.com/video/BV1Qq4y1f7N1?t=693" target="_blank" rel="noopener">tmux的视频推荐</a>,点开看了一下觉得讲的挺好的,感觉是时候在工作中导入这个工具了。视频也直接嵌在正文中,有需要的朋友不妨看一下。</p><a id="more"></a><iframe src="//player.bilibili.com/player.html?aid=588001797&bvid=BV1Qq4y1f7N1&cid=339146205&page=1" scrolling="no" border="0" height="768" width="1024" frameborder="no" framespacing="0" allowfullscreen="true"> </iframe><h1 id="开胃菜"><a href="#开胃菜" class="headerlink" title="开胃菜"></a>开胃菜</h1><p>视频开头的动画up主讲到这个软件名字叫<code>asciiquarium</code>, 可以通过<code>brew install asciiquarium</code>命令来安装。其实没什么太大作用,就是终端中的一个动画,当作终端的屏保也不错。</p><p><code>q</code> 退出动画</p><p><code>p</code> 暂停动画 </p><p><code>r</code> 重置动画</p><h1 id="tmux介绍"><a href="#tmux介绍" class="headerlink" title="tmux介绍"></a>tmux介绍</h1><p>Tmux是一个键盘驱动的终端分屏工具,可以替代Linux下的screen。当然,如果是在Mac下使用的话,它的核心功能(例如window,分屏等)也是可以被iTerm2替代的,不过好在Tmux在*nix操作系统中足够通用,用包管理工具(apt-get, brew等)即可安装,所以了解并熟练使用它,还是能为平时终端下的工作节省不少时间的。而且最重要的功能是它可以保存会话,避免每次开机都得重复性的去开各种窗口。</p><h1 id="session"><a href="#session" class="headerlink" title="session"></a>session</h1><p>会话,Tmux是一个C/S架构的工具,一个会话可以认为是C端和S端一次交互的上下文。我们的所有操作都属于某个session,session可以长时间存在,也可以临时退出再重进。我们可用通过session来区分不同的工作空间,比如本地操作开一个session,远程SSH操作开一个session,又或者SSH生产环境机器开一个session,SSH测试环境机器开一个session。</p><h2 id="新建会话"><a href="#新建会话" class="headerlink" title="新建会话"></a>新建会话</h2><p>第一个启动的 Tmux 窗口,编号是<code>0</code>,第二个窗口的编号是<code>1</code>,以此类推。这些窗口对应的会话,就是 0 号会话、1 号会话。</p><p>使用编号区分会话,不太直观,更好的方法是为会话起名。</p><blockquote> <figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">> $ tmux new -s <session-name></span><br><span class="line">></span><br></pre></td></tr></table></figure></blockquote><p>上面命令新建一个指定名称的会话。</p><h2 id="分离会话"><a href="#分离会话" class="headerlink" title="分离会话"></a>分离会话</h2><p>在 Tmux 窗口中,按下<code>Ctrl+b d</code>或者输入<code>tmux detach</code>命令,就会将当前会话与窗口分离。</p><blockquote> <figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">> $ tmux detach</span><br><span class="line">></span><br></pre></td></tr></table></figure></blockquote><p>上面命令执行后,就会退出当前 Tmux 窗口,但是会话和里面的进程仍然在后台运行。</p><p><code>tmux ls</code>命令可以查看当前所有的 Tmux 会话。</p><blockquote> <figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">> $ tmux ls</span><br><span class="line">> <span class="comment"># or</span></span><br><span class="line">> $ tmux list-session</span><br><span class="line">></span><br></pre></td></tr></table></figure></blockquote><h2 id="接入会话"><a href="#接入会话" class="headerlink" title="接入会话"></a>接入会话</h2><p><code>tmux attach</code>命令用于重新接入某个已存在的会话。</p><blockquote> <figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">> <span class="comment"># 使用会话编号</span></span><br><span class="line">> $ tmux attach -t 0</span><br><span class="line">> </span><br><span class="line">> <span class="comment"># 使用会话名称</span></span><br><span class="line">> $ tmux attach -t <session-name></span><br><span class="line">></span><br></pre></td></tr></table></figure></blockquote><h2 id="杀死会话"><a href="#杀死会话" class="headerlink" title="杀死会话"></a>杀死会话</h2><p><code>tmux kill-session</code>命令用于杀死某个会话。</p><blockquote> <figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">> <span class="comment"># 使用会话编号</span></span><br><span class="line">> $ tmux <span class="built_in">kill</span>-session -t 0</span><br><span class="line">> </span><br><span class="line">> <span class="comment"># 使用会话名称</span></span><br><span class="line">> $ tmux <span class="built_in">kill</span>-session -t <session-name></span><br><span class="line">></span><br></pre></td></tr></table></figure></blockquote><h2 id="切换会话"><a href="#切换会话" class="headerlink" title="切换会话"></a>切换会话</h2><p><code>tmux switch</code>命令用于切换会话。</p><blockquote> <figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">> <span class="comment"># 使用会话编号</span></span><br><span class="line">> $ tmux switch -t 0</span><br><span class="line">> </span><br><span class="line">> <span class="comment"># 使用会话名称</span></span><br><span class="line">> $ tmux switch -t <session-name></span><br><span class="line">></span><br></pre></td></tr></table></figure></blockquote><h2 id="重命名会话"><a href="#重命名会话" class="headerlink" title="重命名会话"></a>重命名会话</h2><p><code>tmux rename-session</code>命令用于重命名会话。</p><blockquote> <figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">> $ tmux rename-session -t <now-name> <new-name></span><br><span class="line">></span><br></pre></td></tr></table></figure></blockquote><p>上面命令可以将会话重命名。</p><h2 id="会话-Session-快捷键"><a href="#会话-Session-快捷键" class="headerlink" title="会话(Session)快捷键"></a>会话(Session)快捷键</h2><blockquote><p> <code>Ctrl+b d</code>:分离当前会话。</p><p> <code>Ctrl+b s</code>:列出所有会话。</p><p> <code>Ctrl+b $</code>:重命名当前会话。</p></blockquote><h1 id="window"><a href="#window" class="headerlink" title="window"></a>window</h1><p>如果说session是个不可见的东西,那么window就是我们输入、执行命令的地方。一个session可以包含多个window。把window类比成iTerm2中的标签应该就理解了。在创建session的时候默认会创建一个以”数字下标+bash”命名的window,并且名称随着bash中执行的不同命令而变化。在新建session时可以通过-n参数指定默认打开的window名称,比如通过 <strong>tmux new -s basic -n win</strong> 命名一个win名称的window。也可以随时通过 <strong>Prefix+,</strong> 来修改window名称。</p><p><code>tmux new-window</code>命令用来创建新窗口。</p><blockquote> <figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">> $ tmux new-window</span><br><span class="line">> </span><br><span class="line">> <span class="comment"># 新建一个指定名称的窗口</span></span><br><span class="line">> $ tmux new-window -n <window-name></span><br><span class="line">></span><br></pre></td></tr></table></figure></blockquote><h2 id="切换窗口"><a href="#切换窗口" class="headerlink" title="切换窗口"></a>切换窗口</h2><p><code>tmux select-window</code>命令用来切换窗口,也可以用按下前置快捷键 ctrl+b ,然后按数字键切换。</p><blockquote> <figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">> <span class="comment"># 切换到指定编号的窗口</span></span><br><span class="line">> $ tmux select-window -t <window-number></span><br><span class="line">> </span><br><span class="line">> <span class="comment"># 切换到指定名称的窗口</span></span><br><span class="line">> $ tmux select-window -t <window-name></span><br><span class="line">></span><br></pre></td></tr></table></figure></blockquote><h2 id="重命名窗口"><a href="#重命名窗口" class="headerlink" title="重命名窗口"></a>重命名窗口</h2><p><code>tmux rename-window</code>命令用于为当前窗口起名(或重命名),也可以按下前置快捷键<code>ctrl+b</code>,然后按<code>,</code>。</p><blockquote> <figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">> $ tmux rename-window <new-name></span><br><span class="line">></span><br></pre></td></tr></table></figure></blockquote><h2 id="窗口-Window-快捷键"><a href="#窗口-Window-快捷键" class="headerlink" title="窗口(Window)快捷键"></a>窗口(Window)快捷键</h2><p>下面是一些窗口操作的快捷键。</p><blockquote><p> <code>Ctrl+b c</code>:创建一个新窗口,状态栏会显示多个窗口的信息。</p><p> <code>Ctrl+b w</code>:从列表中选择窗口。(强烈推荐:它可以跨session选择所有的window)</p><p> <code>Ctrl+b ,</code>:窗口重命名。</p><p> <code>Ctrl+b <number></code>:切换到指定编号的窗口,其中的<code><number></code>是状态栏上的窗口编号。</p></blockquote><h1 id="Pane"><a href="#Pane" class="headerlink" title="Pane"></a>Pane</h1><p>一个window可以切割成多个pane,也就是所谓的分屏,算是Tmux的核心功能之一。</p><h2 id="划分窗格"><a href="#划分窗格" class="headerlink" title="划分窗格"></a>划分窗格</h2><p><code>tmux split-window</code>命令用来划分窗格。</p><blockquote> <figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">> <span class="comment"># 划分上下两个窗格 快捷键 Ctrl+b "</span></span><br><span class="line">> $ tmux split-window</span><br><span class="line">> </span><br><span class="line">> <span class="comment"># 划分左右两个窗格 快捷键 Ctrl+b %</span></span><br><span class="line">> $ tmux split-window -h</span><br><span class="line">></span><br></pre></td></tr></table></figure></blockquote><h2 id="移动光标"><a href="#移动光标" class="headerlink" title="移动光标"></a>移动光标</h2><p><code>tmux select-pane</code>命令用来移动光标位置, 也可以按下前置快捷键 ctrl+b,然后按方向键切换。</p><blockquote> <figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">> <span class="comment"># 光标切换到上方窗格</span></span><br><span class="line">> $ tmux select-pane -U</span><br><span class="line">> </span><br><span class="line">> <span class="comment"># 光标切换到下方窗格</span></span><br><span class="line">> $ tmux select-pane -D</span><br><span class="line">> </span><br><span class="line">> <span class="comment"># 光标切换到左边窗格</span></span><br><span class="line">> $ tmux select-pane -L</span><br><span class="line">> </span><br><span class="line">> <span class="comment"># 光标切换到右边窗格</span></span><br><span class="line">> $ tmux select-pane -R</span><br><span class="line">></span><br></pre></td></tr></table></figure></blockquote><h2 id="交换窗格位置"><a href="#交换窗格位置" class="headerlink" title="交换窗格位置"></a>交换窗格位置</h2><p><code>tmux swap-pane</code>命令用来交换窗格位置。</p><blockquote> <figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">> <span class="comment"># 当前窗格上移</span></span><br><span class="line">> $ tmux swap-pane -U</span><br><span class="line">> </span><br><span class="line">> <span class="comment"># 当前窗格下移</span></span><br><span class="line">> $ tmux swap-pane -D</span><br><span class="line">></span><br></pre></td></tr></table></figure></blockquote><h2 id="窗格(Pane)快捷键"><a href="#窗格(Pane)快捷键" class="headerlink" title="窗格(Pane)快捷键"></a>窗格(Pane)快捷键</h2><p>下面是一些窗格操作的快捷键,窗格相关的快捷键比较多,毕竟是我们主要的工作区,但是实际用的非常频繁的并不多。</p><p><code>Ctrl+b %</code>:划分左右两个窗格。</p><p><code>Ctrl+b "</code>:划分上下两个窗格。</p><p><code>Ctrl+b <arrow key></code>:光标切换到其他窗格。<code><arrow key></code>是指向要切换到的窗格的方向键,比如切换到下方窗格,就按方向键<code>↓</code>。</p><p><code>Ctrl+b x</code>:关闭当前窗格。</p><p><code>Ctrl+b ;</code>:光标切换到上一个窗格。</p><p><code>Ctrl+b o</code>:光标切换到下一个窗格。</p><p><code>Ctrl+b {</code>:当前窗格与上一个窗格交换位置。</p><p><code>Ctrl+b }</code>:当前窗格与下一个窗格交换位置。</p><p><code>Ctrl+b Ctrl+o</code>:所有窗格向前移动一个位置,第一个窗格变成最后一个窗格。</p><p><code>Ctrl+b Alt+o</code>:所有窗格向后移动一个位置,最后一个窗格变成第一个窗格。</p><p><code>Ctrl+b !</code>:将当前窗格拆分为一个独立窗口。</p><p><code>Ctrl+b z</code>:当前窗格全屏显示,再使用一次会变回原来大小。</p><p><code>Ctrl+b Ctrl+<arrow key></code>:按箭头方向调整窗格大小。</p><p><code>Ctrl+b q</code>:显示窗格编号。</p><h1 id="安装tmux"><a href="#安装tmux" class="headerlink" title="安装tmux"></a>安装tmux</h1><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># Ubuntu 或 Debian</span></span><br><span class="line">$ sudo apt-get install tmux</span><br><span class="line"></span><br><span class="line"><span class="comment"># CentOS 或 Fedora</span></span><br><span class="line">$ sudo yum install tmux</span><br><span class="line"></span><br><span class="line"><span class="comment"># Mac</span></span><br><span class="line">$ brew install tmux</span><br></pre></td></tr></table></figure><h1 id="配置tmux"><a href="#配置tmux" class="headerlink" title="配置tmux"></a>配置tmux</h1><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">cd</span></span><br><span class="line">git <span class="built_in">clone</span> https://github.com/gpakosz/.tmux.git</span><br><span class="line">ln -s -f .tmux/.tmux.conf</span><br><span class="line">cp .tmux/.tmux.conf.local .</span><br></pre></td></tr></table></figure><p>做完以上配置后就有了和up演示中一样的功能。</p><h1 id="前缀键"><a href="#前缀键" class="headerlink" title="前缀键"></a>前缀键</h1><p>Tmux 窗口有大量的快捷键。所有快捷键都要通过前缀键唤起。默认的前缀键是<code>Ctrl+b</code>,即先按下<code>Ctrl+b</code>,快捷键才会生效。</p><p>举例来说,帮助命令的快捷键是<code>Ctrl+b ?</code>。它的用法是,在 Tmux 窗口中,先按下<code>Ctrl+b</code>,再按下<code>?</code>,就会显示帮助信息。</p><p>然后,按下 ESC 键或<code>q</code>键,就可以退出帮助。</p><h1 id="tmux帮助命令"><a href="#tmux帮助命令" class="headerlink" title="tmux帮助命令"></a>tmux帮助命令</h1><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 列出所有快捷键,及其对应的 Tmux 命令</span></span><br><span class="line">$ tmux list-keys</span><br><span class="line"></span><br><span class="line"><span class="comment"># 列出所有 Tmux 命令及其参数</span></span><br><span class="line">$ tmux list-commands</span><br><span class="line"></span><br><span class="line"><span class="comment"># 列出当前所有 Tmux 会话的信息</span></span><br><span class="line">$ tmux info</span><br><span class="line"></span><br><span class="line"><span class="comment"># 重新加载当前的 Tmux 配置</span></span><br><span class="line">$ tmux <span class="built_in">source</span>-file ~/.tmux.conf</span><br></pre></td></tr></table></figure><h1 id="tmux快捷键汇总"><a href="#tmux快捷键汇总" class="headerlink" title="tmux快捷键汇总"></a>tmux快捷键汇总</h1><table><thead><tr><th>Ctrl+b</th><th>激活控制台;此时以下按键生效</th><th align="left"></th></tr></thead><tbody><tr><td>系统操作</td><td>?</td><td align="left">列出所有快捷键;按q返回</td></tr><tr><td>d</td><td>脱离当前会话;这样可以暂时返回Shell界面,输入tmux attach能够重新进入之前的会话</td><td align="left"></td></tr><tr><td>D</td><td>选择要脱离的会话;在同时开启了多个会话时使用</td><td align="left"></td></tr><tr><td>Ctrl+z</td><td>挂起当前会话</td><td align="left"></td></tr><tr><td>r</td><td>强制重绘未脱离的会话</td><td align="left"></td></tr><tr><td>s</td><td>选择并切换会话;在同时开启了多个会话时使用</td><td align="left"></td></tr><tr><td>:</td><td>进入命令行模式;此时可以输入支持的命令,例如kill-server可以关闭服务器</td><td align="left"></td></tr><tr><td>[</td><td>进入复制模式;此时的操作与vi/emacs相同,按q/Esc退出</td><td align="left"></td></tr><tr><td>~</td><td>列出提示信息缓存;其中包含了之前tmux返回的各种提示信息</td><td align="left"></td></tr><tr><td>窗口操作</td><td>c</td><td align="left">创建新窗口</td></tr><tr><td>&</td><td>关闭当前窗口</td><td align="left"></td></tr><tr><td>数字键</td><td>切换至指定窗口</td><td align="left"></td></tr><tr><td>p</td><td>切换至上一窗口</td><td align="left"></td></tr><tr><td>n</td><td>切换至下一窗口</td><td align="left"></td></tr><tr><td>l</td><td>在前后两个窗口间互相切换</td><td align="left"></td></tr><tr><td>w</td><td>通过窗口列表切换窗口</td><td align="left"></td></tr><tr><td>,</td><td>重命名当前窗口;这样便于识别</td><td align="left"></td></tr><tr><td>.</td><td>修改当前窗口编号;相当于窗口重新排序</td><td align="left"></td></tr><tr><td>f</td><td>在所有窗口中查找指定文本</td><td align="left"></td></tr><tr><td>面板操作</td><td>”</td><td align="left">将当前面板平分为上下两块</td></tr><tr><td>%</td><td>将当前面板平分为左右两块</td><td align="left"></td></tr><tr><td>x</td><td>关闭当前面板</td><td align="left"></td></tr><tr><td>!</td><td>将当前面板置于新窗口;即新建一个窗口,其中仅包含当前面板</td><td align="left"></td></tr><tr><td>Ctrl+方向键</td><td>以1个单元格为单位移动边缘以调整当前面板大小</td><td align="left"></td></tr><tr><td>Alt+方向键</td><td>以5个单元格为单位移动边缘以调整当前面板大小</td><td align="left"></td></tr><tr><td>Space</td><td>在预置的面板布局中循环切换;依次包括even-horizontal、even-vertical、main-horizontal、main-vertical、tiled</td><td align="left"></td></tr><tr><td>q</td><td>显示面板编号</td><td align="left"></td></tr><tr><td>o</td><td>在当前窗口中选择下一面板</td><td align="left"></td></tr><tr><td>方向键</td><td>移动光标以选择面板</td><td align="left"></td></tr><tr><td>{</td><td>向前置换当前面板</td><td align="left"></td></tr><tr><td>}</td><td>向后置换当前面板</td><td align="left"></td></tr><tr><td>Alt+o</td><td>逆时针旋转当前窗口的面板</td><td align="left"></td></tr><tr><td>Ctrl+o</td><td>顺时针旋转当前窗口的面板</td><td align="left"></td></tr></tbody></table><h1 id="参考文章"><a href="#参考文章" class="headerlink" title="参考文章"></a>参考文章</h1><p><a href="http://www.ruanyifeng.com/blog/2019/10/tmux.html" target="_blank" rel="noopener">阮一峰-Tmux 使用教程</a></p><p><a href="https://www.cnblogs.com/lizhang4/p/7325086.html" target="_blank" rel="noopener">亡灵族-tmux常用命令</a></p>]]></content>
<summary type="html"><p>之前在中期学习计划中将tmux列入其中,但是一直没有花时间研究。周末看番的时候有一个<a href="https://www.bilibili.com/video/BV1Qq4y1f7N1?t=693" target="_blank" rel="noopener">tmux的视频推荐</a>,点开看了一下觉得讲的挺好的,感觉是时候在工作中导入这个工具了。视频也直接嵌在正文中,有需要的朋友不妨看一下。</p></summary>
<category term="technology" scheme="https://blog.xiaomo.info/categories/technology/"/>
<category term="工具" scheme="https://blog.xiaomo.info/tags/工具/"/>
</entry>
<entry>
<title>(转)doTween插件使用介绍</title>
<link href="https://blog.xiaomo.info/2021/doTween/"/>
<id>https://blog.xiaomo.info/2021/doTween/</id>
<published>2021-01-04T00:00:00.000Z</published>
<updated>2022-08-10T23:53:17.655Z</updated>
<content type="html"><![CDATA[<p>今天我们一起来研究一下DOTween动画插件。</p><p>对于该插件官网上的介绍是:“DOtween是一种快速,高效,完全类型安全的面向对象的动画引擎。”</p><p>将它导入Unity中,可以很方便快速地帮我们完成许多动画效果。本篇文章主要将通过一些小案例来介绍DOTween的使用方法和主要功能。</p><a id="more"></a><p>DOTween导入:</p><p>打开Unity创建一个新工程,将下载好的DOTween插件直接拖入Project面板:</p><p><img src="https://image.xiaomo.info//blog/v2-ec2fe2032ac2aa4468a7b022c1300638_1440w.jpg" alt="img"></p><p>基本用法</p><p>一.移动动画</p><p>创建一个小球,位置设为世界坐标原点(0,0,0),挂上此脚本:</p><figure class="highlight csharp"><table><tr><td class="code"><pre><span class="line"><span class="keyword">using</span> DG.Tweening; <span class="comment">//引入命名空间</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">DOTWeenTest</span> : <span class="title">MonoBehaviour</span></span><br><span class="line">{</span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">Start</span>(<span class="params"></span>)</span></span><br><span class="line"><span class="function"></span> {</span><br><span class="line"> transform.DOMove(<span class="keyword">new</span> Vector3(<span class="number">4</span>, <span class="number">3</span>, <span class="number">0</span>), <span class="number">3</span>); <span class="comment">//脚本物体3秒从当前位置移动到世界坐标(4,3,0)位置</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><img src="https://image.xiaomo.info//blog/v2-dae4d8ac280b237d210afd5e1892094d_b.webp" alt="img"></p><p>我们发现,使用的DOTween插件后,transform居然能够点出DOMove方法,这是因为C#的拓展性,使其和Unity的一些类能产生链接,是不是感觉很神奇。因为这些特性,使我们在使用起来非常简单易懂,想让哪个物体动,就让它的transform组件来调用DOTWeen的方法就可以了。</p><p>DOMove相关方法:</p><p>世界坐标上移动:transform.DOMove</p><p>本地坐标上移动:transform.DOLocalMove</p><p>世界坐标的X轴上移动:transform.DOMoveX</p><p>本地坐标的X轴上移动:transform.DOLocalMoveX</p><p>通过以上的方法,我们学会了制作移动动画,但这个动画会自动播放,并且,播放完了就销毁了,不能重复使用,后面我们会通过一些设置来避免。</p><p>二.From()方法的使用</p><p>2秒时间从世界坐标(2,2,0)处回到自身当前位置:</p><figure class="highlight csharp"><table><tr><td class="code"><pre><span class="line">transform.DOMove(<span class="keyword">new</span> Vector3(<span class="number">2</span>, <span class="number">2</span>, <span class="number">0</span>), <span class="number">1</span>).From();</span><br></pre></td></tr></table></figure><p><img src="https://image.xiaomo.info//blog/v2-b1a6447ae7f03ece15e5fae60d762a93_b.jpg" alt="img"></p><p>从以自身为原点的坐标系(2,2,0)处回到自身当前位置:</p><figure class="highlight csharp"><table><tr><td class="code"><pre><span class="line">transform.DOMove(<span class="keyword">new</span> Vector3(<span class="number">2</span>, <span class="number">2</span>, <span class="number">0</span>), <span class="number">2</span>).From(<span class="literal">true</span>);</span><br></pre></td></tr></table></figure><p><img src="https://image.xiaomo.info//blog/v2-85f3326935bd2d39e3aa41abbef6a6bd_b.jpg" alt="img"></p><p>三.动画正放与倒放</p><p>1.场景中创建两个按钮,来控制动画的播放顺序:</p><p><img src="https://image.xiaomo.info//blog/v2-c61bba776ce7b06fb00e2e946be72f48_1440w.jpg" alt="img"></p><p>2.为小球挂上脚本:</p><figure class="highlight csharp"><table><tr><td class="code"><pre><span class="line"><span class="keyword">using</span> DG.Tweening; <span class="comment">//引入命名空间</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">DOTWeenTest</span> : <span class="title">MonoBehaviour</span></span><br><span class="line">{</span><br><span class="line"> Tweener twe; <span class="comment">//声明一个Tweener对象</span></span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">Start</span>(<span class="params"></span>)</span></span><br><span class="line"><span class="function"></span> { </span><br><span class="line"> twe = transform.DOMove(<span class="keyword">new</span> Vector3(<span class="number">3</span>, <span class="number">4</span>, <span class="number">0</span>), <span class="number">2</span>);<span class="comment">//将动画保存在Tweener对象中 </span></span><br><span class="line"> twe.Pause();<span class="comment">//暂停,防止自动播放 </span></span><br><span class="line"> twe.SetAutoKill(<span class="literal">false</span>);<span class="comment">//关闭动画自动销毁 </span></span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//创建两个方法事件,控制前放后倒放</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">Forward</span>(<span class="params"></span>)</span></span><br><span class="line"><span class="function"></span> {</span><br><span class="line"> twe.PlayForward(); <span class="comment">//该动画正放</span></span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">Back</span>(<span class="params"></span>)</span></span><br><span class="line"><span class="function"></span> {</span><br><span class="line"> twe.PlayBackwards(); <span class="comment">//该动画倒放</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>3.将两个方法分别挂在各自按钮上,运行程序:</p><p><img src="https://image.xiaomo.info//blog/v2-2a421966fb003d5b1f2a4fdc981870bc_b.jpg" alt="img"></p><p>当点击了前放后才能倒放,也就是说不能一上来就倒放。</p><p>其它方法:</p><p>Pause(): 暂停动画</p><p>SetLoops(3): 循环3次</p><p>四.设置动画曲线</p><p>在以上我们展示的动画效果中我们,我们通过观察可以看到,物体在播放移动动画时速度是由快到慢(倒放除外),这种现象是由动画播放时的动画曲线决定的,我们是可以通过修改动画曲线来改变物体动画效果的,比如我们可以将运动效果改成由慢到快:</p><figure class="highlight csharp"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Start</span>(<span class="params"></span>)</span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> Tweener twe = transform.DOMoveX(<span class="number">5</span>, <span class="number">2</span>); <span class="comment">//3秒时间在世界坐标中,让X轴移动到5的位置</span></span><br><span class="line"> twe.SetEase(Ease.InCubic); <span class="comment">//由慢到快</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><img src="https://image.xiaomo.info//blog/v2-609f4ad9472c8aab950592b5dc423c7e_b.jpg" alt="img"></p><p>而动画曲线的种类非常多,可以自己去试试看:</p><p><img src="https://image.xiaomo.info//blog/v2-7e488251807f877746c20a269a2fc3b9_1440w.jpg" alt="img"></p><p>五.使用动画改变颜色</p><p>既然让物体移动是通过使用transform来点出DOTWeen的方法,那改变颜色就是让材质组件来调用DOTWeen的方法即可:</p><figure class="highlight csharp"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Start</span>(<span class="params"></span>)</span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> Material material = GetComponent<MeshRenderer>().material;</span><br><span class="line"> material.DOColor(Color.red, <span class="number">3</span>); <span class="comment">//3秒变红</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><img src="https://image.xiaomo.info//blog/v2-c34a01a037c3b15e0c78eaadf54d717e_b.jpg" alt="img"></p><p>六.动画事件</p><figure class="highlight csharp"><table><tr><td class="code"><pre><span class="line">Material material;</span><br><span class="line">Tweener twe;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Start</span>(<span class="params"></span>)</span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> material = GetComponent<MeshRenderer>().material;</span><br><span class="line"> twe = material.DOColor(Color.red, <span class="number">3</span>); <span class="comment">//3秒变红</span></span><br><span class="line"> twe.OnComplete(ChangeColour); <span class="comment">//动画播放结束时调用</span></span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">ChangeColour</span>(<span class="params"></span>) <span class="comment">//变色</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> twe = material.DOColor(Color.blue, <span class="number">2</span>); <span class="comment">//2秒变蓝</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><img src="https://image.xiaomo.info//blog/v2-fe8c6a34eb64115d058b65e8fd71e957_b.jpg" alt="img"></p><p>先变红,再变蓝</p><p>动画事件相关方法:</p><p>OnStart: 动画第一次播放时调用</p><p>OnPlay: 动画每次从暂停状态解除时调用(包括初次播放)</p><p>Pause: 动画暂停时调用一次</p><p>OnUpdate: 动画播放过程中每帧调用</p><p>OnStepComplete: 每次动画播放结束时调用(受循环次数影响)</p><p>OnComplete: 每次动画播放结束时调用(不受循环次数影响,且倒放时不适用)</p><p>七.文本动画</p><p>1.让文本逐字显示:</p><p>使用UGUI创建一个Text,挂上该脚本:</p><figure class="highlight csharp"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Start</span>(<span class="params"></span>)</span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> Text text = GetComponent<Text>();</span><br><span class="line"> text.DOText(<span class="string">"最心爱的情人,却伤害我最深,为什么你背着我爱别人"</span>, <span class="number">5</span>); <span class="comment">//5秒时间将这段文字逐字显示</span></span><br><span class="line"> text.DOColor(Color.green, <span class="number">5</span>); <span class="comment">//颜色逐渐变绿</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><img src="https://image.xiaomo.info//blog/v2-414bac494ef22b9b6c520714239cd8f8_b.jpg" alt="img"></p><p>我们可以结合刚才讲到的动画事件,来组一个连续的文字显示:</p><p><img src="https://image.xiaomo.info//blog/v2-a63a2d5059d4f567b54a65dab3903fde_b.jpg" alt="img"></p><p>代码如下:</p><figure class="highlight csharp"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Start</span>(<span class="params"></span>)</span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> Text text = GetComponent<Text>();</span><br><span class="line"> Tweener twe = text.DOText(<span class="string">"下面是有奖竞猜:"</span>, <span class="number">2</span>);</span><br><span class="line"> twe.OnComplete(() =></span><br><span class="line"> {</span><br><span class="line"> text.text = <span class="string">""</span>;</span><br><span class="line"> text.DOText(<span class="string">"富奸老贼是怎么死的?"</span>, <span class="number">2</span>);</span><br><span class="line"> });</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>2.让文本逐渐显示:</p><p>首先要让文字调成完全透明状态:</p><p><img src="https://image.xiaomo.info//blog/v2-97bb44cd44daa3d50a236660cc7127d9_1440w.jpg" alt="img"></p><p>挂上脚本运行程序:</p><figure class="highlight csharp"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Start</span>(<span class="params"></span>)</span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> Text text = GetComponent<Text>();</span><br><span class="line"> text.DOFade(<span class="number">1</span>, <span class="number">2</span>); <span class="comment">//2秒时间让A值变为1</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><img src="https://image.xiaomo.info//blog/v2-10393f948f84af85dcc87431b9e7ff3d_b-20210104105723116.jpg" alt="img"></p><p>八.屏幕抖动动画:</p><p>很多游戏中经常会用到的效果,比如在战斗的时候,主角被攻击会伴随着一阵屏幕抖动,然后</p><p><img src="https://image.xiaomo.info//blog/v2-f84649b539d618fda7bd7dcc9ab606bd_1440w.jpg" alt="img"></p><p>以此来提高游戏打击感,其实所谓的屏幕抖动其实就是摄像机位置的抖动,还是离不开Transform动画:</p><figure class="highlight csharp"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Start</span>(<span class="params"></span>)</span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="comment">//transform.DOShakePosition(2); //在随机方向震动2秒,幅度默认为1</span></span><br><span class="line"> transform.DOShakePosition(<span class="number">2</span>, <span class="number">3</span>); <span class="comment">//在随机方向震动3秒,振幅为3</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>将这个脚本挂摄像机上,运行程序:</p><p><img src="https://pic3.zhimg.com/v2-885127801a1f9db0b3c40f3b2ca9aa66_b.jpg" alt="img"></p><p>或者:</p><figure class="highlight csharp"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Start</span>(<span class="params"></span>)</span></span><br><span class="line"><span class="function"></span> {</span><br><span class="line"> <span class="comment">//transform.DOShakePosition(2); //在随机方向震动2秒,幅度默认为1</span></span><br><span class="line"> <span class="comment">//transform.DOShakePosition(2, 3); //在随机方向震动2秒,振幅为3</span></span><br><span class="line"> transform.DOShakePosition(<span class="number">3</span>, <span class="keyword">new</span> Vector3(<span class="number">0</span>, <span class="number">2</span>, <span class="number">0</span>));<span class="comment">//只在世界坐标Y轴上震动3秒,振幅为2</span></span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p><img src="https://image.xiaomo.info//blog/v2-ae7d330e8e9a8987da852d94d818d08e_b.jpg" alt="img"></p><p>右下角的摄像机视角可看到,在X轴上并没有发生位移。</p><p>抖动动画不仅有位置上的抖动,还有选择上的抖动和缩放上的抖动:</p><p>旋转抖动: transform.DOShakeRotation</p><p>缩放抖动: transform.DOShakeScale</p><p>我们来看下缩放抖动是什么效果:</p><p>创建一个脚本挂在一个Cube上:</p><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">void Start()</span><br><span class="line">{</span><br><span class="line"> transform.DOShakeScale(2, new Vector3(3, 2, 0));</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><img src="https://pic3.zhimg.com/v2-ddca81162ba212ec744335bc454e230a_b.jpg" alt="img"></p><p>九.可视化编辑</p><p>除了使用脚本来播放DOTween动画外,该插件还拥有可视化编辑的功能。</p><p>动画编辑器</p><p>1.为需要做动画的物体添加DoTweenAnimation组件:</p><p><img src="https://image.xiaomo.info//blog/v2-b8e29b6a592b4ad8d84bf2edf2fd0d03_1440w.jpg" alt="img">直接点击可以进行打开或关闭</p><p>2.选择要做动画的组件:</p><p><img src="https://image.xiaomo.info//blog/v2-06c4a1e711d1e99c7466f1839e249f08_1440w.jpg" alt="img">这里面就包括了我们之前介绍的移动,变色,抖动,文字显示等功能。</p><p>3.当要做动画的组件不存在时则报错,非常方便:</p><p><img src="https://image.xiaomo.info//blog/v2-5cc60445746e3c0bd015e816ca834d0d_1440w.jpg" alt="img"></p><p>该物体上没有Text组件。</p><p>4.各种参数:</p><p><img src="https://image.xiaomo.info//blog/v2-e00bc0a466f6ece74ee996653348592b_1440w.jpg" alt="img"></p><p>5.当在编辑器关闭了自动播放后,使用代码播放:</p><figure class="highlight csharp"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> DOTweenAnimation a; <span class="comment">//在编辑器界面将挂有该组件的物体拖进来</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Start</span>(<span class="params"></span>)</span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> a.DOPlayForward(); </span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>6.如果不使用代码,使用点击按钮播放,在按钮的事件窗口将物体拖进去,找到DoTweenAnimation组件,调用播放方法:</p><p><img src="https://image.xiaomo.info//blog/v2-f1a9c6b4386c70632f63562cfd2dc546_1440w.jpg" alt="img"></p><p>路径编辑器</p><p>使用该组件可以做一些寻路的效果:</p><p>1.场景中创建一个Cube,添加DoTweenPath组件:</p><p><img src="https://image.xiaomo.info//blog/v2-a006823259d0da671a540e4e93e10bfe_1440w.jpg" alt="img"></p><p>2.运行程序:</p><p><img src="https://image.xiaomo.info//blog/v2-3c36483c346e11b211fc213b42c00318_b.jpg" alt="img"></p>]]></content>
<summary type="html"><p>今天我们一起来研究一下DOTween动画插件。</p>
<p>对于该插件官网上的介绍是:“DOtween是一种快速,高效,完全类型安全的面向对象的动画引擎。”</p>
<p>将它导入Unity中,可以很方便快速地帮我们完成许多动画效果。本篇文章主要将通过一些小案例来介绍DOTween的使用方法和主要功能。</p></summary>
<category term="unity" scheme="https://blog.xiaomo.info/categories/unity/"/>
<category term="doTween" scheme="https://blog.xiaomo.info/tags/doTween/"/>
</entry>
<entry>
<title>2020年年终总结和第二个长期学习计划</title>
<link href="https://blog.xiaomo.info/2020/mySecondFiveYearsLifePlan/"/>
<id>https://blog.xiaomo.info/2020/mySecondFiveYearsLifePlan/</id>
<published>2020-12-31T00:00:00.000Z</published>
<updated>2022-08-10T23:53:17.655Z</updated>
<content type="html"><![CDATA[<p>2021年了,离制定<a href="https://blog.xiaomo.info/2016/2016StudyPlan/">第一个5年计划</a>己经过去了将近5年, 回头再看这些内容发现有些现在己经被更好用的工具替代了, 或者被吞并了, 不禁感叹技术的发展让人眼花缭乱, gulp被webpack取代, atom被vscode收购,github被巨硬重金买下, angular2都特么混到V11了, 当时觉得牛逼的jquery纷纷被各大厂抛弃。 国民老公被记在小本本上, 国民老婆从林志玲也变成了黑泽志玲。 时间真是把杀猪刀, 被生活无情的按在地上摩擦了5年, 从一个刚入社会的小萌新变成一个大叔。 我秃了但我感觉我也变强了, 感觉是时候制定一个新的5年计划了, 合计合计下一个五年继续折磨我的头发的会是哪些东西。 最后敢问各位道友, 日本植发哪家强?</p><a id="more"></a><h1 id="第一个五年学习计划回顾"><a href="#第一个五年学习计划回顾" class="headerlink" title="第一个五年学习计划回顾"></a>第一个五年学习计划回顾</h1><h2 id="2016年"><a href="#2016年" class="headerlink" title="2016年"></a>2016年</h2><p> 刚毕业的时候作为一个小萌新,对什么都充满了好奇,但什么都不懂。最开始使用FTP存代码也没有觉得有什么不对,至到后来毕业设计项目的代码再也找不到了痛苦了好一阵子。后来知道了SVN这个神奇的东西之后感概技术改变人生,如果早点知道这个代码管理工具就不至于丢失具有纪念意义的代码了。</p><p> 由于大学不是计算机专业的关系,毕业之后从事技术相关岗位的同学屈指可数。或者说只有我一个人,不知道当时怎么有那么大的勇气自己一个人踏入陌生的领域。也许是因为毕业的压力吧,不得不感慨人的潜力是巨大的。大四的时候到处参加宣讲会,腾讯、百度、360、YY、九城等至今还记得的一些公司,然而都被无情的摩擦。最终进入了一家游戏研发(CP)的公司开发游戏。这一年来主要维护着一款线上的游戏,添加一些新的功能和新的活动,正常的版本更新维护等等。</p><p> 这一年接触了新的开发工具SVN,新的IDE JetBrains IDEA,新的同性交友网站 <a href="https://github.com/houko" target="_blank" rel="noopener">gayhub</a> ,新的NIO框架 netty,mina等,新的Java测试框架Junit。学习了html/css/js的基本页面布局开发技术,linux系统的基本操作等,算是基本上入了编程的门槛。</p><p>技术上来说:</p><ol><li> 了解了游戏开发的流程</li><li> 开始使用github保存自己的代码</li><li> 使用hexo搭建了自己的<a href="https://blog.xiaomo.info/">博客</a></li><li> 使用vue构建了自己的<a href="https://xiaomo.info/" target="_blank" rel="noopener">主站</a></li><li> 学习了web开发的基本知识</li><li> 能够使用java开发游戏后端的功能</li><li> 学会了使用linux环境进行运行环境的搭建和版本的更新维护</li><li> 能够熟练的使用jetbrains家族的产品</li><li> 学会了vim编辑器的功能</li><li> 使用oh-my-zsh对我的终端进行了各种折腾</li></ol><h2 id="2017年"><a href="#2017年" class="headerlink" title="2017年"></a>2017年</h2><p> 人生的20多年来,如果说到玩游戏我还是非常有信心的,从小学时的掌机、小霸王、街机,到初中时的电脑游戏CS、红警、流星蝴蝶结、仙剑、侠盗飞车,再到后来的网络游戏传奇、问道、诛仙、QQ飞车、QQ炫舞等比较主流的游戏都玩了个遍。但是从来没有想过自己能够开发出一款人尽皆知的游戏。但是就在这一年我开始参与开发公司的一款flash pc游戏,学到了游戏开发的各种技术。曾经神秘的游戏背后的逻辑原来都不是魔法,就像普通人观看魔术表演一样只会感叹多么神奇,而进入了游戏开发行业才发现原来游戏的制作也是那么的普通且~有趣。</p><p> 这一年我解开了游戏开发神秘的面纱,让游戏制作不再遥远。像以前玩游戏的的使用道具不知道什么原理,原来逻辑是将玩家背包数据中要使用的道具删掉,再执行对应道具的功能的操作最后将道具使用通知和使用效果的通知发送给客户端,客户端收到消息后播放对应的动画刷新UI等等。再比如说原来游戏中的金币/元宝等看起来都是一样的,但是背后还分了很多种类比如充值的元宝、系统送的元宝、交易道具获得的元宝等等。虽然玩家看到的是这些种类的总数,并无感知,但是它们有各种不同的itemId,实际上是不同的东西。只不过购买道具时允许扣除多个名字都是元宝的itemId而已。这些我之前都是不了解的,进入游戏行业之前玩游戏就是单纯的玩游戏,为了快乐时光也好为了打发时间也好,目的都是很单纯的。但是入了游戏行业之后玩游戏的时候会不自觉的思考某个功能是怎么实现的,如果是我的话我会怎么设计,怎么实现这样的功能等等。这样的变化在其他行业也能体验,我有一个要好的朋友做了UI设计之后打开APP的第一件事就是研究App的设计、交互是否合理,有什么值得借鉴的地方,有什么地方有待改进等等。不自觉的会进行思考。</p><p> 技术上来说:</p><ol><li> 将自己的个人网站全面切换到https</li><li> 使用宝塔管理我的linux运行环境</li><li> 掌握了SpringBoot连接mysql进行CURD的各种操作</li><li> 掌握了markdown的语法,能够使用markdown写各种分享</li><li> 购买了属于自己的服务器和域名,尝试从0到上线一款新项目的整个流程</li><li> 学会了nginx服务器的基本配置和使用</li><li> 学习了angular2的开发使用方法</li><li> 学习了webpack的各种配置</li><li> 学习了node开发相关的技术,koa/express连接mysql/mongodb开发API和网站</li><li> 接触到了lombok开发插件,从此便离不开</li><li> 在各种api文档同步问题了深受折磨后接触到了swagger UI,从此经手的所有项目的API都会被我集成swaggerUI</li><li> 使用Java的jsonp和node的爬虫框架进行数据爬取收集技术的学习和开发</li><li> 学习了maven构建工具的使用和多模块项目的配置。</li><li> 深入学习了spring boot/spring data jpa等后端开发框架的技术</li><li> 系统的学习了vim这个编辑器之神,能够较为熟练的使用vim进行文件编辑</li><li> 折腾了黑苹果,搭建了mac上ios开发的环境。</li><li> 学习了android/ios的移动端开发技术,做了直播软件和汇率换算的软件的demo。</li><li> 学习了shell脚本的开发。</li><li> 狠心买了mac book air丐版,不用在黑苹果系统下委屈求全了,主开发环境切换到mac os。</li><li> 买了HHKB这款只有60个键位的信仰键盘,曾经很长一段时间CTO在我电脑上调试代码都无法找到快捷键。</li><li> 经过了边工作边学车的半年煎熬时光(半年无休,单休+1天学车)终于拿到了驾照。</li></ol><h2 id="2018年"><a href="#2018年" class="headerlink" title="2018年"></a>2018年</h2><p> 在经历了2017年近半年的无休工作,感觉到单休的工作模式己经失去了生活的意义,因此有了换一份双休工作的念头。但是在CTO的挽留下选择继续开发游戏,因为这也是我热爱的工作内容。在2017年过完年之后买了心心念念的神车思域,还做了很多攻略加装了各种配件,对它待遇相当不错。计划着2018年结婚,所以2017年计划到日本旅行,正好当时有一个小伙伴也想到日本旅行,所以便一起做攻略在10月长假进行了为期半个月的旅行并生产了一篇 <a href="https://blog.xiaomo.info/2017/JapanTravel/">日本游记</a></p><p> 开始作为主程开发新的h5游戏,工作状态也发生了非常大的变化,原来单休的工作时间己经是不堪重负了再加上作为主程进行新项目的研发加班、通宵成了家常便饭。由于制作人(93年生)经验欠缺,技术能力不足等各种原因有着做不完的需求,乱作一团的版本规划,在原本规划好的版本里强行强入新需求等等。现在回头想想也感觉自己作为主程太逆来顺受,没有据理力争为组员争取到合理的开发时间导致大家工作内容十分繁重,后面也进行了一些反思<a href="https://blog.xiaomo.info/2018/mainProjectJobs/">主程职责总结和反思</a>。</p><p>技术上来说</p><ol><li> 考取了oracle的java gold的认证。</li><li> 独立带项目,不再是做好项目功能开发就能算工作完成的状态。</li><li> 协调部门之间的工作,工作内容的分配,项目进度的评估,代码的review,线上版本的更新,开发环境的维护等等工作都需要自己来做,所以对于学习工作所需之外新东西的时间就变得非常有限。 <a href="https://blog.xiaomo.info/2018/mainProjectJobs/">主程职责总结和反思</a></li><li> 从之前的纯业务逻辑开发开始转向性能相关问题关注点,jvm优化等内容。</li><li> 开始有计划性的阅读和学习JDK源码 <a href="https://blog.xiaomo.info/tags/JDK%E6%BA%90%E7%A0%81%E7%B3%BB%E5%88%97/">JDK源码系列</a></li><li> 开始学习docker的基本使用。</li><li> 学习unity3d游戏客户端开发技术,做了<a href="https://github.com/houko/Tank" target="_blank" rel="noopener">坦克大战</a> , <a href="https://github.com/houko/FlappyBird" target="_blank" rel="noopener">愤怒的小鸟</a> , <a href="https://github.com/houko/game2048" target="_blank" rel="noopener">2048</a> 等小游戏和 <a href="https://github.com/houko/ARPGGame" target="_blank" rel="noopener">RPG游戏Demo</a></li><li> 学习了c#游戏服务端开发框架 photonServer的使用 <a href="https://github.com/houko/PhotonEngineServer" target="_blank" rel="noopener">PhotonServer</a></li><li> 开源了<a href="https://github.com/houko/SpringBootUnity" target="_blank" rel="noopener">SpringBootUnity</a> 的template类型的项目,获得开源中国首页推荐和精品项目 <a href="https://gitee.com/hupeng_admin/SpringBootUnity" target="_blank" rel="noopener">gitee</a></li></ol><h2 id="2019年"><a href="#2019年" class="headerlink" title="2019年"></a>2019年</h2><p>如果说2018年是工作之后的人生转折点的话,那么2019年这一年就是转折之后的习惯过程。从大学远离故乡,到毕业到沿海城市就业,再到去年来到东京,这一个个的决定无不是对自我挑战的过程,这期间伴随着阵痛,同时也伴随着成长。有的人习惯了熟悉的生活非常害怕改变,怕换工作,怕搬家,怕交新朋友。各种各样的改变都会对生活带来冲击,但也带来了新鲜感,还是要看自己怎么样来看待这种改变。</p><h4 id="进入华人派遣"><a href="#进入华人派遣" class="headerlink" title="进入华人派遣"></a>进入华人派遣</h4><p>来日本之前也算是搜集了不少的信息,经过了各种考量觉得进华人派遣公司是当前的最优选择。一般来说到日本做IT相关工作,如果没有日本留学经验,都会经历<code>华人派遣公司——>日本大手派遣公司——>个人事业主(可能跳过)——>日本自社开发(在日外企)</code>这个过程,而我正处在这个初级阶段但不想长期处于这个阶段,所以为此也做了一些准备。</p><ul><li> 2017年年底通过了Oracle java认证</li><li> 2019年7月考取了日语能力测试N2证书</li><li> 2019年8月获取日本汽车驾照</li><li> 2019年11月参加了中国国家软件考试(软件设计师),但没有通过,以后有机会再考吧</li><li> 2019年12月参加了日语能力测试N1测试(同样没过)</li><li> 学习Angular/Vue前端开发框架(Javascript/Typescript)</li><li> 学习Unity3D</li></ul><p>但是,如果按照这个计划的话在日本转职至少要2次。日本是一个对跳槽容忍度比较低的国家,一份工作干不到2年就辞职会觉得不稳定,对找下份工作和申请永住签证都有一定的影响,所以打算直接进入游戏公司工作。</p><p>技术上来说</p><ol><li> 学习了sass/less/postcss等一些css增强框架 </li><li> 学习了bootstrapUI框架的使用 </li><li> 看完了 javascript 高级编程和javascript高级程序设计 </li><li> 看完了何品翻译的netty in action的书 </li><li> 初步学了一下 深入jvm原理的书 </li><li> 学习了代码重构、effective java等提交代码质量的内容 </li><li> 学习了restful风格的API开发 </li><li> 学习了 git flow开发工作流 </li><li> 折腾了zsh+iTerms的终端环境 </li></ol><p>生活上来说</p><ol><li> 女儿出生了,变正了真正的一家3口</li><li> 拿到了日本的驾照</li><li> 拿到了N2的日语证书</li><li> 给家人办理了家族签证</li><li> 面试了几家游戏开发公司,对游戏开发有的新的理解和思考。</li><li> 拿到了游戏开发公司的offer,但由于被所在的华人派遣公司摆了一道没能及时入职。</li><li> 这一年时间到刚到日本,工作习惯发生了不少了变化,所有新的知识学习都记录了onenote导致博客更新频率非常感人</li></ol><h2 id="2020年"><a href="#2020年" class="headerlink" title="2020年"></a>2020年</h2><p>2020年整体来说,是一个非常魔幻的一年,至今都还是像做梦一样。打乱了很多既定计划,遇到非常多困难的时刻,不过这些困难并不是我一个人碰到,全世界都笼罩在魔幻当中至今没有出来。因为2019年过年没有回老家,所以2020年决定回家过年。于是在1月20日东京飞杭州再从杭州自驾回湖北十堰。回国之前己经听说了一些相关コロナ相关的消息,同事也告诫我要注意安全,所以当时回家时刻意绕过武汉从襄阳回家。当时武汉发布消息的人被抓,央视出来背书说不要危言耸听,我竟然就这么相信了,只买了一袋5个装的口罩就回去了。导致之后的好几个月被关在家里不能出门,口罩几十块一个而且还买不到。买菜不能出门,在微信群发消息订购志愿者去采购,结果送的米发霉严重每次做饭整个家里都有很大一股霉味。说实话,那段时间真怀疑有没有轻度抑郁,莫名的非常暴躁,电脑也没带回家,什么事都做不了。一直到5月份才能回到杭州找了一份临时的工作,为了还房贷车子也卖掉了;半年没有收入,房子没交房也住不了,杭州的房租还要交,日本的房租还也得交,还有孩子要养,感觉每个月都是焦头烂额,现在想想挺过2020真的是太难太难了。一直到8月份日本才允许工作商务签证的入境,算算时间整整半年时间的计划全部打乱,家庭支出严重不足,定的学习目标全部耽搁,我和这个世界一起停摆了半年,终于在8月底回日本渡过了居家14天的隔离之后在9月份开始正式回归工作岗位,截至到11月份生活才慢慢回归正轨。虽然到现在为止整个世界依然被コロナ折磨着,但是经过了将近一年的共处,说麻木也好,习惯也好,这个世界的人们也接受了コロナ存在的事实,虽然每天还是做着正常的工作,过着正常的生活,但是心中依旧在担忧,在忍耐这场灾难什么时候能过去。</p><p>技术中来说</p><ol><li> 学习了新的开发语言<code>Dart</code></li><li> 学习了新的移动端开发框架<code>Flutter</code></li><li> 学习了新的游戏引擎<code>Unreal Engine</code></li><li> 使用<code>unreal engine</code> 的蓝图做了一款小游戏 <a href="https://github.com/houko/Muffin2D" target="_blank" rel="noopener">muffin2d</a></li><li> 制作了一款跨平台的电商app己上线到<code>apple store</code>和<code>google play</code></li><li> 对<code>spring boot</code>及相关技术的使用更加熟练了</li><li> 系统了学习了<code>mybatis-plus</code>框架的用法</li><li> 学会了<code>oauth2.0</code>相关协议及开发的内容</li><li> 学会了<code>jwt</code>的相关技术和使用方式</li><li> 开发了<code>google sign in</code>和<code>google one tap signin</code> 的新功能</li><li> 开发了<a href="https://blog.xiaomo.info/2020/webAppleSignIn/">apple sign in</a> 的新功能</li><li> 学习了vue相关的技术(包括vue2.x,vue3.x,<a href="https://blog.xiaomo.info/2020/vueFrameworkVuex/">vuex</a>,element-ui等)</li><li> 学习了react相关的技术</li><li> 学习了<a href="https://blog.xiaomo.info/2020/awsCommandLineUse/">aws cli</a>的基本命令用法</li><li> 学习了node项目性能监控工具pm2的用法</li><li> 学习了<a href="https://blog.xiaomo.info/2020/AwsSqsUse/">aws sns/sqs</a>和spring boot集成的用法</li><li> 学习了redis的<a href="https://blog.xiaomo.info/2020/AwsSqsUse/">pub/sub模式</a>用法</li><li> 学习了<a href="https://blog.xiaomo.info/2020/dynamoDB/">aws dynamoDB</a>的集成和CRUD相关的操作</li><li> 开了一篇长期向的博客<a href="https://blog.xiaomo.info/2020/npmPackageCollection/">分享npm常用库的用法</a></li><li> 学习了前端项目/后端项目集成datadog进行数据上报的用法</li><li> 学习了http请求框架fetch/axios/redaxios等</li></ol><p>生活上来说</p><ol><li> 决定长期在日本居住</li><li> 更新了3年的新签证</li><li> 本来打算软考的计划被耽误</li><li> 为了还贷失去了小车</li><li> 杭州的房子交房了,买了些家具出租出去补贴家用</li><li> 和老婆孩子在日本团聚了</li><li> 报名了12月份的N1考试,但是没有花太多时间学日语大概率通不过</li><li> 给孩子申请了明年的保育园</li></ol><h1 id="第二个五年学习计划"><a href="#第二个五年学习计划" class="headerlink" title="第二个五年学习计划"></a>第二个五年学习计划</h1><h2 id="开发语言"><a href="#开发语言" class="headerlink" title="开发语言"></a>开发语言</h2><h3 id="※-C"><a href="#※-C" class="headerlink" title="※ C#"></a>※ C#</h3><p>主要是针对unity3d需要用到c#语言,大概语法己经了解,和java类似使用起来没有难度。后续如果想要提升的话就需要通过项目实践来提升熟练度,打算仔细想想做一个什么样的游戏项目来提高自己的技能熟练度。</p><h3 id="C"><a href="#C" class="headerlink" title="C++"></a>C++</h3><p>Unreal engine劝退的大分部人感觉应该是c++这个开发语言,c/c++这门语言古老而稳重,学习曲线较高。从入门到放弃至少有5次以上了,但是虚幻5是真的香啊,所以必须要学习。不过因为工作上用不到,而且时间比较有限暂时还不会花费较多时间在上面。</p><h3 id="※-Typescript"><a href="#※-Typescript" class="headerlink" title="※ Typescript"></a>※ Typescript</h3><p>最开始接触ts是在angular2的beta版本的时候,当时还同步推出了一个Dart分支的angular,但是基本上没什么人使用。我当时就很好奇dart是个什么东西,但是却没有深究。平时的工作中后端主要使用java,前端主要使用ts,所以ts的学习一直在进行中。目来来说公司的项目基本上都在往ts上转,所以现在对ts的掌握程度是能够应对正常的开发,但是还需要进一步的提高。</p><h3 id="※-Java"><a href="#※-Java" class="headerlink" title="※ Java"></a>※ Java</h3><p>java是我进入编程行业的入门语言,虽然它没有js和python简单,但是它是面向对象的语言,结构严谨,比较贴近自然语言,学习难度不大。截至到2020年底使用java己经有近10年时间,从jdk6到现在主要使用的版本jdk11,总体感觉java的生态比较稳定,就算不升级到java11,使用java7或者java8也完全能够胜任日常的工作。刚开始接触的ssh框架+jsp和servelet现在也不怎么听得到了。springboot/spring cloud成了后端开发的标配,后续的计划是围绕着spring boot/spring cloud深入学习。</p><h3 id="Python"><a href="#Python" class="headerlink" title="Python"></a>Python</h3><p>最开始接触python语言是作为游戏主程的2018年,当时需要维护一些组内的开发工具的脚本。bash的语言实在是用不习惯,所以将老的工具都用python重写了,后续的工具开发也是python开发的。虽说如此,也是对python的语法仅仅是能使用,时间久不用的话语法又忘的差不多了。如果想要深入学习的话还是需要以项目为基础进行锻炼,后续的打算是自己如果再写api的话可以用python的flask来写,而不是java。</p><h3 id="※-Dart"><a href="#※-Dart" class="headerlink" title="※ Dart"></a>※ Dart</h3><p>无意间接触了跨平台开发框架flutter,再次看到了熟悉的开发语言dart。这一次不会只混个脸熟了,为学习flutter为契机顺便学会了dart。这个开发言语是到目前为止所有学习的开发语言中最省力的一个,语法像是java+typescirpt的组合。所以学习flutter的时候主要精力放在框架上,语言顺带看了下语法基本上就会使用了。dart语法目前来说感觉没有太大的提升空间,移动端开发的业务也不会因为dart的语法有障碍,所以暂时没有深入研究dart的必要性。</p><h2 id="语言"><a href="#语言" class="headerlink" title="语言"></a>语言</h2><h3 id="※-日语"><a href="#※-日语" class="headerlink" title="※ 日语"></a>※ 日语</h3><p>算起来接触日语也有5年时间了,但是这5年来断断续续的记了些单词学了些语法,自学基本上就是这个样子。有空的时候就学学,没有系统性,所以日语的能力一直是基本能用的样子。虽然也考到了N2证书,但是感觉和日本的小学生语言能力应该差不多,所以后续的主要精力会花费在学习日语上,争取早日考过N1。不论是因为N1证书高级人才加分也好,还是在这个国家生活也好,没有理由不好好学日语。</p><h3 id="※-英语"><a href="#※-英语" class="headerlink" title="※ 英语"></a>※ 英语</h3><p>作为一个技术者,华人还是挺悲哀的,基本上所有的技术都是外国的,活在大陆还有高墙,想学个东西经常会因为网络环境异常的麻烦。所以培养自己的外语能力还是非常重要的,作为一个技术者只要还在这个圈子就要不断的学习。而中文圈子里的内容要么是无脑复制粘贴没有验证就随便往网上发的,要么搜到的资料是老的,基本上用不了。很多工具/框架的官网都只提供了英文的文档,所以英语能力的提升也是极为的重要。但是学习语言是一个非常耗费时间和精力的事情,同时学习多门语法还要兼顾工作和生活是非常困难的。所以目前主要的精力还是日语,平时工作的时候刻意多看一些英文的官方文档。不要用翻译,遇到不认识的词查了记一记提升也是很明显的。</p><h2 id="开发工具"><a href="#开发工具" class="headerlink" title="开发工具"></a>开发工具</h2><h3 id="IDEA"><a href="#IDEA" class="headerlink" title="IDEA"></a>IDEA</h3><p>从正式开发工作时就开始使用IDEA作为开发的主要的编辑器,无论是对快捷键的熟悉程度还是对工具的使用熟练度都足以应付日常的工作,所以之后我还是会继续以jetbrains系列的开发工具使用为主。</p><h3 id="VSCode"><a href="#VSCode" class="headerlink" title="VSCode"></a>VSCode</h3><p>折服于vscode的轻量和颜值,我不断的在对vscode的尝试和放弃中挣扎。被jetbrains系列工具惯坏了感觉其他开发工具都不好用,于是尝试了多次都放弃了。但是随着这么多次尝试也是有收获的,也慢慢的习惯了用vscode开发,虽然感觉有很多不顺手的地方,但是配合着IDEA使用也没什么问题。后续的计划是java开发使用IDEA,前端开发使用vscode然后以IDEA为辅助。多学习一些vscode的用法,找一找vscode好用的插件。</p><h3 id="Android-Studio"><a href="#Android-Studio" class="headerlink" title="Android Studio"></a>Android Studio</h3><p>开发flutter的时候用android studio不仅能构建android应用也能够开发ios应用,这也是我我喜欢jetbrains家族的IDE的重要原因,生态非常的好,想要用到的东西基本上都有对应的功能。但是jetbrains也有不好的地方,非常的吃配置,价格昂贵等等(划掉,反正白嫖)。所以平时写flutter也可以使用vscode安装flutter插件来工具,如果需要构建android环境的话可以使用android studio打开构建。</p><h3 id="Rider"><a href="#Rider" class="headerlink" title="Rider"></a>Rider</h3><p>它同样是jetbrains家族的IDE,是c#语法开发的IDE。网上都说visual studio是宇宙第一IDE。但是和rider相比,我没有觉得它有任何一点能够胜过rider,而且还是跨平台的IDE。所以我写c#的时候首选会用rider。</p><h3 id="Pycharm"><a href="#Pycharm" class="headerlink" title="Pycharm"></a>Pycharm</h3><p>这同样是jetbrains公司的IDE,是针对python开发的IDE。从我学习python开始,大一统推荐的都是pycharm,如果你问我如果写python不用pycharm的话…我应该会用vim吧。</p><h3 id="vim"><a href="#vim" class="headerlink" title="vim"></a>vim</h3><p>vim作为linux系统的默认编辑器,想当初打开vim编辑内容后却不知道怎么退出的萌新状态现在虽然是找不回来了。但是对vim的使用也仅仅是停留了能够对文件进行编辑,并没有达到使用vim进行项目开发的熟练程序。后续可能会考虑在写前端的时候用vim练一练熟练度。</p><h3 id="emacs"><a href="#emacs" class="headerlink" title="emacs"></a>emacs</h3><p>emacs作为神之编辑器,还是挺神秘的。emacs的学习曲线比vim要难上很多,还要使用Lisp编程语言配置环境,适合想折腾的极客。我想我还是愿意折腾一下的。</p><h3 id="Git"><a href="#Git" class="headerlink" title="Git"></a>Git</h3><p>git是在我短暂的使用svn后切换过来的,使用体验和当初从eclipse转到IDEA的感觉是一样的,感觉真的是太得劲了。分支系统,多任务并行 ,git flow开发模式,逻辑清晰合理,团队协作必备良方。现在对git的使用算是基本上没什么问题,但是能再深入学学当然是更好的。</p><h3 id="tmux"><a href="#tmux" class="headerlink" title="tmux"></a>tmux</h3><p>tmux作为分屏最终解决方案,听到了太多关于它美好的描述,但是一直是停留在传说上。据说它能够提高工作效率,保存工作环境后一键恢复,听起来还是有点小激动,也列在我的学习计划中。</p><h2 id="前端框架"><a href="#前端框架" class="headerlink" title="前端框架"></a>前端框架</h2><h3 id="flutter"><a href="#flutter" class="headerlink" title="flutter"></a>flutter</h3><p>用flutter也算是做了一个完整的线上项目了,google play和apple store都己经上线且版本更新己超过10个版本。算是一个商业级的项目经验了,flutter是一个非常年轻的框架,而且野心不小。对web的支持还没有完全成熟,而且google的funsia系统也还没有发布。还算是在蛰伏期,我还是非常看好flutter的前景的。以后有什么好的想法需要做app的话我肯定会首选flutter。</p><h3 id="vue"><a href="#vue" class="headerlink" title="vue"></a>vue</h3><p>现在公司的项目前端技术栈还是挺多的,三大框架轮番上阵,自然vue也少不了。vue是唯一一个华人的非大公司背景的和另外2个巨头同一地位的前端框架,其工具链生态,学习成本,文档等各方面都是非常的值得学习。经过了3个后台的开发,现在基本上是对vue的使用没太大问题,但是感觉需要深入学习的东西还有很多。vue3也出来了,vue+typescript的组合肯定会更好。</p><h3 id="SCSS"><a href="#SCSS" class="headerlink" title="SCSS"></a>SCSS</h3><p>偏后端的全栈开发者应该都对css比较头疼,它不像编程语言那样不是1就是2。它像一层层形状不同的网叠在一起,最终呈现出来的样子是什么样的得在浏览器里实际查看才知道,而且它没有变量,没有条件判断,没有函数。 scss是一个css的增强框架,如果你对scss不熟,也完全可以在scss文件里写css语法。如果你很擅长scss,那也可以写变量,函数,判断各种和编程语言一样的控制css,是不是觉得很酷。</p><h3 id="React"><a href="#React" class="headerlink" title="React"></a>React</h3><p>学习最终是要付诸实践的,没有应用于项目的学习在一定程度上来说是无用的学习。所以学习新技术当然除了兴趣使然,更重要的是以公司的实际需求出发。react是目前全世界使用最为广泛的前端开发框架,公司项目也在使用,有什么理由不学呢。</p><h3 id="webpack"><a href="#webpack" class="headerlink" title="webpack"></a>webpack</h3><p>前5年的学习规划中webpack还处在版本1,还是一个非常稚嫩的框架。当时的学习目标还是browerify、grant、gulp等等构建工具,短短几年时间webapck己经实现了大一统。当时学习webpack的时候各种配置学的迷迷糊糊,现在各大框架都将webpack封装到自己的框架内部,给开发者发提供了简单友好的API配置。新入行的小萌新甚至感受不到webpack的存在,但是没有感知并不代表它不存在。在实际的项目开发中很多的配置需要根据实际情况定制,所以手动配置webpack还算是一个刚需。</p><h3 id="nutx-js"><a href="#nutx-js" class="headerlink" title="nutx.js"></a>nutx.js</h3><p>nuxt.js是vue SSR(服务端渲染)的解决方案,它集成了vue,vue-router,vuex等各种工具。按照约定的结构开发项目,便能够自动生成路由渲染出html,是一个非常值得学习的框架。</p><h2 id="后端框架"><a href="#后端框架" class="headerlink" title="后端框架"></a>后端框架</h2><h3 id="Spring-boot"><a href="#Spring-boot" class="headerlink" title="Spring boot"></a>Spring boot</h3><p>springboot这个框架的出现可以算是java开发者的福音,当初学习spring的时候各种xml的配置把人折腾的欲仙欲死。有了springboot,不需要下载tomcat,不需要配置xml,创建项目就可以写业务,怎么会有这么完美的框架存在。它的功能和spring全家桶大而全,基本上涵盖了所有业务所需的技术,所以学习难度也还是非常大的。现在的业务开发中虽然使用没有问题,也开源一个将近千星的springboot相关的项目,依然不敢说对springboot精通,还有很多需要学习的地方。</p><h3 id="Hibernate"><a href="#Hibernate" class="headerlink" title="Hibernate"></a>Hibernate</h3><p>hibernate和myatis是对db的不同实现,它是基于ORM的实现,可以通过实体类生成sql表。也可以在整合了spring data jpa的情况下使用自然语言代替sql进行查询,感觉非常的极客,如果自己做一些小项目用它可以提高效率。但是灵活程度比mybatis要差,至至怎么选择得看项目实际情况。既然能做选择,前提是得会用,所以学习它是必要的。</p><h3 id="Mybatis"><a href="#Mybatis" class="headerlink" title="Mybatis"></a>Mybatis</h3><p>mybatis从入行到现在乃至以后,这个框架一直是绕不过的。灵活方便,使用简单。关于mybatis自动生成xml和条件查询的使用还需要查询示例。</p><h3 id="Django"><a href="#Django" class="headerlink" title="Django"></a>Django</h3><p>个人感觉像是python版的express,大而全的一个web框架,个人更喜欢flask这种轻而小的框架。</p><h3 id="express-js"><a href="#express-js" class="headerlink" title="express.js"></a>express.js</h3><p>node版本的spring,大而全,用起来比较省心。</p><h3 id="flask"><a href="#flask" class="headerlink" title="flask"></a>flask</h3><p>pythonp写api的首选框架</p><h3 id="koa2"><a href="#koa2" class="headerlink" title="koa2"></a>koa2</h3><p>node写api的首选框架</p><h2 id="数据库"><a href="#数据库" class="headerlink" title="数据库"></a>数据库</h2><h3 id="mysql"><a href="#mysql" class="headerlink" title="mysql"></a>mysql</h3><p>从业以来使用最久也是业界使用最普遍的数据库,免费强大,使用方便。</p><h3 id="graphql"><a href="#graphql" class="headerlink" title="graphql"></a>graphql</h3><p>比较新兴的数据库,个人很喜欢,以后有机会可以在项目中使用一下。</p><h3 id="redis"><a href="#redis" class="headerlink" title="redis"></a>redis</h3><p>缓存数据库的一只独秀,想在项目上加个缓存数据库的话基本上就是redis了。</p><h2 id="游戏开发"><a href="#游戏开发" class="headerlink" title="游戏开发"></a>游戏开发</h2><h3 id="Unity3D"><a href="#Unity3D" class="headerlink" title="Unity3D"></a>Unity3D</h3><p>之前调查过很长一段时间,不管是在中国还是在日本市场,unity的工作岗位和使用都较unreal多出很多,资料也相对较全,学习容易。所以陆陆续续用unity做了好几个小游戏,后续打算进一步的深化相关技能。</p><h3 id="Unreal-Engine"><a href="#Unreal-Engine" class="headerlink" title="Unreal Engine"></a>Unreal Engine</h3><p>一直对unreal抱有极大的兴趣,但是因为c++不熟悉所以一直是从入门到放弃的循环。看到虚幻5发布的视频之后坚定了入了循环的阵营,又重新开始学习虚幻。发现学习blueprint入门比较简单,也使用了unreal的blueprint做了一款游戏。但是因为精力有限,有很长一段时间没有继续学习unreal了。虽然ue功能强大,但是因为学习难度大,个人开发者很少有人驾驭得了,所以后续打算还是以unity为切入点,如果有机会进入游戏开发公司再学ue吧。</p><h2 id="高级人才"><a href="#高级人才" class="headerlink" title="高级人才"></a>高级人才</h2><h3 id="N1证书"><a href="#N1证书" class="headerlink" title="N1证书"></a>N1证书</h3><p>虽然报了2020年12月的N1考试,但是因为2020年耽误了太多时间导致学习日语的时间极为有限。对于考试合格己不报希望,来年6月再战N1。</p><h3 id="软件设计师"><a href="#软件设计师" class="headerlink" title="软件设计师"></a>软件设计师</h3><p>本来计划2020年6月考软件设计师证书的,但是被取消了之后所以计划也被打乱,看2021年有没有机会考吧。</p><h3 id="基本情报技术者"><a href="#基本情报技术者" class="headerlink" title="基本情报技术者"></a>基本情报技术者</h3><p>目前日语能力太差导致不太能看懂相关试题的内容,所以暂时不打算考试基本情报。计划排在N1通过之后再考吧。</p><h3 id="工作相关"><a href="#工作相关" class="headerlink" title="工作相关"></a>工作相关</h3><p>随着公司项目的需要学习新的技术并进行总结,并发布博客。</p><p>坚持每年进行一个总的复盘,总结一年来学到的东西,之后还需要加强的地方。</p><h3 id="前程相关"><a href="#前程相关" class="headerlink" title="前程相关"></a>前程相关</h3><p>坚持学习新的知识</p><h3 id="生活质量相关"><a href="#生活质量相关" class="headerlink" title="生活质量相关"></a>生活质量相关</h3><p>尽快考过N1,为了更好的和别人进行沟通交流。</p><p>申请高级人才签证,因为目前证书都没有考分数达不到80,所以2021年年初就以70分为基准申请吧。</p><h3 id="家人有关"><a href="#家人有关" class="headerlink" title="家人有关"></a>家人有关</h3><p>已经申请了2021年4月孩子的保育园,2021年2月份能够得到结果能否入园,如果能够入园就送老婆去日语培训班学习日语。</p>]]></content>
<summary type="html"><p>2021年了,离制定<a href="https://blog.xiaomo.info/2016/2016StudyPlan/">第一个5年计划</a>己经过去了将近5年, 回头再看这些内容发现有些现在己经被更好用的工具替代了, 或者被吞并了, 不禁感叹技术的发展让人眼花缭乱, gulp被webpack取代, atom被vscode收购,github被巨硬重金买下, angular2都特么混到V11了, 当时觉得牛逼的jquery纷纷被各大厂抛弃。 国民老公被记在小本本上, 国民老婆从林志玲也变成了黑泽志玲。 时间真是把杀猪刀, 被生活无情的按在地上摩擦了5年, 从一个刚入社会的小萌新变成一个大叔。 我秃了但我感觉我也变强了, 感觉是时候制定一个新的5年计划了, 合计合计下一个五年继续折磨我的头发的会是哪些东西。 最后敢问各位道友, 日本植发哪家强?</p></summary>
<category term="summary" scheme="https://blog.xiaomo.info/tags/summary/"/>
<category term="japan" scheme="https://blog.xiaomo.info/tags/japan/"/>
</entry>
<entry>
<title>postman和commandline tools(newman)用法介绍</title>
<link href="https://blog.xiaomo.info/2020/newmanCommandLine/"/>
<id>https://blog.xiaomo.info/2020/newmanCommandLine/</id>
<published>2020-12-17T00:00:00.000Z</published>
<updated>2022-08-10T23:53:17.655Z</updated>
<content type="html"><![CDATA[<p>不知道大家对大批量重复性的工作内容的第一反应是怎样的,我的第一反应肯定是写脚本,还得是python的。但是发现postman对批量api调用也有了较好的支持,所以便省去了写脚本的过程,直接使用命令行就可以了。</p><a id="more"></a><h1 id="postman介绍"><a href="#postman介绍" class="headerlink" title="postman介绍"></a>postman介绍</h1><p> postMan是一款功能强大的网页调试与发送网页HTTP请求的工具。<strong>postMan</strong>能够发送任何类型的HTTP请求(GET, HEAD, POST,PUT..),附带任何数量的参数和HTTP headers。支持不同的认证机制(basic, digest,OAuth),接收到的响应语法高亮(HTML,JSON或XML)。</p><p>postMan既可以以chrome浏览器插件的形式存在,也可以是独立的应用程序存在。可以到<a href="https://www.getpostman.com/%E4%B8%8B%E8%BD%BD%E3%80%82" target="_blank" rel="noopener">https://www.getpostman.com/下载。</a></p><h1 id="操作环境"><a href="#操作环境" class="headerlink" title="操作环境"></a>操作环境</h1><p>postman适用于不同的操作系统,Postman Mac、Windows X32、Windows X64、Linux系统,还支持postman 浏览器扩展程序、postman chrome应用程序等。</p><p><strong>Postman重要提示:</strong></p><p>由于2018年初chrome停止对chrome应用程序的支持,你的<strong>postman插件可能无法正常</strong>使用了。目前chrome应用商店能使用的就是chrome扩展程序和主题背景,在这里建议大家直接下载它的应用程序进行使用。</p><h1 id="postman界面介绍"><a href="#postman界面介绍" class="headerlink" title="postman界面介绍"></a>postman界面介绍</h1><p><img src="https://image.xiaomo.info//blog/webp" alt="img"></p><h1 id="postman工作机制"><a href="#postman工作机制" class="headerlink" title="postman工作机制"></a>postman工作机制</h1><ol><li> 像项目开发一样可以工程化管理</li><li> 可以邀请团队成员进行协作,一次配置,多人使用</li><li> postman有工作工间和collections,workspace就是我们的工作区,collections就是我们的项目。</li></ol><h1 id="批量处理"><a href="#批量处理" class="headerlink" title="批量处理"></a>批量处理</h1><img src="https://image.xiaomo.info//blog/image-20201217144614875.png" alt="image-20201217144614875" style="zoom:50%;"><p>可以直接在postman里跑runner,但是在这之前我们需要配置要跑的API</p><p>举例:</p><p>我们这里用twitter的api为例,它需要传auth1.0的参数和user_id来获取twitter用户的详细信息。</p><ol><li> URL</li></ol><p>我们需要请求的API地址,这里以<a href="https://api.twitter.com/1.1/users/show.json%E4%B8%BA%E4%BE%8B" target="_blank" rel="noopener">https://api.twitter.com/1.1/users/show.json为例</a></p><ol start="2"><li> 参数</li></ol><p>因为我们是要跑runner,所以参数肯定不是固定的,所以我们定义一个参数,用双花括号包里来</p><p><img src="https://image.xiaomo.info//blog/image-20201217145048523.png" alt="image-20201217145048523"></p><ol start="3"><li>环境</li></ol><p>我们直接这么定义的参数名肯定会找不到,所以我们需要创建一个对应的环境</p><img src="https://image.xiaomo.info//blog/image-20201217145242607.png" alt="image-20201217145242607" style="zoom:50%;"><ol start="4"><li> Auth(不是必须)</li></ol><p>twitter的接口访问需要我们提供对应的auth1.0的权限,所以我们要做相关配置,如果请求的是不需要auth的api则不需要配置</p><img src="https://image.xiaomo.info//blog/image-20201217145526421.png" alt="image-20201217145526421" style="zoom:50%;"><ol start="5"><li> 测试用例</li></ol><p>完全的js语法,具体使用参考<a href="https://learning.postman.com/docs/writing-scripts/script-references/test-examples/#getting-started-with-tests" target="_blank" rel="noopener">官方示例</a></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">pm.test(<span class="string">"get user info"</span>,<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">if</span>(pm.response.to.have.status(<span class="number">200</span>)){</span><br><span class="line"> <span class="built_in">console</span>.log(pm.response.json()[<span class="string">'id'</span>] +<span class="string">','</span>+ pm.response.json()[<span class="string">'name'</span>] +<span class="string">','</span>+ pm.response.json()[<span class="string">'screen_name'</span>]);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="built_in">console</span>.log(data.provider_id + <span class="string">',404'</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://image.xiaomo.info//blog/image-20201217145602484.png" alt="image-20201217145602484"></p><p>这样我们的准备工作就做好了,然后打开上一步的runner,就可以跑测试了。</p><p>twitter1.json文件示例,数组中有多少数据iterations会自动修改。</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line">[</span><br><span class="line">{<span class="attr">"user_id"</span>:<span class="string">"G008YIJ"</span>,<span class="attr">"item_amount"</span>:<span class="string">"28648410.0"</span>,<span class="attr">"provider_id"</span>:<span class="string">"383809082"</span>},</span><br><span class="line">{<span class="attr">"user_id"</span>:<span class="string">"GWZ4NZ9"</span>,<span class="attr">"item_amount"</span>:<span class="string">"25058540.0"</span>,<span class="attr">"provider_id"</span>:<span class="string">"1159208855684800512"</span>},</span><br><span class="line">{<span class="attr">"user_id"</span>:<span class="string">"G2CAS475"</span>,<span class="attr">"item_amount"</span>:<span class="string">"8921590.0"</span>,<span class="attr">"provider_id"</span>:<span class="string">"179396017"</span>},</span><br><span class="line">{<span class="attr">"user_id"</span>:<span class="string">"GS24XV0"</span>,<span class="attr">"item_amount"</span>:<span class="string">"8860000.0"</span>,<span class="attr">"provider_id"</span>:<span class="string">"1160002494639955968"</span>},</span><br><span class="line">]</span><br></pre></td></tr></table></figure><p>也可以用csv文件的格式,但是有一个文件是provider_id是数字字符串,postman会把当成数字丢失精度,比如<code>1159208855684800512</code>会变成<code>1159208855684800000</code>,所以我将csv转成了json。<a href="https://www.aconvert.com/cn/document/csv-to-json/" target="_blank" rel="noopener">在线转换工具</a></p><p>twitter1.csv</p><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">user_id,item_amount,provider_id</span><br><span class="line">G008YIJ,28648410.0,383809082</span><br><span class="line">GWZ4NZ9,25058540.0,1159208855684800512</span><br><span class="line">G2CAS475,8921590.0,179396017</span><br><span class="line">GS24XV0,8860000.0,1160002494639955968</span><br><span class="line">GSKNUYF,8379710.0,1163803851612254208</span><br></pre></td></tr></table></figure><p><img src="https://image.xiaomo.info//blog/image-20201217150118155.png" alt="image-20201217150118155"></p><img src="https://image.xiaomo.info//blog/image-20201217150220910.png" alt="image-20201217150220910" style="zoom:50%;"><img src="https://image.xiaomo.info//blog/image-20201217150308422.png" alt="image-20201217150308422" style="zoom:50%;"><p>这里控制台打印了接口调用的详细信息和我们刚才测试用例中打印的内容</p><p>一般来说到这里就结束了,但是我们跑runner的话有可能是自己观察一下数据就好了,也有可能是需要将跑出来的结果保存下来交给市场部做分析,所以我们需要导出相关的数据,这时我们就需要postman的command line工具了。</p><h1 id="newman"><a href="#newman" class="headerlink" title="newman"></a>newman</h1><p>postman提供了npm的包,我们可以在node环境下调用,也可以全局安装后使用命令行调用。</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">yarn global add newman</span><br><span class="line">yarn global add newman-reporter-htmlextra</span><br><span class="line">yarn global add newman-reporter-html</span><br></pre></td></tr></table></figure><p><code>newman</code>提供了基本的运行环境,<code>newman-reporter-html</code>可以导出一个html的报告,但是很简单也很丑,我们可以安装一个<code>newman-reporter-htmlextra</code>来导出文件</p><p>基本的html报告</p><img src="https://image.xiaomo.info//blog/image-20201217150947515.png" alt="image-20201217150947515" style="zoom:50%;"><p>使用<code>newman-reporter-htmlextra</code>跑出来的报告,有很详细的内容报告</p><img src="https://image.xiaomo.info//blog/image-20201217151130164.png" alt="image-20201217151130164" style="zoom:50%;"><p>可以查看每个api调用的请求地址,请求头,返回头,返回体等各种信息</p><img src="https://image.xiaomo.info//blog/image-20201217151416847.png" alt="image-20201217151416847" style="zoom:50%;"><img src="https://image.xiaomo.info//blog/image-20201217151404750.png" alt="image-20201217151404750" style="zoom:50%;"><p><strong>命令格式</strong> <code>newman run collections.json -d data.json</code>, collections.json可以是一个url也可以是一个json文件</p><p>例如 </p><p>基本的html报告</p><p><code>newman run https://www.getpostman.com/collections/631643-f695cab7-6878-eb55-7943-ad88e1ccfd65-JsLv 21 -r html </code></p><p>详细的html报告</p><p> <code>newman run https://www.getpostman.com/collections/631643-f695cab7-6878-eb55-7943-ad88e1ccfd65-JsLv 21 -r htmlextra </code></p><p>文件获取方式</p><img src="https://image.xiaomo.info//blog/image-20201217151835934.png" alt="image-20201217151835934" style="zoom: 50%;"><p>作为一个精益求精的人,怎么会就这么结束。postman导出的基础报告只有简单的多少个通过了测试,多少个失败。加强版的报告虽然内容够全,但是想看数据一个一个点开麻烦的一批,所以只能这么结束吗?当然不是</p><h1 id="newman批量API调用结果导出到文件"><a href="#newman批量API调用结果导出到文件" class="headerlink" title="newman批量API调用结果导出到文件"></a>newman批量API调用结果导出到文件</h1><p><code>newman run collections.json -d data/twitter.json >result/twitter.log</code></p><p>twitter.log内容,包含了每次调用详细的数据和最后的总结,简直完美</p><figure class="highlight verilog"><table><tr><td class="code"><pre><span class="line">newman</span><br><span class="line"></span><br><span class="line">Twitter API V1</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">1</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=383809082 [200 OK, 2.36KB, 314ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">383809082</span>,naota♪,naaaota'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">2</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1159208855684800512 [200 OK, 2.05KB, 158ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1159208855684800500</span>,おず♪,vivid_oz148'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">3</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=179396017 [200 OK, 2.08KB, 169ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">179396017</span>,NEO_Taka,neo_takagi'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">4</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1160002494639955968 [200 OK, 1.91KB, 173ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1160002494639956000</span>,ういオピオイド<span class="number">11119999</span>,rZ4lGzT9BQrXUPI'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">5</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1163803851612254208 [200 OK, 1.3KB, 163ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1163803851612254200</span>,みる,M9zYkNdcbCurkT9'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">6</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=556712852 [200 OK, 1.33KB, 178ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">556712852</span>,はっす,yawning222'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">7</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1164170510185332736 [404 Not Found, 871B, 133ms]</span></span><br><span class="line"> <span class="number">1</span>. get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">8</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=87479134 [200 OK, 2.2KB, 180ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">87479134</span>,はふ,hafucco'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">9</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=352056956 [200 OK, 1.3KB, 141ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">352056956</span>,Akanishi Kakita,noah_joad'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">10</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=941532328593039360 [200 OK, 1.64KB, 174ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">941532328593039400</span>,Mikogami,Mikogami0704'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">11</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=335813584 [200 OK, 2.13KB, 169ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">335813584</span>,カイラス,kai_rasu'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">12</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1236573645314461697 [403 Forbidden, 880B, 186ms]</span></span><br><span class="line"> <span class="number">2</span>. get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">13</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1151103522865201153 [200 OK, 1.28KB, 159ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1151103522865201200</span>,Oyoneko,Oyoneko1'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">14</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1186010624037277696 [200 OK, 1.3KB, 140ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1186010624037277700</span>,ウェル,bv43vMgRlTeoxnQ'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">15</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1270185036902305792 [200 OK, 2.06KB, 181ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1270185036902305800</span>,def961,def9611'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">16</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=117064779 [200 OK, 2.05KB, 174ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">117064779</span>,Ranpuutan@ゆかりさん,Ranpuutan'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">17</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=860714567478804480 [200 OK, 2.03KB, 162ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">860714567478804500</span>,ニケタマ,mike21fire'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">18</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=975259709341970432 [200 OK, 1.86KB, 179ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">975259709341970400</span>,miki,ibajosoko'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">19</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1171681195639234560 [404 Not Found, 871B, 134ms]</span></span><br><span class="line"> <span class="number">3</span>. get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">20</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=991118408891551744 [200 OK, 1.86KB, 160ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">991118408891551700</span>,KYOUWA,KYOUWA10'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">21</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=3193700828 [200 OK, 1.75KB, 156ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">3193700828</span>,ししし,fullflatseet'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">22</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=790132781745451009 [200 OK, 1.89KB, 165ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">790132781745451000</span>,みずな,Mizuna_Project'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">23</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1186900048589647872 [200 OK, 1.29KB, 149ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1186900048589648000</span>,TestName,TestNam34718353'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">24</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=114497638 [200 OK, 1.32KB, 300ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">114497638</span>,nirubanana,nirubanana'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">25</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=2766198625 [200 OK, 1.74KB, 157ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">2766198625</span>,Schwarzschild radius,Celty3q'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">26</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1199362683419545600 [404 Not Found, 871B, 135ms]</span></span><br><span class="line"> <span class="number">4</span>. get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">27</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1179476029963784192 [200 OK, 1.32KB, 174ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1179476029963784200</span>,ムツリアン,JahmTyWWocb9qCL'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">28</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=3180572988 [200 OK, 2.12KB, 156ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">3180572988</span>,せんべろ,senbero_gamer'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">29</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=4458826284 [200 OK, 1.88KB, 151ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">4458826284</span>,goro,HapenTz'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">30</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1173473727452868613 [200 OK, 2.22KB, 154ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1173473727452868600</span>,じーだす,oymjciVHYixxsZ6'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">31</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1184806356236136449 [200 OK, 2.03KB, 156ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1184806356236136400</span>,Posarosa,Posarosa1'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">32</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=2615061966 [200 OK, 2.14KB, 185ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">2615061966</span>,Banana Fish,nananafish'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">33</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=509682027 [200 OK, 1.43KB, 216ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">509682027</span>,kichiy∧,nonnativeJap'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">34</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=14485361 [200 OK, 1.29KB, 139ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">14485361</span>,teldon,teruit'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">35</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1177481153260875776 [200 OK, 2.13KB, 155ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1177481153260875800</span>,トリスたん,Tristram1192'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">36</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=818066125531426817 [200 OK, 2.21KB, 157ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">818066125531426800</span>,IV号ねこ,Pz_Katze_IV_Q'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">37</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1172717140807303169 [200 OK, 1.92KB, 152ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1172717140807303200</span>,終電マスター,vAE2cL5iWsxiIgf'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">38</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=927875285684330496 [200 OK, 1.29KB, 243ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">927875285684330500</span>,AoS,AoSsmo'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">39</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=2881377380 [200 OK, 2.06KB, 151ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">2881377380</span>,あきもと,anikiandaneki'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">40</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=911916999810281472 [200 OK, 1.85KB, 158ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">911916999810281500</span>,misaya,misaya48436608'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">41</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=784037479317774337 [200 OK, 1.96KB, 152ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">784037479317774300</span>,岩田,ccr67280'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">42</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1218420162883284993 [200 OK, 1.29KB, 137ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1218420162883285000</span>,mai,mai72271145'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">43</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1173835954110386176 [200 OK, 2.12KB, 162ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1173835954110386200</span>,ハゲっちん,<span class="number">7</span>wG87Oh5G1QVCts'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">44</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=2605063004 [200 OK, 1.7KB, 148ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">2605063004</span>,GamersWagon,GamersWagon'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">45</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=2538078667 [200 OK, 1.65KB, 163ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">2538078667</span>,matsu,matsu090'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">46</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=2156812442 [200 OK, 2.09KB, 176ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">2156812442</span>,manabu,manabukakinoni'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">47</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1186018216964845568 [200 OK, 2.14KB, 162ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1186018216964845600</span>,木村卓実,sCP1xA8gTvlwXnG'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">48</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1127369632187240448 [200 OK, 2.08KB, 154ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1127369632187240400</span>,つなみ,okoge0273'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">49</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=262535868 [200 OK, 1.89KB, 152ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">262535868</span>,Puun,puun2'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">50</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1245943054155935745 [200 OK, 1.98KB, 159ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1245943054155935700</span>,おず♬,vivid_oz2nd'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">51</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1226802448678838272 [200 OK, 1.29KB, 151ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1226802448678838300</span>,akiaki,akiaki66305129'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">52</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=956960701 [200 OK, 1.5KB, 282ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">956960701</span>,db,joyce02_02'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">53</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1167051043542167553 [200 OK, 2.12KB, 160ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1167051043542167600</span>,どらくえ,NMJEVO9v3RwuilQ'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">54</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1167337087365873664 [200 OK, 2.07KB, 157ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1167337087365873700</span>,nappa,nappa41224809'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">55</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1150292004456112128 [200 OK, 1.92KB, 154ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1150292004456112100</span>,ムム,FomHtfuksJ9XVeZ'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">56</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1194148741013360640 [403 Forbidden, 880B, 136ms]</span></span><br><span class="line"> <span class="number">5</span>. get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">57</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1227563147428384769 [200 OK, 2.08KB, 158ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1227563147428384800</span>,牧野,JvOLq4t8FtllMAR'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">58</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1127305241492217856 [200 OK, 2.18KB, 151ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1127305241492217900</span>,ゲーム専用Satoshi,Shinya_ima194'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">59</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=828982769250246657 [200 OK, 2.04KB, 166ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">828982769250246700</span>,モトアキ,fiKKf0cgwLOXsCX'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">60</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=106433160 [200 OK, 2.09KB, 393ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">106433160</span>,Satoshi Tsuji,satoshi902'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">61</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=422274276 [200 OK, 2.14KB, 154ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">422274276</span>,AC,AC_YN'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">62</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1176675356641615872 [403 Forbidden, 880B, 130ms]</span></span><br><span class="line"> <span class="number">6</span>. get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">63</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1195927672037076992 [200 OK, 1.95KB, 163ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1195927672037077000</span>,EMMA,N1KvBK4eQNADWWn'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">64</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1158431053234626560 [200 OK, 2.11KB, 167ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1158431053234626600</span>,ラムちゃん(フレンチぶる♀),duhys7lK307gmVQ'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">65</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1923549487 [200 OK, 1.3KB, 165ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1923549487</span>,stampede,Stampede_S2000'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">66</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1190809781591691265 [200 OK, 1.89KB, 153ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1190809781591691300</span>,かとゆう,MpiSGqrD3dNZ703'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">67</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=314544319 [200 OK, 2.05KB, 160ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">314544319</span>,轟 音々(旧 いてさん),todoroki_exe'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">68</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1113741309607436288 [200 OK, 2.08KB, 154ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1113741309607436300</span>,イソD,isnaodd'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">69</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=186787348 [200 OK, 1.84KB, 157ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">186787348</span>,over0078♪,over0078'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">70</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1200922702711681024 [200 OK, 2.1KB, 157ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1200922702711681000</span>,Lucio@<span class="number">1234</span>,yani32321'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">71</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1163813572394176514 [200 OK, 2.15KB, 156ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1163813572394176500</span>,椿,wRNSJRz9pRHKhmv'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">72</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1135187807071219713 [200 OK, 2.06KB, 177ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1135187807071219700</span>,さら,K7O2hzfSNrOkZuJ'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">73</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1031751970564886528 [200 OK, 1.45KB, 356ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1031751970564886500</span>,あの,haounosiro'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">74</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1258998916864868352 [200 OK, 2.1KB, 169ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1258998916864868400</span>,kuuro,kuro999996'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">75</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1187247723533066240 [200 OK, 1.96KB, 170ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1187247723533066200</span>,<span class="number">_</span>はるぽん<span class="number">_</span>,<span class="number">_</span>harupon_kouya'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">76</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1128220521705791488 [200 OK, 1.3KB, 162ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1128220521705791500</span>,Lynx@ネコ,Lynx34894293'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">77</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1199861845575647232 [200 OK, 1.29KB, 149ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1199861845575647200</span>,Akifumi Hada,AkifumiHada'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">78</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=2793484524 [200 OK, 2.08KB, 159ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">2793484524</span>,hiro提督,hiroteitoku'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">79</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=2352469440 [200 OK, 2KB, 200ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">2352469440</span>,近藤廣行,VThiroyukikondo'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">80</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1180409321496010753 [200 OK, 2.21KB, 160ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1180409321496010800</span>,暇人,N1hJce13Gmho75K'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">81</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=579945167 [200 OK, 2.22KB, 163ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">579945167</span>,Digimortal (でじも),Digimortal2001'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">82</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1282197904094969856 [200 OK, 1.32KB, 137ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1282197904094969900</span>,将建,ZnZTUfFzpHnZrZE'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">83</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=2869864442 [200 OK, 2.09KB, 188ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">2869864442</span>,momu,subaka0000001'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">84</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=93534630 [200 OK, 2.08KB, 177ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">93534630</span>,つるりん♪,tsururinnn'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">85</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1119604247237025793 [200 OK, 1.29KB, 135ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1119604247237025800</span>,Alpha,Alpha57102127'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">86</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=988960826613612544 [200 OK, 2.12KB, 154ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">988960826613612500</span>,truth,truth86833142'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">87</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=2900033480 [200 OK, 2.01KB, 146ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">2900033480</span>,ZENLAがんばらない,L_of_ZENLA'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">88</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1186696371992055808 [200 OK, 2.03KB, 154ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1186696371992055800</span>,蛙san,san85217859'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">89</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1226406259928158208 [403 Forbidden, 880B, 137ms]</span></span><br><span class="line"> <span class="number">7</span>. get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">90</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=811963896118398976 [200 OK, 1.98KB, 153ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">811963896118399000</span>,Maple,moe_popopopon'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">91</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1033293484877213697 [200 OK, 1.64KB, 166ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1033293484877213700</span>,ムーミン谷のナウシカ,talesfromMoomin'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">92</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=2793176136 [200 OK, 1.66KB, 154ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">2793176136</span>,祥平,matsuba_280820'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">93</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1030545664369876992 [200 OK, 1.31KB, 131ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1030545664369877000</span>,みゅう,rzV5hlxZjDSFSIv'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">94</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1155592675980009472 [200 OK, 1.81KB, 165ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1155592675980009500</span>,Hiro,Hiro24987674'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">95</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=162073408 [200 OK, 1.99KB, 156ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">162073408</span>,taka,tkhrysmr'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">96</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1083045815369707520 [200 OK, 2.14KB, 164ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1083045815369707500</span>,LEX,LEX31542948'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">97</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1644434066 [200 OK, 1.74KB, 158ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1644434066</span>,tanaka yoshinori,tanakayo1219'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">98</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1230489845652307970 [200 OK, 1.31KB, 137ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1230489845652308000</span>,うまい棒,<span class="number">7</span>WPLZfkaesgSBch'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">99</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1162363771576131587 [200 OK, 1.31KB, 182ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1162363771576131600</span>,ザッハーク,FKU65HDVv88bzXk'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">100</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1169999703460601857 [200 OK, 1.58KB, 157ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1169999703460601900</span>,寝顔,ZtONUcn3bTsiu5k'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">101</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=911620768361889792 [200 OK, 1.89KB, 191ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">911620768361889800</span>,ティルフィ,QrnVwf'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">102</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=705815480376758272 [200 OK, 2.17KB, 163ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">705815480376758300</span>,RR,rariver220'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">103</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1204256721939517441 [200 OK, 1.31KB, 134ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1204256721939517400</span>,内田雅夫,C0ANaGUAJSjhVgh'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">104</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1112200902910214144 [200 OK, 2.19KB, 159ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1112200902910214100</span>,さんや,RLHghGnqdYor64h'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">105</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1108082368810610688 [200 OK, 2.09KB, 154ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1108082368810610700</span>,ゆきみん,E7url8MIA9QTiuC'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">106</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1144330044 [200 OK, 2.23KB, 153ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1144330044</span>,STAN,stan_3210'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">107</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=3271871107 [200 OK, 1.69KB, 152ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">3271871107</span>,ペッツィ,DakaraKouichi'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">108</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1168073169745219585 [200 OK, 2.04KB, 158ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1168073169745219600</span>,tamusansan,tamusansan1'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">109</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1250045897213276160 [200 OK, 1.32KB, 136ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1250045897213276200</span>,スガワラユウイチ,w9DfMw822DUtCYN'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">110</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1016730721921069056 [200 OK, 1.43KB, 138ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1016730721921069000</span>,うな,IxOgbJGgfyeUcuP'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">111</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=773396898480852992 [200 OK, 2.03KB, 164ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">773396898480853000</span>,noanoa0511,noanoa0511_0310'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">112</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=3022641198 [200 OK, 1.32KB, 196ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">3022641198</span>,pokopen,pokopenpokopen8'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">113</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=873425051072081921 [200 OK, 1.81KB, 162ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">873425051072081900</span>,G55,joke_888'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">114</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1169236669767118848 [200 OK, 1.34KB, 136ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1169236669767118800</span>,小川健太郎,<span class="number">8</span>AoZ3alAd6jc07S'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">115</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1209399275127394307 [200 OK, 1.56KB, 157ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1209399275127394300</span>,ビビッド用,ahUXJyTy6Plko38'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">116</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1318048442 [200 OK, 1.77KB, 153ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1318048442</span>,mono,<span class="number">11</span>monosann'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">117</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1029531498880294912 [200 OK, 2.43KB, 166ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1029531498880294900</span>,chorita,chorita7'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">118</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=742890983872856064 [200 OK, 1.47KB, 292ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">742890983872856000</span>,中西,o0pA0O30Qb6lN1L'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">119</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=3747318378 [200 OK, 2.03KB, 153ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">3747318378</span>,Shinji Rikimaru,rikimaru_shinji'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">120</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=267226633 [200 OK, 2.18KB, 145ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">267226633</span>,武風@面白いゲーム探してます,takeru25254546'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">121</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1191625812362969088 [200 OK, 2.13KB, 156ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1191625812362969000</span>,ゆ~き,Id9mnSNoVwg1GwO'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">122</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=876956774225420288 [200 OK, 1.79KB, 171ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">876956774225420300</span>,ADONA,ADONA76848656'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">123</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=399518422 [200 OK, 2.27KB, 159ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">399518422</span>,かずい,kazui2011'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">124</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=3825169272 [200 OK, 2.01KB, 177ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">3825169272</span>,エイギル,<span class="number">04</span>edf'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">125</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1173594475676848128 [200 OK, 2.07KB, 163ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1173594475676848000</span>,めめ,<span class="number">6</span>wRiR1UfyDQuNz3'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">126</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=215934363 [200 OK, 1.32KB, 277ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">215934363</span>,わんちゃん@趣味,wanchan_shumi'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">127</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=940195156069777408 [200 OK, 1.98KB, 179ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">940195156069777400</span>,Config,Game_config'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">128</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1146752414113886208 [200 OK, 2.11KB, 196ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1146752414113886200</span>,今日は餃子,OBFBbMfnBO9rWOI'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">129</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=428873715 [200 OK, 1.75KB, 210ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">428873715</span>,相原栄治,eizi_a'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">130</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1202688552590401536 [200 OK, 1.72KB, 484ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1202688552590401500</span>,gyakuhineri972,gyakuhineri972'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">131</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1123737372968083457 [200 OK, 1.31KB, 174ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1123737372968083500</span>,きやっか,<span class="number">9</span>L6mDmVCIjUbMKC'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">132</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=924948705970548736 [200 OK, 2.03KB, 317ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">924948705970548700</span>,kyou,kyoukyou439'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">133</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1124508044669308928 [200 OK, 1.31KB, 198ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1124508044669308900</span>,トク,L2V8u1uNFRfkJMo'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">134</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=3599805614 [200 OK, 2.26KB, 164ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">3599805614</span>,けーな,quena_k_1'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">135</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1028634319021989889 [200 OK, 2.05KB, 157ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1028634319021989900</span>,KO-G,KOG23273306'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">136</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=112220922 [200 OK, 2.04KB, 186ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">112220922</span>,燐鴬 七威 月詠,nanai_tsukuyo'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">137</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=463779133 [200 OK, 2.09KB, 568ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">463779133</span>,Aion(Yuuki),Aion373'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">138</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1164196808010059780 [200 OK, 1.84KB, 178ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1164196808010059800</span>,wea,wea16553776'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">139</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=4318771453 [200 OK, 2.29KB, 162ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">4318771453</span>,浅沼寿幸,URFxp75udMUyTP1'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">140</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=771283722859520001 [200 OK, 2.01KB, 182ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">771283722859520000</span>,もずい,mozui88'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">141</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=886412135814778882 [200 OK, 1.93KB, 176ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">886412135814778900</span>,LILIUM@みがわりMiner,LILIUM_Serval'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">142</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1251803295787544576 [200 OK, 2.08KB, 171ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1251803295787544600</span>,ぼっち猫,<span class="number">6</span>XFsv7rRCSHR7i9'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">143</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1114794534645329921 [200 OK, 1.82KB, 158ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1114794534645329900</span>,💩,encondify'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">144</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=4836191232 [200 OK, 2.08KB, 206ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">4836191232</span>,shimada,shimada_jngr'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">145</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=921842226186948609 [200 OK, 1.87KB, 155ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">921842226186948600</span>,tariki,tariki_99'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">146</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1140951313741565952 [200 OK, 1.85KB, 161ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1140951313741566000</span>,shall,l2QkEG5lvjp97Bv'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">147</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=2198575950 [200 OK, 1.92KB, 225ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">2198575950</span>,アキヒロ,O_akihiro02'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">148</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=2822230333 [200 OK, 1.91KB, 169ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">2822230333</span>,Naota,naota_naota7286'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">149</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1137811586167451648 [200 OK, 1.36KB, 213ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1137811586167451600</span>,浅井夢幻,MNQCkmHDW95qpj5'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">150</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1244470688032776192 [200 OK, 2.14KB, 175ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1244470688032776200</span>,lamf_zep,LamfZep'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">151</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=3776687893 [200 OK, 2.46KB, 196ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">3776687893</span>,華衣@ベネットP,kai_0527_'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">152</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1168877635323060226 [200 OK, 1.94KB, 174ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1168877635323060200</span>,hatatnotosiyasu,hatatnotosiyasu'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">153</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=2989548769 [200 OK, 1.69KB, 167ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">2989548769</span>,源三郎,gentheblow'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">154</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1125399463252512768 [200 OK, 1.31KB, 146ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1125399463252512800</span>,zx9r9087,zx9r9087'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">155</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=113076773 [200 OK, 2.17KB, 189ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">113076773</span>,ともぞう,hero1000_'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">156</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1001139157467516929 [200 OK, 1.81KB, 160ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1001139157467516900</span>,fujisan,FuujiSaAAN'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">157</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1081524759181942784 [404 Not Found, 871B, 170ms]</span></span><br><span class="line"> <span class="number">8</span>. get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">158</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=843709525 [200 OK, 1.65KB, 170ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">843709525</span>,ユユシィ,yuyusy21'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">159</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1113409711850762243 [200 OK, 1.69KB, 182ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1113409711850762200</span>,はと,sgzk14'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">160</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=2821507159 [200 OK, 1.92KB, 179ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">2821507159</span>,ジマール,jimarukizuna'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">161</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=3787106653 [200 OK, 1.32KB, 180ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">3787106653</span>,ジェット,RzyCtPqTclAd3SF'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">162</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1207602203113836544 [200 OK, 1.87KB, 295ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1207602203113836500</span>,とんぱち,KTcr2diW2Ja3CGc'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">163</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1145314636620652544 [200 OK, 1.3KB, 251ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1145314636620652500</span>,Shoy,l7urSDKFmB8HFOU'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">164</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1125685325982867458 [200 OK, 2.16KB, 168ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1125685325982867500</span>,†墓石†,FsLgTwEs'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">165</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1187760468275781632 [200 OK, 2.23KB, 171ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1187760468275781600</span>,胡太師,Rh4ehbLzmdKKB6m'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">166</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1159550163435708417 [200 OK, 1.89KB, 299ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1159550163435708400</span>,ぽんこつ,U8CPlKCnWY3Ww1e'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">167</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=705783507394494464 [200 OK, 2.04KB, 184ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">705783507394494500</span>,丁稚,jiji82163071'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">168</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=945647903262961664 [200 OK, 1.6KB, 166ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">945647903262961700</span>,ありんこ,arinko385'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">169</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=2199121057 [200 OK, 1.35KB, 190ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">2199121057</span>,価値が無いもの,ikimono_0000'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">170</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1115698728931713024 [200 OK, 1.74KB, 155ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1115698728931713000</span>,ごきげんよう,Gokigenyoh23'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">171</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=384210102 [200 OK, 1.83KB, 281ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">384210102</span>,白純@生きてはいる,srzm_0606'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">172</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=894882792865054722 [200 OK, 1.68KB, 473ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">894882792865054700</span>,かっとん,<span class="number">3</span>uwuDeREIVnabZx'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">173</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1270551300233498624 [403 Forbidden, 880B, 130ms]</span></span><br><span class="line"> <span class="number">9</span>. get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">174</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1046369338540736519 [200 OK, 1.39KB, 141ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1046369338540736500</span>,マヨネーズサラダ,NT0j0jZRUQMF4Th'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">175</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1116415349446942720 [200 OK, 2.08KB, 151ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1116415349446942700</span>,(´・ω・<span class="meta">`)しょぼんぬ,Ld9I10nUkJTS43g'</span></span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">176</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=996258785290158081 [200 OK, 2.08KB, 159ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">996258785290158100</span>,ねこるすきー,Necoru_cat'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">177</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=3005839968 [200 OK, 1.81KB, 156ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">3005839968</span>,トロロ・テン,tororoTHEten'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">178</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=371826755 [200 OK, 1.32KB, 143ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">371826755</span>,山岡,yamaokadesuyo'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">179</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=1208066347705331712 [200 OK, 2.1KB, 163ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">1208066347705331700</span>,アレオ,areo_No1'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">Iteration <span class="number">180</span>/<span class="number">180</span></span><br><span class="line"></span><br><span class="line">→ users/show</span><br><span class="line"> GET https:<span class="comment">//api.twitter.com/1.1/users/show.json?user_id=3278435672 [200 OK, 1.86KB, 179ms]</span></span><br><span class="line"> ┌</span><br><span class="line"> │ '<span class="number">3278435672</span>,pot,pot86333242'</span><br><span class="line"> └</span><br><span class="line"> ✓ get user info</span><br><span class="line"></span><br><span class="line">┌─────────────────────────┬────────────────────┬────────────────────┐</span><br><span class="line">│ │ executed │ failed │</span><br><span class="line">├─────────────────────────┼────────────────────┼────────────────────┤</span><br><span class="line">│ iterations │ <span class="number">180</span> │ <span class="number">0</span> │</span><br><span class="line">├─────────────────────────┼────────────────────┼────────────────────┤</span><br><span class="line">│ requests │ <span class="number">180</span> │ <span class="number">0</span> │</span><br><span class="line">├─────────────────────────┼────────────────────┼────────────────────┤</span><br><span class="line">│ test-scripts │ <span class="number">360</span> │ <span class="number">0</span> │</span><br><span class="line">├─────────────────────────┼────────────────────┼────────────────────┤</span><br><span class="line">│ prerequest-scripts │ <span class="number">360</span> │ <span class="number">0</span> │</span><br><span class="line">├─────────────────────────┼────────────────────┼────────────────────┤</span><br><span class="line">│ assertions │ <span class="number">180</span> │ <span class="number">9</span> │</span><br><span class="line">├─────────────────────────┴────────────────────┴────────────────────┤</span><br><span class="line">│ total run duration: <span class="number">37</span><span class="variable">.2s</span> │</span><br><span class="line">├───────────────────────────────────────────────────────────────────┤</span><br><span class="line">│ total data received: <span class="number">180</span><span class="variable">.71KB</span> (approx) │</span><br><span class="line">├───────────────────────────────────────────────────────────────────┤</span><br><span class="line">│ average response <span class="keyword">time</span>: <span class="number">178</span>ms [min: <span class="number">130</span>ms, max: <span class="number">568</span>ms, s<span class="variable">.d</span>.: <span class="number">59</span>ms] │</span><br><span class="line">└───────────────────────────────────────────────────────────────────┘</span><br><span class="line"></span><br><span class="line"> # failure detail </span><br><span class="line"> </span><br><span class="line"> <span class="number">1</span>. AssertionError get user info </span><br><span class="line"> iteration: <span class="number">7</span> expected response to have status code <span class="number">200</span> but got <span class="number">404</span> </span><br><span class="line"> at assertion:<span class="number">0</span> in test-script </span><br><span class="line"> <span class="keyword">inside</span> <span class="string">"users/show"</span> </span><br><span class="line"> </span><br><span class="line"> <span class="number">2</span>. AssertionError get user info </span><br><span class="line"> iteration: <span class="number">12</span> expected response to have status code <span class="number">200</span> but got <span class="number">403</span> </span><br><span class="line"> at assertion:<span class="number">0</span> in test-script </span><br><span class="line"> <span class="keyword">inside</span> <span class="string">"users/show"</span> </span><br><span class="line"> </span><br><span class="line"> <span class="number">3</span>. AssertionError get user info </span><br><span class="line"> iteration: <span class="number">19</span> expected response to have status code <span class="number">200</span> but got <span class="number">404</span> </span><br><span class="line"> at assertion:<span class="number">0</span> in test-script </span><br><span class="line"> <span class="keyword">inside</span> <span class="string">"users/show"</span> </span><br><span class="line"> </span><br><span class="line"> <span class="number">4</span>. AssertionError get user info </span><br><span class="line"> iteration: <span class="number">26</span> expected response to have status code <span class="number">200</span> but got <span class="number">404</span> </span><br><span class="line"> at assertion:<span class="number">0</span> in test-script </span><br><span class="line"> <span class="keyword">inside</span> <span class="string">"users/show"</span> </span><br><span class="line"> </span><br><span class="line"> <span class="number">5</span>. AssertionError get user info </span><br><span class="line"> iteration: <span class="number">56</span> expected response to have status code <span class="number">200</span> but got <span class="number">403</span> </span><br><span class="line"> at assertion:<span class="number">0</span> in test-script </span><br><span class="line"> <span class="keyword">inside</span> <span class="string">"users/show"</span> </span><br><span class="line"> </span><br><span class="line"> <span class="number">6</span>. AssertionError get user info </span><br><span class="line"> iteration: <span class="number">62</span> expected response to have status code <span class="number">200</span> but got <span class="number">403</span> </span><br><span class="line"> at assertion:<span class="number">0</span> in test-script </span><br><span class="line"> <span class="keyword">inside</span> <span class="string">"users/show"</span> </span><br><span class="line"> </span><br><span class="line"> <span class="number">7</span>. AssertionError get user info </span><br><span class="line"> iteration: <span class="number">89</span> expected response to have status code <span class="number">200</span> but got <span class="number">403</span> </span><br><span class="line"> at assertion:<span class="number">0</span> in test-script </span><br><span class="line"> <span class="keyword">inside</span> <span class="string">"users/show"</span> </span><br><span class="line"> </span><br><span class="line"> <span class="number">8</span>. AssertionError get user info </span><br><span class="line"> iteration: <span class="number">157</span> expected response to have status code <span class="number">200</span> but got <span class="number">404</span> </span><br><span class="line"> at assertion:<span class="number">0</span> in test-script </span><br><span class="line"> <span class="keyword">inside</span> <span class="string">"users/show"</span> </span><br><span class="line"> </span><br><span class="line"> <span class="number">9</span>. AssertionError get user info </span><br><span class="line"> iteration: <span class="number">173</span> expected response to have status code <span class="number">200</span> but got <span class="number">403</span> </span><br><span class="line"> at assertion:<span class="number">0</span> in test-script </span><br><span class="line"> <span class="keyword">inside</span> <span class="string">"users/show"</span></span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><p>不知道大家对大批量重复性的工作内容的第一反应是怎样的,我的第一反应肯定是写脚本,还得是python的。但是发现postman对批量api调用也有了较好的支持,所以便省去了写脚本的过程,直接使用命令行就可以了。</p></summary>
<category term="java" scheme="https://blog.xiaomo.info/categories/java/"/>
<category term="postman" scheme="https://blog.xiaomo.info/tags/postman/"/>
</entry>
<entry>
<title>npm优秀库使用收集整理(长期向)</title>
<link href="https://blog.xiaomo.info/2020/npmPackageCollection/"/>
<id>https://blog.xiaomo.info/2020/npmPackageCollection/</id>
<published>2020-12-08T00:00:00.000Z</published>
<updated>2022-08-10T23:53:17.651Z</updated>
<content type="html"><![CDATA[<p>虽然自栩全沾工程师,但是对于前端圈的了解还是相对缺乏的,尤其是大量的npm包。java的maven/gradle, node的npm, swift的pod,,python的pip,php的Composer,c++的<a href="https://github.com/conan-io/conan" target="_blank" rel="noopener">Conan</a>等等。基本上每一种开发语言都有自己的包管理器。开源三方库汇集了全世界的智慧结晶,有了这些优秀的三方库能够让我们很容易的完成复杂的功能。所以打算开一个长期向的优秀库的使用收集博客。大部分内容是搜索到的优秀博文整理而来。</p><a id="more"></a><p>注: 本篇文章开始于2020年11月20,每次修改或新增时都会将时间更新时最新的时间,每个库的使用也会标注版本和来源。</p><h1 id="ajv-v6-12-6"><a href="#ajv-v6-12-6" class="headerlink" title="ajv(v6.12.6)"></a>ajv(v6.12.6)</h1><p>作用 </p><p>ajv 是一个非常流行的JSON Schema验证工具,并且拥有非常出众的性能表现。下方的例子中,我们使用ajv来验证用户输入的表单数据是否合法。</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> Ajv = <span class="built_in">require</span>(<span class="string">'ajv'</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> schema = {</span><br><span class="line"> type: <span class="string">'object'</span>,</span><br><span class="line"> required: [<span class="string">'username'</span>, <span class="string">'email'</span>, <span class="string">'password'</span>],</span><br><span class="line"> properties: {</span><br><span class="line"> username: {</span><br><span class="line"> type: <span class="string">'string'</span>,</span><br><span class="line"> minLength: <span class="number">4</span></span><br><span class="line"> },</span><br><span class="line"> email: {</span><br><span class="line"> type: <span class="string">'string'</span>,</span><br><span class="line"> format: <span class="string">'email'</span></span><br><span class="line"> },</span><br><span class="line"> password: {</span><br><span class="line"> type: <span class="string">'string'</span>,</span><br><span class="line"> minLength: <span class="number">6</span></span><br><span class="line"> },</span><br><span class="line"> age: {</span><br><span class="line"> type: <span class="string">'integer'</span>,</span><br><span class="line"> minimum: <span class="number">0</span></span><br><span class="line"> },</span><br><span class="line"> sex: {</span><br><span class="line"> enum: [<span class="string">'boy'</span>, <span class="string">'girl'</span>, <span class="string">'secret'</span>],</span><br><span class="line"> <span class="keyword">default</span>: <span class="string">'secret'</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="keyword">let</span> ajv = <span class="keyword">new</span> Ajv();</span><br><span class="line"><span class="keyword">let</span> validate = ajv.compile(schema);</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> valid = validate(data);</span><br><span class="line"><span class="keyword">if</span> (!valid) <span class="built_in">console</span>.log(validate.errors);</span><br></pre></td></tr></table></figure><p>在上述代码中,我们声明了一个数据模式<code>schema</code> ,这个模式要求目标数据为一个对象,对象可以有五个字段 <code>username</code>、<code>email</code>、<code>password</code>、<code>age</code>、<code>sex</code>,并分别定义了五个字段的类型和数据格式要求,并且其中 <code>username</code>、<code>email</code>、<code>password</code> 必填。然后我们使用这个模式去验证用户输入的数据 <code>data</code> 是否满足我们的需求。</p><p>注意:</p><ol><li><p>JSON Schema 是一个声明模式描述对象的标准,并非一个库</p></li><li><p>ajv 是一个JSON Schema标准验证器的实现,除了ajv还有很多其他的库</p></li><li><p>代码中的 <code>schema</code> 是使用 JSON Schema 生成的模式描述对象</p></li><li><p> 代码中 <code>data</code> 是我们要进行检查的数据</p></li></ol><p>参考资料</p><p>JSON Schema <a href="http://json-schema.org/" target="_blank" rel="noopener">http://json-schema.org</a></p><p>AJV <a href="https://github.com/epoberezkin/ajv" target="_blank" rel="noopener">https://github.com/epoberezkin/ajv</a> </p><p>来源: <a href="https://segmentfault.com/a/1190000013265287" target="_blank" rel="noopener">https://segmentfault.com/a/1190000013265287</a> </p><h1 id="accounting-0-4-2"><a href="#accounting-0-4-2" class="headerlink" title="accounting(0.4.2)"></a>accounting(0.4.2)</h1><p>作用</p><p>accounting是用来格式化数字的库, 主要提供的方法有 <code>formatMoney()</code> <code>formatColumn()</code> <code>formatNumber()</code> <code>toFixed()</code> <code>unformat()</code></p><p>接下来我们一一介绍:</p><p>formatMoney() 格式化货币</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 默认货币格式:货币符号$,保留两位小数,每千位加逗号</span></span><br><span class="line">accounting.formatMoney(<span class="number">12345678</span>); <span class="comment">// $12,345,678.00</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 指定货币符号、保留小数位、千位间隔符</span></span><br><span class="line">accounting.formatMoney(<span class="number">12345678</span>, <span class="string">'¥'</span>, <span class="number">2</span>, <span class="string">''</span>); <span class="comment">// ¥12345678.00</span></span><br></pre></td></tr></table></figure><p>formatColumn() 格式化并按列对齐</p><p>在制表时,<code>formatColumn()</code> 方法方便我们按照表格列对齐数字和货币符号:</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">accounting.formatColumn([<span class="number">123.5</span>, <span class="number">3456.615</span>, <span class="number">777888.99</span>, <span class="number">-5432</span>, <span class="number">-1234567</span>, <span class="number">0</span>], <span class="string">"$ "</span>);</span><br></pre></td></tr></table></figure><p>格式化后的效果:</p><p><img src="https://image.xiaomo.info//blog/bV3yyE.png" alt="图片描述"></p><p>formatNumber() 格式化数字</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">accounting.formatNumber(<span class="number">5318008</span>); <span class="comment">// 5,318,008</span></span><br><span class="line">accounting.formatNumber(<span class="number">9876543.21</span>, <span class="number">3</span>, <span class="string">" "</span>); <span class="comment">// 9 876 543.210</span></span><br></pre></td></tr></table></figure><p>toFixed() 保留小数位</p><p>和JavaScript内置 <code>Number.prototype.toFixed()</code> 不同的是,<code>accounting.toFixed()</code> 有四舍五入的效果:</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">(<span class="number">0.615</span>).toFixed(<span class="number">2</span>); <span class="comment">// "0.61"</span></span><br><span class="line">accounting.toFixed(<span class="number">0.615</span>, <span class="number">2</span>); <span class="comment">// "0.62"</span></span><br></pre></td></tr></table></figure><p>unformat() 解析数字</p><p><code>unformat()</code> 方法能够从任何格式的字符串中解析出原始数字:</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">accounting.unformat(<span class="string">"£ 12,345,678.90 GBP"</span>); <span class="comment">// 12345678.9</span></span><br></pre></td></tr></table></figure><p>参考资料</p><p><a href="http://openexchangerates.github.io/accounting.js/" target="_blank" rel="noopener">http://openexchangerates.github.io/accounting.js/</a></p><p><a href="https://github.com/openexchangerates/accounting.js" target="_blank" rel="noopener">https://github.com/openexchangerates/accounting.js</a> </p><p>来源: <a href="https://segmentfault.com/a/1190000013201803" target="_blank" rel="noopener">https://segmentfault.com/a/1190000013201803</a></p><h1 id="async-retry-1-3-1"><a href="#async-retry-1-3-1" class="headerlink" title="async-retry(1.3.1)"></a>async-retry(1.3.1)</h1><p>作用</p><p>异步的执行对某个操作的重试,可以设置重试次数。</p><p>使用demo</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// Packages</span></span><br><span class="line"><span class="keyword">const</span> retry = <span class="built_in">require</span>(<span class="string">'async-retry'</span>)</span><br><span class="line"><span class="keyword">const</span> fetch = <span class="built_in">require</span>(<span class="string">'node-fetch'</span>)</span><br><span class="line"> </span><br><span class="line"><span class="keyword">await</span> retry(<span class="keyword">async</span> bail => {</span><br><span class="line"> <span class="comment">// if anything throws, we retry</span></span><br><span class="line"> <span class="keyword">const</span> res = <span class="keyword">await</span> fetch(<span class="string">'https://google.com'</span>)</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> (<span class="number">403</span> === res.status) {</span><br><span class="line"> <span class="comment">// don't retry upon 403</span></span><br><span class="line"> bail(<span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">'Unauthorized'</span>))</span><br><span class="line"> <span class="keyword">return</span></span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">const</span> data = <span class="keyword">await</span> res.text()</span><br><span class="line"> <span class="keyword">return</span> data.substr(<span class="number">0</span>, <span class="number">500</span>)</span><br><span class="line">}, {</span><br><span class="line"> retries: <span class="number">5</span></span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>说明:</p><ol><li><p> 提供的功能可以是async或不是。换句话说,它可以是返回Promise或值的函数。</p></li><li><p>提供的函数接收两个参数</p></li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Function您可以调用A以中止重试(保释)</span><br><span class="line"></span><br><span class="line">Number识别尝试。绝对的首次尝试(在重试之前)为<span class="number">1</span>。</span><br></pre></td></tr></table></figure><ol start="3"><li><p>将opts被传递到node-retry。阅读<a href="https://github.com/tim-kos/node-retry" target="_blank" rel="noopener">其文档</a></p></li><li><p> retries:重试该操作的最大次数。默认值为10。</p></li><li><p> factor:要使用的指数因子。默认值为2。</p></li><li><p> minTimeout:开始第一次重试之前的毫秒数。默认值为1000。</p></li><li><p> maxTimeout:两次重试之间的最大毫秒数。默认值为Infinity。</p></li><li><p> randomize:通过乘以介于之间的因数1来随机化超时2。默认值为true。</p></li><li><p> onRetry:可选Function,在执行新的重试后调用。它传递了Error触发它的参数。</p></li></ol><h1 id="chalk-4-1-0"><a href="#chalk-4-1-0" class="headerlink" title="chalk(4.1.0)"></a>chalk(4.1.0)</h1><p>将在终端中输出蓝色带下划线的MCC。</p><p><img src="https://image.xiaomo.info//blog/chalk.png" alt="chalk"></p><p><code>echo -e "\e[34;4mMCC\e[0m" </code></p><p>虽然我们已经学会了,在终端中控制字符颜色的原理和方法,但是这种操作太过于繁琐,每一次都需要查颜色样式手册,然后写出一堆无法阅读的火星文,抓狂!</p><p>今天介绍的NPM库chalk就是用来优雅地输出带颜色的文本,<strong>不需要记忆、查阅样式手册</strong>。</p><p>安装</p><p><code>npm install chalk </code></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> ctx = <span class="keyword">new</span> chalk.Instance({<span class="attr">level</span>: <span class="number">0</span>});</span><br></pre></td></tr></table></figure><table><thead><tr><th align="center">Level</th><th align="left">Description</th></tr></thead><tbody><tr><td align="center"><code>0</code></td><td align="left">All colors disabled</td></tr><tr><td align="center"><code>1</code></td><td align="left">Basic color support (16 colors)</td></tr><tr><td align="center"><code>2</code></td><td align="left">256 color support</td></tr><tr><td align="center"><code>3</code></td><td align="left">Truecolor support (16 million colors)</td></tr></tbody></table><p>使用</p><p>chalk 将各种颜色和样式修饰符实现为各个函数,并且支持链式调用。</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"> <span class="keyword">const</span> chalk = <span class="built_in">require</span>(<span class="string">'chalk'</span>); </span><br><span class="line"></span><br><span class="line"><span class="comment">// 输出蓝色的MCC </span></span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(chalk.blue(<span class="string">'MCC'</span>)); </span><br><span class="line"></span><br><span class="line"> <span class="comment">// 输出蓝色带下划线的MCC </span></span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(chalk.blue.underline(<span class="string">'MCC'</span>)); </span><br><span class="line"></span><br><span class="line"> <span class="comment">// 使用RGB颜色输出 </span></span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(chalk.rgb(<span class="number">4</span>, <span class="number">156</span>, <span class="number">219</span>).underline(<span class="string">'MCC'</span>)); </span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(chalk.hex(<span class="string">'#049CDB'</span>).bold(<span class="string">'MCC'</span>)); </span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(chalk.bgHex(<span class="string">'#049CDB'</span>).bold(<span class="string">'MCC'</span>));</span><br></pre></td></tr></table></figure><p>效果</p><p><img src="https://image.xiaomo.info//blog/image-20201204181923326.png" alt="image-20201204181923326"></p><p>文本样式修饰符函数</p><figure class="highlight scss"><table><tr><td class="code"><pre><span class="line">reset 重置样式 </span><br><span class="line"></span><br><span class="line">bold 加粗 </span><br><span class="line"></span><br><span class="line">dim 昏暗 </span><br><span class="line"></span><br><span class="line">italic 斜体 </span><br><span class="line"></span><br><span class="line">underline 下划线 </span><br><span class="line"></span><br><span class="line">inverse 反色 </span><br><span class="line"></span><br><span class="line">hidden 隐藏 </span><br><span class="line"></span><br><span class="line">strikethrough 删除线 </span><br><span class="line"></span><br><span class="line">visible 可见</span><br></pre></td></tr></table></figure><p> </p><p>颜色函数</p><figure class="highlight armasm"><table><tr><td class="code"><pre><span class="line"><span class="keyword">black </span> </span><br><span class="line"></span><br><span class="line"><span class="symbol">red</span> </span><br><span class="line"></span><br><span class="line"><span class="symbol">green</span> </span><br><span class="line"></span><br><span class="line"><span class="symbol">yellow</span> </span><br><span class="line"></span><br><span class="line"><span class="keyword">blue </span> </span><br><span class="line"></span><br><span class="line"><span class="symbol">magenta</span> </span><br><span class="line"></span><br><span class="line"><span class="symbol">cyan</span> </span><br><span class="line"></span><br><span class="line"><span class="symbol">white</span> </span><br><span class="line"></span><br><span class="line"><span class="symbol">gray</span> (<span class="string">"bright black"</span>) </span><br><span class="line"></span><br><span class="line"><span class="symbol">redBright</span> </span><br><span class="line"></span><br><span class="line"><span class="symbol">greenBright</span> </span><br><span class="line"></span><br><span class="line"><span class="symbol">yellowBright</span> </span><br><span class="line"></span><br><span class="line"><span class="keyword">blueBright </span> </span><br><span class="line"></span><br><span class="line"><span class="symbol">magentaBright</span> </span><br><span class="line"></span><br><span class="line"><span class="symbol">cyanBright</span> </span><br><span class="line"></span><br><span class="line"><span class="symbol">whiteBright</span></span><br></pre></td></tr></table></figure><p> </p><p>背景色函数</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">bgBlack </span><br><span class="line"></span><br><span class="line">bgRed </span><br><span class="line"></span><br><span class="line">bgGreen </span><br><span class="line"></span><br><span class="line">bgYellow </span><br><span class="line"></span><br><span class="line">bgBlue </span><br><span class="line"></span><br><span class="line">bgMagenta </span><br><span class="line"></span><br><span class="line">bgCyan </span><br><span class="line"></span><br><span class="line">bgWhite </span><br><span class="line"></span><br><span class="line">bgBlackBright </span><br><span class="line"></span><br><span class="line">bgRedBright </span><br><span class="line"></span><br><span class="line">bgGreenBright </span><br><span class="line"></span><br><span class="line">bgYellowBright </span><br><span class="line"></span><br><span class="line">bgBlueBright </span><br><span class="line"></span><br><span class="line">bgMagentaBright </span><br><span class="line"></span><br><span class="line">bgCyanBright </span><br><span class="line"></span><br><span class="line">bgWhiteBright</span><br></pre></td></tr></table></figure><p> </p><p>源码</p><p><a href="https://github.com/chalk/chalk" target="_blank" rel="noopener">https://github.com/chalk/chalk</a></p><h1 id="ora-5-1-0"><a href="#ora-5-1-0" class="headerlink" title="ora(5.1.0)"></a>ora(5.1.0)</h1><p>优雅的转圈圈,让你的等待不再煎熬~</p><p><img src="https://image.xiaomo.info//blog/ora.gif" alt="ora"></p><p><code>yarn add ora</code></p><p>使用</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> ora = <span class="built_in">require</span>(<span class="string">'ora'</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> spinner = ora(<span class="string">'Loading unicorns'</span>).start();</span><br><span class="line"></span><br><span class="line">setTimeout(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> spinner.color = <span class="string">'yellow'</span>;</span><br><span class="line"> spinner.text = <span class="string">'Loading rainbows'</span>;</span><br><span class="line">}, <span class="number">1000</span>);</span><br></pre></td></tr></table></figure><p><a href="https://github.com/sindresorhus/ora" target="_blank" rel="noopener">https://github.com/sindresorhus/ora</a></p><h1 id="figlet-1-5-0"><a href="#figlet-1-5-0" class="headerlink" title="figlet(1.5.0)"></a>figlet(1.5.0)</h1><p><code>yarn add figlet</code></p><p>使用</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">figlet(<span class="string">'Hello World!!'</span>, <span class="function"><span class="keyword">function</span>(<span class="params">err, data</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (err) {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'Something went wrong...'</span>);</span><br><span class="line"> <span class="built_in">console</span>.dir(err);</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="built_in">console</span>.log(data)</span><br><span class="line">});</span><br></pre></td></tr></table></figure><p>效果</p><p><img src="https://image.xiaomo.info//blog/image-20201204182909081.png" alt="image-20201204182909081"></p><h1 id="boxen-4-2-0"><a href="#boxen-4-2-0" class="headerlink" title="boxen(4.2.0)"></a>boxen(4.2.0)</h1><p>给你的代码画上界限,守护自己的地盘~</p><p><code>yarn add boxen</code></p><p>使用</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> boxen = <span class="built_in">require</span>(<span class="string">'boxen'</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(boxen(<span class="string">'unicorn'</span>, {<span class="attr">padding</span>: <span class="number">1</span>}));</span><br></pre></td></tr></table></figure><p>效果</p><p><img src="https://image.xiaomo.info//blog/image-20201204183307440.png" alt="image-20201204183307440"></p><p><a href="https://github.com/sindresorhus/boxen" target="_blank" rel="noopener">https://github.com/sindresorhus/boxen</a></p><h1 id="classnames(2-2-6)"><a href="#classnames(2-2-6)" class="headerlink" title="classnames(2.2.6)"></a>classnames(2.2.6)</h1><p>快速的组合class name</p><p>在前端开发中,我们经常需要JS来判断生成DOM节点CSS类,比如:</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">let</span> className=<span class="string">'btn-primary'</span>;</span><br><span class="line"><span class="keyword">if</span>(active){</span><br><span class="line"> className+=<span class="string">' active'</span>;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">return</span> <span class="xml"><span class="tag"><<span class="name">div</span> <span class="attr">className</span>=<span class="string">{className}</span>></span>Save<span class="tag"></<span class="name">div</span>></span></span>;</span><br></pre></td></tr></table></figure><p>在上述代码中,我们需要判断active变量来控制生成的按钮的CSS样式是否是激活状态,在实际开发中,可能会有更多的类似这样的样式控制逻辑,从而干扰阅读业务逻辑代码,使得代码变得很“脏”。<br>classnames<br>classnames 库对CSS样式类操作进行了封装,方便我们快速使用:</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> classNames = <span class="built_in">require</span>(<span class="string">'classnames'</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">//...</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> <span class="xml"><span class="tag"><<span class="name">div</span> <span class="attr">className</span>=<span class="string">{classNames(</span>'<span class="attr">btn-primary</span>',{ <span class="attr">active</span> })}></span>Save<span class="tag"></<span class="name">div</span>></span></span>;</span><br></pre></td></tr></table></figure><p>更多调用方式:</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">classNames(<span class="string">'foo'</span>, <span class="string">'bar'</span>); <span class="comment">// => 'foo bar' </span></span><br><span class="line">classNames(<span class="string">'foo'</span>, { <span class="attr">bar</span>: <span class="literal">true</span> }); <span class="comment">// => 'foo bar' </span></span><br><span class="line">classNames({ <span class="string">'foo-bar'</span>: <span class="literal">true</span> }); <span class="comment">// => 'foo-bar' </span></span><br><span class="line">classNames({ <span class="string">'foo-bar'</span>: <span class="literal">false</span> }); <span class="comment">// => '' </span></span><br><span class="line">classNames({ <span class="attr">foo</span>: <span class="literal">true</span> }, { <span class="attr">bar</span>: <span class="literal">true</span> }); <span class="comment">// => 'foo bar' </span></span><br><span class="line">classNames({ <span class="attr">foo</span>: <span class="literal">true</span>, <span class="attr">bar</span>: <span class="literal">true</span> }); <span class="comment">// => 'foo bar'</span></span><br></pre></td></tr></table></figure><p><a href="https://github.com/JedWatson/classnames" target="_blank" rel="noopener">https://github.com/JedWatson/classnames</a></p><h1 id="concurrently(5-3-0)"><a href="#concurrently(5-3-0)" class="headerlink" title="concurrently(5.3.0)"></a>concurrently(5.3.0)</h1><p>主要是方便我们写前端工程化的时候,我们可以同时启动多个命令用。<br>比如我的前端代码运行起来,既要有一个web工程,同时又要启动一个mock进程。这时候我们就可以使用这个并行解决方案。<br>文档地址:<a href="https://www.npmjs.com/package/concurrently" target="_blank" rel="noopener">https://www.npmjs.com/package/concurrently</a><br>github地址:<a href="https://github.com/kimmobrunfeldt/concurrently" target="_blank" rel="noopener">https://github.com/kimmobrunfeldt/concurrently</a></p><p>全局安装<br><code>npm install concurrently -g</code></p><p>项目安装<br><code>npm install concurrently -D</code></p><p>以上果是开发阶段写工具用的时候的安装方法。<br>如果比如node项目在生产环境使用则用以下安装方法:<br><code>npm install concurrently -s</code></p><p>三、使用方法<br>3.1 命令行使用方式<br>语法:<br><code>concurrently "command1 arg" "command2 arg"</code></p><p>比如我要启动两个node程序:</p><p>然后当我们ctrl + c后,他就会把两个进程都停止了。</p><p>方式1:<br>如果我们在package.json里面则要注意引号的问题:<br><code>"start": "concurrently \"command1 arg\" \"command2 arg\""</code><br>就是将”变成".<br>方式2:</p><p>然后我们要让这个进行并行有两个方式,在命令行执行:</p><p>或者:</p><p>这块-n之后的两个单词,只能用,间隔,不能加空格。<br>方式三:<br>如果我们的package.json里面有以下三个watch类型的</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line"> <span class="attr">"scripts"</span>: {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> <span class="attr">"watch-js"</span>: <span class="string">"..."</span>,</span><br><span class="line"> <span class="attr">"watch-css"</span>: <span class="string">"..."</span>,</span><br><span class="line"> <span class="attr">"watch-node"</span>: <span class="string">"..."</span>,</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> },</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>那可以批量执行的方式:<br><code>concurrently "npm:watch-*"</code></p><p>我们可以书写一个node程序来调用concurrently。<br>我们写一个main.js的代码</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> concurrently = <span class="built_in">require</span>(<span class="string">'concurrently'</span>);</span><br><span class="line"></span><br><span class="line">concurrently([<span class="string">'npm:index'</span>, <span class="string">"npm:hello"</span>]).then(<span class="function"><span class="params">()</span>=></span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"success"</span>);</span><br><span class="line">}, ()=>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"fail"</span>)</span><br><span class="line">});</span><br></pre></td></tr></table></figure><p>然后它的效果,就是<code>concurrently "npm:index" "npm:hello"</code></p><p>然后当我们按了ctrl + c之后,他会打出success.</p><p>上面的index和hello对应的代码是:<br>index:</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> koa = <span class="built_in">require</span>(<span class="string">'koa'</span>);</span><br><span class="line"><span class="keyword">var</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> app = <span class="keyword">new</span> koa();</span><br><span class="line"></span><br><span class="line">app.use(<span class="keyword">async</span> (ctx, next)=>{</span><br><span class="line"> ctx.body = (<span class="keyword">await</span> axios.get(<span class="string">"http://127.0.0.1:8001/hello"</span>)).data;</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line">app.listen(<span class="number">8000</span>)</span><br><span class="line">hello:</span><br><span class="line"><span class="keyword">var</span> koa = <span class="built_in">require</span>(<span class="string">'koa'</span>);</span><br><span class="line"></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">app.use(<span class="function">(<span class="params">ctx, next</span>)=></span>{</span><br><span class="line"> ctx.body = <span class="string">'hello world2'</span>;</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line">app.listen(<span class="number">8001</span>)</span><br></pre></td></tr></table></figure><p>3.2.2 构建失败场景<br>我们修改上面的hello的代码:</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// hello:</span></span><br><span class="line"><span class="keyword">var</span> koa = <span class="built_in">require</span>(<span class="string">'koa'</span>);</span><br><span class="line"></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">app.use(<span class="function">(<span class="params">ctx, next</span>)=></span>{</span><br><span class="line"> ctx.body = <span class="string">'hello world2'</span>;</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line">process.exit(<span class="number">-1</span>);</span><br></pre></td></tr></table></figure><p>然后我们在运行”node ./main.js”<br>我们会看到报错,但是程序不会退出。</p><p>然后当我们再按ctrl + c后,效果如下:</p><p>所以得出一个结论:当我们有一个进程返回失败的话,总体会进入fail的callback中。</p><p>应用场景</p><p>开发工具<br>比如当我们跑npm run start的时候,我们同时需要让sass编译,同时webpack也要跑hot 模式,则这个使用可以使用concurrently运行这两个command。<br>这块我们可以查看grafana里面的前端代码中,关于工具这块代码查看一下就可以看到这块的使用。<br>grafana地址: <a href="https://github.com/grafana/grafana" target="_blank" rel="noopener">https://github.com/grafana/grafana</a></p><p>常见需求</p><p>当一个进程起来失败,整体concurrently失败?</p><p>我们可以通过killOthers这个参数来解决。<br>这样当我们有一个程序失败,则直接进入fail callback。也就把所有进程关闭了。<br>6.2 当一个进程起来失败,但是有可能是跟另外进程有关,这时候需要尝试几次?</p><p>这种情况,如上比如npm:index能启动的,npm:hello不能启动,虽然试了三次npm:hello,一直失败,但整体最终不会退出,只有我们按ctrl + c才有用,相当于killOthers就失效了。</p><h1 id="cors-2-8-5"><a href="#cors-2-8-5" class="headerlink" title="cors(2.8.5)"></a>cors(2.8.5)</h1><p>CORS是一个node.js软件包,用于提供可用于通过各种选项启用<a href="http://en.wikipedia.org/wiki/Cross-origin_resource_sharing" target="_blank" rel="noopener">CORS</a>的<a href="http://www.senchalabs.org/connect/" target="_blank" rel="noopener">Connect</a> / <a href="http://expressjs.com/" target="_blank" rel="noopener">Express</a>中间件。</p><p>安装</p><p><code>yarn add cors</code></p><p>开启所有的CORS</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> express = <span class="built_in">require</span>(<span class="string">'express'</span>)</span><br><span class="line"><span class="keyword">var</span> cors = <span class="built_in">require</span>(<span class="string">'cors'</span>)</span><br><span class="line"><span class="keyword">var</span> app = express()</span><br><span class="line"> </span><br><span class="line">app.use(cors())</span><br><span class="line"> </span><br><span class="line">app.get(<span class="string">'/products/:id'</span>, <span class="function"><span class="keyword">function</span> (<span class="params">req, res, next</span>) </span>{</span><br><span class="line"> res.json({<span class="attr">msg</span>: <span class="string">'This is CORS-enabled for all origins!'</span>})</span><br><span class="line">})</span><br><span class="line"> </span><br><span class="line">app.listen(<span class="number">80</span>, <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'CORS-enabled web server listening on port 80'</span>)</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>开启单路由的CORS</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> express = <span class="built_in">require</span>(<span class="string">'express'</span>)</span><br><span class="line"><span class="keyword">var</span> cors = <span class="built_in">require</span>(<span class="string">'cors'</span>)</span><br><span class="line"><span class="keyword">var</span> app = express()</span><br><span class="line"> </span><br><span class="line">app.get(<span class="string">'/products/:id'</span>, cors(), <span class="function"><span class="keyword">function</span> (<span class="params">req, res, next</span>) </span>{</span><br><span class="line"> res.json({<span class="attr">msg</span>: <span class="string">'This is CORS-enabled for a Single Route'</span>})</span><br><span class="line">})</span><br><span class="line"> </span><br><span class="line">app.listen(<span class="number">80</span>, <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'CORS-enabled web server listening on port 80'</span>)</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>配置CORS</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> express = <span class="built_in">require</span>(<span class="string">'express'</span>)</span><br><span class="line"><span class="keyword">var</span> cors = <span class="built_in">require</span>(<span class="string">'cors'</span>)</span><br><span class="line"><span class="keyword">var</span> app = express()</span><br><span class="line"> </span><br><span class="line"><span class="keyword">var</span> corsOptions = {</span><br><span class="line"> origin: <span class="string">'http://example.com'</span>,</span><br><span class="line"> optionsSuccessStatus: <span class="number">200</span> <span class="comment">// some legacy browsers (IE11, various SmartTVs) choke on 204</span></span><br><span class="line">}</span><br><span class="line"> </span><br><span class="line">app.get(<span class="string">'/products/:id'</span>, cors(corsOptions), <span class="function"><span class="keyword">function</span> (<span class="params">req, res, next</span>) </span>{</span><br><span class="line"> res.json({<span class="attr">msg</span>: <span class="string">'This is CORS-enabled for only example.com.'</span>})</span><br><span class="line">})</span><br><span class="line"> </span><br><span class="line">app.listen(<span class="number">80</span>, <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'CORS-enabled web server listening on port 80'</span>)</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>配置多个跨域白名单</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> express = <span class="built_in">require</span>(<span class="string">'express'</span>)</span><br><span class="line"><span class="keyword">var</span> cors = <span class="built_in">require</span>(<span class="string">'cors'</span>)</span><br><span class="line"><span class="keyword">var</span> app = express()</span><br><span class="line"> </span><br><span class="line"><span class="keyword">var</span> whitelist = [<span class="string">'http://example1.com'</span>, <span class="string">'http://example2.com'</span>]</span><br><span class="line"><span class="keyword">var</span> corsOptions = {</span><br><span class="line"> origin: <span class="function"><span class="keyword">function</span> (<span class="params">origin, callback</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (whitelist.indexOf(origin) !== <span class="number">-1</span>) {</span><br><span class="line"> callback(<span class="literal">null</span>, <span class="literal">true</span>)</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> callback(<span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">'Not allowed by CORS'</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.get(<span class="string">'/products/:id'</span>, cors(corsOptions), <span class="function"><span class="keyword">function</span> (<span class="params">req, res, next</span>) </span>{</span><br><span class="line"> res.json({<span class="attr">msg</span>: <span class="string">'This is CORS-enabled for a whitelisted domain.'</span>})</span><br><span class="line">})</span><br><span class="line"> </span><br><span class="line">app.listen(<span class="number">80</span>, <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'CORS-enabled web server listening on port 80'</span>)</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>如果您不想阻止REST工具或服务器到服务器的请求,<code>!origin</code>请在源函数中添加一个检查,如下所示:</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> corsOptions = {</span><br><span class="line"> origin: <span class="function"><span class="keyword">function</span> (<span class="params">origin, callback</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (whitelist.indexOf(origin) !== <span class="number">-1</span> || !origin) {</span><br><span class="line"> callback(<span class="literal">null</span>, <span class="literal">true</span>)</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> callback(<span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">'Not allowed by CORS'</span>))</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>异步CORS</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> express = <span class="built_in">require</span>(<span class="string">'express'</span>)</span><br><span class="line"><span class="keyword">var</span> cors = <span class="built_in">require</span>(<span class="string">'cors'</span>)</span><br><span class="line"><span class="keyword">var</span> app = express()</span><br><span class="line"> </span><br><span class="line"><span class="keyword">var</span> whitelist = [<span class="string">'http://example1.com'</span>, <span class="string">'http://example2.com'</span>]</span><br><span class="line"><span class="keyword">var</span> corsOptionsDelegate = <span class="function"><span class="keyword">function</span> (<span class="params">req, callback</span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> corsOptions;</span><br><span class="line"> <span class="keyword">if</span> (whitelist.indexOf(req.header(<span class="string">'Origin'</span>)) !== <span class="number">-1</span>) {</span><br><span class="line"> corsOptions = { <span class="attr">origin</span>: <span class="literal">true</span> } <span class="comment">// reflect (enable) the requested origin in the CORS response</span></span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> corsOptions = { <span class="attr">origin</span>: <span class="literal">false</span> } <span class="comment">// disable CORS for this request</span></span><br><span class="line"> }</span><br><span class="line"> callback(<span class="literal">null</span>, corsOptions) <span class="comment">// callback expects two parameters: error and options</span></span><br><span class="line">}</span><br><span class="line"> </span><br><span class="line">app.get(<span class="string">'/products/:id'</span>, cors(corsOptionsDelegate), <span class="function"><span class="keyword">function</span> (<span class="params">req, res, next</span>) </span>{</span><br><span class="line"> res.json({<span class="attr">msg</span>: <span class="string">'This is CORS-enabled for a whitelisted domain.'</span>})</span><br><span class="line">})</span><br><span class="line"> </span><br><span class="line">app.listen(<span class="number">80</span>, <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'CORS-enabled web server listening on port 80'</span>)</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p><a href="https://github.com/expressjs/cors#readme" target="_blank" rel="noopener">https://github.com/expressjs/cors#readme</a></p><h1 id="cross-env(7-0-3)"><a href="#cross-env(7-0-3)" class="headerlink" title="cross-env(7.0.3)"></a>cross-env(7.0.3)</h1><p><code>NODE_ENV=production</code>像这样设置环境变量时,大多数Windows命令提示符找不到命令,我们使用跨平台环境时能够抹平这个不同。</p><p>安装</p><p><code>yarn add corss-env</code></p><p>使用</p><p>直接在原来的脚本前加上<code>corss-env</code></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="string">"scripts"</span>: {</span><br><span class="line"> <span class="string">"build"</span>: <span class="string">"cross-env NODE_ENV=production webpack --config build/webpack.config.js"</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h1 id="cssnao-4-1-10"><a href="#cssnao-4-1-10" class="headerlink" title="cssnao(4.1.10)"></a>cssnao(4.1.10)</h1><p>什么是缩减(minification)?<a href="https://www.cssnano.cn/docs/introduction#%E4%BB%80%E4%B9%88%E6%98%AF%E7%BC%A9%E5%87%8F%EF%BC%88minification%EF%BC%89%EF%BC%9F" target="_blank" rel="noopener">#</a></p><p>缩减(minification)是指利用各种方法来 减少代码体积的过程。和 gzip 之类的保留 CSS 文件的原始语义(即无损失)的技术不同,缩减(minification) 天生是一个有损失的过程,例如,其中某些值可能会被替换为更简化的 等价语法,或者选择器被合并。</p><p>缩减(minification)步骤的最终结果是生成的代码将与 原始代码行为相同,但是某些部分被修改以 尽可能减少代码体积。</p><p>将 gzip 压缩和缩减(minification)相结合,可以最大限度的减少 文件体积,但是不要耳听为虚、眼见为实,为什么不去试试 <a href="https://npmjs.org/package/css-size" target="_blank" rel="noopener">css-size</a> ? css-size 是一个专门对比缩减(minification)前后文件体积大小变化的模块。</p><p>cssnano 是什么?<a href="https://www.cssnano.cn/docs/introduction#cssnano-%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F" target="_blank" rel="noopener">#</a></p><p>cssnano 就是这样的一个缩减器,它使基于 <a href="https://nodejs.org/" target="_blank" rel="noopener">Node.js</a> 开发的。cssnano 是一个 <a href="http://postcss.org/" target="_blank" rel="noopener">PostCSS</a> 插件,可以添加到你的构建流程中,用于确保最终生成的 用于生产环境的 CSS 样式表文件尽可能的小。</p><p>如果你不了解什么是构建流程,没关系,我们在 <a href="https://www.cssnano.cn/guides/getting-started" target="_blank" rel="noopener">入门指南</a> 中做了讲解。</p><p>这对我有什么好处?<a href="https://www.cssnano.cn/docs/introduction#%E8%BF%99%E5%AF%B9%E6%88%91%E6%9C%89%E4%BB%80%E4%B9%88%E5%A5%BD%E5%A4%84%EF%BC%9F" target="_blank" rel="noopener">#</a></p><p>大量的代码优化<a href="https://www.cssnano.cn/docs/introduction#%E5%A4%A7%E9%87%8F%E7%9A%84%E4%BB%A3%E7%A0%81%E4%BC%98%E5%8C%96" target="_blank" rel="noopener">#</a></p><p>我们提供了众多不同的优化,从简单的 清除空白符到复杂的不同名称的同一 keyframes 的合并等。 更多信息请参考 <a href="https://www.cssnano.cn/guides/presets" target="_blank" rel="noopener">预设指南</a>。</p><p>统一的 CSS 处理<a href="https://www.cssnano.cn/docs/introduction#%E7%BB%9F%E4%B8%80%E7%9A%84-css-%E5%A4%84%E7%90%86" target="_blank" rel="noopener">#</a></p><p>cssnano 基于 <a href="http://postcss.org/" target="_blank" rel="noopener">PostCSS</a> 来处理 CSS 代码。因为很多 现代化的 CSS 工具都是基于 <a href="http://postcss.org/" target="_blank" rel="noopener">PostCSS</a> 开发的,因此你可以把这些工具组合起来 并生成一棵单一的抽象语法树(AST)。这就意味着总的处理时间 减少了,因为 CSS 不再需要进行多次解析了。</p><p>现代化的架构以及模块化<a href="https://www.cssnano.cn/docs/introduction#%E7%8E%B0%E4%BB%A3%E5%8C%96%E7%9A%84%E6%9E%B6%E6%9E%84%E4%BB%A5%E5%8F%8A%E6%A8%A1%E5%9D%97%E5%8C%96" target="_blank" rel="noopener">#</a></p><p>因为 cssnano 是基于 <a href="http://postcss.org/" target="_blank" rel="noopener">PostCSS</a> 的,因此我们可以将 cssnano 的功能拆解为多个 插件,每个插件只需负责执行一项小的优化即可。并且许多优化可以限定 到某一组特定的 CSS 属性上,这就比利用正则表达式对 CSS 做全局处理更加安全。</p><h1 id="dotenv-8-2-0"><a href="#dotenv-8-2-0" class="headerlink" title="dotenv(8.2.0)"></a>dotenv(8.2.0)</h1><p>我们经常需要Node.js程序运行时加载不同的配置,比如开发环境和生产环境的数据数据库配置就可能不一样,使process.env.DB_HOST 环境变量,可以在Node.js程序内部方便获取参数信息。但是,程序启动时,怎样将环境变量传递给程序,这可能会是一个相对麻烦的事情,因为这关系到操作系统层的配置问题。</p><p>安装</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># with npm </span></span><br><span class="line">npm install dotenv</span><br><span class="line"> </span><br><span class="line"><span class="comment"># or with Yarn </span></span><br><span class="line">yarn add dotenv</span><br></pre></td></tr></table></figure><p>配置文件</p><p>在项目根路径下新建<code>.env</code>文件</p><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">DB_HOST=localhost</span><br><span class="line">DB_USER=root</span><br><span class="line">DB_PASS=s1mpl3</span><br></pre></td></tr></table></figure><p>然后,在Node.js程序启动时运行:</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="built_in">require</span>(<span class="string">'dotenv'</span>).config()</span><br></pre></td></tr></table></figure><p>接着,我们就可以在接下来的程序中方便地使用环境变量了:</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> db = <span class="built_in">require</span>(<span class="string">'db'</span>)</span><br><span class="line">db.connect({</span><br><span class="line"> host: process.env.DB_HOST,</span><br><span class="line"> username: process.env.DB_USER,</span><br><span class="line"> password: process.env.DB_PASS</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>因此,我们可以创建不同的配置文件并提并git,然后在开发的时候copy一份<code>local.env</code>到根路径重命名为<code>.env</code>就可以做本地开发了,线上部署同理。</p><p><img src="https://image.xiaomo.info//blog/image-20201204190948866.png" alt="image-20201204190948866"></p><p><a href="https://github.com/motdotla/dotenv#readme" target="_blank" rel="noopener">https://github.com/motdotla/dotenv#readme</a></p><h1 id="ini-1-3-5"><a href="#ini-1-3-5" class="headerlink" title="ini(1.3.5)"></a>ini(1.3.5)</h1><figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="comment">; this comment is being ignored</span></span><br><span class="line"><span class="attr">scope</span> = global</span><br><span class="line"></span><br><span class="line"><span class="section">[database]</span></span><br><span class="line"><span class="attr">user</span> = dbuser</span><br><span class="line"><span class="attr">password</span> = dbpassword</span><br><span class="line"><span class="attr">database</span> = use_this_database</span><br><span class="line"></span><br><span class="line"><span class="section">[paths.default]</span></span><br><span class="line"><span class="attr">datadir</span> = /var/lib/data</span><br><span class="line"><span class="attr">array[]</span> = first value</span><br><span class="line"><span class="attr">array[]</span> = second value</span><br><span class="line"><span class="attr">array[]</span> = third value</span><br></pre></td></tr></table></figure><p>读取ini文件</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> ini = <span class="built_in">require</span>(<span class="string">'ini'</span>);</span><br><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">'fs'</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> config = ini.parse(fs.readFileSync(<span class="string">'config.ini'</span>, <span class="string">'utf-8'</span>));</span><br></pre></td></tr></table></figure><p>写入ini文件</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> fs = <span class="built_in">require</span>(<span class="string">'fs'</span>)</span><br><span class="line"> , ini = <span class="built_in">require</span>(<span class="string">'ini'</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> config = ini.parse(fs.readFileSync(<span class="string">'./config.ini'</span>, <span class="string">'utf-8'</span>))</span><br><span class="line"></span><br><span class="line">config.scope = <span class="string">'local'</span></span><br><span class="line">config.database.database = <span class="string">'use_another_database'</span></span><br><span class="line">config.paths.default.tmpdir = <span class="string">'/tmp'</span></span><br><span class="line"><span class="keyword">delete</span> config.paths.default.datadir</span><br><span class="line">config.paths.default.array.push(<span class="string">'fourth value'</span>)</span><br><span class="line"></span><br><span class="line">fs.writeFileSync(<span class="string">'./config_modified.ini'</span>, ini.stringify(config, { <span class="attr">section</span>: <span class="string">'section'</span> }))</span><br></pre></td></tr></table></figure><p><a href="https://github.com/npm/ini" target="_blank" rel="noopener">https://github.com/npm/ini</a></p><h1 id="ip-1-1-5"><a href="#ip-1-1-5" class="headerlink" title="ip(1.1.5)"></a>ip(1.1.5)</h1><p>ip库能够获取本机IP地址、比较、转换、掩码/子网计算等各种和网络IP相关的操作:</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> ip = <span class="built_in">require</span>(<span class="string">'ip'</span>);</span><br><span class="line"><span class="comment">// 获取本机网卡IP</span></span><br><span class="line">ip.address();</span><br><span class="line"><span class="comment">// 比较两个IP是否相同</span></span><br><span class="line">ip.isEqual(<span class="string">'::1'</span>, <span class="string">'::0:1'</span>); <span class="comment">// true </span></span><br><span class="line"><span class="comment">// IP 表示格式互转</span></span><br><span class="line">ip.toBuffer(<span class="string">'127.0.0.1'</span>) <span class="comment">// Buffer([127, 0, 0, 1]) </span></span><br><span class="line">ip.toString(<span class="keyword">new</span> Buffer([<span class="number">127</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">1</span>])) <span class="comment">// 127.0.0.1 </span></span><br><span class="line">ip.toLong(<span class="string">'127.0.0.1'</span>); <span class="comment">// 2130706433 </span></span><br><span class="line">ip.fromLong(<span class="number">2130706433</span>); <span class="comment">// '127.0.0.1' </span></span><br><span class="line"><span class="comment">// 判断是否是内网IP</span></span><br><span class="line">ip.isPrivate(<span class="string">'127.0.0.1'</span>) <span class="comment">// true </span></span><br><span class="line"><span class="comment">// 判断IP版本</span></span><br><span class="line">ip.isV4Format(<span class="string">'127.0.0.1'</span>); <span class="comment">// true </span></span><br><span class="line">ip.isV6Format(<span class="string">'::ffff:127.0.0.1'</span>); <span class="comment">// true </span></span><br><span class="line"><span class="comment">// 掩码计算</span></span><br><span class="line">ip.fromPrefixLen(<span class="number">24</span>) <span class="comment">// 255.255.255.0 </span></span><br><span class="line">ip.mask(<span class="string">'192.168.1.134'</span>, <span class="string">'255.255.255.0'</span>) <span class="comment">// 192.168.1.0 </span></span><br><span class="line">ip.cidr(<span class="string">'192.168.1.134/26'</span>) <span class="comment">// 192.168.1.128 </span></span><br><span class="line">ip.not(<span class="string">'255.255.255.0'</span>) <span class="comment">// 0.0.0.255 </span></span><br><span class="line">ip.or(<span class="string">'192.168.1.134'</span>, <span class="string">'0.0.0.255'</span>) <span class="comment">// 192.168.1.255 </span></span><br><span class="line"><span class="comment">// 子网计算</span></span><br><span class="line">ip.subnet(<span class="string">'192.168.1.134'</span>, <span class="string">'255.255.255.192'</span>);</span><br><span class="line"><span class="comment">// { networkAddress: '192.168.1.128', </span></span><br><span class="line"><span class="comment">// firstAddress: '192.168.1.129', </span></span><br><span class="line"><span class="comment">// lastAddress: '192.168.1.190', </span></span><br><span class="line"><span class="comment">// broadcastAddress: '192.168.1.191', </span></span><br><span class="line"><span class="comment">// subnetMask: '255.255.255.192', </span></span><br><span class="line"><span class="comment">// subnetMaskLength: 26, </span></span><br><span class="line"><span class="comment">// numHosts: 62, </span></span><br><span class="line"><span class="comment">// length: 64, </span></span><br><span class="line"><span class="comment">// contains: function(addr){...} } </span></span><br><span class="line"><span class="comment">// 子网范围判断</span></span><br><span class="line">ip.cidrSubnet(<span class="string">'192.168.1.134/26'</span>).contains(<span class="string">'192.168.1.190'</span>) <span class="comment">// true</span></span><br></pre></td></tr></table></figure><p>参考资料</p><p><a href="https://github.com/indutny/node-ip" target="_blank" rel="noopener">https://github.com/indutny/node-ip</a></p><h1 id="jschardet(2-2-1)"><a href="#jschardet(2-2-1)" class="headerlink" title="jschardet(2.2.1)"></a>jschardet(2.2.1)</h1><p>识别字符编码</p><p><code>yarn add jschardet</code></p><p>jschardet 可以识别出一个Buffer数据所使用的编码格式,具体支持的格式包括:</p><ul><li> Big5, GB2312/GB18030, EUC-TW, HZ-GB-2312, and ISO-2022-CN (Traditional and Simplified Chinese)</li><li> EUC-JP, SHIFT_JIS, and ISO-2022-JP (Japanese)</li><li> EUC-KR and ISO-2022-KR (Korean)</li><li> KOI8-R, MacCyrillic, IBM855, IBM866, ISO-8859-5, and windows-1251 (Russian)</li><li> ISO-8859-2 and windows-1250 (Hungarian)</li><li> ISO-8859-5 and windows-1251 (Bulgarian)</li><li> windows-1252</li><li> ISO-8859-7 and windows-1253 (Greek)</li><li> ISO-8859-8 and windows-1255 (Visual and Logical Hebrew)</li><li> TIS-620 (Thai)</li><li> UTF-32 BE, LE, 3412-ordered, or 2143-ordered (with a BOM)</li><li> UTF-16 BE or LE (with a BOM)</li><li> UTF-8 (with or without a BOM)</li><li> ASCII</li></ul><p>我们能方便地使用 jschardet 库:</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> jschardet = <span class="built_in">require</span>(<span class="string">"jschardet"</span>);</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// "次常用國字標準字體表" in Big5</span></span><br><span class="line"> jschardet.detect(<span class="string">"\xa6\xb8\xb1\x60\xa5\xce\xb0\xea\xa6\x72\xbc\xd0\xb7\xc7\xa6\x72\xc5\xe9\xaa\xed"</span>);</span><br><span class="line"> <span class="comment">// { encoding: "Big5", confidence: 0.99 }</span></span><br></pre></td></tr></table></figure><p><a href="https://github.com/aadsm/jschardet" target="_blank" rel="noopener">https://github.com/aadsm/jschardet</a></p><h1 id="iconv-lite-0-6-2"><a href="#iconv-lite-0-6-2" class="headerlink" title="iconv-lite(0.6.2)"></a>iconv-lite(0.6.2)</h1><p>将转换任意的字符编码到JavaScript内置的Unicode编码,以便于我们的程序和外部系统友好对接。</p><ul><li> 所有node.js本机编码:utf8,ucs2 / utf16-le,ASCII,二进制,base64,十六进制。</li><li> 其他Unicode编码:utf16,utf16-be,utf-7,utf-7-imap,utf32,utf32-le和utf32-be。</li><li> 所有广泛的单字节编码:Windows 125x家族,ISO-8859家族,IBM / DOS代码页,Macintosh家族,KOI8家族,以及iconv库支持的所有其他字符。还支持“ latin1”,“ us-ascii”之类的别名。</li><li> 所有广泛使用的多字节编码:CP932,CP936,CP949,CP950,GB2312,GBK,GB18030,Big5,Shift_JIS,EUC-JP。</li></ul><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> iconv = <span class="built_in">require</span>(<span class="string">'iconv-lite'</span>);</span><br><span class="line"> </span><br><span class="line"><span class="comment">// Convert from an encoded buffer to a js string.</span></span><br><span class="line">str = iconv.decode(Buffer.from([<span class="number">0x68</span>, <span class="number">0x65</span>, <span class="number">0x6c</span>, <span class="number">0x6c</span>, <span class="number">0x6f</span>]), <span class="string">'win1251'</span>);</span><br><span class="line"> </span><br><span class="line"><span class="comment">// Convert from a js string to an encoded buffer.</span></span><br><span class="line">buf = iconv.encode(<span class="string">"Sample input string"</span>, <span class="string">'win1251'</span>);</span><br><span class="line"> </span><br><span class="line"><span class="comment">// Check if encoding is supported</span></span><br><span class="line">iconv.encodingExists(<span class="string">"us-ascii"</span>)</span><br></pre></td></tr></table></figure><p>流API</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">http.createServer(<span class="function"><span class="keyword">function</span>(<span class="params">req, res</span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> converterStream = iconv.decodeStream(<span class="string">'win1251'</span>);</span><br><span class="line"> req.pipe(converterStream);</span><br><span class="line"> </span><br><span class="line"> converterStream.on(<span class="string">'data'</span>, <span class="function"><span class="keyword">function</span>(<span class="params">str</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(str); <span class="comment">// Do something with decoded strings, chunk-by-chunk.</span></span><br><span class="line"> });</span><br><span class="line">});</span><br><span class="line"> </span><br><span class="line"><span class="comment">// Convert encoding streaming example</span></span><br><span class="line">fs.createReadStream(<span class="string">'file-in-win1251.txt'</span>)</span><br><span class="line"> .pipe(iconv.decodeStream(<span class="string">'win1251'</span>))</span><br><span class="line"> .pipe(iconv.encodeStream(<span class="string">'ucs2'</span>))</span><br><span class="line"> .pipe(fs.createWriteStream(<span class="string">'file-in-ucs2.txt'</span>));</span><br><span class="line"> </span><br><span class="line"><span class="comment">// Sugar: all encode/decode streams have .collect(cb) method to accumulate data.</span></span><br><span class="line">http.createServer(<span class="function"><span class="keyword">function</span>(<span class="params">req, res</span>) </span>{</span><br><span class="line"> req.pipe(iconv.decodeStream(<span class="string">'win1251'</span>)).collect(<span class="function"><span class="keyword">function</span>(<span class="params">err, body</span>) </span>{</span><br><span class="line"> assert(<span class="keyword">typeof</span> body == <span class="string">'string'</span>);</span><br><span class="line"> <span class="built_in">console</span>.log(body); <span class="comment">// full request body string</span></span><br><span class="line"> });</span><br><span class="line">});</span><br></pre></td></tr></table></figure><h1 id="js-cookie-2-2"><a href="#js-cookie-2-2" class="headerlink" title="js-cookie(2.2)"></a>js-cookie(2.2)</h1><p>一个简单,轻巧的JavaScript API,用于处理Cookie</p><p><code>yarn add js-cookie</code></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> Cookies <span class="keyword">from</span> <span class="string">'js-cookie'</span></span><br><span class="line"></span><br><span class="line">Cookies.set(<span class="string">'foo'</span>, <span class="string">'bar'</span>) </span><br><span class="line">Cookies.set(<span class="string">'name'</span>, <span class="string">'value'</span>, { <span class="attr">expires</span>: <span class="number">7</span> }) <span class="comment">//Create a cookie that expires 7 days from now, valid across the entire site:</span></span><br><span class="line">Cookies.get(<span class="string">'name'</span>) <span class="comment">// => 'value'</span></span><br><span class="line">Cookies.get(<span class="string">'nothing'</span>) <span class="comment">// => undefined</span></span><br><span class="line">Cookies.get() <span class="comment">// => { name: 'value' } //Cookies.get() // => { name: 'value' }</span></span><br><span class="line">Cookies.get(<span class="string">'foo'</span>, { <span class="attr">domain</span>: <span class="string">'sub.example.com'</span> }) <span class="comment">// `domain` won't have any effect...!</span></span><br><span class="line">Cookies.remove(<span class="string">'name'</span>)</span><br></pre></td></tr></table></figure><p>demo</p><figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="comment">// cookies.ts 使用示例,给的api己经很精简了,没有再封装的必要,如果想要隔离原则的话也可以封一下</span></span><br><span class="line"><span class="keyword">import</span> Cookies <span class="keyword">from</span> <span class="string">'js-cookie'</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// App</span></span><br><span class="line"><span class="keyword">const</span> sidebarStatusKey = <span class="string">'sidebar_status'</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> getSidebarStatus = <span class="function"><span class="params">()</span> =></span> Cookies.get(sidebarStatusKey)</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> setSidebarStatus = <span class="function">(<span class="params">sidebarStatus: <span class="built_in">string</span></span>) =></span> Cookies.set(sidebarStatusKey, sidebarStatus)</span><br><span class="line"></span><br><span class="line"><span class="comment">// User</span></span><br><span class="line"><span class="keyword">const</span> tokenKey = <span class="string">'pinpinle_token'</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> getToken = <span class="function"><span class="params">()</span> =></span> Cookies.get(tokenKey)</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> setToken = <span class="function">(<span class="params">token: <span class="built_in">string</span></span>) =></span> Cookies.set(tokenKey, token)</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> removeToken = <span class="function"><span class="params">()</span> =></span> Cookies.remove(tokenKey)</span><br></pre></td></tr></table></figure><h1 id="js-yaml-3-14-1"><a href="#js-yaml-3-14-1" class="headerlink" title="js-yaml(3.14.1)"></a>js-yaml(3.14.1)</h1><p>js-yaml 是一个专门用来读写YAML格式数据的库,他可以将JS对象转换成YAML字符串,也可以将YAML字符串转换为JS对象。</p><p>example.yaml</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">receipt:</span> <span class="string">Oz-Ware</span> <span class="string">Purchase</span> <span class="string">Invoice</span></span><br><span class="line"><span class="attr">date:</span> <span class="number">2012</span><span class="number">-08</span><span class="number">-06</span></span><br><span class="line"><span class="attr">customer:</span> <span class="comment">#对象</span></span><br><span class="line"> <span class="attr">given:</span> <span class="string">Dorothy</span></span><br><span class="line"> <span class="attr">family:</span> <span class="string">Gale</span></span><br><span class="line"></span><br><span class="line"><span class="attr">items:</span> <span class="comment"># 对象数组</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">part_no:</span> <span class="string">A4786</span></span><br><span class="line"> <span class="attr">descrip:</span> <span class="string">Water</span> <span class="string">Bucket</span> <span class="string">(Filled)</span></span><br><span class="line"> <span class="attr">price:</span> <span class="number">1.47</span></span><br><span class="line"> <span class="attr">quantity:</span> <span class="number">4</span></span><br><span class="line"></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">part_no:</span> <span class="string">E1628</span></span><br><span class="line"> <span class="attr">descrip:</span> <span class="string">High</span> <span class="string">Heeled</span> <span class="string">"Ruby"</span> <span class="string">Slippers</span></span><br><span class="line"> <span class="attr">size:</span> <span class="number">8</span></span><br><span class="line"> <span class="attr">price:</span> <span class="number">133.7</span></span><br><span class="line"> <span class="attr">quantity:</span> <span class="number">1</span></span><br><span class="line"></span><br><span class="line"><span class="attr">bill-to:</span> <span class="string">&id001</span> <span class="comment"># 锚点标记 id001</span></span><br><span class="line"> <span class="attr">street:</span> <span class="string">|</span> <span class="comment"># 多行字符串</span></span><br><span class="line"> <span class="number">123</span> <span class="string">Tornado</span> <span class="string">Alley</span></span><br><span class="line"> <span class="string">Suite</span> <span class="number">16</span></span><br><span class="line"> <span class="attr">city:</span> <span class="string">East</span> <span class="string">Centerville</span></span><br><span class="line"> <span class="attr">state:</span> <span class="string">KS</span></span><br><span class="line"></span><br><span class="line"><span class="attr">ship-to:</span> <span class="string">*id001</span> <span class="comment"># 引用锚点标记id001的数据</span></span><br><span class="line"></span><br><span class="line"><span class="attr">specialDelivery:</span> <span class="string">></span> <span class="comment"># 多行字符串</span></span><br><span class="line"> <span class="string">Follow</span> <span class="string">the</span> <span class="string">Yellow</span> <span class="string">Brick</span></span><br><span class="line"> <span class="string">Road</span> <span class="string">to</span> <span class="string">the</span> <span class="string">Emerald</span> <span class="string">City.</span></span><br><span class="line"> <span class="string">Pay</span> <span class="literal">no</span> <span class="string">attention</span> <span class="string">to</span> <span class="string">the</span></span><br><span class="line"> <span class="string">man</span> <span class="string">behind</span> <span class="string">the</span> <span class="string">curtain.</span></span><br></pre></td></tr></table></figure><p>读取yaml</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> yaml = <span class="built_in">require</span>(<span class="string">'js-yaml'</span>);</span><br><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">'fs'</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> obj = yaml.safeLoad(fs.readFileSync(<span class="string">'example.yml'</span>, <span class="string">'utf8'</span>));</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> str = yaml.safeDump(obj);</span><br></pre></td></tr></table></figure><p><a href="https://github.com/nodeca/js-yaml" target="_blank" rel="noopener">https://github.com/nodeca/js-yaml</a></p><h1 id="log4jsdd-1-0-2"><a href="#log4jsdd-1-0-2" class="headerlink" title="log4jsdd(1.0.2)"></a>log4jsdd(1.0.2)</h1><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> log4js = <span class="built_in">require</span>(<span class="string">'log4js'</span>);</span><br><span class="line">log4js.configure({</span><br><span class="line"> appenders: {</span><br><span class="line"> out: { <span class="attr">type</span>: <span class="string">'stdout'</span>},</span><br><span class="line"> dingding: {</span><br><span class="line"> type: <span class="string">'log4jsdd'</span>,</span><br><span class="line"> hookUrl: <span class="string">'填写获取钉钉里面设置的 webhook 地址'</span>,</span><br><span class="line"> title: <span class="string">'Node 消息'</span></span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> categories: { <span class="attr">default</span>: { <span class="attr">appenders</span>: [<span class="string">'out'</span>, <span class="string">'dingding'</span>], <span class="attr">level</span>: <span class="string">'debug'</span> }}</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> app = log4js.getLogger();</span><br><span class="line">app.info(<span class="string">'测试发送到钉钉'</span>);</span><br></pre></td></tr></table></figure><p><img src="https://image.xiaomo.info//blog/1.jpg" alt="图片"></p><p><img src="https://image.xiaomo.info//blog/2.jpg" alt="图片"></p><p><img src="https://image.xiaomo.info//blog/2-20201208145544618.jpg" alt="图片"></p><p><img src="https://image.xiaomo.info//blog/3.jpg" alt="图片"></p><p><img src="https://image.xiaomo.info//blog/4.jpg" alt="图片"></p><p><img src="https://image.xiaomo.info//blog/5.jpg" alt="图片"></p><p><img src="https://image.xiaomo.info//blog/6.jpg" alt="图片"></p><h1 id="material-ui-4-11-1"><a href="#material-ui-4-11-1" class="headerlink" title="material-ui(4.11.1)"></a>material-ui(4.11.1)</h1><p> 安装material-ui<br> <code>npm install @material-ui/core</code></p><p>安装material icons<br><code>npm install @material-ui/icons</code></p><p>Material-UI组件是相互独立的,自支持的,工作时仅注入当前组件所需要的样式。这些Material-UI组件并依赖于任何全局的样式表,尽管Material-UI提供了可选的CssBaseline组件。</p><figure class="highlight jsx"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> React <span class="keyword">from</span> <span class="string">'react'</span>;</span><br><span class="line"><span class="keyword">import</span> ReactDOM <span class="keyword">from</span> <span class="string">'react-dom'</span>;</span><br><span class="line"><span class="keyword">import</span> Button <span class="keyword">from</span> <span class="string">'@material-ui/core/Button'</span>; <span class="comment">// 导入Button组件</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">App</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <Button variant=<span class="string">'contained'</span> color=<span class="string">'primary'</span>>按钮<<span class="regexp">/Button></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">ReactDOM.render(<App /</span>>, <span class="built_in">document</span>.querySelector(<span class="string">'#app'</span>));</span><br></pre></td></tr></table></figure><p><a href="https://material-ui.com/getting-started/supported-components/" target="_blank" rel="noopener">对组件的支持</a></p><p>Materail-UI在努力遵循实际的指导规范时,却并不期望支持每一个组件或者组件的每一个特征。Materail-UI更希望能提供一套能帮助开发者创建引人注目的用户界面所需要的构建模块和经验。</p><p><a href="https://material-ui.com/getting-started/supported-platforms/" target="_blank" rel="noopener">Material-UI 支持哪些平台?</a></p><p>Material-UI支持所有主流的稳定的浏览器,支持IE11以上。Material-UI还支持node.js v6.x以上的服务端渲染。</p><p><a href="https://material-ui.com/getting-started/example-projects/" target="_blank" rel="noopener">在哪里可以获取到demo资源?</a></p><p>参见 <a href="https://github.com/mui-org/material-ui" target="_blank" rel="noopener">Material-UI 的GitHub仓库</a></p><p><a href="https://material-ui.com/getting-started/faq/" target="_blank" rel="noopener">Material-UI常见的问题列表</a></p><ol><li> 类名冲突时怎么处理?</li><li> 当打开Modal时,为什么fixed定位的元素会移动?</li><li> 如何禁用掉app中的波浪效果?</li><li> 必须使用JSS来控制样式?</li><li> 什么时候使用行内样式?什么时候使用类名?</li><li> 在Material项目中如何使用React-Router?</li><li> 如何合并withStyles() 和 withTheme() ?</li><li> 如何获取DOM元素?</li><li> 为什么项目中的色彩和官方效果有差异?</li><li> Material-UI令人疯狂,我该如何支持这个项目?</li></ol><p><a href="https://material-ui.com/getting-started/comparison/" target="_blank" rel="noopener">与其它UI库进行优势劣势对比</a></p><ol><li> Material-UI</li><li> Material Design Lite (MDL)</li><li> Material Components Web</li><li> Materialize</li><li>React Toolbox</li></ol><p><strong>二、</strong>样式</p><p><a href="https://material-ui.com/style/css-baseline/" target="_blank" rel="noopener">什么是 CssBaseline ?</a></p><p>Material-UI提供了一个CssBaseline组件以建立统一的简单的样式基准。</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> CssBaseline <span class="keyword">from</span> <span class="string">'@material-ui/core/CssBaseline'</span>;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">App</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">return</span>(</span><br><span class="line"> <CssBaseline></span><br><span class="line"> {<span class="comment">/* 页面元素 */</span>}</span><br><span class="line"> <<span class="regexp">/CssBaseline></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><p>有关Material-UI默认样式的详细说明:</p><ol><li> 元素的margin为零。</li><li> 在标准设备上,使用默认的背景色。在打印时,使用白色作为背景色。</li><li> 所有元素的box-sizing都被默认设置为”border-box”。所有元素都包含 ::before 和 ::after 样式,以保证元素的width 和 height 包含padding 和 border。</li><li> 使用Roboto字体,以避免了打印时的字体堆叠。</li><li> 打印时,不会为<html>标签声明基准字号,但会使用浏览器支持的默认字号16px。</html></li></ol><p><a href="https://material-ui.com/style/color/" target="_blank" rel="noopener">认识Material-UI中的色彩系统</a></p><p>在Material-UI中,色彩系统是非常强大的。它支持粗色调、深阴影和鲜亮的强调色彩。</p><p>色彩系统中的重要组成:</p><ol><li> Palette,主色调。</li><li> Hue / Shade 色调、色度。</li></ol><p><a href="https://material-ui.com/style/icons/" target="_blank" rel="noopener">Icon系统</a></p><p>Material-UI提供了两个组件用于icon系统。Icon组件用于渲染icons,SvgIcon用于渲染SVG路径。推荐使用<strong>SVG Material icons</strong>。</p><p>// 安装@material-ui/icons<br><code>npm install -S @material-ui/icons</code></p><figure class="highlight jsx"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> React <span class="keyword">from</span> <span class="string">'react'</span>;</span><br><span class="line"><span class="keyword">import</span> DeleteIcon <span class="keyword">from</span> <span class="string">'@material-ui/icons/Delete'</span>;</span><br></pre></td></tr></table></figure><p> <strong>三、布局</strong></p><p><a href="https://material-ui.com/layout/basics/" target="_blank" rel="noopener">Material-UI的布局设计基础</a></p><p>Material-UI布局,使用统一的组件和间距,实现了多平台、多环境和屏幕尺寸的统一性。</p><p>1、使用 <strong>Grid / Hidden / Breakpoints</strong> 这三类组件,实现响应式UI,适配各种尺寸的屏幕。</p><p>2、组件使用 z-index属性,实现 Z 轴上的空间层次排布。</p><p><a href="https://material-ui.com/layout/grid/" target="_blank" rel="noopener">Grid 组件</a></p><p>Material-UI的响应式UI,是基于12列的栅格布局。Material-UI的栅格系统是由 Grid 组件实现的,它使用了 CSS 弹性盒模型,它有两种类型的布局,分别是 containers 和 items。item的宽度被设置为百分比,因此它们总是基于父元素而流动、动态地变换大小。items使用内边距padding生成元素之间的间距空间。Material-UI栅格系统支持五种断点模式,分别是 xs / sm / md / lg / xl 。</p><p><a href="https://material-ui.com/layout/hidden/" target="_blank" rel="noopener">Hidden 组件</a></p><p>Hidden组件用于隐藏任何内容,它可以结合Grid / Breakpoints组件一起使用。它的实现原理是,基于breakpoints进行显示与隐藏。</p><p><a href="https://material-ui.com/layout/breakpoints/" target="_blank" rel="noopener">Breakpoints 组件</a></p><p>一个 breakpoint 就是一组预定义的屏幕尺寸范围,它决定了特定了布局需求。为了最适宜的用户体验,material design要实现多屏幕适配。Material-UI对material design规范进行了简化实现。每一个breakpoint都会匹配一定范围的屏幕尺寸。</p><ol><li> xs, 特别小的屏幕,0px or larger</li><li> sm, 小屏幕,600px or larger</li><li> md, 中等屏幕,960px or larger</li><li> lg, 大屏幕, 1280px or larger</li><li> xl, 超大屏幕, 1920px or larger</li></ol><p>以上这些取值,在Material-UI中,还支持自定义。Material-UI支持媒体查询,根据屏幕尺寸变化动态地变化元素样式。有时候,仅使用 CSS 是不够的,你或许还需要在屏幕尺寸发生变化时改变DOM内容。这个时候,可以使用 Material-UI 提供的 withWidth() 来实现。</p><p><strong>四、工具</strong></p><p><a href="https://material-ui.com/utils/modals/" target="_blank" rel="noopener">Modal 组件</a></p><p>用于创建对话框、lightbox 和 popover。</p><p><a href="https://material-ui.com/utils/popovers/" target="_blank" rel="noopener">Popover 组件</a></p><p>用于给元素提供额外的展示/提示信息,类似于html元素的title属性。</p><p><a href="https://material-ui.com/utils/portal/" target="_blank" rel="noopener">Portal 组件</a></p><p>The portal component renders its children into a new “subtree” outside of current component hierarchy.</p><p><a href="https://material-ui.com/utils/transitions/" target="_blank" rel="noopener">Transitions 过渡效果</a></p><p>Transition提供了一系列的动画效果。如折叠效果、渐显渐现、Grow效果、Slide效果、Zoom效果等。</p><p><a href="https://material-ui.com/utils/click-away-listener/" target="_blank" rel="noopener">Click away listener</a></p><p>监听DOM页面上的所有事件,元素之内、元素之外。比如,在元素之外点击页面时,应该让弹框消失等。</p><p><strong>五、Component Demos / Api</strong></p><p>如何使用Material-UI组件?</p><p>代码演示如下:</p><figure class="highlight jsx"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> React <span class="keyword">from</span> <span class="string">'react'</span>;</span><br><span class="line"><span class="keyword">import</span> ReactDOM <span class="keyword">from</span> <span class="string">'react-dom'</span>;</span><br><span class="line"><span class="keyword">import</span> { withStyles } <span class="keyword">from</span> <span class="string">'@material-ui/core/styles'</span>;</span><br><span class="line"><span class="comment">// 导入组件</span></span><br><span class="line"><span class="keyword">import</span> AppBar <span class="keyword">from</span> <span class="string">'@material-ui/core/AppBar'</span>;</span><br><span class="line"><span class="keyword">const</span> App = <span class="function">(<span class="params">props</span>) =></span> {</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <div></span><br><span class="line"> { <span class="comment">/* 使用组件 */</span>}</span><br><span class="line"> <AppBar position=<span class="string">'static'</span> color=<span class="string">'default'</span>>title<<span class="regexp">/AppBar></span></span><br><span class="line"><span class="regexp"> </</span>div></span><br><span class="line"> );</span><br><span class="line">}</span><br><span class="line">ReactDOM.render(<span class="xml"><span class="tag"><<span class="name">App</span> /></span></span>, <span class="built_in">document</span>.getElementById(<span class="string">'root'</span>));</span><br></pre></td></tr></table></figure><p>在使用 Material-UI 组件时,常需要参考<a href="https://material-ui.com/api/app-bar/" target="_blank" rel="noopener">组件的api文档</a>,可查看相关属性的使用。</p><p><strong>六、定制化</strong></p><p><a href="https://material-ui.com/customization/overrides/" target="_blank" rel="noopener">概览</a></p><p>由于组件会被用于各种不同的环境中,Material-UI支持不同类型的定制化需求,从最特殊的到最普通的场景。</p><ol><li>定制一次性样式<pre><code> 在一些特别的场景下,你或许需要改变一个组件的样式。定制方案有:覆盖组件的类名,重写组件样式,使用内联样式等。</code></pre></li><li>定制动态样式<pre><code> 在一些场景下,你需要使用动态的样式。可选的方案有:使用withStyles,类名切换,CSS变量,行内样式,主题嵌套等。</code></pre></li><li> 为某个组件定制样式</li><li> Material Design 样式</li><li>全局的主题样式</li></ol><p><a href="https://material-ui.com/customization/themes/" target="_blank" rel="noopener">主题</a></p><p>主题特指组件的主体颜色、组件表面的墨色、阴影的级别和元素适合的透明度等。主题使得你的应用具有统一的色调。它允许你定制所有的设计方面,以满足你的商业或品牌需求。为了促进应用更高的统一性,Material-UI提供了 light 和 dark 两种主题类型。默认情况下,组件使用 light 主题类型。</p><p>什么时候使用 MuiThemeProvider 组件?</p><p>如果你想定制主题样式,你需要使用 MuiThemeProvider 组件来包裹那些需要定制样式的组件,用以把主题注入到你的应用中。然而这是可选的,当不使用 MuiThemeProvider 组件时,Material-UI 会使用默认主题。</p><p>如何使用主题配置变量?</p><p>改变主题配置变量,是匹配Material-UI定制样式最高效的方式。如下几个配置变量是非常重要的:</p><ol><li> Palette</li><li> Type ( light / dark )</li><li> Typography</li><li> Other varialbles</li><li> Custom variables</li></ol><p><a href="https://material-ui.com/customization/default-theme/" target="_blank" rel="noopener">查看 Material-UI 的默认主题样式</a></p><p><a href="https://material-ui.com/customization/css-in-js/" target="_blank" rel="noopener">CSS in JS</a></p><p><a href="https://material-ui.com/zh/getting-started/installation/" target="_blank" rel="noopener">https://material-ui.com/zh/getting-started/installation/</a></p><h1 id="nodemon(2-0-6)"><a href="#nodemon(2-0-6)" class="headerlink" title="nodemon(2.0.6)"></a>nodemon(2.0.6)</h1><p>nodemon用来监视node.js应用程序中的任何更改并自动重启服务,非常适合用在开发环境中。nodemon将监视启动目录中的文件,如果有任何文件更改,nodemon将自动重新启动node应用程序。</p><p>nodemon不需要对代码或开发方式进行任何更改。 nodemon只是简单的包装你的node应用程序,并监控任何已经改变的文件。nodemon只是node的替换包,只是在运行脚本时将其替换命令行上的node。</p><p>在项目目录下创建 nodemon.json 文件</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"restartable"</span>: <span class="string">"rs"</span>,</span><br><span class="line"> <span class="attr">"ignore"</span>: [</span><br><span class="line"> <span class="string">".git"</span>,</span><br><span class="line"> <span class="string">".svn"</span>,</span><br><span class="line"> <span class="string">"node_modules/**/node_modules"</span></span><br><span class="line"> ],</span><br><span class="line"> <span class="attr">"verbose"</span>: <span class="literal">true</span>,</span><br><span class="line"> <span class="attr">"execMap"</span>: {</span><br><span class="line"> <span class="attr">"js"</span>: <span class="string">"node --harmony"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"watch"</span>: [</span><br><span class="line"></span><br><span class="line"> ],</span><br><span class="line"> <span class="attr">"env"</span>: {</span><br><span class="line"> <span class="attr">"NODE_ENV"</span>: <span class="string">"development"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"ext"</span>: <span class="string">"js json"</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><figure class="highlight maxima"><table><tr><td class="code"><pre><span class="line">restartable-设置重启模式 </span><br><span class="line">ignore-设置忽略文件 </span><br><span class="line"><span class="built_in">verbose</span>-设置日志输出模式,<span class="literal">true</span> 详细模式 </span><br><span class="line">execMap-设置运行服务的后缀名与对应的命令</span><br></pre></td></tr></table></figure><p>修改app.js文件<br> 记得注释最后一行的:module.exports = app;</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> debug = <span class="built_in">require</span>(<span class="string">'debug'</span>)(<span class="string">'my-application'</span>); <span class="comment">// debug模块</span></span><br><span class="line">app.set(<span class="string">'port'</span>, process.env.PORT || <span class="number">3000</span>); <span class="comment">// 设定监听端口</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//启动监听</span></span><br><span class="line"><span class="keyword">var</span> server = app.listen(app.get(<span class="string">'port'</span>), <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> debug(<span class="string">'Express server listening on port '</span> + server.address().port);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="comment">//module.exports = app;//这是 4.x 默认的配置,分离了 app 模块,将它注释即可,上线时可以重新改回来</span></span><br></pre></td></tr></table></figure><p>在package.json中添加脚本</p><figure class="highlight"><table><tr><td class="code"><pre><span class="line">"start": "nodemon app.js"</span><br></pre></td></tr></table></figure><h1 id="pkg-dir-5-0-0"><a href="#pkg-dir-5-0-0" class="headerlink" title="pkg-dir(5.0.0)"></a>pkg-dir(5.0.0)</h1><figure class="highlight routeros"><table><tr><td class="code"><pre><span class="line">/</span><br><span class="line">└── Users</span><br><span class="line"> └── xiaomo</span><br><span class="line"> └── foo</span><br><span class="line"> ├── package.json</span><br><span class="line"> └── bar</span><br><span class="line"> ├── baz</span><br><span class="line"> └── example.js</span><br></pre></td></tr></table></figure><p><code>npm install pkg-dir</code></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// example.js</span></span><br><span class="line"><span class="keyword">const</span> pkgDir = <span class="built_in">require</span>(<span class="string">'pkg-dir'</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> rootDir = <span class="keyword">await</span> pkgDir(__dirname);</span><br><span class="line"> </span><br><span class="line"> <span class="built_in">console</span>.log(rootDir);</span><br><span class="line"> <span class="comment">//=> '/Users/xiaomo/foo'</span></span><br><span class="line">})();</span><br></pre></td></tr></table></figure><p>返回Promise项目根路径或undefined找不到的根路径。</p><p>返回项目的根路径,或者undefined找不到它。</p><p>类型:string<br> 默认值:process.cwd()</p><p>起始目录。</p><ul><li> <a href="https://github.com/sindresorhus/pkg-dir-cli" target="_blank" rel="noopener">pkg-dir-cli-</a>此模块的CLI</li><li> <a href="https://github.com/sindresorhus/pkg-up" target="_blank" rel="noopener">pkg-</a> up-查找最近的package.json文件</li><li> <a href="https://github.com/sindresorhus/find-up" target="_blank" rel="noopener">查找</a>-通过上级父目录查找文件</li></ul><h1 id="qs-6-9-4"><a href="#qs-6-9-4" class="headerlink" title="qs(6.9.4)"></a>qs(6.9.4)</h1><p>Node.js 标准库中有一个库叫querystring,这个库用来处理URL查询字符串:</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> querystring = <span class="built_in">require</span>(<span class="string">'querystring'</span>);</span><br><span class="line"> </span><br><span class="line"> querystring.parse(<span class="string">'foo=bar&baz=1'</span>);</span><br><span class="line"> <span class="comment">// { foo:'bar', baz: '1' }</span></span><br></pre></td></tr></table></figure><p>但是很遗憾,querystring 不支持内嵌对象和数组:</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> querystring = <span class="built_in">require</span>(<span class="string">'querystring'</span>);</span><br><span class="line"> </span><br><span class="line"> querystring.parse(<span class="string">'foo[bar]=1&baz[]=2'</span>);</span><br><span class="line"> <span class="comment">// { 'foo[bar]': '1', 'baz[]': '2' }</span></span><br></pre></td></tr></table></figure><p>如果我们程序的前端界面form表单中存在数组,标准库的querystring就无法满足我们的需求了。</p><p>qs 是querystring的增强版本,最重要的特性就是支持内嵌对象和数组:</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> qs = <span class="built_in">require</span>(<span class="string">'qs'</span>);</span><br><span class="line"><span class="keyword">var</span> assert = <span class="built_in">require</span>(<span class="string">'assert'</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> obj = qs.parse(<span class="string">'a=c'</span>);</span><br><span class="line">assert.deepEqual(obj, { <span class="attr">a</span>: <span class="string">'c'</span> });</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> str = qs.stringify(obj);</span><br><span class="line">assert.equal(str, <span class="string">'a=c'</span>);</span><br></pre></td></tr></table></figure><p><a href="https://github.com/ljharb/qs" target="_blank" rel="noopener">https://github.com/ljharb/qs</a></p><h1 id="redux-toolkit-1-5-0"><a href="#redux-toolkit-1-5-0" class="headerlink" title="redux-toolkit(1.5.0)"></a><strong>redux-toolkit</strong>(1.5.0)</h1><p>如果你的React项目中使用react hook、redux、redux-thunk,可能你需要用 redux-toolkit (以下简称RTK)优化你的项目结构,它看起来可以这么清爽</p><p>Redux 使用常见问题</p><ul><li> 配置复杂,devtool…</li><li> 模板代码太多,创建constant,action,reducer…</li><li> 需要添加很多依赖包,如redux-thunk、immer…</li></ul><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"># 优化前</span><br><span class="line"> /counter</span><br><span class="line"> constants.ts</span><br><span class="line"> actions.ts</span><br><span class="line"> reducer.ts</span><br><span class="line"> saga.ts</span><br><span class="line"> index.tsxÏ</span><br><span class="line"># 优化后</span><br><span class="line">/counter</span><br><span class="line"> slice.ts</span><br><span class="line"> index.tsx</span><br></pre></td></tr></table></figure><p><strong>RTK干了哪些事?</strong></p><ul><li> configureStore() 包裹createStore,并集成了redux-thunk、Redux DevTools Extension,默认开启</li><li> createReducer() 创建一个reducer,action type 映射到 case reducer 函数中,不用写switch-case,并集成immer</li><li> createAction() 创建一个action,传入动作类型字符串,返回动作函数</li><li> createSlice() 创建一个slice,包含 createReducer、createAction的所有功能</li><li> createAsyncThunk() 创建一个thunk,接受一个动作类型字符串和一个Promise的函数</li></ul><p>新的项目</p><p>create-react-app 初始化项目,最受欢迎的脚手架之一</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 使用 redux-typescript 模板,推荐使用typescript</span></span><br><span class="line">npx create-react-app react-rtk-ts --template redux-typescript</span><br><span class="line"><span class="comment"># 使用redux 模板</span></span><br><span class="line"><span class="comment"># npx create-react-app react-rtk-ts --template redux</span></span><br></pre></td></tr></table></figure><p>老的项目</p><ol><li> 安装 @reduxjs/toolkit</li></ol><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 使用 npm</span></span><br><span class="line">npm install @reduxjs/toolkit</span><br><span class="line"><span class="comment"># 使用 yarn</span></span><br><span class="line">yarn add @reduxjs/toolkit</span><br></pre></td></tr></table></figure><ol start="2"><li> configureStore 替换 createStore</li></ol><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> React <span class="keyword">from</span> <span class="string">"react"</span>;</span><br><span class="line"><span class="keyword">import</span> { render } <span class="keyword">from</span> <span class="string">"react-dom"</span>;</span><br><span class="line">- <span class="keyword">import</span> { createStore } <span class="keyword">from</span> <span class="string">"redux"</span>;</span><br><span class="line">+ <span class="keyword">import</span> { configureStore } <span class="keyword">from</span> <span class="string">"@reduxjs/toolkit"</span>;</span><br><span class="line"><span class="keyword">import</span> { Provider } <span class="keyword">from</span> <span class="string">"react-redux"</span>;</span><br><span class="line"><span class="keyword">import</span> App <span class="keyword">from</span> <span class="string">"./components/App"</span>;</span><br><span class="line"><span class="keyword">import</span> rootReducer <span class="keyword">from</span> <span class="string">"./reducers"</span>;</span><br><span class="line">- <span class="keyword">const</span> store = createStore(rootReducer);</span><br><span class="line">+ <span class="keyword">const</span> store = configureStore({</span><br><span class="line">+ reducer: rootReducer,</span><br><span class="line">+});</span><br></pre></td></tr></table></figure><p>创建action</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"># 创建 action</span><br><span class="line"><span class="keyword">const</span> increment = createAction(<span class="string">'INCREMENT'</span>)</span><br><span class="line"><span class="keyword">const</span> decrement = createAction(<span class="string">'DECREMENT'</span>)</span><br><span class="line"># 创建reducer</span><br><span class="line"><span class="keyword">const</span> counter = createReducer(<span class="number">0</span>, {</span><br><span class="line"> [increment]: <span class="function"><span class="params">state</span> =></span> state + <span class="number">1</span>,</span><br><span class="line"> [decrement]: <span class="function"><span class="params">state</span> =></span> state - <span class="number">1</span></span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>以上看起比原来结构上好一些,创建action、reducer方便了,但是看着还是不爽,action也可以去掉</p><p>创建Slice</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> counterSlice = createSlice({</span><br><span class="line"> name: <span class="string">'counter'</span>,</span><br><span class="line"> initialState: <span class="number">0</span>,</span><br><span class="line"> reducers: {</span><br><span class="line"> increment: <span class="function"><span class="params">state</span> =></span> state + <span class="number">1</span>,</span><br><span class="line"> decrement: <span class="function"><span class="params">state</span> =></span> state - <span class="number">1</span></span><br><span class="line"> }</span><br><span class="line">})</span><br><span class="line"># action</span><br><span class="line">counterSlice.action;</span><br><span class="line"># reducer</span><br><span class="line">counterSlice.reducer;</span><br></pre></td></tr></table></figure><p>counterSlice 看起起来像</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">{</span><br><span class="line"> name: <span class="string">"counter"</span>,</span><br><span class="line"> reducer: <span class="function">(<span class="params">state, action</span>) =></span> newState,</span><br><span class="line"> actions: {</span><br><span class="line"> increment: <span class="function">(<span class="params">payload</span>) =></span> ({<span class="attr">type</span>: <span class="string">"counter/increment"</span>, payload}),</span><br><span class="line"> increment: <span class="function">(<span class="params">payload</span>) =></span> ({<span class="attr">type</span>: <span class="string">"counter/increment"</span>, payload})</span><br><span class="line"> },</span><br><span class="line"> caseReducers: {</span><br><span class="line"> increment: <span class="function">(<span class="params">state, action</span>) =></span> newState,</span><br><span class="line"> increment: <span class="function">(<span class="params">state, action</span>) =></span> newState,</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>只需要createSlice,包含着 action,reducer的创建</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"># 对于一部请求API,可以配合async/await使用</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> incrementAsync = (amount: number): <span class="function"><span class="params">AppThunk</span> =></span> <span class="function"><span class="params">dispatch</span> =></span> {</span><br><span class="line"> setTimeout(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> dispatch(incrementByAmount(amount));</span><br><span class="line"> }, <span class="number">1000</span>);</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>创建 selecter</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"># 配合 react-redux 中 useSelector hook使用</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> selectCount = <span class="function">(<span class="params">state: RootState</span>) =></span> state.counter.value;</span><br></pre></td></tr></table></figure><p><strong>完整 slice文件</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> { createSlice, PayloadAction } <span class="keyword">from</span> <span class="string">'@reduxjs/toolkit'</span>;</span><br><span class="line"><span class="keyword">import</span> { AppThunk, RootState } <span class="keyword">from</span> <span class="string">'../../app/store'</span>;</span><br><span class="line">interface CounterState {</span><br><span class="line"> value: number;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">const</span> initialState: CounterState = {</span><br><span class="line"> value: <span class="number">0</span>,</span><br><span class="line">};</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> counterSlice = createSlice({</span><br><span class="line"> name: <span class="string">'counter'</span>,</span><br><span class="line"> initialState,</span><br><span class="line"> reducers: {</span><br><span class="line"> increment: <span class="function"><span class="params">state</span> =></span> {</span><br><span class="line"> state.value += <span class="number">1</span>;</span><br><span class="line"> },</span><br><span class="line"> decrement: <span class="function"><span class="params">state</span> =></span> {</span><br><span class="line"> state.value -= <span class="number">1</span>;</span><br><span class="line"> },</span><br><span class="line"> incrementByAmount: <span class="function">(<span class="params">state, action: PayloadAction<number></span>) =></span> {</span><br><span class="line"> state.value += action.payload;</span><br><span class="line"> },</span><br><span class="line"> },</span><br><span class="line">});</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> { increment, decrement, incrementByAmount } = counterSlice.actions;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> incrementAsync = (amount: number): <span class="function"><span class="params">AppThunk</span> =></span> <span class="function"><span class="params">dispatch</span> =></span> {</span><br><span class="line"> setTimeout(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> dispatch(incrementByAmount(amount));</span><br><span class="line"> }, <span class="number">1000</span>);</span><br><span class="line">};</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> selectCount = <span class="function">(<span class="params">state: RootState</span>) =></span> state.counter.value;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> counterSlice.reducer;</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><p>虽然自栩全沾工程师,但是对于前端圈的了解还是相对缺乏的,尤其是大量的npm包。java的maven/gradle, node的npm, swift的pod,,python的pip,php的Composer,c++的<a href="https://github.com/conan-io/conan" target="_blank" rel="noopener">Conan</a>等等。基本上每一种开发语言都有自己的包管理器。开源三方库汇集了全世界的智慧结晶,有了这些优秀的三方库能够让我们很容易的完成复杂的功能。所以打算开一个长期向的优秀库的使用收集博客。大部分内容是搜索到的优秀博文整理而来。</p></summary>
<category term="web" scheme="https://blog.xiaomo.info/categories/web/"/>
<category term="npm" scheme="https://blog.xiaomo.info/tags/npm/"/>
</entry>
<entry>
<title>js/ts常用公有方法收集整理(长期向)</title>
<link href="https://blog.xiaomo.info/2020/jsCommonUtilsMethods/"/>
<id>https://blog.xiaomo.info/2020/jsCommonUtilsMethods/</id>
<published>2020-12-08T00:00:00.000Z</published>
<updated>2022-08-10T23:53:17.655Z</updated>
<content type="html"><![CDATA[<p>不管是什么语言,不管是前端还是后端,我们都有一个很重要的模块,那就是utils包。在java界有apache-commons这种出身明门的,也有像hutool这种民间的库。前端界目前还没有发现一个比应用比较频繁的库,那就出一个长期项的自己整理整一下吧。</p><a id="more"></a><p>注: 本篇文章开始于2020年12月8日,每次修改或新增时都会将时间更新时最新的时间。</p><h1 id="动态创建meta的方法"><a href="#动态创建meta的方法" class="headerlink" title="动态创建meta的方法"></a>动态创建meta的方法</h1><figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="comment">// meta-utils.ts</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> createMeta=(name:<span class="built_in">string</span>,content:<span class="built_in">string</span>):<span class="function"><span class="params">void</span>=></span>{</span><br><span class="line"> consthead=<span class="built_in">document</span>.getElementsByTagName(<span class="string">'head'</span>);</span><br><span class="line"> constmeta=<span class="built_in">document</span>.createElement(<span class="string">'meta'</span>);</span><br><span class="line"> meta.name=name;</span><br><span class="line"> meta.content=content;</span><br><span class="line"> head[<span class="number">0</span>].appendChild(meta);</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>使用</p><figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> { createMeta } <span class="keyword">from</span> <span class="string">'./metaUtil'</span> </span><br><span class="line"></span><br><span class="line">createMeta(<span class="string">'xxx'</span>, <span class="string">'xxxx'</span>)</span><br></pre></td></tr></table></figure><h1 id="对函数只执行一次"><a href="#对函数只执行一次" class="headerlink" title="对函数只执行一次"></a>对函数只执行一次</h1><figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">// eslint-disable-next-line @typescript-eslint/ban-types</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> once = <T <span class="keyword">extends</span> <span class="built_in">Function</span>, U><span class="function">(<span class="params">fn: (<span class="params">...args: T[]</span>) => U</span>): <span class="params">typeof</span> <span class="params">fn</span> =></span> {</span><br><span class="line"> <span class="keyword">let</span> result: ReturnType<<span class="keyword">typeof</span> fn>;</span><br><span class="line"> <span class="keyword">let</span> func: <span class="keyword">typeof</span> fn | <span class="literal">undefined</span> = fn;</span><br><span class="line"> <span class="keyword">return</span> (...args: Parameters<<span class="keyword">typeof</span> fn>): ReturnType<<span class="keyword">typeof</span> fn> => {</span><br><span class="line"> <span class="keyword">if</span> (func) {</span><br><span class="line"> result = func(...args);</span><br><span class="line"> func = <span class="literal">undefined</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line"> };</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>使用</p><figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> axios <span class="keyword">from</span> <span class="string">'redaxios'</span>;</span><br><span class="line"><span class="keyword">import</span> { AuthConfig } <span class="keyword">from</span> <span class="string">'@/client/auth/index'</span>;</span><br><span class="line"><span class="comment">// eslint-disable-next-line no-restricted-imports</span></span><br><span class="line"><span class="keyword">import</span> { once } <span class="keyword">from</span> <span class="string">'../utils/functionUtils'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> fetchAuthConfig = once(</span><br><span class="line"><span class="keyword">async</span> (): <span class="built_in">Promise</span><AuthConfig> => {</span><br><span class="line"> <span class="keyword">const</span> response = <span class="keyword">await</span> axios({</span><br><span class="line"> url: <span class="string">'/oauth/v1/config'</span>,</span><br><span class="line"> method: <span class="string">'get'</span>,</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> config: AuthConfig = response.data <span class="keyword">as</span> AuthConfig;</span><br><span class="line"><span class="keyword">return</span> config;</span><br><span class="line">}</span><br><span class="line">);</span><br></pre></td></tr></table></figure><h1 id="获取URL参数"><a href="#获取URL参数" class="headerlink" title="获取URL参数"></a>获取URL参数</h1><figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="comment">// ParmaterUtil.ts</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> ParameterUtil {</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* 根据名字获取url参数的值(不能获取带#的url参数)</span></span><br><span class="line"><span class="comment">**/</span> </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> getUrlParameter(parameter:<span class="built_in">string</span>): <span class="built_in">string</span>|<span class="literal">null</span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> URL(<span class="built_in">window</span>.location.href).searchParams.get(parameter);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>注:不支持 <code>vue-router</code>具有锚点的URL,例如 <code>https://test.xiaomo.info/#?name=xiaomo&age=15</code>。</p><p>只支持直接在url上拼参数的方式<code>https://test.xiaomo.info?name=xiaomo&age=15</code></p><h1 id="动态创建script"><a href="#动态创建script" class="headerlink" title="动态创建script"></a>动态创建script</h1><p>需要安装 loadjs</p><p><code>yarn add loadjs</code></p><figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> loadjs, { LoadOptions } <span class="keyword">from</span> <span class="string">'loadjs'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> CacheableOptions <span class="keyword">extends</span> LoadOptions {</span><br><span class="line"> cacheable?: <span class="built_in">boolean</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> loadScript = ((): ((</span><br><span class="line"> src: <span class="built_in">string</span>,</span><br><span class="line"> options?: CacheableOptions</span><br><span class="line">) => <span class="built_in">Promise</span><<span class="built_in">void</span>>) => {</span><br><span class="line"> <span class="keyword">const</span> cache: Record<<span class="built_in">string</span>, <span class="built_in">Promise</span><<span class="built_in">void</span>>> = {};</span><br><span class="line"> <span class="keyword">return</span> (src: <span class="built_in">string</span>, options?: CacheableOptions): <span class="built_in">Promise</span><<span class="built_in">void</span>> => {</span><br><span class="line"> <span class="keyword">const</span> opt: CacheableOptions = {</span><br><span class="line"> cacheable: <span class="literal">true</span>,</span><br><span class="line"> numRetries: <span class="number">3</span>,</span><br><span class="line"> ...(options || {}),</span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (opt.cacheable && cache[src]) {</span><br><span class="line"> <span class="keyword">return</span> cache[src];</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">const</span> promise = loadjs([src], {</span><br><span class="line"> ...opt,</span><br><span class="line"> returnPromise: <span class="literal">true</span>,</span><br><span class="line"> });</span><br><span class="line"> <span class="keyword">if</span> (opt.cacheable) {</span><br><span class="line"> cache[src] = promise;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> promise;</span><br><span class="line"> };</span><br><span class="line">})();</span><br></pre></td></tr></table></figure><h1 id="邮箱格式检测"><a href="#邮箱格式检测" class="headerlink" title="邮箱格式检测"></a>邮箱格式检测</h1><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">export const checkEmail = (v: string): boolean =></span><br><span class="line"> /[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/.test(</span><br><span class="line"> v</span><br><span class="line"> );</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><p>不管是什么语言,不管是前端还是后端,我们都有一个很重要的模块,那就是utils包。在java界有apache-commons这种出身明门的,也有像hutool这种民间的库。前端界目前还没有发现一个比应用比较频繁的库,那就出一个长期项的自己整理整一下吧。</p></summary>
<category term="web" scheme="https://blog.xiaomo.info/categories/web/"/>
<category term="web" scheme="https://blog.xiaomo.info/tags/web/"/>
</entry>
<entry>
<title>spring cloud集成spring security</title>
<link href="https://blog.xiaomo.info/2020/springBootWithSpringSecurity/"/>
<id>https://blog.xiaomo.info/2020/springBootWithSpringSecurity/</id>
<published>2020-11-30T00:00:00.000Z</published>
<updated>2022-08-10T23:53:17.651Z</updated>
<content type="html"><![CDATA[<p>在spring boot/spring cloud成为java生态中绝对主流的开发框架时,spring家族其他的框架也开始变得越来越流行。对于认证授权的方案也有更多的小伙伴选择从shrio过渡到spring security。但是spring security的难度确实要比shiro高出不少,需要花费较多精力学习。</p><a id="more"></a><h3 id="1-原理"><a href="#1-原理" class="headerlink" title="1.原理"></a>1.原理</h3><p><img src="https://image.xiaomo.info//blog/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM1MDY3MzIy,size_16,color_FFFFFF,t_70.png" alt="img"></p><p><strong>Spring Security</strong> 内置了一些过滤器,他们各有各的本事。如果你掌握了这些过滤器,很多实际开发中的需求和问题都很容易解决。今天我们来见识一下这些内置的过滤器。</p><h2 id="2-内置过滤器初始化"><a href="#2-内置过滤器初始化" class="headerlink" title="2. 内置过滤器初始化"></a>2. 内置过滤器初始化</h2><p>在 <strong>Spring Security</strong> 初始化核心过滤器时 <code>HttpSecurity</code> 会通过将 <strong>Spring Security</strong> 内置的一些过滤器以 <code>FilterComparator</code> 提供的规则进行比较按照比较结果进行排序注册。</p><h3 id="2-1-排序规则"><a href="#2-1-排序规则" class="headerlink" title="2.1 排序规则"></a>2.1 排序规则</h3><p><code>FilterComparator</code> 维护了一个顺序的注册表 <code>filterToOrder</code> 。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">FilterComparator() {</span><br><span class="line"> Step order = <span class="keyword">new</span> Step(INITIAL_ORDER, ORDER_STEP);</span><br><span class="line"> put(ChannelProcessingFilter<span class="class">.<span class="keyword">class</span>, <span class="title">order</span>.<span class="title">next</span>())</span>;</span><br><span class="line"> put(ConcurrentSessionFilter<span class="class">.<span class="keyword">class</span>, <span class="title">order</span>.<span class="title">next</span>())</span>;</span><br><span class="line"> put(WebAsyncManagerIntegrationFilter<span class="class">.<span class="keyword">class</span>, <span class="title">order</span>.<span class="title">next</span>())</span>;</span><br><span class="line"> put(SecurityContextPersistenceFilter<span class="class">.<span class="keyword">class</span>, <span class="title">order</span>.<span class="title">next</span>())</span>;</span><br><span class="line"> put(HeaderWriterFilter<span class="class">.<span class="keyword">class</span>, <span class="title">order</span>.<span class="title">next</span>())</span>;</span><br><span class="line"> put(CorsFilter<span class="class">.<span class="keyword">class</span>, <span class="title">order</span>.<span class="title">next</span>())</span>;</span><br><span class="line"> put(CsrfFilter<span class="class">.<span class="keyword">class</span>, <span class="title">order</span>.<span class="title">next</span>())</span>;</span><br><span class="line"> put(LogoutFilter<span class="class">.<span class="keyword">class</span>, <span class="title">order</span>.<span class="title">next</span>())</span>;</span><br><span class="line"> filterToOrder.put(</span><br><span class="line"> <span class="string">"org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter"</span>,</span><br><span class="line"> order.next());</span><br><span class="line"> filterToOrder.put(</span><br><span class="line"> <span class="string">"org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationRequestFilter"</span>,</span><br><span class="line"> order.next());</span><br><span class="line"> put(X509AuthenticationFilter<span class="class">.<span class="keyword">class</span>, <span class="title">order</span>.<span class="title">next</span>())</span>;</span><br><span class="line"> put(AbstractPreAuthenticatedProcessingFilter<span class="class">.<span class="keyword">class</span>, <span class="title">order</span>.<span class="title">next</span>())</span>;</span><br><span class="line"> filterToOrder.put(<span class="string">"org.springframework.security.cas.web.CasAuthenticationFilter"</span>,</span><br><span class="line"> order.next());</span><br><span class="line"> filterToOrder.put(</span><br><span class="line"> <span class="string">"org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter"</span>,</span><br><span class="line"> order.next());</span><br><span class="line"> filterToOrder.put(</span><br><span class="line"> <span class="string">"org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationFilter"</span>,</span><br><span class="line"> order.next());</span><br><span class="line"> put(UsernamePasswordAuthenticationFilter<span class="class">.<span class="keyword">class</span>, <span class="title">order</span>.<span class="title">next</span>())</span>;</span><br><span class="line"> put(ConcurrentSessionFilter<span class="class">.<span class="keyword">class</span>, <span class="title">order</span>.<span class="title">next</span>())</span>;</span><br><span class="line"> filterToOrder.put(</span><br><span class="line"> <span class="string">"org.springframework.security.openid.OpenIDAuthenticationFilter"</span>, order.next());</span><br><span class="line"> put(DefaultLoginPageGeneratingFilter<span class="class">.<span class="keyword">class</span>, <span class="title">order</span>.<span class="title">next</span>())</span>;</span><br><span class="line"> put(DefaultLogoutPageGeneratingFilter<span class="class">.<span class="keyword">class</span>, <span class="title">order</span>.<span class="title">next</span>())</span>;</span><br><span class="line"> put(ConcurrentSessionFilter<span class="class">.<span class="keyword">class</span>, <span class="title">order</span>.<span class="title">next</span>())</span>;</span><br><span class="line"> put(DigestAuthenticationFilter<span class="class">.<span class="keyword">class</span>, <span class="title">order</span>.<span class="title">next</span>())</span>;</span><br><span class="line"> filterToOrder.put(</span><br><span class="line"> <span class="string">"org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter"</span>, order.next());</span><br><span class="line"> put(BasicAuthenticationFilter<span class="class">.<span class="keyword">class</span>, <span class="title">order</span>.<span class="title">next</span>())</span>;</span><br><span class="line"> put(RequestCacheAwareFilter<span class="class">.<span class="keyword">class</span>, <span class="title">order</span>.<span class="title">next</span>())</span>;</span><br><span class="line"> put(SecurityContextHolderAwareRequestFilter<span class="class">.<span class="keyword">class</span>, <span class="title">order</span>.<span class="title">next</span>())</span>;</span><br><span class="line"> put(JaasApiIntegrationFilter<span class="class">.<span class="keyword">class</span>, <span class="title">order</span>.<span class="title">next</span>())</span>;</span><br><span class="line"> put(RememberMeAuthenticationFilter<span class="class">.<span class="keyword">class</span>, <span class="title">order</span>.<span class="title">next</span>())</span>;</span><br><span class="line"> put(AnonymousAuthenticationFilter<span class="class">.<span class="keyword">class</span>, <span class="title">order</span>.<span class="title">next</span>())</span>;</span><br><span class="line"> filterToOrder.put(</span><br><span class="line"> <span class="string">"org.springframework.security.oauth2.client.web.OAuth2AuthorizationCodeGrantFilter"</span>,</span><br><span class="line"> order.next());</span><br><span class="line"> put(SessionManagementFilter<span class="class">.<span class="keyword">class</span>, <span class="title">order</span>.<span class="title">next</span>())</span>;</span><br><span class="line"> put(ExceptionTranslationFilter<span class="class">.<span class="keyword">class</span>, <span class="title">order</span>.<span class="title">next</span>())</span>;</span><br><span class="line"> put(FilterSecurityInterceptor<span class="class">.<span class="keyword">class</span>, <span class="title">order</span>.<span class="title">next</span>())</span>;</span><br><span class="line"> put(SwitchUserFilter<span class="class">.<span class="keyword">class</span>, <span class="title">order</span>.<span class="title">next</span>())</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这些就是所有内置的过滤器。 他们是通过下面的方法获取自己的序号:</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> Integer <span class="title">getOrder</span><span class="params">(Class<?> clazz)</span> </span>{</span><br><span class="line"> <span class="keyword">while</span> (clazz != <span class="keyword">null</span>) {</span><br><span class="line"> Integer result = filterToOrder.get(clazz.getName());</span><br><span class="line"> <span class="keyword">if</span> (result != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line"> }</span><br><span class="line"> clazz = clazz.getSuperclass();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong>通过过滤器的类全限定名从注册表 <code>filterToOrder</code> 中获取自己的序号,如果没有直接获取到序号通过递归获取父类在注册表中的序号作为自己的序号,序号越小优先级越高。上面的过滤器并非全部会被初始化。有的需要额外引入一些功能包,有的看 <code>HttpSecurity</code> 的配置情况。</strong> 在上一篇<a href="https://www.felord.cn/spring-security-login.html" target="_blank" rel="noopener">文章</a>中。我们禁用了 <code>CSRF</code> 功能,就意味着 <code>CsrfFilter</code> 不会被注册。</p><h2 id="3-内置过滤器讲解"><a href="#3-内置过滤器讲解" class="headerlink" title="3. 内置过滤器讲解"></a>3. 内置过滤器讲解</h2><p>接下来我们就对这些内置过滤器进行一个系统的认识。<strong>我们将按照默认顺序进行讲解</strong>。</p><h3 id="3-1-ChannelProcessingFilter"><a href="#3-1-ChannelProcessingFilter" class="headerlink" title="3.1 ChannelProcessingFilter"></a>3.1 ChannelProcessingFilter</h3><p><code>ChannelProcessingFilter</code> 通常是用来过滤哪些请求必须用 <code>https</code> 协议, 哪些请求必须用 <code>http</code> 协议, 哪些请求随便用哪个协议都行。它主要有两个属性:</p><ul><li> <code>ChannelDecisionManager</code> 用来判断请求是否符合既定的协议规则。它维护了一个 <code>ChannelProcessor</code> 列表 这些<code>ChannelProcessor</code> 是具体用来执行 <code>ANY_CHANNEL</code> 策略 (任何通道都可以), <code>REQUIRES_SECURE_CHANNEL</code> 策略 (只能通过<code>https</code> 通道), <code>REQUIRES_INSECURE_CHANNEL</code> 策略 (只能通过 <code>http</code> 通道)。</li><li> <code>FilterInvocationSecurityMetadataSource</code> 用来存储 url 与 对应的<code>ANY_CHANNEL</code>、<code>REQUIRES_SECURE_CHANNEL</code>、<code>REQUIRES_INSECURE_CHANNEL</code> 的映射关系。</li></ul><p><code>ChannelProcessingFilter</code> 通过 <code>HttpScurity#requiresChannel()</code> 等相关方法引入其配置对象 <code>ChannelSecurityConfigurer</code> 来进行配置。</p><h3 id="3-2-ConcurrentSessionFilter"><a href="#3-2-ConcurrentSessionFilter" class="headerlink" title="3.2 ConcurrentSessionFilter"></a>3.2 ConcurrentSessionFilter</h3><p><code>ConcurrentSessionFilter</code> 主要用来判断<code>session</code>是否过期以及更新最新的访问时间。其流程为:</p><ol><li> <code>session</code> 检测,如果不存在直接放行去执行下一个过滤器。存在则进行下一步。</li><li>根据<code>sessionid</code>从<code>SessionRegistry</code>中获取<code>SessionInformation</code>,从<code>SessionInformation</code>中获取<code>session</code>是否过期;没有过期则更新<code>SessionInformation</code>中的访问日期;<br> 如果过期,则执行<code>doLogout()</code>方法,这个方法会将<code>session</code>无效,并将 <code>SecurityContext</code> 中的<code>Authentication</code>中的权限置空,同时在<code>SecurityContenxtHoloder</code>中清除<code>SecurityContext</code>然后查看是否有跳转的 <code>expiredUrl</code>,如果有就跳转,没有就输出提示信息。</li></ol><p><code>ConcurrentSessionFilter</code> 通过<code>SessionManagementConfigurer</code> 来进行配置。</p><h3 id="3-3-WebAsyncManagerIntegrationFilter"><a href="#3-3-WebAsyncManagerIntegrationFilter" class="headerlink" title="3.3 WebAsyncManagerIntegrationFilter"></a>3.3 WebAsyncManagerIntegrationFilter</h3><p><code>WebAsyncManagerIntegrationFilter</code>用于集成SecurityContext到Spring异步执行机制中的WebAsyncManager。用来处理异步请求的安全上下文。具体逻辑为:</p><ol><li> 从请求属性上获取所绑定的<code>WebAsyncManager</code>,如果尚未绑定,先做绑定。</li><li>从<code>asyncManager</code> 中获取 <code>key</code> 为 <code>CALLABLE_INTERCEPTOR_KEY</code> 的安全上下文多线程处理器 <code>SecurityContextCallableProcessingInterceptor</code>, 如果获取到的为 <code>null</code>,<br> 新建一个 <code>SecurityContextCallableProcessingInterceptor</code> 并绑定 <code>CALLABLE_INTERCEPTOR_KEY</code> 注册到 <code>asyncManager</code> 中。</li></ol><p>这里简单说一下 <code>SecurityContextCallableProcessingInterceptor</code> 。它实现了接口 <code>CallableProcessingInterceptor</code>,<br>当它被应用于一次异步执行时,<code>beforeConcurrentHandling()</code> 方法会在调用者线程执行,该方法会相应地从当前线程获取<code>SecurityContext</code>,然后被调用者线程中执行逻辑时,会使用这个 <code>SecurityContext</code>,从而实现安全上下文从调用者线程到被调用者线程的传输。</p><p><code>WebAsyncManagerIntegrationFilter</code> 通过 <code>WebSecurityConfigurerAdapter#getHttp()</code>方法添加到 <code>HttpSecurity</code> 中成为 <code>DefaultSecurityFilterChain</code> 的一个链节。</p><h3 id="3-4-SecurityContextPersistenceFilter"><a href="#3-4-SecurityContextPersistenceFilter" class="headerlink" title="3.4 SecurityContextPersistenceFilter"></a>3.4 SecurityContextPersistenceFilter</h3><p><code>SecurityContextPersistenceFilter</code> 主要控制 <code>SecurityContext</code> 的在一次请求中的生命周期 。 请求来临时,创建<code>SecurityContext</code> 安全上下文信息,请求结束时清空 <code>SecurityContextHolder</code>。</p><p><code>SecurityContextPersistenceFilter</code> 通过 <code>HttpScurity#securityContext()</code> 及相关方法引入其配置对象 <code>SecurityContextConfigurer</code> 来进行配置。</p><h3 id="3-5-HeaderWriterFilter"><a href="#3-5-HeaderWriterFilter" class="headerlink" title="3.5 HeaderWriterFilter"></a>3.5 HeaderWriterFilter</h3><p><code>HeaderWriterFilter</code> 用来给 <code>http</code> 响应添加一些 <code>Header</code>,比如 <code>X-Frame-Options</code>, <code>X-XSS-Protection</code> ,<code>X-Content-Type-Options</code>。</p><p>你可以通过 <code>HttpScurity#headers()</code> 来定制请求<code>Header</code> 。</p><h3 id="3-6-CorsFilter"><a href="#3-6-CorsFilter" class="headerlink" title="3.6 CorsFilter"></a>3.6 CorsFilter</h3><p>跨域相关的过滤器。这是<code>Spring MVC Java</code>配置和<code>XML</code> 命名空间 <code>CORS</code> 配置的替代方法, 仅对依赖于<code>spring-web</code>的应用程序有用(不适用于<code>spring-webmvc</code>)或 要求在<code>javax.servlet.Filter</code> 级别进行CORS检查的安全约束链接。这个是目前官方的一些解读,但是我还是不太清楚实际机制。</p><p>你可以通过 <code>HttpSecurity#cors()</code> 来定制。</p><h3 id="3-7-CsrfFilter"><a href="#3-7-CsrfFilter" class="headerlink" title="3.7 CsrfFilter"></a>3.7 CsrfFilter</h3><p><code>CsrfFilter</code> 用于防止<code>csrf</code>攻击,前后端使用json交互需要注意的一个问题。</p><p>你可以通过 <code>HttpSecurity.csrf()</code> 来开启或者关闭它。在你使用 <code>jwt</code> 等 <code>token</code> 技术时,是不需要这个的。</p><h3 id="3-8-LogoutFilter"><a href="#3-8-LogoutFilter" class="headerlink" title="3.8 LogoutFilter"></a>3.8 LogoutFilter</h3><p><code>LogoutFilter</code> 很明显这是处理注销的过滤器。</p><p>你可以通过 <code>HttpSecurity.logout()</code> 来定制注销逻辑,非常有用。</p><h3 id="3-9-OAuth2AuthorizationRequestRedirectFilter"><a href="#3-9-OAuth2AuthorizationRequestRedirectFilter" class="headerlink" title="3.9 OAuth2AuthorizationRequestRedirectFilter"></a>3.9 OAuth2AuthorizationRequestRedirectFilter</h3><p>和上面的有所不同,这个需要依赖 <code>spring-scurity-oauth2</code> 相关的模块。该过滤器是处理 <code>OAuth2</code> 请求首选重定向相关逻辑的。以后会我会带你们认识它,请多多关注公众号:<code>Felordcn</code> 。</p><h3 id="3-10-Saml2WebSsoAuthenticationRequestFilter"><a href="#3-10-Saml2WebSsoAuthenticationRequestFilter" class="headerlink" title="3.10 Saml2WebSsoAuthenticationRequestFilter"></a>3.10 Saml2WebSsoAuthenticationRequestFilter</h3><p>这个需要用到 <code>Spring Security SAML</code> 模块,这是一个基于 <code>SMAL</code> 的 <code>SSO</code> 单点登录请求认证过滤器。</p><h4 id="关于SAML"><a href="#关于SAML" class="headerlink" title="关于SAML"></a>关于SAML</h4><p><code>SAML</code> 即安全断言标记语言,英文全称是 <code>Security Assertion Markup Language</code>。它是一个基于 <code>XML</code> 的标准,用于在不同的安全域(<code>security domain</code>)之间交换认证和授权数据。在 <code>SAML</code> 标准定义了身份提供者 (<code>identity provider</code>) 和服务提供者 (<code>service provider</code>),这两者构成了前面所说的不同的安全域。 <code>SAML</code> 是 <code>OASIS</code> 组织安全服务技术委员会(<strong>Security Services Technical Committee</strong>) 的产品。</p><p><code>SAML</code>(<strong>Security Assertion Markup Language</strong>)是一个 <code>XML</code> 框架,也就是一组协议,可以用来传输安全声明。比如,两台远程机器之间要通讯,为了保证安全,我们可以采用加密等措施,也可以采用 <code>SAML</code> 来传输,传输的数据以 <code>XML</code> 形式,符合 <code>SAML</code> 规范,这样我们就可以不要求两台机器采用什么样的系统,只要求能理解 <code>SAML</code> 规范即可,显然比传统的方式更好。<code>SAML</code> 规范是一组 <code>Schema</code> 定义。</p><p>可以这么说,在<code>Web Service</code> 领域,<code>schema</code> 就是规范,在 <code>Java</code> 领域,<code>API</code> 就是规范</p><h3 id="3-11-X509AuthenticationFilter"><a href="#3-11-X509AuthenticationFilter" class="headerlink" title="3.11 X509AuthenticationFilter"></a>3.11 X509AuthenticationFilter</h3><p><code>X509</code> 认证过滤器。你可以通过 <code>HttpSecurity#X509()</code> 来启用和配置相关功能。</p><h3 id="3-12-AbstractPreAuthenticatedProcessingFilter"><a href="#3-12-AbstractPreAuthenticatedProcessingFilter" class="headerlink" title="3.12 AbstractPreAuthenticatedProcessingFilter"></a>3.12 AbstractPreAuthenticatedProcessingFilter</h3><p><code>AbstractPreAuthenticatedProcessingFilter</code> 处理处理经过预先认证的身份验证请求的过滤器的基类,其中认证主体已经由外部系统进行了身份验证。 目的只是从传入请求中提取主体上的必要信息,而不是对它们进行身份验证。</p><p>你可以继承该类进行具体实现并通过 <code>HttpSecurity#addFilter</code> 方法来添加个性化的<code>AbstractPreAuthenticatedProcessingFilter</code> 。</p><h3 id="3-13-CasAuthenticationFilter"><a href="#3-13-CasAuthenticationFilter" class="headerlink" title="3.13 CasAuthenticationFilter"></a>3.13 CasAuthenticationFilter</h3><p><code>CAS</code> 单点登录认证过滤器 。依赖 Spring Security CAS 模块</p><h3 id="3-14-OAuth2LoginAuthenticationFilter"><a href="#3-14-OAuth2LoginAuthenticationFilter" class="headerlink" title="3.14 OAuth2LoginAuthenticationFilter"></a>3.14 OAuth2LoginAuthenticationFilter</h3><p>这个需要依赖 <code>spring-scurity-oauth2</code> 相关的模块。<code>OAuth2</code> 登录认证过滤器。处理通过 <code>OAuth2</code> 进行认证登录的逻辑。</p><h3 id="3-15-Saml2WebSsoAuthenticationFilter"><a href="#3-15-Saml2WebSsoAuthenticationFilter" class="headerlink" title="3.15 Saml2WebSsoAuthenticationFilter"></a>3.15 Saml2WebSsoAuthenticationFilter</h3><p>这个需要用到 <code>Spring Security SAML</code> 模块,这是一个基于 <code>SMAL</code> 的 <code>SSO</code> 单点登录认证过滤器。 <a href="https://www.cnblogs.com/felordcn/p/12142538.html#%E5%85%B3%E4%BA%8ESAML" target="_blank" rel="noopener">关于SAML</a></p><h3 id="3-16-UsernamePasswordAuthenticationFilter"><a href="#3-16-UsernamePasswordAuthenticationFilter" class="headerlink" title="3.16 UsernamePasswordAuthenticationFilter"></a>3.16 UsernamePasswordAuthenticationFilter</h3><p>这个看过我相关文章的应该不陌生了。处理用户以及密码认证的核心过滤器。认证请求提交的<code>username</code>和 <code>password</code>,被封装成<code>token</code>进行一系列的认证,便是主要通过这个过滤器完成的,在表单认证的方法中,这是最最关键的过滤器。</p><p>你可以通过 <code>HttpSecurity#formLogin()</code> 及相关方法引入其配置对象 <code>FormLoginConfigurer</code> 来进行配置。 我们在 <a href="https://www.felord.cn/spring-security-login.html" target="_blank" rel="noopener">Spring Security 实战干货: 玩转自定义登录</a> 已经对其进行过个性化的配置和魔改。</p><h3 id="3-17-ConcurrentSessionFilter"><a href="#3-17-ConcurrentSessionFilter" class="headerlink" title="3.17 ConcurrentSessionFilter"></a>3.17 ConcurrentSessionFilter</h3><p>参见 <a href="https://www.cnblogs.com/felordcn/p/12142538.html#32_ConcurrentSessionFilter_104" target="_blank" rel="noopener">3.2 ConcurrentSessionFilter</a> 。 该过滤器可能会被多次执行。</p><h3 id="3-18-OpenIDAuthenticationFilter"><a href="#3-18-OpenIDAuthenticationFilter" class="headerlink" title="3.18 OpenIDAuthenticationFilter"></a>3.18 OpenIDAuthenticationFilter</h3><p>基于<code>OpenID</code> 认证协议的认证过滤器。 你需要在依赖中依赖额外的相关模块才能启用它。</p><h3 id="3-19-DefaultLoginPageGeneratingFilter"><a href="#3-19-DefaultLoginPageGeneratingFilter" class="headerlink" title="3.19 DefaultLoginPageGeneratingFilter"></a>3.19 DefaultLoginPageGeneratingFilter</h3><p>生成默认的登录页。默认 <code>/login</code> 。</p><h3 id="3-20-DefaultLogoutPageGeneratingFilter"><a href="#3-20-DefaultLogoutPageGeneratingFilter" class="headerlink" title="3.20 DefaultLogoutPageGeneratingFilter"></a>3.20 DefaultLogoutPageGeneratingFilter</h3><p>生成默认的退出页。 默认 <code>/logout</code> 。</p><h3 id="3-21-ConcurrentSessionFilter"><a href="#3-21-ConcurrentSessionFilter" class="headerlink" title="3.21 ConcurrentSessionFilter"></a>3.21 ConcurrentSessionFilter</h3><p>参见 <a href="https://www.cnblogs.com/felordcn/p/12142538.html#32_ConcurrentSessionFilter_104" target="_blank" rel="noopener">3.2 ConcurrentSessionFilter</a> 。 该过滤器可能会被多次执行。</p><h3 id="3-23-DigestAuthenticationFilter"><a href="#3-23-DigestAuthenticationFilter" class="headerlink" title="3.23 DigestAuthenticationFilter"></a>3.23 DigestAuthenticationFilter</h3><p><code>Digest</code>身份验证是 <code>Web</code> 应用程序中流行的可选的身份验证机制 。<code>DigestAuthenticationFilter</code> 能够处理 <code>HTTP</code> 头中显示的摘要式身份验证凭据。你可以通过 <code>HttpSecurity#addFilter()</code> 来启用和配置相关功能。</p><h3 id="3-24-BasicAuthenticationFilter"><a href="#3-24-BasicAuthenticationFilter" class="headerlink" title="3.24 BasicAuthenticationFilter"></a>3.24 BasicAuthenticationFilter</h3><p>和<code>Digest</code>身份验证一样都是<code>Web</code> 应用程序中流行的可选的身份验证机制 。 <code>BasicAuthenticationFilter</code> 负责处理 <code>HTTP</code> 头中显示的基本身份验证凭据。这个 <strong>Spring Security</strong> 的 <strong>Spring Boot</strong> 自动配置默认是启用的 。</p><p><code>BasicAuthenticationFilter</code> 通过 <code>HttpSecurity#httpBasic()</code> 及相关方法引入其配置对象 <code>HttpBasicConfigurer</code> 来进行配置。</p><h3 id="3-25-RequestCacheAwareFilter"><a href="#3-25-RequestCacheAwareFilter" class="headerlink" title="3.25 RequestCacheAwareFilter"></a>3.25 RequestCacheAwareFilter</h3><p>用于用户认证成功后,重新恢复因为登录被打断的请求。当匿名访问一个需要授权的资源时。会跳转到认证处理逻辑,此时请求被缓存。在认证逻辑处理完毕后,从缓存中获取最开始的资源请求进行再次请求。</p><p><code>RequestCacheAwareFilter</code> 通过 <code>HttpScurity#requestCache()</code> 及相关方法引入其配置对象 <code>RequestCacheConfigurer</code> 来进行配置。</p><h3 id="3-26-SecurityContextHolderAwareRequestFilter"><a href="#3-26-SecurityContextHolderAwareRequestFilter" class="headerlink" title="3.26 SecurityContextHolderAwareRequestFilter"></a>3.26 SecurityContextHolderAwareRequestFilter</h3><p>用来 实现<code>j2ee</code>中 <code>Servlet Api</code> 一些接口方法, 比如 <code>getRemoteUser</code> 方法、<code>isUserInRole</code> 方法,在使用 <strong>Spring Security</strong> 时其实就是通过这个过滤器来实现的。</p><p><code>SecurityContextHolderAwareRequestFilter</code> 通过 <code>HttpSecurity.servletApi()</code> 及相关方法引入其配置对象 <code>ServletApiConfigurer</code> 来进行配置。</p><h3 id="3-27-JaasApiIntegrationFilter"><a href="#3-27-JaasApiIntegrationFilter" class="headerlink" title="3.27 JaasApiIntegrationFilter"></a>3.27 JaasApiIntegrationFilter</h3><p>适用于<code>JAAS</code> (<code>Java</code> 认证授权服务)。 如果 <code>SecurityContextHolder</code> 中拥有的 <code>Authentication</code> 是一个 <code>JaasAuthenticationToken</code>,那么该 <code>JaasApiIntegrationFilter</code> 将使用包含在 <code>JaasAuthenticationToken</code> 中的 <code>Subject</code> 继续执行 <code>FilterChain</code>。</p><h3 id="3-28-RememberMeAuthenticationFilter"><a href="#3-28-RememberMeAuthenticationFilter" class="headerlink" title="3.28 RememberMeAuthenticationFilter"></a>3.28 RememberMeAuthenticationFilter</h3><p>处理 <strong><code>记住我</code></strong> 功能的过滤器。</p><p><code>RememberMeAuthenticationFilter</code> 通过 <code>HttpSecurity.rememberMe()</code> 及相关方法引入其配置对象 <code>RememberMeConfigurer</code> 来进行配置。</p><h3 id="3-29-AnonymousAuthenticationFilter"><a href="#3-29-AnonymousAuthenticationFilter" class="headerlink" title="3.29 AnonymousAuthenticationFilter"></a>3.29 AnonymousAuthenticationFilter</h3><p>匿名认证过滤器。<strong>对于 <code>Spring Security</code> 来说,所有对资源的访问都是有 <code>Authentication</code> 的。对于无需登录(<code>UsernamePasswordAuthenticationFilter</code> )直接可以访问的资源,会授予其匿名用户身份</strong>。</p><p><code>AnonymousAuthenticationFilter</code> 通过 <code>HttpSecurity.anonymous()</code> 及相关方法引入其配置对象 <code>AnonymousConfigurer</code> 来进行配置。</p><h3 id="3-30-SessionManagementFilter"><a href="#3-30-SessionManagementFilter" class="headerlink" title="3.30 SessionManagementFilter"></a>3.30 SessionManagementFilter</h3><p><code>Session</code> 管理器过滤器,内部维护了一个 <code>SessionAuthenticationStrategy</code> 用于管理 <code>Session</code> 。</p><p><code>SessionManagementFilter</code> 通过 <code>HttpScurity#sessionManagement()</code> 及相关方法引入其配置对象 <code>SessionManagementConfigurer</code> 来进行配置。</p><h3 id="3-31-ExceptionTranslationFilter"><a href="#3-31-ExceptionTranslationFilter" class="headerlink" title="3.31 ExceptionTranslationFilter"></a>3.31 ExceptionTranslationFilter</h3><p>主要来传输异常事件,还记得之前我们见过的 <code>DefaultAuthenticationEventPublisher</code> 吗?</p><h3 id="3-32-FilterSecurityInterceptor"><a href="#3-32-FilterSecurityInterceptor" class="headerlink" title="3.32 FilterSecurityInterceptor"></a>3.32 FilterSecurityInterceptor</h3><p>这个过滤器决定了访问特定路径应该具备的权限,访问的用户的角色,权限是什么?访问的路径需要什么样的角色和权限?这些判断和处理都是由该类进行的。<strong>如果你要实现动态权限控制就必须研究该类</strong> 。</p><h3 id="3-33-SwitchUserFilter"><a href="#3-33-SwitchUserFilter" class="headerlink" title="3.33 SwitchUserFilter"></a>3.33 SwitchUserFilter</h3><p><code>SwitchUserFilter</code> 是用来做账户切换的。默认的切换账号的<code>url</code>为<code>/login/impersonate</code>,默认注销切换账号的<code>url</code>为<code>/logout/impersonate</code>,默认的账号参数为<code>username</code> 。</p><p>你可以通过此类实现自定义的账户切换。</p>]]></content>
<summary type="html"><p>在spring boot/spring cloud成为java生态中绝对主流的开发框架时,spring家族其他的框架也开始变得越来越流行。对于认证授权的方案也有更多的小伙伴选择从shrio过渡到spring security。但是spring security的难度确实要比shiro高出不少,需要花费较多精力学习。</p></summary>
<category term="java" scheme="https://blog.xiaomo.info/categories/java/"/>
<category term="java" scheme="https://blog.xiaomo.info/tags/java/"/>
</entry>
<entry>
<title>使用flyway版本控制工具维护数据库表</title>
<link href="https://blog.xiaomo.info/2020/flyway/"/>
<id>https://blog.xiaomo.info/2020/flyway/</id>
<published>2020-11-27T00:00:00.000Z</published>
<updated>2022-08-10T23:53:17.651Z</updated>
<content type="html"><![CDATA[<p>最近的公司项目需求需要在现有的项目中集成spring-security,鉴于对spring security的知识较为零散,虽然之前集中学习了一段时间的spring security,但没有实践还需要多学习。今天在查看spring security资料的时候接触到了flyway这个东西,感觉用起来还是挺方便,打算学习学习也一起集成到项目中。</p><a id="more"></a><h3 id="我们为什么需要数据库迁移管理"><a href="#我们为什么需要数据库迁移管理" class="headerlink" title="我们为什么需要数据库迁移管理"></a>我们为什么需要数据库迁移管理</h3><p>比如第一个版本的产品只包含了最基本的功能,而第二版本就需要增加评论功能,这就涉及到数据结构的修改(包括创建新表,修改旧表的列,增加已有表的列等等)。直接进入产品数据库修改数据库并不适合快速的开发节奏,不仅仅不安全,更多的情况下数据库可能并不对外或者并不适合对外直接暴露连接,比如PAAS平台的数据库以服务的形式直接提供。</p><p>对比代码管理的一些实践,很明显在数据库方面做的还欠缺很多。比如代码管理中我们有</p><ul><li> 版本管理(svn,git等等)</li><li> 持续集成技术</li><li> 良好的发布工具和流程</li></ul><p>而在数据库方面会遇到很多问题</p><ul><li> 某台数据库现在是什么状态</li><li> 修改变更的脚本是否已经应用</li><li> 对于生产环境的紧急修复有没有被应用在测试环境</li><li> 如何创建一个新的数据库实例</li></ul><p>数据库迁移工具可以很好的管理这些问题,并提供了以下特性</p><ul><li> 从迁移脚本中创建新的数据库</li><li> 检查数据库状态</li><li> 从一个版本快速到达另外一个版本</li></ul><h2 id="什么是-Flyway"><a href="#什么是-Flyway" class="headerlink" title="什么是 Flyway"></a>什么是 Flyway</h2><p>我们在做开发时,由于项目需求的变化,或者前期设计缺陷,导致在后期需要修改数据库,这应该是一个比较常见的事情,如果项目还没上线,你可能把表删除了重新创建,但是如果项目已经上线了,就不能这样简单粗暴了,我们需要通过 SQL 脚本在已有数据表的基础上进行升级。</p><p>目前 Java 这块,想要对数据库的版本进行管理主要有两个工具:</p><p>Flyway,Liquibase两个工具各有千秋,但是核心功能都是数据库的版本管理,这里主要来看 Flyway。就像我们使用 Git 来管理代码版本一样,Flyway 可以用来管理数据库版本。</p><h3 id="Flyway-的特点"><a href="#Flyway-的特点" class="headerlink" title="Flyway 的特点"></a>Flyway 的特点</h3><p><strong>Flyway</strong> 大受欢迎是因为它具有以下优点:</p><ul><li> <strong>简单</strong> 非常容易安装和学习,同时迁移的方式也很容易被开发者接受。</li><li> <strong>专一</strong> <strong>Flyway</strong> 专注于搞数据库迁移、版本控制而并没有其它副作用。</li><li> <strong>强大</strong> 专为连续交付而设计。让Flyway在应用程序启动时迁移数据库。</li></ul><h3 id="Flyway-的工作机制"><a href="#Flyway-的工作机制" class="headerlink" title="Flyway 的工作机制"></a>Flyway 的工作机制</h3><p><strong>Flyway</strong> 需要在 <code>DB</code> 中先创建一个 <code>metadata</code> 表 (缺省表名为 <code>flyway_schema_history</code>), 在该表中保存着每次 <code>migration</code> (迁移)的记录, 记录包含 <code>migration</code> 脚本的版本号和 <strong>SQL</strong> 脚本的 <code>checksum</code> 值。下图表示了多个数据库版本。</p><p><img src="https://image.xiaomo.info//blog/1460000020850224.png" alt="img"></p><p>对应的 <code>metadata</code> 表记录:</p><table><thead><tr><th>installed_rank</th><th>version</th><th>description</th><th>type</th><th>script</th><th>checksum</th><th>installed_by</th><th>installed_on</th><th>execution_time</th><th>success</th></tr></thead><tbody><tr><td>1</td><td>1</td><td>Initial Setup</td><td>SQL</td><td>V1__Initial_Setup.sql</td><td>1996767037</td><td>axel</td><td>2016-02-04 22:23:00.0</td><td>546</td><td>true</td></tr><tr><td>2</td><td>2</td><td>First Changes</td><td>SQL</td><td>V2__First_Changes.sql</td><td>1279644856</td><td>axel</td><td>2016-02-06 09:18:00.0</td><td>127</td><td>true</td></tr></tbody></table><h3 id="Flyway-的规则"><a href="#Flyway-的规则" class="headerlink" title="Flyway 的规则"></a>Flyway 的规则</h3><p><strong>Flyway</strong> 是如何比较两个 <strong>SQL</strong> 文件的先后顺序呢?它采用 <strong>采用左对齐原则, 缺位用 0 代替</strong> 。举几个例子:</p><blockquote><p> 1.0.1.1 比 1.0.1 版本高。</p><p> 1.0.10 比 1.0.9.4 版本高。</p><p> 1.0.10 和 1.0.010 版本号一样高, 每个版本号部分的前导 0 会被忽略。</p></blockquote><p><strong>Flyway</strong> 将 <strong>SQL</strong> 文件分为 <strong>Versioned</strong> 、<strong>Repeatable</strong> 和 <strong>Undo</strong> 三种:</p><ul><li> <strong>Versioned</strong> 用于版本升级, 每个版本有唯一的版本号并只能执行一次.</li><li> <strong>Repeatable</strong> 可重复执行, 当 <strong>Flyway</strong>检测到 <strong>Repeatable</strong> 类型的 <strong>SQL</strong> 脚本的 <code>checksum</code> 有变动, <strong>Flyway</strong> 就会重新应用该脚本. 它并不用于版本更新, 这类的 <code>migration</code> 总是在 <strong>Versioned</strong> 执行之后才被执行。</li><li> <strong>Undo</strong> 用于撤销具有相同版本的版本化迁移带来的影响。但是该回滚过于粗暴,过于机械化,一般不推荐使用。一般建议使用 <strong>Versioned</strong> 模式来解决。</li></ul><p>这三种的命名规则如下图:</p><p><img src="https://image.xiaomo.info//blog/1460000020850226.png" alt="naming.png"></p><ul><li> <strong>Prefix</strong> 可配置,前缀标识,默认值 <code>V</code> 表示 <strong>Versioned</strong>, <code>R</code> 表示 <strong>Repeatable</strong>, <code>U</code> 表示 <strong>Undo</strong></li><li> <strong>Version</strong> 标识版本号, 由一个或多个数字构成, 数字之间的分隔符可用点 <code>.</code> 或下划线 <code>_</code></li><li> <strong>Separator</strong> 可配置, 用于分隔版本标识与描述信息, 默认为两个下划线 <code>__</code></li><li> <strong>Description</strong> 描述信息, 文字之间可以用下划线 <code>_</code> 或空格 ``分隔</li><li> <strong>Suffix</strong> 可配置, 后续标识, 默认为 <code>.sql</code></li></ul><h3 id="集成到项目中"><a href="#集成到项目中" class="headerlink" title="集成到项目中"></a><strong>集成到项目中</strong></h3><p>如果是在一个全新的项目中使用 Flyway,那么在新建一个 Spring Boot 项目时,就有 Flyway 的选项,如下图:</p><p><img src="https://image.xiaomo.info//blog/63d0f703918fa0ec5564ea32fc8564e83d6ddb03-20201126145549131.jpeg" alt="img"></p><p>项目创建成功后,resources目录下也会多出来一个db/migration目录,这个目录用来存放数据库脚本,如下:</p><p><img src="https://image.xiaomo.info//blog/caef76094b36acaf6a70bc05aacbb01600e99c4b-20201126145410156.jpeg" alt="img"></p><p><strong>注意</strong></p><p><img src="https://image.xiaomo.info//blog/810a19d8bc3eb1355b5f34bbf26d94d4fc1f4494-20201126171130380.png" alt="img"></p><p>这个如果创建项目时就选择了 Flyway 依赖,就会有这个目录。现在我要在已经做好的项目中加入 Flyway,这个目录就需要我手动创建了。 </p><ol><li> 首先在pom中添加 flyway 依赖:</li></ol><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="comment"><!-- 无需版本号 --></span></span><br><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.flywaydb<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>flyway-core<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure><p>gradle</p><figure class="highlight yml"><table><tr><td class="code"><pre><span class="line"><span class="string">implementation</span> <span class="string">'org.flywaydb:flyway-core'</span></span><br></pre></td></tr></table></figure><ol start="2"><li>然后在项目的 resources 目录下,手动创建 db/migration 目录</li><li>然后在该目录下创建数据库脚本,数据库脚本的命名方式如下:</li></ol><p>V<version>__<name>.sql首先是大写字母 V,然后是版本号,要是有小版本可以用下划线隔开,例如 2_1,版本号后面是两个下划线,然后是脚本名称,文件后缀是 .sql。</name></version></p><p>例如我这里创建我的第一个数据库脚本,取名为 V1.20.1__gp.sql。</p><p>可以不用添加额外配置,大家只需要在本地 MySQL 中创建一个空的 gp数据库即可,然后直接启动项目,项目启动成功后,我们查看启动日志:</p><p><img src="https://image.xiaomo.info//blog/71cf3bc79f3df8dcf2705fd4b0024f8d4710283b.jpeg" alt="img"></p><p>从这段启动日志中,我们可以看到 Flyway 的执行信息,数据库脚本的执行执行,同时这里还说了,Flyway 还给创建了一个 flyway_schema_history 表,这个表用来记录数据库的更新历史。</p><p>这个时候,打开本地数据库,我们发现 gp库中该有的表都有了。同时还发现了 flyway_schema_history 表,如下:</p><p><img src="https://image.xiaomo.info//blog/58ee3d6d55fbb2fbd7cbe73c94581da24723dcd1.jpeg" alt="img"></p><p>有了这条记录,下次再启动项目,这个sql脚本文件就不会执行了,因为系统知道这个脚本已经执行过了,如果你还想让脚本再执行一遍,需要手动删除 flyway_schema_history 表中的对应记录,那么项目启动时,这个脚本就会被执行了。</p><p><strong>3.执行细节</strong></p><p>我们在定义脚本的时候,除了 V 字开头的脚本之外,还有一种 R 字开头的脚本,V 字开头的脚本只会执行一次,而 R 字开头的脚本,只要脚本内容发生了变化,启动时候就会执行。使用了 Flyway 之后,如果再想进行数据库版本升级,就不用改以前的数据库脚本了,直接创建新的数据库脚本,项目在启动时检测了有新的更高版本的脚本,就会自动执行,这样,在和其他同事配合工作时,也会方便很多。因为正常我们都是从 Git 上拉代码下来,不拉数据库脚本,这样要是有人更新了数据库,其他同事不一定能够收到最新的通知,使用了 Flyway 就可以有效避免这个问题了。所有的脚本,一旦执行了,就会在 flyway_schema_history 表中有记录,如果你不小心搞错了,可以手动从 flyway_schema_history 表中删除记录,然后修改 SQL 脚本后再重新启动(生产环境不建议)。</p><p><strong>4.其他配置</strong></p><p>在 Spring Boot 中,关于 Flyway 也有不少配置,这些配置都在 application.properties 中进行配置,常用的几个来和大家说下:</p><ol><li><p>spring.flyway.enabled:是否开启 flyway,默认就是开启的</p></li><li><p>spring.flyway.encoding:flyway 字符编码</p></li><li><p>spring.flyway.locations:sql 脚本的目录,默认是 classpath:db/migration,如果有多个,用 , 隔开</p></li><li><p>spring.flyway.clean-disabled:这个属性非常关键,它表示是否要清除已有库下的表,如果执行的脚本是 V1__xxx.sql,那么会先清除已有库下的表,然后再执行脚本,这在开发环境下还挺方便,但是在生产环境下就要命了,而且它默认就是要清除,生产环境一定要自己配置设置为 true。</p></li><li><p>spring.flyway.table:配置数据库信息表的名称,默认是 flyway_schema_history。</p></li></ol><figure class="highlight yml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line"> <span class="attr">flyway:</span></span><br><span class="line"> <span class="attr">enabled:</span> <span class="literal">true</span></span><br><span class="line"> <span class="comment"># 禁止清理数据库表</span></span><br><span class="line"> <span class="attr">clean-disabled:</span> <span class="literal">true</span></span><br><span class="line"> <span class="comment"># 如果数据库不是空表,需要设置成 true,否则启动报错</span></span><br><span class="line"> <span class="attr">baseline-on-migrate:</span> <span class="literal">true</span></span><br><span class="line"> <span class="comment"># 与 baseline-on-migrate: true 搭配使用</span></span><br><span class="line"> <span class="attr">baseline-version:</span> <span class="number">0</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">classpath:db/migration</span></span><br><span class="line"> <span class="attr">table:</span> <span class="string">schemas_version</span></span><br><span class="line"> <span class="attr">validate-on-migrate:</span> <span class="literal">false</span></span><br></pre></td></tr></table></figure><h3 id="Flyway-最佳实践"><a href="#Flyway-最佳实践" class="headerlink" title="Flyway 最佳实践"></a>Flyway 最佳实践</h3><p>通过上面的介绍相信你很快就会使用 <strong>Flyway</strong> 进行数据库版本控制了。这里总结了一些在实际开发中的使用经验:</p><ol><li> 生产务必禁 <code>spring.flyway.cleanDisabled=false</code> 。</li><li> 尽量避免使用 Undo 模式。</li><li> 开发版本号尽量根据团队来进行多层次的命名避免混乱。比如 <code>V1.0.1__ProjectName_{Feature|fix}_Developer_Description.sql</code> ,这种命名同时也可以获取更多脚本的开发者和相关功能的信息。</li><li> <code>spring.flyway.outOfOrder</code> 取值 生产上使用 <code>false</code>,开发中使用 <code>true</code>。</li><li> 多个系统公用一个 数据库 <code>schema</code> 时配置<code>spring.flyway.table</code> 为不同的系统设置不同的 <code>metadata</code> 表名而不使用缺省值 <code>flyway_schema_history</code> 。</li></ol><h2 id="附录-Flyway配置详解"><a href="#附录-Flyway配置详解" class="headerlink" title="附录: Flyway配置详解"></a>附录: Flyway配置详解</h2><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">flyway.baseline-description= # 执行基线时标记已有Schema的描述</span><br><span class="line">flyway.baseline-version=1 # 基线版本默认开始序号 默认为 1. </span><br><span class="line">flyway.baseline-on-migrate=false # 针对非空数据库是否默认调用基线版本 , 这也是我们上面版本号从 2 开始的原因</span><br><span class="line">flyway.check-location=false # 是否开启脚本检查 检查脚本是否存在 默认false</span><br><span class="line">flyway.clean-on-validation-error=false # 验证错误时 是否自动清除数据库 高危操作!!!</span><br><span class="line">flyway.enabled=true # 是否启用 flyway.</span><br><span class="line">flyway.encoding=UTF-8 # 脚本编码.</span><br><span class="line">flyway.ignore-failed-future-migration=true # 在读元数据表时,是否忽略失败的后续迁移.</span><br><span class="line">flyway.init-sqls= # S获取连接后立即执行初始化的SQL语句</span><br><span class="line">flyway.locations=classpath:db/migration # 脚本位置, 默认为classpath: db/migration.</span><br><span class="line">flyway.out-of-order=false # 是否允许乱序(out of order)迁移</span><br><span class="line">flyway.placeholder-prefix= # 设置每个占位符的前缀。 默认值: ${ 。 </span><br><span class="line">flyway.placeholder-replacement=true # 是否要替换占位符。 默认值: true 。 </span><br><span class="line">flyway.placeholder-suffix=} # 设置占位符的后缀。 默认值: } 。 </span><br><span class="line">flyway.placeholders.*= # 设置占位符的值。</span><br><span class="line">flyway.schemas= # Flyway管理的Schema列表,区分大小写。默认连接对应的默认Schema。</span><br><span class="line">flyway.sql-migration-prefix=V # 迁移脚本的文件名前缀。 默认值: V 。 </span><br><span class="line">flyway.sql-migration-separator=__ # 迁移脚本的分割符 默认双下划线</span><br><span class="line">flyway.sql-migration-suffix=.sql # 迁移脚本的后缀 默认 .sql</span><br><span class="line">flyway.table=schema_version # Flyway使用的Schema元数据表名称 默认schema_version</span><br><span class="line">flyway.url= # 待迁移的数据库的JDBC URL。如果没有设置,就使用配置的主数据源。</span><br><span class="line">flyway.user= # 待迁移数据库的登录用户。</span><br><span class="line">flyway.password= # 待迁移数据库的登录用户密码。</span><br><span class="line">flyway.validate-on-migrate=true # 在运行迁移时是否要自动验证。 默认值: true 。</span><br></pre></td></tr></table></figure><h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><ol><li> <a href="https://www.cnblogs.com/moonlightL/p/10576844.html" target="_blank" rel="noopener"><a href="https://www.cnblogs.com/moonlightL/p/10576844.html" target="_blank" rel="noopener">Flyway 简单入门教程</a></a></li><li> <a href="https://segmentfault.com/a/1190000020850220" target="_blank" rel="noopener">Spring Boot 2 实战:使用 Flyway 管理你数据库的版本变更</a></li><li> <a href="https://helloworlde.github.io/2018/01/07/SpringBoot-%E4%BD%BF%E7%94%A8-Flyway/" target="_blank" rel="noopener">Spring Boot 使用 Flyway</a></li></ol>]]></content>
<summary type="html"><p>最近的公司项目需求需要在现有的项目中集成spring-security,鉴于对spring security的知识较为零散,虽然之前集中学习了一段时间的spring security,但没有实践还需要多学习。今天在查看spring security资料的时候接触到了flyway这个东西,感觉用起来还是挺方便,打算学习学习也一起集成到项目中。</p></summary>
<category term="java" scheme="https://blog.xiaomo.info/categories/java/"/>
<category term="mysql" scheme="https://blog.xiaomo.info/tags/mysql/"/>
</entry>
<entry>
<title>使用IDEA保存时自动格式化代码配置</title>
<link href="https://blog.xiaomo.info/2020/autoFormatOnSvae/"/>
<id>https://blog.xiaomo.info/2020/autoFormatOnSvae/</id>
<published>2020-11-26T00:00:00.000Z</published>
<updated>2022-08-10T23:53:17.651Z</updated>
<content type="html"><![CDATA[<p>公司的Java项目在提交代码时会自动执行CI。为了统一代码风格,CI中配置了使用google-java-format来检查代码格式,所以IDEA默认的格式化风格会和google的风格不一样,最终提交代码时需要执行 <code>./gradlew :spotlessApply</code>来再次format,经常会遇到在提交代码时会忘记执行脚本会导致CI构建失败。所以研究了一下如何让IDEA应用google的风格自动进行格式化。</p><a id="more"></a><h2 id="安装google-java-format"><a href="#安装google-java-format" class="headerlink" title="安装google-java-format"></a>安装google-java-format</h2><p>preferences -> plugins -> Browse repositories…<br>搜索google-java-format安装插件</p><img src="https://image.xiaomo.info//blog/image-20201126110557847.png" alt="image-20201126110557847" style="zoom: 25%;"><h3 id="启用google-java-format(AOSP)"><a href="#启用google-java-format(AOSP)" class="headerlink" title="启用google-java-format(AOSP)"></a>启用google-java-format(AOSP)</h3><img src="https://image.xiaomo.info//blog/image-20201126110636172.png" alt="image-20201126110636172" style="zoom:25%;"><h2 id="安装save-actions"><a href="#安装save-actions" class="headerlink" title="安装save actions"></a>安装save actions</h2><p>preferences -> plugins -> Browse repositories…<br>安装save actions<br><img src="https://image.xiaomo.info//blog/image-20201126110724471.png" alt="image-20201126110724471" style="zoom:25%;"></p><h3 id="启用save-actions"><a href="#启用save-actions" class="headerlink" title="启用save actions"></a>启用save actions</h3><p>保存时自动格式化</p><p><img src="https://image.xiaomo.info//blog/image-20201126110813221.png" alt="image-20201126110813221"></p><h3 id="其他配置"><a href="#其他配置" class="headerlink" title="其他配置"></a>其他配置</h3><p>关于formatting、build action和quick fix的部分根据需要开启</p>]]></content>
<summary type="html"><p>公司的Java项目在提交代码时会自动执行CI。为了统一代码风格,CI中配置了使用google-java-format来检查代码格式,所以IDEA默认的格式化风格会和google的风格不一样,最终提交代码时需要执行 <code>./gradlew :spotlessApply</code>来再次format,经常会遇到在提交代码时会忘记执行脚本会导致CI构建失败。所以研究了一下如何让IDEA应用google的风格自动进行格式化。</p></summary>
<category term="java" scheme="https://blog.xiaomo.info/categories/java/"/>
<category term="IDEA" scheme="https://blog.xiaomo.info/tags/IDEA/"/>
</entry>
<entry>
<title>nuxt.js使用介绍</title>
<link href="https://blog.xiaomo.info/2020/vueSSRNuxt/"/>
<id>https://blog.xiaomo.info/2020/vueSSRNuxt/</id>
<published>2020-11-08T00:00:00.000Z</published>
<updated>2022-08-10T23:53:17.651Z</updated>
<content type="html"><![CDATA[<p>vue全家桶还算是比较全面的,从构建工具vue-cli、vite,到vue-router、vuex、element-ui,vue-dev-tools,最后服务器渲染方案nuxt.js等等构成了一个完整的开发生态,用着还是比较省心的。总结一下nux.js用法以供参考。</p><a id="more"></a><h1 id="什么是SSR"><a href="#什么是SSR" class="headerlink" title="什么是SSR"></a>什么是SSR</h1><p>服务端渲染(Server Side Render),即:网页是通过服务端渲染生成后输出给客户端。</p><p>在 SPA(Single Page Application,即单页面应用) 之前的时代,我们的Web架构大都是 SSR,如:Wordpress(PHP)、JSP技术、JavaWeb…或者 DEDE CMS、Discuz! 等这些程序都是传统典型的 SSR 架构, 即:服务端取出数据和模板组合生成 html 输出给前端,前端发生请求时,重新向服务端请求 html 资源,路由也由服务端来控制。</p><p>其次,有个概念叫预渲染(Prerendering)。</p><p>如果你只是用服务端渲染来改善一个少数的营销页面(如 首页,关于,联系 等等)的 SEO,那你可以用预渲染来实现。 预渲染不像服务器渲染那样即时编译 HTML,它只在构建时为了特定的路由生成特定的几个静态页面,等于我们可以通过 Webpack 插件将一些特定页面组件 build 时就编译为 html 文件,直接以静态资源的形式输出给搜索引擎。</p><p>但实际的商业应用中,大部分时候我们需要的是即时渲染,这也是我们今天讨论的主题。</p><h1 id="为什么需要SSR"><a href="#为什么需要SSR" class="headerlink" title="为什么需要SSR"></a>为什么需要SSR</h1><ol><li><p>为了兼容性 </p><p> 虽然现在大部分的浏览器对单页面应用支持优化,但还有一少部分浏览器比较古老。对于世界上的一些地区人,可能只能用1998年产的电脑访问互联网的方式使用计算机。 而 Vue 只能运行在 IE9 以上的浏览器,你可能也想为那些老式浏览器提供基础内容 - 或者是在命令行中使用 Lynx 的时髦的黑客。</p></li><li><p>为了SEO </p><p> SEO是流量是变现的快车道,SEO 是低成本获取流量的最佳方法。</p><p> 目前大部分的搜索引擎仅能抓取URI直接输出的数据资源,对于 Ajax 类的异步请求的数据无法抓取;Google 除外,Google 有自己的<a href="https://developers.google.com/webmasters/ajax-crawling/" target="_blank" rel="noopener">Google’s Webmaster AJAX Crawling Guidelines.</a>技术支持。在大部分的商业应用中,我们有 SEO 的需求,我们需要搜索引擎更多地抓取到我们的内容,更详细地认识到我们的网页结构,而不是仅对首页或特定静态页进行索引,这是 SSR 最重要的意义。</p><p> 简单说就是,我们需要搜素引擎看到这样的代码:</p><p> <img src="https://image.xiaomo.info//blog/image-20201106154440746.png" alt="image-20201106154440746"></p><p> 而不是这样的代码:</p><p> <img src="https://image.xiaomo.info//blog/image-20201106154506477.png" alt="image-20201106154506477"></p><ol start="3"><li><p>为了数据安全 </p><p>现在基本上B/S架构的应用都是前后端分离方式开发的,即前端使用XHR异步获取数据并渲染到页面上,如果我们不使用SSR的话,用户可以直接在调试控制台拿到我们的接口数据。但如果是我们使用的是SSR渲染的话,浏览器收到的就是一个填充好数据的HTML,如果别有用心的人想拿我们的数据。要么人肉复制,要么使用jsonp等技术定位我们的元素。当我们发现有spider在偷我们的数据的时候,稍微换下html的dom结构,偷数据的人就得吭赤吭赤的更新他们的爬虫代码。</p><p>此外,我们还需要在 SSR 的基础上实现 SPA,即:<strong>首屏渲染</strong>。</p></li></ol></li></ol><p>基本流程是:</p><p>在浏览器第一次访问某个 URI 资源的时候(首屏),Web 服务器根据路由拿到对应数据渲染并输出,且输出的数据中包含两部分:</p><p>路由页对应的页面及已渲染好的数据</p><p>完整的SPA程序代码</p><p>在客户端首屏渲染完成之后,此时我们看到的其实已经是一个和之前的 SPA 相差无几的应用程序了,接下来我们进行的任何操作都只是客户端的应用进行交互, 页面/组件由Web端渲染,路由也由浏览器控制,用户只需要和当前浏览器内的应用打交道就可以了。</p><p>之前在各大 SPA 框架还未正式官方支持 SSR 时,有一些第三方的解决方案,如:<a href="https://prerender.io/" target="_blank" rel="noopener">prerender.io</a>, 它们做的事情就是建立HTTP一个中间层,在判断到访问来源是蜘蛛时,输出已缓存好的html数据,此数据若不存在,则调用第三方服务对 html 进行缓存,往复进行。</p><p>另一方法是自行构建蜘蛛渲染逻辑,当识别 UA 为搜索引擎时,拿服务端已准备好的模板和数据进行渲染输出 html 数据,反之,则输出 SPA 应用代码;</p><p>我当时也考虑过此方法,但有很多弊端,如:</p><p>需要针对蜘蛛编写一套独立的渲染模板,因为大部分情况下 SPA 的代码是没法直接在服务端使用的</p><p>搜索引擎若检测到蜘蛛抓取数据和真实访问数据不一致,会做降权惩罚,也就意味着渲染模板还必须和SPA预期输出一模一样</p><p>所以,最好的方法是 SPA 能和服务端使用同一套模板,且使用同一个服务端逻辑分支,再简单说:<strong>最好 Vue、Ng2… 能直接在服务端跑起来</strong>。</p><p>于是,陆续诞生了基于 React 的<a href="https://github.com/zeit/next.js/" target="_blank" rel="noopener">Next.js</a>、基于 Vue 的<a href="https://cn.nuxtjs.org/" target="_blank" rel="noopener">Nuxt.js</a>、Ng2 诞生之日便支持。</p><h1 id="VUE的SSR方案-nuxt"><a href="#VUE的SSR方案-nuxt" class="headerlink" title="VUE的SSR方案(nuxt)"></a>VUE的SSR方案(nuxt)</h1><p>Nuxt.js是使用 Webpack 和 Node.js 进行封装的基于Vue的SSR框架,使用它,你可以不需要自己搭建一套 SSR 程序,而是通过其约定好的文件结构和API就可以实现一个首屏渲染的 Web 应用。之所以叫 Nuxt.js 也是因为受到了 Next.js 的启发。作者是法国的兄弟俩,EvenYou 在微博多次提到,也在欧洲见过哥俩。</p><p>在此之前,国内有一些对 Vue SSR 的整合尝试,但都没有成功,主要在于 Webpack 和 Node 的结合上没有实践出最佳方案, 当我看到 Nuxt.js 以约束文件夹和配置文件<code>nuxt.config.js</code>的方式来管理多个程序组件之间的关系时,就觉得,很酷!</p><p>Nuxt.js 是一个 Node 程序,就像上面说的,我们是要把 Vue 跑在服务端,所以必须使用 Node 环境。我们对 Nuxt.js 应用的访问,实际上是在访问这个 Node.js 程序的路由,程序输出首屏渲染内容 + 用以重新渲染的 SPA 的脚本代码,而路由是由 Nuxt.js 约定好的 pages 文件夹生成的。</p><p>所以,整体上,Nuxt.js 通过各个文件夹和配置文件的约束来管理我们的程序,而又不失扩展性,其有自己的<a href="https://nuxtjs.org/guide/plugins/" target="_blank" rel="noopener">插件机制</a>。</p><h1 id="Nuxt项目的创建"><a href="#Nuxt项目的创建" class="headerlink" title="Nuxt项目的创建"></a>Nuxt项目的创建</h1><ol><li><p>npx</p> <figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">npx create-nuxt-app <project-name></span><br></pre></td></tr></table></figure></li><li><p>yarn</p> <figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">yarn create nuxt-app <project-name></span><br></pre></td></tr></table></figure></li><li><p>npm</p> <figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">npm init nuxt-app <project-name></span><br></pre></td></tr></table></figure></li></ol><h1 id="启动"><a href="#启动" class="headerlink" title="启动"></a>启动</h1><ol><li><p>yarn</p> <figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">cd</span> <project-name></span><br><span class="line">yarn dev</span><br></pre></td></tr></table></figure></li><li><p>npm </p> <figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">cd</span> <project-name></span><br><span class="line">npm run dev</span><br></pre></td></tr></table></figure></li></ol><h1 id="nuxt项目结构介绍"><a href="#nuxt项目结构介绍" class="headerlink" title="nuxt项目结构介绍"></a>nuxt项目结构介绍</h1><p>按照目前的版本,Nuxt.js 的程序的文件结构大概分为以下部分:</p><p><strong>pages</strong>:各页面组件,用于生成对应路由,支持嵌套,支持动态路由</p><p><strong>components</strong>:各组件,用于你自己管理公共组件或非公共组件</p><p><strong>layouts</strong>:宿主布局页面模板组件,用于你可以把不同的页面指定使用不同的布局</p><p><strong>assets</strong>:用于 Webpack 编译的各类资源,通常是一些小的资源,如代替雪碧图之类的图片等东西</p><p><strong>middleware</strong>:中间件,首屏渲染和路由跳转前均执行对应中间件,可以返回promise或直接next(像是一个网关,很实用!)</p><p><strong>plugins</strong>:插件,SPA中用的各类第三方组件和一些node模块都可以在这引入,甚至可以引入自己编写的第三方库</p><p><strong>store</strong>:内置了vuex,可以直接返回数据模块或返回一个自建vuex根对象,具体要翻文档</p><p><strong>其他</strong>:你可以自定义文件夹和别名映射,文档都有提及,这里有<a href="https://github.com/surmon-china/surmon.me/blob/master/nuxt.config.js#L18" target="_blank" rel="noopener">配置代码</a></p><h1 id="nuxt-js-配置文件介绍"><a href="#nuxt-js-配置文件介绍" class="headerlink" title="nuxt.js 配置文件介绍"></a>nuxt.js 配置文件介绍</h1><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> {</span><br><span class="line"> <span class="comment">// Disable server-side rendering (https://go.nuxtjs.dev/ssr-mode)</span></span><br><span class="line"> ssr: <span class="literal">true</span>,</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Global page headers (https://go.nuxtjs.dev/config-head)</span></span><br><span class="line"> head: {</span><br><span class="line"> title: <span class="string">'hello-nuxt'</span>,</span><br><span class="line"> meta: [</span><br><span class="line"> { <span class="attr">charset</span>: <span class="string">'utf-8'</span> },</span><br><span class="line"> { <span class="attr">name</span>: <span class="string">'viewport'</span>, <span class="attr">content</span>: <span class="string">'width=device-width, initial-scale=1'</span> },</span><br><span class="line"> { <span class="attr">hid</span>: <span class="string">'description'</span>, <span class="attr">name</span>: <span class="string">'description'</span>, <span class="attr">content</span>: <span class="string">''</span> },</span><br><span class="line"> ],</span><br><span class="line"> link: [{ <span class="attr">rel</span>: <span class="string">'icon'</span>, <span class="attr">type</span>: <span class="string">'image/x-icon'</span>, <span class="attr">href</span>: <span class="string">'/favicon.ico'</span> }],</span><br><span class="line"> },</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Global CSS (https://go.nuxtjs.dev/config-css)</span></span><br><span class="line"> css: [<span class="string">'element-ui/lib/theme-chalk/index.css'</span>],</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Plugins to run before rendering page (https://go.nuxtjs.dev/config-plugins)</span></span><br><span class="line"> plugins: [<span class="string">'@/plugins/element-ui'</span>],</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Auto import components (https://go.nuxtjs.dev/config-components)</span></span><br><span class="line"> components: <span class="literal">true</span>,</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Modules for dev and build (recommended) (https://go.nuxtjs.dev/config-modules)</span></span><br><span class="line"> buildModules: [</span><br><span class="line"> <span class="comment">// https://go.nuxtjs.dev/typescript</span></span><br><span class="line"> <span class="string">'@nuxt/typescript-build'</span>,</span><br><span class="line"> ],</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Modules (https://go.nuxtjs.dev/config-modules)</span></span><br><span class="line"> modules: [</span><br><span class="line"> <span class="comment">// https://go.nuxtjs.dev/axios</span></span><br><span class="line"> <span class="string">'@nuxtjs/axios'</span>,</span><br><span class="line"> <span class="comment">// https://go.nuxtjs.dev/pwa</span></span><br><span class="line"> <span class="string">'@nuxtjs/pwa'</span>,</span><br><span class="line"> ],</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Axios module configuration (https://go.nuxtjs.dev/config-axios)</span></span><br><span class="line"> axios: {},</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Build Configuration (https://go.nuxtjs.dev/config-build)</span></span><br><span class="line"> build: {</span><br><span class="line"> transpile: [<span class="regexp">/^element-ui/</span>],</span><br><span class="line"> },</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><a href="https://zh.nuxtjs.org/docs/2.x/directory-structure/nuxt-config/" target="_blank" rel="noopener"><code>nuxt.config.js</code></a>对程序的扩展管理可大概分为以下类:</p><p><strong>build</strong>:主要对应 Webpack 中的各配置项,可以对默认的 Webpack 配置进行扩展,如<a href="https://github.com/surmon-china/surmon.me/blob/master/nuxt.config.js#L17" target="_blank" rel="noopener">这里代码</a></p><p><strong>cache</strong>:主要对应内置的组件缓存模块<code>lru-cache</code>的配置对象,有默认值,可选关闭</p><p><strong>css</strong>:对应我们在SPA随处引用样式文件的<code>require</code>语句</p><p><strong>dev</strong>:用于自定义配置环境变量,对应之前<code>webpack.config.js</code>相关文件中的变量语句</p><p><strong>env</strong>:同上息息相关</p><p><strong>generate</strong>:对<code>generate</code>命令执行时的行为做一些定制</p><p><strong>head</strong>:对应<code>vue-meta</code>插件的全局配置,<code>vue-meta</code>用于VUE/SSR程序的文档元信息的管理</p><p><strong>loading</strong>:用于定制化Nuxt.js内置的进度条组件</p><p><strong>performance</strong>:用于配置Node.js服务器性能上的配置</p><p><strong>plugins</strong>:用于管理和应用对应<code>plugins</code>文件夹中的插件</p><p><strong>rootdir</strong>:用于设置 Nuxt.js 应用的根目录(这俩api有很大合并的意义)</p><p><strong>srcdir</strong>:用于设置 Nuxt.js 应用的源码目录(这俩api有很大合并的意义)</p><p><strong>router</strong>:用于对<code>vue-router</code>的扩展和定制,其中还包括了中间件的配置,但并不完美(后面说)</p><p><strong>transition</strong>:用于定制Nuxt.js内置的页面切换过渡效果的默认属性值</p><p><strong>watchers</strong>:用于定制Nuxt.js内置的文件监听模块<code>chokidar</code>和 Webpack 的相关配置项</p><h1 id="路由"><a href="#路由" class="headerlink" title="路由"></a>路由</h1><p>nuxt的没有固定配置路由的文件,它是根据约定自动生成的路由,所有的页面都在pages目录下。</p><ol><li><p>首页 (url:port)</p><p> 对应 pages/index.vue</p></li><li><p>订单列表( url:port/order)</p><p> 对应 pages/order/index.vue</p></li></ol><p>使用时</p><p><code><nuxt-link to="/order">订单</nuxt-link></code></p><h1 id="动态路由"><a href="#动态路由" class="headerlink" title="动态路由"></a><a href="https://www.nuxtjs.cn/guide/routing" target="_blank" rel="noopener">动态路由</a></h1><p>在 Nuxt.js 里面定义带参数的动态路由,需要创建对应的<strong>以下划线作为前缀</strong>的 Vue 文件 或 目录。</p><p>在 Nuxt.js 里面定义带参数的动态路由,需要创建对应的<strong>以下划线作为前缀</strong>的 Vue 文件 或 目录。</p><p>以下目录结构:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">pages/</span><br><span class="line">--| _slug/</span><br><span class="line">-----| comments.vue</span><br><span class="line">-----| index.vue</span><br><span class="line">--| users/</span><br><span class="line">-----| _id.vue</span><br><span class="line">--| index.vue</span><br></pre></td></tr></table></figure><p>Nuxt.js 生成对应的路由配置表为:</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">router: {</span><br><span class="line"> routes: [</span><br><span class="line"> {</span><br><span class="line"> name: <span class="string">'index'</span>,</span><br><span class="line"> path: <span class="string">'/'</span>,</span><br><span class="line"> component: <span class="string">'pages/index.vue'</span></span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> name: <span class="string">'users-id'</span>,</span><br><span class="line"> path: <span class="string">'/users/:id?'</span>,</span><br><span class="line"> component: <span class="string">'pages/users/_id.vue'</span></span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> name: <span class="string">'slug'</span>,</span><br><span class="line"> path: <span class="string">'/:slug'</span>,</span><br><span class="line"> component: <span class="string">'pages/_slug/index.vue'</span></span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> name: <span class="string">'slug-comments'</span>,</span><br><span class="line"> path: <span class="string">'/:slug/comments'</span>,</span><br><span class="line"> component: <span class="string">'pages/_slug/comments.vue'</span></span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>你会发现名称为 <code>users-id</code> 的路由路径带有 <code>:id?</code> 参数,表示该路由是可选的。如果你想将它设置为必选的路由,需要在 <code>users/_id</code> 目录内创建一个 <code>index.vue</code> 文件。</p><p>举例:</p><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">// layouts/default.vue 配置菜单</span><br><span class="line"><template></span><br><span class="line"> <div></span><br><span class="line"> <nuxt-link to="/">home</nuxt-link></span><br><span class="line"> <nuxt-link to="/order">order</nuxt-link></span><br><span class="line"> <Nuxt /></span><br><span class="line"> </div></span><br><span class="line"></template></span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">// pages/order/index.vue 对应/order</span><br><span class="line"></span><br><span class="line"><template></span><br><span class="line"> <div></span><br><span class="line"> <ul></span><br><span class="line"> <li v-for="phone in phones" :key="phone"></span><br><span class="line"> <nuxt-link :to="'/detail/' + phone">{{ phone }}</nuxt-link></span><br><span class="line"> </li></span><br><span class="line"> </ul></span><br><span class="line"> </div></span><br><span class="line"></template></span><br><span class="line"></span><br><span class="line"><script lang="ts"></span><br><span class="line">export default {</span><br><span class="line"> data() {</span><br><span class="line"> return {</span><br><span class="line"> phones: ['锤子', 'iphone', 'google'],</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line">}</span><br><span class="line"></script></span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">// pages/detail/_id.vue 对应 /detail/:id 动态路由</span><br><span class="line"></span><br><span class="line"><template></span><br><span class="line"> <div></span><br><span class="line"> <h2>详情</h2></span><br><span class="line"> <!-- 因为文件名是/detail/_id.vue,所以这里的参数是id --></span><br><span class="line"> {{ $route.params.id }}</span><br><span class="line"> </div></span><br><span class="line"></template></span><br><span class="line"></span><br><span class="line"><script></span><br><span class="line">export default {}</span><br><span class="line"></script></span><br></pre></td></tr></table></figure><h1 id="嵌套路由"><a href="#嵌套路由" class="headerlink" title="嵌套路由"></a>嵌套路由</h1><p>创建内嵌子路由,你需要添加一个 Vue 文件,同时添加一个<strong>与该文件同名</strong>的目录用来存放子视图组件。别忘了在父组件(<code>.vue</code>文件) 内增加 <code><nuxt-child/></code> 用于显示子视图内容。</p><p>父路由</p><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">// pages/users.vue 这是父路由,还需要创建一个同名的users文件夹。子路由/users和/users/profile2,别忘了<nuxt-child/></span><br><span class="line"><template></span><br><span class="line"> <div></span><br><span class="line"> <h2>parent users</h2></span><br><span class="line"> <nuxt-link to="/users">user</nuxt-link></span><br><span class="line"> <nuxt-link to="/users/profile">user detail</nuxt-link></span><br><span class="line"> <nuxt-child /></span><br><span class="line"> </div></span><br><span class="line"></template></span><br><span class="line"></span><br><span class="line"><script></span><br><span class="line">export default {}</span><br><span class="line"></script></span><br></pre></td></tr></table></figure><p>2个子路由</p><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">// pages/users/index.vue</span><br><span class="line"></span><br><span class="line"><template></span><br><span class="line"> <div>用户列表</div></span><br><span class="line"></template></span><br><span class="line"></span><br><span class="line"><script></span><br><span class="line">export default {}</span><br><span class="line"></script></span><br><span class="line"></span><br><span class="line"><style></style></span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">// pages/users/profile.vue</span><br><span class="line"><template></span><br><span class="line"> <div>用户详情</div></span><br><span class="line"></template></span><br><span class="line"></span><br><span class="line"><script></span><br><span class="line">export default {}</span><br><span class="line"></script></span><br><span class="line"></span><br><span class="line"><style></style></span><br></pre></td></tr></table></figure><h1 id="404-页面"><a href="#404-页面" class="headerlink" title="404 页面"></a>404 页面</h1><p><code>_.vue</code>意思是无限次嵌套,因此在pages下创建如下文件</p><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">// pages/_.vue</span><br><span class="line"><template></span><br><span class="line"> <div>404</div></span><br><span class="line"></template></span><br><span class="line"></span><br><span class="line"><script></span><br><span class="line">export default {}</span><br><span class="line"></script></span><br><span class="line"></span><br><span class="line"><style></style></span><br></pre></td></tr></table></figure><h1 id="中间件"><a href="#中间件" class="headerlink" title="中间件"></a>中间件</h1><p>使用中间件做权限认证</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// middleware/auth.js</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="function"><span class="keyword">function</span> (<span class="params">context</span>) </span>{</span><br><span class="line"> context.userAgent = process.server</span><br><span class="line"> ? context.req.headers[<span class="string">'user-agent'</span>]</span><br><span class="line"> : navigator.userAgent</span><br><span class="line">}</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// nutx.config.js 配置</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> { </span><br><span class="line"> router: {</span><br><span class="line"> middleware: <span class="string">'auth'</span>, <span class="comment">// 对应middleware/auth.js</span></span><br><span class="line"> },</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h1 id="使用插件"><a href="#使用插件" class="headerlink" title="使用插件"></a>使用插件</h1><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">yarn add vue-notifications</span><br><span class="line">yarn add mini-toastr</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> Vue <span class="keyword">from</span> <span class="string">'vue'</span></span><br><span class="line"><span class="keyword">import</span> VueNotification <span class="keyword">from</span> <span class="string">'vue-notifications'</span></span><br><span class="line"><span class="keyword">import</span> miniToastr <span class="keyword">from</span> <span class="string">'mini-toastr'</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> toastTypes = {</span><br><span class="line"> success: <span class="string">'success'</span>,</span><br><span class="line"> error: <span class="string">'error'</span>,</span><br><span class="line"> info: <span class="string">'info'</span>,</span><br><span class="line"> warn: <span class="string">'warn'</span>,</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">miniToastr.init({ <span class="attr">types</span>: toastTypes })</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">toast</span>(<span class="params">{ title, message, type, timeout, cb }</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> miniToastr[type](message, title, timeout, cb)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> options = {</span><br><span class="line"> success: toast,</span><br><span class="line"> error: toast,</span><br><span class="line"> info: toast,</span><br><span class="line"> warn: toast,</span><br><span class="line">}</span><br><span class="line">Vue.use(VueNotifications, options)</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// nutx.config.js 配置</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> {</span><br><span class="line"> <span class="comment">// Build Configuration (https://go.nuxtjs.dev/config-build)</span></span><br><span class="line"> <span class="comment">// 如果插件位于node_modules并导出模块,需要将其添加到transpile构建选项:</span></span><br><span class="line"> build: {</span><br><span class="line"> transpile: [<span class="regexp">/^element-ui/</span>, <span class="string">'vue-notifications'</span>],</span><br><span class="line"> },</span><br><span class="line"> <span class="comment">// Plugins to run before rendering page (https://go.nuxtjs.dev/config-plugins)</span></span><br><span class="line"> plugins: [</span><br><span class="line"> { <span class="attr">src</span>: <span class="string">'@/plugins/element-ui'</span>, <span class="attr">ssr</span>: <span class="literal">false</span> },</span><br><span class="line"> { <span class="attr">src</span>: <span class="string">'@/plugins/vue-notifications'</span>, <span class="attr">ssr</span>: <span class="literal">false</span> }, <span class="comment">// 对应/plugins/vue-notifications.js</span></span><br><span class="line"> ],</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h1 id="ts扩展"><a href="#ts扩展" class="headerlink" title="ts扩展"></a>ts扩展</h1><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">yarn add --dev @nuxt/typescript-build @nuxt/types</span><br><span class="line"><span class="comment"># 或</span></span><br><span class="line">npm install --save-dev @nuxt/typescript-build @nuxt/types</span><br></pre></td></tr></table></figure><figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="comment">// /vue-shim.d.ts</span></span><br><span class="line"><span class="keyword">declare</span> <span class="keyword">module</span> "*.vue" {</span><br><span class="line"> <span class="keyword">import</span> Vue <span class="keyword">from</span> <span class="string">'vue'</span></span><br><span class="line"> <span class="keyword">export</span> <span class="keyword">default</span> Vue</span><br><span class="line">}</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// nuxt.config.js</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> {</span><br><span class="line"> buildModules: [</span><br><span class="line"> <span class="comment">// https://go.nuxtjs.dev/typescript</span></span><br><span class="line"> <span class="string">'@nuxt/typescript-build'</span>,</span><br><span class="line"> ],</span><br><span class="line">}</span><br></pre></td></tr></table></figure><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="comment">// tsconfig.json</span></span><br><span class="line">{</span><br><span class="line"> <span class="attr">"compilerOptions"</span>: {</span><br><span class="line"> <span class="attr">"target"</span>: <span class="string">"ES2018"</span>,</span><br><span class="line"> <span class="attr">"module"</span>: <span class="string">"ESNext"</span>,</span><br><span class="line"> <span class="attr">"moduleResolution"</span>: <span class="string">"Node"</span>,</span><br><span class="line"> <span class="attr">"lib"</span>: [</span><br><span class="line"> <span class="string">"ESNext"</span>,</span><br><span class="line"> <span class="string">"ESNext.AsyncIterable"</span>,</span><br><span class="line"> <span class="string">"DOM"</span></span><br><span class="line"> ],</span><br><span class="line"> <span class="attr">"esModuleInterop"</span>: <span class="literal">true</span>,</span><br><span class="line"> <span class="attr">"allowJs"</span>: <span class="literal">true</span>,</span><br><span class="line"> <span class="attr">"sourceMap"</span>: <span class="literal">true</span>,</span><br><span class="line"> <span class="attr">"strict"</span>: <span class="literal">true</span>,</span><br><span class="line"> <span class="attr">"noEmit"</span>: <span class="literal">true</span>,</span><br><span class="line"> <span class="attr">"experimentalDecorators"</span>: <span class="literal">true</span>,</span><br><span class="line"> <span class="attr">"baseUrl"</span>: <span class="string">"."</span>,</span><br><span class="line"> <span class="attr">"paths"</span>: {</span><br><span class="line"> <span class="attr">"~/*"</span>: [</span><br><span class="line"> <span class="string">"./*"</span></span><br><span class="line"> ],</span><br><span class="line"> <span class="attr">"@/*"</span>: [</span><br><span class="line"> <span class="string">"./*"</span></span><br><span class="line"> ]</span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"types"</span>: [</span><br><span class="line"> <span class="string">"@types/node"</span>,</span><br><span class="line"> <span class="string">"@nuxt/types"</span></span><br><span class="line"> ]</span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"exclude"</span>: [</span><br><span class="line"> <span class="string">"node_modules"</span>,</span><br><span class="line"> <span class="string">".nuxt"</span>,</span><br><span class="line"> <span class="string">"dist"</span></span><br><span class="line"> ]</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h1 id="修改端口号"><a href="#修改端口号" class="headerlink" title="修改端口号"></a>修改端口号</h1><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// nuxt.config.js</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> {</span><br><span class="line"> server: {</span><br><span class="line"> port: <span class="number">3030</span>, <span class="comment">// default: 3000</span></span><br><span class="line"> host: <span class="string">'0.0.0.0'</span>, <span class="comment">// default: localhost</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h1 id="异步加载数据的hook-nuxt自动调用"><a href="#异步加载数据的hook-nuxt自动调用" class="headerlink" title="异步加载数据的hook(nuxt自动调用)"></a>异步加载数据的hook(nuxt自动调用)</h1><p><code>yarn add @nuxt/http</code></p><p>需要添加nuxt的http模块</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// nuxt.config.js</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> {</span><br><span class="line"> modules: [</span><br><span class="line"> <span class="comment">// https://go.nuxtjs.dev/axios</span></span><br><span class="line"> <span class="string">'@nuxtjs/axios'</span>,</span><br><span class="line"> <span class="comment">// https://go.nuxtjs.dev/pwa</span></span><br><span class="line"> <span class="string">'@nuxtjs/pwa'</span>,</span><br><span class="line"> <span class="string">'@nuxt/http'</span>,</span><br><span class="line"> ],</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line"><template></span><br><span class="line"> <div></span><br><span class="line"> <h1>Data fetched using asyncData</h1></span><br><span class="line"> <ul></span><br><span class="line"> <li v-for="mountain in mountains" :key="mountain.title"></span><br><span class="line"> {{ mountain.title }}</span><br><span class="line"> </li></span><br><span class="line"> </ul></span><br><span class="line"> </div></span><br><span class="line"></template></span><br><span class="line"></span><br><span class="line"><script></span><br><span class="line">export default {</span><br><span class="line"> async asyncData({ $http }) {</span><br><span class="line"> const mountains = await $http.$get('https://api.nuxtjs.dev/mountains')</span><br><span class="line"> return { mountains }</span><br><span class="line"> },</span><br><span class="line">}</span><br><span class="line"></script></span><br></pre></td></tr></table></figure><p><img src="https://image.xiaomo.info//blog/image-20201124160222403.png" alt="image-20201124160222403"></p>]]></content>
<summary type="html"><p>vue全家桶还算是比较全面的,从构建工具vue-cli、vite,到vue-router、vuex、element-ui,vue-dev-tools,最后服务器渲染方案nuxt.js等等构成了一个完整的开发生态,用着还是比较省心的。总结一下nux.js用法以供参考。</p></summary>
<category term="web" scheme="https://blog.xiaomo.info/categories/web/"/>
<category term="nuxt" scheme="https://blog.xiaomo.info/tags/nuxt/"/>
</entry>
<entry>
<title>web端接入apple Sign in流程</title>
<link href="https://blog.xiaomo.info/2020/webAppleSignIn/"/>
<id>https://blog.xiaomo.info/2020/webAppleSignIn/</id>
<published>2020-11-08T00:00:00.000Z</published>
<updated>2022-08-10T23:53:17.651Z</updated>
<content type="html"><![CDATA[<p>前一段时间接入了google sign in的功能,现在继续接入apple sign in。待apple sign in 正式上线之后,我们的游戏支持 line、Facebook、twitter、google、apple5种三方登陆,基本上涵盖了主流sns。apple和google虽然是不同的平台,但是都是采用上oauth2.0的协议,所以接入流程大同小异。</p><a id="more"></a><h1 id="知识储备"><a href="#知识储备" class="headerlink" title="知识储备"></a>知识储备</h1><ol><li> jwt相关知识(apple采用的是jwt的验证方式)</li><li> js/ts/java基础编程技能</li></ol><h1 id="前提准备"><a href="#前提准备" class="headerlink" title="前提准备"></a>前提准备</h1><p>登陆<a href="https://developer.apple.com/account/resources/identifiers/list" target="_blank" rel="noopener">apple开发者中心</a></p><ol><li> 在identifidr注册一个App ID</li></ol><p><img src="https://image.xiaomo.info//blog/image-20201111175510479.png" alt="image-20201111175510479"></p><p><img src="https://image.xiaomo.info//blog/image-20201111175649365.png" alt="image-20201111175553237"></p><p><img src="https://image.xiaomo.info//blog/image-20201111175741824.png" alt="image-20201111175741824"></p><p>注册之后可以得到3个内容 </p><p><code>Description</code>:对app的描述 </p><p><code>Bundle ID</code>: 注册时第2步填的反向域名(也就是client_id) </p><p><code>App ID Prefix</code>: 也就是teamId,apple自动生成的随机唯一标识(init的时候不需要) </p><ol start="2"><li> 注册在identifier中注册service ID, 有几个环境就可以注册几个service ID,绑定同一个App Id就可以了。</li></ol><p><img src="https://image.xiaomo.info//blog/image-20201111180250556.png" alt="image-20201111180250556"></p><p>注册好之后打开apple sign in 并配置</p><p><img src="https://image.xiaomo.info//blog/image-20201111180330148.png" alt="image-20201111180330148"></p><p><img src="https://image.xiaomo.info//blog/image-20201111180600392.png" alt="image-20201111180600392"></p><p><img src="https://image.xiaomo.info//blog/image-20201111180815110.png" alt="image-20201111180815110"></p><h1 id="代码演示"><a href="#代码演示" class="headerlink" title="代码演示"></a>代码演示</h1><ol><li> 下载声明</li></ol><p><code>yarn add @types/apple-sign-in-api</code></p><p><code>yarn add loadjs</code></p><ol start="2"><li> 初始化 <code>AppleID</code></li></ol><figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> axios <span class="keyword">from</span> <span class="string">'redaxios'</span>;</span><br><span class="line"><span class="keyword">import</span> { AppleWebConfig } <span class="keyword">from</span> <span class="string">'@/shared/models/AppleWebConfig'</span>;</span><br><span class="line"><span class="keyword">import</span> { AuthConfig } <span class="keyword">from</span> <span class="string">'@/client/auth/index'</span>;</span><br><span class="line"><span class="keyword">import</span> { loadScript } <span class="keyword">from</span> <span class="string">'@/client/utils/scriptUtils'</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * apple init</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">appleInit</span>(<span class="params"></span>): <span class="title">Promise</span><<span class="title">void</span>> </span>{</span><br><span class="line"> $(<span class="string">'#apple-login'</span>).removeClass(<span class="string">'hidden'</span>);</span><br><span class="line"> $(<span class="string">'#apple-link'</span>).removeClass(<span class="string">'hidden'</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// get the apple config</span></span><br><span class="line"> <span class="keyword">const</span> response = <span class="keyword">await</span> axios({</span><br><span class="line"> url: <span class="string">'/oauth/v1/config'</span>,</span><br><span class="line"> method: <span class="string">'get'</span>,</span><br><span class="line"> });</span><br><span class="line"> <span class="keyword">const</span> config: AuthConfig = response.data <span class="keyword">as</span> AuthConfig;</span><br><span class="line"> <span class="keyword">if</span> (!config.apple) {</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">const</span> appleWebConfig: AppleWebConfig = <span class="keyword">new</span> AppleWebConfig();</span><br><span class="line"> <span class="built_in">Object</span>.assign(appleWebConfig, config.apple);</span><br><span class="line"> <span class="built_in">console</span>.log(config.apple);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// use the config instance the AppleID</span></span><br><span class="line"> <span class="keyword">await</span> loadScript(appleWebConfig.sdk_url)</span><br><span class="line"> .then(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> AppleID.auth.init({</span><br><span class="line"> clientId: appleWebConfig.identifier, <span class="comment">// 对应配置的反向域名</span></span><br><span class="line"> scope: appleWebConfig.scope, <span class="comment">// name email 需要获取的内容,多个用空格分开</span></span><br><span class="line"> redirectURI: appleWebConfig.redirect_url, <span class="comment">// 回调地址</span></span><br><span class="line"> usePopup: <span class="literal">true</span>, <span class="comment">// 是否用弹窗方式</span></span><br><span class="line"> });</span><br><span class="line"> <span class="built_in">console</span>.info(<span class="string">'apple environment ready'</span>);</span><br><span class="line"> })</span><br><span class="line"> .catch(<span class="function">(<span class="params">e</span>) =></span> <span class="built_in">console</span>.error(e));</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>loadScript 用于动态加载js</p><figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> loadjs, { LoadOptions } <span class="keyword">from</span> <span class="string">'loadjs'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> CacheableOptions <span class="keyword">extends</span> LoadOptions {</span><br><span class="line"> cacheable?: <span class="built_in">boolean</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> loadScript = ((): ((</span><br><span class="line"> src: <span class="built_in">string</span>,</span><br><span class="line"> options?: CacheableOptions</span><br><span class="line">) => <span class="built_in">Promise</span><<span class="built_in">void</span>>) => {</span><br><span class="line"> <span class="keyword">const</span> cache: Record<<span class="built_in">string</span>, <span class="built_in">Promise</span><<span class="built_in">void</span>>> = {};</span><br><span class="line"> <span class="keyword">return</span> (src: <span class="built_in">string</span>, options?: CacheableOptions): <span class="built_in">Promise</span><<span class="built_in">void</span>> => {</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">typeof</span> src !== <span class="string">'string'</span>) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">'src must be string'</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">const</span> opt: CacheableOptions = {</span><br><span class="line"> cacheable: <span class="literal">true</span>,</span><br><span class="line"> numRetries: <span class="number">3</span>,</span><br><span class="line"> ...(options || {}),</span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (opt.cacheable && cache[src]) {</span><br><span class="line"> <span class="keyword">return</span> cache[src];</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">const</span> promise = loadjs([src], {</span><br><span class="line"> ...opt,</span><br><span class="line"> returnPromise: <span class="literal">true</span>,</span><br><span class="line"> });</span><br><span class="line"> <span class="keyword">if</span> (opt.cacheable) {</span><br><span class="line"> cache[src] = promise;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> promise;</span><br><span class="line"> };</span><br><span class="line">})();</span><br></pre></td></tr></table></figure><h1 id="触发登陆"><a href="#触发登陆" class="headerlink" title="触发登陆"></a>触发登陆</h1><figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">button</span> <span class="attr">class</span>=<span class="string">"sns-btn apple sns-btn-apple-link hidden"</span> <span class="attr">id</span>=<span class="string">"apple-login"</span>></span><span class="tag"><<span class="name">span</span>></span>Apple<span class="tag"></<span class="name">span</span>></span><span class="tag"></<span class="name">button</span>></span></span><br></pre></td></tr></table></figure><p>监听按钮点击事件</p><figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line">$(<span class="string">'#apple-login'</span>).on(<span class="string">'click'</span>, <span class="keyword">async</span> () => {</span><br><span class="line"> <span class="keyword">if</span> (!AppleID) {</span><br><span class="line"> showLoginLinkTip();</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">const</span> data = <span class="keyword">await</span> AppleID.auth.signIn();</span><br><span class="line"> <span class="built_in">console</span>.log(data); <span class="comment">// data是auth相关信息</span></span><br><span class="line"></span><br><span class="line">});</span><br></pre></td></tr></table></figure><p>监听成功/失败(可选)</p><figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line"><span class="built_in">document</span>.addEventListener(<span class="string">'AppleIDSignInOnSuccess'</span>, <span class="function">(<span class="params">data: <span class="built_in">any</span></span>) =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(data);</span><br><span class="line">});</span><br><span class="line"><span class="built_in">document</span>.addEventListener(<span class="string">'AppleIDSignInOnFailure'</span>, <span class="function">(<span class="params">error: <span class="built_in">any</span></span>) =></span> {</span><br><span class="line"> <span class="built_in">console</span>.error(error.detail);</span><br><span class="line">});</span><br></pre></td></tr></table></figure><p>实例</p><figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> { AppleWebConfig } <span class="keyword">from</span> <span class="string">'@/shared/models/AppleWebConfig'</span>;</span><br><span class="line"><span class="keyword">import</span> {</span><br><span class="line"> AuthConfig,</span><br><span class="line"> reloadSession,</span><br><span class="line"> SNSLoginBindResponse,</span><br><span class="line">} <span class="keyword">from</span> <span class="string">'@/client/auth/index'</span>;</span><br><span class="line"><span class="keyword">import</span> { loadScript } <span class="keyword">from</span> <span class="string">'@/client/utils/scriptUtils'</span>;</span><br><span class="line"><span class="keyword">import</span> axios <span class="keyword">from</span> <span class="string">'redaxios'</span>;</span><br><span class="line"><span class="keyword">import</span> { PlatformReportType } <span class="keyword">from</span> <span class="string">'@/shared/types/reportTypes/platform'</span>;</span><br><span class="line"><span class="keyword">import</span> { GameConst } <span class="keyword">from</span> <span class="string">'@/shared/constant/gameConst'</span>;</span><br><span class="line"><span class="keyword">import</span> { showLoginLinkTip } <span class="keyword">from</span> <span class="string">'../common/popup'</span>;</span><br><span class="line"><span class="keyword">import</span> { setCookie } <span class="keyword">from</span> <span class="string">'../common/cookie'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> appleWebConfig: AppleWebConfig;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * apple signIn handler</span></span><br><span class="line"><span class="comment"> * @param data authInfo</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">appleSignInSuccess</span>(<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params"> data: AppleSignInAPI.SignInResponseI</span></span></span><br><span class="line"><span class="function"><span class="params"></span>): <span class="title">Promise</span><<span class="title">void</span>> </span>{</span><br><span class="line"> <span class="keyword">const</span> idToken = data.authorization.id_token;</span><br><span class="line"> <span class="keyword">const</span> params = {</span><br><span class="line"> appId: <span class="built_in">window</span>.option.appId,</span><br><span class="line"> idToken,</span><br><span class="line"> provider_type: <span class="string">'apple'</span>,</span><br><span class="line"> };</span><br><span class="line"> <span class="keyword">const</span> loginResponse = <span class="keyword">await</span> axios({</span><br><span class="line"> url: <span class="string">'/oauth/v1/login'</span>,</span><br><span class="line"> method: <span class="string">'post'</span>,</span><br><span class="line"> params,</span><br><span class="line"> }).catch(<span class="function">(<span class="params">error</span>) =></span> {</span><br><span class="line"> showLoginLinkTip(error);</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (loginResponse) {</span><br><span class="line"> <span class="keyword">const</span> result = loginResponse.data <span class="keyword">as</span> SNSLoginBindResponse;</span><br><span class="line"> showLoginLinkTip(result.code);</span><br><span class="line"> reloadSession(<span class="string">'visibilitychange'</span>);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> showLoginLinkTip(<span class="string">'network timeout, please try again later'</span>);</span><br><span class="line"> }</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="comment"> * apple link success handler</span></span><br><span class="line"><span class="comment"> * @param data authInfo</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">appleLinkSuccess</span>(<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params"> data: AppleSignInAPI.SignInResponseI</span></span></span><br><span class="line"><span class="function"><span class="params"></span>): <span class="title">Promise</span><<span class="title">void</span>> </span>{</span><br><span class="line"> <span class="keyword">const</span> idToken = data.authorization.id_token;</span><br><span class="line"> <span class="keyword">const</span> params = {</span><br><span class="line"> appId: <span class="built_in">window</span>.option.appId,</span><br><span class="line"> idToken,</span><br><span class="line"> provider_type: <span class="string">'apple'</span>,</span><br><span class="line"> };</span><br><span class="line"> <span class="keyword">const</span> linkResponse = <span class="keyword">await</span> axios({</span><br><span class="line"> url: <span class="string">'/oauth/v1/link'</span>,</span><br><span class="line"> method: <span class="string">'post'</span>,</span><br><span class="line"> params,</span><br><span class="line"> }).catch(<span class="function">(<span class="params">error</span>) =></span> {</span><br><span class="line"> showLoginLinkTip(error);</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (linkResponse) {</span><br><span class="line"> <span class="keyword">const</span> result = linkResponse.data <span class="keyword">as</span> SNSLoginBindResponse;</span><br><span class="line"> showLoginLinkTip(result.code);</span><br><span class="line"> reloadSession(<span class="string">'visibilitychange'</span>);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> showLoginLinkTip(<span class="string">'network timeout ,please try again later'</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// eslint-disable-next-line @typescript-eslint/ban-types</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">appleSignInFail</span>(<span class="params">error: object</span>): <span class="title">void</span> </span>{</span><br><span class="line"> <span class="built_in">console</span>.error(error);</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">getQueryVariable</span>(<span class="params">variable: <span class="built_in">string</span></span>): <span class="title">string</span> | <span class="title">null</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> URL(<span class="built_in">window</span>.location.href).searchParams.get(variable);</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="comment"> * apple init</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">appleInit</span>(<span class="params"></span>): <span class="title">Promise</span><<span class="title">void</span>> </span>{</span><br><span class="line"> <span class="keyword">const</span> cookie = getQueryVariable(GameConst.appleFeatureSwitchCookie);</span><br><span class="line"> <span class="keyword">if</span> (cookie) {</span><br><span class="line"> setCookie(GameConst.appleFeatureSwitchCookie, cookie, <span class="number">360000</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// get the apple config</span></span><br><span class="line"> <span class="keyword">const</span> response = <span class="keyword">await</span> axios({</span><br><span class="line"> url: <span class="string">'/oauth/v1/config'</span>,</span><br><span class="line"> method: <span class="string">'get'</span>,</span><br><span class="line"> });</span><br><span class="line"> <span class="keyword">const</span> config: AuthConfig = response.data <span class="keyword">as</span> AuthConfig;</span><br><span class="line"> <span class="keyword">if</span> (!config.apple) {</span><br><span class="line"> <span class="built_in">console</span>.error(<span class="string">"can't get the apple config"</span>);</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> $(<span class="string">'#apple-login'</span>).removeClass(<span class="string">'hidden'</span>);</span><br><span class="line"> $(<span class="string">'#apple-link'</span>).removeClass(<span class="string">'hidden'</span>);</span><br><span class="line"> appleWebConfig = <span class="keyword">new</span> AppleWebConfig();</span><br><span class="line"> <span class="built_in">Object</span>.assign(appleWebConfig, config.apple);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// use the config instance the AppleID</span></span><br><span class="line"> <span class="keyword">await</span> loadScript(appleWebConfig.sdk_url).catch(<span class="function">(<span class="params">e</span>) =></span> <span class="built_in">console</span>.error(e));</span><br><span class="line"> <span class="built_in">console</span>.info(<span class="string">'apple environment ready!'</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="comment"> * login listener</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line">$(<span class="string">'#apple-login'</span>).on(<span class="string">'click'</span>, <span class="keyword">async</span> () => {</span><br><span class="line"> <span class="keyword">if</span> (!AppleID) {</span><br><span class="line"> showLoginLinkTip();</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> AppleID.auth.init({</span><br><span class="line"> clientId: appleWebConfig.identifier,</span><br><span class="line"> scope: appleWebConfig.scope,</span><br><span class="line"> redirectURI: appleWebConfig.login_callback,</span><br><span class="line"> usePopup: <span class="literal">true</span>,</span><br><span class="line"> });</span><br><span class="line"> <span class="keyword">if</span> (!AppleID) {</span><br><span class="line"> showLoginLinkTip();</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">const</span> data: AppleSignInAPI.SignInResponseI = <span class="keyword">await</span> AppleID.auth.signIn();</span><br><span class="line"> <span class="keyword">if</span> (data) {</span><br><span class="line"> appleSignInSuccess(data);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (e) {</span><br><span class="line"> appleSignInFail(e);</span><br><span class="line"> }</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="comment"> * apple link listener</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line">$(<span class="string">'#apple-link'</span>).on(<span class="string">'click'</span>, <span class="keyword">async</span> () => {</span><br><span class="line"> <span class="keyword">if</span> (!AppleID) {</span><br><span class="line"> showLoginLinkTip();</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> AppleID.auth.init({</span><br><span class="line"> clientId: appleWebConfig.identifier,</span><br><span class="line"> scope: appleWebConfig.scope,</span><br><span class="line"> redirectURI: appleWebConfig.link_callback,</span><br><span class="line"> usePopup: <span class="literal">true</span>,</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (!AppleID) {</span><br><span class="line"> showLoginLinkTip();</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">const</span> data: AppleSignInAPI.SignInResponseI = <span class="keyword">await</span> AppleID.auth.signIn();</span><br><span class="line"> <span class="keyword">if</span> (data) {</span><br><span class="line"> appleLinkSuccess(data);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (e) {</span><br><span class="line"> appleSignInFail(e);</span><br><span class="line"> }</span><br><span class="line">});</span><br></pre></td></tr></table></figure><p>服务端代码(主要验证idToken的合法性和自己的用户系统关联起来)</p><p>build.gradle</p><figure class="highlight groovy"><table><tr><td class="code"><pre><span class="line">implementation <span class="string">'com.nimbusds:nimbus-jose-jwt:9.0'</span></span><br></pre></td></tr></table></figure><p>application.yml</p><figure class="highlight yml"><table><tr><td class="code"><pre><span class="line"><span class="attr">apple:</span></span><br><span class="line"> <span class="attr">identifier:</span> <span class="string">info.xiaomo.app</span></span><br><span class="line"> <span class="attr">login_callback:</span> <span class="string">https://info.xiaomo.app/oauth/v1/login</span></span><br><span class="line"> <span class="attr">link_callback:</span> <span class="string">https://info.xiaomo.app/oauth/v1/link</span></span><br><span class="line"> <span class="attr">sdk_url:</span> <span class="string">https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/ja_JP/appleid.auth.js</span></span><br><span class="line"> <span class="attr">verify_url:</span> <span class="string">https://appleid.apple.com/auth/token</span></span><br><span class="line"> <span class="attr">team_id:</span> <span class="string">your</span> <span class="string">teamId</span></span><br><span class="line"> <span class="attr">scope:</span> <span class="string">email</span> <span class="string">name</span></span><br></pre></td></tr></table></figure><p>配置的实体类</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="meta">@ConfigurationProperties</span>(prefix = <span class="string">"apple"</span>)</span><br><span class="line"><span class="meta">@Validated</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">AppleProperties</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> String identifier;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> String sdkUrl;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> String verifyUrl;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> String teamId;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> String scope;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> String loginCallback;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> String linkCallback;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>配置文件和实体类关联</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@EnableConfigurationProperties</span>(AppleProperties<span class="class">.<span class="keyword">class</span>)</span></span><br><span class="line"><span class="class">@<span class="title">RequiredArgsConstructor</span></span></span><br><span class="line"><span class="class"><span class="title">public</span> <span class="title">class</span> <span class="title">AppleConfiguration</span> </span>{}</span><br></pre></td></tr></table></figure><p>验证jwt</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"></span><br><span class="line"><span class="meta">@autowird</span></span><br><span class="line"><span class="keyword">private</span> AppleProperties appleProperties;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> AppleJwtPayload <span class="title">verifyAppleJWTAndGetPayload</span><span class="params">(String idToken)</span></span></span><br><span class="line"><span class="function"> <span class="keyword">throws</span> IllegalProviderProfileException </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> JWSObject jwt = JWSObject.parse(idToken);</span><br><span class="line"> JWSHeader appleJwtHeader = jwt.getHeader();</span><br><span class="line"> AppleJwtPayload appleJwtPayload =</span><br><span class="line"> JsonUtil.toJsonObject(jwt.getPayload().toString(), AppleJwtPayload<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line"> jwtVerifyHandler(appleJwtHeader, idToken);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> appleJwtPayload;</span><br><span class="line"></span><br><span class="line"> } <span class="keyword">catch</span> (ParseException e) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> IllegalProviderProfileException();</span><br><span class="line"> }</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="comment"> * verify jwt</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> appleJwtHeader appleJwtHeader</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> jwt jwt</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">jwtVerifyHandler</span><span class="params">(JWSHeader appleJwtHeader, String jwt)</span></span></span><br><span class="line"><span class="function"> <span class="keyword">throws</span> IllegalProviderProfileException </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> Optional<JWKSet> publicKeyCache = findPublicKey();</span><br><span class="line"> <span class="keyword">if</span> (publicKeyCache.isEmpty()) {</span><br><span class="line"> reloadPublicKeyCache();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (publicKeyCache.isEmpty()) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> IllegalProviderProfileException();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> RSAKey rsaKey =</span><br><span class="line"> publicKeyCache.get().getKeyByKeyId(appleJwtHeader.getKeyID()).toRSAKey();</span><br><span class="line"> SignedJWT signedJWT = SignedJWT.parse(jwt);</span><br><span class="line"> JWSVerifier verifier = <span class="keyword">new</span> RSASSAVerifier(rsaKey);</span><br><span class="line"> <span class="keyword">boolean</span> verify = signedJWT.verify(verifier);</span><br><span class="line"> <span class="keyword">if</span> (!verify) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> IllegalProviderProfileException();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@NotNull</span> List<String> audience = signedJWT.getJWTClaimsSet().getAudience();</span><br><span class="line"> <span class="meta">@Nullable</span> Date expirationTime = signedJWT.getJWTClaimsSet().getExpirationTime();</span><br><span class="line"> <span class="meta">@Nullable</span> String issuer = signedJWT.getJWTClaimsSet().getIssuer();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (!<span class="string">"https://appleid.apple.com"</span>.equals(issuer)) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> IllegalProviderProfileException();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (expirationTime == <span class="keyword">null</span> || expirationTime.before(<span class="keyword">new</span> Date())) {</span><br><span class="line"> log.error(</span><br><span class="line"> <span class="string">"token expired: {} {}->{}"</span>,</span><br><span class="line"> jwt,</span><br><span class="line"> expirationTime == <span class="keyword">null</span> ? <span class="keyword">null</span> : expirationTime.getTime(),</span><br><span class="line"> <span class="keyword">new</span> Date().getTime());</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> IllegalProviderProfileException();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (!audience.contains(appleProperties.getIdentifier())) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> IllegalProviderProfileException();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> } <span class="keyword">catch</span> (JOSEException | ParseException e) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> IllegalProviderProfileException();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Scheduled</span>(fixedRate = <span class="number">5</span> * <span class="number">60</span> * <span class="number">1000</span>) <span class="comment">// auto reload/5 min</span></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">reloadPublicKeyCache</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> URL authKeyUrl = <span class="keyword">new</span> URL(<span class="string">"https://appleid.apple.com/auth/keys"</span>);</span><br><span class="line"> JWKSet publicKeys = JWKSet.load(authKeyUrl);</span><br><span class="line"> publicKeysCache.invalidateAll();</span><br><span class="line"> publicKeysCache.put(<span class="string">"key"</span>, publicKeys);</span><br><span class="line"> } <span class="keyword">catch</span> (IOException | ParseException e) {</span><br><span class="line"> log.error(<span class="string">"get apple auth key error:{}"</span>, e.getMessage());</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">public</span> Optional<JWKSet> <span class="title">findPublicKey</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> Optional.ofNullable(publicKeysCache.getIfPresent(<span class="string">"key"</span>));</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><ol><li><a href="https://doofox.cn/sign-with-in-apple.html" target="_blank" rel="noopener"> Sign with in Apple,网站配置 Apple 登录</a></li><li><a href="https://segmentfault.com/a/1190000020786994" target="_blank" rel="noopener">Sign in with Apple NODE,web端接入苹果第三方登录</a></li><li><a href="https://bugjia.net/200531/804216.html" target="_blank" rel="noopener">java-如何在Nimbus JOSE JWT中验证令牌签名</a></li></ol><h1 id="后记"><a href="#后记" class="headerlink" title="后记"></a>后记</h1><p>文章中贴的实例代码只是提供一个开发思路,实际业务开发中牵扯的内容比较多。像是一些零散的util方法、css样式、业务耦合较重的内容等等没有一一提及,所以直接拷贝代码的话肯定会有不少的依赖文件找不到。</p>]]></content>
<summary type="html"><p>前一段时间接入了google sign in的功能,现在继续接入apple sign in。待apple sign in 正式上线之后,我们的游戏支持 line、Facebook、twitter、google、apple5种三方登陆,基本上涵盖了主流sns。apple和google虽然是不同的平台,但是都是采用上oauth2.0的协议,所以接入流程大同小异。</p></summary>
<category term="web" scheme="https://blog.xiaomo.info/categories/web/"/>
<category term="web" scheme="https://blog.xiaomo.info/tags/web/"/>
</entry>
<entry>
<title>node项目监控工具之pm2</title>
<link href="https://blog.xiaomo.info/2020/pm2UseApi/"/>
<id>https://blog.xiaomo.info/2020/pm2UseApi/</id>
<published>2020-11-08T00:00:00.000Z</published>
<updated>2022-08-10T23:53:17.651Z</updated>
<content type="html"><![CDATA[<p>用了好几年的宝塔,不知道什么时候出了个pm2管理器,才开始以为它是对node版本切换管理的,直到今天看到有一篇文章才发现理解错了。研究了一下pm2的作用和用法,也顺便玩一玩宝塔中的pm2。</p><a id="more"></a><blockquote><p> pm2官方文档:<a href="http://pm2.keymetrics.io/docs/usage/quick-start/" target="_blank" rel="noopener">http://pm2.keymetrics.io/docs/usage/quick-start/</a></p></blockquote><h2 id="简单教程"><a href="#简单教程" class="headerlink" title="简单教程"></a>简单教程</h2><p>首先需要安装pm2:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">npm install -g pm2</span><br></pre></td></tr></table></figure><p>运行:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">pm2 start app.js</span><br></pre></td></tr></table></figure><p>开机自动运行:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">pm2 start app.js --watch</span><br></pre></td></tr></table></figure><p>开机自启:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">pm2 startup</span><br><span class="line">pm2 save</span><br></pre></td></tr></table></figure><p>初次安装并运行,会有一个高大上的界面:</p><p><img src="https://image.xiaomo.info//blog/1240.png" alt="img"></p><p>那么pm2与<a href="http://www.jianshu.com/p/82a64aee0710" target="_blank" rel="noopener">forever</a>相比,比较有哪些高大上的功能呢?我们看一下对比表格:</p><table><thead><tr><th align="center">Feature</th><th align="left">Forever</th><th align="left">PM2</th></tr></thead><tbody><tr><td align="center">Keep Alive</td><td align="left">✔</td><td align="left">✔</td></tr><tr><td align="center">Coffeescript</td><td align="left">✔</td><td align="left"></td></tr><tr><td align="center">Log aggregation</td><td align="left"></td><td align="left">✔</td></tr><tr><td align="center">API</td><td align="left"></td><td align="left">✔</td></tr><tr><td align="center">Terminal monitoring</td><td align="left"></td><td align="left">✔</td></tr><tr><td align="center">Clustering</td><td align="left"></td><td align="left">✔</td></tr><tr><td align="center">JSON configuration</td><td align="left"></td><td align="left">✔</td></tr></tbody></table><p>我们可以很直观的看出,pm2相比较Forever,功能更加强大一些。</p><h2 id="查看运行状态"><a href="#查看运行状态" class="headerlink" title="查看运行状态"></a>查看运行状态</h2><p>我们可以通过简单的命令查看应用的运行状态:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">pm2 list</span><br></pre></td></tr></table></figure><p>效果如下:</p><p><img src="https://image.xiaomo.info//blog/1240-20201106184135599.png" alt="img"></p><blockquote><p> ANodeBlog应用正在运行,pid为31480,并且占用内存为89.113 MB。</p></blockquote><h2 id="追踪资源运行情况"><a href="#追踪资源运行情况" class="headerlink" title="追踪资源运行情况"></a>追踪资源运行情况</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">pm2 monit</span><br></pre></td></tr></table></figure><p>会看到应用资源的实时运行情况</p><p><img src="https://image.xiaomo.info//blog/1240-20201106184139553.png" alt="img"></p><h2 id="查看应用详细部署状态"><a href="#查看应用详细部署状态" class="headerlink" title="查看应用详细部署状态"></a>查看应用详细部署状态</h2><p>如果我们想要查看一个应用详细的运行状态,比如<code>ANodeBlog</code>的状态,可以运行:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">pm2 describe 3</span><br></pre></td></tr></table></figure><blockquote><p> “3”是指App Id。</p></blockquote><p>结果如下:</p><p><img src="https://image.xiaomo.info//blog/1240-20201106184144285.png" alt="img"></p><h2 id="查看日志"><a href="#查看日志" class="headerlink" title="查看日志"></a>查看日志</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">pm2 logs</span><br></pre></td></tr></table></figure><p>系统会打印出详细的logs。</p><h2 id="重启应用"><a href="#重启应用" class="headerlink" title="重启应用"></a>重启应用</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">pm2 restart appId</span><br></pre></td></tr></table></figure><h2 id="停止应用"><a href="#停止应用" class="headerlink" title="停止应用"></a>停止应用</h2><p>想要终止应用,只需要运行:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">pm2 stop app.js</span><br></pre></td></tr></table></figure><h2 id="强健的API"><a href="#强健的API" class="headerlink" title="强健的API"></a>强健的API</h2><p>在项目中运行:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">pm2 web</span><br></pre></td></tr></table></figure><p>然后浏览器访问<a href="http://localhost:9615/" target="_blank" rel="noopener">http://localhost:9615</a> 你会有惊喜!</p><h2 id="预定义运行配置文件"><a href="#预定义运行配置文件" class="headerlink" title="预定义运行配置文件"></a>预定义运行配置文件</h2><p>我们可以预定义一个配置文件,然后制定运行这个配置文件,比如我们定义一个文件<code>process.json</code>,内容如下:</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"apps"</span>: [</span><br><span class="line"> {</span><br><span class="line"> <span class="attr">"name"</span>: <span class="string">"ANodeBlog"</span>,</span><br><span class="line"> <span class="attr">"script"</span>: <span class="string">"bin/www"</span>,</span><br><span class="line"> <span class="attr">"watch"</span>: <span class="string">"../"</span>,</span><br><span class="line"> <span class="attr">"log_date_format"</span>: <span class="string">"YYYY-MM-DD HH:mm Z"</span></span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>然后可以通过</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">pm2 start process.json</span><br></pre></td></tr></table></figure><p>运行这个App。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>常用命令总结如下:</p><ol><li><p>安装pm2</p> <figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">npm install -g pm2</span><br></pre></td></tr></table></figure></li><li><p>启动应用</p> <figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">pm2 start app.js</span><br></pre></td></tr></table></figure></li><li><p>列出所有应用</p> <figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">pm2 list</span><br></pre></td></tr></table></figure></li><li><p>查看资源消耗</p> <figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">pm2 monit</span><br></pre></td></tr></table></figure></li><li><p>查看某一个应用状态</p> <figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">pm2 describe [app id]</span><br></pre></td></tr></table></figure></li><li><p>查看所有日志</p> <figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">pm2 logs</span><br></pre></td></tr></table></figure></li><li><p>重启应用</p> <figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">pm2 restart [app id]</span><br></pre></td></tr></table></figure></li><li><p>停止应用</p> <figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">pm2 stop [app id]</span><br></pre></td></tr></table></figure></li><li><p>开启api访问</p> <figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">pm2 web</span><br></pre></td></tr></table></figure></li></ol><p>更多pm2内容请参考官方文档:<a href="http://pm2.keymetrics.io/docs/usage/quick-start" target="_blank" rel="noopener">http://pm2.keymetrics.io/docs/usage/quick-start</a></p><h1 id="宝塔中的pm2管理器使用方法"><a href="#宝塔中的pm2管理器使用方法" class="headerlink" title="宝塔中的pm2管理器使用方法"></a>宝塔中的pm2管理器使用方法</h1><p><strong>1、安装版本</strong> </p><p>windows默认支持3个版本(8.x/9.x/10.x),选择任意一个版本安装即可 安装时间可能会比较久,请耐心等待.. <img src="https://www.bt.cn/bbs/data/attachment/forum/201908/05/113406f0lvelgqogeq0yop.png" alt="img"> <img src="https://www.bt.cn/bbs/data/attachment/forum/201908/05/111557qgguiyzy52nnxuuk.png" alt="img"> </p><p><strong>2、添加项目</strong> </p><p>如果添加项目后无法启动,可能是当前环境变量未生效,需要重启面板或者服务器后,重新添加项目 <img src="https://www.bt.cn/bbs/data/attachment/forum/201908/05/111735k1cw3t5a1oc5cs85.png" alt="img"> <img src="https://www.bt.cn/bbs/data/attachment/forum/201908/05/111735jsjqgpqwjwddg0j4.png" alt="img"> </p><p><strong>3、给项目绑定一个域名</strong> </p><p>如图点击映射,出现绑定域名窗口,绑定完成之后,可在网站管理查看到对应的网站 <img src="https://www.bt.cn/bbs/data/attachment/forum/201908/05/111912i5o6eb6uabnc89bo.png" alt="img"> </p><p><strong>4、访问网站</strong> </p><p>浏览器输入刚刚绑定的域名如图:<a href="http://node.ffce.cn/" target="_blank" rel="noopener">http://node.ffce.cn</a> 至此第一个node.js搭建成功 <img src="https://www.bt.cn/bbs/data/attachment/forum/201908/05/112153ilqp5s11sqy5rsqp.png" alt="img"> </p><p> <strong>5、添加模块</strong> </p><p>如果项目需要安装其他模块,则通过模块管理安装,如图我需要安装express <img src="https://www.bt.cn/bbs/data/attachment/forum/201908/05/112455zgqi8qwzwiy8ky9m.png" alt="img"> <img src="https://www.bt.cn/bbs/data/attachment/forum/201908/05/112455ny999dz59wq3qf9c.png" alt="img"> </p><p><strong>6、查看日志</strong> </p><p> <img src="https://www.bt.cn/bbs/data/attachment/forum/201908/05/112914dbk57ooxx7q3q57k.png" alt="img"> </p><p>测试js <img src="https://www.bt.cn/bbs/static/image/filetype/html.gif" alt="img"> <a href="https://www.bt.cn/bbs/forum.php?mod=attachment&aid=MjgyODB8YTI4NzM0ZDl8MTYwNDY0NzY4OHwwfDM1NjA3" target="_blank" rel="noopener">app.js</a> <em>(423 Bytes, 下载次数: 1191)</em></p><h1 id="在mac上安装bt"><a href="#在mac上安装bt" class="headerlink" title="在mac上安装bt"></a>在mac上安装bt</h1><h2 id="安装Docker"><a href="#安装Docker" class="headerlink" title="安装Docker"></a>安装Docker</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">brew cask install docker</span><br></pre></td></tr></table></figure><h2 id="在Docker里安装CentOS"><a href="#在Docker里安装CentOS" class="headerlink" title="在Docker里安装CentOS"></a>在Docker里安装CentOS</h2><p>Docker Hub:<a href="https://hub.docker.com/_/centos" target="_blank" rel="noopener">https://hub.docker.com/_/centos</a></p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker pull centos</span><br></pre></td></tr></table></figure><h2 id="映射宝塔端口"><a href="#映射宝塔端口" class="headerlink" title="映射宝塔端口"></a>映射宝塔端口</h2><p>创建一个CentOS容器并映射<code>8888</code>端口,复制返回的<code>容器ID</code></p><figure class="highlight applescript"><table><tr><td class="code"><pre><span class="line">docker <span class="built_in">run</span> -d -<span class="keyword">it</span> -p <span class="number">10086</span>:<span class="number">10086</span> centos</span><br></pre></td></tr></table></figure><p><img src="https://img-blog.csdnimg.cn/20190901231059858.jpg" alt="img"></p><h2 id="进入CentOS安装宝塔"><a href="#进入CentOS安装宝塔" class="headerlink" title="进入CentOS安装宝塔"></a>进入CentOS安装宝塔</h2><p>进入容器终端</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker <span class="built_in">exec</span> -it [容器ID] bash</span><br></pre></td></tr></table></figure><p>__在CentOS中__执行宝塔安装命令</p><p>在<a href="https://www.bt.cn/bbs/forum-36-1.html" target="_blank" rel="noopener">宝塔论坛</a>中打开安装教程,找到最新的版本安装命令,像以下这样的命令。</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh</span><br></pre></td></tr></table></figure><p>安装完成后,提示给的外网IP是用不了的,所以这里需要用<code>本地ip</code>访问面板</p><p><img src="https://img-blog.csdnimg.cn/2019090123111685.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2FhNDY0OTcx,size_16,color_FFFFFF,t_70" alt="img"></p><h2 id="docker相关命令"><a href="#docker相关命令" class="headerlink" title="docker相关命令"></a>docker相关命令</h2><p><a href="https://www.runoob.com/docker/docker-command-manual.html" target="_blank" rel="noopener">Docker 命令大全</a></p><p>列出正在运行的容器信息</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps</span><br></pre></td></tr></table></figure><p>列出所有容器信息</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps</span><br></pre></td></tr></table></figure><p>停止所有容器</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker stop $(docker ps -a -q)</span><br></pre></td></tr></table></figure><h1 id="或者通过docker-compose安装"><a href="#或者通过docker-compose安装" class="headerlink" title="或者通过docker-compose安装"></a>或者通过docker-compose安装</h1><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git <span class="built_in">clone</span> https://github.com/ifui/baota.git</span><br></pre></td></tr></table></figure><h3 id="3-进入项目根目录"><a href="#3-进入项目根目录" class="headerlink" title="3. 进入项目根目录"></a>3. 进入项目根目录</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">cd</span> baota</span><br></pre></td></tr></table></figure><h3 id="4-生成配置文件"><a href="#4-生成配置文件" class="headerlink" title="4. 生成配置文件"></a>4. 生成配置文件</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">cp .env-example .env</span><br></pre></td></tr></table></figure><h3 id="5-启动宝塔镜像,在项目根目录下执行命令"><a href="#5-启动宝塔镜像,在项目根目录下执行命令" class="headerlink" title="5. 启动宝塔镜像,在项目根目录下执行命令"></a>5. 启动宝塔镜像,在项目根目录下执行命令</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker-compose up -d app</span><br></pre></td></tr></table></figure><h3 id="6-查看默认登录信息"><a href="#6-查看默认登录信息" class="headerlink" title="6. 查看默认登录信息"></a>6. 查看默认登录信息</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker-compose logs app</span><br></pre></td></tr></table></figure><p>如果自己修改过env中的端口配置,那么访问的时候就需要写自己修改的端口</p><p><img src="https://image.xiaomo.info//blog/image-20201106174751622.png" alt="image-20201106174751622"></p><p>比如我这里填的是18888,那我的访问地址就是 <a href="http://localhost:18888/63c795f5" target="_blank" rel="noopener">http://localhost:18888/63c795f5</a> ,后面的随机码可以用<code>docker-compose logs app</code>查看</p>]]></content>
<summary type="html"><p>用了好几年的宝塔,不知道什么时候出了个pm2管理器,才开始以为它是对node版本切换管理的,直到今天看到有一篇文章才发现理解错了。研究了一下pm2的作用和用法,也顺便玩一玩宝塔中的pm2。</p></summary>
<category term="web" scheme="https://blog.xiaomo.info/categories/web/"/>
<category term="pm2" scheme="https://blog.xiaomo.info/tags/pm2/"/>
</entry>
<entry>
<title>聊聊Javascript的事件循环(转载)</title>
<link href="https://blog.xiaomo.info/2020/browserEventLoop/"/>
<id>https://blog.xiaomo.info/2020/browserEventLoop/</id>
<published>2020-11-06T00:00:00.000Z</published>
<updated>2022-08-10T23:53:17.655Z</updated>
<content type="html"><![CDATA[<p>苹果的safari对不在一个事件循环内的popup操作判定为非用户主动触发,后果就是会被浏览器自动拦截。所以开始查询浏览器事件循环相关的资料,如果有遇到同样问题的小伙伴可以参考一下。</p><a id="more"></a><p>原地址 : <a href="https://juejin.im/post/6844903653120163848" target="_blank" rel="noopener">聊聊Javascript的事件循环</a></p><h2 id="JavaScript、浏览器、事件之间的关系"><a href="#JavaScript、浏览器、事件之间的关系" class="headerlink" title="JavaScript、浏览器、事件之间的关系"></a>JavaScript、浏览器、事件之间的关系</h2><p>JavaScript程序采用了异步事件驱动编程(Event-driven programming)模型,维基百科对它的解释是:</p><blockquote><p> 事件驱动程序设计(英语:Event-driven programming)是一种电脑程序设计模型。这种模型的程序运行流程是由用户的动作(如鼠标的按键,键盘的按键动作)或者是由其他程序的消息来决定的。相对于批处理程序设计(batch programming)而言,程序运行的流程是由程序员来决定。批量的程序设计在初级程序设计教学课程上是一种方式。然而,事件驱动程序设计这种设计模型是在交互程序(Interactive program)的情况下孕育而生的</p></blockquote><p>简而言之,在web前端编程里面JavaScript通过浏览器提供的事件模型API和用户交互,接受用户的输入。</p><p>事件驱动程序模型基本的实现原理基本上都是使用 事件循环(Event Loop)。</p><p>而JS的运行环境主要有两个:浏览器、Node。</p><p>在两个环境下的Event Loop实现是不一样的,在浏览器中基于 <a href="https://www.w3.org/TR/2017/REC-html52-20171214/webappapis.html#event-loops" target="_blank" rel="noopener">规范</a> 来实现,不同浏览器可能有小小区别。在Node中基于 libuv 这个库来实现</p><p>JS是单线程执行的,而基于事件循环模型,形成了基本没有阻塞(除了alert或同步XHR等操作)的状态。</p><h3 id="浏览器中的事件循环-event-loop"><a href="#浏览器中的事件循环-event-loop" class="headerlink" title="浏览器中的事件循环 event loop"></a>浏览器中的事件循环 event loop</h3><p>先看HTML标准的一系列解释:</p><blockquote><p> 为了协调事件(event),用户交互(user interaction),脚本(script),渲染(rendering),网络(networking)等,用户代理(user agent)必须使用事件循环(event loops)。 有两类事件循环:一种针对浏览上下文(browsing context),还有一种针对worker(web worker)。</p></blockquote><p>为了更好地理解Event Loop,请看下图(转引自Philip Roberts的演讲《<a href="http://vimeo.com/96425312" target="_blank" rel="noopener">Help, I’m stuck in an event-loop</a>》)</p><img src="https://image.xiaomo.info//blog/image-20201106150325595.png" alt="image-20201106150325595" style="zoom: 50%;"><p>上图中,主线程运行的时候,产生堆栈,栈中的代码调用各种外部API,异步操作执行完成后,就在消息队列中排队。只要栈中的代码执行完毕,主线程就会去读取“任务队列”,依次执行那些事件所对应的回调函数。</p><h5 id="详细的步骤如下:"><a href="#详细的步骤如下:" class="headerlink" title="详细的步骤如下:"></a>详细的步骤如下:</h5><ol><li> 所有同步任务都在主线程上执行,形成一个执行栈</li><li> 主线程之外,还存在一个“消息队列”。只要异步操作执行完成,就到消息队列中排队</li><li> 一旦执行栈中的所有同步任务执行完毕,系统就会依次读取消息队列的异步任务,于是被读取的异步任务结束等待状态,进入执行栈,开始执行</li><li> 主线程不断重复上面的的第三步</li></ol><p>下面看一个有意思的例子,猜一下它的运行结果:</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">setTimeout(</span><br><span class="line"> <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'1'</span>)</span><br><span class="line">},<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">new</span> <span class="built_in">Promise</span>(</span><br><span class="line"> <span class="function"><span class="keyword">function</span>(<span class="params">resolve</span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'2'</span>);</span><br><span class="line"> resolve()</span><br><span class="line">}).then(</span><br><span class="line"> <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'3'</span>);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">'4'</span>);</span><br></pre></td></tr></table></figure><p>打印结果:</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="number">2</span></span><br><span class="line"><span class="number">4</span></span><br><span class="line"><span class="number">3</span></span><br><span class="line"><span class="number">1</span></span><br></pre></td></tr></table></figure><p>这是为什么?是不是跟上面说的相违背了?其实这里面就有了两个概念宏任务(task/macrotask),微任务(microtask),下面我们来详细介绍一下这两个东东。</p><h2 id="Macrotask-与-Microtask"><a href="#Macrotask-与-Microtask" class="headerlink" title="Macrotask 与 Microtask"></a>Macrotask 与 Microtask</h2><p>根据 <a href="https://www.w3.org/TR/2017/REC-html52-20171214/webappapis.html#event-loops" target="_blank" rel="noopener">规范</a>,每个线程都有一个事件循环(Event Loop),在浏览器中除了主要的页面执行线程 外,Web worker是在一个新的线程中运行的,所以可以将其独立看待。</p><p>每个事件循环有至少一个任务队列(Task Queue,也可以称作Macrotask宏任务),各个任务队列中放置着不同来源(或者不同分类)的任务,可以让浏览器根据自己的实现来进行优先级排序</p><p>以及一个微任务队列(Microtask Queue),主要用于处理一些状态的改变,UI渲染工作之前的一些必要操作(可以防止多次无意义的UI渲染)</p><p>主线程的代码执行时,会将执行程序置入执行栈(Stack)中,执行完毕后出栈,另外有个堆空间(Heap),主要用于存储对象及一些非结构化的数据。</p><img src="https://image.xiaomo.info//blog/image-20201106150407277.png" style="zoom: 50%;"><p>常见的macrotask有:</p><ol><li><p> run <script>(同步的代码执行)</p></li><li><p> setTimeout</p></li><li><p> setInterval</p></li><li><p> setImmediate (Node环境中)</p></li><li><p> requestAnimationFrame</p></li><li><p> I/O</p></li><li><p> UI rendering</p></li></ol><p>常见的microtask有:</p><ol><li><p> process.nextTick (Node环境中)</p></li><li><p> Promise callback</p></li><li><p> Object.observe (基本上已经废弃)</p></li><li><p> MutationObserver</p></li></ol><h2 id="事件循环执行顺序"><a href="#事件循环执行顺序" class="headerlink" title="事件循环执行顺序"></a>事件循环执行顺序</h2><h3 id="1-event-loop-执行步骤:"><a href="#1-event-loop-执行步骤:" class="headerlink" title="1. event loop 执行步骤:"></a>1. event loop 执行步骤:</h3><p>1、执行宏任务(先进先出),一次循环只执行一个宏任务)<br>2、执行栈 —— 同步方法顺序执行,异步方法交给异步处理模块<br>3、执行栈为空时取出微任务执行(先进先出),直到微任务队列为空<br>4、更新UI渲染。完成一轮循环,反复执行1-4。(不一定每次循环都会渲染)</p><h3 id="2-update-the-rendering-渲染更新:"><a href="#2-update-the-rendering-渲染更新:" class="headerlink" title="2.update the rendering 渲染更新:"></a>2.update the rendering 渲染更新:</h3><p>在一轮event loop中多次修改同一dom,只有最后一次会进行绘制。<br>渲染更新(Update the rendering)会在event loop中的tasks和microtasks完成后进行,但并不是每轮event loop都会更新渲染,浏览器有自己的机制来确定是否要更新渲染。如果在一帧(16.7ms)里多次修改了dom,浏览器可能只会渲染绘制一次。<br>如果希望在每轮event loop都即时呈现变动,可以使用requestAnimationFrame.</p><p>那么我们回到上面的那个例子就不难解释了:</p><p>==注意==: Promise 自身的代码是同步执行的,只有 .then后的回调函数才是微任务。</p><p>主线程的执行过程:</p><ol><li> 从宏任务队列(task)中取出 script,将所有同步代码推入执行栈中执行,遇到异步代码交给异步处理模块,异步处理模块处理完成后将任务按规则推入事件队列,宏任务推宏任务队列(先进先出),微任务推微任务队列(先进先出)。所以输出 2 和 4。</li><li> 执行完 script 中的同步代码,再将微任务队列中最老的任务推入执行栈执行,直到清空微任务队列。所以输出 3。</li><li> 浏览器更新渲染,再去宏任务队列中取出最老的任务推入执行栈中执行,循环以上步骤。所以输出 1。</li></ol><h2 id="在Node中的实现"><a href="#在Node中的实现" class="headerlink" title="在Node中的实现"></a>在Node中的实现</h2><p>在Node环境中,macrotask部分主要多了setImmediate,microtask部分主要多了process.nextTick,而这个nextTick是独立出来自成队列的,优先级高于其他microtask</p><p>不过事件循环的的实现就不太一样了,可以参考 <a href="https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/">Node事件文档</a> <a href="http://docs.libuv.org/en/v1.x/design.html">libuv事件文档</a></p><h4 id="Node中的事件循环有6个阶段"><a href="#Node中的事件循环有6个阶段" class="headerlink" title="Node中的事件循环有6个阶段"></a>Node中的事件循环有6个阶段</h4><ol><li> timers:执行setTimeout() 和 setInterval()中到期的callback</li><li> I/O callbacks:上一轮循环中有少数的I/Ocallback会被延迟到这一轮的这一阶段执行</li><li> idle, prepare:仅内部使用</li><li> poll:最为重要的阶段,执行I/Ocallback,在适当的条件下会阻塞在这个阶段</li><li> check:执行setImmediate的callback</li><li> close callbacks:执行close事件的callback,例如socket.on(“close”,func)</li></ol><img src="https://image.xiaomo.info//blog/image-20201106150543628.png" alt="image-20201106150543628" style="zoom: 25%;" /><p>每一轮事件循环都会经过六个阶段,在每个阶段后,都会执行microtask</p><img src="https://image.xiaomo.info//blog/image-20201106150609147.png" alt="image-20201106150609147" style="zoom:25%;" /><p>比较特殊的是在poll阶段,执行程序同步执行poll队列里的回调,直到队列为空或执行的回调达到系统上限</p><p>接下来再检查有无预设的setImmediate,如果有就转入check阶段,没有就先查询最近的timer的距离,以其作为poll阶段的阻塞时间,如果timer队列是空的,它就一直阻塞下去</p><p>而nextTick并不在这些阶段中执行,它在每个阶段之后都会执行。</p><p>一个简单的例子:</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">setTimeout(<span class="function"><span class="params">()</span> =></span> <span class="built_in">console</span>.log(<span class="number">1</span>));</span><br><span class="line"></span><br><span class="line">setImmediate(<span class="function"><span class="params">()</span> =></span> <span class="built_in">console</span>.log(<span class="number">2</span>));</span><br><span class="line"></span><br><span class="line">process.nextTick(<span class="function"><span class="params">()</span> =></span> <span class="built_in">console</span>.log(<span class="number">3</span>));</span><br><span class="line"></span><br><span class="line"><span class="built_in">Promise</span>.resolve().then(<span class="function"><span class="params">()</span> =></span> <span class="built_in">console</span>.log(<span class="number">4</span>));</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(<span class="number">5</span>);</span><br></pre></td></tr></table></figure><p>根据以上知识,应该很快就能知道输出结果是 5 3 4 1 2</p><p>修改一下:</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">process.nextTick(<span class="function"><span class="params">()</span> =></span> <span class="built_in">console</span>.log(<span class="number">1</span>));</span><br><span class="line"></span><br><span class="line"><span class="built_in">Promise</span>.resolve().then(<span class="function"><span class="params">()</span> =></span> <span class="built_in">console</span>.log(<span class="number">2</span>));</span><br><span class="line"></span><br><span class="line">process.nextTick(<span class="function"><span class="params">()</span> =></span> <span class="built_in">console</span>.log(<span class="number">3</span>));</span><br><span class="line"></span><br><span class="line"><span class="built_in">Promise</span>.resolve().then(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> process.nextTick(<span class="function"><span class="params">()</span> =></span> <span class="built_in">console</span>.log(<span class="number">0</span>));</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="number">4</span>);</span><br><span class="line">});</span><br></pre></td></tr></table></figure><p>输出为 1 3 2 4 0,因为nextTick队列优先级高于同一轮事件循环中其他microtask队列</p><p>再次修改:</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">process.nextTick(<span class="function"><span class="params">()</span> =></span> <span class="built_in">console</span>.log(<span class="number">1</span>));</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">setTimeout(<span class="function"><span class="params">()</span>=></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'timer1'</span>);</span><br><span class="line"></span><br><span class="line"> <span class="built_in">Promise</span>.resolve().then(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'promise1'</span>);</span><br><span class="line"> });</span><br><span class="line">}, <span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">process.nextTick(<span class="function"><span class="params">()</span> =></span> <span class="built_in">console</span>.log(<span class="number">2</span>));</span><br><span class="line"></span><br><span class="line">setTimeout(<span class="function"><span class="params">()</span>=></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'timer2'</span>);</span><br><span class="line"></span><br><span class="line"> process.nextTick(<span class="function"><span class="params">()</span> =></span> <span class="built_in">console</span>.log(<span class="number">3</span>));</span><br><span class="line"></span><br><span class="line"> <span class="built_in">Promise</span>.resolve().then(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'promise2'</span>);</span><br><span class="line"> });</span><br><span class="line">}, <span class="number">0</span>);</span><br></pre></td></tr></table></figure><p>输出结果为:</p><figure class="highlight"><table><tr><td class="code"><pre><span class="line"><span class="number">0</span></span><br><span class="line"><span class="number">1</span></span><br><span class="line"><span class="number">2</span></span><br><span class="line">timer1</span><br><span class="line">timer2</span><br><span class="line"><span class="number">3</span></span><br><span class="line">promise1</span><br><span class="line">promise2</span><br></pre></td></tr></table></figure><p>与在浏览器中不同,这里promise1并不是在timer1之后输出,因为在setTimeout执行的时候是出于timer阶段,会先一并处理timer回调.</p><h2 id="善用事件循环"><a href="#善用事件循环" class="headerlink" title="善用事件循环"></a>善用事件循环</h2><p>知道JS的事件循环是怎么样的了,就需要知道怎么才能把它用好:</p><ol><li> 在microtask中不要放置复杂的处理程序,防止阻塞UI的渲染</li><li> 可以使用process.nextTick处理一些比较紧急的事情</li><li> 可以在setTimeout回调中处理上轮事件循环中UI渲染的结果</li><li> 注意不要滥用setInterval和setTimeout,它们并不是可以保证能够按时处理的,setInterval甚至还会出现丢帧的情况,可考虑使用 requestAnimationFrame</li><li> 一些可能会影响到UI的异步操作,可放在promise回调中处理,防止多一轮事件循环导致重复执行UI的渲染</li><li> 在Node中使用immediate来可能会得到更多的保证</li></ol><p>如有错误欢迎指正,相互进步。</p><p>参考链接:</p><p><a href="http://www.alloyteam.com/2015/10/turning-to-javascript-series-from-settimeout-said-the-event-loop-model/">JavaScript 运行机制详解:再谈Event Loop</a></p><p><a href="https://www.cnblogs.com/dong-xu/p/7000163.html">深入理解 JavaScript 事件循环(一)— event loop</a></p><p><a href="https://zhuanlan.zhihu.com/p/26229293">深入浅出Javascript事件循环机制(上)</a></p><h1 id="MDN中对并发模型与事件循环的讲解"><a href="#MDN中对并发模型与事件循环的讲解" class="headerlink" title="MDN中对并发模型与事件循环的讲解"></a>MDN中对并发模型与事件循环的讲解</h1><p>JavaScript有一个基于<strong>事件循环</strong>的并发模型,事件循环负责执行代码、收集和处理事件以及执行队列中的子任务。这个模型与其它语言中的模型截然不同,比如 C 和 Java。</p><h2 id="运行时概念"><a href="#运行时概念" class="headerlink" title="运行时概念"></a>运行时概念</h2><p>接下来的内容解释了这个理论模型。现代JavaScript引擎实现并着重优化了以下描述的这些语义。</p><h3 id="可视化描述"><a href="#可视化描述" class="headerlink" title="可视化描述"></a>可视化描述</h3><p><img src="https://mdn.mozillademos.org/files/17124/The_Javascript_Runtime_Environment_Example.svg" alt="Stack, heap, queue"></p><h3 id="栈"><a href="#栈" class="headerlink" title="栈"></a>栈</h3><p>函数调用形成了一个由若干帧组成的栈。</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params">b</span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> a = <span class="number">10</span>;</span><br><span class="line"> <span class="keyword">return</span> a + b + <span class="number">11</span>;</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">bar</span>(<span class="params">x</span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> y = <span class="number">3</span>;</span><br><span class="line"> <span class="keyword">return</span> foo(x * y);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(bar(<span class="number">7</span>)); <span class="comment">// 返回 42</span></span><br></pre></td></tr></table></figure><p>当调用 <code>bar</code> 时,第一个帧被创建并压入栈中,帧中包含了 <code>bar</code> 的参数和局部变量。 当 <code>bar</code> 调用 <code>foo</code> 时,第二个帧被创建并被压入栈中,放在第一个帧之上,帧中包含 <code>foo</code> 的参数和局部变量。当 <code>foo</code> 执行完毕然后返回时,第二个帧就被弹出栈(剩下 <code>bar</code> 函数的调用帧 )。当 <code>bar</code> 也执行完毕然后返回时,第一个帧也被弹出,栈就被清空了。</p><h3 id="堆"><a href="#堆" class="headerlink" title="堆"></a>堆</h3><p>对象被分配在堆中,堆是一个用来表示一大块(通常是非结构化的)内存区域的计算机术语。</p><h3 id="队列"><a href="#队列" class="headerlink" title="队列"></a>队列</h3><p>一个 JavaScript 运行时包含了一个待处理消息的消息队列。每一个消息都关联着一个用以处理这个消息的回调函数。</p><p>在 <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/EventLoop#%E4%BA%8B%E4%BB%B6%E5%BE%AA%E7%8E%AF">事件循环</a> 期间的某个时刻,运行时会从最先进入队列的消息开始处理队列中的消息。被处理的消息会被移出队列,并作为输入参数来调用与之关联的函数。正如前面所提到的,调用一个函数总是会为其创造一个新的栈帧。</p><p>函数的处理会一直进行到执行栈再次为空为止;然后事件循环将会处理队列中的下一个消息(如果还有的话)。</p><h2 id="事件循环"><a href="#事件循环" class="headerlink" title="事件循环"></a>事件循环</h2><p>之所以称之为 <strong>事件循环</strong>,是因为它经常按照类似如下的方式来被实现:</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">while</span> (queue.waitForMessage()) {</span><br><span class="line"> queue.processNextMessage();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>queue.waitForMessage()</code> 会同步地等待消息到达(如果当前没有任何消息等待被处理)。</p><h3 id="“执行至完成”"><a href="#“执行至完成”" class="headerlink" title="“执行至完成”"></a>“执行至完成”</h3><p>每一个消息完整地执行后,其它消息才会被执行。这为程序的分析提供了一些优秀的特性,包括:当一个函数执行时,它不会被抢占,只有在它运行完毕之后才会去运行任何其他的代码,才能修改这个函数操作的数据。这与C语言不同,例如,如果函数在线程中运行,它可能在任何位置被终止,然后在另一个线程中运行其他代码。</p><p>这个模型的一个缺点在于当一个消息需要太长时间才能处理完毕时,Web应用程序就无法处理与用户的交互,例如点击或滚动。为了缓解这个问题,浏览器一般会弹出一个“这个脚本运行时间过长”的对话框。一个良好的习惯是缩短单个消息处理时间,并在可能的情况下将一个消息裁剪成多个消息。</p><h3 id="添加消息"><a href="#添加消息" class="headerlink" title="添加消息"></a>添加消息</h3><p>在浏览器里,每当一个事件发生并且有一个事件监听器绑定在该事件上时,一个消息就会被添加进消息队列。如果没有事件监听器,这个事件将会丢失。所以当一个带有点击事件处理器的元素被点击时,就会像其他事件一样产生一个类似的消息。</p><p>函数 <code>setTimeout</code> 接受两个参数:待加入队列的消息和一个时间值(可选,默认为 0)。这个时间值代表了消息被实际加入到队列的最小延迟时间。如果队列中没有其它消息并且栈为空,在这段延迟时间过去之后,消息会被马上处理。但是,如果有其它消息,<code>setTimeout</code> 消息必须等待其它消息处理完。因此第二个参数仅仅表示最少延迟时间,而非确切的等待时间。</p><p>下面的例子演示了这个概念(<code>setTimeout</code> 并不会在计时器到期之后直接执行):</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> s = <span class="keyword">new</span> <span class="built_in">Date</span>().getSeconds();</span><br><span class="line"></span><br><span class="line">setTimeout(<span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="comment">// 输出 "2",表示回调函数并没有在 500 毫秒之后立即执行</span></span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"Ran after "</span> + (<span class="keyword">new</span> <span class="built_in">Date</span>().getSeconds() - s) + <span class="string">" seconds"</span>);</span><br><span class="line">}, <span class="number">500</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span>(<span class="literal">true</span>) {</span><br><span class="line"> <span class="keyword">if</span>(<span class="keyword">new</span> <span class="built_in">Date</span>().getSeconds() - s >= <span class="number">2</span>) {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"Good, looped for 2 seconds"</span>);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="零延迟"><a href="#零延迟" class="headerlink" title="零延迟"></a>零延迟</h3><p>零延迟并不意味着回调会立即执行。以 0 为第二参数调用 <code>setTimeout</code> 并不表示在 0 毫秒后就立即调用回调函数。</p><p>其等待的时间取决于队列里待处理的消息数量。在下面的例子中,<code>"这是一条消息"</code> 将会在回调获得处理之前输出到控制台,这是因为延迟参数是运行时处理请求所需的最小等待时间,但并不保证是准确的等待时间。</p><p>基本上,<code>setTimeout</code> 需要等待当前队列中所有的消息都处理完毕之后才能执行,即使已经超出了由第二参数所指定的时间。</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">(<span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"></span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'这是开始'</span>);</span><br><span class="line"></span><br><span class="line"> setTimeout(<span class="function"><span class="keyword">function</span> <span class="title">cb</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'这是来自第一个回调的消息'</span>);</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'这是一条消息'</span>);</span><br><span class="line"></span><br><span class="line"> setTimeout(<span class="function"><span class="keyword">function</span> <span class="title">cb1</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'这是来自第二个回调的消息'</span>);</span><br><span class="line"> }, <span class="number">0</span>);</span><br><span class="line"></span><br><span class="line"> <span class="built_in">console</span>.log(<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="comment">// "这是开始"</span></span><br><span class="line"><span class="comment">// "这是一条消息"</span></span><br><span class="line"><span class="comment">// "这是结束"</span></span><br><span class="line"><span class="comment">// "这是来自第一个回调的消息"</span></span><br><span class="line"><span class="comment">// "这是来自第二个回调的消息"</span></span><br></pre></td></tr></table></figure><h3 id="多个运行时互相通信"><a href="#多个运行时互相通信" class="headerlink" title="多个运行时互相通信"></a>多个运行时互相通信</h3><p>一个 web worker 或者一个跨域的 <code>iframe</code> 都有自己的栈、堆和消息队列。两个不同的运行时只能通过 <a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Window/postMessage"><code>postMessage</code></a> 方法进行通信。如果另一个运行时侦听 <code>message</code> 事件,则此方法会向该运行时添加消息。</p><h2 id="永不阻塞"><a href="#永不阻塞" class="headerlink" title="永不阻塞"></a>永不阻塞</h2><p>JavaScript的事件循环模型与许多其他语言不同的一个非常有趣的特性是,它永不阻塞。 处理 I/O 通常通过事件和回调来执行,所以当一个应用正等待一个 <a href="https://developer.mozilla.org/zh-CN/docs/Web/API/IndexedDB_API">IndexedDB</a> 查询返回或者一个 <a href="https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest">XHR</a> 请求返回时,它仍然可以处理其它事情,比如用户输入。</p><p>由于历史原因有一些例外,如 <code>alert</code> 或者同步 XHR,但应该尽量避免使用它们。注意,<a href="https://stackoverflow.com/questions/2734025/is-javascript-guaranteed-to-be-single-threaded/2734311#2734311">例外的例外也是存在的</a>(但通常是实现错误而非其它原因)。</p><h2 id="标准规范"><a href="#标准规范" class="headerlink" title="标准规范"></a>标准规范</h2><table><thead><tr><th align="left">标准规范</th><th align="left">状态</th><th align="left">注释</th></tr></thead><tbody><tr><td align="left"><a href="https://html.spec.whatwg.org/multipage/webappapis.html#event-loops">HTML Living Standard Event loops</a></td><td align="left">Living Standard</td><td align="left"></td></tr><tr><td align="left"><a href="https://nodejs.org/zh-cn/docs/guides/event-loop-timers-and-nexttick/#what-is-the-event-loop">Node.js 事件循环</a></td><td align="left">Living Standard</td><td align="left"></td></tr></tbody></table><h1 id="阮老师对EventLoop的解释"><a href="#阮老师对EventLoop的解释" class="headerlink" title="阮老师对EventLoop的解释"></a><a href="https://www.ruanyifeng.com/blog/2014/10/event-loop.html">阮老师对EventLoop的解释</a></h1><p>Event Loop 是一个很重要的概念,指的是计算机系统的一种运行机制。</p><p>JavaScript语言就采用这种机制,来解决单线程运行带来的一些问题。</p><p><img src="http://www.ruanyifeng.com/blogimg/asset/201310/2013102001.png" alt="Event Loop"></p><p>本文参考C. Aaron Cois的<a href="https://www.udemy.com/lectures/understanding-the-nodejs-event-loop-91298">《Understanding The Node.js Event Loop》</a>,解释什么是Event Loop,以及它与JavaScript语言的单线程模型有何关系。</p><p>想要理解Event Loop,就要从程序的运行模式讲起。运行以后的程序叫做<a href="http://zh.wikipedia.org/wiki/%E8%BF%9B%E7%A8%8B">“进程”</a>(process),一般情况下,一个进程一次只能执行一个任务。</p><p>如果有很多任务需要执行,不外乎三种解决方法。</p><blockquote><p> <strong>(1)排队。</strong>因为一个进程一次只能执行一个任务,只好等前面的任务执行完了,再执行后面的任务。</p><p> <strong>(2)新建进程。</strong>使用fork命令,为每个任务新建一个进程。</p><p> <strong>(3)新建线程。</strong>因为进程太耗费资源,所以如今的程序往往允许一个进程包含多个线程,由线程去完成任务。(进程和线程的详细解释,请看<a href="http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html">这里</a>。)</p></blockquote><p>以JavaScript语言为例,它是一种单线程语言,所有任务都在一个线程上完成,即采用上面的第一种方法。一旦遇到大量任务或者遇到一个耗时的任务,网页就会出现”假死”,因为JavaScript停不下来,也就无法响应用户的行为。</p><p>你也许会问,JavaScript为什么是单线程,难道不能实现为多线程吗?</p><p>这跟历史有关系。JavaScript从诞生起就是单线程。原因大概是不想让浏览器变得太复杂,因为多线程需要共享资源、且有可能修改彼此的运行结果,对于一种网页脚本语言来说,这就太复杂了。后来就约定俗成,JavaScript为一种单线程语言。(Worker API可以实现多线程,但是JavaScript本身始终是单线程的。)</p><p>如果某个任务很耗时,比如涉及很多I/O(输入/输出)操作,那么线程的运行大概是下面的样子。</p><p><img src="http://www.ruanyifeng.com/blogimg/asset/201310/2013102002.png" alt="synchronous mode"></p><p>上图的绿色部分是程序的运行时间,红色部分是等待时间。可以看到,由于I/O操作很慢,所以这个线程的大部分运行时间都在空等I/O操作的返回结果。这种运行方式称为”同步模式”(synchronous I/O)或”堵塞模式”(blocking I/O)。</p><p>如果采用多线程,同时运行多个任务,那很可能就是下面这样。</p><p><img src="http://www.ruanyifeng.com/blogimg/asset/201310/2013102003.png" alt="synchronous mode"></p><p>上图表明,多线程不仅占用多倍的系统资源,也闲置多倍的资源,这显然不合理。</p><p>Event Loop就是为了解决这个问题而提出的。<a href="http://en.wikipedia.org/wiki/Event_loop">Wikipedia</a>这样定义:</p><blockquote><p> “<strong>Event Loop是一个程序结构,用于等待和发送消息和事件。</strong>(a programming construct that waits for and dispatches events or messages in a program.)”</p></blockquote><p>简单说,就是在程序中设置两个线程:一个负责程序本身的运行,称为”主线程”;另一个负责主线程与其他进程(主要是各种I/O操作)的通信,被称为”Event Loop线程”(可以译为”消息线程”)。</p><p><img src="http://www.ruanyifeng.com/blogimg/asset/201310/2013102004.png" alt="asynchronous mode"></p><p>上图主线程的绿色部分,还是表示运行时间,而橙色部分表示空闲时间。每当遇到I/O的时候,主线程就让Event Loop线程去通知相应的I/O程序,然后接着往后运行,所以不存在红色的等待时间。等到I/O程序完成操作,Event Loop线程再把结果返回主线程。主线程就调用事先设定的回调函数,完成整个任务。</p><p>可以看到,由于多出了橙色的空闲时间,所以主线程得以运行更多的任务,这就提高了效率。这种运行方式称为”<a href="http://en.wikipedia.org/wiki/Asynchronous_I/O">异步模式</a>“(asynchronous I/O)或”非堵塞模式”(non-blocking mode)。</p><p>这正是JavaScript语言的运行方式。单线程模型虽然对JavaScript构成了很大的限制,但也因此使它具备了其他语言不具备的优势。如果部署得好,JavaScript程序是不会出现堵塞的,这就是为什么node.js平台可以用很少的资源,应付大流量访问的原因。</p><h2 id="一、为什么JavaScript是单线程?"><a href="#一、为什么JavaScript是单线程?" class="headerlink" title="一、为什么JavaScript是单线程?"></a>一、为什么JavaScript是单线程?</h2><p>JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊。</p><p>JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?</p><p>所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。</p><p>为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。</p><h2 id="二、任务队列"><a href="#二、任务队列" class="headerlink" title="二、任务队列"></a>二、任务队列</h2><p>单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。</p><p>如果排队是因为计算量大,CPU忙不过来,倒也算了,但是很多时候CPU是闲着的,因为IO设备(输入输出设备)很慢(比如Ajax操作从网络读取数据),不得不等着结果出来,再往下执行。</p><p>JavaScript语言的设计者意识到,这时主线程完全可以不管IO设备,挂起处于等待中的任务,先运行排在后面的任务。等到IO设备返回了结果,再回过头,把挂起的任务继续执行下去。</p><p>于是,所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入”任务队列”(task queue)的任务,只有”任务队列”通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。</p><p>具体来说,异步执行的运行机制如下。(同步执行也是如此,因为它可以被视为没有异步任务的异步执行。)</p><blockquote><p> (1)所有同步任务都在主线程上执行,形成一个<a href="http://www.ruanyifeng.com/blog/2013/11/stack.html">执行栈</a>(execution context stack)。</p><p> (2)主线程之外,还存在一个”任务队列”(task queue)。只要异步任务有了运行结果,就在”任务队列”之中放置一个事件。</p><p> (3)一旦”执行栈”中的所有同步任务执行完毕,系统就会读取”任务队列”,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。</p><p> (4)主线程不断重复上面的第三步。</p></blockquote><p>下图就是主线程和任务队列的示意图。</p><p><img src="https://www.ruanyifeng.com/blogimg/asset/2014/bg2014100801.jpg" alt="任务队列"></p><p>只要主线程空了,就会去读取”任务队列”,这就是JavaScript的运行机制。这个过程会不断重复。</p><h2 id="三、事件和回调函数"><a href="#三、事件和回调函数" class="headerlink" title="三、事件和回调函数"></a>三、事件和回调函数</h2><p>“任务队列”是一个事件的队列(也可以理解成消息的队列),IO设备完成一项任务,就在”任务队列”中添加一个事件,表示相关的异步任务可以进入”执行栈”了。主线程读取”任务队列”,就是读取里面有哪些事件。</p><p>“任务队列”中的事件,除了IO设备的事件以外,还包括一些用户产生的事件(比如鼠标点击、页面滚动等等)。只要指定过回调函数,这些事件发生时就会进入”任务队列”,等待主线程读取。</p><p>所谓”回调函数”(callback),就是那些会被主线程挂起来的代码。异步任务必须指定回调函数,当主线程开始执行异步任务,就是执行对应的回调函数。</p><p>“任务队列”是一个先进先出的数据结构,排在前面的事件,优先被主线程读取。主线程的读取过程基本上是自动的,只要执行栈一清空,”任务队列”上第一位的事件就自动进入主线程。但是,由于存在后文提到的”定时器”功能,主线程首先要检查一下执行时间,某些事件只有到了规定的时间,才能返回主线程。</p><h2 id="四、Event-Loop"><a href="#四、Event-Loop" class="headerlink" title="四、Event Loop"></a>四、Event Loop</h2><p>主线程从”任务队列”中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。</p><p>为了更好地理解Event Loop,请看下图(转引自Philip Roberts的演讲<a href="http://vimeo.com/96425312">《Help, I’m stuck in an event-loop》</a>)。</p><p><img src="https://www.ruanyifeng.com/blogimg/asset/2014/bg2014100802.png" alt="Event Loop"></p><p>上图中,主线程运行的时候,产生堆(heap)和栈(stack),栈中的代码调用各种外部API,它们在”任务队列”中加入各种事件(click,load,done)。只要栈中的代码执行完毕,主线程就会去读取”任务队列”,依次执行那些事件所对应的回调函数。</p><p>执行栈中的代码(同步任务),总是在读取”任务队列”(异步任务)之前执行。请看下面这个例子。</p><blockquote> <figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line">> <span class="keyword">var</span> req = <span class="keyword">new</span> XMLHttpRequest();</span><br><span class="line">> req.open(<span class="string">'GET'</span>, url); </span><br><span class="line">> req.onload = <span class="function"><span class="keyword">function</span> (<span class="params"></span>)</span>{}; </span><br><span class="line">> req.onerror = <span class="function"><span class="keyword">function</span> (<span class="params"></span>)</span>{}; </span><br><span class="line">> req.send();</span><br><span class="line">></span><br></pre></td></tr></table></figure></blockquote><p>上面代码中的req.send方法是Ajax操作向服务器发送数据,它是一个异步任务,意味着只有当前脚本的所有代码执行完,系统才会去读取”任务队列”。所以,它与下面的写法等价。</p><blockquote> <figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line">> <span class="keyword">var</span> req = <span class="keyword">new</span> XMLHttpRequest();</span><br><span class="line">> req.open(<span class="string">'GET'</span>, url);</span><br><span class="line">> req.send();</span><br><span class="line">> req.onload = <span class="function"><span class="keyword">function</span> (<span class="params"></span>)</span>{}; </span><br><span class="line">> req.onerror = <span class="function"><span class="keyword">function</span> (<span class="params"></span>)</span>{}; </span><br><span class="line">></span><br></pre></td></tr></table></figure></blockquote><p>也就是说,指定回调函数的部分(onload和onerror),在send()方法的前面或后面无关紧要,因为它们属于执行栈的一部分,系统总是执行完它们,才会去读取”任务队列”。</p><h2 id="五、定时器"><a href="#五、定时器" class="headerlink" title="五、定时器"></a>五、定时器</h2><p>除了放置异步任务的事件,”任务队列”还可以放置定时事件,即指定某些代码在多少时间之后执行。这叫做”定时器”(timer)功能,也就是定时执行的代码。</p><p>定时器功能主要由setTimeout()和setInterval()这两个函数来完成,它们的内部运行机制完全一样,区别在于前者指定的代码是一次性执行,后者则为反复执行。以下主要讨论setTimeout()。</p><p>setTimeout()接受两个参数,第一个是回调函数,第二个是推迟执行的毫秒数。</p><blockquote> <figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line">> <span class="built_in">console</span>.log(<span class="number">1</span>);</span><br><span class="line">> setTimeout(<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{<span class="built_in">console</span>.log(<span class="number">2</span>);},<span class="number">1000</span>);</span><br><span class="line">> <span class="built_in">console</span>.log(<span class="number">3</span>);</span><br><span class="line">></span><br></pre></td></tr></table></figure></blockquote><p>上面代码的执行结果是1,3,2,因为setTimeout()将第二行推迟到1000毫秒之后执行。</p><p>如果将setTimeout()的第二个参数设为0,就表示当前代码执行完(执行栈清空)以后,立即执行(0毫秒间隔)指定的回调函数。</p><blockquote> <figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line">> setTimeout(<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{<span class="built_in">console</span>.log(<span class="number">1</span>);}, <span class="number">0</span>);</span><br><span class="line">> <span class="built_in">console</span>.log(<span class="number">2</span>);</span><br><span class="line">></span><br></pre></td></tr></table></figure></blockquote><p>上面代码的执行结果总是2,1,因为只有在执行完第二行以后,系统才会去执行”任务队列”中的回调函数。</p><p>总之,setTimeout(fn,0)的含义是,指定某个任务在主线程最早可得的空闲时间执行,也就是说,尽可能早得执行。它在”任务队列”的尾部添加一个事件,因此要等到同步任务和”任务队列”现有的事件都处理完,才会得到执行。</p><p>HTML5标准规定了setTimeout()的第二个参数的最小值(最短间隔),不得低于4毫秒,如果低于这个值,就会自动增加。在此之前,老版本的浏览器都将最短间隔设为10毫秒。另外,对于那些DOM的变动(尤其是涉及页面重新渲染的部分),通常不会立即执行,而是每16毫秒执行一次。这时使用requestAnimationFrame()的效果要好于setTimeout()。</p><p>需要注意的是,setTimeout()只是将事件插入了”任务队列”,必须等到当前代码(执行栈)执行完,主线程才会去执行它指定的回调函数。要是当前代码耗时很长,有可能要等很久,所以并没有办法保证,回调函数一定会在setTimeout()指定的时间执行。</p><h2 id="六、Node-js的Event-Loop"><a href="#六、Node-js的Event-Loop" class="headerlink" title="六、Node.js的Event Loop"></a>六、Node.js的Event Loop</h2><p>Node.js也是单线程的Event Loop,但是它的运行机制不同于浏览器环境。</p><p>请看下面的示意图(作者<a href="https://twitter.com/BusyRich/status/494959181871316992">@BusyRich</a>)。</p><p><img src="https://www.ruanyifeng.com/blogimg/asset/2014/bg2014100803.png" alt="Node.js"></p><p>根据上图,Node.js的运行机制如下。</p><blockquote><p> (1)V8引擎解析JavaScript脚本。</p><p> (2)解析后的代码,调用Node API。</p><p> (3)<a href="https://github.com/joyent/libuv">libuv库</a>负责Node API的执行。它将不同的任务分配给不同的线程,形成一个Event Loop(事件循环),以异步的方式将任务的执行结果返回给V8引擎。</p><p> (4)V8引擎再将结果返回给用户。</p></blockquote><p>除了setTimeout和setInterval这两个方法,Node.js还提供了另外两个与”任务队列”有关的方法:<a href="http://nodejs.org/docs/latest/api/process.html#process_process_nexttick_callback">process.nextTick</a>和<a href="http://nodejs.org/docs/latest/api/timers.html#timers_setimmediate_callback_arg">setImmediate</a>。它们可以帮助我们加深对”任务队列”的理解。</p><p>process.nextTick方法可以在当前”执行栈”的尾部—-下一次Event Loop(主线程读取”任务队列”)之前—-触发回调函数。也就是说,它指定的任务总是发生在所有异步任务之前。setImmediate方法则是在当前”任务队列”的尾部添加事件,也就是说,它指定的任务总是在下一次Event Loop时执行,这与setTimeout(fn, 0)很像。请看下面的例子(via <a href="http://stackoverflow.com/questions/17502948/nexttick-vs-setimmediate-visual-explanation">StackOverflow</a>)。</p><blockquote> <figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line">> process.nextTick(<span class="function"><span class="keyword">function</span> <span class="title">A</span>(<span class="params"></span>) </span>{</span><br><span class="line">> <span class="built_in">console</span>.log(<span class="number">1</span>);</span><br><span class="line">> process.nextTick(<span class="function"><span class="keyword">function</span> <span class="title">B</span>(<span class="params"></span>)</span>{<span class="built_in">console</span>.log(<span class="number">2</span>);});</span><br><span class="line">> });</span><br><span class="line">> </span><br><span class="line">> setTimeout(<span class="function"><span class="keyword">function</span> <span class="title">timeout</span>(<span class="params"></span>) </span>{</span><br><span class="line">> <span class="built_in">console</span>.log(<span class="string">'TIMEOUT FIRED'</span>);</span><br><span class="line">> }, <span class="number">0</span>)</span><br><span class="line">> <span class="comment">// 1</span></span><br><span class="line">> <span class="comment">// 2</span></span><br><span class="line">> <span class="comment">// TIMEOUT FIRED</span></span><br><span class="line">></span><br></pre></td></tr></table></figure></blockquote><p>上面代码中,由于process.nextTick方法指定的回调函数,总是在当前”执行栈”的尾部触发,所以不仅函数A比setTimeout指定的回调函数timeout先执行,而且函数B也比timeout先执行。这说明,如果有多个process.nextTick语句(不管它们是否嵌套),将全部在当前”执行栈”执行。</p><p>现在,再看setImmediate。</p><blockquote> <figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line">> setImmediate(<span class="function"><span class="keyword">function</span> <span class="title">A</span>(<span class="params"></span>) </span>{</span><br><span class="line">> <span class="built_in">console</span>.log(<span class="number">1</span>);</span><br><span class="line">> setImmediate(<span class="function"><span class="keyword">function</span> <span class="title">B</span>(<span class="params"></span>)</span>{<span class="built_in">console</span>.log(<span class="number">2</span>);});</span><br><span class="line">> });</span><br><span class="line">> </span><br><span class="line">> setTimeout(<span class="function"><span class="keyword">function</span> <span class="title">timeout</span>(<span class="params"></span>) </span>{</span><br><span class="line">> <span class="built_in">console</span>.log(<span class="string">'TIMEOUT FIRED'</span>);</span><br><span class="line">> }, <span class="number">0</span>);</span><br><span class="line">></span><br></pre></td></tr></table></figure></blockquote><p>上面代码中,setImmediate与setTimeout(fn,0)各自添加了一个回调函数A和timeout,都是在下一次Event Loop触发。那么,哪个回调函数先执行呢?答案是不确定。运行结果可能是1–TIMEOUT FIRED–2,也可能是TIMEOUT FIRED–1–2。</p><p>令人困惑的是,Node.js文档中称,setImmediate指定的回调函数,总是排在setTimeout前面。实际上,这种情况只发生在递归调用的时候。</p><blockquote> <figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line">> setImmediate(<span class="function"><span class="keyword">function</span> (<span class="params"></span>)</span>{</span><br><span class="line">> setImmediate(<span class="function"><span class="keyword">function</span> <span class="title">A</span>(<span class="params"></span>) </span>{</span><br><span class="line">> <span class="built_in">console</span>.log(<span class="number">1</span>);</span><br><span class="line">> setImmediate(<span class="function"><span class="keyword">function</span> <span class="title">B</span>(<span class="params"></span>)</span>{<span class="built_in">console</span>.log(<span class="number">2</span>);});</span><br><span class="line">> });</span><br><span class="line">> </span><br><span class="line">> setTimeout(<span class="function"><span class="keyword">function</span> <span class="title">timeout</span>(<span class="params"></span>) </span>{</span><br><span class="line">> <span class="built_in">console</span>.log(<span class="string">'TIMEOUT FIRED'</span>);</span><br><span class="line">> }, <span class="number">0</span>);</span><br><span class="line">> });</span><br><span class="line">> <span class="comment">// 1</span></span><br><span class="line">> <span class="comment">// TIMEOUT FIRED</span></span><br><span class="line">> <span class="comment">// 2</span></span><br><span class="line">></span><br></pre></td></tr></table></figure></blockquote><p>上面代码中,setImmediate和setTimeout被封装在一个setImmediate里面,它的运行结果总是1–TIMEOUT FIRED–2,这时函数A一定在timeout前面触发。至于2排在TIMEOUT FIRED的后面(即函数B在timeout后面触发),是因为setImmediate总是将事件注册到下一轮Event Loop,所以函数A和timeout是在同一轮Loop执行,而函数B在下一轮Loop执行。</p><p>我们由此得到了process.nextTick和setImmediate的一个重要区别:多个process.nextTick语句总是在当前”执行栈”一次执行完,多个setImmediate可能则需要多次loop才能执行完。事实上,这正是Node.js 10.0版添加setImmediate方法的原因,否则像下面这样的递归调用process.nextTick,将会没完没了,主线程根本不会去读取”事件队列”!</p><blockquote> <figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line">> process.nextTick(<span class="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params"></span>) </span>{</span><br><span class="line">> process.nextTick(foo);</span><br><span class="line">> });</span><br><span class="line">></span><br></pre></td></tr></table></figure></blockquote><p>事实上,现在要是你写出递归的process.nextTick,Node.js会抛出一个警告,要求你改成setImmediate。</p><p>另外,由于process.nextTick指定的回调函数是在本次”事件循环”触发,而setImmediate指定的是在下次”事件循环”触发,所以很显然,前者总是比后者发生得早,而且执行效率也高(因为不用检查”任务队列”)。</p></script></p></li></ol>]]></content>
<summary type="html"><p>苹果的safari对不在一个事件循环内的popup操作判定为非用户主动触发,后果就是会被浏览器自动拦截。所以开始查询浏览器事件循环相关的资料,如果有遇到同样问题的小伙伴可以参考一下。</p></summary>
<category term="web" scheme="https://blog.xiaomo.info/categories/web/"/>
<category term="web" scheme="https://blog.xiaomo.info/tags/web/"/>
</entry>
<entry>
<title>vue框架状态管理之vuex</title>
<link href="https://blog.xiaomo.info/2020/vueFrameworkVuex/"/>
<id>https://blog.xiaomo.info/2020/vueFrameworkVuex/</id>
<published>2020-10-28T00:00:00.000Z</published>
<updated>2022-08-10T23:53:17.651Z</updated>
<content type="html"><![CDATA[<p>作为一个全沾攻城狮,需要会的东西实在太多了。虽然思路都大同小异,但是对于不同的框架,架构和使用的api都有着不小的区别,还是需要花费点时间学习一下,今天要记录的是便是管理vue状态的框架vuex。</p><a id="more"></a><h1 id="vuex是什么"><a href="#vuex是什么" class="headerlink" title="vuex是什么"></a>vuex是什么</h1><p><a href="https://vuex.vuejs.org/zh/" target="_blank" rel="noopener">vuex</a>是一个状态管理框架,也可以叫做数据共享框架。它能够创建一个独立于组件之外的一个共享数据仓储,将需要的数据放在store当中,当需要用到数据的组件通过this.$store.state.xxx获取 。</p><h1 id="不使用vuex的现状"><a href="#不使用vuex的现状" class="headerlink" title="不使用vuex的现状"></a>不使用vuex的现状</h1><p>父向子传值: v-bind,子组件通过props接收。 </p><p>子向父传值: $emit()发射自定义事件, 父组件用v-on监听事件。 </p><p>不相干组件传值,eventbus实现。 接收方用$on,传递方用$emit() </p><p>此种方式只能在小型项目中使用,当需要大量组件需要传值时,会通过其他不相干的组件层层传递,导致组件之间耦合严重,维护相当吃力。</p><h1 id="使用vuex有什么好处"><a href="#使用vuex有什么好处" class="headerlink" title="使用vuex有什么好处"></a>使用vuex有什么好处</h1><p>vuex创建的state是独立于组件的,能够解藕组件。</p><p>集中管理数据,易于维护。</p><p>共享数据,存储和获取都非常便捷。</p><p>state中的数据都是响应式的,引用state的组件能够实时同步UI</p><p> 存储原则:一般来说将需要共享的数据放在state中,私有的数据放在组件的data中。但是也可以将所有数据都放在state中,这要看个人和项目组的的开发习惯。</p><h1 id="vuex组成部分"><a href="#vuex组成部分" class="headerlink" title="vuex组成部分"></a>vuex组成部分</h1><p>解构注意事项: state和getter本质上是属性,所以解构的时候是放在computed中,actions和mutactions是方法,所以解构是要放在methods中。</p><h3 id="state(公共数据源,需要共享的数据都放在这里面)"><a href="#state(公共数据源,需要共享的数据都放在这里面)" class="headerlink" title="state(公共数据源,需要共享的数据都放在这里面)"></a>state(公共数据源,需要共享的数据都放在这里面)</h3><p>使用的时候: this.$store.state.count (this可以省略)</p><p>或者使用mapState解构为computed属性,使用哪种方法都可以</p><figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">template</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span>></span></span><br><span class="line"> {{count}}</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">template</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 class="javascript"><span class="keyword">import</span> { mapState } <span class="keyword">from</span> <span class="string">'vuex'</span></span></span><br><span class="line"><span class="javascript"><span class="keyword">export</span> <span class="keyword">default</span> {</span></span><br><span class="line"></span><br><span class="line"> computed: {</span><br><span class="line"> ...mapState([</span><br><span class="line"><span class="actionscript"><span class="string">'count'</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"><span class="tag"></<span class="name">script</span>></span></span><br></pre></td></tr></table></figure><h3 id="mutations-同步的修改state,mutations中不能使用异步操作"><a href="#mutations-同步的修改state,mutations中不能使用异步操作" class="headerlink" title="mutations(同步的修改state,mutations中不能使用异步操作)"></a>mutations(同步的修改state,mutations中不能使用异步操作)</h3><p>用setTimeoutt等异步操作会出错 ,异步操作需要放在action中</p><p>好处:在统一的地方对应,方便管理和维护</p><figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line">add (state) {</span><br><span class="line">state.count++</span><br><span class="line">},</span><br><span class="line">addN (state, n) {</span><br><span class="line">state.count += n</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>使用时</p><p>第一种</p><p><code>this.$store.commit('add')</code></p><p>如果想传递参数</p><p><code>this.$store.commit('addN',3)</code></p><p>第二种mapMutations (注意要放在<code>methods</code>中)</p> <figure class="highlight js"><table><tr><td class="code"><pre><span class="line"> <span class="keyword">import</span> { mapMutations } <span class="keyword">from</span> <span class="string">'vuex'</span> </span><br><span class="line"><span class="comment">/// 省略其他内容</span></span><br><span class="line">methods: { ...mapMutations([ <span class="string">'add'</span>, <span class="string">'addN'</span> ]) }</span><br></pre></td></tr></table></figure><h3 id="actions-异步的修改state,它不能直接操作state,需要调用mucations中的方法来操作"><a href="#actions-异步的修改state,它不能直接操作state,需要调用mucations中的方法来操作" class="headerlink" title="actions(异步的修改state,它不能直接操作state,需要调用mucations中的方法来操作)"></a>actions(异步的修改state,它不能直接操作state,需要调用mucations中的方法来操作)</h3><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">mutations: {</span><br><span class="line"> add (state) {</span><br><span class="line"> state.count++</span><br><span class="line"> },</span><br><span class="line"> addN (state, n) {</span><br><span class="line"> state.count += n</span><br><span class="line"> }</span><br><span class="line">},</span><br><span class="line">actions: {</span><br><span class="line">addAsync (context) {</span><br><span class="line"> setTimeout(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> context.commit(<span class="string">'add'</span>)</span><br><span class="line"> }, <span class="number">1000</span>)</span><br><span class="line"> },</span><br><span class="line">addNAsync (context,n) {</span><br><span class="line"> setTimeout(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> context.commit(<span class="string">'addN'</span>,n)</span><br><span class="line"> }, <span class="number">1000</span>)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">},</span><br></pre></td></tr></table></figure><p>使用时:方式一</p><p>写一个方法,通过$store.dispatch这个action,想要传参数和mucation中写法一样</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">methods: {</span><br><span class="line"> handleAsyncAdd () {</span><br><span class="line"> <span class="keyword">this</span>.$store.dispatch(<span class="string">'addAsync'</span>)</span><br><span class="line"> },</span><br><span class="line"> handleAsyncAddN () {</span><br><span class="line"> <span class="keyword">this</span>.$store.dispatch(<span class="string">'addNAsync'</span>,<span class="number">4</span>)</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>第二种使用方法,解构出来直接使用(注意要放在<code>methods</code>中)</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">methods:{</span><br><span class="line"> ...mapActions([</span><br><span class="line"> <span class="string">'addAsync'</span>,</span><br><span class="line"> <span class="string">'addNAsync'</span></span><br><span class="line"> ]),</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><div @click=<span class="string">"addAsync"</span>></span><br></pre></td></tr></table></figure><h3 id="getters-用于对store中的数据进行加工形成新的数据,但不会影响store中的数据"><a href="#getters-用于对store中的数据进行加工形成新的数据,但不会影响store中的数据" class="headerlink" title="getters(用于对store中的数据进行加工形成新的数据,但不会影响store中的数据)"></a>getters(用于对store中的数据进行加工形成新的数据,但不会影响store中的数据)</h3><p>store的数据变化之后,getter中的数据也会变化</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">getters: {</span><br><span class="line"> showNum (state) {</span><br><span class="line"> <span class="keyword">return</span> <span class="string">`当前最新的数量 <span class="subst">${state.count}</span>`</span></span><br><span class="line"> }</span><br><span class="line">},</span><br></pre></td></tr></table></figure><p>使用时</p><figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">div</span> @<span class="attr">click</span>=<span class="string">"addAsync"</span>></span></span><br><span class="line"> {{$store.getters.showNum}}</span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br></pre></td></tr></table></figure><p>第二种使用方法(注意要放在<code>computed</code>中)</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">computed: {</span><br><span class="line"> ...mapGetters([</span><br><span class="line"> <span class="string">'showNum'</span></span><br><span class="line"> ])</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>使用时</p><figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">div</span> @<span class="attr">click</span>=<span class="string">"addAsync"</span>></span></span><br><span class="line"> {{showNum}}</span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br></pre></td></tr></table></figure><h3 id="modules-对store进行模块划分"><a href="#modules-对store进行模块划分" class="headerlink" title="modules(对store进行模块划分)"></a>modules(对store进行模块划分)</h3><p>使用场景:当项目组中有多个人同时开发时,如果放在一个文件里面则会出现修改冲突,这里需要使用modules进行文件切分再组合。</p><p>目录结构:</p><img src="https://image.xiaomo.info//blog/image-20201028145155073.png" alt="image-20201028145155073" style="zoom:50%;"><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// store/index.js</span></span><br><span class="line"><span class="keyword">import</span> Vue <span class="keyword">from</span> <span class="string">'vue'</span></span><br><span class="line"><span class="keyword">import</span> Vuex <span class="keyword">from</span> <span class="string">'vuex'</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> users <span class="keyword">from</span> <span class="string">'./users'</span></span><br><span class="line"><span class="keyword">import</span> todos <span class="keyword">from</span> <span class="string">'./todos'</span></span><br><span class="line"></span><br><span class="line">Vue.use(Vuex)</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">new</span> Vuex.Store({</span><br><span class="line"> modules: {</span><br><span class="line"> users,</span><br><span class="line"> todos</span><br><span class="line"> }</span><br><span class="line">})</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// /store/todos/index.js</span></span><br><span class="line"><span class="keyword">import</span> todoStates <span class="keyword">from</span> <span class="string">'./todoStates'</span></span><br><span class="line"><span class="keyword">import</span> todoActions <span class="keyword">from</span> <span class="string">'./todoActions'</span></span><br><span class="line"><span class="keyword">import</span> todoMutations <span class="keyword">from</span> <span class="string">'./todoMutations'</span></span><br><span class="line"><span class="keyword">import</span> todoGetters <span class="keyword">from</span> <span class="string">'./todoGetters'</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> {</span><br><span class="line"> namespaced: <span class="literal">true</span>,</span><br><span class="line"> state: todoStates,</span><br><span class="line"> mutations: todoMutations,</span><br><span class="line"> actions: todoActions,</span><br><span class="line"> getters: todoGetters</span><br><span class="line">}</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// /store/users.js</span></span><br><span class="line"><span class="keyword">import</span> userStates <span class="keyword">from</span> <span class="string">'./userStates'</span></span><br><span class="line"><span class="keyword">import</span> userActions <span class="keyword">from</span> <span class="string">'./userActions'</span></span><br><span class="line"><span class="keyword">import</span> userMutations <span class="keyword">from</span> <span class="string">'./userMutations'</span></span><br><span class="line"><span class="keyword">import</span> userGetters <span class="keyword">from</span> <span class="string">'./userGetters'</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> {</span><br><span class="line"> namespaced: <span class="literal">true</span>,</span><br><span class="line"> state: userStates,</span><br><span class="line"> mutations: userMutations,</span><br><span class="line"> actions: userActions,</span><br><span class="line"> getters: userGetters</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>使用的时候,解构的时候写法有区别。commit的时候需要加上模块名</p><figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">template</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">button</span> @<span class="attr">click</span>=<span class="string">"addTodo"</span>></span>add<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">li</span> <span class="attr">v-for</span>=<span class="string">"todo in todoList"</span> <span class="attr">:key</span>=<span class="string">"todo.name"</span>></span></span><br><span class="line"> {{todo.name}}</span><br><span class="line"> <span class="tag"></<span class="name">li</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">template</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="keyword">import</span> { mapState, mapMutations } <span class="keyword">from</span> <span class="string">'vuex'</span></span></span><br><span class="line"></span><br><span class="line"><span class="javascript"><span class="keyword">export</span> <span class="keyword">default</span> {</span></span><br><span class="line"> computed: {</span><br><span class="line"> ...mapState({</span><br><span class="line"><span class="javascript"> todoList: <span class="function"><span class="params">state</span> =></span> state.todos.todoList</span></span><br><span class="line"> })</span><br><span class="line"> },</span><br><span class="line"> methods: {</span><br><span class="line"> ...mapMutations({</span><br><span class="line"> addTodo (commit) {</span><br><span class="line"><span class="actionscript"> commit(<span class="string">'todos/addTodo'</span>, <span class="string">'test'</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"><span class="tag"></<span class="name">script</span>></span></span><br></pre></td></tr></table></figure><h1 id="使用注解方式"><a href="#使用注解方式" class="headerlink" title="使用注解方式"></a>使用注解方式</h1><p>只有使用ts的环境才能使用注解方式,通过调研有以下两种方案。至于怎么选择,看实际项目的情况下,有的时候不一定有发言权,leader定哪个就用哪个吧。</p><ul><li> <a href="https://github.com/championswimmer/vuex-module-decorators#readme" target="_blank" rel="noopener">vuex-module-decorators</a> : <a href="https://github.com/Armour/vue-typescript-admin-template/tree/minimal" target="_blank" rel="noopener">vue-typescript-admin-template</a>使用的解决方案</li><li> <a href="https://github.com/ktsn/vuex-class" target="_blank" rel="noopener">vuex-class</a>:非官方维护,在 vue-class-component 基础上补充一定<code>vuex</code>支持(支持有限)</li></ul><h3 id="vuex-module-decorators-用法"><a href="#vuex-module-decorators-用法" class="headerlink" title="vuex-module-decorators 用法"></a>vuex-module-decorators 用法</h3><p><code>yarn add vuex-module-decorators</code></p><figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> { Module, VuexModule, Mutation, Action } <span class="keyword">from</span> <span class="string">'vuex-module-decorators'</span></span><br><span class="line"> </span><br><span class="line"><span class="meta">@Module</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">class</span> Counter2 <span class="keyword">extends</span> VuexModule {</span><br><span class="line"> count = <span class="number">0</span></span><br><span class="line"> </span><br><span class="line"> <span class="meta">@Mutation</span></span><br><span class="line"> increment(delta: <span class="built_in">number</span>) {</span><br><span class="line"> <span class="keyword">this</span>.count += delta</span><br><span class="line"> }</span><br><span class="line"> <span class="meta">@Mutation</span></span><br><span class="line"> decrement(delta: <span class="built_in">number</span>) {</span><br><span class="line"> <span class="keyword">this</span>.count -= delta</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// action 'incr' commits mutation 'increment' when done with return value as payload</span></span><br><span class="line"> <span class="meta">@Action</span>({ commit: <span class="string">'increment'</span> })</span><br><span class="line"> incr() {</span><br><span class="line"> <span class="keyword">return</span> <span class="number">5</span></span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// action 'decr' commits mutation 'decrement' when done with return value as payload</span></span><br><span class="line"> <span class="meta">@Action</span>({ commit: <span class="string">'decrement'</span> })</span><br><span class="line"> decr() {</span><br><span class="line"> <span class="keyword">return</span> <span class="number">5</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="vuex-class用法"><a href="#vuex-class用法" class="headerlink" title="vuex-class用法"></a>vuex-class用法</h3><p><code>yarn add vuex-class</code></p><figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> Vue <span class="keyword">from</span> <span class="string">'vue'</span></span><br><span class="line"><span class="keyword">import</span> Component <span class="keyword">from</span> <span class="string">'vue-class-component'</span></span><br><span class="line"><span class="keyword">import</span> {</span><br><span class="line"> State,</span><br><span class="line"> Getter,</span><br><span class="line"> Action,</span><br><span class="line"> Mutation,</span><br><span class="line"> <span class="keyword">namespace</span></span><br><span class="line">} <span class="keyword">from</span> <span class="string">'vuex-class'</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> someModule = <span class="keyword">namespace</span>(<span class="string">'path/to/module'</span>)</span><br><span class="line"></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> MyComp <span class="keyword">extends</span> Vue {</span><br><span class="line"> <span class="meta">@State</span>(<span class="string">'foo'</span>) stateFoo</span><br><span class="line"> <span class="meta">@State</span>(<span class="function"><span class="params">state</span> =></span> state.bar) stateBar</span><br><span class="line"> <span class="meta">@Getter</span>(<span class="string">'foo'</span>) getterFoo</span><br><span class="line"> <span class="meta">@Action</span>(<span class="string">'foo'</span>) actionFoo</span><br><span class="line"> <span class="meta">@Mutation</span>(<span class="string">'foo'</span>) mutationFoo</span><br><span class="line"> <span class="meta">@someModule</span>.Getter(<span class="string">'foo'</span>) moduleGetterFoo</span><br><span class="line"></span><br><span class="line"> <span class="comment">// If the argument is omitted, use the property name</span></span><br><span class="line"> <span class="comment">// for each state/getter/action/mutation type</span></span><br><span class="line"> <span class="meta">@State</span> foo</span><br><span class="line"> <span class="meta">@Getter</span> bar</span><br><span class="line"> <span class="meta">@Action</span> baz</span><br><span class="line"> <span class="meta">@Mutation</span> qux</span><br><span class="line"></span><br><span class="line"> created () {</span><br><span class="line"> <span class="keyword">this</span>.stateFoo <span class="comment">// -> store.state.foo</span></span><br><span class="line"> <span class="keyword">this</span>.stateBar <span class="comment">// -> store.state.bar</span></span><br><span class="line"> <span class="keyword">this</span>.getterFoo <span class="comment">// -> store.getters.foo</span></span><br><span class="line"> <span class="keyword">this</span>.actionFoo({ value: <span class="literal">true</span> }) <span class="comment">// -> store.dispatch('foo', { value: true })</span></span><br><span class="line"> <span class="keyword">this</span>.mutationFoo({ value: <span class="literal">true</span> }) <span class="comment">// -> store.commit('foo', { value: true })</span></span><br><span class="line"> <span class="keyword">this</span>.moduleGetterFoo <span class="comment">// -> store.getters['path/to/module/foo']</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>参考文章:</p><p><a href="https://juejin.im/post/6844904003633954829" target="_blank" rel="noopener">Vue & TypeScript 初体验 - 使用Vuex (vuex-module-decorators)</a> </p>]]></content>
<summary type="html"><p>作为一个全沾攻城狮,需要会的东西实在太多了。虽然思路都大同小异,但是对于不同的框架,架构和使用的api都有着不小的区别,还是需要花费点时间学习一下,今天要记录的是便是管理vue状态的框架vuex。</p></summary>
<category term="web" scheme="https://blog.xiaomo.info/categories/web/"/>
<category term="vue" scheme="https://blog.xiaomo.info/tags/vue/"/>
</entry>
<entry>
<title>疫情下的回日坎坷路</title>
<link href="https://blog.xiaomo.info/2020/backToJapan/"/>
<id>https://blog.xiaomo.info/2020/backToJapan/</id>
<published>2020-08-18T00:00:00.000Z</published>
<updated>2022-08-10T23:53:17.651Z</updated>
<content type="html"><![CDATA[<p>背景介绍:熟悉小莫的应该知道,小莫在日本依旧从事着掉头发的工作。在去年过年前打算重温一下家乡,毅然决然请了半个月假(当时已经知道疫情,但是没想到的是这次影响居然大到我等凡人不可想象之境)。盼望着,盼望着,脱掉冬装换成春装,却依旧像一只只老鼠在这个魔幻的鼠年蹲在各自的小房子里。盼望着盼望着,历经重重艰难,回到了杭州,然而入馆限制却越来越厉害,一直持续到了8月。终于!!!!!可以迈上回日的坎坷之路了。</p><a id="more"></a><p>众所周知,日本入馆局从7.29日宣布:4.3号之前出境的长期签证可以回去了。但是!他是有条件滴….</p><ul><li>护照在留卡不可或缺</li><li>需要再入国确认书</li><li>日本规定格式的核酸检测证明<br>以上</li></ul><h1 id="再入国确认书"><a href="#再入国确认书" class="headerlink" title="再入国确认书"></a>再入国确认书</h1><p>去当地的入馆局申请,带上护照和签证<br>200大洋,8个工作日左右,可以邮寄(浙江地区,其他地区不详)<br><img src="https://oss.xiaomo.info/blog/toJapan.jpg"></p><h1 id="核酸检测证明(72小时以内)"><a href="#核酸检测证明(72小时以内)" class="headerlink" title="核酸检测证明(72小时以内)"></a>核酸检测证明(72小时以内)</h1><p>重要!!!!!<br>必须开日本大使馆提供的证明格式,有很多人到了日本因为证明格式不行导致相当大麻烦,具体参考 徐静波的文章 <a href="https://mp.weixin.qq.com/s/hIc-GqUXgY5fy8_TkFPKPA" target="_blank" rel="noopener">中国的核酸检测证明到了日本机场为啥不管用?</a><br><img src="https://oss.xiaomo.info/blog/china_certification.jpg"></p><h1 id="乘机经验分享"><a href="#乘机经验分享" class="headerlink" title="乘机经验分享"></a>乘机经验分享</h1><p>众所周知:目前国际航班价格升天,非富即贵者望票兴叹而不可得。但是!国内的机票是便宜的,并且!大连飞东京的票也是相对万元票也是便宜的。因此!从你在的地方飞大连周水子国际机场,出来找小黑店马杀鸡一晚,第二天神清气爽飞东京,NICE!</p><h1 id="乘机过程"><a href="#乘机过程" class="headerlink" title="乘机过程"></a>乘机过程</h1><p>特别提醒!<br>因为特殊时期检查繁琐,内容众多,效率低下,下,下不为例。反正就是提醒你早点去机场排队队队队队队队!!!</p><h1 id="上灰机"><a href="#上灰机" class="headerlink" title="上灰机"></a>上灰机</h1><ul><li>排队出示绿码</li><li>排队出示国务院行程卡</li><li>排队填写健康申报单</li><li>排队安检</li><li>排队办登机</li><li>排队测体温</li><li>排队再安检</li><li>排队登机</li><li>排队……算了上灰机了坐一会儿不排了</li></ul><h1 id="下灰机"><a href="#下灰机" class="headerlink" title="下灰机"></a>下灰机</h1><p>经历了天上3个小时的短暂休息,艰难的过程还在后面。如果国内的总结起来是排队排到怀疑人生,那么日本这边就是小板凳坐到欲哭无泪</p><ul><li>下鸡后经过长长的望眼欲不穿的走廊,坐上那整齐而又优雅的小板凳,一波一波的等待检阅(拿上证件过检时出示调查表,确认信息后等待核酸检测,这个表之后很长一段时间都用它)<br><img src="https://oss.xiaomo.info/blog/chair.jpg"></li><li>到新的地方坐上那心爱的小板凳,等待检察官给你发一个小管管,到属于自己的小格子疯狂喷水。啊不好意思,是吐吐沫。吐半管管之后拿去确认没问题就可以继续下一步</li><li>拿上绿色的回执小卡片,在新的检察官的引导下来到新的小板凳聚集地,坐上那茫茫众多属于你的你的小板凳开始漫长的等待,这篇攻略便是在小板凳上用我的爪机写出来的<br><img src="https://oss.xiaomo.info/blog/check_result.jpg"></li><li>听到叫号之后拿上小卡去取结果,会在一个结果受付房间拿结果,阴性可以得一张红色小卡片,拿到直接就可以去办入境审查了。<br><img src="https://oss.xiaomo.info/blog/check_certification.jpg"></li><li>办入境审查的时候把阴性成就卡片,护照,在留卡,再入国确认书,国内开的阴性证明交到审查管,然后在一个小房间等待,大概15分钟可以即可出关拿行李。</li></ul><h1 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h1><p>之前听说出来之后会有审查官陪你到一直送上车并记录车牌号之后才会离开,我实际遇到的情况是审查管会问有没有人来接你,并不会跟着你。从你拿行李开始就和以前入境没有任何区别,但是有规定不能坐公共交通,万一查到,后果自负。</p><h1 id="转载请注明原文地址"><a href="#转载请注明原文地址" class="headerlink" title="转载请注明原文地址"></a>转载请注明原文地址</h1><p><a href="https://blog.xiaomo.info/2020/backToJapan/">https://blog.xiaomo.info/2020/backToJapan/</a></p>]]></content>
<summary type="html"><p>背景介绍:熟悉小莫的应该知道,小莫在日本依旧从事着掉头发的工作。在去年过年前打算重温一下家乡,毅然决然请了半个月假(当时已经知道疫情,但是没想到的是这次影响居然大到我等凡人不可想象之境)。盼望着,盼望着,脱掉冬装换成春装,却依旧像一只只老鼠在这个魔幻的鼠年蹲在各自的小房子里。盼望着盼望着,历经重重艰难,回到了杭州,然而入馆限制却越来越厉害,一直持续到了8月。终于!!!!!可以迈上回日的坎坷之路了。</p></summary>
<category term="japan" scheme="https://blog.xiaomo.info/categories/japan/"/>
<category term="japan" scheme="https://blog.xiaomo.info/tags/japan/"/>
</entry>
<entry>
<title>unity常见面试题</title>
<link href="https://blog.xiaomo.info/2020/unityInterviewQuestions/"/>
<id>https://blog.xiaomo.info/2020/unityInterviewQuestions/</id>
<published>2020-01-07T00:00:00.000Z</published>
<updated>2022-08-10T23:53:17.651Z</updated>
<content type="html"><![CDATA[<p>算起来到现在为止面试的大多是游戏服务端或者web前端相关的类容,没有经历过一次正儿八经的Unity面试,通过面试题也可以侧面了解到Unity开发中有哪些常用的需要掌握的知识,因此搬运一篇号称“屎上最全”的博客当作备份。</p><a id="more"></a><h3 id="一.什么是渲染管道?"><a href="#一.什么是渲染管道?" class="headerlink" title="一.什么是渲染管道?"></a>一.什么是渲染管道?</h3><p>是指在显示器上为了显示出图像而经过的一系列必要操作。<br>渲染管道中的很多步骤,都要将几何物体从一个坐标系中变换到另一个坐标系中去。<br>主要步骤有:<br>本地坐标->视图坐标->背面裁剪->光照->裁剪->投影->视图变换->光栅化。</p><h3 id="二.如何优化内存?"><a href="#二.如何优化内存?" class="headerlink" title="二.如何优化内存?"></a>二.如何优化内存?</h3><p>有很多种方式,例如</p><ul><li>1.压缩自带类库;</li><li>2.将暂时不用的以后还需要使用的物体隐藏起来而不是直接Destroy掉;</li><li>3.释放AssetBundle占用的资源;</li><li>4.降低模型的片面数,降低模型的骨骼数量,降低贴图的大小;</li><li>5.使用光照贴图,使用多层次细节(LOD),使用着色器(Shader),使用预设(Prefab)。</li></ul><h3 id="三、动态加载资源的方式?-有时候也问区别,具体请百度"><a href="#三、动态加载资源的方式?-有时候也问区别,具体请百度" class="headerlink" title="三、动态加载资源的方式?(有时候也问区别,具体请百度)"></a>三、动态加载资源的方式?(有时候也问区别,具体请百度)</h3><ul><li>1.Resources.Load();</li><li>2.AssetBundle</li></ul><h3 id="四:什么是协同程序?"><a href="#四:什么是协同程序?" class="headerlink" title="四:什么是协同程序?"></a>四:什么是协同程序?</h3><p>在主线程运行的同时开启另一段逻辑处理,来协助当前程序的执行,协程很像多线程,但是不是多线程,Unity的协程实在每帧结束之后去检测yield的条件是否满足。<br>五:Unity3d中的碰撞器和触发器的区别?</p><p>碰撞器是触发器的载体,而触发器只是碰撞器身上的一个属性。当Is Trigger=false时,碰撞器根据物理引擎引发碰撞,产生碰撞的效果,可以调用OnCollisionEnter/Stay/Exit函数;当Is Trigger=true时,碰撞器被物理引擎所忽略,没有碰撞效果,可以调用OnTriggerEnter/Stay/Exit函数。如果既要检测到物体的接触又不想让碰撞检测影响物体移动或要检测一个物件是否经过空间中的某个区域这时就可以用到触发器</p><h3 id="六:物体发生碰撞的必要条件?"><a href="#六:物体发生碰撞的必要条件?" class="headerlink" title="六:物体发生碰撞的必要条件?"></a>六:物体发生碰撞的必要条件?</h3><p>两个物体都必须带有碰撞器(Collider),其中一个物体还必须带有Rigidbody刚体,而且必须是运动的物体带有Rigidbody脚本才能检测到碰撞。</p><h3 id="七:请简述ArrayList和List的主要区别?"><a href="#七:请简述ArrayList和List的主要区别?" class="headerlink" title="七:请简述ArrayList和List的主要区别?"></a>七:请简述ArrayList和List的主要区别?</h3><p>ArrayList存在不安全类型(ArrayList会把所有插入其中的数据都当做Object来处理)
装箱拆箱的操作(费时)
List是接口,ArrayList是一个实现了该接口的类,可以被实例化</p><h3 id="八:如何安全的在不同工程间安全地迁移asset数据?三种方法"><a href="#八:如何安全的在不同工程间安全地迁移asset数据?三种方法" class="headerlink" title="八:如何安全的在不同工程间安全地迁移asset数据?三种方法"></a>八:如何安全的在不同工程间安全地迁移asset数据?三种方法</h3><ul><li>1.将Assets目录和Library目录一起迁移</li><li>2.导出包,export Package</li><li>3.用unity自带的assets Server功能</li></ul><h3 id="九:OnEnable、Awake、Start运行时的发生顺序?哪些可能在同一个对象周期中反复的发生"><a href="#九:OnEnable、Awake、Start运行时的发生顺序?哪些可能在同一个对象周期中反复的发生" class="headerlink" title="九:OnEnable、Awake、Start运行时的发生顺序?哪些可能在同一个对象周期中反复的发生"></a>九:OnEnable、Awake、Start运行时的发生顺序?哪些可能在同一个对象周期中反复的发生</h3><p>Awake –>OnEnable->Start,OnEnable在同一周期中可以反复地发生。</p><h3 id="十:MeshRender中material和sharedmaterial的区别?"><a href="#十:MeshRender中material和sharedmaterial的区别?" class="headerlink" title="十:MeshRender中material和sharedmaterial的区别?"></a>十:MeshRender中material和sharedmaterial的区别?</h3><p>修改sharedMaterial将改变所有物体使用这个材质的外观,并且也改变储存在工程里的材质设置。不推荐修改由sharedMaterial返回的材质。如果你想修改渲染器的材质,使用material替代。</p><h3 id="十一:Unity提供了几种光源,分别是什么"><a href="#十一:Unity提供了几种光源,分别是什么" class="headerlink" title="十一:Unity提供了几种光源,分别是什么"></a>十一:Unity提供了几种光源,分别是什么</h3><p>四种。</p><ul><li>平行光:Directional Light</li><li>点光源:Point Light</li><li>聚光灯:Spot Light</li><li>区域光源:Area Light</li></ul><h3 id="十二:简述一下对象池,你觉得在FPS里哪些东西适合使用对象池"><a href="#十二:简述一下对象池,你觉得在FPS里哪些东西适合使用对象池" class="headerlink" title="十二:简述一下对象池,你觉得在FPS里哪些东西适合使用对象池"></a>十二:简述一下对象池,你觉得在FPS里哪些东西适合使用对象池</h3><p>对象池就存放需要被反复调用资源的一个空间,当一个对象回大量生成的时候如果每次都销毁创建会很费时间,通过对象池把暂时不用的对象放到一个池中(也就是一个集合),当下次要重新生成这个对象的时候先去池中查找一下是否有可用的对象,如果有的话就直接拿出来使用,不需要再创建,如果池中没有可用的对象,才需要重新创建,利用空间换时间来达到游戏的高速运行效果,在FPS游戏中要常被大量复制的对象包括子弹,敌人,粒子等</p><h3 id="十三:CharacterController和Rigidbody的区别"><a href="#十三:CharacterController和Rigidbody的区别" class="headerlink" title="十三:CharacterController和Rigidbody的区别"></a>十三:CharacterController和Rigidbody的区别</h3><p>Rigidbody具有完全真实物理的特性,Unity中物理系统最基本的一个组件,包含了常用的物理特性,而CharacterController可以说是受限的的Rigidbody,具有一定的物理效果但不是完全真实的,是Unity为了使开发者能方便的开发第一人称视角的游戏而封装的一个组件</p><h3 id="十四:简述prefab的用处"><a href="#十四:简述prefab的用处" class="headerlink" title="十四:简述prefab的用处"></a>十四:简述prefab的用处</h3><p>在游戏运行时实例化,prefab相当于一个模板,对你已经有的素材、脚本、参数做一个默认的配置,以便于以后的修改,同时prefab打包的内容简化了导出的操作,便于团队的交流。</p><h3 id="十五:请简述sealed关键字用在类声明时与函数声明时的作用"><a href="#十五:请简述sealed关键字用在类声明时与函数声明时的作用" class="headerlink" title="十五:请简述sealed关键字用在类声明时与函数声明时的作用"></a>十五:请简述sealed关键字用在类声明时与函数声明时的作用</h3><p>sealed修饰的类为密封类,类声明时可防止其他类继承此类,在方法中声明则可防止派生类重写此方法。</p><h3 id="十六:请简述private,public,protected,internal的区别"><a href="#十六:请简述private,public,protected,internal的区别" class="headerlink" title="十六:请简述private,public,protected,internal的区别"></a>十六:请简述private,public,protected,internal的区别</h3><ul><li>public:对任何类和成员都公开,无限制访问</li><li>private:仅对该类公开</li><li>protected:对该类和其派生类公开</li><li>internal:只能在包含该类的程序集中访问该类</li></ul><h3 id="十七:使用Unity3d实现2d游戏,有几种方式?"><a href="#十七:使用Unity3d实现2d游戏,有几种方式?" class="headerlink" title="十七:使用Unity3d实现2d游戏,有几种方式?"></a>十七:使用Unity3d实现2d游戏,有几种方式?</h3><ul><li>1.使用本身的GUI,在Unity4.6以后出现的UGUI</li><li>2.把摄像机的Projection(投影)值调为Orthographic(正交投影),不考虑z轴;</li><li>3.使用2d插件,如:2DToolKit,和NGUI</li></ul><h3 id="十八:在物体发生碰撞的整个过程中,有几个阶段,分别列出对应的函数"><a href="#十八:在物体发生碰撞的整个过程中,有几个阶段,分别列出对应的函数" class="headerlink" title="十八:在物体发生碰撞的整个过程中,有几个阶段,分别列出对应的函数"></a>十八:在物体发生碰撞的整个过程中,有几个阶段,分别列出对应的函数</h3><p>三个阶段,1.OnCollisionEnter 2.OnCollisionStay 3.OnCollisionExit</p><h3 id="十九:Unity3d的物理引擎中,有几种施加力的方式,分别描述出来"><a href="#十九:Unity3d的物理引擎中,有几种施加力的方式,分别描述出来" class="headerlink" title="十九:Unity3d的物理引擎中,有几种施加力的方式,分别描述出来"></a>十九:Unity3d的物理引擎中,有几种施加力的方式,分别描述出来</h3><p>rigidbody.AddForce/AddForceAtPosition,都在rigidbody系列函数中。大家可以自己去查看一下rigidbody的API</p><h3 id="二十:什么叫做链条关节?"><a href="#二十:什么叫做链条关节?" class="headerlink" title="二十:什么叫做链条关节?"></a>二十:什么叫做链条关节?</h3><p>Hinge Joint,可以模拟两个物体间用一根链条连接在一起的情况,能保持两个物体在一个固定距离内部相互移动而不产生作用力,但是达到固定距离后就会产生拉力。</p><h3 id="二十一:物体自身旋转使用的函数?"><a href="#二十一:物体自身旋转使用的函数?" class="headerlink" title="二十一:物体自身旋转使用的函数?"></a>二十一:物体自身旋转使用的函数?</h3><p>Transform.Rotate()</p><h3 id="二十二:Unity3d提供了一个用于保存和读取数据的类-PlayerPrefs-,请列出保存和读取整形数据的函数"><a href="#二十二:Unity3d提供了一个用于保存和读取数据的类-PlayerPrefs-,请列出保存和读取整形数据的函数" class="headerlink" title="二十二:Unity3d提供了一个用于保存和读取数据的类(PlayerPrefs),请列出保存和读取整形数据的函数"></a>二十二:Unity3d提供了一个用于保存和读取数据的类(PlayerPrefs),请列出保存和读取整形数据的函数</h3><p>PlayerPrefs.SetInt() PlayerPrefs.GetInt()</p><h3 id="二十三:Unity3d脚本从唤醒到销毁有着一套比较完整的生命周期,请列出系统自带的几个重要的方法。"><a href="#二十三:Unity3d脚本从唤醒到销毁有着一套比较完整的生命周期,请列出系统自带的几个重要的方法。" class="headerlink" title="二十三:Unity3d脚本从唤醒到销毁有着一套比较完整的生命周期,请列出系统自带的几个重要的方法。"></a>二十三:Unity3d脚本从唤醒到销毁有着一套比较完整的生命周期,请列出系统自带的几个重要的方法。</h3><p>Awake——>OnEnable–>Start——>Update——>FixedUpdate——>LateUpdate——>OnGUI——>OnDisable——>OnDestroy</p><h3 id="二十四:物理更新一般放在哪个系统函数里?"><a href="#二十四:物理更新一般放在哪个系统函数里?" class="headerlink" title="二十四:物理更新一般放在哪个系统函数里?"></a>二十四:物理更新一般放在哪个系统函数里?</h3><p>FixedUpdate,每固定帧绘制时执行一次,和Update不同的是FixedUpdate是渲染帧执行,如果你的渲染效率低下的时候FixedUpdate调用次数就会跟着下降。FixedUpdate比较适用于物理引擎的计算,因为是跟每帧渲染有关。Update就比较适合做控制。</p><h3 id="二十五:在场景中放置多个Camera并同时处于活动状态会发生什么?"><a href="#二十五:在场景中放置多个Camera并同时处于活动状态会发生什么?" class="headerlink" title="二十五:在场景中放置多个Camera并同时处于活动状态会发生什么?"></a>二十五:在场景中放置多个Camera并同时处于活动状态会发生什么?</h3><p>游戏界面可以看到很多摄像机的混合。</p><h3 id="二十六:如何销毁一个UnityEngine-Object及其子类?"><a href="#二十六:如何销毁一个UnityEngine-Object及其子类?" class="headerlink" title="二十六:如何销毁一个UnityEngine.Object及其子类?"></a>二十六:如何销毁一个UnityEngine.Object及其子类?</h3><p>使用Destroy()方法;</p><h3 id="二十七:请描述为什么Unity3d中会发生在组件上出现数据丢失的情况"><a href="#二十七:请描述为什么Unity3d中会发生在组件上出现数据丢失的情况" class="headerlink" title="二十七:请描述为什么Unity3d中会发生在组件上出现数据丢失的情况"></a>二十七:请描述为什么Unity3d中会发生在组件上出现数据丢失的情况</h3><p>一般是组件上绑定的物体对象被删除了</p><h3 id="二十八:LOD是什么,优缺点是什么?"><a href="#二十八:LOD是什么,优缺点是什么?" class="headerlink" title="二十八:LOD是什么,优缺点是什么?"></a>二十八:LOD是什么,优缺点是什么?</h3><p>LOD(Level of detail)多层次细节,是最常用的游戏优化技术。它按照模型的位置和重要程度决定物体渲染的资源分配,降低非重要物体的面数和细节度,从而获得高效率的渲染运算。缺点是增加了内存。</p><h3 id="二十九:MipMap是什么,作用?"><a href="#二十九:MipMap是什么,作用?" class="headerlink" title="二十九:MipMap是什么,作用?"></a>二十九:MipMap是什么,作用?</h3><p>MipMapping:在三维计算机图形的贴图渲染中有常用的技术,为加快渲染进度和减少图像锯齿,贴图被处理成由一系列被预先计算和优化过的图片组成的文件,这样的贴图被称为MipMap。</p><h3 id="三十:请描述Interface与抽象类之间的不同"><a href="#三十:请描述Interface与抽象类之间的不同" class="headerlink" title="三十:请描述Interface与抽象类之间的不同"></a>三十:请描述Interface与抽象类之间的不同</h3><p>抽象类表示该类中可能已经有一些方法的具体定义,但接口就是公公只能定义各个方法的界面 ,不能具体的实现代码在成员方法中。类是子类用来继承的,当父类已经有实际功能的方法时该方法在子类中可以不必实现,直接引用父类的方法,子类也可以重写该父类的方法。实现接口的时候必须要实现接口中所有的方法,不能遗漏任何一个。</p><h3 id="三十一:-Net与Mono的关系?"><a href="#三十一:-Net与Mono的关系?" class="headerlink" title="三十一:.Net与Mono的关系?"></a>三十一:.Net与Mono的关系?</h3><p>mono是.net的一个开源跨平台工具,就类似java虚拟机,java本身不是跨平台语言,但运行在虚拟机上就能够实现了跨平台。.net只能在windows下运行,mono可以实现跨平台跑,可以运行于linux,Unix,Mac OS等。</p><h3 id="三十二:简述Unity3D支持的作为脚本的语言的名称"><a href="#三十二:简述Unity3D支持的作为脚本的语言的名称" class="headerlink" title="三十二:简述Unity3D支持的作为脚本的语言的名称"></a>三十二:简述Unity3D支持的作为脚本的语言的名称</h3><p>Unity的脚本语言基于Mono的.Net平台上运行,可以使用.NET库,这也为XML、数据库、正则表达式等问题提供了很好的解决方案。Unity里的脚本都会经过编译,他们的运行速度也很快。这三种语言实际上的功能和运行速度是一样的,区别主要体现在语言特性上。JavaScript、 C#、Boo</p><h3 id="三十三:U3D中用于记录节点空间几何信息的组件名称,及其父类名称"><a href="#三十三:U3D中用于记录节点空间几何信息的组件名称,及其父类名称" class="headerlink" title="三十三:U3D中用于记录节点空间几何信息的组件名称,及其父类名称"></a>三十三:U3D中用于记录节点空间几何信息的组件名称,及其父类名称</h3><p>Transform 父类是 Component</p><h3 id="三十四:向量的点乘、叉乘以及归一化的意义?"><a href="#三十四:向量的点乘、叉乘以及归一化的意义?" class="headerlink" title="三十四:向量的点乘、叉乘以及归一化的意义?"></a>三十四:向量的点乘、叉乘以及归一化的意义?</h3><ul><li>1.点乘描述了两个向量的相似程度,结果越大两向量越相似,还可表示投影</li><li>2.叉乘得到的向量垂直于原来的两个向量</li><li>3.标准化向量:用在只关系方向,不关心大小的时候<h3 id="三十五:为何大家都在移动设备上寻求U3D原生GUI的替代方案"><a href="#三十五:为何大家都在移动设备上寻求U3D原生GUI的替代方案" class="headerlink" title="三十五:为何大家都在移动设备上寻求U3D原生GUI的替代方案"></a>三十五:为何大家都在移动设备上寻求U3D原生GUI的替代方案</h3>不美观,OnGUI很耗费时间,效率不高,使用不方便</li></ul><h3 id="三十六:请简述如何在不同分辨率下保持UI的一致性"><a href="#三十六:请简述如何在不同分辨率下保持UI的一致性" class="headerlink" title="三十六:请简述如何在不同分辨率下保持UI的一致性"></a>三十六:请简述如何在不同分辨率下保持UI的一致性</h3><p>NGUI很好的解决了这一点,屏幕分辨率的自适应性,原理就是计算出屏幕的宽高比跟原来的预设的屏幕分辨率求出一个对比值,然后修改摄像机的size。UGUI通过锚点和中心点和分辨率也解决这个问题</p><h3 id="三十七:什么是LightMap?"><a href="#三十七:什么是LightMap?" class="headerlink" title="三十七:什么是LightMap?"></a>三十七:什么是LightMap?</h3><p>LightMap:就是指在三维软件里实现打好光,然后渲染把场景各表面的光照输出到贴图上,最后又通过引擎贴到场景上,这样就使物体有了光照的感觉。</p><h3 id="三十八:Unity和cocos2d的区别"><a href="#三十八:Unity和cocos2d的区别" class="headerlink" title="三十八:Unity和cocos2d的区别"></a>三十八:Unity和cocos2d的区别</h3><ul><li><ol><li>Unity3D支持C#、javascript等,cocos2d-x 支持c++、Html5、Lua等。</li></ol></li><li><ol start="2"><li>cocos2d 开源 并且免费</li></ol></li><li><ol start="3"><li>Unity3D支持iOS、Android、Flash、Windows、Mac、Wii等平台的游戏开发,cocos2d-x支持iOS、Android、WP等。</li></ol></li></ul><h3 id="三十九:C-和C-的区别?"><a href="#三十九:C-和C-的区别?" class="headerlink" title="三十九:C#和C++的区别?"></a>三十九:C#和C++的区别?</h3><p>简单的说:C### 与C++ 比较的话,最重要的特性就是C### 是一种完全面向对象的语言,而C++ 不是,另外C### 是基于IL 中间语言和.NET Framework CLR 的,在可移植性,可维护性和强壮性都比C++ 有很大的改进。C### 的设计目标是用来开发快速稳定可扩展的应用程序,当然也可以通过Interop 和Pinvoke 完成一些底层操作。更详细的区别大家可以参考这里</p><h3 id="四十:结构体和类有何区别?"><a href="#四十:结构体和类有何区别?" class="headerlink" title="四十:结构体和类有何区别?"></a>四十:结构体和类有何区别?</h3><p>结构体是一种值类型,而类是引用类型。(值类型、引用类型是根据数据存储的角度来分的)就是值类型用于存储数据的值,引用类型用于存储对实际数据的引用。那么结构体就是当成值来使用的,类则通过引用来对实际数据操作</p><h3 id="四十一:ref参数和out参数是什么?有什么区别?"><a href="#四十一:ref参数和out参数是什么?有什么区别?" class="headerlink" title="四十一:ref参数和out参数是什么?有什么区别?"></a>四十一:ref参数和out参数是什么?有什么区别?</h3><p>ref和out参数的效果一样,都是通过关键字找到定义在主函数里面的变量的内存地址,并通过方法体内的语法改变它的大小。不同点就是输出参数必须对参数进行初始化。ref必须初始化,out 参数必须在函数里赋值。ref参数是引用,out参数为输出参数。</p><h3 id="四十二:C-的委托是什么?有何用处?"><a href="#四十二:C-的委托是什么?有何用处?" class="headerlink" title="四十二:C#的委托是什么?有何用处?"></a>四十二:C#的委托是什么?有何用处?</h3><p>委托类似于一种安全的指针引用,在使用它时是当做类来看待而不是一个方法,相当于对一组方法的列表的引用。用处:使用委托使程序员可以将方法引用封装在委托对象内。然后可以将该委托对象传递给可调用所引用方法的代码,而不必在编译时知道将调用哪个方法。与C或C++中的函数指针不同,委托是面向对象,而且是类型安全的。</p><h3 id="四十三:C-中的排序方式有哪些?"><a href="#四十三:C-中的排序方式有哪些?" class="headerlink" title="四十三:C#中的排序方式有哪些?"></a>四十三:C#中的排序方式有哪些?</h3><p>选择排序,冒泡排序,快速排序,插入排序,希尔排序,归并排序</p><h3 id="四十四:射线检测碰撞物的原理是?"><a href="#四十四:射线检测碰撞物的原理是?" class="headerlink" title="四十四:射线检测碰撞物的原理是?"></a>四十四:射线检测碰撞物的原理是?</h3><p>射线是3D世界中一个点向一个方向发射的一条无终点的线,在发射轨迹中与其他物体发生碰撞时,它将停止发射 。</p><h3 id="四十五:Unity中,照相机的Clipping-Planes的作用是什么?调整Near、Fare两个值时,应该注意什么?"><a href="#四十五:Unity中,照相机的Clipping-Planes的作用是什么?调整Near、Fare两个值时,应该注意什么?" class="headerlink" title="四十五:Unity中,照相机的Clipping Planes的作用是什么?调整Near、Fare两个值时,应该注意什么?"></a>四十五:Unity中,照相机的Clipping Planes的作用是什么?调整Near、Fare两个值时,应该注意什么?</h3><p>剪裁平面 。从相机到开始渲染和停止渲染之间的距离。</p><h3 id="四十六:如何让已经存在的GameObject在LoadLevel后不被卸载掉?"><a href="#四十六:如何让已经存在的GameObject在LoadLevel后不被卸载掉?" class="headerlink" title="四十六:如何让已经存在的GameObject在LoadLevel后不被卸载掉?"></a>四十六:如何让已经存在的GameObject在LoadLevel后不被卸载掉?</h3><figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Awake</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> DontDestroyOnLoad(transform.gameObject);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="四十七:请简述GC(垃圾回收)产生的原因,并描述如何避免?"><a href="#四十七:请简述GC(垃圾回收)产生的原因,并描述如何避免?" class="headerlink" title="四十七:请简述GC(垃圾回收)产生的原因,并描述如何避免?"></a>四十七:请简述GC(垃圾回收)产生的原因,并描述如何避免?</h3><p>GC回收堆上的内存<br>避免:</p><ul><li>1.减少new产生对象的次数</li><li>2.使用公用的对象(静态成员)</li><li>3.将String换为StringBuilder</li></ul><h3 id="四十八:反射的实现原理?"><a href="#四十八:反射的实现原理?" class="headerlink" title="四十八:反射的实现原理?"></a>四十八:反射的实现原理?</h3><p>审查元数据并收集关于它的类型信息的能力。实现原理:在运行时根据程序集及其中的类型得到元数据。下面是实现步骤:</p><ol><li>导入using System.Reflection;</li><li>Assembly.Load(“程序集”)加载程序集,返回类型是一个Assembly</li><li>得到程序集中所有类的名称</li></ol><figure class="highlight routeros"><table><tr><td class="code"><pre><span class="line"><span class="keyword">foreach</span> (Type<span class="built_in"> type </span><span class="keyword">in</span> assembly.GetTypes())</span><br><span class="line">{</span><br><span class="line"> string t = type.Name;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ol start="4"><li>Type type = assembly.GetType(“程序集.类名”);获取当前类的类型</li><li>Activator.CreateInstance(type); 创建此类型实例</li><li>MethodInfo mInfo = type.GetMethod(“方法名”);获取当前方法</li><li>m.Info.Invoke(null,方法参数);</li></ol><h3 id="四十九:简述四元数的作用,四元数对欧拉角的优点?"><a href="#四十九:简述四元数的作用,四元数对欧拉角的优点?" class="headerlink" title="四十九:简述四元数的作用,四元数对欧拉角的优点?"></a>四十九:简述四元数的作用,四元数对欧拉角的优点?</h3><p>四元数用于表示旋转<br>相对欧拉角的优点:<br>1.能进行增量旋转<br>2.避免万向锁<br>3.给定方位的表达方式有两种,互为负(欧拉角有无数种表达方式)</p><h3 id="五十:移动相机动作在哪个函数里,为什么在这个函数里?"><a href="#五十:移动相机动作在哪个函数里,为什么在这个函数里?" class="headerlink" title="五十:移动相机动作在哪个函数里,为什么在这个函数里?"></a>五十:移动相机动作在哪个函数里,为什么在这个函数里?</h3><p>LateUpdate,是在所有的update结束后才调用,比较适合用于命令脚本的执行。官网上例子是摄像机的跟随,都是所有的update操作完才进行摄像机的跟进,不然就有可能出现摄像机已经推进了,但是视角里还未有角色的空帧出现。</p><h3 id="五十一:GPU的工作原理"><a href="#五十一:GPU的工作原理" class="headerlink" title="五十一:GPU的工作原理"></a>五十一:GPU的工作原理</h3><p>简而言之,GPU的图形(处理)流水线完成如下的工作:(并不一定是按照如下顺序) </p><ul><li>顶点处理:这阶段GPU读取描述3D图形外观的顶点数据并根据顶点数据确定3D图形的形状及位置关系,建立起3D图形的骨架。在支持DX8和DX9规格的GPU中,这些工作由硬件实现的Vertex Shader(定点着色器)完成。 </li><li>光栅化计算:显示器实际显示的图像是由像素组成的,我们需要将上面生成的图形上的点和线通过一定的算法转换到相应的像素点。把一个矢量图形转换为一系列像素点的过程就称为光栅化。例如,一条数学表示的斜线段,最终被转化成阶梯状的连续像素点。 </li><li>纹理帖图:顶点单元生成的多边形只构成了3D物体的轮廓,而纹理映射(texture mapping)工作完成对多变形表面的帖图,通俗的说,就是将多边形的表面贴上相应的图片,从而生成“真实”的图形。TMU(Texture mapping unit)即是用来完成此项工作。 </li><li>像素处理:这阶段(在对每个像素进行光栅化处理期间)GPU完成对像素的计算和处理,从而确定每个像素的最终属性。在支持DX8和DX9规格的GPU中,这些工作由硬件实现的Pixel Shader(像素着色器)完成。 </li><li>最终输出:由ROP(光栅化引擎)最终完成像素的输出,1帧渲染完毕后,被送到显存帧缓冲区。</li><li>总结:GPU的工作通俗的来说就是完成3D图形的生成,将图形映射到相应的像素点上,对每个像素进行计算确定最终颜色并完成输出。</li></ul><h3 id="五十二:什么是渲染管道?"><a href="#五十二:什么是渲染管道?" class="headerlink" title="五十二:什么是渲染管道?"></a>五十二:什么是渲染管道?</h3><p>是指在显示器上为了显示出图像而经过的一系列必要操作。 渲染管道中的很多步骤,都要将几何物体从一个坐标系中变换到另一个坐标系中去。主要步骤有:<br>本地坐标->视图坐标->背面裁剪->光照->裁剪->投影->视图变换->光栅化</p><h3 id="五十三:如何优化内存?"><a href="#五十三:如何优化内存?" class="headerlink" title="五十三:如何优化内存?"></a>五十三:如何优化内存?</h3><p>有很多种方式,例如</p><ul><li>1.压缩自带类库;</li><li>2.将暂时不用的以后还需要使用的物体隐藏起来而不是直接Destroy掉;</li><li>3.释放AssetBundle占用的资源;</li><li>4.降低模型的片面数,降低模型的骨骼数量,降低贴图的大小;</li><li>5.使用光照贴图,使用多层次细节(LOD),使用着色器(Shader),使用预设(Prefab)。</li><li>6.代码中少产生临时变量</li></ul><h3 id="五十四:动态加载资源的方式?他们之间的区别"><a href="#五十四:动态加载资源的方式?他们之间的区别" class="headerlink" title="五十四:动态加载资源的方式?他们之间的区别"></a>五十四:动态加载资源的方式?他们之间的区别</h3><ul><li>1.Resources.Load();</li><li>2.AssetBundle<br>区别参考</li></ul><h3 id="五十五:请描述游戏动画有哪几种,以及其原理?"><a href="#五十五:请描述游戏动画有哪几种,以及其原理?" class="headerlink" title="五十五:请描述游戏动画有哪几种,以及其原理?"></a>五十五:请描述游戏动画有哪几种,以及其原理?</h3><p>主要有关节动画、骨骼动画、单一网格模型动画(关键帧动画)。<br>关节动画:把角色分成若干独立部分,一个部分对应一个网格模型,部分的动画连接成一个整体的动画,角色比较灵活,Quake2中使用这种动画;<br>骨骼动画,广泛应用的动画方式,集成了以上两个方式的优点,骨骼按角色特点组成一定的层次结构,有关节相连,可做相对运动,皮肤作为单一网格蒙在骨骼之外,决定角色的外观;<br>单一网格模型动画由一个完整的网格模型构成,在动画序列的关键帧里记录各个顶点的原位置及其改变量,然后插值运算实现动画效果,角色动画较真实。</p><h3 id="五十六:alpha-blend工作原理"><a href="#五十六:alpha-blend工作原理" class="headerlink" title="五十六:alpha blend工作原理"></a>五十六:alpha blend工作原理</h3><p>Alpha Blend 实现透明效果,不过只能针对某块区域进行alpha操作,透明度可设。</p><h3 id="五十七:写出光照计算中的diffuse的计算公式"><a href="#五十七:写出光照计算中的diffuse的计算公式" class="headerlink" title="五十七:写出光照计算中的diffuse的计算公式"></a>五十七:写出光照计算中的diffuse的计算公式</h3><figure class="highlight excel"><table><tr><td class="code"><pre><span class="line">diffuse = Kd x colorLight x <span class="built_in">max</span>(<span class="built_in">N</span>*L,<span class="number">0</span>);</span><br><span class="line">Kd 漫反射系数、colorLight 光的颜色、</span><br><span class="line"><span class="built_in">N</span> 单位法线向量、</span><br><span class="line">L 由点指向光源的单位向量、</span><br><span class="line">其中<span class="built_in">N</span>与L点乘,如果结果小于等于<span class="number">0</span>,则漫反射为<span class="number">0</span>。</span><br></pre></td></tr></table></figure><h3 id="五十八:两种阴影判断的方法、工作原理。"><a href="#五十八:两种阴影判断的方法、工作原理。" class="headerlink" title="五十八:两种阴影判断的方法、工作原理。"></a>五十八:两种阴影判断的方法、工作原理。</h3><ul><li>本影和半影:参考本影和半影</li><li>本影:景物表面上那些没有被光源直接照射的区域(全黑的轮廓分明的区域)。</li><li>半影:景物表面上那些被某些特定光源直接照射但并非被所有特定光源直接照射的区域(半明半暗区域)</li><li>工作原理:从光源处向物体的所有可见面投射光线,将这些面投影到场景中得到投影面,再将这些投影面与场景中的其他平面求交得出阴影多边形,保存这些阴影多边形信息,然后再按视点位置对场景进行相应处理得到所要求的视图(利用空间换时间,每次只需依据视点位置进行一次阴影计算即可,省去了一次消隐过程)</li></ul><h3 id="五十九:Vertex-Shader是什么,怎么计算?"><a href="#五十九:Vertex-Shader是什么,怎么计算?" class="headerlink" title="五十九:Vertex Shader是什么,怎么计算?"></a>五十九:Vertex Shader是什么,怎么计算?</h3><p>顶点着色器是一段执行在GPU上的程序,用来取代fixed pipeline中的transformation和lighting,Vertex Shader主要操作顶点。<br>Vertex Shader对输入顶点完成了从local space到homogeneous space(齐次空间)的变换过程,homogeneous space即projection space的下一个space。在这其间共有world transformation, view transformation和projection transformation及lighting几个过程。</p><h3 id="六十:下列代码在运行中会产生几个临时对象?"><a href="#六十:下列代码在运行中会产生几个临时对象?" class="headerlink" title="六十:下列代码在运行中会产生几个临时对象?"></a>六十:下列代码在运行中会产生几个临时对象?</h3><figure class="highlight cs"><table><tr><td class="code"><pre><span class="line"><span class="keyword">string</span> a = <span class="keyword">new</span> <span class="keyword">string</span>(<span class="string">"abc"</span>);</span><br><span class="line">a = (a.ToUpper() + <span class="string">"123"</span>).Substring(<span class="number">0</span>, <span class="number">2</span>); </span><br><span class="line">在C<span class="meta">#中第一行是会报错的(Java中倒是可行)。</span></span><br><span class="line">应该这样初始化:</span><br><span class="line"><span class="keyword">string</span> b = <span class="keyword">new</span> <span class="keyword">string</span>(<span class="keyword">new</span> <span class="keyword">char</span>[]{<span class="string">'a'</span>,<span class="string">'b'</span>,<span class="string">'c'</span>});</span><br></pre></td></tr></table></figure><p>答案为:5个临时对象</p><h3 id="六十一:下列代码在运行中会发生什么问题?如何避免?"><a href="#六十一:下列代码在运行中会发生什么问题?如何避免?" class="headerlink" title="六十一:下列代码在运行中会发生什么问题?如何避免?"></a>六十一:下列代码在运行中会发生什么问题?如何避免?</h3><figure class="highlight zephir"><table><tr><td class="code"><pre><span class="line"><span class="keyword">List</span><<span class="keyword">int</span>> ls = <span class="keyword">new</span> <span class="keyword">List</span><<span class="keyword">int</span>>(<span class="keyword">new</span> <span class="keyword">int</span>[] { <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span> });</span><br><span class="line"><span class="keyword">foreach</span> (<span class="keyword">int</span> item in ls)</span><br><span class="line">{</span><br><span class="line"> Console.WriteLine(item * item);</span><br><span class="line"> ls.Remove(item);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>产生运行时错误,在 ls.Remove(item)这行,因为foreach是只读的。不能一边遍历一边修改。</p><h3 id="六十二:Unity3D是否支持写成多线程程序?如果支持的话需要注意什么?"><a href="#六十二:Unity3D是否支持写成多线程程序?如果支持的话需要注意什么?" class="headerlink" title="六十二:Unity3D是否支持写成多线程程序?如果支持的话需要注意什么?"></a>六十二:Unity3D是否支持写成多线程程序?如果支持的话需要注意什么?</h3><p>仅能从主线程中访问Unity3D的组件,对象和Unity3D系统调用<br>支持:如果同时你要处理很多事情或者与Unity的对象互动小可以用thread,否则使用coroutine。<br>注意:C#中有lock这个关键字,以确保只有一个线程可以在特定时间内访问特定的对象</p><h3 id="六十三:Unity3D的协程和C-线程之间的区别是什么?"><a href="#六十三:Unity3D的协程和C-线程之间的区别是什么?" class="headerlink" title="六十三:Unity3D的协程和C#线程之间的区别是什么?"></a>六十三:Unity3D的协程和C#线程之间的区别是什么?</h3><p>多线程程序同时运行多个线程 ,而在任一指定时刻只有一个协程在运行,并且这个正在运行的协同程序只在必要时才被挂起。除主线程之外的线程无法访问Unity3D的对象、组件、方法。<br>Unity3d没有多线程的概念,不过unity也给我们提供了StartCoroutine(协同程序)和LoadLevelAsync(异步加载关卡)后台加载场景的方法。 StartCoroutine为什么叫协同程序呢,所谓协同,就是当你在StartCoroutine的函数体里处理一段代码时,利用yield语句等待执行结果,这期间不影响主程序的继续执行,可以协同工作。</p><h3 id="六十四:矩阵相乘的意义及注意点"><a href="#六十四:矩阵相乘的意义及注意点" class="headerlink" title="六十四:矩阵相乘的意义及注意点"></a>六十四:矩阵相乘的意义及注意点</h3><p>用于表示线性变换:旋转、缩放、投影、平移、仿射<br>注意矩阵的蠕变:误差的积累</p><h3 id="六十五:为什么dynamic-font在unicode环境下优于static-font"><a href="#六十五:为什么dynamic-font在unicode环境下优于static-font" class="headerlink" title="六十五:为什么dynamic font在unicode环境下优于static font"></a>六十五:为什么dynamic font在unicode环境下优于static font</h3><p>Unicode是国际组织制定的可以容纳世界上所有文字和符号的字符编码方案。<br>使用动态字体时,Unity将不会预先生成一个与所有字体的字符纹理。当需要支持亚洲语言或者较大的字体的时候,若使用正常纹理,则字体的纹理将非常大。</p><h3 id="六十六:当一个细小的高速物体撞向另一个较大的物体时,会出现什么情况?如何避免?"><a href="#六十六:当一个细小的高速物体撞向另一个较大的物体时,会出现什么情况?如何避免?" class="headerlink" title="六十六:当一个细小的高速物体撞向另一个较大的物体时,会出现什么情况?如何避免?"></a>六十六:当一个细小的高速物体撞向另一个较大的物体时,会出现什么情况?如何避免?</h3><p>穿透(碰撞检测失败)</p><h3 id="六十七:请简述OnBecameVisible及OnBecameInvisible的发生时机,以及这一对回调函数的意义?"><a href="#六十七:请简述OnBecameVisible及OnBecameInvisible的发生时机,以及这一对回调函数的意义?" class="headerlink" title="六十七:请简述OnBecameVisible及OnBecameInvisible的发生时机,以及这一对回调函数的意义?"></a>六十七:请简述OnBecameVisible及OnBecameInvisible的发生时机,以及这一对回调函数的意义?</h3><p>当物体是否可见切换之时。可以用于只需要在物体可见时才进行的计算。</p><h3 id="六十八:什么叫动态合批?跟静态合批有什么区别?"><a href="#六十八:什么叫动态合批?跟静态合批有什么区别?" class="headerlink" title="六十八:什么叫动态合批?跟静态合批有什么区别?"></a>六十八:什么叫动态合批?跟静态合批有什么区别?</h3><p>如果动态物体共用着相同的材质,那么Unity会自动对这些物体进行批处理。动态批处理操作是自动完成的,并不需要你进行额外的操作。<br>区别:动态批处理一切都是自动的,不需要做任何操作,而且物体是可以移动的,但是限制很多。静态批处理:自由度很高,限制很少,缺点可能会占用更多的内存,而且经过静态批处理后的所有物体都不可以再移动了。<br>参考</p><h3 id="六十九:简述StringBuilder和String的区别?"><a href="#六十九:简述StringBuilder和String的区别?" class="headerlink" title="六十九:简述StringBuilder和String的区别?"></a>六十九:简述StringBuilder和String的区别?</h3><ul><li>String是字符串常量。</li><li>StringBuffer是字符串变量 ,线程安全。</li><li>StringBuilder是字符串变量,线程不安全。</li><li>String类型是个不可变的对象,当每次对String进行改变时都需要生成一个新的String对象,然后将指针指向一个新的对象,如果在一个循环里面,不断的改变一个对象,就要不断的生成新的对象,所以效率很低,建议在不断更改String对象的地方不要使用String类型。</li><li>StringBuilder对象在做字符串连接操作时是在原来的字符串上进行修改,改善了性能。这一点我们平时使用中也许都知道,连接操作频繁的时候,使用StringBuilder对象。</li></ul><h3 id="七十:Unity3D-Shader分哪几种,有什么区别?"><a href="#七十:Unity3D-Shader分哪几种,有什么区别?" class="headerlink" title="七十:Unity3D Shader分哪几种,有什么区别?"></a>七十:Unity3D Shader分哪几种,有什么区别?</h3><p>表面着色器的抽象层次比较高,它可以轻松地以简洁方式实现复杂着色。表面着色器可同时在前向渲染及延迟渲染模式下正常工作。<br>顶点片段着色器可以非常灵活地实现需要的效果,但是需要编写更多的代码,并且很难与Unity的渲染管线完美集成。<br>固定功能管线着色器可以作为前两种着色器的备用选择,当硬件无法运行那些酷炫Shader的时,还可以通过固定功能管线着色器来绘制出一些基本的内容。</p><h3 id="七十一:已知strcpy函数的原型是:char-strcpy-char-strDest-const-char-strSrc-1-不调用库函数,实现strcpy函数。2-解释为什么要返回char"><a href="#七十一:已知strcpy函数的原型是:char-strcpy-char-strDest-const-char-strSrc-1-不调用库函数,实现strcpy函数。2-解释为什么要返回char" class="headerlink" title="七十一:已知strcpy函数的原型是:char * strcpy(char * strDest,const char * strSrc); 1.不调用库函数,实现strcpy函数。2.解释为什么要返回char *"></a>七十一:已知strcpy函数的原型是:char * strcpy(char * strDest,const char * strSrc); 1.不调用库函数,实现strcpy函数。2.解释为什么要返回char *</h3><figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">char</span> * <span class="title">strcpy</span><span class="params">(<span class="keyword">char</span> * strDest,<span class="keyword">const</span> <span class="keyword">char</span> * strSrc)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">if</span> ((strDest==<span class="literal">NULL</span>)||(strSrc==<span class="literal">NULL</span>))</span><br><span class="line"> <span class="keyword">throw</span> <span class="string">"Invalid argument(s)"</span>;</span><br><span class="line"> <span class="keyword">char</span> * strDestCopy=strDest;</span><br><span class="line"> <span class="keyword">while</span> ((*strDest++=*strSrc++)!=<span class="string">'\0'</span>);</span><br><span class="line"> <span class="keyword">return</span> strDestCopy;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="七十二:C-中四种访问修饰符是哪些?各有什么区别?"><a href="#七十二:C-中四种访问修饰符是哪些?各有什么区别?" class="headerlink" title="七十二:C#中四种访问修饰符是哪些?各有什么区别?"></a>七十二:C#中四种访问修饰符是哪些?各有什么区别?</h3><p>1.属性修饰符 2.存取修饰符 3.类修饰符 4.成员修饰符。<br>属性修饰符:</p><ul><li>Serializable:按值将对象封送到远程服务器。</li><li>STATread:是单线程套间的意思,是一种线程模型。</li><li>MATAThread:是多线程套间的意思,也是一种线程模型。<br>存取修饰符:</li><li>public:存取不受限制。</li><li>private:只有包含该成员的类可以存取。</li><li>internal:只有当前工程可以存取。</li><li>protected:只有包含该成员的类以及派生类可以存取。<br>类修饰符:</li><li>abstract:抽象类。指示一个类只能作为其它类的基类。</li><li>sealed:密封类。指示一个类不能被继承。理所当然,密封类不能同时又是抽象类,因为抽象总是希望被继承的。<br>成员修饰符:</li><li>abstract:指示该方法或属性没有实现。</li><li>sealed:密封方法。可以防止在派生类中对该方法的override(重载)。不是类的每个成员方法都可以作为密封方法密封方法,必须对基类的虚方法进行重载,提供具体的实现方法。所以,在方法的声明中,sealed修饰符总是和override修饰符同时使用。</li><li>delegate:委托。用来定义一个函数指针。C#中的事件驱动是基于delegate + event的。</li><li>const:指定该成员的值只读不允许修改。</li><li>event:声明一个事件。</li><li>extern:指示方法在外部实现。</li><li>override:重写。对由基类继承成员的新实现。</li><li>readonly:指示一个域只能在声明时以及相同类的内部被赋值。</li><li>static:指示一个成员属于类型本身,而不是属于特定的对象。即在定义后可不经实例化,就可使用。</li><li>virtual:指示一个方法或存取器的实现可以在继承类中被覆盖。<br>new:在派生类中隐藏指定的基类成员,从而实现重写的功能。 若要隐藏继承类的成员,请使用相同名称在派生类中声明该成员,并用 new 修饰符修饰它。</li></ul><h3 id="七十三:Heap与Stack有何区别?"><a href="#七十三:Heap与Stack有何区别?" class="headerlink" title="七十三:Heap与Stack有何区别?"></a>七十三:Heap与Stack有何区别?</h3><ul><li>1.heap是堆,stack是栈。</li><li>2.stack的空间由操作系统自动分配和释放,heap的空间是手动申请和释放的,heap常用new关键字来分配。</li><li>3.stack空间有限,heap的空间是很大的自由区。</li></ul><h3 id="七十四:值类型和引用类型有何区别?"><a href="#七十四:值类型和引用类型有何区别?" class="headerlink" title="七十四:值类型和引用类型有何区别?"></a>七十四:值类型和引用类型有何区别?</h3><p>1.值类型的数据存储在内存的栈中;引用类型的数据存储在内存的堆中,而内存单元中只存放堆中对象的地址。<br>2.值类型存取速度快,引用类型存取速度慢。<br>3.值类型表示实际数据,引用类型表示指向存储在内存堆中的数据的指针或引用<br>4.值类型继承自System.ValueType,引用类型继承自System.Object<br>5.栈的内存分配是自动释放;而堆在.NET中会有GC来释放<br>6.值类型的变量直接存放实际的数据,而引用类型的变量存放的则是数据的地址,即对象的引用。</p><h3 id="七十五:协同程序的执行代码是什么?有何用处,有何缺点?"><a href="#七十五:协同程序的执行代码是什么?有何用处,有何缺点?" class="headerlink" title="七十五:协同程序的执行代码是什么?有何用处,有何缺点?"></a>七十五:协同程序的执行代码是什么?有何用处,有何缺点?</h3><figure class="highlight lua"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Start</span><span class="params">()</span></span> { </span><br><span class="line"> // 协同程序WaitAndPrint在Start函数内执行,可以视同于它与Start函数同步执行.</span><br><span class="line"> StartCoroutine(WaitAndPrint(<span class="number">2.0</span>)); </span><br><span class="line"> <span class="built_in">print</span> (<span class="string">"Before WaitAndPrint Finishes "</span> + Time.<span class="built_in">time</span> );</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">WaitAndPrint</span> <span class="params">(waitTime : float)</span></span> {</span><br><span class="line"> // 暂停执行waitTime秒</span><br><span class="line"> <span class="built_in">yield</span> WaitForSeconds (waitTime);</span><br><span class="line"> <span class="built_in">print</span> (<span class="string">"WaitAndPrint "</span>+ Time.<span class="built_in">time</span> );</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>作用:一个协同程序在执行过程中,可以在任意位置使用yield语句。yield的返回值控制何时恢复协同程序向下执行。协同程序在对象自有帧执行过程中堪称优秀。协同程序在性能上没有更多的开销。<br>缺点:协同程序并非真线程,可能会发生堵塞。</p>]]></content>
<summary type="html"><p>算起来到现在为止面试的大多是游戏服务端或者web前端相关的类容,没有经历过一次正儿八经的Unity面试,通过面试题也可以侧面了解到Unity开发中有哪些常用的需要掌握的知识,因此搬运一篇号称“屎上最全”的博客当作备份。</p></summary>
<category term="game" scheme="https://blog.xiaomo.info/categories/game/"/>
<category term="game" scheme="https://blog.xiaomo.info/tags/game/"/>
</entry>
<entry>
<title>2019年年终总结(作为前端开发这一年)</title>
<link href="https://blog.xiaomo.info/2019/my2019Summary/"/>
<id>https://blog.xiaomo.info/2019/my2019Summary/</id>
<published>2019-12-31T00:00:00.000Z</published>
<updated>2022-08-10T23:53:17.651Z</updated>
<content type="html"><![CDATA[<p>如果说2018年是工作之后的人生转折点的话,那么2019年这一年就是转折之后的习惯过程。从大学远离故乡,到毕业到沿海城市就业,再到去年来到东京,这一个个的决定无不是对自我挑战的过程,这期间伴随着阵痛,同时也伴随着成长。有的人习惯了熟悉的生活非常害怕改变,怕换工作,怕搬家,怕交新朋友。各种各样的改变都会对生活带来冲击,但也带来了新鲜感,还是要看自己怎么样来看待这种改变。</p><a id="more"></a><h3 id="进入华人派遣"><a href="#进入华人派遣" class="headerlink" title="进入华人派遣"></a>进入华人派遣</h3><p>来日本之前也算是搜集了不少的信息,经过了各种考量觉得进华人派遣公司是不是最优但当前的唯一选择。一般来说到日本做IT相关工作,如果没有日本留学经验,都会经历<code>华人派遣公司——>日本大手派遣公司——>个人事业主(可能跳过)——>日本自社开发(在日外企)</code>这个过程,而我正处在这个初级阶段但不想长期处于初级阶段,所以为此也做了一些准备。</p><ul><li>2017年年底通过了Oracle java认证</li><li>2019年7月考取了日语能力测试N2证书</li><li>2019年8月获取日本自动车驾照</li><li>2019年11月参加了中国国家软件考试(软件设计师),但不幸没有通过,准备报2020年5月的软考</li><li>2019年12月参加了日语能力测试N1测试(2020年3月公布成绩)</li><li>学习Angular/Vue前端开发框架(Javascript/Typescript)</li><li>学习Unity3D</li></ul><p>但是,如果按照这个计划的话在日本转职至少要2次。日本是一个对跳槽容忍度比较低的国家,一份工作干不到2年就辞职会觉得相当不稳定,对找下份工作和申请永住签证都有一定的影响,所以之后可能会有一些不同的打算。</p><p>接下来的计划</p><ul><li>打算2020年参加日本IT考试 基本情報技術者試験</li><li>打算参加2020年5月的软考</li><li>学习Python以便于开发脚本工具/效率工具/游戏逻辑/AI/爬虫等</li><li>带老婆孩子在日本定居</li><li>申请高级人才签证之后转永住者签证(中国软考和日本IT考试获取证书有助于申请高级人才)</li><li>学习英语之后参加托业英语考试进入英语系外企游戏公司</li></ul><h3 id="面试游戏公司"><a href="#面试游戏公司" class="headerlink" title="面试游戏公司"></a>面试游戏公司</h3><p>在做了11个月的前端开发之后(首次从事前端商业项目开发,但受到领导好评),所在的项目也接近尾声。在寻找下个项目时优先挑选游戏行业的项目,因为在自己离开游戏行业后发现自己不是不喜欢游戏行业,反而是特别喜欢游戏行业所以越发的厌恶中国的圈钱式垃圾游戏。我从来不觉得贪玩蓝月是一个好玩的游戏,只是被资本运作圈钱的一个工具。从小接触到红白机和各种游戏充满了回忆,而这些游戏基本上都是日本开发出来的。所以我离开了待了3年的游戏公司,毅然来到日本。当时也的确厌倦了给垃圾页游换皮的工作内容,再加上自己工作这么多年自己无法独立开发出一个完整的应用(只会后端但前端技术不是很擅长),为了锻炼自己所以找了一个前端开发的工作。到现在来看,我丝毫不觉得这个决定有任何坏的影响。在这近一年的时间中学到了非常非常多前端开发的知识,弥补了自己不会前端的遗憾,也增强了自己的信心。接下来如果运气好能够遇到做游戏客户端开发的工作的话,那就太好了。</p><h3 id="面试日本自社公司"><a href="#面试日本自社公司" class="headerlink" title="面试日本自社公司"></a>面试日本自社公司</h3><p>申请了<code>geekly</code>,<code>doda</code>,<code>linkedbrain</code>的面谈,希望匹配到合适的自社游戏公司。听在日本做HR的朋友说,日本的游戏行业待遇相当不尽人意,他的一个应届毕业的朋友在做《只狼》的超牛逼的公司年收才给300万+,这着实有点打击了我,不过先面试看看情况吧。</p><h3 id="不同契约的区别"><a href="#不同契约的区别" class="headerlink" title="不同契约的区别"></a>不同契约的区别</h3><p>刚到日本的时候因为没有什么底气为了保险起见签了正社员的契约,它的区别就是在派遣公司如果一个项目结束之后第二个项目没接上(也就是说没有找到新项目)工资也照常发,但如果是契约社员的话待机的话每个月大概只有12万的待机费。个人当时感觉有点慌,所以选择了正社。但是之后在日本熟悉了,也拿到了N2的证书就转了契约社员。有人肯定会好奇我的动机,因为正社员公司会交保险和年金,各负担一半。但是我在的公司没有交年金,保险也是自己负担,契约的话到手的工资会高一些,所以就转成了契约。后来听前辈说契约社员的话签证更新的话比较难拿到3年以上的,这个我也找别的朋友确认过,没有统一的说话,但是长期待在华人派遣公司不是办法。至少也要向日本大手派遣公司努力,签个正社待遇也是不错的。</p>]]></content>
<summary type="html"><p>如果说2018年是工作之后的人生转折点的话,那么2019年这一年就是转折之后的习惯过程。从大学远离故乡,到毕业到沿海城市就业,再到去年来到东京,这一个个的决定无不是对自我挑战的过程,这期间伴随着阵痛,同时也伴随着成长。有的人习惯了熟悉的生活非常害怕改变,怕换工作,怕搬家,怕交新朋友。各种各样的改变都会对生活带来冲击,但也带来了新鲜感,还是要看自己怎么样来看待这种改变。</p></summary>
<category term="summary" scheme="https://blog.xiaomo.info/tags/summary/"/>
<category term="japan" scheme="https://blog.xiaomo.info/tags/japan/"/>
</entry>
</feed>