-
Notifications
You must be signed in to change notification settings - Fork 0
/
search.xml
818 lines (387 loc) · 248 KB
/
search.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>MQ思考总结</title>
<link href="/2022/08/24/MQ%E6%80%9D%E8%80%83%E6%80%BB%E7%BB%93/"/>
<url>/2022/08/24/MQ%E6%80%9D%E8%80%83%E6%80%BB%E7%BB%93/</url>
<content type="html"><![CDATA[<h2 id="数据从一端到另一端,中途的所有链路和节点都可能丢失数据"><a href="#数据从一端到另一端,中途的所有链路和节点都可能丢失数据" class="headerlink" title="数据从一端到另一端,中途的所有链路和节点都可能丢失数据"></a>数据从一端到另一端,中途的所有链路和节点都可能丢失数据</h2><p>假设两端需要传输数据,则数据可能会在中途丢失<br>P -> C</p><p>若增加一台broker来中转数据,则broker前后的链路传输途中可能丢失数据,broker自身也可能丢失数据<br>P -> B -> C</p><h2 id="解决数据丢失问题"><a href="#解决数据丢失问题" class="headerlink" title="解决数据丢失问题"></a>解决数据丢失问题</h2><p>1、防止P -> B丢失,使用同步机制,或者异步+回调+本地消息表<br>2、防止B自身丢失,可使用定时持久化消息,使用分布式节点+冗余数据<br>3、防止B -> C丢失,<del>可以使用同步机制</del>,或者异步+回调+本地消息表</p><p>其中,3的同步机制,即C每次Poll后,直到业务处理完,再commit消息。连接需要等待漫长的业务处理完后才能释放,明显不合理,很少使用<br>而3的异步+回调+本地消息表,则是C每次Poll完后直接释放连接,等待业务完成消息后,再回调broker commit消息,是大多MQ默认的手段</p><p>注意:消费端收到消息,要保证走完全流程处理才Commit消息,且只要走完业务全流程,哪怕处理失败,也应该commit。只有在处理消息过程中宕机这种极端情况,才需要重新消费</p><h2 id="提升数据传输过程吞吐量的思路,是减少端到端的网络通信次数"><a href="#提升数据传输过程吞吐量的思路,是减少端到端的网络通信次数" class="headerlink" title="提升数据传输过程吞吐量的思路,是减少端到端的网络通信次数"></a>提升数据传输过程吞吐量的思路,是减少端到端的网络通信次数</h2><p>1、P -> B,可以在P设置缓冲区,定时或定量 批量传输消息,从“N条消息N次连接”优化为“N条消息1次连接”(同步机制)<br>2、B -> C,C端批量Poll消息,定时 批量提交Commit请求,从“N条消息N次连接”优化为“N条消息2次连接”(异步+回调)</p><h2 id="MQ产品的特殊情况"><a href="#MQ产品的特殊情况" class="headerlink" title="MQ产品的特殊情况"></a>MQ产品的特殊情况</h2><p>Kafka,使用Offset作为B -> C端的同步机制,只有当某个offset之前的所有消息都被消费了,这个offset才能更新到B端。这就导致了如果一个早期的消息一直得不到commit,offset无法被更新,则早期的消息会不断的被重发。这个问题可以通过阈值到期加入死信队列来解决</p><p>RocketMQ,为什么不使用Zookeeper作为注册中心,<strong>待调研</strong>,参考:<br> - <a href="https://zhuanlan.zhihu.com/p/368773517">面试题系列:MQ 夺命连环11问 - 知乎 (zhihu.com)</a></p><h2 id="容错性优化"><a href="#容错性优化" class="headerlink" title="容错性优化"></a>容错性优化</h2><ol><li>B端不可靠的场景,在P端增加一个自消费的topic和机器,增加容错性</li><li>C端不可靠场景,增加一个临时Consumer端,吸收积压消息,转发到一个新的topic机器中做缓冲</li></ol><p>总结:针对节点不可靠场景,可以临时增加(或预设)机器模拟该节点,来消化和缓冲临时数据</p><h2 id="分布式节点-冗余数据的技术关键"><a href="#分布式节点-冗余数据的技术关键" class="headerlink" title="分布式节点+冗余数据的技术关键"></a>分布式节点+冗余数据的技术关键</h2><ol><li>需要多台机器能够自治<ol><li>心跳机制,保证节点健康监测的基础</li><li>选举机制,保证节点健康监测的可靠性</li></ol></li><li>需要保证多台机器的数据一致性<ol><li> raft协议</li></ol></li><li>提供多台Broker的发现机制:使用注册中心处理</li></ol>]]></content>
<tags>
<tag> MQ </tag>
</tags>
</entry>
<entry>
<title>java请求https证书问题,`unable to find valid certification path to requested target`</title>
<link href="/2022/08/24/java%E8%AF%B7%E6%B1%82https%E8%AF%81%E4%B9%A6%E9%97%AE%E9%A2%98/"/>
<url>/2022/08/24/java%E8%AF%B7%E6%B1%82https%E8%AF%81%E4%B9%A6%E9%97%AE%E9%A2%98/</url>
<content type="html"><![CDATA[<h1 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h1><p>使用okHttp请求某https服务(<a href="https://api.github.com/">https://api.github.com</a>),然而却报证书相关的错误:<code>unable to find valid certification path to requested target</code>,问题是如果直接用浏览器访问该地址,却是安全的。</p><h1 id="疑惑"><a href="#疑惑" class="headerlink" title="疑惑"></a>疑惑</h1><p>在未跟进源码之前,个人理解JAVA默认使用的是系统自带的CA证书,那么浏览器访问该HTTPS没问题,说明该服务是能够被系统内置的CA证书认证的,按理说JAVA访问也应该没问题才对。重点就在于为什么JAVA的和浏览器的表现不一致。</p><p>搜索引擎出来的全部都是教你如何绕过安全校验、信任所有HTTPS网站,这固然能临时解决问题,但根本就是因噎废食,这种解决是建立在牺牲安全性之上的!</p><h1 id="原因分析"><a href="#原因分析" class="headerlink" title="原因分析"></a>原因分析</h1><p>断点跟进源码,发现okhttp底层使用的是JDK原生工具库<code>sun.security.ssl</code>。再跟了几层,发现ssl库使用的信任证书库是指定路径下的<code>..\jdk1.8.0_292\jre\lib\security\cacerts</code>文件。</p><blockquote><p>读取配置代码:TrustStoreManager.TrustStoreDescriptor.createInstance()<br>信任库优先读取顺序:javax.net.ssl.trustStore(环境变量) > cacerts文件(默认)</p></blockquote><p>通过<code>keytool</code>命令打印jdk的信任证书库。</p><pre><code class="hljs bash">keytool -list -keystore cacerts -rfc</code></pre><p>再在浏览器下载验证通过的根证书,存为base64格式。</p><p>对比二者,发现jdk的信任证书库并不包含该根证书。可见jdk的信任证书库与系统证书库不一致,导致了上文提到的表现上的差异。</p><h1 id="解决"><a href="#解决" class="headerlink" title="解决"></a>解决</h1><p>cd到<code>\jre\lib\security</code>目录下,然后导入缺少的那个根证书</p><pre><code class="hljs bash"><span class="hljs-comment"># 注意查看的时候不要求口令,但是修改的时候是要求口令的,而默认的口令就是 changeit</span>keytool -importcert -file target.cer -keystore cacerts -storepass changeit -storetype jks</code></pre><p>然后再重新跑一遍程序,很好,能正常请求https获取响应了</p><p>相关关键词:</p><ul><li>unable to find valid certification path to requested target</li></ul>]]></content>
<tags>
<tag> https </tag>
<tag> 证书 </tag>
</tags>
</entry>
<entry>
<title>maven repository mirror优先级</title>
<link href="/2022/08/24/maven%20repository%20mirror%E4%BC%98%E5%85%88%E7%BA%A7/"/>
<url>/2022/08/24/maven%20repository%20mirror%E4%BC%98%E5%85%88%E7%BA%A7/</url>
<content type="html"><![CDATA[<ul><li>依赖的下载只会从repository中挑选仓库下载</li><li> 挑选仓库的顺序是从上到下</li><li> 如果下载依赖过程中出现问题(比如https证书校验失败),则会报错最上面第一个仓库找不到依赖</li><li> mirror只会改变仓库地址的映射,不会影响加载顺序,通过 mirrorOf 与 repository的Id 绑定</li><li> 中心仓在超级pom中有配置,默认id=central,由于加载太慢,可以覆写中心仓的地址</li><li> 中心仓放在最后兜底查询</li><li> mirror的mirrorOf * 配置,可以充当repository,作为终极兜底</li><li> 实践理解: mvn clean compile -X</li></ul>]]></content>
<tags>
<tag> maven </tag>
</tags>
</entry>
<entry>
<title>checked exception VS unchecked exception</title>
<link href="/2022/08/24/checked%20exception%20VS%20unchecked%20exception/"/>
<url>/2022/08/24/checked%20exception%20VS%20unchecked%20exception/</url>
<content type="html"><![CDATA[<p>checked exception 即你在方法里某个逻辑抛出了这个异常,且在方法签名显式声明会抛出这个异常。<br>unchecked exception 即你在方法里某个逻辑抛出了这个异常,但是不会在方法签名显式声明抛出这个异常。</p><p>一个是抛了且要求调用层处理,一个是只抛不强制要求调用层处理。</p><p>其中一种声音是,是否声明checked异常取决于你判断caller是否关心这个异常。</p><blockquote><p>This is the philosophy used by many frameworks. Spring and hibernate, in particularly, come to mind - they convert known checked exception to unchecked exception precisely because checked exceptions are overused in Java. One example that I can think of is the JSONException from json.org, which is a checked exception and is mostly annoying - it should be unchecked, but the developer simply haven’t thought it through.</p></blockquote><p>异常可以从以下维度去理解,1、是否可预见的,2、是否可预防的,3、是否可恢复的(通过兜底、降级等手段)。针对以上维度,我们可以整理出如下的对策</p><ul><li>不可预见的:我们没有任何手段去阻止,通常只能尝试(try)去监测(catch),然后打印下日志,等待开发来修改BUG代码</li><li>可预见的<ul><li>可预防的:直接就在代码里预判且处理妥当了,比如常见的<code>StringUtils.isEmpty()</code></li><li>不可预防的:<ul><li>可恢复的:不可预防可以理解为,我们知道这事可能发生,但是我们没法阻止异常的发生,比如在”检查文件是否存在”和”读取文件内容”两个操作之间,文件被人删除了。这种异常我们无法通过写代码来阻止另外一个用户把文件删除,但是也不能置之不理,所以此时就要在方法签名中明确声明一个异常,调用层被迫要对该异常进行处理,可以是兜底、降级,当然调用层可以继续往外抛。</li><li>不可恢复的:如果预见一个不可预防的异常,且无法通过兜底、降级的手段解决,则提前声明checked exception也没意义,此时可采取的手段就跟“不可预见”异常一样了。</li></ul></li></ul></li></ul><p>以一个表格来进行小结:</p><table><thead><tr><th>可预见</th><th>可预防</th><th>可恢复</th><th>所属异常类型</th><th>可采取手段</th></tr></thead><tbody><tr><td>×</td><td>-</td><td>-</td><td>uncheck exception</td><td>try catch</td></tr><tr><td>√</td><td>√</td><td>-</td><td>not exception</td><td>代码规避</td></tr><tr><td>√</td><td>×</td><td>×</td><td>uncheck exception</td><td>try catch</td></tr><tr><td>√</td><td>×</td><td>√</td><td>checked exception</td><td>方法签名声明异常+调用方try catch</td></tr></tbody></table><blockquote><p>Compiler will check that we have done one of the two things (catch, or declare). So these are called Checked exceptions.</p></blockquote><blockquote><p>当在方法签名显式声明了throws Exception,编译器会检查(check)调用者需要执行这两个动作之一:1、进行catch 2、“继续声明throws Exception 往外抛”,否则过不了编译,所以这种异常叫做checked exception</p></blockquote><p>Checked Exceptions 应该用于可以合理恢复的可预测但不可预防的错误。</p><p>就像工作一样,处理Exception,理清责任很重要,如果一个checked exception由当前方法处理不当导致,则当前方法需要catch并做兜底处理,至少得打日志;反之如果判断它是由上层参数导致,则把异常外抛给调用层,而不是自己揽过来处理。</p><p>参考文章:</p><ul><li><a href="https://webcache.googleusercontent.com/search?q=cache:zKMFajzvoiQJ:https://www.geeksforgeeks.org/checked-vs-unchecked-exceptions-in-java/+&cd=4&hl=zh-CN&ct=clnk">Checked vs Unchecked Exceptions in Java - GeeksforGeeks (googleusercontent.com)</a></li><li><a href="https://stackoverflow.com/questions/27578/when-to-choose-checked-and-unchecked-exceptions">java - When to choose checked and unchecked exceptions - Stack Overflow</a></li></ul>]]></content>
<tags>
<tag> java </tag>
</tags>
</entry>
<entry>
<title>股票开户学习笔记</title>
<link href="/2022/05/15/%E8%82%A1%E7%A5%A8%E5%BC%80%E6%88%B7%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
<url>/2022/05/15/%E8%82%A1%E7%A5%A8%E5%BC%80%E6%88%B7%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/</url>
<content type="html"><![CDATA[<ol><li>本地有营业部很重要</li><li>佣金分为全佣和净佣+规费(常规费用?印花税+过户费)</li><li>大部分在线开户佣金都为万2.5~万3,费率高</li><li>建议找对应券商经理人开户,可以谈低佣金,记得只谈全佣</li><li>国内开户建议搜索202x年证券公司排名,找排前的大公司准没错,看中之后先体验下对方的app是否顺手好用</li><li>国外(港美股)开户建议富途证券,因为是腾讯系,总部在深圳网络会好点</li><li>港美股交易一般佣金较高,一单至少得有15R以上</li><li>港美股一些交易所如果没相应资质证,会接入第三方在线国际券商,来代理交易,相当于被薅多一层羊毛,最好不要选这种交易所</li><li>同花顺只是一个第三方交易平台,作为中介接入多个券商平台,实际上是后台接入真正开户的平台来进行交易的。实际使用界面也就那样,盯盘体验还不如支付宝股票。而且作为一个中介,开户佣金都是收取最高的点数万2.5~3</li><li>建议“支付宝股票”用于盯盘,搭配“开户平台自家APP”交易</li><li>综上,炒港美股实在不太划算,首先要开户,其次佣金费率高。此时炒币成了跨国投资的一个重要的手段,它不需要开户,而且走势和美股挂钩(高度相关)。</li></ol>]]></content>
<tags>
<tag> 投资 </tag>
</tags>
</entry>
<entry>
<title>C++指针理解</title>
<link href="/2022/04/28/C++%E6%8C%87%E9%92%88%E7%90%86%E8%A7%A3/"/>
<url>/2022/04/28/C++%E6%8C%87%E9%92%88%E7%90%86%E8%A7%A3/</url>
<content type="html"><![CDATA[<h2 id="指针的声明"><a href="#指针的声明" class="headerlink" title="指针的声明"></a>指针的声明</h2><pre><code class="hljs c++"><span class="hljs-keyword">int</span> j = <span class="hljs-number">49</span>;<span class="hljs-keyword">int</span> *i = &j;# 更好的声明方式<span class="hljs-keyword">int</span>* i = &j;</code></pre><p>只要声明的时候,加了<code>*</code> 号,这个变量就是<code>指针变量</code>,比如这个 i 就是一个<code>指针类型的变量</code>,而不是一个<code>int类型的变量</code></p><h3 id="那这个int在这里的作用是什么?"><a href="#那这个int在这里的作用是什么?" class="headerlink" title="那这个int在这里的作用是什么?"></a>那这个int在这里的作用是什么?</h3><p>先说结论:这个int,表明了从指针指向的头地址开始,需要往后取多少位长度的数据。</p><p>我们首先要知道,各个基础类型,都有自己的长度,如int型是32位,long型是64位。而指针类型也有自己的长度,它的值存放的是一个内存地址,即寻址长度。在32位系统中,指针类型的长度是32;在64位系统中,指针类型的长度为64。</p><p>由于指针存放的是一个内存地址,它指向的是内存中的某个比特位,即所谓的头地址。如果单看头地址的这个比特位,并没有什么实际意义,因为它只能包含0/1两种信息。而<code>int* i</code>这样声明指针类型的话,实际是在指导机器不止要看头地址,而且还要往后取32位,才能完整的把int类型的数据取出来。总结来说,单纯的指针类型变量只包含了某个内存地址(头地址)指向的那一个bit的信息,并无实际意义,只有带上具体的类型,指针类型变量才具有意义。</p><h2 id="指针的使用"><a href="#指针的使用" class="headerlink" title="指针的使用"></a>指针的使用</h2><p>假如当前声明了一个指针变量<code>int* i</code>,一个普通变量<code>int j</code></p><ul><li><code>i</code> 表示内存地址,类型为<code>指针类型</code></li><li><code>*i</code>表示真正的数据,类型为具体声明的类型,如<code>int</code></li><li><code>&j</code>表示变量 j 的内存首地址,类型为<code>指针类型</code></li><li><code>&</code>也可以用在指针类型上,如:<code>&i</code>得到的是<code>指针类型的</code> <code>变量i</code>的内存首地址</li></ul><p>示例:</p><ul><li><code>cout << i</code> 可以的得到<code>指针变量</code>对应的值(即某内存地址)</li><li><code>cout << *i</code> 可以可以得到真正的数据,通过 <code>头地址+类型长度</code> 获取</li></ul><h2 id="指针类型变量存在栈中还是堆中"><a href="#指针类型变量存在栈中还是堆中" class="headerlink" title="指针类型变量存在栈中还是堆中"></a>指针类型变量存在栈中还是堆中</h2><p>指针类型的变量,以<code>int* i</code>为例,它可能存在于方法栈的内存空间中,也可能存在堆空间中(通过malloc函数)。</p><h3 id="为什么要有堆内存?"><a href="#为什么要有堆内存?" class="headerlink" title="为什么要有堆内存?"></a>为什么要有堆内存?</h3><p>局部变量在方法栈中分配空间,当方法执行结束、方法栈弹出之后它也会跟着被清除。此时哪怕把局部变量的指针返回出去,外部拿到的指针对应的值也是空的。只有在堆中分配的内存空间,才不会随着方法结束出栈而被清理,此时对应的指针在外部就依然能够正常获取数据。</p><p>堆内存的局限在于需要开发者手动回收,不然程序长时间运行可能会导致内存溢出。当然很多语言自带的GC也是一种优雅手段。</p><pre><code class="hljs c++"><span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string"><iostream></span></span><span class="hljs-function"><span class="hljs-keyword">int</span>* <span class="hljs-title">b</span><span class="hljs-params">()</span></span>;<span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span>{ <span class="hljs-keyword">int</span> j = <span class="hljs-number">43</span>; <span class="hljs-keyword">int</span>* i; std::cout << <span class="hljs-string">"hello world"</span> << std::endl; i = <span class="hljs-built_in">b</span>(); std::cout << i<< std::endl; std::cout << *i<< std::endl; <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> z = <span class="hljs-number">0</span>; z < <span class="hljs-number">100</span>; z++) { <span class="hljs-comment">/* code */</span> } std::cout << i<< std::endl;}<span class="hljs-function"><span class="hljs-keyword">int</span>* <span class="hljs-title">b</span><span class="hljs-params">()</span></span>{ <span class="hljs-keyword">int</span> j = <span class="hljs-number">49</span>; <span class="hljs-comment">// int* i = &j;</span> <span class="hljs-keyword">int</span>* i = (<span class="hljs-keyword">int</span>*)<span class="hljs-built_in">malloc</span>(<span class="hljs-number">1</span>); *i = <span class="hljs-number">256</span>; std::cout << <span class="hljs-string">"in b:"</span> <<*i<< std::endl; <span class="hljs-keyword">return</span> i;}</code></pre>]]></content>
<categories>
<category> c++ </category>
</categories>
<tags>
<tag> c++ </tag>
</tags>
</entry>
<entry>
<title>zookeeper / etcd 的区别</title>
<link href="/2022/04/05/zookeeper%20,%20etcd%20%E7%9A%84%E5%8C%BA%E5%88%AB/"/>
<url>/2022/04/05/zookeeper%20,%20etcd%20%E7%9A%84%E5%8C%BA%E5%88%AB/</url>
<content type="html"><![CDATA[<h1 id="zookeeper-etcd-的区别"><a href="#zookeeper-etcd-的区别" class="headerlink" title="zookeeper / etcd 的区别"></a>zookeeper / etcd 的区别</h1><p>etcd,分布式一致性键值存储引擎,专门为分布式系统提供服务。它基于Raft共识算法,使用GOlang开发(这点很重要),由<a href="https://zh.wikipedia.org/wiki/CoreOS" title="CoreOS">CoreOS</a>开发。在github release上可以跟踪到最早发布在2013年8月12号,最近更新是在3天前,可以说是相当活跃了。</p><p>zookeeper,官方介绍是一个高可靠分布式<strong>协调</strong>服务。它提供配置维护、命名、分布式同步、分组服务(group services)等等功能。它起源于雅虎研究院的一个研究小组,后来成为hadoop的子项目,主要<strong>为Hadoop生态系统中一些列组件提供统一的分布式协作服务</strong>,再后来在(Hadoop 1.0时代)<strong>2011年1月</strong>, ZooKeeper 脱离Hadoop,成为Apache顶级项目,并成为开源项目,一直发展至今。它在官网上可找到最早的发布版本是2008年10月27好,github上最近的提交是4天前,且仅有少量提交,活跃对相对etcd较低。它基于基于 ZAB (Zookeeper Atomic Broadcast)协议实现,由java语言编写。</p><table><thead><tr><th>-</th><th>语言</th><th>能力</th><th>社区活跃度</th><th>其他</th></tr></thead><tbody><tr><td>zookeeper</td><td>java</td><td>配置维护、命名、分布式同步、分组服务</td><td>良</td><td>老牌分布式同步工具,kafka、dubbo、hadoop等都在用</td></tr><tr><td>etcd</td><td>GO</td><td>分布式键值存储、共享配置、服务发现</td><td>优</td><td>因支撑k8s而火</td></tr></tbody></table><h2 id="Raft-consensus-algorithm"><a href="#Raft-consensus-algorithm" class="headerlink" title="Raft consensus algorithm"></a>Raft consensus algorithm</h2><p>参考大神的动画: <a href="http://thesecretlivesofdata.com/raft/">http://thesecretlivesofdata.com/raft/</a></p><p>raft其实是一个协议(protocol)。在这个协议当中,参与通信的结点有三种状态,leader、follower、candidate。在协议当中,有两种超时类型(timeout),election timeout,heartbeat timeout。leader 以 heartbeat timeout 为周期同步信息,follower 收到消息立即响应。同时存在多个节点进行leader选举时,先得到半数响应的节点成为leader。leader通过两阶段(预设、提交)同步数据,并且会记录log。</p><h2 id="ZAB(Zookeeper-Atomic-Broadcast)协议"><a href="#ZAB(Zookeeper-Atomic-Broadcast)协议" class="headerlink" title="ZAB(Zookeeper Atomic Broadcast)协议"></a>ZAB(Zookeeper Atomic Broadcast)协议</h2><p>选举通常是选zxid,sid(myid)最大的。新选举完毕会产生新的纪元,epoch,老leader恢复后其他节点也不听他管,毕竟前朝的剑不能斩本朝的官。</p><hr><p>Zookeeper的ZAB,Viewstamped Replication(VR),raft,multi-paxos,这些都可以被称之为Leader-based一致性协议。总的来说raft相对来说比较亲民好理解。</p><p>参考文章:</p><ul><li><a href="https://blog.csdn.net/weixin_38256474/article/details/90636262">[zookeeper] 00 - 初识:由来、版本、架构_神是念着倒-CSDN博客_zookeeper发展历史</a></li><li><a href="https://blog.csdn.net/qq_22115231/article/details/80784535">Zookeeper理解_老史的足迹-CSDN博客</a></li><li><a href="https://www.zhihu.com/question/36648084">raft算法与paxos算法相比有什么优势,使用场景有什么差异? - 知乎 (zhihu.com)</a></li><li><a href="https://dzone.com/articles/apache-zookeeper-vs-etcd3">https://dzone.com/articles/apache-zookeeper-vs-etcd3</a></li></ul>]]></content>
<categories>
<category> 工具调研 </category>
</categories>
<tags>
<tag> zookeeper </tag>
<tag> etcd </tag>
</tags>
</entry>
<entry>
<title>python 实现图片汉字识别</title>
<link href="/2022/04/05/python%20%E5%AE%9E%E7%8E%B0%E5%9B%BE%E7%89%87%E6%B1%89%E5%AD%97%E8%AF%86%E5%88%AB/"/>
<url>/2022/04/05/python%20%E5%AE%9E%E7%8E%B0%E5%9B%BE%E7%89%87%E6%B1%89%E5%AD%97%E8%AF%86%E5%88%AB/</url>
<content type="html"><![CDATA[<h1 id="核心库使用"><a href="#核心库使用" class="headerlink" title="核心库使用"></a>核心库使用</h1><p>相关库:tesseract-ocr<br><strong><a href="https://github.com/tesseract-ocr">https://github.com/tesseract-ocr/</a></strong></p><p>核心库软件:tesseract<br>语言模块:tessdata,其中汉字的是 <code>chi_sim</code></p><p>步骤:</p><ol><li>下载安装核心库,自己编译太麻烦了,直接下载安装包</li><li>下载汉字模块:<code>chi_sim.traineddata</code>、<code>chi_sim_vert.traineddata</code><ol><li>放到 tresseract根目录/tessdata/下</li></ol></li><li>命令行测试:tesseract -v</li><li>图片识别:<code>tesseract 输入文件 输出路径 -l 解析使用到的语言模块</code><ol><li>例:<code>tesseract E://figures/other/timg.jpg E://figures/other/timg.txt -l chi_sim</code></li></ol></li></ol><p>核心库已经能正常使用,接下来安装python相关库。</p><h1 id="安装python-依赖"><a href="#安装python-依赖" class="headerlink" title="安装python 依赖"></a>安装python 依赖</h1><ol><li>安装 tesseract api:<code>pip install pytesseract</code></li><li>安装图片处理模块:<code>pip install Pillow</code></li><li>测试实践:<pre><code class="hljs python"><span class="hljs-keyword">import</span> pytesseract<span class="hljs-keyword">from</span> PIL <span class="hljs-keyword">import</span> Imagepytesseract.pytesseract.tesseract_cmd = <span class="hljs-string">'C://Program Files (x86)/Tesseract-OCR/tesseract.exe'</span>text = pytesseract.image_to_string(Image.<span class="hljs-built_in">open</span>(<span class="hljs-string">'E://figures/other/poems.jpg'</span>),lang=<span class="hljs-string">'chi_sim'</span>)<span class="hljs-built_in">print</span>(text)</code></pre></li></ol><h1 id="提升识别度(实践)"><a href="#提升识别度(实践)" class="headerlink" title="提升识别度(实践)"></a>提升识别度(实践)</h1><p>对于提升识别度,官方有一个较全面的指导,点击<a href="https://github.com/tesseract-ocr/tessdoc/blob/main/ImproveQuality.md">链接</a>查看<br>经实践,想要提升识别度,提升图片的分辨率是最简单直接有效的方式。建议图片的分辨率转换到300以上,再走tesseract进行处理。</p><p>参考:</p><ul><li><a href="https://blog.csdn.net/T_maker/article/details/82622447">python 5行代码实现图片中文字识别_T_maker的博客-CSDN博客_文字识别python代码实现</a></li><li><a href="https://blog.51cto.com/u_12139363/3025427">python ocr图片中汉字识别【图文】_gisoracleplus_51CTO博客</a></li></ul><p>待参考:</p><ul><li><a href="http://3ms.huawei.com/km/blogs/details/11393875">python——识别图片中的文字 - 王耀的博客 (huawei.com)</a></li><li><a href="http://3ms.huawei.com/km/blogs/details/9519567">通过Tesseract OCR识别扫描件PDF - 高文光的博客 (huawei.com)</a></li></ul>]]></content>
<tags>
<tag> python </tag>
<tag> ocr </tag>
</tags>
</entry>
<entry>
<title>apache common-lang3 DateUtils使用</title>
<link href="/2022/04/05/apache%20common-lang3%20DateUtils%E4%BD%BF%E7%94%A8/"/>
<url>/2022/04/05/apache%20common-lang3%20DateUtils%E4%BD%BF%E7%94%A8/</url>
<content type="html"><![CDATA[<ul><li>isSameDay</li><li>parseDate:传入多个模板,直到匹配其中一个就可以,若全不匹配则抛出异常</li><li>addXXX</li><li>setXXX</li><li>toCalendar(date)</li><li>round:其注释描述的<a href="https://zh.wikipedia.org/wiki/%E5%A4%8F%E6%97%B6%E5%88%B6">夏时制</a>(daylight time),只针对特定timezone的国家,对我们没影响</li><li>truncate:裁剪尾部</li><li>ceiling:跟truncate一样,但是会向上对齐</li><li>getFragmentInXXX:裁剪头部,跟truncate相反</li><li>truncatedEquals:两日期裁剪后对比,可以达到和isSameDay一样的效果</li><li>truncatedCompareTo:两日期裁剪后对比</li></ul>]]></content>
<categories>
<category> apache生态 </category>
</categories>
<tags>
<tag> apache </tag>
<tag> 使用手册 </tag>
</tags>
</entry>
<entry>
<title>what does "TCP segment of a reassembled PDU" mean?</title>
<link href="/2022/04/05/TCP%20segment%20of%20a%20reassembled%20PDU/"/>
<url>/2022/04/05/TCP%20segment%20of%20a%20reassembled%20PDU/</url>
<content type="html"><![CDATA[<h3 id="what-does-“TCP-segment-of-a-reassembled-PDU”-mean"><a href="#what-does-“TCP-segment-of-a-reassembled-PDU”-mean" class="headerlink" title="what does “TCP segment of a reassembled PDU” mean?"></a>what does “TCP segment of a reassembled PDU” mean?</h3><p>wireshark抓TCP包抓到<code>"TCP segment of a reassembled PDU"</code>,表示该TCP包是被分片了的,被分片的理由是因为发送方的原数据太大,当原数据长度超过了双方协商的<code>MSS</code>值,就会将数据进行TCP分片。</p><pre><code>注意与IP层切片参数`MTU`区分。</code></pre><p>当使用wireshark抓包时,有以下分辨方法</p><ol><li>若多个TCP包作为一个大段数据的分片进行传输,它们的ACK会是一样的,这很好分辨</li><li>或者点开任意分片的数据包查看内容,里面会出现诸如<code>[Reassembled PDU in frame: 9]</code>的信息,表示这些分片将在9号记录中整合。此时跳到9号记录,可以看到<code>[3 Reassembled TCP Segments (2782 bytes): #6(1306), #7(1360), #9(116)]</code>这样的描述,表示这段完整的数据由6、7、9号记录整合而成。</li></ol><p>参考文献:</p><ul><li><a href="https://www.cnblogs.com/tomato0906/articles/3991388.html">TCP segment of a reassembled PDU - 小西红柿 - 博客园 (cnblogs.com)</a></li></ul><h3 id="拓展"><a href="#拓展" class="headerlink" title="拓展"></a>拓展</h3><p>MTU全程最大传输单元,相当于划了一条红线。TCP的MSS将基于MTU来计算获得。</p>]]></content>
<tags>
<tag> 网络 </tag>
<tag> 抓包 </tag>
</tags>
</entry>
<entry>
<title>如何在一台机器管理两个github账号</title>
<link href="/2022/03/23/%E5%A6%82%E4%BD%95%E5%9C%A8%E4%B8%80%E5%8F%B0%E6%9C%BA%E5%99%A8%E7%AE%A1%E7%90%86%E4%B8%A4%E4%B8%AAgithub%E8%B4%A6%E5%8F%B7/"/>
<url>/2022/03/23/%E5%A6%82%E4%BD%95%E5%9C%A8%E4%B8%80%E5%8F%B0%E6%9C%BA%E5%99%A8%E7%AE%A1%E7%90%86%E4%B8%A4%E4%B8%AAgithub%E8%B4%A6%E5%8F%B7/</url>
<content type="html"><![CDATA[<p>这个问题的背景比较特殊,不是每个人都会遇到</p><p>比如你和你的对象都有github账号,而且两人偶尔会用同一台电脑push东西,就会遇到这种问题</p><p>现假如我要在本机维护两套github账号,一个是donnie,一个是alpha</p><h1 id="一-创建密钥对"><a href="#一-创建密钥对" class="headerlink" title="一. 创建密钥对"></a>一. 创建密钥对</h1><p>先用命令创建各自对应的密钥对,按着步骤走,密钥存储的文件名要各自命名</p><pre><code class="hljs bash">ssh-keygen</code></pre><p>此时两个账号的密钥对存放到了<code>~/.ssh/</code>目录下了</p><h1 id="二-配置pubKey到GitHub账号下"><a href="#二-配置pubKey到GitHub账号下" class="headerlink" title="二. 配置pubKey到GitHub账号下"></a>二. 配置pubKey到GitHub账号下</h1><p>在本例中,会把<code>~/.ssh/id_rsa_alpha.pub</code>里的公钥配到<code>alpha</code>的GitHub账号下;<code>~/.ssh/id_rsa_donnie.pub</code>里的公钥配置到donnie的GitHub账号下</p><h1 id="三-配置ssh-config"><a href="#三-配置ssh-config" class="headerlink" title="三. 配置ssh config"></a>三. 配置ssh config</h1><p>创建<code>~/.ssh/config</code>文件,为每个账号配置如下内容</p><pre><code class="hljs crmsh"><span class="hljs-comment"># donnie </span>Host donnieHostName github.com<span class="hljs-keyword">User</span> <span class="hljs-title">git</span>IdentityFile ~/.ssh/id_rsa_donnie<span class="hljs-comment"># alpha</span>Host alphaHostName github.com<span class="hljs-keyword">User</span> <span class="hljs-title">git</span>IdentityFile ~/.ssh/id_rsa_alpha</code></pre><p>配置完后可以使用命令<code>ssh -T git@${Host}</code>验证效果,例:</p><pre><code class="hljs bash">ssh -T git@donniessh -T git@alpha</code></pre><p>出现以下响应代表配置成功了</p><pre><code class="hljs ada">Hi xxx! You<span class="hljs-symbol">'ve</span> successfully authenticated, but GitHub does <span class="hljs-keyword">not</span> provide shell <span class="hljs-keyword">access</span>.</code></pre><h1 id="四-修改提交人信息"><a href="#四-修改提交人信息" class="headerlink" title="四. 修改提交人信息"></a>四. 修改提交人信息</h1><p>由于一台机器有两个GitHub账号使用,所以不能配置全局提交人,不然提交信息就乱套了。</p><p>首先删除全局配置:</p><pre><code class="hljs bash">git config --global --<span class="hljs-built_in">unset</span> <span class="hljs-string">'user.name'</span>git config --global --<span class="hljs-built_in">unset</span> <span class="hljs-string">'user.email'</span></code></pre><p>然后再在各自的git仓库设置本地配置:</p><pre><code class="hljs bash">git config user.email [email protected] config user.name xxx</code></pre><h1 id="五-将远程仓库地址与账号配置进行关联"><a href="#五-将远程仓库地址与账号配置进行关联" class="headerlink" title="五. 将远程仓库地址与账号配置进行关联"></a>五. 将远程仓库地址与账号配置进行关联</h1><p>假如原仓库地址为:<br><code>[email protected]:donnieYeh/picCrawler.git</code><br>或<br><code>https://github.com/donnieYeh/picCrawler.git</code></p><p>则重新关联的远程仓库格式为:<br><code>git@${Host}:donnieYeh/picCrawler.git</code></p><p>之后进行远程仓库操作,git会自动按照<code>config</code>文件中的配置把<code>${Host}</code>映射成<code>${HostName}</code>,而且会使用对应的私钥进行通信</p><h2 id="实践:"><a href="#实践:" class="headerlink" title="实践:"></a>实践:</h2><p>若已经有现成的拉下来的远程仓库,则修改远程仓库的地址:</p><pre><code class="hljs bash"><span class="hljs-comment"># 查看现有的关联</span>$ git remote -vorigin https://github.com/donnieYeh/picCrawler.git (fetch)origin https://github.com/donnieYeh/picCrawler.git (push)<span class="hljs-comment"># 先删除原先绑定的地址</span>$ git remote remove origin<span class="hljs-comment"># 再重新添加 </span>$ git remote add origin git@donnie:donnieYeh/picCrawler.git</code></pre><p>若是新clone的仓库,自行手动把clone地址的<code>github.com</code>改成自己的<code>${Host}</code>:</p><pre><code class="hljs bash">$ git <span class="hljs-built_in">clone</span> git@donnie:donnieYeh/picCrawler.git</code></pre><p>之后就能正常的进行仓库的各种管理操作啦</p>]]></content>
<categories>
<category> 日常经验 </category>
</categories>
<tags>
<tag> github </tag>
<tag> ssh </tag>
</tags>
</entry>
<entry>
<title>《java并发编程实战》读书笔记</title>
<link href="/2022/03/10/%E3%80%8Ajava%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E5%AE%9E%E6%88%98%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
<url>/2022/03/10/%E3%80%8Ajava%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E5%AE%9E%E6%88%98%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/</url>
<content type="html"><![CDATA[<p>书籍:《java concurrency in practice》英文原版<br>作者:Brian Goetz</p><p><strong>2.1</strong><br>线程安全如何定义?</p><p>首先,“线程安全”针对的对象是java的类。通常是描述“某个类是否线程安全”。<br>其次,一个类是“线程安全”,意味着它不需要使用任何“同步”、“协调”的代码就能在多个线程内正常运行<br>由此可知,只有“非线程安全”的类,才需要借助“同步”工具进行辅助</p><blockquote><p>我们很难理解线程安全的概念,是由于缺乏一个清晰精准的定义,而“精准”意味着class必须要遵循它的规范。一个好的规范定义了约束对象状态的不变量(invariants)和描述其操作效果的后置条件(postconditions)。</p></blockquote><p>说人话就是:书中给与”线程安全”的定义是:一个类是线程安全的话,那它必须保证在多线程操作下,它的invariants保持不变,而且操作结果符合给定的条件。</p><p>举个例子,”因素分解servlet缓存”例子中,请求入参的<strong>lastNumber</strong>和计算出来的<strong>lastFactors</strong>,组成了一个不变量(invariant),就是无论如何操作,用lastNumber在缓存取得的值一定与计算出来的lastFactors的一样。此处可见一个invariant可由多个相关联的state组成</p><p><strong>2.1.1</strong><br>无状态的类总是线程安全的。</p><p><strong>2.2</strong><br>通过了一个”counter++”的例子,解释了什么叫有状态,且有状态是线程不安全的。因为“counter”的操作包含了”读、改、写“三步,在多线程中这种操作并不是原子的。这种情况叫做”竞态(race condition)“</p><p><strong>2.2.1</strong><br>通过了”两人相约星巴克”的例子,展示了竞态的问题。简单来说就是:你观察某个条件为ture,当你基于这个观测来行动的时候,在观测到行动的这段时间内系统的状态已经发生了改变。这种竞态问题叫做“<strong>检查后行动(check-then-act)</strong>”</p><p><strong>2.2.2</strong><br>通过“懒初始化(lazy initialization)”例子来展示了代码层面的“检查后行动”问题。同时揭示了“counter”问题属于另一种竞态问题:“<strong>读、改、写(Read-modify-write)</strong>”。还举了些例子也会有这种问题,诸如:注册器、id生成器</p><p><strong>2.2.3</strong><br>把”检查后行动“和”读、改、写“这两种竞态问题概括为”<strong>复合操作(compound actions)</strong>”。而“复合操作”必须保证<strong>原子性</strong>。介绍了现成的”线程安全类”:Atomic*,用于解决“读改写”问题。同时作者建议多使用现成的“线程安全对象“来解决线程安全问题,而不是一上来就加锁。</p><p><strong>2.3</strong><br>通过了”对servlet的请求处理增加缓存”的例子,揭示了当程序有多个状态时,我们对每个状态都使用”线程安全对象”来维护,也是没法保证线程安全性的。因为这多个状态是相互关注的,为了保护状态的一致性,应当在原子性操作里更新相关联的变量。</p><p><strong>2.3.1</strong><br>介绍了保证多个状态原子性的手段:<strong>内置锁 synchronized</strong>,又称 intrinsic locks,monitor locks。</p><p>介绍了注解:@GuardedBy(“this”),可以标注变量由哪个锁保护,仅用于提高可读性。</p><p>同时注明了过度的”线程安全”则会造成”性能问题”,</p><p><strong>2.3.2</strong><br>介绍了什么叫<strong>可重入</strong></p><p><strong>2.3.4</strong><br>当可变状态被多个线程访问时,要对所有的访问动作都加同一个锁,此时可以说这个状态被那个锁保护着。<br>每个可变的、共享的状态都应该只由一个锁来保护,我们应该让维护者明确的知道保护状态的是哪个锁。<br>一个常见的做法是,当一个对象封装了多个可变状态,这些状态通常会用封装它们的对象的内置锁来保护。<br>作者再次强调,只有可能被多线程访问的可变对象,才需要使用锁来保护。<br>一个invariant设计的多个state,应该由同一把锁来保护</p><p>总的来说,线程安全围绕着三种解决思路,按顺序优先使用:</p><ul><li>要么不共享</li><li>要么发布不可变对象,对此有三个要求</li><li>对象状态不可变(指操作)</li><li>对象里的引用需要使用final修饰</li><li>正确的构造(不暴露this)</li><li>最后他自己是要可见的(添加volatile修饰)</li><li>要么使用同步机制(synchronized 、 lock)</li></ul><p><strong>2.4</strong><br>最后还强调了并不是对每个方法都加锁就能解决竞态问题,因为有些需要原子性的复合操作需要涉及到多个方法,而且这样做反而有可能造成活跃性问题和性能问题。</p><p><strong>2.5</strong><br>作者说明了对整个方法加锁不是一种好的做法,会直接导致所有线程都排队执行该业务,称其为“弱并发”。并提供了一种思路,就是尽量把不影响”共享状态”的耗时操作移出同步块,让这些耗时操作可以利用到并发的优势。这个思路能让我们在简单性、安全性和并发性之间找到一个平衡。如何找到这种平衡,衡量同步块的规模,要求要有tradeoffs思维。有时候简单性和性能是互相矛盾的,在实现同步的时候要注意抵制”过早牺牲简单性来满足性能”的诱惑。要避免在漫长的计算过程中持有锁。</p>]]></content>
<tags>
<tag> 读书笔记 </tag>
<tag> 并发编程 </tag>
</tags>
</entry>
<entry>
<title>github actions checkout失败问题</title>
<link href="/2022/03/09/github%20actions%20checkout%E5%A4%B1%E8%B4%A5%E9%97%AE%E9%A2%98/"/>
<url>/2022/03/09/github%20actions%20checkout%E5%A4%B1%E8%B4%A5%E9%97%AE%E9%A2%98/</url>
<content type="html"><![CDATA[<h1 id="背景:"><a href="#背景:" class="headerlink" title="背景:"></a>背景:</h1><p>最近github主页项目,提交代码后CI流程失败。提示</p><pre><code class="hljs clean">##[error]fatal: could not read Username for <span class="hljs-string">'https://github.com'</span>: terminal prompts disabled</code></pre><p>以前都是跟着教程搭的github page 的CI配置,也没有认真了解过其原理,正好借着这次机会学习下。</p><h1 id="github-workflow工作流程:"><a href="#github-workflow工作流程:" class="headerlink" title="github workflow工作流程:"></a>github workflow工作流程:</h1><ol><li>编写一个CI流程配置文件,里面包含了:<ol><li>触发CI的条件</li><li>CI流程每一步的操作</li></ol></li><li>接下来比如从本地仓push代码到github,触发CI条件,然后github Actions 下的workflow就会按照CI配置文件对应的步骤进行相应的构建、部署操作。</li></ol><h1 id="CI配置文件的结构:"><a href="#CI配置文件的结构:" class="headerlink" title="CI配置文件的结构:"></a>CI配置文件的结构:</h1><ul><li><p>触发条件</p></li><li><p>执行的任务(可有多个)</p><ul><li>任务的每一步具体做什么</li></ul><p> 该配置通常为存放在项目根目录的 <code>.github/workflows/</code>下的yml文件</p></li></ul><h1 id="什么是actions?"><a href="#什么是actions?" class="headerlink" title="什么是actions?"></a>什么是actions?</h1><p>通常 “任务的每一步具体动作”,由 run 命令来执行。而github的actions市场,则提供了一系列封装好的通用动作库。用法则是使用uses 命令来引入。</p><h1 id="checkout-actions"><a href="#checkout-actions" class="headerlink" title="checkout actions"></a>checkout actions</h1><p>其中一个最流行的actions就是 actions/checkout@v2,可以在github.com/actions 页找到它,然后点进去查看相应文档。<br>该actions中的token选项,是指定PAT(Personal access token)</p><h1 id="什么是PAT"><a href="#什么是PAT" class="headerlink" title="什么是PAT"></a>什么是PAT</h1><p>Personal access token,个人账号范围(可以是组织范围)的一个token,在<code>个人Setting/developer settings</code>里面创建,该token用于在命令行操作个人github资源时鉴权用。在创建的时候,会让你选择该token的授权范围,以及指定token的过期时间,通常不建议设置永久期限,定期更换能减少token暴露风险。</p><h1 id="如何在actions中引入PAT"><a href="#如何在actions中引入PAT" class="headerlink" title="如何在actions中引入PAT"></a>如何在actions中引入PAT</h1><p>可以通过表达式<code>${{ secrets.PERSONAL_TOKEN }} </code>来引入,其中 secrets 是指<strong>项目setting</strong>中的secrets栏,PERSONAL_TOKEN是我们在secrets栏中创建的变量,创建的时候把上一步创建的PAT粘贴到变量值,之后即可以在actions中通过表达式<code>${{ secrets.PERSONAL_TOKEN }}</code> 引入环境变量PAT</p><h1 id="回归问题"><a href="#回归问题" class="headerlink" title="回归问题"></a>回归问题</h1><p>造成该异常的主要原因就是PAT过期了,解决办法就是重新生成一个PAT,并重新设置到项目的”secrets.自定义变量名”中,只要CI配置文件的变量名跟”secrets.自定义变量名”匹配,就能重新成功跑通actions/checkout@v2构建流程了。</p><h1 id="参考资料:"><a href="#参考资料:" class="headerlink" title="参考资料:"></a>参考资料:</h1><ul><li><a href="https://www.bilibili.com/video/BV1RE411R7Uy?from=search&seid=4861890920343690403&spm_id_from=333.337.0.0" title="【CICD】github新功能actions全方位讲解!!">【CICD】_github_新功能_actions_全方位讲解!!</a></li><li><a href="https://github.com/actions/checkout/issues/26">https://github.com/actions/checkout/issues/26</a></li></ul>]]></content>
<tags>
<tag> CI </tag>
<tag> github </tag>
</tags>
</entry>
<entry>
<title>张执中——说服力(1)</title>
<link href="/2022/02/27/%E5%BC%A0%E6%89%A7%E4%B8%AD%E2%80%94%E2%80%94%E8%AF%B4%E6%9C%8D%E5%8A%9B%EF%BC%881%EF%BC%89/"/>
<url>/2022/02/27/%E5%BC%A0%E6%89%A7%E4%B8%AD%E2%80%94%E2%80%94%E8%AF%B4%E6%9C%8D%E5%8A%9B%EF%BC%881%EF%BC%89/</url>
<content type="html"><![CDATA[<blockquote><p>听了张执中的公开课,觉得颠覆了以前的很多观点,遂记录</p></blockquote><ul><li><p>每个人的看法都是其过往人生的总结</p></li><li><p>别说“你应该”,要说“我需要”</p></li></ul><p><strong>说教5典</strong></p><ol><li>你为什么不去试试呢 <blockquote><p>—— 帮助别人复习反驳的理由</p></blockquote></li><li>我可以陪你去做<blockquote><p>—— 热心只会给人压力</p></blockquote></li><li>你有什么困难吗<blockquote><p>—— 你只是在想机会反驳对方</p></blockquote></li><li>当你这么样这么样做完以后,不会觉得很舒服吗<blockquote><p>—— 别告诉对方,他该有什么感觉</p></blockquote></li><li>我觉得你应该不是这种人,你可以做的更好<blockquote><p>—— 不要替我决定我是谁</p></blockquote></li></ol><ul><li><p>别问“为什么不”,要问“为什么要”</p></li><li><p>说教者的反面:点燃者(每个人都想改变,只是需要发觉和点燃!)</p></li></ul><p><strong>说教者总是以自己目的为出发点去控制他人,而不是去理解他人</strong></p><ul><li>你只看到了对方点炸鸡,却没看到对方点无糖可乐</li><li>你只看到了他平时不好好学习,却没看到他临时抱佛脚</li></ul><p>点燃句式</p><ol><li>咦,你为什么会提起xx?<blockquote><p>—— 针对既有行为/让对方找出自己的理由</p></blockquote></li><li>这理由,真有那么重要吗?<blockquote><p>—— 让他为自己的理由辩护</p></blockquote></li><li>没想到,你是在意这种事的人<blockquote><p>—— 让对方重新定义自己</p></blockquote></li></ol><p>综上:所有说服,本质上都是自我说服</p><p>不是找个说服他的理由,而是帮他找到说服自己的理由</p>]]></content>
<categories>
<category> 张执中——说服力 </category>
</categories>
<tags>
<tag> 笔记 </tag>
<tag> 公开课 </tag>
</tags>
</entry>
<entry>
<title>python爬取邮件订阅的pinterest热门图片</title>
<link href="/2022/02/26/python%E7%88%AC%E5%8F%96%E9%82%AE%E4%BB%B6%E8%AE%A2%E9%98%85%E7%9A%84pinterest%E7%83%AD%E9%97%A8%E5%9B%BE%E7%89%87/"/>
<url>/2022/02/26/python%E7%88%AC%E5%8F%96%E9%82%AE%E4%BB%B6%E8%AE%A2%E9%98%85%E7%9A%84pinterest%E7%83%AD%E9%97%A8%E5%9B%BE%E7%89%87/</url>
<content type="html"><![CDATA[<p><a href="https://github.com/donnieYeh/picCrawler">项目地址</a></p><h1 id="来由"><a href="#来由" class="headerlink" title="来由"></a>来由</h1><p>不知何时开始订阅了pinterest的消息,pinterest会不时的发送热门图片到邮箱里。无奈于平时没时间也懒得去翻阅,堆积了有900+封pinterest的邮件。在某个下午突发奇想:如果能自动爬取热门图片,然后在电视大屏里轮播,把平时不怎么开的电视利用起来,当成一块动态大画框,貌似也挺不错。然后这个工具就产生了。</p><h1 id="构思"><a href="#构思" class="headerlink" title="构思"></a>构思</h1><p>要实现我的想法,梳理了以下大致有如下几步:</p><ol><li>获取pinterest邮件记录</li><li>打开热图链接,获取网页dom</li><li>解析网页dom,获取图片地址列表</li><li>爬取图片,保存到本地目录</li><li>电视通过smb协议访问电脑的壁纸目录,轮播图片</li></ol><p>拓展:</p><ol><li>图片去重,hash值存db中</li></ol><h1 id="过程记录"><a href="#过程记录" class="headerlink" title="过程记录"></a>过程记录</h1><h2 id="获取pinterest邮件记录"><a href="#获取pinterest邮件记录" class="headerlink" title="获取pinterest邮件记录"></a>获取pinterest邮件记录</h2><p>参考文章:</p><ul><li><a href="https://zhuanlan.zhihu.com/p/35521803">https://zhuanlan.zhihu.com/p/35521803</a></li></ul><p>使用python读取outlook邮件</p><ul><li>需要关注每次只拉未处理过的邮件</li></ul><p>跟着文章操作,可以顺利获取到pinterest未读邮件列表,以及其内容</p><p><a href="https://docs.microsoft.com/zh-cn/dotnet/api/microsoft.office.interop.outlook.items?view=outlook-pia">outlookAPI相关文档</a></p><h3 id="2种邮件处理策略"><a href="#2种邮件处理策略" class="headerlink" title="2种邮件处理策略"></a>2种邮件处理策略</h3><p>公共特征: </p><ul><li>sender address 包含 recommend</li><li>跳转链接带有 “utm_content” 字符串</li></ul><h4 id="图板推荐"><a href="#图板推荐" class="headerlink" title="图板推荐"></a>图板推荐</h4><p>特征:</p><ul><li>subject 包含“<strong>图板</strong>”二字</li><li>跳转到图板页,需要模拟浏览器动作以获取二级图片</li></ul><p>策略:</p><ul><li>图板的图我们可以只下载前20张</li></ul><h4 id="热门pin图"><a href="#热门pin图" class="headerlink" title="热门pin图"></a>热门pin图</h4><p>特征</p><ul><li>subject包含“pin图”二字</li><li>跳转到图片页,可直接获取图片链接</li></ul><hr><p>此处涉及到使用正则表达式过滤关键链接,相关操作参考:</p><pre><code class="hljs python"><span class="hljs-keyword">import</span> re// 匹配整串re.match// 搜索第一个匹配re.search// 搜索所有匹配re.findall</code></pre><h2 id="打开热图链接,获取网页dom"><a href="#打开热图链接,获取网页dom" class="headerlink" title="打开热图链接,获取网页dom"></a>打开热图链接,获取网页dom</h2><p>参考文章:</p><ul><li><a href="https://steam.oxxostudio.tw/category/python/spider/pinterest.html">https://steam.oxxostudio.tw/category/python/spider/pinterest.html</a></li><li><a href="https://www.selenium.dev/selenium/docs/api/py/index.html">selenium学习</a></li><li><a href="https://www.w3schools.com/xml/xpath_intro.asp">xpath学习</a></li><li><a href="https://www.w3schools.com/xml/xpath_syntax.asp">xpath语法手册</a></li><li><a href="https://blog.csdn.net/butthechi/article/details/80844330">https://blog.csdn.net/butthechi/article/details/80844330</a></li></ul><p>由于pinterest网页有个特性,就是每次只展示特定窗口范围的图片,在浏览器滚动过程中,前面的图片结点会消失,后面的图片结点会加载。所以没法一次性获取整个dom资源,需要模拟滚动网页,才能获取到完整的DOM。</p><p>这里使用selenium来实现模拟,需要了解一些前置知识:XPATH</p><h3 id="XPATH"><a href="#XPATH" class="headerlink" title="XPATH"></a>XPATH</h3><p>有7种结点类型:<br> element, attribute, text, namespace, processing-instruction, comment, and document nodes</p><ul><li>最上层的为 root Element node</li><li><code><title lang="en">Harry Potter</title></code> 中的<code> lang="en"</code>为attribute node</li><li><code><author>J K. Rowling</author></code>中的<code>J K. Rowling</code>为text node</li><li>atomic value 指的是没有子节点和父节点的node,如text node</li><li>Items 指的是 atomic values 或者 nodes</li><li>ancestors(祖先)结点指的是<strong>包括父节点</strong>的所有上级结点</li><li>descendants(子孙)结点指的是<strong>包括子节点</strong>的所有下级结点</li></ul><p>模糊匹配属性:<code>//tr[contains(@class,'result')] # 得到所有class 包含result的语句</code></p><hr><p>python set 定义:<code>imgs = {}</code></p><h2 id="解析网页dom,获取图片地址列表"><a href="#解析网页dom,获取图片地址列表" class="headerlink" title="解析网页dom,获取图片地址列表"></a>解析网页dom,获取图片地址列表</h2><p>使用BeautifulSoup + 正则轻松搞定</p><h2 id="爬取图片,保存到本地目录"><a href="#爬取图片,保存到本地目录" class="headerlink" title="爬取图片,保存到本地目录"></a>爬取图片,保存到本地目录</h2><hr><h1 id="后话"><a href="#后话" class="headerlink" title="后话"></a>后话</h1><ul><li>由于使用单线程处理,对pinterest服务器是友好的,以后考虑提升效率,或许会使用代理池+多线程抓取</li><li>后续考虑TB上看下有没有电子相框,这样连电视都不用开了</li></ul>]]></content>
<tags>
<tag> python </tag>
<tag> 爬虫 </tag>
</tags>
</entry>
<entry>
<title>PostMan自动生成鉴权参数</title>
<link href="/2022/01/28/PostMan%E8%87%AA%E5%8A%A8%E7%94%9F%E6%88%90%E9%89%B4%E6%9D%83%E5%8F%82%E6%95%B0/"/>
<url>/2022/01/28/PostMan%E8%87%AA%E5%8A%A8%E7%94%9F%E6%88%90%E9%89%B4%E6%9D%83%E5%8F%82%E6%95%B0/</url>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>工具版本:</p><ul><li>PostMan: 8.0.7</li><li>Crypto-js: 3.1.9</li></ul><p>我们通常联调的时候,有可能会需要协商好密钥,用于在API调用时加签和验签。由于签名是基于请求体和请求头动态生成的,postman预置静态参数的方式根本不能满足我们对效率的追求,然后我就捣鼓了一下用postman为每个请求动态生成鉴权参数,以此文章来记录过程中的经验心得。</p><p>postman中有个选项页叫做<code>Pre-request script</code>,顾名思义,它可以在请求前执行该脚本。它本质上就是个js脚本,而我们主要就是要在这里编写脚本生成所需数据。</p><h2 id="零、预研"><a href="#零、预研" class="headerlink" title="零、预研"></a>零、预研</h2><p>调查了一下找到了JS里好评率较高的密钥库<a href="https://github.com/brix/crypto-js">Crypto-js</a>,随便谷歌一个好用的CDN地址,我这里使用的是jsdelivr的源(jsdelivr真的太香了)。为了验证它好不好使,我习惯先用交互式编程研究下它的用法(使用ipython留下的习惯),最好的JS交互式界面当然就是Chrome了。</p><p>打开Chrome控制台,输入命令,引入密钥库。</p><pre><code class="hljs javascript"><span class="hljs-keyword">var</span> script = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">"script"</span>);script.src = <span class="hljs-string">"https://cdn.jsdelivr.net/npm/[email protected]/crypto-js.js"</span><span class="hljs-built_in">document</span>.getElementsByTagName(<span class="hljs-string">"head"</span>)[<span class="hljs-number">0</span>].appendChild(script);</code></pre><p>简单找下,很容易找到它的主类是<code>CryptoJS</code></p><p>我这里需要用到密钥库里的HmacSHA256、Base64两个工具,通过摸索它的API,很快了解了用法。</p><pre><code class="hljs javascript"><span class="hljs-keyword">var</span> key = foo<span class="hljs-keyword">var</span> message = bar<span class="hljs-keyword">var</span> sign = CryptoJS.HmacSHA256(<span class="hljs-string">"foo"</span>,<span class="hljs-string">'bar'</span>)<span class="hljs-keyword">var</span> signenc = CryptoJS.enc.Base64.stringify(sign)</code></pre><pre><code>此处要注意一个大坑,由于CryptoJS生成的Sign是特别的数据结构,而不是纯粹的二进制,所以用chrome自带的base64工具(btoa、atob)是无法得到正确的编码的,总之别人工具库都给你准备好了那就直接用工具库里的Base64吧。</code></pre><p>然后用得到的签名跟服务器生成的签名作比对,或者直接调用API验证,验证成功,至此前期工作已经准备好。</p><h2 id="一、引入密钥库"><a href="#一、引入密钥库" class="headerlink" title="一、引入密钥库"></a>一、引入密钥库</h2><p>打开PostMan的 <code>Pre-request script</code> 界面,编写脚本,在脚本初始化阶段引入密钥库。</p><pre><code class="hljs javascript"><span class="hljs-keyword">if</span>(!pm.globals.has(<span class="hljs-string">"cryptojs"</span>)){ pm.sendRequest(<span class="hljs-string">"https://cdn.jsdelivr.net/npm/[email protected]/crypto-js.js"</span>, <span class="hljs-function">(<span class="hljs-params">err, res</span>) =></span> { <span class="hljs-keyword">if</span> (!err) { pm.globals.set(<span class="hljs-string">"cryptojs"</span>, res.text()) }})}<span class="hljs-built_in">eval</span>(pm.globals.get(<span class="hljs-string">"cryptojs"</span>));</code></pre><h2 id="二、构造鉴权数据"><a href="#二、构造鉴权数据" class="headerlink" title="二、构造鉴权数据"></a>二、构造鉴权数据</h2><p>先确定构造数据所需的关键元素,参考对接文档,用注释把步骤大致整理出来</p><pre><code class="hljs javascript"><span class="hljs-comment">// plaintext = httpmethod + RequestURI + http body</span><span class="hljs-comment">// key=AppSecret + Nonce + timestamp</span><span class="hljs-comment">// token=BASE64(HMAC_SHA256(plaintext, key))</span></code></pre><p>然后对着注释把代码补全</p><blockquote><p>有条件的都建议使用新版客户端,不建议用chrome插件。客户端会在编写脚本的时候有智能代码提醒,很方便。</p></blockquote><pre><code>1. 获取请求头使用 pm.request.headers2. 获取body使用 pm.request.url.getPath(true),加true是指不按'/'切割路径</code></pre><pre><code class="hljs javascript"><span class="hljs-comment">// let plaintext = httpmethod+RequestURI+http body</span><span class="hljs-keyword">let</span> plaintext = <span class="hljs-string">'POST'</span>+pm.request.url.getPath(<span class="hljs-literal">true</span>)+pm.request.body.raw;<span class="hljs-comment">// key=AppSecret(主题服务器分配)+Nonce+timestamp</span><span class="hljs-keyword">let</span> appSecret = <span class="hljs-string">'ABCDEFGHIJKLMNOPQRSTUVWXYZ'</span>;<span class="hljs-keyword">let</span> key = appSecret+pm.request.headers.get(<span class="hljs-string">"nonce"</span>)+pm.request.headers.get(<span class="hljs-string">"timestamp"</span>);<span class="hljs-comment">// token=BASE64(HMAC_SHA256(plaintext, key))</span><span class="hljs-keyword">let</span> sign = CryptoJS.HmacSHA256(plaintext,key);<span class="hljs-keyword">let</span> signenc = CryptoJS.enc.Base64.stringify(sign);pm.collectionVariables.set(<span class="hljs-string">"usertoken"</span>, signenc);</code></pre><p>最后把构造好的<code>token</code>设置到环境变量中:<code>pm.collectionVariables.set("usertoken", signenc);</code></p><h2 id="三、引用鉴权参数"><a href="#三、引用鉴权参数" class="headerlink" title="三、引用鉴权参数"></a>三、引用鉴权参数</h2><p>在请求用例的Header中,就可以用PostMan的匹配字符来引用上一步放置到环境变量中的数据了。<br>![[Pasted image 20220128154749.png]]</p><p>之后就可以点击请求按钮验证结果了,此时无论我怎样的修改请求体,修改timestamp,都可以自行动态生成token了,舒畅~。</p>]]></content>
<categories>
<category> 效率人生 </category>
</categories>
<tags>
<tag> PostMan </tag>
<tag> 测试 </tag>
</tags>
</entry>
<entry>
<title>数字证书与安全协议</title>
<link href="/2022/01/22/%E6%95%B0%E5%AD%97%E8%AF%81%E4%B9%A6%E4%B8%8E%E5%AE%89%E5%85%A8%E5%8D%8F%E8%AE%AE/"/>
<url>/2022/01/22/%E6%95%B0%E5%AD%97%E8%AF%81%E4%B9%A6%E4%B8%8E%E5%AE%89%E5%85%A8%E5%8D%8F%E8%AE%AE/</url>
<content type="html"><![CDATA[<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><p>数字身份的本质是一对秘钥,分别为公钥和私钥。</p><p>把数字身份比喻成一个证件,那么数字证书就是“身份认证机构”盖在证件上的一个章(即权威机构的背书)。没有背书的数字身份是没实际意义的。</p><h3 id="实际应用"><a href="#实际应用" class="headerlink" title="实际应用"></a>实际应用</h3><h4 id="生活中"><a href="#生活中" class="headerlink" title="生活中"></a>生活中</h4><p>一切进行数据通信的地方都有可能会用到数字证书,如</p><ul><li>电子邮件</li><li>浏览网页</li><li>手机APP</li><li>科学上网</li></ul><h4 id="开发中"><a href="#开发中" class="headerlink" title="开发中"></a>开发中</h4><ul><li>工具抓包:fiddler</li></ul><h3 id="为什么要有数字证书"><a href="#为什么要有数字证书" class="headerlink" title="为什么要有数字证书"></a>为什么要有数字证书</h3><h4 id="演进过程"><a href="#演进过程" class="headerlink" title="演进过程"></a>演进过程</h4><p>下面展示了网络安全通信是如何逐步衍生出数字证书的。</p><p><img src="/images/%E6%BC%94%E8%BF%9B%E8%BF%87%E7%A8%8B1.gif"></p><p>第一阶段,双方协商好对称密钥,之后的通信都使用对称密钥对明文进行加密解密操作。但是这个阶段的缺点是双方需要提前约定好密钥,该模式无法满足临时与陌生对象通信的需求。</p><p><img src="/images/%E6%BC%94%E8%BF%9B%E8%BF%87%E7%A8%8B2.gif"><br>第二阶段,双方使用非对称秘钥通信,其中bob有一对公私钥,通信初始他会把公钥提供给alice,alice使用该公钥来加密明文。由于密文只有bob的私钥能解密,所以在通信过程中其他人是无法解开密文的(未被篡改的情况下)。<br>该阶段的主要问题在于非对称加密明文的性能相较对称加密性能要差很多。</p><p><img src="/images/%E6%BC%94%E8%BF%9B%E8%BF%87%E7%A8%8B3.gif"><br>第三阶段,alice使用了临时生成的<strong>对称密钥</strong>来加密明文,然后再用bob的公钥加密<strong>对称密钥</strong>,然后把<strong>被加密的明文</strong>和<strong>被加密的密钥</strong>发送给bob,由于<strong>被加密的密钥</strong>只能用bob的私钥解密,所以过程是安全的(未被篡改的情况下),同时<strong>对称密钥</strong>加密明文保证了性能。但这个过程真的没缺陷了吗?我们看看以下场景:<br><img src="/images/%E6%BC%94%E8%BF%9B%E8%BF%87%E7%A8%8B3-%E7%BC%BA%E9%99%B7.gif"><br>该过程中,中间人可以拦截bob发给alice的公钥,同时发送自己的公钥给alice,伪装成bob。之后的通信过程中,alice使用伪装的公钥加密出的密文,自然能被中间人破解。更有甚者中间人可以伪造一份欺骗性的报文发给bob。该过程省略了alice用自己私钥加签的过程,本质上一样是不安全的,中间人既然能伪造bob给alice的公钥,当然也能伪造alice给bob的公钥,那么这个过程中的签名自然也是可以伪造的。<br><img src="/images/%E6%BC%94%E8%BF%9B%E8%BF%87%E7%A8%8B4.gif"><br>上一阶段的核心问题在于bob的公钥在传输过程中被篡改了,对于alice来说bob的公钥是不可信的。那么解决了公钥的信任问题,这个过程就安全了。</p><p>第四阶段:这一阶段bob把自己的公钥封装到数字证书里。在之后的通信初始阶段bob会先把数字证书发给alice,alice去CA机构验证数字证书的合法性,若验证通过,则代表bob的数字证书是可信的,那么证书里面的公钥自然也是可信的。这样就顺利的解决了公钥的信任问题。</p><h3 id="为什么数字证书的认证过程是可信的"><a href="#为什么数字证书的认证过程是可信的" class="headerlink" title="为什么数字证书的认证过程是可信的"></a>为什么数字证书的认证过程是可信的</h3><p><img src="/images/%E8%AF%81%E4%B9%A6%E8%AE%A4%E8%AF%81%E5%8F%AF%E4%BF%A1.gif"><br>在服务域名上线之前,会先用自己的公钥生成一份证书签名请求文件(certificate sign request,csr),可以理解为填了份带有公钥的申请表单,发给CA机构,然后CA机构会把这份表单里面的关键信息(包括公钥、服务域名、所属机构等)提取成摘要,用自己的私钥进行加签。然后把签名和关键信息,输出到正式的数据结构中,形成了数字证书,再把这个数字证书发送给服务方,这个过程就叫做证书的签发。</p><p>服务器得到了具有公证力的证书,上线新的域名。随后客户端就可以访问该域名对服务器发起握手,握手阶段服务器会把证书发给客户端,客户端在证书里找到它的签发机构,遂去获取签发机构的证书。此时我们注意服务器证书的签名是用CA机构的私钥加签的,理所当然可以用CA机构的公钥进行验签。如果验签成功,则说明了服务器的证书是可信的。</p><p>可是此时出现了新的问题,我们通过这个流程可以验证服务器证书的有效性,前提是CA机构的证书也是合法的,但如何保证CA证书不会被篡改呢?</p><p>同理我们的客户端可以走相同的流程,去获取CA机构的上级机构的证书,来验证CA证书的有效性,这样就形成了一个递归验证链。这个递归链是有边界的,它的边界就是<strong>根证书。</strong></p><p><img src="/images/Pasted%20image%2020220123125102.png"></p><p>由上图的信任链我们可以看出验证操作会一直递归到根证书。这里的关键就在于根证书是内置于我们操作系统本地的,那么在获取根证书的时候就不想要经过网络。若不经过网络,则就不存在被中间人攻击的风险。所以,到根证书为止,就可以证明整个信任链是可信的了。</p><h4 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h4><p>加密技术的存在是为了保障通信信息不被第三方窃取,保证信息的安全性,该技术甚至可以追溯到3900年前的古希腊。但是随着互联网尤其是无线网络的发展,出现了一种新的场景,就是通信双方节点,通常都是需要临时交换密钥来进行安全通信。伴随着这种场景,新的攻击手段也应运而生,那就是中间人攻击,简单来说就是中间人可以伪装成你的通信对象,交换非法秘钥,以达到监听窃取、篡改信息的目的。</p><p>而<strong>数字证书</strong>就是为了解决<strong>信任危机</strong>而的产生。<br>目前大部分数字证书都采用 x509第三版数据结构<br>数字证书是实现<strong>安全协议</strong>过程的基石。</p><h4 id="Q-amp-A"><a href="#Q-amp-A" class="headerlink" title="Q&A"></a>Q&A</h4><p>为什么要有中间证书?<br>1.根证书的私钥安全性隔离<br>A.降低替换私钥成本<br>B.降低暴露私钥风险<br>2.根证书的操作效率无法满足要求</p><p>浏览器如何获取中间证书?<br>一般来说,服务器会将中间证书一并发送过来。也就是说,当我们访问某个网站时,收到的不是一个证书,而是一系列证书。<br>当然,这不是强制要求,在服务器不发送的情况下,浏览器也会使用一些方法去定位中间证书,比如缓存之前下载过的证书<br>证书文件中的 Authority Information Access (AIA) Extension 里面含有上级证书的相关信息,浏览器可以使用这个信息去下载上级证书</p><h3 id="PKI-Public-Key-Infrastructure-体系"><a href="#PKI-Public-Key-Infrastructure-体系" class="headerlink" title="PKI(Public Key Infrastructure)体系"></a>PKI(Public Key Infrastructure)体系</h3><p>当我们掌握了证书的理论性知识后。需要有一套完整的创建, 管理, 分发, 使用, 存储 和 吊销 证书的体系,这就是PKI体系。该体系里完整的囊括了所需的指导性工具,包括规则、策略、相关软硬件、和流程。该体系并不由某一家制定,而是互联网组织共同参与,其中做出过贡献的组织包括但不限于 ITU-T(X.500~X.599系列)、IETF、RSA实验室(RSA Security,PKCS系列)、ISO。综上所述,PKI可以认为是一系列的规范和标准。而为了实现安全基础服务目的的技术都可称为PKI。## 安全协议</p><h3 id="TLS"><a href="#TLS" class="headerlink" title="TLS"></a>TLS</h3><h4 id="TLS属于哪层协议?"><a href="#TLS属于哪层协议?" class="headerlink" title="TLS属于哪层协议?"></a>TLS属于哪层协议?</h4><p>TLS所处的协议层如下:<br>数据帧{ IP层{ TCP层{ <strong>TLS层{</strong> 应用层{} <strong>}</strong> } } }</p><p>可见TLS介于传输层(TCP)和应用层之间。</p><p>如果把网络协议想象成一个纸团,协议之间的嵌套就像一张纸包住另一个纸团一样。最里面的一层就是应用层协议,如HTTP。而网络报文的传输过程中,任何人能够截获这个纸团,并层层剥开,但是如果没有相应的秘钥,是没法剥开TLS这一层的,也就没法看到里面的HTTP报文。<br><img src="/images/Pasted%20image%2020220123093321.png"></p><h4 id="SSL和TLS的关系"><a href="#SSL和TLS的关系" class="headerlink" title="SSL和TLS的关系"></a>SSL和TLS的关系</h4><p>SSL是网景制定的,TLS是IETF指定的。TLS1.0建立在SSL3.0基础之上,可以理解为SSL3.1。由于习惯原因,现在很多对安全层依旧沿用SSL的称呼,但实际上已经使用的是TLS的技术了。</p><p>网景是一家传说级的公司,Mozilla(火狐前身)和JavaScript都诞生于这家公司。<br>而IETF是一个开放的标准组织,网络上随处可见的RFC标准就是出自他们。</p><p><img src="/images/Pasted%20image%2020220123093824.png"></p><h4 id="TLS-的运作过程"><a href="#TLS-的运作过程" class="headerlink" title="TLS 的运作过程"></a>TLS 的运作过程</h4><p><img src="/images/Pasted%20image%2020220123121739.png"></p><h4 id="TLS的关键阶段"><a href="#TLS的关键阶段" class="headerlink" title="TLS的关键阶段"></a>TLS的关键阶段</h4><p>握手阶段是TLS的关键<br>握手过程,主要就是双方交换公共参数,生成对称密钥,用于加密通信<br>秘钥交换算法(key exchange):</p><ul><li>RSA based key exchange</li><li>DH(Diffie-Hellman) based key exchange</li></ul><h4 id="交换秘钥过程"><a href="#交换秘钥过程" class="headerlink" title="交换秘钥过程"></a>交换秘钥过程</h4><p>HTTPS常用的密钥交换算法有两种,分别是<code>RSA</code>和<code>ECDHE</code><br>由于RSA不具备向前安全性质,现在大部分服务器不会使用它来交换秘钥<br>每个通信过程的秘钥没有关系,相互独立,才能保证 <strong>「前向安全」</strong>。<br><code>DH</code>作为<code>ECDHE</code>的基础,详细介绍参考[DH]一节<br><code>ECDHE</code>的演进路线基本是这样的:<code>DH</code>-> <code>DHE</code>-><code>ECDHE</code>,</p><h4 id="向前安全性"><a href="#向前安全性" class="headerlink" title="向前安全性"></a>向前安全性</h4><p><strong>前向安全性</strong>或<strong>前向</strong>保密<strong>性</strong>(英语:Forward Secrecy,缩写:FS),有时也被称为完美<strong>前向安全</strong>(英语:Perfect Forward Secrecy,缩写:PFS),<strong>是</strong>密码学中通讯协议的<strong>安全</strong>属性,指的<strong>是</strong>长期使用的主密钥泄漏不会导致过去的会话密钥泄漏。</p><h3 id="两种密钥交换模式"><a href="#两种密钥交换模式" class="headerlink" title="两种密钥交换模式"></a>两种密钥交换模式</h3><h4 id="RSA-Base"><a href="#RSA-Base" class="headerlink" title="RSA Base"></a>RSA Base</h4><p>流程<br>从浏览器请求HTTPS,到渲染数据的整个过程</p><ul><li>Client Hello</li><li>随机数、算法套件</li><li>Server Hello</li><li>随机数、选定算法、公钥</li><li>Certificate</li><li>验证身份的项目:</li><li>涉及证书链的有效期</li><li>涉及证书链的签名</li></ul><p> 举例子,证书验证不通过的情况<br> <img src="/images/Pasted%20image%2020220123121951.png"></p><ul><li>Server Hello Done</li><li>Client Key Exchange, Change Cipher Spec, Encrypted Handshake Message</li><li>服务端 Change Cipher Spec, Encrypted Handshake Message</li><li>加密通信</li></ul><p>premaster key、masterKey的作用<br><img src="/images/Pasted%20image%2020220123122022.png"></p><h4 id="DH-Base"><a href="#DH-Base" class="headerlink" title="DH Base"></a>DH Base</h4><h5 id="DH算法"><a href="#DH算法" class="headerlink" title="DH算法"></a>DH算法</h5><p>DH算法的原理可以参考这一篇文章:<a href="https://bbs.huaweicloud.com/blogs/detail/273779">为了搞懂 HTTPS,我把大学的数学书拿了出来。。。)</a></p><h5 id="小结-1"><a href="#小结-1" class="headerlink" title="小结"></a>小结</h5><p>要注意dh算法本身并不能防范中间人攻击,中间人可以通过分别对双方进行DH密钥交换进行攻击,所以DH实际需要配合身份认证机制才能安全工作。DH两种工作方式:</p><ol><li>Fixed Diffie-Hellman:直接把DH公钥固化到服务方证书里,和RSA一样不具备向前安全性</li><li>Ephemeral Diffie-Hellman (简称为DHE):服务方使用动态DH公钥,只是会对公钥进行签名</li></ol><p>在DH模式中,数字证书,不是用于生成通信用的秘钥,而是单纯用于双方认证身份(这个是大部分人很容易理解错的地方)</p><h4 id="演进过程-1"><a href="#演进过程-1" class="headerlink" title="演进过程"></a>演进过程</h4><p>以下过程基于网络交换秘钥场景</p><table><thead><tr><th>技术</th><th>性能</th><th>防监听(未篡改)</th><th>防篡改</th><th>向前安全</th></tr></thead><tbody><tr><td>对称加密{原文}</td><td>√</td><td>x</td><td>x</td><td>x</td></tr><tr><td>非对称加密{原文}</td><td>x</td><td>√</td><td>x</td><td>x</td></tr><tr><td>非对称{密钥}</td><td>√</td><td>√</td><td>x</td><td>x</td></tr><tr><td>非对称{密钥}+公钥认证</td><td>√</td><td>√</td><td>√</td><td>x</td></tr><tr><td>临时DH公钥+公钥认证</td><td>√</td><td>√</td><td>√</td><td>√</td></tr></tbody></table><h3 id="TLS1-3介绍"><a href="#TLS1-3介绍" class="headerlink" title="TLS1.3介绍"></a>TLS1.3介绍</h3><p>TLS 1.3 是时隔九年对 TLS 1.2 等之前版本的新升级,也是迄今为止改动最大的一次。针对目前已知的安全威胁,IETF(Internet Engineering Task Force,互联网工程任务组) 正在制定 TLS 1.3 的新标准,使其有望成为有史以来最安全,但也最复杂的 TLS 协议。<br>![[Pasted image 20220112144654.png]]</p><p>v1.3 优点:<br>1.握手只需要 1TTR,性能更好<br>2.只支持向前安全的算法,安全性更高</p><h3 id="实践"><a href="#实践" class="headerlink" title="实践"></a>实践</h3><p>相关配置</p><ul><li>internet属性<br>如何查看一个网站使用的TLS版本:f12->security<br><img src="/images/Pasted%20image%2020220123122235.png"></li></ul><h4 id="证书主要生命周期"><a href="#证书主要生命周期" class="headerlink" title="证书主要生命周期"></a>证书主要生命周期</h4><p>常见的证书管理工具有两种,一个是jdk自带的keytool;一个是开源项目openssl<br>以下命令均可以加上 –help 查看帮助,示例:<code>keytool –genkeypair -help</code></p><table><thead><tr><th>阶段</th><th>Keytool相关命令</th><th>Openssl相关命令</th></tr></thead><tbody><tr><td>生成密钥对</td><td>-genkeypair</td><td>genrsa</td></tr><tr><td>生成证书请求</td><td>-certreq</td><td>req</td></tr><tr><td>签发证书</td><td>-gencert</td><td>X509</td></tr><tr><td>管理密钥库</td><td>-list, -importkeystore</td><td>pkcs12 -nodes -nocerts</td></tr><tr><td>使用证书</td><td></td><td></td></tr><tr><td>吊销</td><td></td><td></td></tr></tbody></table><h5 id="keytool实践"><a href="#keytool实践" class="headerlink" title="keytool实践"></a>keytool实践</h5><pre><code class="hljs shell"><span class="hljs-meta">#</span><span class="bash"> 生成根证书,存储到密钥库caroot.ks</span>keytool -genkeypair -alias caroot -keyalg RSA -keystore caroot.ks -storepass aaaaaa<span class="hljs-meta"></span><span class="hljs-meta">#</span><span class="bash"> 根据提示,把密钥库转换成pkcs12</span>keytool -importkeystore -srckeystore caroot.ks -destkeystore caroot.p12 -deststoretype pkcs12<span class="hljs-meta"></span><span class="hljs-meta">#</span><span class="bash"> 这里我们可以把keytool命令做个别名,方便调用(可选)</span>alias mykt='keytool -keystore $ksfile -storepass $kspass $*'export ksfile=caroot.p12 && export kspass=aaaaaa<span class="hljs-meta"></span><span class="hljs-meta">#</span><span class="bash"> 生成服务证书,存储到密钥库server.p12</span>keytool -genkeypair -alias server -keyalg rsa -keysize 1024 -keystore server.p12 -storepass aaaaaa -storetype pkcs12<span class="hljs-meta"></span><span class="hljs-meta">#</span><span class="bash"> 生成服务证书签名申请</span>keytool -certreq -alias server -file server.csr -storepass aaaaaa -keystore server.p12<span class="hljs-meta"></span><span class="hljs-meta">#</span><span class="bash"> 使用ca根证书签发服务证书</span>keytool -gencert -infile server.csr -outfile server.crt -alias caroot -keystore caroot.p12 -storepass aaaaaa -ext san=dns:www.mycompany.com,dns:mycompany.com<span class="hljs-meta"></span><span class="hljs-meta">#</span><span class="bash"> 此时我们发现ca签发的证书不可信,导出根证书并安装到本地即可解决</span>keytool -exportcert -alias caroot -file caroot.crt -keystore caroot.p12 -storepass aaaaaa</code></pre><h4 id="验证der编码和base64编码的区别"><a href="#验证der编码和base64编码的区别" class="headerlink" title="验证der编码和base64编码的区别"></a>验证der编码和base64编码的区别</h4><p>der编码 base64编码<br>直接导出证书 导出证书 -rfc<br>windows下可以点击证书复制到文件<br>相互转换:notepad++转base64</p><p>要在nginx中开启TLS,需要先生成两个文件,一个是代表公钥的服务证书文件(.pem),一个是私钥的文件(.key)。此处公钥可以直接用windows的证书工具导出,私钥需要用openssl导出。</p><pre><code class="hljs shell"><span class="hljs-meta">#</span><span class="bash"> 从密钥库中导出服务器私钥</span>openssl pkcs12 -in server.p12 -out server.key -nodes -nocerts</code></pre><pre><div class="caption"><span>config</span></div><code class="hljs nginx"><span class="hljs-section">server</span> { <span class="hljs-comment">#监听端口和域名 </span> <span class="hljs-attribute">listen</span> <span class="hljs-number">443</span> ssl; <span class="hljs-attribute">server_name</span> localhost; <span class="hljs-comment">#以下两个为证书文件 </span> <span class="hljs-attribute">ssl_certificate</span> D:/java/nginx-<span class="hljs-number">1</span>.<span class="hljs-number">14</span>.<span class="hljs-number">2</span>/cert/server-p.pem; <span class="hljs-attribute">ssl_certificate_key</span> D:/java/nginx-<span class="hljs-number">1</span>.<span class="hljs-number">14</span>.<span class="hljs-number">2</span>/cert/server.key; <span class="hljs-attribute">ssl_session_timeout</span> <span class="hljs-number">1m</span>; <span class="hljs-attribute">ssl_protocols</span> SSLv2 SSLv3 TLSv1.<span class="hljs-number">2</span>; <span class="hljs-attribute">ssl_ciphers</span> ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256:AES128-SHA:AES256-SHA:RC4-SHA:DES-CBC3-SHA:RC4-MD5; <span class="hljs-attribute">ssl_prefer_server_ciphers</span> <span class="hljs-literal">on</span>; <span class="hljs-comment">#location / { </span> <span class="hljs-comment">#root D:/nginx/portal; </span> <span class="hljs-comment">#index index.html; </span> <span class="hljs-comment">#}</span> <span class="hljs-attribute">localtion</span> / { <span class="hljs-attribute">proxy_pass</span> http://localhost:8899;<span class="hljs-attribute">proxy_set_header</span> X-Forwarded-For $remote_addr; } } <span class="hljs-comment"># 访问80端口时转发到443端口,转为https访问 </span> <span class="hljs-section">server</span> { <span class="hljs-attribute">listen</span> <span class="hljs-number">80</span>; <span class="hljs-attribute">server_name</span> localhost; <span class="hljs-attribute">return</span> <span class="hljs-number">301</span> https://$host$request_uri; }</code></pre><h5 id="ERR-CERT-COMMON-NAME-INVALID错误"><a href="#ERR-CERT-COMMON-NAME-INVALID错误" class="headerlink" title="ERR_CERT_COMMON_NAME_INVALID错误"></a>ERR_CERT_COMMON_NAME_INVALID错误</h5><p>域名和Common Name 一致,chrome依然报错,该问题如何解决?<br>原因在于浏览器校验域名并不是从CN入手,而是检查x509的扩展字段SAN(subject alter name),所以只有加上该扩展字段,才能让浏览器认为网站是安全的。</p><p>针对keytool来说,有两个阶段可以加上该扩展字段:</p><ol><li>生成密钥对时</li><li>签发证书时</li></ol><p>扩展字段的指定方法参考<a href="https://docs.oracle.com/javase/8/docs/technotes/tools/unix/keytool.html">keytool文档</a></p><p>如果是重新生成的证书未生效,需要清下浏览器缓存<br>chrome://net-internals/#hsts</p><p>若还是不行,看看是不是Nginx进程没清干净。</p><h4 id="待办"><a href="#待办" class="headerlink" title="待办"></a>待办</h4><ul><li>什么时候会进行数字证书认证</li><li>keytool -gencert -sigalg 有什么用</li></ul><h3 id="拓展"><a href="#拓展" class="headerlink" title="拓展"></a>拓展</h3><h4 id="如何理解证书链"><a href="#如何理解证书链" class="headerlink" title="如何理解证书链"></a>如何理解证书链</h4><h4 id="RSA-vs-DSA"><a href="#RSA-vs-DSA" class="headerlink" title="RSA vs DSA"></a>RSA vs DSA</h4><p>这两个算法都是对称密钥算法,其中</p><ul><li>名称问题<br>RSA三位发明者的名字的缩写<br>DSA则是 Digital Signature Algorithm 的缩写</li><li>数学基础</li><li>RSA基于大数分解(两个素数的乘积)</li><li>DSA基于离散对数难题<h4 id="指纹和签名的区别"><a href="#指纹和签名的区别" class="headerlink" title="指纹和签名的区别"></a>指纹和签名的区别</h4>指纹只是证书上的散列。主要用于人工接收,检查证书是否为预定证书,比如 打电话给 CA认证机构 并说出指纹进行核对。 浏览器是通过签名来验证证书的有效性的,浏览器不会关注指纹。<h4 id="数字证书的数据结构"><a href="#数字证书的数据结构" class="headerlink" title="数字证书的数据结构"></a>数字证书的数据结构</h4>CN(Common Name名字与姓氏)<br>OU(Organization Unit组织单位名称)<br>O(Organization组织名称)<br>L(Locality城市或区域名称)<br>ST(State州或省份名称)<br>C(Country国家名称)</li></ul><p><img src="/images/Pasted%20image%2020220123125851.png"><br>详细参考: <a href="https://www.cem.me/20150209-cert-binaries-4.html">https://www.cem.me/20150209-cert-binaries-4.html</a></p><ul><li>头(4byte)<ul><li>body(4byte + len)</li><li>signalg(2byte + len(13))</li><li>sign(5byte+ len)</li></ul></li></ul><p>以sha256签名为例<br>计算公式:len(body) = 总大小 - 4 - (2+13) - (5 + 256)</p><p>body = cert[4:-276]</p><h4 id="truststore和keystore的区别"><a href="#truststore和keystore的区别" class="headerlink" title="truststore和keystore的区别"></a>truststore和keystore的区别</h4><p>keystore用于存放自己的证书和对应私钥,通常里面的证书作为TLS端的身份。<br>truststore用于存放自己这端信任的带签名的证书。</p><p>JKS、PKCS12都既可以做keystore也可以做truststore</p><h4 id="CRL——证书吊销列表"><a href="#CRL——证书吊销列表" class="headerlink" title="CRL——证书吊销列表"></a>CRL——证书吊销列表</h4><p>证书超出有效期后会作废,用户也可以主动向 CA 申请撤销某证书文件,由于 CA 无法强制收回已经颁发出去的数字证书,因此为了实现证书的作废,往往还需要维护一个撤销证书列表(Certificate Revocation List,CRL),用于记录已经撤销的证书序号。</p><p> 因此,通常情况下,当第三方对某个证书进行验证时,需要首先检查该证书是否在撤销列表中。如果存在,则该证书无法通过验证。如果不在,则继续进行后续的证书验证过程。</p><p>值得注意的是: 目前有些 CA 颁发的证书和大部分自签SSL证书都没有提供吊销列表 (CRL) 服务或证书 吊销列表分发点是不可访问的 ,固然更别提 OSCP 服务,这是很是危险的,由于若是证书丢失或被盗而没法吊销的话,就极有可能被用于非法用途而让用户蒙受损失。</p><h4 id="秘钥、与ssh打通"><a href="#秘钥、与ssh打通" class="headerlink" title="秘钥、与ssh打通"></a>秘钥、与ssh打通</h4><h4 id="域名及CName-AAA、AAAA"><a href="#域名及CName-AAA、AAAA" class="headerlink" title="域名及CName AAA、AAAA"></a>域名及CName AAA、AAAA</h4><h4 id="PGP和GPG加签模式的区别"><a href="#PGP和GPG加签模式的区别" class="headerlink" title="PGP和GPG加签模式的区别"></a>PGP和GPG加签模式的区别</h4><h4 id="openssl验签流程"><a href="#openssl验签流程" class="headerlink" title="openssl验签流程"></a>openssl验签流程</h4><pre><code class="hljs shell"><span class="hljs-meta">#</span><span class="bash"> 转换 DER 到 PEM 格式</span><span class="hljs-meta">$</span><span class="bash"> openssl x509 -inform der -<span class="hljs-keyword">in</span> root.cer -out root.pem</span><span class="hljs-meta"></span><span class="hljs-meta">#</span><span class="bash"> 查看证书的签名,可以看到签名所使用的的 <span class="hljs-built_in">hash</span> 算法是 sha256</span><span class="hljs-meta">$</span><span class="bash"> openssl x509 -<span class="hljs-keyword">in</span> root.pem -text -noout -certopt ca_default -certopt no_validity -certopt no_serial -certopt no_subject -certopt no_extensions -certopt no_signame</span><span class="hljs-meta"></span><span class="hljs-meta">#</span><span class="bash"> 提取签名内容到文件中</span><span class="hljs-meta">$</span><span class="bash"> openssl x509 -<span class="hljs-keyword">in</span> root.pem -text -noout -certopt ca_default -certopt no_validity -certopt no_serial -certopt no_subject -certopt no_extensions -certopt no_signame | grep -v <span class="hljs-string">'Signature Algorithm'</span> | tr -d <span class="hljs-string">'[:space:]:'</span> | xxd -r -p > root-signature.bin</span><span class="hljs-meta"></span><span class="hljs-meta">#</span><span class="bash"> 提取根证书中含有的公钥</span><span class="hljs-meta">$</span><span class="bash"> openssl x509 -<span class="hljs-keyword">in</span> root.pem -noout -pubkey > root-pub.pem</span><span class="hljs-meta"></span><span class="hljs-meta">#</span><span class="bash"> 使用公钥解密签名</span><span class="hljs-meta">$</span><span class="bash"> openssl rsautl -verify -inkey root-pub.pem -<span class="hljs-keyword">in</span> root-signature.bin -pubin > root-signature-decrypted.bin</span><span class="hljs-meta"></span><span class="hljs-meta">#</span><span class="bash"> 查看解密后的内容</span><span class="hljs-meta">$</span><span class="bash"> openssl asn1parse -inform DER -<span class="hljs-keyword">in</span> root-signature-decrypted.bin</span><span class="hljs-meta"></span><span class="hljs-meta">#</span><span class="bash"> 接下来我们计算证书的 <span class="hljs-built_in">hash</span> 值</span><span class="hljs-meta"></span><span class="hljs-meta">#</span><span class="bash"> 首先提取证书的 body</span><span class="hljs-meta">#</span><span class="bash"> 因为证书中含有签名,签名是不包含在 <span class="hljs-built_in">hash</span> 值计算中的</span><span class="hljs-meta">#</span><span class="bash"> 所以不能简单地对整个证书文件进行 <span class="hljs-built_in">hash</span> 运算</span><span class="hljs-meta">$</span><span class="bash"> openssl asn1parse -<span class="hljs-keyword">in</span> root.pem -strparse 4 -out root-body.bin &> /dev/null</span><span class="hljs-meta"></span><span class="hljs-meta">#</span><span class="bash"> 计算 sha1 哈希值</span><span class="hljs-meta">$</span><span class="bash"> openssl dgst -sha256 root-body.bin</span><span class="hljs-meta">#</span><span class="bash">SHA1(root-body.bin)= xxx</span><span class="hljs-meta"></span><span class="hljs-meta"></span><span class="hljs-meta"></span><span class="hljs-meta">#</span><span class="bash"> linux 下验证网站证书完整流程</span><span class="hljs-meta"></span><span class="hljs-meta">#</span><span class="bash"> 新建一个文件夹 github 保存所有的文件</span><span class="hljs-meta">$</span><span class="bash"> mkdir github && <span class="hljs-built_in">cd</span> github</span><span class="hljs-meta"></span><span class="hljs-meta">#</span><span class="bash"> 首先,我们下载 github.com 发送的证书</span><span class="hljs-meta">$</span><span class="bash"> openssl s_client -connect github.com:443 -showcerts 2>/dev/null </dev/null | sed -n <span class="hljs-string">'/-----BEGIN/,/-----END/p'</span> > github.com.crt</span><span class="hljs-meta"></span><span class="hljs-meta">#</span><span class="bash"> github.com.crt 是 PEM 格式的文本文件</span><span class="hljs-meta">#</span><span class="bash"> 打开可以发现里面有两段 -----BEGIN CERTIFICATE----</span><span class="hljs-meta">#</span><span class="bash"> 这说明有两个证书,也就是 github.com 把中间证书也一并发过来了</span><span class="hljs-meta"></span><span class="hljs-meta">#</span><span class="bash"> 接下来我们把两个证书提取出来</span><span class="hljs-meta">$</span><span class="bash"> awk <span class="hljs-string">'/BEGIN/,/END/{ if(/BEGIN/){a++}; out="cert"a".tmpcrt"; print >out}'</span> < github.com.crt && <span class="hljs-keyword">for</span> cert <span class="hljs-keyword">in</span> *.tmpcrt; <span class="hljs-keyword">do</span> newname=$(openssl x509 -noout -subject -<span class="hljs-keyword">in</span> <span class="hljs-variable">$cert</span> | sed -n <span class="hljs-string">'s/^.*CN=\(.*\)$/\1/; s/[ ,.*]/_/g; s/__/_/g; s/^_//g;p'</span>).pem; mv <span class="hljs-variable">$cert</span> <span class="hljs-variable">$newname</span>; <span class="hljs-keyword">done</span></span><span class="hljs-meta"></span><span class="hljs-meta">#</span><span class="bash"> 我们得到了两个证书文件</span><span class="hljs-meta">#</span><span class="bash"> github_com.pem 和 DigiCert_SHA2_High_Assurance_Server_CA.pem</span><span class="hljs-meta"></span><span class="hljs-meta">#</span><span class="bash"> 首先,验证 github_com.pem 证书确实</span><span class="hljs-meta">#</span><span class="bash"> 是由 DigiCert_SHA2_High_Assurance_Server_CA.pem 签发的</span><span class="hljs-meta"></span><span class="hljs-meta">#</span><span class="bash"> 提取 DigiCert_SHA2_High_Assurance_Server_CA 的公钥</span><span class="hljs-meta">#</span><span class="bash"> 命名为 issuer-pub.pem</span><span class="hljs-meta">$</span><span class="bash"> openssl x509 -<span class="hljs-keyword">in</span> DigiCert_SHA2_High_Assurance_Server_CA.pem -noout -pubkey > issuer-pub.pem</span><span class="hljs-meta"></span><span class="hljs-meta">#</span><span class="bash"> 查看 github_com.pem 的签名</span><span class="hljs-meta">#</span><span class="bash"> 可以看到 <span class="hljs-built_in">hash</span> 算法是 sha256</span><span class="hljs-meta">$</span><span class="bash"> openssl x509 -<span class="hljs-keyword">in</span> github_com.pem -text -noout -certopt ca_default -certopt no_validity -certopt no_serial -certopt no_subject -certopt no_extensions -certopt no_signame</span><span class="hljs-meta"></span><span class="hljs-meta">#</span><span class="bash"> 提取签名到文件中</span><span class="hljs-meta">$</span><span class="bash"> openssl x509 -<span class="hljs-keyword">in</span> github_com.pem -text -noout -certopt ca_default -certopt no_validity -certopt no_serial -certopt no_subject -certopt no_extensions -certopt no_signame | grep -v <span class="hljs-string">'Signature Algorithm'</span> | tr -d <span class="hljs-string">'[:space:]:'</span> | xxd -r -p > github_com-signature.bin</span><span class="hljs-meta"></span><span class="hljs-meta">#</span><span class="bash"> 使用上级证书的公钥解密签名</span><span class="hljs-meta">$</span><span class="bash"> openssl rsautl -verify -inkey issuer-pub.pem -<span class="hljs-keyword">in</span> github_com-signature.bin -pubin > github_com-signature-decrypted.bin</span><span class="hljs-meta"></span><span class="hljs-meta">#</span><span class="bash"> 查看解密后的信息</span><span class="hljs-meta">$</span><span class="bash"> openssl asn1parse -inform DER -<span class="hljs-keyword">in</span> github_com-signature-decrypted.bin</span><span class="hljs-meta"></span><span class="hljs-meta">#</span><span class="bash"> 接下来计算 github_com.pem 的 <span class="hljs-built_in">hash</span> 值</span><span class="hljs-meta"></span><span class="hljs-meta">#</span><span class="bash"> 提取证书的 body 部分</span><span class="hljs-meta">$</span><span class="bash"> openssl asn1parse -<span class="hljs-keyword">in</span> github_com.pem -strparse 4 -out github_com-body.bin &> /dev/null</span><span class="hljs-meta"></span><span class="hljs-meta">#</span><span class="bash"> 计算 <span class="hljs-built_in">hash</span> 值</span><span class="hljs-meta">$</span><span class="bash"> openssl dgst -sha256 github_com-body.bin</span><span class="hljs-meta"></span><span class="hljs-meta">#</span><span class="bash"> <span class="hljs-built_in">hash</span> 值匹配,我们成功校验了 github.pem 这个证书确实是由 DigiCert_SHA2_High_Assurance_Server_CA.pem 这个证书来签发的。</span>上面的流程比较繁琐,其实也可以直接让 openssl 来帮我们验证。<span class="hljs-meta">$</span><span class="bash"> openssl dgst -sha256 -verify issuer-pub.pem -signature github_com-signature.bin github_com-body.bin</span><span class="hljs-meta"></span><span class="hljs-meta">#</span><span class="bash"> 获取上级证书的名字</span><span class="hljs-meta">$</span><span class="bash"> openssl x509 -<span class="hljs-keyword">in</span> DigiCert_SHA2_High_Assurance_Server_CA.pem -text -noout | grep Issuer:</span> Issuer: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert High Assurance EV Root CA</code></pre><h3 id="参考文献:"><a href="#参考文献:" class="headerlink" title="参考文献:"></a>参考文献:</h3><blockquote><p>[序号]主要责任者.电子文献题名[电子文献及载体类型标识].电子文献的出版或获得地址,发表更新日期/引用日期.</p></blockquote><blockquote><p>例如:[12]王明亮.关于中国学术期刊标准化数据库系统工程的进展[EB/OL].1998-08-16/1998-10-01.</p></blockquote><blockquote><p>[8]万锦.中国大学学报文摘(1983-1993).英文版[DB/CD].北京:<a href="https://baike.baidu.com/item/%E4%B8%AD%E5%9B%BD%E5%A4%A7%E7%99%BE%E7%A7%91%E5%85%A8%E4%B9%A6%E5%87%BA%E7%89%88%E7%A4%BE/2267934">中国大百科全书出版社</a>,1996.</p></blockquote><ul><li><p>[1]梁栋.java加密与解密的艺术(第二版)</p></li><li><p>[2]2018-2019年中国网络可信身份服务发展蓝皮书</p></li><li><p>[3]贾铁军.网络安全技术与应用(第三版)</p></li><li><p>[4]王绍斌.云计算安全事件:从入门到精通</p></li><li><p>[5]韩立刚.深入浅出计算机网络</p></li><li><p>[6]汪德嘉.身份危机</p></li><li><p><a href="https://zh.wikipedia.org/wiki/%E6%95%B0%E5%AD%97%E8%BA%AB%E4%BB%BD">数字身份 - 维基百科,自由的百科全书 (wikipedia.org)</a></p></li><li><p><a href="http://kapsterio.github.io/test/2021/09/17/TLS.html">TLS协议分析(密码学101以及TLS协议简介) · kaspterio (kapsterio.github.io)</a></p></li><li><p><a href="https://houbb.github.io/2018/09/26/SSL-TLS">SSL/TSL | Echo Blog (houbb.github.io)</a></p></li><li><p><a href="https://www.linuxidc.com/Linux/2015-07/120230.htm">Https SSL/TLS PreMaster/Master Secret(Key)计算_服务器应用_Linux公社-Linux系统门户网站 (linuxidc.com)</a></p></li><li><p><a href="https://dev.to/wayofthepie/structure-of-an-SSL-x-509-certificate-16b">dev.to</a></p></li><li><p><a href="https://bbs.huaweicloud.com/blogs/detail/273779">为了搞懂 HTTPS,我把大学的数学书拿了出来。。。-云社区-华为云 (huaweicloud.com)</a></p></li><li><p><a href="https://www.cnblogs.com/bonelee/p/12931388.html">DSA与RSA——DSA 只能用于数字签名,而无法用于加密(某些扩展可以支持加密);RSA 即可作为数字签名,也可以作为加密算法。在业界支持方面,RSA 具有更为广泛的部署与支持。 - bonelee - 博客园 (cnblogs.com)</a></p></li><li><p><a href="https://www.cnblogs.com/leslies2/p/7442956.html">数字证书应用综合揭秘(包括证书生成、加密、解密、签名、验签) - 风尘浪子 - 博客园 (cnblogs.com)</a></p></li><li><p><a href="https://juejin.cn/post/6930446060846481416">https://juejin.cn/post/6930446060846481416</a></p></li><li><p><a href="https://stackoverflow.com/questions/37041254/ssl-certificate-signature-algorithm-shows-sha256rsa-but-thumbprint-algorithm">security - SSL Certificate: Signature Algorithm shows “sha256rsa” but thumbprint algorithm shows “sha1” - Stack Overflow</a></p></li><li><p><a href="https://cjting.me/2021/03/02/how-to-validate-tls-certificate/">https://cjting.me/2021/03/02/how-to-validate-tls-certificate/</a></p></li><li><p><a href="https://www.ibm.com/docs/en/zosconnect/3.0?topic=ee-keystores-truststores">Keystores and truststores - IBM Documentation</a></p></li><li><p><a href="https://support.huaweicloud.com/scm_faq/scm_01_0128.html">如何将证书格式转换为PEM格式?_SSL证书管理 SCM_常见问题_其他_证书管理类_华为云 (huaweicloud.com)</a></p></li><li><p><a href="https://stackoverflow.com/questions/43665243/invalid-self-signed-ssl-cert-subject-alternative-name-missing">https://stackoverflow.com/questions/43665243/invalid-self-signed-ssl-cert-subject-alternative-name-missing</a></p></li><li><p><a href="https://docs.oracle.com/javase/8/docs/technotes/tools/unix/keytool.html">https://docs.oracle.com/javase/8/docs/technotes/tools/unix/keytool.html</a></p></li><li><p><a href="https://www.rfc-editor.org/rfc/rfc5246">RFC 5246: The Transport Layer Security (TLS) Protocol Version 1.2 (rfc-editor.org)</a></p></li></ul>]]></content>
<categories>
<category> 密码学 </category>
</categories>
<tags>
<tag> 数字证书 </tag>
<tag> 密码学 </tag>
</tags>
</entry>
<entry>
<title>ZeroTier 梳理</title>
<link href="/2022/01/04/ZeroTier%E6%A2%B3%E7%90%86/"/>
<url>/2022/01/04/ZeroTier%E6%A2%B3%E7%90%86/</url>
<content type="html"><![CDATA[<p>是一款 p2p vpn</p><p>p2p 的原理是什么</p><ol><li>中继(原始方案):两个客户端主动连接公网服务器,由服务器中继通信</li><li>逆向链接(改良版):要求其中一个客户端(假设是B)是公网ip,且有公网服务器做中继,B先通过服务器向A中继一个连接请求,然后A主动跟B建立连接。</li><li>UDP打洞(当前最优):两个客户端都在NAT之后,有一台公网服务器,客户端A向B发送请求,同时要求服务器中继一个“B到A的连接请求”给B,此时两边都会导致NAT打开一个自身内网到对方外网的通信,之后双方内网就可以相互通信了。该模式适用于锥形NAT。如果有多级NAT,则要求NAT得支持回环传输(loopback transmission)。<br><a href="https://zhuanlan.zhihu.com/p/26796476">https://zhuanlan.zhihu.com/p/26796476</a><br><a href="https://evilpan.com/2015/10/31/p2p-over-middle-box/">https://evilpan.com/2015/10/31/p2p-over-middle-box/</a><br>vpn 是什么<br>vpn全称visual private network,最早是公司开发给员工远程连接内网用的,此处体现了第一个用处就是代理网络请求,其次它目的是防止公司信息泄漏给第三方,所以vpn被设计成能够加密通信。所以它具备以下特点:1、提高上网安全性;2、隐藏上网者身份;3、突破网站的地域限制</li></ol><p>NAT是什么<br>NAT即网络地址转换器,NAT不止检查进入数据包的头部,而且对其进行修改,从而实现同一内网中不同主机共用更少的公网IP(通常是一个).</p><p>隧道是什么</p><p>STUN是什么</p><p>STUN和TUN的区别</p>]]></content>
<tags>
<tag> vpn </tag>
</tags>
</entry>
<entry>
<title>mysql,GROUP_CONCAT,341长度疑案</title>
<link href="/2021/12/15/mysql%EF%BC%8CGROUP_CONCAT%EF%BC%8C341%E9%95%BF%E5%BA%A6%E7%96%91%E6%A1%88/"/>
<url>/2021/12/15/mysql%EF%BC%8CGROUP_CONCAT%EF%BC%8C341%E9%95%BF%E5%BA%A6%E7%96%91%E6%A1%88/</url>
<content type="html"><![CDATA[<h3 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h3><p>今天被同事找来讨论一个数据库问题,一个select语句带有聚合函数<code>GROUP_CONCAT(countryCode)</code>,countryCode是纯数字,所以这一列出来的结果应该是这样的:<code>24,25,1003,1004,1025,......</code>,但是实际上结果被截断了,把结果copy出来到notepad++,发现每一条记录的长度都是<code>341</code>。</p><p>经过一番查询,发现mysql对这个函数有个配置:<code>group_concat_max_len</code>,指的是对<code>GROUP_CONCAT()</code>函数的结果进行截断。比如<code>group_concat_max_len=1024</code>,那么mysql会对该函数的结果截取前1024个字节(Byte)。看起来就是它了。</p><p>通过以下命令,可以查看该库对应的配置:</p><pre><code class="hljs sql"><span class="hljs-keyword">show</span> variables <span class="hljs-keyword">like</span> <span class="hljs-string">'group_concat_max_len'</span><span class="hljs-operator">>></span> <span class="hljs-number">1024</span></code></pre><p>可见我们的库也是1024,那为啥实际结果长度只有<code>341</code>呢。据回答的小哥说:当<code>GROUP_CONCAT()</code> 和 <code>Order By</code>一起使用的时候,结果就会进一步裁剪到1/3,如果去掉<code>Order By</code>条件,结果长度会是<code>1024</code>。但是这个规律是实践出来的,具体为啥会这样,官方手册里并没有说。</p><h3 id="验证"><a href="#验证" class="headerlink" title="验证"></a>验证</h3><p>为了验证思路,可参考以下sql进行配置</p><pre><code class="hljs sql"># SESSION 表示变动只影响当前会话<span class="hljs-keyword">SET</span> SESSION group_concat_max_len<span class="hljs-operator">=</span><span class="hljs-number">102400</span>;</code></pre><p>配置后验证结果长度确实不会被截断了。</p><h3 id="修复"><a href="#修复" class="headerlink" title="修复"></a>修复</h3><p>由于我们只在某一个环境中才复现了这个问题,别的环境都没问题,所以思路就是参考别的环境的配置,经查询发现其他环境的值都是<code>group_concat_max_len=8192</code>,所以其他环境没有这个问题,遂对齐配置即可。</p><pre><code class="hljs sql"># <span class="hljs-keyword">GLOBAL</span> 表示变动影响全局<span class="hljs-keyword">SET</span> <span class="hljs-keyword">GLOBAL</span> group_concat_max_len<span class="hljs-operator">=</span>your_value_here;</code></pre><p>如果严谨一点,应该需要排查项目里所有用到<code>GROUP_CONCAT()</code>的地方,然后结合业务评估一下合理的大小,再找DBA协商进行变更。所幸我们生产环境配的是<code>102400</code>,足够大了,也就没这种顾虑了。</p><hr><p>参考:</p><ul><li><a href="https://stackoverflow.com/questions/12001919/mysql-truncates-concatenated-result-of-a-group-concat-function">https://stackoverflow.com/questions/12001919/mysql-truncates-concatenated-result-of-a-group-concat-function</a></li><li><a href="https://stackoverflow.com/questions/4469474/mysql-truncating-of-result-when-using-group-concat-and-concat/37979180#37979180">https://stackoverflow.com/questions/4469474/mysql-truncating-of-result-when-using-group-concat-and-concat/37979180#37979180</a></li></ul>]]></content>
<tags>
<tag> mysql </tag>
</tags>
</entry>
<entry>
<title>阅读文章自动生成大纲浏览器插件</title>
<link href="/2021/12/12/%E9%98%85%E8%AF%BB%E6%96%87%E7%AB%A0%E8%87%AA%E5%8A%A8%E7%94%9F%E6%88%90%E5%A4%A7%E7%BA%B2%E6%B5%8F%E8%A7%88%E5%99%A8%E6%8F%92%E4%BB%B6/"/>
<url>/2021/12/12/%E9%98%85%E8%AF%BB%E6%96%87%E7%AB%A0%E8%87%AA%E5%8A%A8%E7%94%9F%E6%88%90%E5%A4%A7%E7%BA%B2%E6%B5%8F%E8%A7%88%E5%99%A8%E6%8F%92%E4%BB%B6/</url>
<content type="html"><![CDATA[<p>有段时间读文章发现没有大纲,阅读不顺畅,针对那些没有大纲的页面,手撸了一个自动生成大纲的浏览器脚本。基本上实现了大部分想要的功能,在此记录分享。</p><h3 id="使用方法"><a href="#使用方法" class="headerlink" title="使用方法"></a>使用方法</h3><ol><li>可以导入油猴里,打开脚本开关即可生效。</li><li>使用浏览器的dev-tools来跑。</li></ol><h3 id="效果"><a href="#效果" class="headerlink" title="效果"></a>效果</h3><p><img src="/images/outlineTool.gif"></p><p><i class="fa fa-file-code-o" aria-hidden="true"></i><a href="/data/outlineTool.user.js">文章大纲生成插件</a></p>]]></content>
<tags>
<tag> javascript </tag>
<tag> 效率工具 </tag>
<tag> 原创 </tag>
</tags>
</entry>
<entry>
<title>js模块化简述</title>
<link href="/2021/12/09/js%E6%A8%A1%E5%9D%97%E5%8C%96%E7%AE%80%E8%BF%B0/"/>
<url>/2021/12/09/js%E6%A8%A1%E5%9D%97%E5%8C%96%E7%AE%80%E8%BF%B0/</url>
<content type="html"><![CDATA[<p>本文是阅读《mastering-modular-javascript》的心得笔记,该书成书于2018年,对当前JS技术的描述有较新的时效性。同时其模块化思想比较实用,可以应用到任何语言</p><h2 id="历史简述"><a href="#历史简述" class="headerlink" title="历史简述"></a>历史简述</h2><h3 id="scriptTag和闭包"><a href="#scriptTag和闭包" class="headerlink" title="scriptTag和闭包"></a>scriptTag和闭包</h3><p>原生js提供使用<code><script></code>tag的方式引入,这样一个页面会看到一大坨的第三方库以<code><script></code>的方式引入。但是这种引入天生是有缺陷的,引入的函数和变量都会挂载到window下成为全局对象。这样导致一个长期存在的毛病就是各个包之间定义的变量会相互覆盖,稍有差池就会让页面渲染失败。</p><p>之后官方推出了IIFE的特性(也就是闭包),各个库的代码以闭包的方式包裹,这样它们各自定义的变量的作用域都是在闭包范围之内的,也就解决了全部暴露到全局的问题。</p><p>但是这种方式弊病在于它不会显式的声明依赖的库,这样就需要手动精细调整各个<code><script></code>的顺序,以达到被依赖的库优先被引入的问题,</p><p>闭包的几种写法:</p><pre><code class="hljs javascript">(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{ <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'IIFE using parenthesis'</span>)})()~<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{ <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'IIFE using a bitwise operator'</span>)}()<span class="hljs-keyword">void</span> <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{ <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'IIFE using the void operator'</span>)}()</code></pre><blockquote><p><mastering-modular-javascript> chapter1</p></blockquote><h3 id="RequireJs,AngularJs,和依赖注入"><a href="#RequireJs,AngularJs,和依赖注入" class="headerlink" title="RequireJs,AngularJs,和依赖注入"></a>RequireJs,AngularJs,和依赖注入</h3><p>上节描述的问题一直都是让前端开发人员头疼的问题,直到模块化框架RequireJs以及AngularJS中依赖注入机制的降临。</p><h4 id="RequireJs"><a href="#RequireJs" class="headerlink" title="RequireJs"></a>RequireJs</h4><p>通过如下方式暴露接口,define()是RequireJs框架暴露到全局的函数<br>第一个参数的数组元素声明了该模块的路径,第二个参数意思是通过把函数传入回调接口,返回该路径对应的接口</p><pre><code class="hljs javascript">define([<span class="hljs-string">'mathlib/sum'</span>], <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">sum</span>) </span>{ <span class="hljs-keyword">return</span> { sum }})</code></pre><p>通过如下方式调用依赖项,require()跟define()一样,<br>第一个参数声明了需要引入的依赖路径,可以引入多个依赖<br>这些依赖会按顺序放到第二个回调函数的参数中,提供使用。</p><pre><code class="hljs javascript"><span class="hljs-built_in">require</span>([<span class="hljs-string">'mathlib'</span>], <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">mathlib</span>) </span>{ mathlib.sum(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>) <span class="hljs-comment">// <- 6</span>})</code></pre><p>requireJS也有它的问题,首先整个模式围绕着它的异步请求能力,这导致了每个引用点都要发起网络请求获取对应的模块,这会导致一个页面发起瀑布式的上百个加载模块的网络请求,这无疑是性能很差的一件事。为此,需要引入一个构建工具,把所有模块整合成一个脚本,包含冗长的依赖链、require函数、和回调参数</p><h4 id="AngularJS"><a href="#AngularJS" class="headerlink" title="AngularJS"></a>AngularJS</h4><p>AngularJS中的依赖注入系统也遇到了许多相同的问题。它通过解析参数名来解决依赖问题。但是这导致了进行代码混淆时,参数名被改变而依赖失败的问题。在AngularJS v1后期,引入了一个构建任务来解决这个问题,它会进行如下的代码转换:</p><pre><code class="hljs javascript"><span class="hljs-comment">//转换前</span><span class="hljs-built_in">module</span>.factory(<span class="hljs-string">'calculator'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">mathlib【<-通过该参数名来引入依赖】</span>) </span>{ <span class="hljs-comment">// …</span>})</code></pre><pre><code class="hljs javascript"><span class="hljs-comment">//转换后</span><span class="hljs-built_in">module</span>.factory(<span class="hljs-string">'calculator'</span>, [<span class="hljs-string">'mathlib'</span>【<-显式的指明了依赖的模块名称,像RequireJs一样】, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">mathlib</span>) </span>{ <span class="hljs-comment">// …</span>}])</code></pre><blockquote><p>不用说,引入这个鲜为人知的构建工具的延迟,加上过度设计的方面,有一个额外的构建步骤来解开不应该被破坏的东西,我无论如何都不鼓励使用一种能带来如此微不足道的好处的模式。开发人员大多选择坚持使用熟悉的类似RequireJS的硬编码依赖数组格式。——<mastering-modular-javascript></p></blockquote><h3 id="Node-js和CommonJS的降临和Browserify"><a href="#Node-js和CommonJS的降临和Browserify" class="headerlink" title="Node.js和CommonJS的降临和Browserify"></a>Node.js和CommonJS的降临和Browserify</h3><p>在Node.js被称赞的众多创新中,一个是CommonJS模块系统——简称CJS。利用Node.js程序可以访问文件系统的能力,CommonJS标准更符合传统的模块加载机制。在CommonJS中,每个文件都是一个具有自己作用域和上下文的模块。依赖关系是使用同步<code>require</code>函数加载的,该函数可以在模块生命周期中的任何时候动态调用,如下一个代码段所示。</p><pre><code class="hljs javascript"><span class="hljs-keyword">const</span> mathlib = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./mathlib'</span>)</code></pre><p>可见,与RequireJs、AngularJS一样,都是使用路径来指定模块,而不同之处在于曾经的样板函数、依赖数组已经不复存在。像前两者,它们可以在一个文件里定义多个模块的接口,但是CJS相对比较严格,它约定一个模块就是一个文件,通过<code>module.exports</code>就能暴露该模块的接口。其实这样的好处也显而易见,这样可以让开发者更加清晰的了解CJS模块的层次结构,也便于IDE去解析。</p><p>由于CJS是提供给NodeJs用的,浏览器引擎并没有能力去引入依赖,这个时候就要靠<code>Browserify</code>,来把所有的依赖打包到供浏览器使用的捆绑包中。</p><h3 id="ES6-import-Babel-and-Webpack"><a href="#ES6-import-Babel-and-Webpack" class="headerlink" title="ES6, import, Babel, and Webpack"></a>ES6, <code>import</code>, Babel, and Webpack</h3><h4 id="Babel"><a href="#Babel" class="headerlink" title="Babel"></a>Babel</h4><p>ECMA标准组织一直都在持续的采纳和计划新的语法标准。很多时候一些很实用的语法已经提上议程,但是待各大浏览器对标准进行实现,开发者还是得苦苦的等待一段漫长的事间。Babel的出现就是为了解决这个问题,它是一个语法转换工具,能够把ES6语法的代码,转换成当前浏览器都能兼容的ES5语法,解决了很多开发想要尝试新的语法特性的燃眉之急</p><p>随着ES6在2015年6月标准化,以及Babel在那之前很久就一直有将ES6转换为ES5,一场新的革命正在迅速临近。ES6规范包括JavaScript原生的模块语法,通常被称为ECMAScript模块(ESM)。</p><h4 id="ES6"><a href="#ES6" class="headerlink" title="ES6"></a>ES6</h4><p>ESM在很大程度上受到CJS及其前身的影响,提供了静态声明性API以及基于承诺的动态可编程API,如下所示。</p><pre><code class="hljs javascript"><span class="hljs-keyword">import</span> mathlib <span class="hljs-keyword">from</span> <span class="hljs-string">'./mathlib'</span><span class="hljs-keyword">import</span>(<span class="hljs-string">'./mathlib'</span>).then(<span class="hljs-function"><span class="hljs-params">mathlib</span> =></span> { <span class="hljs-comment">// …</span>})</code></pre><p>同CJS一样,ESM约定一个文件就是一个模块。而优秀于CJS的地方是ESM能引用静态依赖,静态依赖意味着可以无需运行代码就能被工具检测出相关的依赖,我猜应该是能够便于编辑工具进行依赖解析吧,对书本原文说的“内省”不是很理解。(参考:<a href="https://stackoverflow.com/questions/52965907/what-is-the-meaning-of-static-import-in-es6">javascript - what is the meaning of static import in ES6? - Stack Overflow</a>)。</p><blockquote><p>Static imports vastly improve the introspection capabilities of module systems, given they can be analyzed statically and lexically extracted from the abstract syntax tree (AST) of each module in the system. ——<mastering-modular-javascript></p></blockquote><p>另外ESM比CJS更强的一个地方在于它可以指定加载模块的异步完成事件(<code>.then()</code>)。这让模块加载的动作变得更加灵活和更多可能性。</p><h4 id="Webpack"><a href="#Webpack" class="headerlink" title="Webpack"></a>Webpack</h4><p>Webpack是browserify的接班人。</p><hr><p>综上所述,由于ESM纯正血统以及优异表现,毫无疑问ESM将在未来的几年内,将会接管整个JS模块化生态。</p><p>参考文献:<a href="https://github.com/mjavascript/mastering-modular-javascript">GitHub - mjavascript/mastering-modular-javascript: 📦 Module thinking, principles, design patterns and best practices.</a></p><h2 id="模块化原则"><a href="#模块化原则" class="headerlink" title="模块化原则"></a>模块化原则</h2><h3 id="模块化精要"><a href="#模块化精要" class="headerlink" title="模块化精要"></a>模块化精要</h3><ol><li>单一职责原则</li><li>API优先原则</li><li>不暴露非必要的方法或变量(public 要有度,能private的尽量private)<ol><li>从使用者角度去考虑暴露的粒度</li></ol></li><li>找到合适的抽象(最好就是在第二次出现重复的时候进行重构)</li><li>状态管理<ol><li>模块化设计的一个目标就是让状态最小化,不要让功能内部存在太多可能性</li><li>把状态树砍成更好管理的状态树分支,每个分支都是状态树的一个子集</li><li>纯粹的函数不应该对其它地方有影响,下面是例子 <pre><code class="hljs javascript"><span class="hljs-comment">// 纯粹函数</span><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sum</span>(<span class="hljs-params">numbers</span>) </span>{<span class="hljs-keyword">return</span> numbers.reduce(<span class="hljs-function">(<span class="hljs-params">a, b</span>) =></span> a + b, <span class="hljs-number">0</span>)}<span class="hljs-comment">// 非纯粹函数,每次调用的结果可能被其他调用影响</span><span class="hljs-keyword">let</span> count = <span class="hljs-number">0</span><span class="hljs-keyword">const</span> increment = <span class="hljs-function">() =></span> count++<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> increment</code></pre></li><li>可以通过工厂来封装可能暴露给外部的状态,来减少状态的熵值 <pre><code class="hljs javascript"><span class="hljs-keyword">const</span> factory = <span class="hljs-function">() =></span> { <span class="hljs-keyword">let</span> count = <span class="hljs-number">0</span> <span class="hljs-keyword">const</span> increment = <span class="hljs-function">() =></span> count++ <span class="hljs-keyword">return</span> increment}<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> factory</code></pre></li></ol></li></ol><h3 id="CRUST-Consistent-Resilient-Unambiguous-Simple-and-Tiny"><a href="#CRUST-Consistent-Resilient-Unambiguous-Simple-and-Tiny" class="headerlink" title="CRUST: Consistent, Resilient, Unambiguous, Simple and Tiny"></a>CRUST: Consistent, Resilient, Unambiguous, Simple and Tiny</h3><ol><li>Consistent:一个接口只要输入一样,无论执行多少次,输出都应该一样</li><li>Resilient:接口应能够灵活的指定参数,包括可选参数和重载</li><li>Unambiguous:接口应该是明确的,不存在多种用法、多套不同业务意义的入参、多套不同解释的返回值</li><li>Simple:接口应该保持简单,它可以以少配置甚至无配置,执行常规的用例,同时也能允许用户通过传递自定义配置执行更高级的用例</li><li>Tiny:它应该是精简的,保持高扩展性</li></ol>]]></content>
<categories>
<category> js系列 </category>
</categories>
<tags>
<tag> javascript </tag>
<tag> modular </tag>
<tag> 模块化 </tag>
</tags>
</entry>
<entry>
<title>DDD——驱动领域设计</title>
<link href="/2021/11/26/DDD%E2%80%94%E2%80%94%E9%A9%B1%E5%8A%A8%E9%A2%86%E5%9F%9F%E8%AE%BE%E8%AE%A1/"/>
<url>/2021/11/26/DDD%E2%80%94%E2%80%94%E9%A9%B1%E5%8A%A8%E9%A2%86%E5%9F%9F%E8%AE%BE%E8%AE%A1/</url>
<content type="html"><![CDATA[<h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><h3 id="如何应对复杂软件开发?以unix为例"><a href="#如何应对复杂软件开发?以unix为例" class="headerlink" title="如何应对复杂软件开发?以unix为例"></a>如何应对复杂软件开发?以unix为例</h3><ol><li>封装与抽象</li><li>分层与模块化</li><li>基于接口通信</li><li>为扩展而设计</li></ol><blockquote><p>linux运作漫画图)理论太干,辅以图片说明,同时让听众思考buffer</p></blockquote><h3 id="DDD介绍"><a href="#DDD介绍" class="headerlink" title="DDD介绍"></a>DDD介绍</h3><ul><li>历史</li><li>简介<ul><li> 一种思维模式</li><li> 本质是方法论</li><li> by experience</li></ul></li></ul><h4 id="由数据模型驱动设计-引入"><a href="#由数据模型驱动设计-引入" class="headerlink" title="由数据模型驱动设计 引入"></a>由数据模型驱动设计 引入</h4><p>一门语言的基本语法和编程技巧、一个ORM框架的使用方法及基本的sql编写能力——就这三板斧,足以!</p><p>这种设计方式的弊端:mvc模式</p><ul><li>缺乏边界</li><li>贫血模型,transactional scripts</li><li>由贫血症导致的失忆症(即业务代码意图不明</li><li>跟数据库表定义强耦合</li><li>缺乏对领域模型的思考和建模</li></ul><h3 id="正式引入DDD"><a href="#正式引入DDD" class="headerlink" title="正式引入DDD"></a>正式引入DDD</h3><h4 id="怎么玩?"><a href="#怎么玩?" class="headerlink" title="怎么玩?"></a>怎么玩?</h4><ol><li>团队+领域专家讨论,统一语言</li><li>战略设计:划分界限上下文</li><li>战术设计:界限上下文落地</li><li>根据实际情况迭代、重构界限上下文,往复循环</li></ol><h4 id="对比数据模型驱动模式"><a href="#对比数据模型驱动模式" class="headerlink" title="对比数据模型驱动模式"></a>对比数据模型驱动模式</h4><p>虚拟钱包案例</p><p><strong>mvc模型</strong></p><ul><li>贫血模型,不够面向对象</li><li>业务类命名太泛,缺乏边界,职责模糊,后续很容易被腐化</li><li>数据库表模型散落到业务层,灵活性差</li></ul><p><strong>ddd建模</strong></p><ul><li>充血模型,更加面向对象</li><li>使用门面模式,屏蔽数据库操作细节</li><li>service层变逻辑变简单,领域逻辑交给领域层做,service层只负责编排</li></ul><h4 id="使用原则"><a href="#使用原则" class="headerlink" title="使用原则"></a>使用原则</h4><ul><li>ddd非银弹,不应滥用</li><li>设计系统时,多考虑墨菲定律</li><li>划分系统时,多考虑康威定律</li></ul><h4 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h4><ul><li>数据驱动设计有诸多弊端</li><li>DDD天生为控制大型软件的复杂度而生</li></ul><h3 id="领域建模流程"><a href="#领域建模流程" class="headerlink" title="领域建模流程"></a>领域建模流程</h3><ol><li>事件风暴。一起头脑风暴,完善用例图</li><li>抽象。提炼出系统的主要行为</li><li>划分界限上下文。主要区分好核心域、支撑域、公共域</li><li>界限上下文映射。主要区分好各个上下文的上下游关系</li><li>翻译为代码。一个上下文一个module</li></ol><h3 id="DDD的基本概念"><a href="#DDD的基本概念" class="headerlink" title="DDD的基本概念"></a>DDD的基本概念</h3><ul><li>领域、子域</li><li>界限上下文、界限上下文映射</li><li>实体、值对象</li><li>聚合、聚合根</li><li>领域服务</li><li>领域事件</li><li>工厂</li><li>资源库</li></ul><h4 id="领域、子域"><a href="#领域、子域" class="headerlink" title="领域、子域"></a>领域、子域</h4><p>在领域部不断划分的过程中,领域会细分成不同的子域:核心域、通用域、支撑域</p><p>核心域如何区分?桃树例子。<strong>界限上下文关心的就是核心域</strong></p><h4 id="界限上下文"><a href="#界限上下文" class="headerlink" title="界限上下文"></a>界限上下文</h4><p>什么是界限上下文?<br>例子:我有 kuai di<br>①我有块地,<strong>祖上留下来的</strong><br>②我有快递,<strong>顺丰的</strong></p><p>帮助我们理解对话含义的语气和语境就是<strong>上下文</strong></p><p>例子:</p><ol><li>引用 Eric Evans 对界限上下文的解释(细胞-上下文,细胞膜-边界</li><li>我—>业务流程:乘客 | 宾客 | 支付者 | 咨询师</li></ol><p>领域是问题域(即问题空间),界限上下文是问题的解决空间<br>界限上下文是直译术语,晦涩难懂,理解成本高。应该叫<strong>上下文边界</strong></p><h4 id="上下文映射(Context-Map)"><a href="#上下文映射(Context-Map)" class="headerlink" title="上下文映射(Context Map)"></a>上下文映射(Context Map)</h4><p>应该译为<strong>上下文图</strong>,是描述各个<strong>上下文之间的关系</strong>的总体视图。分别是:</p><ul><li>合作关系</li><li>共享内核(如:多个服务共享jar包,或者公共基础服务</li><li>客户/供应商(如:服务间相互约定模型,由供应方维护</li><li>追随者(如:调用支付宝接口,无法约定,只能按照他们协议走</li><li>防腐层(如:上游系统模型太烂,下游可以用防腐层来将其转换成理想模型,<strong>目的是将上游系统的影响降到最低</strong></li><li>公开主机服务(如:对外发布公共服务及接口文档</li><li>发布语言</li><li>各行其道</li><li>大泥球( 毫无设计可言</li></ul><h4 id="实体"><a href="#实体" class="headerlink" title="实体"></a>实体</h4><p>有唯一标识(可理解为其id),实现了领域行为(充血模型),对象的延续性和标识会跨越甚至超出软件的生命周期。</p><p>一个典型的实体应具备三要素:</p><ul><li>身份标识</li><li>属性</li><li>领域行为</li></ul><h4 id="值对象(-值-对象)"><a href="#值对象(-值-对象)" class="headerlink" title="值对象(= 值 + 对象)"></a>值对象(= 值 + 对象)</h4><p>是否拥有<strong>唯一标识</strong>,是实体和值对象的<strong>根本区别</strong>。<br>值对象特征:</p><ul><li>不可变,只读,安全</li><li>将不同的相关属性组合成了一个概念整体</li><li>可以和其他值对象进行相等性比较</li><li>行为不会对属性产生副作用</li></ul><h4 id="实体和值对象的区别"><a href="#实体和值对象的区别" class="headerlink" title="实体和值对象的区别"></a>实体和值对象的区别</h4><p>在实践中,实体和值对象是一起使用的,值对象作为实体的附属属性。如:领域模型中的人员是实体 (有唯一身份标识),而地址对象被人员实体引用。</p><p>值对象可以在不同的场景中被不同的实体引用。如在电商系统中,地址对象作为收货地址;在员工系统中,地址对象作为家庭地址。</p><p>此外,在某些场景中,地址会占据领域的主导地位,如行政区划中的地址信息维护,这时应该设计为实体。</p><table><thead><tr><th>实体</th><th>值对象</th></tr></thead><tbody><tr><td>具有生命周期</td><td>起描述作用</td></tr><tr><td>有唯一标识</td><td>无</td></tr><tr><td>通过id判断相等性</td><td>实现equals方法</td></tr><tr><td>增删改查/持久化</td><td>即时创建用完就扔</td></tr><tr><td>可变</td><td>不可变</td></tr><tr><td>如 Order/Car</td><td>Address/Color</td></tr></tbody></table><h4 id="聚合、聚合根"><a href="#聚合、聚合根" class="headerlink" title="聚合、聚合根"></a>聚合、聚合根</h4><p>在DDD中,实体、值对象是基础的领域对象,表现出的是个体的能力。<br>而让实体、值对象协同工作的组织,就是聚合,它用来确保这些领域对象在实现共同的业务逻辑时,能保证数据的一致性</p><p>聚合定义了一组具有<strong>内聚关系</strong>的相关对象的<strong>集合</strong>。可以把聚合看做是一个修改数据的单元。</p><p>如果一个聚合只有一个实体,那么这个实体就是聚合根;如果有多个实体,那么需要思考聚合内哪个对象有独立存在的意义,并且可以和外部直接进行交互,以其为聚合根</p><p>聚合根是与其他聚合交互的唯一接口。</p><p>聚合设计原则:</p><ul><li>设计小聚合,79%的聚合通常只有一个实体,即聚合根</li><li>聚合之间通过Id关联,而不是对象引用</li><li>一个事务只能创建或更新一个聚合。这是理想情况,遵循最终一致性。大多数情况我们需要的是事务一致性。</li></ul><p>可打破原则的理由:</p><ul><li>方便用户界面</li><li>缺乏技术支持</li><li>全局事务</li><li>查询性能</li></ul>]]></content>
</entry>
<entry>
<title>Graphviz实践手册</title>
<link href="/2021/11/26/Graphviz%E5%AE%9E%E8%B7%B5%E6%89%8B%E5%86%8C/"/>
<url>/2021/11/26/Graphviz%E5%AE%9E%E8%B7%B5%E6%89%8B%E5%86%8C/</url>
<content type="html"><![CDATA[<h1 id="dot语言"><a href="#dot语言" class="headerlink" title="dot语言"></a>dot语言</h1><h2 id="语法"><a href="#语法" class="headerlink" title="语法"></a>语法</h2><pre><code class="hljs clojure">[ strict ] (<span class="hljs-name">graph</span> | digraph) [ ID ] {rankdir = //布局方向,只有一个生效 <span class="hljs-string">"TB"</span>, <span class="hljs-string">"LR"</span>, <span class="hljs-string">"BT"</span>, <span class="hljs-string">"RL"</span>// 设置公共属性(<span class="hljs-name">graph</span> | node | edge) [ID = ID, ID2 = ID2, ...]<span class="hljs-comment">;</span>...// 声明节点NODEID [ port ] [ID = ID, ID2 = ID2, ...]<span class="hljs-comment">;</span>...// 节点链(<span class="hljs-name">NODEID</span> [ port ] | subgraph) edgeop (<span class="hljs-name">node_id</span> | subgraph) [ID = ID, ID2 = ID2, ...]<span class="hljs-comment">;</span>...}</code></pre><h2 id="编译命令"><a href="#编译命令" class="headerlink" title="编译命令"></a>编译命令</h2><p><code>dot -Tpng *.dot -o *.png</code></p><h2 id="画Label格子技巧"><a href="#画Label格子技巧" class="headerlink" title="画Label格子技巧"></a>画Label格子技巧</h2><ol><li>先整理1级分类,如A|B|C</li><li>整理某分类的二级分类,如B的二级分类:{B1|B2|B3}</li><li>把二级分类挂到一级分类下边,得:A|{B|{B1|B2|B3}|C</li><li>以此类推</li><li>得到的结果方label里,如<code>nodeName [label="A|{B|{B1|B2|B3}|C"]</code></li></ol><h2 id="shape"><a href="#shape" class="headerlink" title="shape"></a>shape</h2><p><img src="/images/Pasted%20image%2020201218173054.png"></p><h2 id="子图边框样式"><a href="#子图边框样式" class="headerlink" title="子图边框样式"></a>子图边框样式</h2><pre><code class="hljs abnf">subgraph{style = dotted<span class="hljs-comment">;</span>}</code></pre><p>可选样式:<br><img src="/images/Pasted%20image%2020201222153845.png"></p><h2 id="允许子图边界可以被指定"><a href="#允许子图边界可以被指定" class="headerlink" title="允许子图边界可以被指定"></a>允许子图边界可以被指定</h2><pre><code class="hljs actionscript">digraph {<span class="hljs-comment">//设置该选项开启</span>compound=<span class="hljs-literal">true</span> }</code></pre><p>指定子图边界:</p><pre><code class="hljs routeros">CacheUtil -> Ehcache [<span class="hljs-attribute">label</span>=<span class="hljs-string">"findCache()"</span>,ltail = cluster_CatcherManager]</code></pre><h2 id="中文乱码"><a href="#中文乱码" class="headerlink" title="中文乱码"></a>中文乱码</h2><p>指定 <code>edge [fontname="MicroSoft YaHei"]</code></p><h2 id="subgraphs-和-clusters-的区别"><a href="#subgraphs-和-clusters-的区别" class="headerlink" title="subgraphs 和 clusters 的区别"></a>subgraphs 和 clusters 的区别</h2><p>subgraph有以下几个作用:</p><ul><li>负责抽象多个节点的公共属性</li><li>给组件分组(单纯是语义上的分组,不可见)</li></ul><p>cluster继承subgraph的特性,并额外提供可视化的边界,cluster的定义方法就是给子图的名字加前缀<code>cluster_</code></p>]]></content>
</entry>
<entry>
<title>Spring data</title>
<link href="/2021/11/26/Spring%20data/"/>
<url>/2021/11/26/Spring%20data/</url>
<content type="html"><![CDATA[<p>Spring repository(Spring data common) 负责定义应用和数据库服务之间的抽象API接口。<br>Spring repository 从对接的数据库类型上看有多种实现,关系型数据库、nosql、内存数据库、云数据库等等。</p><p>Spring repository 对关系型数据库的实现,最主流的有 Spring Data JPA,底层使用Hibernate 这种ORM框架,此外原生一点的实现有 Spring Data JDBC。</p><p>不同的实现,需要接入不同的数据源,不同的数据源由统一的公共API操作。各数据源需要单独配置,配置相应的连接参数,如使用jdbc-Mysql作为数据源,则要为DataSource配置相应的连接串、账号、密码等等。</p><p>小结<br>Spring repository –> Spring Data JPA/JDBC/MongoDB…. –> mysql/oracle/h2/… </p><h2 id="Spring-JPA"><a href="#Spring-JPA" class="headerlink" title="Spring JPA"></a>Spring JPA</h2><h3 id="引入步骤"><a href="#引入步骤" class="headerlink" title="引入步骤"></a>引入步骤</h3><p>要使用Spring Data,有一下步骤</p><ol><li>pom 引入 spring-boot-starter-data-jpa<ol><li><blockquote><p>自动依赖 spring Data common</p></blockquote></li></ol></li><li>pom 引入DataSource需要的引擎,如Driven等</li><li>自行在 Configuration 中配置 DataSource</li><li>编写model的interface,继承对应的repository,如JPA实现则继承JPArepository。Spring Data 将会自动的扫描所有 Repository,并通过动态代理来实现它。</li><li>在Configuration 中设置 @EnableJpaRepositories 使Spring Data生效</li></ol><p>####设置</p><pre><code class="hljs java"><span class="hljs-meta">@EnableJpaRepositories(basePackages = "com.baeldung.spring.data.persistence.repository")</span> <span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PersistenceConfig</span> </span>{ ...}</code></pre><h3 id="自定义DB操作"><a href="#自定义DB操作" class="headerlink" title="自定义DB操作"></a>自定义DB操作</h3><p>Spring Data 默认已经对Repository 生成好基本的db操作了。但难免有时还是无法满足需求,此时需要自定义DB操作,有如下方式可选:</p><ol><li>命名规则自定义查询</li><li>手动sql自定义查询</li></ol><h4 id="命名规则自定义查询"><a href="#命名规则自定义查询" class="headerlink" title="命名规则自定义查询"></a>命名规则自定义查询</h4><p>在 Repository 接口中定义新方法,如果Entity中有name字段,不妨可以在Repository 接口中新增 findBy<strong>Name</strong>方法,这样Spring Data会自动生成对应的实现。</p><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">IFooDAO</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">JpaRepository</span><<span class="hljs-title">Foo</span>, <span class="hljs-title">Long</span>> </span>{ <span class="hljs-function">Foo <span class="hljs-title">findByName</span><span class="hljs-params">(String name)</span></span>;}</code></pre><p>更多自动化关键字可以查阅:<a href="https://docs.spring.io/spring-data/data-jpa/docs/current/reference/html/#jpa.query-methods.query-creation">https://docs.spring.io/spring-data/data-jpa/docs/current/reference/html/#jpa.query-methods.query-creation</a> </p><h4 id="手动sql自定义查询"><a href="#手动sql自定义查询" class="headerlink" title="手动sql自定义查询"></a>手动sql自定义查询</h4><p>示例</p><pre><code class="hljs java"><span class="hljs-meta">@Query("SELECT f FROM Foo f WHERE LOWER(f.name) = LOWER(:name)")</span><span class="hljs-function">Foo <span class="hljs-title">retrieveByName</span><span class="hljs-params">(<span class="hljs-meta">@Param("name")</span> String name)</span></span>;</code></pre><h2 id="未分类"><a href="#未分类" class="headerlink" title="未分类"></a>未分类</h2><h3 id="Modifying"><a href="#Modifying" class="headerlink" title="@Modifying"></a>@Modifying</h3><p>可在@Query上使用@Modifying注释,此时@Query可以执行查询以外的sql,如增、删、改。</p><h3 id="Modifying-和-命名方法-操作的区别"><a href="#Modifying-和-命名方法-操作的区别" class="headerlink" title="@Modifying 和 命名方法 操作的区别"></a>@Modifying 和 命名方法 操作的区别</h3><p>后者会先把DB的数据全查出来,然后逐个做操作,这样可以通过AOP对每条数据设置前置操作。而前者会直接把操作语句扔到DB执行。</p><h3 id="Modifying-与持久化容器的过期数据"><a href="#Modifying-与持久化容器的过期数据" class="headerlink" title="@Modifying 与持久化容器的过期数据"></a>@Modifying 与持久化容器的过期数据</h3><p>通过 @Modifying 执行的操作,不会同步给持久化容器,此时持久化容器的数据处于过期状态。一个方法是通过手动清空持久化容器。但也可以通过以下方式让其自动清空。</p><pre><code class="hljs java"><span class="hljs-meta">@Modifying(clearAutomatically = true)</span></code></pre><p>但如果清空持久化容器,则会导致未flush的数据也被清除,导致未保存的更改被丢弃。可以通过以下属性来在清空前flush。</p><pre><code class="hljs java"><span class="hljs-meta">@Modifying(flushAutomatically = true)</span></code></pre><h3 id="Query"><a href="#Query" class="headerlink" title="@Query"></a>@Query</h3><p>@Query的优先度优先于按方法名查询。</p><p>@Query 优先使用JPQL语法,如果要使用原生SQL语法,需要指定native=ture。</p><pre><code class="hljs java"><span class="hljs-comment">//JPQL语法</span><span class="hljs-meta">@Query("SELECT u FROM User u WHERE u.status = 1")</span><span class="hljs-function">Collection<User> <span class="hljs-title">findAllActiveUsers</span><span class="hljs-params">()</span></span>;<span class="hljs-comment">//原生SQL</span><span class="hljs-meta">@Query(</span><span class="hljs-meta"> value = "SELECT * FROM USERS u WHERE u.status = 1", </span><span class="hljs-meta"> nativeQuery = true)</span><span class="hljs-function">Collection<User> <span class="hljs-title">findAllActiveUsersNative</span><span class="hljs-params">()</span></span>;</code></pre><h3 id="连接持久层"><a href="#连接持久层" class="headerlink" title="连接持久层"></a>连接持久层</h3><p>至少需要:</p><ol><li>datasource,涉及和维护driver、username、password</li><li>SqlSessionFactory,用来连接服务器,创建sqlSession</li><li>mapper,定义相关的Query(在JPA中,一个方法就是一个mapper)</li></ol><p>图示:<br><img src="/images/Pasted%20image%2020201222154530.png"></p><h4 id="JPA需要准备环境"><a href="#JPA需要准备环境" class="headerlink" title="JPA需要准备环境"></a>JPA需要准备环境</h4><p>对于JPA,是一回事。</p><ol><li>DataSource</li><li>EntityManager —— 相当于SqlSessionFactory</li><li>Entity Repository —— 相当于Mapper</li></ol><h4 id="Mybatis"><a href="#Mybatis" class="headerlink" title="Mybatis"></a>Mybatis</h4><p>需要注册Bean org.mybatis.spring.mapper.MapperScannerConfigurer 或者 注解 @MapperScanner,来生效mybatis。该bean负责整合上述的组件到DAO的代理类中,开发者就可以通过dao接口来操作db了。</p>]]></content>
</entry>
<entry>
<title>aop实践手册</title>
<link href="/2021/11/26/aop%E5%AE%9E%E8%B7%B5%E6%89%8B%E5%86%8C/"/>
<url>/2021/11/26/aop%E5%AE%9E%E8%B7%B5%E6%89%8B%E5%86%8C/</url>
<content type="html"><![CDATA[<h3 id="aspect语法"><a href="#aspect语法" class="headerlink" title="aspect语法"></a>aspect语法</h3><h4 id="call和execution的区别"><a href="#call和execution的区别" class="headerlink" title="call和execution的区别"></a>call和execution的区别</h4><p>结合这篇<a href="https://stackoverflow.com/questions/28251596/difference-between-call-and-execution-pointcuts-in-php/28252742#28252742">java - Difference between call and execution pointcuts in PHP? - Stack Overflow</a>食用<br><img src="/images/Pasted%20image%2020210820103450.png"></p><p>重点:But wait a minute, it still makes a difference: <code>execution</code> is just woven in one place while <code>call</code> it woven into potentially many places, so the amount of code generated is smaller for <code>execution</code>.</p><p>搞清楚 @target、@withIn 的区别<br>参考<a href="https://blog.csdn.net/jinnianshilongnian/article/details/84156354?utm_medium=distribute.pc_relevant.none-task-blog-2~default~BlogCommendFromMachineLearnPai2~default-1.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2~default~BlogCommendFromMachineLearnPai2~default-1.control">»Spring 之AOP AspectJ切入点语法详解(最全了,不需要再去其他地找了)_jinnianshilongnian的专栏-CSDN博客</a></p><h4 id="语法表"><a href="#语法表" class="headerlink" title="语法表"></a>语法表</h4><p>execution(annotation scope return package.Class.method( params ) )</p><ul><li>可省略:annotation、scope</li><li>任意 return、包、方法:*</li><li>任意参数:(..)</li><li>任意多个包:..</li></ul>]]></content>
</entry>
<entry>
<title>cdn</title>
<link href="/2021/11/26/cdn/"/>
<url>/2021/11/26/cdn/</url>
<content type="html"><![CDATA[<p>我觉得cdn是反向代理的延伸,反向代理初期被用来解决服务器压力和响应速度的问题,大规模部署反向代理时,如果都部署在一起,那么带宽和距离仍然是瓶颈,而异地部署则要考虑负载均衡的问题,解决了这些问题也就基本部署成CDN了。</p>]]></content>
</entry>
<entry>
<title>cpp概念整理</title>
<link href="/2021/11/26/cpp%E6%A6%82%E5%BF%B5%E6%95%B4%E7%90%86/"/>
<url>/2021/11/26/cpp%E6%A6%82%E5%BF%B5%E6%95%B4%E7%90%86/</url>
<content type="html"><![CDATA[<h3 id="Include"><a href="#Include" class="headerlink" title="Include"></a>Include</h3><p><code>#include</code> 明显是预编译命令,作用是把真正的代码段替换掉命令所在位置。<br><code>#include <></code>引入类库路径里的头文件,<code>#include ""</code>引入的是程序目录路径里的头文件。</p><h3 id="函数-const"><a href="#函数-const" class="headerlink" title="函数+const"></a>函数+const</h3><p><code>返回值 函数名(参数) const</code><br>像上面这种函数后加const标识符的函数,表示它不会修改入参</p><h3 id="运算符重载"><a href="#运算符重载" class="headerlink" title="运算符重载"></a>运算符重载</h3><p>类似java的重写toString()<br>形式:<code>返回值 operator+(入参1, 入参2...)</code></p><ul><li>这个“+”可以替换为其他运算符</li><li>运算符一般从左往右解析,如:<code>cout << a << b 等价于 (cout << a) << b</code></li></ul><h3 id="友元"><a href="#友元" class="headerlink" title="友元"></a>友元</h3><p>定义:友元 修饰 函数,如友元函数,表示该函数可以访问这个(定义友元函数的)类的实例的私有变量<br>形式:<code>friend 函数声明</code><br>只能用在函数声明中</p><h4 id="应用"><a href="#应用" class="headerlink" title="应用"></a>应用</h4><p>使一个自定义的类可以以如下形式输出到cout(类似java的toString):</p><pre><code class="hljs c++">cout << anClassObj</code></pre><h3 id="转换函数"><a href="#转换函数" class="headerlink" title="转换函数"></a>转换函数</h3><h4 id="转换函数的原型声明"><a href="#转换函数的原型声明" class="headerlink" title="转换函数的原型声明"></a>转换函数的原型声明</h4><pre><code class="hljs c++"><span class="hljs-function"><span class="hljs-keyword">operator</span> <span class="hljs-title">typeName</span><span class="hljs-params">()</span></span>;</code></pre><h4 id="“类”-与-“基本类型”的转换"><a href="#“类”-与-“基本类型”的转换" class="headerlink" title="“类” 与 “基本类型”的转换"></a>“类” 与 “基本类型”的转换</h4><p>类可以定义单一参数的构造函数,来与基本类型进行转换,如:</p><pre><code class="hljs c++"><span class="hljs-comment">// 类定义:</span><span class="hljs-function"><span class="hljs-keyword">operator</span> <span class="hljs-title">int</span><span class="hljs-params">()</span> <span class="hljs-keyword">const</span></span>;<span class="hljs-comment">// 隐式转换:</span>aClass a = <span class="hljs-number">3</span>;<span class="hljs-comment">// 等价于显式声明</span>aClass a = <span class="hljs-built_in">aClass</span>(<span class="hljs-number">3</span>);</code></pre><p>如果在类的构造函数声明前加上<strong>explicit</strong>,则构造函数只能用于显式声明</p><pre><code class="hljs c++"><span class="hljs-comment">// 声明为显式转换</span><span class="hljs-function"><span class="hljs-keyword">explicit</span> <span class="hljs-keyword">operator</span> <span class="hljs-title">typeName</span><span class="hljs-params">()</span></span>;</code></pre><h3 id="变量-amp-指针"><a href="#变量-amp-指针" class="headerlink" title="变量&指针"></a>变量&指针</h3><p>变量是编译时分配的有名称的内存<br>指针主要作用于运行时,为其分配未命名的内存以存储值</p><pre><code class="hljs c++"><span class="hljs-comment">// 模式1</span><span class="hljs-keyword">int</span> num = <span class="hljs-number">3</span>;<span class="hljs-keyword">int</span> * ptr = &num;<span class="hljs-comment">// 模式2</span><span class="hljs-keyword">int</span> * ptr = <span class="hljs-keyword">new</span> <span class="hljs-built_in"><span class="hljs-keyword">int</span></span>(<span class="hljs-number">3</span>);</code></pre><p>如上述代码,模式1 和 模式2 的本质是一样的,都是指针ptr指向了一个3,但是前者有变量名num,后者没有变量名</p><h3 id="引用"><a href="#引用" class="headerlink" title="引用"></a>引用</h3><ul><li>赋值时它标记内容所在地址,使用时它是地址内的内容</li></ul>]]></content>
<tags>
<tag> c++ </tag>
</tags>
</entry>
<entry>
<title>db锁</title>
<link href="/2021/11/26/db%E9%94%81/"/>
<url>/2021/11/26/db%E9%94%81/</url>
<content type="html"><![CDATA[<p>仅针对已被操作的数据</p><p>读锁<br>写锁<br> 行级锁<br> 表级锁</p>]]></content>
</entry>
<entry>
<title>disruptor学习记录</title>
<link href="/2021/11/26/disruptor%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95/"/>
<url>/2021/11/26/disruptor%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95/</url>
<content type="html"><![CDATA[<h2 id="术语概念"><a href="#术语概念" class="headerlink" title="术语概念"></a>术语概念</h2><p><a href="https://github.com/LMAX-Exchange/disruptor/wiki/Introduction#core-concepts">参考地址</a></p><p>RingBuffer——Disruptor底层数据结构实现,核心类,是线程间交换数据的中转地;</p><p>Sequencer——序号管理器,生产同步的实现者,负责消费者/生产者各自序号、序号栅栏的管理和协调,Sequencer有单生产者,多生产者两种不同的模式,里面实现了各种同步的算法;</p><p>Sequence——序号,声明一个序号,用于跟踪ringbuffer中任务的变化和消费者的消费情况,disruptor里面大部分的并发代码都是通过对Sequence的值同步修改实现的,而非锁,这是disruptor高性能的一个主要原因;</p><p>SequenceBarrier——序号栅栏,管理和协调生产者的游标序号和各个消费者的序号,确保生产者不会覆盖消费者未来得及处理的消息,确保存在依赖的消费者之间能够按照正确的顺序处理</p><p>EventProcessor——事件处理器,监听RingBuffer的事件,并消费可用事件,从RingBuffer读取的事件会交由实际的生产者实现类来消费;它会一直侦听下一个可用的序号,直到该序号对应的事件已经准备好。</p><p>EventHandler——业务处理器,是实际消费者的接口,完成具体的业务逻辑实现,第三方实现该接口;代表着消费者。</p><p>Producer——生产者接口,第三方线程充当该角色,producer向RingBuffer写入事件。</p><p>Wait Strategy:Wait Strategy决定了一个消费者怎么等待生产者将事件(Event)放入Disruptor中。</p><h2 id="基本用法"><a href="#基本用法" class="headerlink" title="基本用法"></a>基本用法</h2><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MsgEventMain</span> </span>{ <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> InterruptedException </span>{ Disruptor disruptor = <span class="hljs-keyword">new</span> Disruptor(<span class="hljs-keyword">new</span> MsgEventFactory(), <span class="hljs-number">1024</span>, Executors.defaultThreadFactory() , ProducerType.SINGLE, <span class="hljs-keyword">new</span> BlockingWaitStrategy()); <span class="hljs-comment">// ①</span> disruptor.handleEventsWith(<span class="hljs-keyword">new</span> MsgEventHandler()); <span class="hljs-comment">//②</span> disruptor.start(); <span class="hljs-comment">//③</span> RingBuffer ringBuffer = disruptor.getRingBuffer(); <span class="hljs-comment">//④</span> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i < <span class="hljs-number">10</span>; i++) { <span class="hljs-keyword">int</span> finalI = i; EventTranslator<MsgEvent> eventTranslator = (event, sequence) -> { event.setMsg(<span class="hljs-string">"business msg:"</span> + finalI); }; ringBuffer.publishEvent(eventTranslator); <span class="hljs-comment">//⑤</span> Thread.sleep(<span class="hljs-number">300</span>); } disruptor.shutdown(); } }</code></pre><p>核心分为五步:</p><ol><li>创建一个disruptor<blockquote><p>(构造涉及 1. Event工厂; 2. 线程工厂; 3. 生产策略:多线程、单线程 4. 消费等待策略)</p></blockquote></li><li>指定消费者</li><li>启动disruptor</li><li>从disruptor中获取 ringbuffer</li><li>生产者调用 <code>ringBuffer.publishEvent</code> 发消息<blockquote><p>此处需要一个eventTranslator,来填充消息内容)</p></blockquote></li></ol><h3 id="官方实践"><a href="#官方实践" class="headerlink" title="官方实践"></a>官方实践</h3><ol><li>每个productor维护自己的 translator 的静态实例作为成员变量,且注入ringBuffer,用于生产消息</li></ol><h3 id="可调优项"><a href="#可调优项" class="headerlink" title="可调优项"></a>可调优项</h3><p>核心5步可以覆盖大多数生产场景使用,但是有些可调参数可以用于促进性能,分别是</p><ol><li>生产策略:多线程、单线程</li><li>消费等待策略</li></ol><h4 id="等待策略"><a href="#等待策略" class="headerlink" title="等待策略"></a>等待策略</h4><p><strong>「BlockingWaitStrategy」</strong></p><p>Disruptor的默认策略是BlockingWaitStrategy。在BlockingWaitStrategy内部是使用锁和condition来控制线程的唤醒。BlockingWaitStrategy是最低效的策略,但其对CPU的消耗最小并且在各种不同部署环境中能提供更加一致的性能表现。</p><p><strong>「SleepingWaitStrategy」</strong></p><p>SleepingWaitStrategy 的性能表现跟 BlockingWaitStrategy 差不多,对 CPU 的消耗也类似,但其对生产者线程的影响最小,通过使用<code>LockSupport.parkNanos(1)</code>来实现循环等待。</p><p><strong>「YieldingWaitStrategy」</strong></p><p>YieldingWaitStrategy是可以使用在低延迟系统的策略之一。YieldingWaitStrategy将自旋以等待序列增加到适当的值。在循环体内,将调用<code>Thread.yield()</code>以允许其他排队的线程运行。在要求极高性能且事件处理线数小于 CPU 逻辑核心数的场景中,推荐使用此策略;例如,CPU开启超线程的特性。</p><p><strong>「BusySpinWaitStrategy」</strong></p><p>性能最好,适合用于低延迟的系统。在要求极高性能且事件处理线程数小于CPU逻辑核心数的场景中,推荐使用此策略;例如,CPU开启超线程的特性。</p><p><strong>「PhasedBackoffWaitStrategy」</strong></p><p>自旋 + yield + 自定义策略,CPU资源紧缺,吞吐量和延迟并不重要的场景。</p>]]></content>
</entry>
<entry>
<title>guice实践手册</title>
<link href="/2021/11/26/guice%E5%AE%9E%E8%B7%B5%E6%89%8B%E5%86%8C/"/>
<url>/2021/11/26/guice%E5%AE%9E%E8%B7%B5%E6%89%8B%E5%86%8C/</url>
<content type="html"><![CDATA[<h2 id="核心"><a href="#核心" class="headerlink" title="核心"></a>核心</h2><p>主要由 <code>key</code> 和 <code>provider</code> 来构成容器,所以容器本质上是个map</p><h2 id="使用方式"><a href="#使用方式" class="headerlink" title="使用方式"></a>使用方式</h2><p>分两个步骤</p><ol><li>配置</li><li>注入</li></ol><h3 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h3><p>有两种方式</p><ol><li>注解</li><li>用DSL(Domain specify language)</li></ol><p>一个Module就是一个配置单元,bean的配置都在module里完成,类比Spring的@Configuration类</p><h4 id="注解"><a href="#注解" class="headerlink" title="注解"></a>注解</h4><p><code>@Provides</code>,用法如下:</p><pre><code class="hljs java"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DemoModule</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">AbstractModule</span> </span>{ <span class="hljs-meta">@Provides</span> <span class="hljs-meta">@Count</span> <span class="hljs-function"><span class="hljs-keyword">static</span> Integer <span class="hljs-title">provideCount</span><span class="hljs-params">()</span> </span>{ <span class="hljs-keyword">return</span> <span class="hljs-number">3</span>; } <span class="hljs-meta">@Provides</span> <span class="hljs-meta">@Message</span> <span class="hljs-function"><span class="hljs-keyword">static</span> String <span class="hljs-title">provideMessage</span><span class="hljs-params">()</span> </span>{ <span class="hljs-keyword">return</span> <span class="hljs-string">"hello world"</span>; }}</code></pre><h4 id="DSL"><a href="#DSL" class="headerlink" title="DSL"></a>DSL</h4><table><thead><tr><th>Guice DSL syntax</th><th>Mental model</th></tr></thead><tbody><tr><td>bind(key).toInstance(value)</td><td>map.put(key, () -> value)(instance binding)</td></tr><tr><td>bind(key).toProvider(provider)</td><td>map.put(key, provider)(provider binding)</td></tr><tr><td>bind(key).to(anotherKey)</td><td>map.put(key, map.get(anotherKey))(linked binding)</td></tr><tr><td>@Provides Foo provideFoo() {…}</td><td>map.put(Key.get(Foo.class), module::provideFoo)(provider method binding)</td></tr></tbody></table><h3 id="注入"><a href="#注入" class="headerlink" title="注入"></a>注入</h3><p><code>@Inject</code>,用在构造方法、set方法、成员变量,可以对参数进行注入</p><p>示例1</p><pre><code class="hljs java"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Foo</span> </span>{ <span class="hljs-keyword">private</span> Database database; <span class="hljs-meta">@Inject</span> Foo(Database database) { <span class="hljs-comment">// We need a database, from somewhere</span> <span class="hljs-keyword">this</span>.database = database; }}</code></pre><h3 id="整合"><a href="#整合" class="headerlink" title="整合"></a>整合</h3><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyWebServer</span> </span>{ <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">start</span><span class="hljs-params">()</span> </span>{ ... } <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{ <span class="hljs-comment">// Creates an injector that has all the necessary dependencies needed to</span> <span class="hljs-comment">// build a functional server.</span> Injector injector = Guice.createInjector( <span class="hljs-keyword">new</span> RequestLoggingModule(), <span class="hljs-keyword">new</span> RequestHandlerModule(), <span class="hljs-keyword">new</span> AuthenticationModule(), <span class="hljs-keyword">new</span> DatabaseModule(), ...); <span class="hljs-comment">// Bootstrap the application by creating an instance of the server then</span> <span class="hljs-comment">// start the server to handle incoming requests.</span> injector.getInstance(MyWebServer.class) .start(); }}</code></pre><h2 id="Multibindings"><a href="#Multibindings" class="headerlink" title="Multibindings"></a>Multibindings</h2><p>Multibindings属于绑定模式里的一种,适用于插件架构。说白了就是把多个同类型的bean注入到一个列表中。</p><h3 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h3><ol><li>通过 <code>Multibinder.newSetBinder</code> 生成一个指定接口类型的binder</li><li>调用1里生成的 <code>binder.addBinding()</code> 来追加插件(不是覆盖)</li></ol><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">FlickrPluginModule</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">AbstractModule</span> </span>{ <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">configure</span><span class="hljs-params">()</span> </span>{ Multibinder<UriSummarizer> uriBinder = Multibinder.newSetBinder(binder(), UriSummarizer.class); uriBinder.addBinding().to(FlickrPhotoSummarizer.class); ... <span class="hljs-comment">// bind plugin dependencies, such as our Flickr API key</span> }}</code></pre>]]></content>
</entry>
<entry>
<title>hexo美化</title>
<link href="/2021/11/26/hexo%E7%BE%8E%E5%8C%96/"/>
<url>/2021/11/26/hexo%E7%BE%8E%E5%8C%96/</url>
<content type="html"><![CDATA[<h2 id="基础知识"><a href="#基础知识" class="headerlink" title="基础知识"></a>基础知识</h2><p><a href="https://liuyib.github.io/2019/08/20/develop-hexo-theme-from-0-to-1/">从 0 到 1 开发 Hexo 主题杂谈 | Liuyib’s Blog</a></p><h2 id="theme"><a href="#theme" class="headerlink" title="theme"></a>theme</h2><p>个人喜好:</p><ul><li>next</li><li>beautiful-hexo(源于 beautiful-jekyll)</li></ul><h2 id="设置背景图"><a href="#设置背景图" class="headerlink" title="设置背景图"></a>设置背景图</h2><p>参考:<br><a href="https://ducmanhphan.github.io/2020-01-15-State-pattern/">State pattern (ducmanhphan.github.io)</a></p><h3 id="1-准备好无缝背景图"><a href="#1-准备好无缝背景图" class="headerlink" title="1. 准备好无缝背景图"></a>1. 准备好无缝背景图</h3><p>可以从喜欢的博客上扒,也可以google关键字:seamless icon</p><h3 id="2-修改主题css"><a href="#2-修改主题css" class="headerlink" title="2. 修改主题css"></a>2. 修改主题css</h3><p>通过chrome,定位到 /source/css/main.css</p><ul><li><p>修改body、导航栏、footer,添加如下两行</p><pre><code class="hljs css"><span class="hljs-selector-tag">body</span> { <span class="hljs-attribute">background-image</span>: <span class="hljs-built_in">url</span>(<span class="hljs-string">/bgimg/bgimage.png</span>); <span class="hljs-attribute">background-attachment</span>: fixed;}<span class="hljs-selector-class">.navbar-custom</span> { <span class="hljs-attribute">background-image</span>: <span class="hljs-built_in">url</span>(<span class="hljs-string">/bgimg/bgimage.png</span>); <span class="hljs-attribute">background-attachment</span>: fixed;}<span class="hljs-selector-tag">footer</span> { <span class="hljs-attribute">background-image</span>: <span class="hljs-built_in">url</span>(<span class="hljs-string">/bgimg/bgimage.png</span>); <span class="hljs-attribute">background-attachment</span>: fixed;}</code></pre></li><li><p>修改注释样式,添加下面两行</p><pre><code class="hljs css"><span class="hljs-selector-tag">blockquote</span> { <span class="hljs-attribute">background-color</span>: <span class="hljs-built_in">rgba</span>(<span class="hljs-number">255</span>, <span class="hljs-number">255</span>, <span class="hljs-number">255</span>, <span class="hljs-number">0.2</span>); <span class="hljs-attribute">border-left-color</span>: <span class="hljs-number">#cccccc</span>;}</code></pre></li></ul><h2 id="添加搜索框"><a href="#添加搜索框" class="headerlink" title="添加搜索框"></a>添加搜索框</h2><p>参考<a href="https://hzfans.github.io/2018/01/03/2018-1-3%20%E4%B8%BAHexo%20blog%E5%8D%9A%E5%AE%A2%E5%88%9B%E5%BB%BA%E6%9C%AC%E5%9C%B0%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E/">为Hexo blog博客创建本地搜索引擎 | 花子凡 (hzfans.github.io)</a></p><blockquote><p>备用方案<br><a href="https://liuyib.github.io/2019/08/20/develop-hexo-theme-from-0-to-1/">从 0 到 1 开发 Hexo 主题杂谈 | Liuyib’s Blog</a></p></blockquote><h3 id="样式"><a href="#样式" class="headerlink" title="样式"></a>样式</h3><p><img src="/images/Pasted%20image%2020211127182011.png"></p><h3 id="使用框架"><a href="#使用框架" class="headerlink" title="使用框架"></a>使用框架</h3><p><a href="https://github.com/Liam0205/hexo-search-plugin-snippets">GitHub - Liam0205/hexo-search-plugin-snippets: 一些辅助 <code>hexo-generator-search</code> 插件的代码片段,博客右上角看效果 —-></a></p><blockquote><p>注意:和next主题用的插件<code>hexo-generator-searchdb</code> 有一定的差别</p></blockquote><h3 id="引入步骤(只关注定制化的部分)"><a href="#引入步骤(只关注定制化的部分)" class="headerlink" title="引入步骤(只关注定制化的部分)"></a>引入步骤(只关注定制化的部分)</h3><ol><li>做些基本的配置,主工程引入<code>hexo-generator-search</code>插件——产物:search.xml。并按照<a href="https://github.com/wzpan/hexo-generator-search">插件文档</a>进行一定配置</li><li>拷贝search.js文件到主题下的js目录中</li><li>拷贝search.stylus样式文件到主题下的css目录中<ol><li>其最后会转换成css文件,需要在main.css、main.js或者模板文件中引入</li></ol></li><li>在导航栏模板文件中,插入搜索组件代码,监听键盘输入事件,调用search.js的函数</li><li>添加搜索icon,<a href="https://fontawesome.com/v4.7/">icon商城</a></li></ol><h3 id="hexo-renderer-stylus的用法"><a href="#hexo-renderer-stylus的用法" class="headerlink" title="hexo-renderer-stylus的用法"></a>hexo-renderer-stylus的用法</h3><p>stylus文件直接放到source目录及其任何子目录下,都能被解析。</p><h2 id="设置字体"><a href="#设置字体" class="headerlink" title="设置字体"></a>设置字体</h2><p><a href="https://fonts.google.com/">字体商城</a>挑选</p><p>英语正文:Roboto Slab</p><h2 id="个性化404页面"><a href="#个性化404页面" class="headerlink" title="个性化404页面"></a>个性化404页面</h2><p>参考:<a href="http://yelog.org/2017/02/25/hexo-create-404-page/">http://yelog.org/2017/02/25/hexo-create-404-page/</a><br>对于github page来说,只要在根目录又404.html,当页面找不到时,就会被转发到/404.html页面,所以我们只要更改这个页面,就可以实现自定义404页面了。</p><p>但是我们通常会需要与本主题相符的404页面。那我们就需要以下操作</p><h3 id="新建404页面"><a href="#新建404页面" class="headerlink" title="新建404页面"></a>新建404页面</h3><ol><li><p> 进入 Hexo 所在文件夹,输入 <code>hexo new page 404</code> ;</p></li><li><p> 打开刚新建的页面文件,默认在 Hexo 文件夹根目录下 /source/404/index.md;</p></li><li><p>在顶部插入一行,写上 <code>permalink: /404</code>,这表示指定该页固定链接为 <code>http://"主页"/404.html</code></p><pre><code> 注意:该操作在本地启动看不到效果,需要在github page上才能看到效果。</code></pre></li></ol><pre><code class="hljs yaml"><span class="hljs-meta">---</span><span class="hljs-attr">title:</span><span class="hljs-attr">permalink:</span> <span class="hljs-string">/404</span><span class="hljs-attr">date:</span> <span class="hljs-number">2021-11-28 05:45:59</span><span class="hljs-meta">---</span><span class="hljs-comment"># Whoops, this page doesn't exist.</span><span class="hljs-comment"># Move along. (404 error)</span><span class="hljs-type">![](/images/404</span><span class="hljs-string">-southpark.jpg)</span></code></pre><h3 id="效果"><a href="#效果" class="headerlink" title="效果"></a>效果</h3><p> <a href="https://donnieyeh.github.io/%E4%B8%BE%E4%B8%AA%E4%BE%8B%E5%AD%90">https://donnieyeh.github.io/举个例子</a></p><h2 id="代码高亮"><a href="#代码高亮" class="headerlink" title="代码高亮"></a>代码高亮</h2><p>要使用好代码高亮,甚至想要自己定制化,就首先需要了解清楚Hexo进行代码高亮的原理。对一个.md文件的代码片段进行高亮,会经过如下的处理过程:</p><ol><li>将.md文件的代码片段渲染成html语言</li><li>对html语言中的代码片段进行语法解析,给各个关键词标记上相应的样式class</li><li>引入高亮样式CSS文件,使高亮样式生效</li></ol><blockquote><p> 注意:3阶段的css样式要与2阶段标记的class相匹配<br> 另:第2点语法解析用到的是highlight.js,一个代码高亮库,关于其介绍不是本章的重点,感兴趣的同学可以自行了解</p></blockquote><p>而在hexo中,对于这3步有两种渲染模式:服务器渲染 和 客户端渲染</p><ul><li><p>服务器渲染:由服务器执行步骤 1 和 2<br><img src="/images/highlight_01.png"></p></li><li><p>客户端渲染:由客户端执行步骤 2 和 3<br><img src="/images/highlight_02.png"></p></li></ul><h3 id="说在前头"><a href="#说在前头" class="headerlink" title="说在前头"></a>说在前头</h3><p>必须注意的一点是,任何关于高亮的改动,都需要执行<code>hexo clean</code>后再渲染才能生效。</p><pre><code class="hljs shell">hexo clean && hexo server</code></pre><h3 id="客户端渲染"><a href="#客户端渲染" class="headerlink" title="客户端渲染"></a>客户端渲染</h3><h4 id="一、在项目配置中关闭高亮选项"><a href="#一、在项目配置中关闭高亮选项" class="headerlink" title="一、在项目配置中关闭高亮选项"></a>一、在项目配置中关闭高亮选项</h4><pre><div class="caption"><span>_config.yml</span></div><code class="hljs yml"><span class="hljs-attr">highlight:</span> <span class="hljs-attr">enable:</span> <span class="hljs-literal">false</span> <span class="hljs-attr">prismjs:</span> <span class="hljs-attr">enable:</span> <span class="hljs-literal">false</span></code></pre><p>此时插件 <a href="https://hexo.io/docs/tag-plugins#Backtick-Code-Block">Tag Plugin - Backtick Code Block</a>会将.md文件的代码块,渲染到<code><code></code>标签下:</p><pre><code class="hljs angelscript">```yaml hello: hexo ```将被编译成:<pre> <code <span class="hljs-keyword">class</span>="<span class="hljs-symbol">yaml</span>"><span class="hljs-symbol">hello: <span class="hljs-symbol">hexo</span></span></<span class="hljs-symbol">code</span>> </<span class="hljs-symbol">pre</span>></code></pre><h4 id="二、配置语法解析库"><a href="#二、配置语法解析库" class="headerlink" title="二、配置语法解析库"></a>二、配置语法解析库</h4><p>参考hexo内置的highlight模块文档,在页面中引入相应的js文件,并调用解析函数:</p><pre><div class="caption"><span>readme.md \blog\node_modules\highlight.js\README.md</span></div><code class="hljs html"><span class="hljs-comment"><!-- 引入样式 --></span><span class="hljs-tag"><<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/path/to/styles/default.css"</span>></span><span class="hljs-comment"><!-- 引入js --></span><span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"/path/to/highlight.min.js"</span>></span><span class="hljs-tag"></<span class="hljs-name">script</span>></span><span class="hljs-comment"><!-- 调用解析函数 --></span><span class="hljs-tag"><<span class="hljs-name">script</span>></span>hljs.highlightAll();<span class="hljs-tag"></<span class="hljs-name">script</span>></span></code></pre><blockquote><p>注意:这个解析函数,不同版本的highlight.js会不一样,需要查看相应版本的readme确认。</p></blockquote><p>这样配置以后,就会在浏览器端执行解析函数解析<code><code></code>中的代码。并将结果替换掉旧的dom节点。</p><h4 id="三、配置语法高亮css"><a href="#三、配置语法高亮css" class="headerlink" title="三、配置语法高亮css"></a>三、配置语法高亮css</h4><p>代码在第二步中已经给出,引入之后就会对新的dom节点进行样式渲染,最终得到语法高亮效果。</p><h3 id="服务器渲染"><a href="#服务器渲染" class="headerlink" title="服务器渲染"></a>服务器渲染</h3><h4 id="一、在项目配置中开启高亮选项"><a href="#一、在项目配置中开启高亮选项" class="headerlink" title="一、在项目配置中开启高亮选项"></a>一、在项目配置中开启高亮选项</h4><pre><div class="caption"><span>_config.yml</span></div><code class="hljs yml"><span class="hljs-attr">highlight:</span> <span class="hljs-attr">enable:</span> <span class="hljs-literal">true</span> <span class="hljs-attr">auto_detect:</span> <span class="hljs-literal">true</span> <span class="hljs-attr">line_number:</span> <span class="hljs-literal">false</span> <span class="hljs-attr">tab_replace:</span> <span class="hljs-string">' '</span> <span class="hljs-attr">wrap:</span> <span class="hljs-literal">false</span> <span class="hljs-attr">hljs:</span> <span class="hljs-literal">true</span> <span class="hljs-attr">prismjs:</span> <span class="hljs-attr">enable:</span> <span class="hljs-literal">false</span></code></pre><p>参数解释:</p><ul><li><p>auto_detect: </p><pre><code> 自动检测语言,就是你的代码块不指定语言,插件会帮你检测,如果代码量不多的话建议打开,耗不了多少资源 </code></pre></li><li><p>line_number</p><pre><code> 是否展示行号</code></pre></li><li><p>tab_replace</p><pre><code> 替换缩进符为指定字符串</code></pre></li><li><p>wrap</p><pre><code> 当line_number=true时,比起原先只有展示代码的<code>容器,还额外增加了展示代码行数的<gutter>容器,此时就要指定wrap=true,生成一个<figure>容器来包裹<code>和<gutter>。该参数建议无脑与line_number同步就是了,要么都是ture,要么都是false</code></pre></li><li><p>hljs</p><pre><code> 给高亮样式的元素加上class前缀`hljs-`,建议配true,生成的html直接就能匹配css</code></pre></li></ul><h4 id="二、配置语法高亮css"><a href="#二、配置语法高亮css" class="headerlink" title="二、配置语法高亮css"></a>二、配置语法高亮css</h4><p>通过上一步的操作,客户端获取到的html已经是解析完语法的代码段,是使用服务器hexo内置的highlight.js解析的,只需要再引入css即可观察效果,这个在[[#客户端渲染]]中已经提及,此处不再赘述。</p><blockquote><p>hexo内置的highlight.js库,通过package-lock.json可以知道它是来自于 hexo->hexo-util 库的依赖</p></blockquote><p>由于最后都是通过highlight.js生成带有样式class的HTML代码块,所以想要有不同的样式效果,引入替换各种第三方highlight-css文件就行了。</p><p>highlight官方高亮样式主题地址:<a href="https://github.com/highlightjs/highlight.js/tree/main/src/styles">https://github.com/highlightjs/highlight.js/tree/main/src/styles</a><br><a href="https://highlightjs.org/static/demo/">预览</a></p><h3 id="拓展"><a href="#拓展" class="headerlink" title="拓展"></a>拓展</h3><p>通过上面的实践,我们已经对代码高亮的原理有了一个感性的理解了。此时我们甚至可以放弃hexo原生指定的代码高亮库highlight,自定义改造使用一些第三方的代码高亮库及颜色主题。比如<a href="https://github.com/jmblog/color-themes-for-google-code-prettify">google-code-prettify</a></p><h4 id="为什么样式只覆盖到代码行?"><a href="#为什么样式只覆盖到代码行?" class="headerlink" title="为什么样式只覆盖到代码行?"></a>为什么样式只覆盖到代码行?</h4><p><img src="/images/Pasted%20image%2020211128195458.png"><br>这么难看,不能忍啊,经过一番研究得出结论,<code><code></code>标签需要设置样式:<code>display:block</code>,不然有可能被bootstrap的样式影响到了,就会变成这样。修复之后好看多了<br><img src="/images/Pasted%20image%2020211128195644.png"></p><h2 id="添加访问日志数据统计"><a href="#添加访问日志数据统计" class="headerlink" title="添加访问日志数据统计"></a>添加访问日志数据统计</h2><p>参考:<br>Copyright © xxx’s Blog 2021<br>Theme by Hux | Ported by Kaijun | Modified by xxx<br>本站访问量75368次 | 本站访客数46404人 | 本文阅读量2183次</p><p>使用<a href="http://busuanzi.ibruce.info/">不蒜子</a>计数</p><h3 id="参考文章"><a href="#参考文章" class="headerlink" title="参考文章"></a>参考文章</h3><p><a href="https://chrischen0405.github.io/2018/09/11/post20180911/">https://chrischen0405.github.io/2018/09/11/post20180911/</a><br><a href="https://github.com/JoeyBling/busuanzi.pure.js">https://github.com/JoeyBling/busuanzi.pure.js</a></p><h3 id="核心思路"><a href="#核心思路" class="headerlink" title="核心思路"></a>核心思路</h3><ol><li>监听页面,当路由发生变化时,触发函数,浏览器通过jsonp方式请求计数服务器进行计数(参数是网页路径)。</li><li>计数服务器返回计数,并由脚本渲染到对应的元素节点</li></ol><h3 id="步骤"><a href="#步骤" class="headerlink" title="步骤"></a>步骤</h3><ol><li><p>引入<code>不蒜子</code>官网的计数库</p><pre><div class="caption"><span>footer-script.jade</span></div><code class="hljs js"><script <span class="hljs-keyword">async</span> src=<span class="hljs-string">"//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"</span>></script></code></pre></li><li><p>主题配置中添加展示开关配置</p><pre><div class="caption"><span>_config.jekyll.yml</span></div><code class="hljs yml"><span class="hljs-attr">site_counter:</span><span class="hljs-attr">show:</span> <span class="hljs-literal">true</span></code></pre></li><li><p>在copyright下面一行插入展示元素即可</p><pre><div class="caption"><span>footer.jade</span></div><code class="hljs jade">if theme.site_counter<span id="busuanzi_container_site_pv">本站总访问量<span id="busuanzi_value_site_pv"></span>次</span>span.meta-devider |<span id="busuanzi_container_site_uv">本站访客数<span id="busuanzi_value_site_uv"></span>次</span>span.theme-by.text-muted.Theme base on#[a(href="https://github.com/twoyao/beautiful-hexo") beautiful-hexo]</code></pre></li><li><p>在文章首部插入<code>本页访问量</code>元素</p><pre><div class="caption"><span>post-meta.jade</span></div><code class="hljs jade">mixin post_meta(date, tags, showviews) p.post-meta Posted on #{full_date(date, "MMM D YYYY")} if showviews if theme.site_counter span.post-meta-group span#busuanzi_container_page_pv span.with-love i.fa.fa-eye span 阅读次数: span#busuanzi_value_page_pv if tags && tags.length > 0 span.post-meta-group span.with-love i.fa.fa-tag - tags.each(function(tag) { = ' · ' a.tag.post-meta(href=url_for(tag.path))= tag.name - })</code></pre></li></ol><h3 id="效果-1"><a href="#效果-1" class="headerlink" title="效果"></a>效果</h3><p>文章首部<br><img src="/images/Pasted%20image%2020211129230131.png"></p><p>文章底部<br><img src="/images/Pasted%20image%2020211129230106.png"></p><h2 id="添加鼠标皮肤、特效"><a href="#添加鼠标皮肤、特效" class="headerlink" title="添加鼠标皮肤、特效"></a>添加鼠标皮肤、特效</h2><h2 id="添加看板娘"><a href="#添加看板娘" class="headerlink" title="添加看板娘"></a>添加看板娘</h2><p>参考 </p><ul><li>github搜索:hexo live2d</li><li><a href="https://cjjkkk.github.io/next_live2d/">https://cjjkkk.github.io/next_live2d/</a> 使用↓</li><li><a href="https://github.com/EYHN/hexo-helper-live2d">https://github.com/EYHN/hexo-helper-live2d</a> 中文文档↓</li><li><a href="https://github.com/EYHN/hexo-helper-live2d/blob/master/README.zh-CN.md">https://github.com/EYHN/hexo-helper-live2d/blob/master/README.zh-CN.md</a> 使用核心库↓</li><li><a href="https://github.com/xiazeyu/live2d-widget.js">https://github.com/xiazeyu/live2d-widget.js</a> 关联模型库↓</li><li><a href="https://github.com/xiazeyu/live2d-widget-models">https://github.com/xiazeyu/live2d-widget-models</a> 模型预览↓</li><li><a href="https://huaji8.top/post/live2d-plugin-2.0/">https://huaji8.top/post/live2d-plugin-2.0/</a> </li><li>然而live2d-widget核心库的大佬已经弃坑,新的社区阵地出现↓</li><li><a href="https://github.com/stevenjoezhang/live2d-widget">https://github.com/stevenjoezhang/live2d-widget</a> 关联模型库↓</li><li><a href="https://github.com/luanshizhimei/live2d_models_collect">https://github.com/luanshizhimei/live2d_models_collect</a></li><li><a href="https://github.com/summerscar/live2dDemo.git">https://github.com/summerscar/live2dDemo.git</a></li></ul><h3 id="工具介绍"><a href="#工具介绍" class="headerlink" title="工具介绍"></a>工具介绍</h3><p>Live2D Widget 是一款网页看板娘部件。它提供了一套框架把 对话框、人物模型、对话脚本(自动触发、互动触发)整合在一起。框架已经预置了一套完善的对话脚本。使用该框架时我们可以自由的定制化:对话事件、对话脚本、文本框样式、人物模型等等。</p><blockquote><p>要注意的一点是该框架本身并不提供人物模型,预置的人物模型数据是从第三方(/fghrsh/live2d_api)引入的,这意味着我们可以从任意第三方模型库引入人物模型。</p></blockquote><h3 id="前置要求"><a href="#前置要求" class="headerlink" title="前置要求"></a>前置要求</h3><p>font-awesome需要引入4.0以上版本</p><h3 id="引入一键傻瓜库"><a href="#引入一键傻瓜库" class="headerlink" title="引入一键傻瓜库"></a>引入一键傻瓜库</h3><pre><code class="hljs html"><span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://cdn.jsdelivr.net/gh/stevenjoezhang/live2d-widget@latest/autoload.js"</span>></span><span class="hljs-tag"></<span class="hljs-name">script</span>></span></code></pre><h3 id="订制一键傻瓜库"><a href="#订制一键傻瓜库" class="headerlink" title="订制一键傻瓜库"></a>订制一键傻瓜库</h3><p>需要自行学习前置知识:</p><ul><li><a href="https://git-scm.com/book/en/v2/Git-Basics-Tagging">Git - Tagging (git-scm.com)</a></li><li><a href="https://docs.github.com/en/repositories/releasing-projects-on-github/managing-releases-in-a-repository">Managing releases in a repository - GitHub Docs</a></li></ul><p>首先fork以下项目<br><a href="https://github.com/stevenjoezhang/live2d-widget.git">https://github.com/stevenjoezhang/live2d-widget.git</a></p><p>修改autoload.js进行自定义化<br>可以通过以下两种方式引入客制化的库</p><ol><li>引入本地库</li><li>引入线上库:然后把傻瓜库的CDN地址引导到自己的地址,<code>username</code>改成自己的即可,CDN会引导到自己 github 项目的最新tag<pre><code class="hljs html"><span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://cdn.jsdelivr.net/gh/username/live2d-widget@latest/autoload.js"</span>></span><span class="hljs-tag"></<span class="hljs-name">script</span>></span></code></pre></li></ol><h3 id="核心实现"><a href="#核心实现" class="headerlink" title="核心实现"></a>核心实现</h3><p>整个工具最核心的部分就是调用live2d库的接口,重点为第二个参数,无论我们收集到的模型放哪里都好,只要该参数能准确匹配模型位置,就能加载出来。</p><pre><code class="hljs js">loadlive2d(<span class="hljs-string">"live2d"</span>, <span class="hljs-string">`模型路径/index.json`</span>);</code></pre><blockquote><p>当然,对话脚本的订制也是看板娘的精髓所在。<br>TODO 有时间出篇文章讲解如何订制对话脚本</p></blockquote><h3 id="如何预览第三方模型"><a href="#如何预览第三方模型" class="headerlink" title="如何预览第三方模型"></a>如何预览第三方模型</h3><ol><li>首先要找到第三方模型库,github上一大堆,此处以<a href="https://github.com/summerscar/live2dDemo">summerscar/live2dDemo</a>提供的库为例。</li><li>找到库里的人物模型目录,本例使用的是板鸭的模型<code>summerscar/live2dDemo/assets/Bronya/Bronya.model.json</code></li><li>此时就要神器 jsDelivr 上场了,把<code>https://cdn.jsdelivr.net/gh/</code>拼接上刚刚找的模型地址,得到了完整的<a href="https://cdn.jsdelivr.net/gh/summerscar/live2dDemo/assets/Bronya/Bronya.model.json">模型信息地址</a>,可以点开看看是否能正常访问</li><li>打开博客页的控制台,输入以下命令,即可实时查看效果了!<pre><code class="hljs js">loadlive2d(<span class="hljs-string">"live2d"</span>, <span class="hljs-string">`https://cdn.jsdelivr.net/gh/summerscar/live2dDemo/assets/Bronya/Bronya.model.json`</span>);</code></pre></li></ol><h3 id="如何实时刷新jsdeliver-CDN"><a href="#如何实时刷新jsdeliver-CDN" class="headerlink" title="如何实时刷新jsdeliver CDN"></a>如何实时刷新jsdeliver CDN</h3><p>CDN本质上是分布式缓存,所以同一个地址,即便你服务端的文件产生了变化,CDN还是会优先使用缓存。</p><p>这里主要针对github,在实践中发现<code>username/repository@latest</code>的方式无法拿到最新的资源。改为使用具体的tag即可,如<code>username/[email protected]</code></p><h3 id="批量调整画布大小"><a href="#批量调整画布大小" class="headerlink" title="批量调整画布大小"></a>批量调整画布大小</h3><p>看板娘的实现是先定义一个canvas,并指定好内联宽高(也就是画布大小),然后进行绘制,最后再对canvas进行css渲染。所以如果初始化的canvas宽高和css的宽高比例不一致的话,就会导致画布绘制好之后,被css样式拉扯,导致人物比例失调。</p><p>写死指定默认的画布大小,并不能灵活适应所有模型。有些模型太大,画布放不完,有些模型又太小,人物描绘出来后悬空,还有模型大小不好控制等问题。</p><p>所以需要实现便捷调整看板娘画布信息,并存储到json文件中,提供框架加载人物画布大小,人物偏移量的能力。</p><p>同时调整大小和偏移的方法<br><code>matrix( scaleX(), skewY(), skewX(), scaleY(), translateX(), translateY() )</code></p><h3 id="常见问题"><a href="#常见问题" class="headerlink" title="常见问题"></a>常见问题</h3><p>live2d模型报错:Uncaught TypeError: Cannot read properties of undefined (reading ‘0’)<br>原因在于有些模型未支持点击事件,点击模型的时候就会报此错误,可以忽视</p><h2 id="添加滚动条皮肤"><a href="#添加滚动条皮肤" class="headerlink" title="添加滚动条皮肤"></a>添加滚动条皮肤</h2><p><a href="https://cv-programmer.github.io/2021/03/15/Hexo%E7%B3%BB%E5%88%97%EF%BC%88%E4%BA%8C%EF%BC%89%EF%BC%9A%E4%BF%AE%E6%94%B9hexo%E4%B8%BB%E9%A2%98/">Hexo系列(二):修改hexo主题 | cv-programmer</a></p><p><a href="https://rem486.github.io/web/css/chrome-scroll-bar.html">chrome 自定义滚动条样式 | 从零开始的故事 (rem486.github.io)</a></p><p><a href="http://lengyun.github.io/js/3-2-2dynamicAddCSS.html#%E4%B8%8E%E5%8A%A8%E6%80%81%E5%8A%A0%E8%BD%BDjs%E7%9A%84%E5%8C%BA%E5%88%AB">动态加载css | 冷云 (lengyun.github.io)</a></p><pre><code class="hljs css"><span class="hljs-comment">/* 设置滚动条的样式 */</span>::-webkit-scrollbar { width: <span class="hljs-number">8px</span>; <span class="hljs-attribute">height</span>: <span class="hljs-number">8px</span>;}<span class="hljs-comment">/* 滚动槽 */</span>::-webkit-scrollbar-track { -webkit-box-shadow: inset <span class="hljs-number">0</span> <span class="hljs-number">0</span> <span class="hljs-number">6px</span> <span class="hljs-built_in">rgba</span>(<span class="hljs-number">0</span>,<span class="hljs-number">0</span>,<span class="hljs-number">0</span>,<span class="hljs-number">0.2</span>); <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">8px</span>;}<span class="hljs-comment">/* 滚动条滑块 */</span>::-webkit-scrollbar-thumb { border-radius: <span class="hljs-number">8px</span>; <span class="hljs-attribute">background</span>: <span class="hljs-number">#bbb</span>; -webkit-<span class="hljs-attribute">box-shadow</span>: inset <span class="hljs-number">0</span> <span class="hljs-number">0</span> <span class="hljs-number">6px</span> <span class="hljs-built_in">rgba</span>(<span class="hljs-number">0</span>,<span class="hljs-number">0</span>,<span class="hljs-number">0</span>,<span class="hljs-number">0.25</span>);}<span class="hljs-comment">/* 非激活窗口 */</span>::-webkit-scrollbar-thumb:window-inactive { background: <span class="hljs-built_in">rgba</span>(<span class="hljs-number">0</span>,<span class="hljs-number">255</span>,<span class="hljs-number">0</span>,<span class="hljs-number">0.4</span>);}</code></pre><h2 id="添加标签云"><a href="#添加标签云" class="headerlink" title="添加标签云"></a>添加标签云</h2><h2 id="添加大纲"><a href="#添加大纲" class="headerlink" title="添加大纲"></a>添加大纲</h2><p>参考<a href="https://liuyib.github.io/2019/08/20/develop-hexo-theme-from-0-to-1/">从 0 到 1 开发 Hexo 主题杂谈 | Liuyib’s Blog</a></p><p>参考next,知道了aside使用的是<a href="https://getbootstrap.com/docs/3.4/javascript/#affix">affix库</a>,我们可以拿来用用。</p><h2 id="添加文章字数、阅读时长等属性"><a href="#添加文章字数、阅读时长等属性" class="headerlink" title="添加文章字数、阅读时长等属性"></a>添加文章字数、阅读时长等属性</h2><h3 id="一、引入wordcount插件"><a href="#一、引入wordcount插件" class="headerlink" title="一、引入wordcount插件"></a>一、引入wordcount插件</h3><pre><code class="hljs bash">npm i --save hexo-wordcount</code></pre><h3 id="二、增加字数统计,阅读时长的代码片段:"><a href="#二、增加字数统计,阅读时长的代码片段:" class="headerlink" title="二、增加字数统计,阅读时长的代码片段:"></a>二、增加<code>字数统计</code>,<code>阅读时长</code>的代码片段:</h3><p><a href="https://github.com/willin/hexo-wordcount">https://github.com/willin/hexo-wordcount</a> 找到对应模板的使用方法,本例中使用的模板是jade(现改名为pug)</p><pre><code class="hljs pug">mixin word_count span.post-meta-group span.with-love i.fa.fa-keyboard-o span.post-meta-item-text 文章字数: span.post-count= wordcount(page.content) span.post-meta-item-text 字mixin min2read span.post-meta-group span.with-love i.fa.fa-hourglass-half span.post-meta-item-text 阅读时长: span.post-count= min2read(page.content) span.post-meta-item-text min</code></pre><h3 id="三、效果演示"><a href="#三、效果演示" class="headerlink" title="三、效果演示"></a>三、效果演示</h3><p><img src="/images/Pasted%20image%2020220515193208.png"></p><p>参考文章:</p><ul><li><a href="https://moguangpeng998.github.io/2020/06/05/Hexo%E5%8D%9A%E5%AE%A2%E4%BC%98%E5%8C%96-%E6%B7%BB%E5%8A%A0%E5%AD%97%E6%95%B0%E7%BB%9F%E8%AE%A1%E5%92%8C%E9%98%85%E8%AF%BB%E6%97%B6%E9%95%BF/">https://moguangpeng998.github.io/2020/06/05/Hexo博客优化-添加字数统计和阅读时长/</a></li><li><a href="https://hexo.io/plugins/">https://hexo.io/plugins/</a> 搜索wordcount</li><li><a href="https://github.com/willin/hexo-wordcount">https://github.com/willin/hexo-wordcount</a></li><li><a href="https://html2jade.org/">https://html2jade.org/</a> html转jade</li><li><a href="https://jade-lang.com/reference/inheritance">https://jade-lang.com/reference/inheritance</a> 查询jade手册</li><li><a href="https://pugjs.org/api/getting-started.html">https://pugjs.org/api/getting-started.html</a> pug手册</li><li><a href="https://github.com/hexojs/hexo-renderer-jade">https://github.com/hexojs/hexo-renderer-jade</a> jade已废弃</li></ul><h2 id="离开页面展示个性标题"><a href="#离开页面展示个性标题" class="headerlink" title="离开页面展示个性标题"></a>离开页面展示个性标题</h2><p><a href="https://hzfans.github.io/2018/01/03/2018-1-3%20%E4%B8%BAHexo%20blog%E5%8D%9A%E5%AE%A2%E5%88%9B%E5%BB%BA%E6%9C%AC%E5%9C%B0%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E/">为Hexo blog博客创建本地搜索引擎 | 花子凡 (hzfans.github.io)</a></p><p>参考以下代码,引入到页面中,改成自己的个性标题即可</p><pre><div class="caption"><span>crash_cheat.js</span></div><code class="hljs javascript"><span class="hljs-comment">// <!--崩溃欺骗--></span><span class="hljs-keyword">var</span> OriginTitle = <span class="hljs-built_in">document</span>.title;<span class="hljs-keyword">var</span> titleTime;<span class="hljs-built_in">document</span>.addEventListener(<span class="hljs-string">'visibilitychange'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{ <span class="hljs-keyword">if</span> (<span class="hljs-built_in">document</span>.hidden) { $(<span class="hljs-string">'[rel="icon"]'</span>).attr(<span class="hljs-string">'href'</span>, <span class="hljs-string">"/img/TEP.ico"</span>); <span class="hljs-built_in">document</span>.title = <span class="hljs-string">'☀死鬼你去哪里了!'</span>; <span class="hljs-built_in">clearTimeout</span>(titleTime); } <span class="hljs-keyword">else</span> { $(<span class="hljs-string">'[rel="icon"]'</span>).attr(<span class="hljs-string">'href'</span>, <span class="hljs-string">"/avatar.jpg"</span>); <span class="hljs-built_in">document</span>.title = <span class="hljs-string">'☀欢迎回来'</span>; titleTime = <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{ <span class="hljs-built_in">document</span>.title = OriginTitle; }, <span class="hljs-number">2000</span>); }});</code></pre><h2 id="添加小火箭"><a href="#添加小火箭" class="headerlink" title="添加小火箭"></a>添加小火箭</h2><h2 id="修改个性代码框"><a href="#修改个性代码框" class="headerlink" title="修改个性代码框"></a>修改个性代码框</h2><h2 id="导航分类提供下拉框"><a href="#导航分类提供下拉框" class="headerlink" title="导航分类提供下拉框"></a>导航分类提供下拉框</h2><h2 id="添加文章结束分割线"><a href="#添加文章结束分割线" class="headerlink" title="添加文章结束分割线"></a>添加文章结束分割线</h2><p>参考:<a href="https://cjjkkk.github.io/next_live2d/">https://cjjkkk.github.io/next_live2d/</a><br><code>-------------本文结束<icon gift>感谢您的阅读-------------</code></p><h2 id="高级特性"><a href="#高级特性" class="headerlink" title="高级特性"></a>高级特性</h2><h3 id="添加到个人域名"><a href="#添加到个人域名" class="headerlink" title="添加到个人域名"></a>添加到个人域名</h3><h4 id="添加cdn"><a href="#添加cdn" class="headerlink" title="添加cdn"></a>添加cdn</h4><h3 id="SEO优化"><a href="#SEO优化" class="headerlink" title="SEO优化"></a>SEO优化</h3><h1 id="themes-config-yml设置"><a href="#themes-config-yml设置" class="headerlink" title="themes/_config.yml设置"></a>themes/_config.yml设置</h1><h2 id="设置banner图"><a href="#设置banner图" class="headerlink" title="设置banner图"></a>设置banner图</h2><pre><code class="hljs dts"><span class="hljs-symbol">header:</span><span class="hljs-symbol"> title:</span> <span class="hljs-string">"Xr's Blog"</span><span class="hljs-symbol"> motto:</span> Build a beautiful and simple website in minutes<span class="hljs-symbol"> bigimgs:</span> - src: <span class="hljs-meta-keyword">/bigimgs/</span><span class="hljs-number">04.</span>jpg<span class="hljs-symbol"> desc:</span> fukei - src: <span class="hljs-meta-keyword">/bigimgs/</span><span class="hljs-number">05.</span>jpg<span class="hljs-symbol"> desc:</span> fukei - src: <span class="hljs-meta-keyword">/bigimgs/</span><span class="hljs-number">06.</span>jpg<span class="hljs-symbol"> desc:</span> fukei</code></pre><h2 id="一些图标"><a href="#一些图标" class="headerlink" title="一些图标"></a>一些图标</h2><pre><code class="hljs avrasm"><span class="hljs-symbol">avatar:</span> /avatar.jfif<span class="hljs-symbol">favicon:</span> /avatar.jfif</code></pre><h2 id="布局自己的静态页面"><a href="#布局自己的静态页面" class="headerlink" title="布局自己的静态页面"></a>布局自己的静态页面</h2><ol><li>把静态页面及资源放到<code>source</code>目录下</li><li>修改页面html,顶部添加如下代码:<pre><code class="hljs html">---layout: false---<span class="hljs-tag"><<span class="hljs-name">html</span>></span> ...<span class="hljs-tag"></<span class="hljs-name">html</span>></span></code></pre></li></ol>]]></content>
</entry>
<entry>
<title>java探针技术</title>
<link href="/2021/11/26/java%E6%8E%A2%E9%92%88%E6%8A%80%E6%9C%AF/"/>
<url>/2021/11/26/java%E6%8E%A2%E9%92%88%E6%8A%80%E6%9C%AF/</url>
<content type="html"><![CDATA[<h3 id="vm参数"><a href="#vm参数" class="headerlink" title="vm参数"></a>vm参数</h3><ul><li>-javaagent:<pathname>[=<选项>]<br> 加载本机代理库 <libname>, 例如 -agentlib:hprof</li><li>-agentlib:<libname>[=<选项>]<br> 按完整路径名加载本机代理库</li><li>-agentpath:<pathname>[=<选项>]<br> 加载 Java 编程语言代理, 请参阅 java.lang.instrument</li></ul><h3 id="相关基础包"><a href="#相关基础包" class="headerlink" title="相关基础包"></a>相关基础包</h3><p> java.lang.instrument</p><h3 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h3><ul><li><a href="https://www.cnblogs.com/CLAYJJ/p/7992064.html">https://www.cnblogs.com/CLAYJJ/p/7992064.html</a></li></ul>]]></content>
</entry>
<entry>
<title>java类加载机制&实践案例分析</title>
<link href="/2021/11/26/java%E7%B1%BB%E5%8A%A0%E8%BD%BD%E6%9C%BA%E5%88%B6&%E5%AE%9E%E8%B7%B5%E6%A1%88%E4%BE%8B%E5%88%86%E6%9E%90/"/>
<url>/2021/11/26/java%E7%B1%BB%E5%8A%A0%E8%BD%BD%E6%9C%BA%E5%88%B6&%E5%AE%9E%E8%B7%B5%E6%A1%88%E4%BE%8B%E5%88%86%E6%9E%90/</url>
<content type="html"><![CDATA[<h1 id="定义"><a href="#定义" class="headerlink" title="定义"></a>定义</h1><p>所有的代码,都是用来描述数据结构和算法逻辑的<br>运行代码时,需要把陈述性的逻辑转换成物理性的数据结构,然后照着逻辑描述的算法去操作这些数据结构。</p><h2 id="java程序跑起来,需要哪几步?"><a href="#java程序跑起来,需要哪几步?" class="headerlink" title="java程序跑起来,需要哪几步?"></a>java程序跑起来,需要哪几步?</h2><ol><li>获取描述性的文本</li><li>按照特定语法,把文本转成物理结构,即一堆二进制。</li><li>按照特定语法,遵循着文本描述的逻辑去操作这些数据结构,如果在此时又遇到了其它描述性文本的名字(类名),也要走第1步把它获取到。</li></ol><p>发生在第1、3步的获取动作,就是类的加载</p><h2 id="类加载的机制有以下描述:"><a href="#类加载的机制有以下描述:" class="headerlink" title="类加载的机制有以下描述:"></a>类加载的机制有以下描述:</h2><ol><li>不同的类加载器,可以从不同的地方加载类文本,各司其职</li><li>A类中用到了B类,B类由A的类加载器加载</li><li>所有的类,优先由顶层类加载器自顶向下加载</li><li>Thread中可以自行设置类加载器供程序获取(默认是系统类加载器)</li></ol><p>通过以上描述,可以得知以下几个特性:</p><ol><li>在程序进行到某个节点时,能加载哪些类,取决于当前可以取得哪些classLoader,1. 加载类的caller的classloader;2. Thread中设置的classLoader</li><li>就算当前能获得<strong>预期的ClassLoader</strong>,还要父辈 类加载器 找不到类,<strong>它们</strong>才能派上用场</li></ol><h1 id="案例"><a href="#案例" class="headerlink" title="案例"></a>案例</h1><h2 id="一、tomcat"><a href="#一、tomcat" class="headerlink" title="一、tomcat"></a>一、tomcat</h2><p>一个Tomcat容器,即为一个JVM容器。</p><p>下面可以有多个Web应用(可理解为一个war包为一个应用),相当于多个web应用可以同时跑在一个tomcat容器下</p><p>不同的web应用会使用到不同版本的依赖包。都会加载到同一个jvm容器中。可以理解为:同一个类名存在 v1.0、v2.0 … 等多个版本,都被加载到同一个jvm的元数据区。</p><h3 id="如何保证多个相同全限定名的类互相不冲突?"><a href="#如何保证多个相同全限定名的类互相不冲突?" class="headerlink" title="如何保证多个相同全限定名的类互相不冲突?"></a>如何保证多个相同全限定名的类互相不冲突?</h3><p>每个web应用下的所有类(web-inf/classes,web-info/lib),都由一个新的WebAppClassLoader来加载。这样,它们新引入的类,也都会由这个loader来加载。各个web应用的loader,都只能加载各自范围内的类,这样就能保证各个web应用的类相互隔离。</p><h3 id="如何保证Jsp能热加载"><a href="#如何保证Jsp能热加载" class="headerlink" title="如何保证Jsp能热加载"></a>如何保证Jsp能热加载</h3><p>jsp本质也是一个描述逻辑的文本,也就是class,所以也需要被类加载器加载。当jsp被修改,为了不让程序去元数据区取已经加载过的类,要给这个类指定一个新的类加载器,来重新加载类文本。每个jsp类都由一个属于自己的类加载器。</p><h2 id="二、spring"><a href="#二、spring" class="headerlink" title="二、spring"></a>二、spring</h2><p>沿用上面描述的场景,一个tomcat容器下有多个web应用。但是spring框架包放在公共类库中,多个web应用都可以通过同一个祖先类加载器共享spring类。此时再通过spring类来创建各个web应用的bean,是不可能的,因为祖先类加载器无法加载不在其负责范围的类。</p><p>spring通过把每个web应用各自的WebAppClassLoader设置到线程中,在spring容器加载类时,直接用线程中的loader来加载。</p><p>此时,在一个JVM容器的元数据区中,spring类是独一份的,而各个web应用的类由它们自己的loader加载。spring获取线程loader的核心代码在 DefaultResourceLoader 中</p><h2 id="三、arthas"><a href="#三、arthas" class="headerlink" title="三、arthas"></a>三、arthas</h2><p>使用arthas的ognl命令,可以实时的调用jvm容器里的类的方法,或者获取类的属性。然而在实践中,通常会出现ClassNotFound异常。原因在于arthas使用的类加载器独立于系统类加载器,直接使用ognl命令会使用arthas的类加载器来加载类,导致找不到类。通过加上参数,指定使用具体的web应用类加载器,即可正常调用类方法了。</p>]]></content>
</entry>
<entry>
<title>linux系统目录层级标准——/bin、/usr/bin、/usr/local/bin、/opt的区别</title>
<link href="/2021/11/26/linux%E7%B3%BB%E7%BB%9F%E7%9B%AE%E5%BD%95%E5%B1%82%E7%BA%A7%E6%A0%87%E5%87%86/"/>
<url>/2021/11/26/linux%E7%B3%BB%E7%BB%9F%E7%9B%AE%E5%BD%95%E5%B1%82%E7%BA%A7%E6%A0%87%E5%87%86/</url>
<content type="html"><![CDATA[<h2 id="bin-VS-usr-bin-VS-usr-local-bin-VS-opt"><a href="#bin-VS-usr-bin-VS-usr-local-bin-VS-opt" class="headerlink" title="/bin VS /usr/bin/ VS /usr/local/bin VS /opt"></a>/bin VS /usr/bin/ VS /usr/local/bin VS /opt</h2><h3 id="当前标准"><a href="#当前标准" class="headerlink" title="当前标准"></a>当前标准</h3><p>wiki上搜索 FileSystem hierarchy standard,有对这些目录用途的一个官方标准的解析,但是对于 /usr/local 的解析,依然是模棱两可,所以要完全了解它们的本质,还是得纵观一下发展历史。</p><h3 id="历史趣闻"><a href="#历史趣闻" class="headerlink" title="历史趣闻"></a>历史趣闻</h3><p><a href="http://lists.busybox.net/pipermail/busybox/2010-December/074114.html">Understanding the bin, sbin, usr/bin , usr/sbin split (busybox.net)</a></p><p>据<strong>Rob Landley</strong>这位老哥的说法,/bin 和 /usr/bin 的分裂,属于历史产物,本质上没有区别。</p><p>参考<strong>Rob Landley</strong>的说法,再结合社区的讨论《<a href="https://unix.stackexchange.com/questions/11544/what-is-the-difference-between-opt-and-usr-local">What is the difference between /opt and /usr/local?</a>》,可知,/usr/local 源于BSD,要不就是用于编译自己编写的源码,或者用于存放第三方包源码(未安装),其构建结果就放在 /usr/local/bin 。一些人认为已打包好的第三方binary包不适合放在 /usr/local ,于是 /opt 出现了。</p><blockquote><p>Rob Landley:<br>and<br>/usr/local was for your specific installation’s files. Then somebody decided<br>/usr/local wasn’t a good place to install new packages, so let’s add /opt!<br>I’m still waiting for /opt/local to show up…</p></blockquote><blockquote><p>《What is the difference between /opt and /usr/local?》:<br><code>/usr/local</code> is a place to install files built by the administrator<br><code>/usr/local</code> is a legacy from the original BSD.</p></blockquote><p>这一篇讨论 <a href="https://unix.stackexchange.com/questions/332764/role-of-the-usr-local-directory-in-freebsd">Role of the /usr/local directory in FreeBSD</a> 可以进一步了解BSD的 /usr/local</p><h3 id="usr的含义"><a href="#usr的含义" class="headerlink" title="usr的含义"></a>usr的含义</h3><p>有两种说法</p><ol><li>unix system resource</li><li>user 的缩写</li></ol><p>根据上面的文章,<a href="https://twitter.com/linuxtoy/status/1228572721597964288">还有推特的讨论</a>,个人更倾向于 usr 就是user的缩写。</p>]]></content>
</entry>
<entry>
<title>mermaid语法</title>
<link href="/2021/11/26/mermaid%E8%AF%AD%E6%B3%95/"/>
<url>/2021/11/26/mermaid%E8%AF%AD%E6%B3%95/</url>
<content type="html"><![CDATA[<p>Mermaid是一种基于Javascript的绘图工具</p><p>示例:</p><p>时序图</p><pre><code class="hljs mermaid">sequenceDiagrama ->> b:asdb -->> a:asd</code></pre><p>类图</p><pre><code class="hljs mermaid">classDiagramClass01 <|-- AveryLongClass: CoolClass03 *-- Class04Class05 o-- Class06Class07 .. Class08Class09 --> C2: Where am i?Class09 --* C3Class09 --|> Class07Class07: equals()Class07: Object[] elemeentDataClass01: size()Class01: int chimpClass01: int gorillaClass08 <--> C2: Cool label</code></pre>]]></content>
</entry>
<entry>
<title>visual studio 2019实践整理</title>
<link href="/2021/11/26/visual%20studio%202019%E5%AE%9E%E8%B7%B5%E6%95%B4%E7%90%86/"/>
<url>/2021/11/26/visual%20studio%202019%E5%AE%9E%E8%B7%B5%E6%95%B4%E7%90%86/</url>
<content type="html"><![CDATA[<p>简介<br>c、c++([[cpp]])以及.net框架(c#、vb等)的通用开发ide</p><p>经验整理</p><p>找不到 Windows SDK<br>在项目上右击选择重定目标解决方案,将windosSdk的版本号选择成合适的</p><p>为何c#不需要include头文件<br>c#中用using namespace完成了java中import packages的能力。前提是项目引用中需要指定了相应的dll,然后using就可以了。</p><p>MSB8040 此项目需要缓解了 Spectre 漏洞的库。<br>打开安装器,选择<strong>修改</strong> -> <strong>单个组件</strong>,搜索MSVC查看版本(如:v14.28),然后安装相应的MSVC缓解库</p><p>E1696 cannot open source file<br>删掉依赖包重新nuget下载</p><p>LINK : fatal error LNK1104: cannot open file ‘atls.lib’ 问题<br>安装atl、mfc缓解库</p><p>powershell 执行 ps脚本,报 PSSecurityException<br>解除文件锁定,<code>Get-ChildItem -r|Unblock-File</code></p><p>c++项目没有设置output directory<br>一般是通过以下配置设置了:<br><code><Import Project="$(VCTargetsPath)\Microsoft.cpp.Default.props" /></code></p><p>/obj 是什么文件夹<br>c++的中间产物存放处</p>]]></content>
</entry>
<entry>
<title>事务隔离级别</title>
<link href="/2021/11/26/%E4%BA%8B%E5%8A%A1%E9%9A%94%E7%A6%BB%E7%BA%A7%E5%88%AB/"/>
<url>/2021/11/26/%E4%BA%8B%E5%8A%A1%E9%9A%94%E7%A6%BB%E7%BA%A7%E5%88%AB/</url>
<content type="html"><![CDATA[<p>read uncommited<br>read commited<br>repeatable read<br>serializable</p><p>关注事务过程中<strong>可读数据</strong>的表现</p><ol><li>未提交也可读</li><li>提交后才可读,同一条数据会读存储系统(脏读</li><li>同一条数据从缓存取,新读取的数据从存储系统取(幻读</li><li>串行化</li></ol>]]></content>
</entry>
<entry>
<title>执行计划查询手册</title>
<link href="/2021/11/26/%E6%89%A7%E8%A1%8C%E8%AE%A1%E5%88%92%E6%9F%A5%E8%AF%A2%E6%89%8B%E5%86%8C/"/>
<url>/2021/11/26/%E6%89%A7%E8%A1%8C%E8%AE%A1%E5%88%92%E6%9F%A5%E8%AF%A2%E6%89%8B%E5%86%8C/</url>
<content type="html"><![CDATA[<p> 表关联查询</p><ul><li>先从驱动表查询出一批数据</li><li>再用这批数据去关联表查询</li></ul><p>核心就在于,两个表对应的查询条件都应该有对应的索引</p><p>成本计算</p><table><thead><tr><th>场景</th><th>数值</th><th>描述</th></tr></thead><tbody><tr><td>查一页数据</td><td>1.0</td><td>io成本</td></tr><tr><td>筛选一条数据</td><td>0.2</td><td>cpu成本</td></tr></tbody></table><p>一条sql里可能用的到的索引叫做possibleKeys</p><p>查询一个表有多少页</p><ol><li>执行 <code>show table status like 表名</code></li><li>获取data_length</li><li>计算 页 = data_length / 16kb</li><li>获取rows</li><li>总成本 = io成本+cpu成本 = 页数 * 1.0 + rows * 0.2</li></ol><p>子查询场景</p><ul><li>先执行子查询语句,生成物化表</li><li>遍历物化表,到主表查索引</li></ul><p>explain表</p><table><thead><tr><th>column</th><th>describe</th></tr></thead><tbody><tr><td>id</td><td></td></tr><tr><td>select_type</td><td></td></tr><tr><td>table</td><td></td></tr><tr><td>type</td><td></td></tr><tr><td>possible_keys</td><td></td></tr><tr><td>key</td><td></td></tr><tr><td>key_len</td><td></td></tr><tr><td>ref</td><td></td></tr><tr><td>rows</td><td>会扫描这个表的多少记录</td></tr><tr><td>Extra</td><td>用什么方式来匹配记录。nestedLoop表示会用上一张表的每一条结果,来循环查询当前表的每条记录</td></tr></tbody></table><table><thead><tr><th>selectType类型</th><th>解释</th></tr></thead><tbody><tr><td>simple</td><td>普通的单表或多表查询</td></tr><tr><td>primary</td><td>存在多个独立查询时,主查询语句</td></tr><tr><td>subQuery</td><td></td></tr><tr><td>union</td><td>union查询的第二个查询</td></tr><tr><td>union_result</td><td>union查询的整合步骤</td></tr><tr><td>dependent</td><td>标记多个子查询</td></tr><tr><td>derived</td><td>生成物化表</td></tr></tbody></table><table><thead><tr><th>type类型</th><th>解释</th></tr></thead><tbody><tr><td>const</td><td>走唯一索引</td></tr><tr><td>ref</td><td>走非唯一二级索引</td></tr><tr><td>range</td><td>走索引进行范围查询</td></tr><tr><td>ref_or_null</td><td>走非唯一索引,且 is null</td></tr><tr><td>index</td><td>只需要遍历二级索引,不需要回表的操作</td></tr><tr><td>all</td><td>全表扫描</td></tr><tr><td>eq_ref</td><td>走主键,针对被驱动表</td></tr></tbody></table>]]></content>
</entry>
<entry>
<title>生产问题排查思路</title>
<link href="/2021/11/26/%E7%94%9F%E4%BA%A7%E9%97%AE%E9%A2%98%E6%8E%92%E6%9F%A5%E6%80%9D%E8%B7%AF/"/>
<url>/2021/11/26/%E7%94%9F%E4%BA%A7%E9%97%AE%E9%A2%98%E6%8E%92%E6%9F%A5%E6%80%9D%E8%B7%AF/</url>
<content type="html"><![CDATA[<h3 id="做足监控"><a href="#做足监控" class="headerlink" title="做足监控"></a>做足监控</h3><p>Prometheus——监控工具<br>ELK——日志收集、展示套件<br>Micrometer——监控JVM</p><h3 id="常见指标"><a href="#常见指标" class="headerlink" title="常见指标"></a>常见指标</h3><p>应用:业务日志、错误日志<br>JVM:GC、内存、类加载、线程<br>OS:硬盘、内存、cpu、网络<br>外部:专线带宽、交换机基本情况、网络延迟</p><h3 id="定位工具"><a href="#定位工具" class="headerlink" title="定位工具"></a>定位工具</h3><table><thead><tr><th>目标</th><th>工具</th></tr></thead><tbody><tr><td>cpu</td><td>top、vmstat、pidstat、ps</td></tr><tr><td>内存</td><td>free、top、ps、vmstat、cachestat、sar</td></tr><tr><td>IO</td><td>lsof、iostat、pidstat、sar、iotop、df、du</td></tr><tr><td>网络</td><td>ifconfig、ip、nslookup、dig、ping、tcpdump、iptables</td></tr></tbody></table><h3 id="实践案例"><a href="#实践案例" class="headerlink" title="实践案例"></a>实践案例</h3><ol><li>top + jstack,分析阻塞线程</li><li>压测+Jprofiler,分析热点函数</li><li>查看gclog,分析gc情况</li><li>netstat 检查外调连接数量,大量tcp堆积说明调用某个服务有问题,tcpdump、wireshark 分析外调的详细信息</li><li>观察曲线指标,达到水平线上说明资源瓶颈</li><li>jmap + jhat + jvisualvm ,导出+分析堆内存快照</li><li>jconsole 监控jvm基础资源使用状况</li></ol><hr><h4 id="top-jstack,分析阻塞线程"><a href="#top-jstack,分析阻塞线程" class="headerlink" title="top + jstack,分析阻塞线程"></a>top + jstack,分析阻塞线程</h4><pre><code class="hljs shell">top -Hp $pid #获取$pid对应的所有线程p #按cpu消耗排序jstack -l $pid #获取进程对应的所有线程栈快照</code></pre><h4 id="查看gclog,分析gc情况"><a href="#查看gclog,分析gc情况" class="headerlink" title="查看gclog,分析gc情况"></a>查看gclog,分析gc情况</h4><p>可以使用ps获取gcLog的位置,如果是GC压力,说明内存使用不正常,从而进一步跟踪内存快照,排查内存情况</p><pre><code class="hljs shell">ps -ef|grep bootstrap|grep gc</code></pre>]]></content>
</entry>
<entry>
<title>编码探究</title>
<link href="/2021/11/26/%E7%BC%96%E7%A0%81%E6%8E%A2%E7%A9%B6/"/>
<url>/2021/11/26/%E7%BC%96%E7%A0%81%E6%8E%A2%E7%A9%B6/</url>
<content type="html"><![CDATA[<h2 id="编码的本质"><a href="#编码的本质" class="headerlink" title="编码的本质"></a>编码的本质</h2><p>编码的本质含义是对一组事物进行标号。例:</p><ul><li>二进制编码:1是对高电平的标号,0是对低电平标号</li><li>ASCII编码:61是对字符图像<code>a</code>的标号,41是对字符图像<code>A</code>的标号</li><li>UTF8编码:113是字符图像<code>国</code>的标号</li><li>base64编码:对数字<code>0</code>,标记为A; 对数字<code>63</code>,标记为 /</li></ul><p>由上可见,标号的对象可以是任何事物,如电平、图像、甚至是数字。而标记的记号,也不一定是数字,如base64反直觉的用图像反过来标记数字。这取决于编码的实际用途,ASCII、UTF8这类字符集编码,目的是为了把图像转换成二进制; 而二进制编码,其中一个用途就是把电信号抽象成0、1两个数字,实现硬件层面的信息传输; 此时,二进制就成为了虚拟图形和现实电信号的桥梁,真正意义上的实现了底层的数据传输。而对于某些二进制数据,没有相应的字符集可以把它们转换为图像,为了便于人为操作这类数据(阅读、复制粘贴),base64出现了。</p><p>总结一下,上述各编码的用途:</p><ul><li>二进制编码:底层信息传输的基础</li><li>字符集编码:为了使文字图形能够在硬件层面传输</li><li>base64编码:为了方便人类操作二进制数据,主要还是复制粘贴</li></ul><p>到此为止,我们可以说,对照着某种规则,把目标转为编号,称之为<strong>编码</strong>;把编号转为目标对象,称之为<strong>解码</strong>。</p><h2 id="JVM-参数:file-encoding"><a href="#JVM-参数:file-encoding" class="headerlink" title="JVM 参数:file.encoding"></a>JVM 参数:file.encoding</h2><hr><p>jvm 运行时采用unicode编码,所以java内部不会存在乱码问题。但是跟外部交互的文本,还是需要关注编码。</p><p>如果设置了file.encoding参数,System.in、System.out、string.getBytes()等都会使用这个设置的编码解析数据,否则使用平台的默认编码。</p><h2 id="JDBC编码"><a href="#JDBC编码" class="headerlink" title="JDBC编码"></a>JDBC编码</h2><hr><p>因为JAVA系统运行时采用unicode编码,如果数据库采用GBK或者utf8编码,则可能出现乱码问题。<br>Oracle数据库采用唯一编码,oracle的jdbc会自动将unicode转换为目标Oracle数据库的编码。<br>而Mysql可以同时使用多种编码,所以需要在Mysql JDBC的url中指定目标数据库的编码。</p><h2 id="servlet服务请求、响应编码"><a href="#servlet服务请求、响应编码" class="headerlink" title="servlet服务请求、响应编码"></a>servlet服务请求、响应编码</h2><hr><h3 id="ContentType"><a href="#ContentType" class="headerlink" title="ContentType"></a>ContentType</h3><p>可在请求、响应的ContentType处指定body的编码,如下:<br><code>Content-Type:application/x-www-form-urlencoded;charset=utf-8</code></p><h3 id="servletFilter"><a href="#servletFilter" class="headerlink" title="servletFilter"></a>servletFilter</h3><p>也可以统一在<br>无论是Spring的CharacterEncodingFilter还是request的setCharacterEncoding,都是为byte[]指定解码的规则,而不是转换。</p><blockquote><p>注意:CharacterEncodingFilter要设置在其他filter之前,以免parameter被提前解析</p></blockquote><h3 id="urlEncode"><a href="#urlEncode" class="headerlink" title="urlEncode"></a>urlEncode</h3><p>urlEncode就是对url特殊字符 = 、&、? 等进行转义的编码方式,需要指定字符集编码,流程是:先编码 -> 转成HexString -> 每个HexString前加%</p><blockquote><p>经测试,Edge、postMan默认中文编码都是utf8</p></blockquote><h3 id="tomcat-连接器编码"><a href="#tomcat-连接器编码" class="headerlink" title="tomcat 连接器编码"></a>tomcat 连接器编码</h3><p>在post请求中,url和body是可以分开编码的(参考[[#urlEncode]]),而body可以相对灵活地指定编码。useBodyEncodingForURI、UriEncoding 都是用于指定 url 解码用的。当未指定时,tomcat默认使用utf8解码。</p><blockquote><p>关于body、url 编码的更详细研究,可以参考该<a href="https://www.cnblogs.com/yvkm/p/10551484.html">文章</a></p></blockquote><h4 id="1、UriEncoding"><a href="#1、UriEncoding" class="headerlink" title="1、UriEncoding"></a>1、UriEncoding</h4><p>解析参数时默认指定的url解码字符集,映射于tomcat源码中的:<code>queryStringCharset</code></p><h4 id="2、useBodyEncodingForURI"><a href="#2、useBodyEncodingForURI" class="headerlink" title="2、useBodyEncodingForURI"></a>2、useBodyEncodingForURI</h4><p>此时把该配置设为<code>true</code>,可以在解析url时使用body指定的编码(参考[[#ContentType]]),相当于强行覆盖body编码给url。它只是针对如”author=君山”的查询参数(QueryString)有效,他的设置对于URL和URI无效。</p><h3 id="编码优先级"><a href="#编码优先级" class="headerlink" title="编码优先级"></a>编码优先级</h3><p>body:contentType > filter > 默认<br>url:useBodyEncodingForURI > UriEncoding</p><h2 id="C2-C3疑案"><a href="#C2-C3疑案" class="headerlink" title="C2 C3疑案"></a>C2 C3疑案</h2><p>在服务器接收中文参数(“捕鱼”)后,用iSO-8859-1解码,字符串变乱码,对乱码用不同的字符集编码,得到了两套HexString</p><table><thead><tr><th></th><th>1</th><th>2</th><th>3</th><th>4</th><th>5</th><th>6</th></tr></thead><tbody><tr><td>客户端utf8编码</td><td>E6</td><td>8D</td><td>95</td><td>E9</td><td>B1</td><td>BC</td></tr><tr><td>8859 解码得到乱码字符</td><td>æ</td><td></td><td></td><td>é</td><td>±</td><td>¼</td></tr><tr><td>8859对字符再编码</td><td>E6</td><td>8D</td><td>95</td><td>E9</td><td>B1</td><td>BC</td></tr><tr><td>utf8对字符再编码</td><td>C3 A6</td><td>C2 8D</td><td>C2 95</td><td>C3 A9</td><td>C2 B1</td><td>C2 BC</td></tr></tbody></table><p>可以发现,①8859对乱码字符的编码,②utf8对乱码字符的编码。同一套字符,两套编码之间有种神秘而又规律的联系。</p><p>通过对比UTF8、ISO-8859-1两套字符集的对比,可以还原这里面的真相</p><h3 id="utf8编码规则"><a href="#utf8编码规则" class="headerlink" title="utf8编码规则"></a>utf8编码规则</h3><p>UTF-8是一种变长字节编码方式。对于某一个字符的UTF-8编码,如果只有一个字节则其最高二进制位为0;如果是多字节,其第一个字节从最高位开始,连续的二进制位值为1的个数决定了其编码的位数,其余各字节均以10开头。UTF-8最多可用到6个字节。<br>如表:<br>1字节 0xxxxxxx<br>2字节 110xxxxx 10xxxxxx<br>3字节 1110xxxx 10xxxxxx 10xxxxxx<br>4字节 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx<br>5字节 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx<br>6字节 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx</p><h3 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h3><p>西欧码(8859)的编码方式是纯序列。而UTF8的编码方式是 Unicode序列+特殊前缀,是为了达到可变长的目的。</p><p>ISO-8859-1字符集,可以算作ASCII的拓展,共256个字符,用一个Byte可以装完。所以当使用 8859 解码,得到的是与字节数一一对应的字符。而在UTF8字符集中,前256个字符的序列和 8859 是一致的。只是在编码时,还会对字符序列加上特定规则([[#utf8编码规则]])。此时UTF8再编码的序列中,频繁出现的C2、C3,实际上都是因为utf8的这种特殊的前缀规则导致的。</p><p>这个问题极易误导人地方在于,它可以让你解读为:①utf8对原字符的编码,②utf8对乱码字符的编码。同一套编码,两套字符之间有种神秘而又规律的联系。</p>]]></content>
<tags>
<tag> 编码 </tag>
</tags>
</entry>
<entry>
<title>hexo经验</title>
<link href="/2021/11/24/hexo%E7%BB%8F%E9%AA%8C/"/>
<url>/2021/11/24/hexo%E7%BB%8F%E9%AA%8C/</url>
<content type="html"><![CDATA[<h3 id="图片加载不出"><a href="#图片加载不出" class="headerlink" title="图片加载不出"></a>图片加载不出</h3><ul><li>asset文件名不能有空格<ul><li>文件名、引用都去掉空格</li><li>或者引用的空格进行url编码:%20</li></ul></li><li>marked渲染器的appendRoot,是指给asset地址插入 root 值</li></ul><h3 id="批量修改文件名"><a href="#批量修改文件名" class="headerlink" title="批量修改文件名"></a>批量修改文件名</h3><p>rename 命令</p><pre><code class="hljs shell">rename oldstr newstr filesrename 's/oldstr/newstr/' files</code></pre><h3 id="替换文件内容"><a href="#替换文件内容" class="headerlink" title="替换文件内容"></a>替换文件内容</h3><pre><code class="hljs shell">sed -i "s/原字符串/新字符串/g" `grep 原字符串 -rl 所在目录`sed -选项 "行,行 动作命令 动作命令参数 显示命令(p)" 文件</code></pre><blockquote><p>invalid reference \1 on `s’ command’s RHS 错误:<br>-n 和 -i 不能同时使用,要么打印,要么修改</p></blockquote><h4 id="优秀实践"><a href="#优秀实践" class="headerlink" title="优秀实践"></a>优秀实践</h4><pre><code class="hljs shell"><span class="hljs-meta">#</span><span class="bash"> 先通过grep找到想要的内容</span><span class="hljs-meta">$</span><span class="bash"> grep <span class="hljs-string">"\!\["</span> *</span><span class="hljs-meta">></span><span class="bash"> Graphviz.md:![](/images/Pasted_image_20201218173054.png)</span><span class="hljs-meta"></span><span class="hljs-meta">#</span><span class="bash"> 使用sed -n 模式看下替换效果(-r表示使用正则)</span><span class="hljs-meta">#</span><span class="bash"> 先写个伪代码</span><span class="hljs-meta">$</span><span class="bash"> sed -nr <span class="hljs-string">"s|![[.*?]]|test|gp"</span> *</span><span class="hljs-meta"></span><span class="hljs-meta">#</span><span class="bash"> 给特殊字符转义</span><span class="hljs-meta">$</span><span class="bash"> sed -nr <span class="hljs-string">"s|\!\[\[.*?\]\]|test|gp"</span> *</span><span class="hljs-meta">></span><span class="bash"> <span class="hljs-built_in">test</span></span><span class="hljs-meta"></span><span class="hljs-meta">#</span><span class="bash"> 获取关键属性</span><span class="hljs-meta">$</span><span class="bash"> sed -nr <span class="hljs-string">"s|\!\[\[(.*?)\]\]|\1|gp"</span> *</span><span class="hljs-meta">></span><span class="bash"> Pasted_image_20201218173054.png</span><span class="hljs-meta"></span><span class="hljs-meta">#</span><span class="bash"> 补全新串</span><span class="hljs-meta">$</span><span class="bash"> sed -nr <span class="hljs-string">"s|\!\[\[(.*?)\]\]|\!\[\]\(\/images\/\1\)|gp"</span> *</span><span class="hljs-meta">></span><span class="bash"> ![](/images/Pasted_image_20201218173054.png)</span><span class="hljs-meta"></span><span class="hljs-meta">#</span><span class="bash"> 改为 sed -i 替换模式(注意-i -r 要分开)</span><span class="hljs-meta">$</span><span class="bash"> sed -i -r <span class="hljs-string">"s|\!\[\[(.*?)\]\]|\!\[\]\(\/images\/\1\)|g"</span> *</span></code></pre><h3 id="什么是Permalink"><a href="#什么是Permalink" class="headerlink" title="什么是Permalink"></a>什么是Permalink</h3><p>Permalink是指部署网站时文章的url路径的展示方式。</p><h3 id="好用的插件"><a href="#好用的插件" class="headerlink" title="好用的插件"></a>好用的插件</h3><ul><li>pangu:盘古空白,可以自动给中英文间插入空白</li></ul><h3 id="主题渲染原理"><a href="#主题渲染原理" class="headerlink" title="主题渲染原理"></a>主题渲染原理</h3><p><img src="/images/Pasted%20image%2020211124145841.png"></p><h3 id="模板引擎"><a href="#模板引擎" class="headerlink" title="模板引擎"></a>模板引擎</h3><p>模板引擎分为两部分:语言,渲染器</p><p>常见语言有:</p><ul><li>jade,因为商标问题已经更名为pug,例:beautiful-hexo主题</li><li>swig,例next主题</li></ul><p>通过对应的渲染器(hexo-renderer-xxx)可以渲染成完整的html</p><h4 id="语法概要"><a href="#语法概要" class="headerlink" title="语法概要"></a>语法概要</h4><p>模板引擎语法通常有以下几种操作组合而成,<strong>本质上就是堆积木!</strong>:</p><ul><li>extends:扩展</li><li>include:引入</li><li>block:类似于抽象方法,子孙可以覆写具体实现(模板方法模式)</li></ul><blockquote><p>由于 <code>layout.pug</code> 只用于被其他页面继承,并不会单独渲染成页面,因此,可以将文件名改为 <code>_layout.pug</code>(以下划线开头)这样 Hexo 就不会解析这个文件,可以提高 Hexo 生成页面的速度。</p></blockquote><h5 id="pug-语法概要"><a href="#pug-语法概要" class="headerlink" title="pug 语法概要"></a>pug 语法概要</h5><ul><li>子模块用缩进表示</li><li><code>header.someClass</code>表示<code>header</code>标签带<code>class=someClass</code></li><li><code>.someClass</code>单独出现表示默认使用<code>div</code>标签</li></ul><h3 id="使用模板优先级"><a href="#使用模板优先级" class="headerlink" title="使用模板优先级"></a>使用模板优先级</h3><p>layout<br> page > post > index</p><p>意思是,layout会作为通用模板。不在_posts目录下的文章属于page,page文章优先匹配page模板,若没有page模板则匹配post模板,若没有post模板则匹配index模板。</p><p>hexo支持的根模板:<a href="https://hexo.io/docs/templates">https://hexo.io/docs/templates</a></p><h3 id="如何不渲染指定文件"><a href="#如何不渲染指定文件" class="headerlink" title="如何不渲染指定文件"></a>如何不渲染指定文件</h3><p>以下两种方法均可</p><ol><li><p>参考官方文档:<a href="https://hexo.io/zh-cn/docs/configuration.html">https://hexo.io/zh-cn/docs/configuration.html</a><br>关键字:skip_render</p></li><li><p>参考官方文档:<a href="https://hexo.io/zh-cn/docs/writing.html">https://hexo.io/zh-cn/docs/writing.html</a><br>关键字:layout: false</p></li></ol><p>扩展阅读:<a href="https://github.com/hexojs/hexo/issues/1146">https://github.com/hexojs/hexo/issues/1146</a></p>]]></content>
<tags>
<tag> hexo </tag>
</tags>
</entry>
<entry>
<title>c++学习</title>
<link href="/2021/11/18/c++%E5%AD%A6%E4%B9%A0/"/>
<url>/2021/11/18/c++%E5%AD%A6%E4%B9%A0/</url>
<content type="html"><![CDATA[<h1 id="学习资料"><a href="#学习资料" class="headerlink" title="学习资料"></a>学习资料</h1><h2 id="书籍"><a href="#书籍" class="headerlink" title="书籍"></a>书籍</h2><ul><li><c++ primer plus></li></ul><h2 id="视频"><a href="#视频" class="headerlink" title="视频"></a>视频</h2><p>【零基础学C++】老九零基础学编程系列之C++<br><a href="https://www.bilibili.com/video/BV12x411D7xr?p=102">https://www.bilibili.com/video/BV12x411D7xr?p=102</a></p><h1 id="入门"><a href="#入门" class="headerlink" title="入门"></a>入门</h1><h2 id="windows环境"><a href="#windows环境" class="headerlink" title="windows环境"></a>windows环境</h2><h3 id="hello-world"><a href="#hello-world" class="headerlink" title="hello world"></a>hello world</h3><ol><li>安装cygwin</li><li>cygwin安装g++</li><li>编写hello world.cpp文件</li><li>使用g++编译第一个 hello world 文件</li></ol><h2 id="关键知识点实践"><a href="#关键知识点实践" class="headerlink" title="关键知识点实践"></a>关键知识点实践</h2><ul><li>编写 sizeof(struct) ,观察struct 的内存占用<ul><li>sizeof(数组) ,输出的是数组占用内存</li><li>输出数组长度</li></ul></li><li>使用sort(arr)排序,需要引入 <algroithm> 包</li><li>指针需要习惯赋初值:0</li><li>指针动态分配内存:<code>int* num = new int[5]</code><ul><li>记得手动释放:<code>delete[] num</code></li></ul></li><li>函数传递指针、引用作为参数</li><li>函数传递函数指针作为参数</li><li>数组作为函数入参,内容容易被修改,若不能被改,入参定义前可加<code>const</code></li></ul><h3 id="自定义函数"><a href="#自定义函数" class="headerlink" title="自定义函数"></a>自定义函数</h3><ol><li>函数原型(可省略参数名,调用前必须先写原型</li><li>函数定义(逻辑实现</li><li>返回值类型不能是数组,参数可以</li></ol><h3 id="函数指针"><a href="#函数指针" class="headerlink" title="函数指针"></a>函数指针</h3><p>声明:</p><pre><code class="hljs c++"><span class="hljs-comment">//函数原型</span><span class="hljs-function"><span class="hljs-keyword">double</span> <span class="hljs-title">sum</span><span class="hljs-params">(<span class="hljs-keyword">double</span>,<span class="hljs-keyword">double</span>)</span></span>;<span class="hljs-comment">//函数指针类型声明</span><span class="hljs-function"><span class="hljs-keyword">typedef</span> <span class="hljs-title">double</span> <span class="hljs-params">(*ptrSum)</span><span class="hljs-params">(<span class="hljs-keyword">double</span>,<span class="hljs-keyword">double</span>)</span></span>; <span class="hljs-comment">//此时ptrSum表示类型</span><span class="hljs-comment">//函数指针变量声明</span><span class="hljs-built_in"><span class="hljs-keyword">double</span></span> (*ptrSum)(<span class="hljs-keyword">double</span>,<span class="hljs-keyword">double</span>); <span class="hljs-comment">//此时ptrSum表示变量名</span><span class="hljs-comment">//函数指针赋值</span>ptrSum = sum;<span class="hljs-comment">//函数原型中,入参类型声明为:(函数指针类型,数1,数2)</span><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">print_result</span><span class="hljs-params">(<span class="hljs-keyword">double</span> (*)(<span class="hljs-keyword">double</span>,<span class="hljs-keyword">double</span>),<span class="hljs-keyword">double</span>,<span class="hljs-keyword">double</span>)</span></span>;</code></pre><h2 id="关键概念理清"><a href="#关键概念理清" class="headerlink" title="关键概念理清"></a>关键概念理清</h2><ul><li># 开头的<strong>都</strong>是<strong>预处理</strong>指令,就是编译之前编译器的预操作</li><li>运算符重载,应用:迭代器 iterator++、指针++(与前面一回事)</li><li>数组名就是数组的首<strong>地址</strong>,所以定义指针时,数组名前不用加&<pre><code class="hljs c++"><span class="hljs-keyword">double</span> score[] {<span class="hljs-number">1.1</span>, <span class="hljs-number">1.2</span>, <span class="hljs-number">22.3</span>};<span class="hljs-keyword">double</span> * ptr_score = score;<span class="hljs-comment">//typeof score = double[]</span><span class="hljs-comment">//typeof ptr_score = 指针<T></span></code></pre></li><li>指针类型(地址)++,只会指向新地址,+的是类型长度</li><li>普通变量赋值:开辟新区域,变量重新指向,指针赋值:在指针区域直接赋值,引用变量的赋值:引用区域直接赋值(本质是指针)</li><li>endl 和 \n 的区别:endl带flush</li><li>动态数组vector,使用:<ul><li>可见iterator是一个数组指针,arr.begin()是数组头地址,arr.end()同理<pre><code class="hljs c++">vector<<span class="hljs-keyword">int</span>> arr {<span class="hljs-number">2</span>,<span class="hljs-number">3</span>,<span class="hljs-number">4</span>};vector<<span class="hljs-keyword">int</span>>::iterator it;it = arr.<span class="hljs-built_in">begin</span>();<span class="hljs-keyword">while</span>(it != arr.<span class="hljs-built_in">end</span>()){it++;cout << *it << endl;}</code></pre></li></ul></li><li>void* 指针,可以存任意对象地址,但无法赋值,只能作对比用</li><li>指针变量(<em>p++)的优先级是(</em>(p++)),先运算再寻址</li></ul><h2 id="与java的差异点"><a href="#与java的差异点" class="headerlink" title="与java的差异点"></a>与java的差异点</h2><ul><li>数组初始化不需要加 = 号<pre><code class="hljs c++"><span class="hljs-keyword">int</span> array[] {<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>,<span class="hljs-number">6</span>,<span class="hljs-number">7</span>,<span class="hljs-number">8</span>};</code></pre></li></ul><h2 id="概念图"><a href="#概念图" class="headerlink" title="概念图"></a>概念图</h2><h3 id="编译过程"><a href="#编译过程" class="headerlink" title="编译过程"></a>编译过程</h3><p><img src="/images/img/Pasted%20image%2020201114230333.png"></p>]]></content>
<categories>
<category> c++ 系列 </category>
</categories>
<tags>
<tag> c++ </tag>
</tags>
</entry>
<entry>
<title>hexo实践</title>
<link href="/2021/11/17/hexo%E5%AE%9E%E8%B7%B5/"/>
<url>/2021/11/17/hexo%E5%AE%9E%E8%B7%B5/</url>
<content type="html"><![CDATA[<h1 id="scaffolds(模板)"><a href="#scaffolds(模板)" class="headerlink" title="scaffolds(模板)"></a>scaffolds(模板)</h1><p>可以在 根目录/scaffolds/ 下,创建一些文章模板,让新建文章变得更加高效。在该目录下,已经存在了一些现成的模板:draft.md、page.md、post.md,这些都是可以拿来做参考的。</p><h2 id="使用模板"><a href="#使用模板" class="headerlink" title="使用模板"></a>使用模板</h2><p>就跟使用其他layout一样,比如我现在已经在scaffolds下,创建了一个photo.md模板,则可以通过以下命令来基于该模板创建文章:</p><pre><code class="hljs gauss">$ hexo <span class="hljs-keyword">new</span> photo <<span class="hljs-built_in">title</span>></code></pre><h1 id="发布文章"><a href="#发布文章" class="headerlink" title="发布文章"></a>发布文章</h1><p>把草稿发布到post下</p><pre><code class="hljs xml">$ hexo publish draft <span class="hljs-tag"><<span class="hljs-name">title</span>></span></code></pre><h2 id="预览发布"><a href="#预览发布" class="headerlink" title="预览发布"></a>预览发布</h2><p>若不想直接发布草稿,想要先预览一下效果,有两种方式:</p><ol><li>临时性:跑 <code>hexo server</code>时带上<code>--draft</code></li><li>永久性:修改 <code>_config.yml</code> 文件,开启<code>render_drafts</code></li></ol><h1 id="文章头部"><a href="#文章头部" class="headerlink" title="文章头部"></a>文章头部</h1><p>文章头部提供以下几种属性:</p><table><thead><tr><th>Setting</th><th>Description</th><th>Default</th></tr></thead><tbody><tr><td>layout</td><td>Layout</td><td></td></tr><tr><td>title</td><td>Title</td><td></td></tr><tr><td>date</td><td>Published date</td><td>File created date</td></tr><tr><td>updated</td><td>Updated date</td><td>File updated date</td></tr><tr><td>comments</td><td>Enables comment feature for the post</td><td>true</td></tr><tr><td>tags</td><td>Tags (Not available for pages)</td><td></td></tr><tr><td>categories</td><td>Categories (Not available for pages)</td><td></td></tr><tr><td>permalink</td><td>Overrides the default permalink of the post</td><td></td></tr></tbody></table><h2 id="tags-与-categories-的区别"><a href="#tags-与-categories-的区别" class="headerlink" title="tags 与 categories 的区别"></a>tags 与 categories 的区别</h2><p>一篇文章中的多个tags,它们都是同一层级的,没有优先度之分<br>而categories是带层级的,当设置了多个categories时,它表现出嵌套层级</p><p>值得一提的是一篇文章也可以指定同层级的categories,如下:</p><ul><li><p>嵌套层级:</p><pre><code class="hljs asciidoc">categories:<span class="hljs-bullet">- </span>sport<span class="hljs-bullet">- </span>football</code></pre></li><li><p>平行层级:</p><pre><code class="hljs asciidoc">categories:<span class="hljs-bullet">- </span>[sport]<span class="hljs-bullet">- </span>[games]</code></pre></li><li><p>平行多层嵌套:</p><pre><code class="hljs asciidoc">categories:<span class="hljs-bullet">- </span>[sport, football]<span class="hljs-bullet">- </span>[games, souls like]</code></pre></li></ul><h1 id="读取数据文件"><a href="#读取数据文件" class="headerlink" title="读取数据文件"></a>读取数据文件</h1><p>hexo提供了一个特性,可以读取 <code>source/_data</code>目录下的<code>yaml</code>或<code>json</code>文件。</p><p>比如,现在在<code>_data</code>目录下创建一个<code>menu.yml</code>数据文件,内容如下:</p><pre><code class="hljs dts"><span class="hljs-symbol">Home:</span> /<span class="hljs-symbol">Gallery:</span> <span class="hljs-meta-keyword">/gallery/</span><span class="hljs-symbol">Archives:</span> <span class="hljs-meta-keyword">/archives/</span></code></pre><p>里面有3个键值对,此时,我们可以通过以下(类似jsp)语法,遍历该数据文件的所有键值对。</p><pre><code class="hljs mojolicious"><span class="xml"><%</span><span class="perl"> <span class="hljs-keyword">for</span> (var <span class="hljs-keyword">link</span> in site.data.menu) { </span><span class="xml">%></span><span class="xml"> <span class="hljs-tag"><<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"<%=</span></span></span><span class="perl"> site.data.menu[<span class="hljs-keyword">link</span>] </span><span class="xml"><span class="hljs-tag"><span class="hljs-string">%>"</span>></span> <%=</span><span class="perl"> <span class="hljs-keyword">link</span> </span><span class="xml">%> <span class="hljs-tag"></<span class="hljs-name">a</span>></span></span><span class="xml"><%</span><span class="perl"> } </span><span class="xml">%></span></code></pre><p>该代码写在模板里才生效,最终的效果如下:</p><pre><code class="hljs xml"><span class="hljs-tag"><<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/"</span>></span> Home <span class="hljs-tag"></<span class="hljs-name">a</span>></span><span class="hljs-tag"><<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/gallery/"</span>></span> Gallery <span class="hljs-tag"></<span class="hljs-name">a</span>></span><span class="hljs-tag"><<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/archives/"</span>></span> Archives <span class="hljs-tag"></<span class="hljs-name">a</span>></span></code></pre><h1 id="常见问题"><a href="#常见问题" class="headerlink" title="常见问题"></a>常见问题</h1><h2 id="语言配置不生效"><a href="#语言配置不生效" class="headerlink" title="语言配置不生效"></a>语言配置不生效</h2><p>语言配置位于站点配置文件的 language 一栏,若配置language = zh-Hans不生效,以next主题为例,观察 themes/next/language 下是否存在 zh-Hans.yml 文件,而我这边的目录下存在 zh-CN.yml,则要不修改 language = zh-CN,要不就修改其文件名为 zh-Hans.yml</p>]]></content>
<categories>
<category> 效率人生 </category>
<category> 自建博客 </category>
</categories>
<tags>
<tag> hexo </tag>
<tag> 工具 </tag>
</tags>
</entry>
</search>