-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.xml
7375 lines (7129 loc) · 660 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
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>Go书写建议</title>
<url>/2024/07/12/Golang/Go%E4%B9%A6%E5%86%99%E5%BB%BA%E8%AE%AE/</url>
<content><![CDATA[<blockquote>
<p>类似于Java开发手册之类的</p>
</blockquote>
<span id="more"></span>
<h1 id="前言"><strong>前言</strong></h1>
<p>本文中概述的一些标准都是客观性的评估,是根据场景、上下文、或者主观性的判断;</p>
<p>但是最重要的是,<strong>保持一致</strong>.</p>
<p>一致性的代码更容易维护、是更合理的、需要更少的学习成本、并且随着新的约定出现或者出现错误后更容易迁移、更新、修复
bug</p>
<p>相反,在一个代码库中包含多个完全不同或冲突的代码风格会导致维护成本开销、不确定性和认知偏差。所有这些都会直接导致速度降低、代码审查痛苦、而且增加
bug 数量</p>
<h1 id="通用"><strong>通用</strong></h1>
<h2 id="注释"><strong>注释</strong></h2>
<h3 id="强制"><strong>强制</strong></h3>
<ol type="1">
<li>每一个可导出(首字母大写)的函数、名称都应该有文档注释</li>
<li>关键流程和关键算法都应该有注释。</li>
<li>一些魔法数字或者妥协而加的代码都应该有注释说明为什么需要添加</li>
</ol>
<p>注释不应该写是什么,而应该写为什么。比如说我有一段休眠代码</p>
<pre class="language-none"><code class="language-none">for i := range arr {
if i%500 == 0{
// 睡眠一段时间
time.Sleep(500)
}
}</code></pre>
<p>写 “睡眠一段时间”
这个注释是无效的,后续看代码的同学还是不清楚为什么要休眠,</p>
<p>但是应该写 “为了缓解服务器压力,这里等待一段时间再做逻辑”
就很清晰了</p>
<p><strong>Bad</strong></p>
<pre class="language-go" data-language="go"><code class="language-go"><span class="token keyword">func</span> <span class="token function">Complie</span><span class="token punctuation">(</span>str <span class="token builtin">string</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>regexp <span class="token operator">*</span>Regexp<span class="token punctuation">,</span>err <span class="token builtin">error</span><span class="token punctuation">)</span></code></pre>
<p><strong>Good</strong></p>
<pre class="language-go" data-language="go"><code class="language-go"><span class="token comment">// Complie 用于解析正则表达式并返回,如果成功,则 Regexp 对象就可用于匹配所针对的文本。</span>
<span class="token keyword">func</span> <span class="token function">Complie</span><span class="token punctuation">(</span>str <span class="token builtin">string</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>regexp <span class="token operator">*</span>Regexp<span class="token punctuation">,</span>err <span class="token builtin">error</span><span class="token punctuation">)</span></code></pre>
<h3 id="推荐"><strong>推荐</strong></h3>
<ol type="1">
<li>修改代码的同时,注释也要相应的进行修改,尤其是参数、返回值、异常、核心逻辑等</li>
<li>与其“半吊子cv英文来注释,不如用中文注释把问题说清楚。专有名词与关键字保持
英文原文即可</li>
<li>谨慎注释掉代码。在上方详细说明,而不是简单地注释掉。如果无用,则删除。</li>
<li>对于注释的要求:第一、能够准确反映设计思想和代码逻辑;第二、能够描述业务含
义,使别的程序员能够迅速了解到代码背后的信息。注释是给自己看的,即使隔很长时间,也能清晰理解当时的思路,注释也是给接受人,使其能够快速接替自己的工作。</li>
</ol>
<h2 id="内存管理"><strong>内存管理</strong></h2>
<h3 id="强制-1"><strong>强制</strong></h3>
<h4 id="切片长度校验"><strong>切片长度校验</strong></h4>
<p>对slice进行操作时,必须判断长度是否合法,防止程序Panic</p>
<p><strong>Bad</strong></p>
<pre class="language-go" data-language="go"><code class="language-go"><span class="token keyword">func</span> <span class="token function">foo</span><span class="token punctuation">(</span>data <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">int</span><span class="token punctuation">)</span> <span class="token builtin">bool</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> data<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">==</span> <span class="token number">0</span>
<span class="token punctuation">}</span></code></pre>
<p><strong>Good</strong></p>
<pre class="language-go" data-language="go"><code class="language-go"><span class="token keyword">func</span> <span class="token function">foo</span><span class="token punctuation">(</span>data <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">int</span><span class="token punctuation">)</span> <span class="token builtin">bool</span> <span class="token punctuation">{</span>
<span class="token keyword">if</span> data <span class="token operator">==</span> <span class="token boolean">nil</span> <span class="token operator">||</span> <span class="token function">len</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token boolean">false</span>
<span class="token punctuation">}</span>
<span class="token keyword">return</span> data<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">==</span> <span class="token number">0</span>
<span class="token punctuation">}</span></code></pre>
<h4 id="nil指针判空"><strong>nil指针判空</strong></h4>
<p>进行指针操作时,需要判断该指针是否为空,防止程序Panic,<strong>在Get函数中时如果时指针引用也必须判断指针是否为空</strong>,尤其是在结构体进行Unmarshal之后</p>
<p><strong>Bad</strong></p>
<pre class="language-go" data-language="go"><code class="language-go"><span class="token keyword">func</span> <span class="token function">foo</span><span class="token punctuation">(</span>src <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">byte</span><span class="token punctuation">)</span> <span class="token builtin">error</span> <span class="token punctuation">{</span>
<span class="token keyword">var</span> dest <span class="token operator">=</span> <span class="token function">new</span><span class="token punctuation">(</span>Data<span class="token punctuation">)</span>
err <span class="token operator">:=</span> json<span class="token punctuation">.</span><span class="token function">Unmarshal</span><span class="token punctuation">(</span>src<span class="token punctuation">)</span>
<span class="token keyword">if</span> err<span class="token operator">!=</span><span class="token boolean">nil</span><span class="token punctuation">{</span>
<span class="token keyword">return</span> err
<span class="token punctuation">}</span>
<span class="token function">use</span><span class="token punctuation">(</span>dest<span class="token punctuation">.</span>Member<span class="token punctuation">.</span>UserName<span class="token punctuation">)</span> <span class="token comment">// panic</span>
<span class="token keyword">return</span>
<span class="token punctuation">}</span></code></pre>
<p><strong>Good</strong></p>
<pre class="language-go" data-language="go"><code class="language-go"><span class="token keyword">func</span> <span class="token function">foo</span><span class="token punctuation">(</span>src <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">byte</span><span class="token punctuation">)</span> <span class="token builtin">error</span> <span class="token punctuation">{</span>
<span class="token keyword">if</span> src <span class="token operator">==</span> <span class="token boolean">nil</span> <span class="token operator">||</span> <span class="token function">len</span><span class="token punctuation">(</span>src<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">{</span>
<span class="token keyword">return</span> ErrSliceIsEmpty
<span class="token punctuation">}</span>
<span class="token keyword">var</span> dest <span class="token operator">=</span> <span class="token function">new</span><span class="token punctuation">(</span>Data<span class="token punctuation">)</span>
err <span class="token operator">:=</span> json<span class="token punctuation">.</span><span class="token function">Unmarshal</span><span class="token punctuation">(</span>src<span class="token punctuation">)</span>
<span class="token keyword">if</span> err<span class="token operator">!=</span><span class="token boolean">nil</span><span class="token punctuation">{</span>
<span class="token keyword">return</span> err
<span class="token punctuation">}</span>
<span class="token keyword">if</span> dest<span class="token punctuation">.</span>Member <span class="token operator">==</span> <span class="token boolean">nil</span><span class="token punctuation">{</span>
<span class="token keyword">return</span> Err
<span class="token punctuation">}</span>
<span class="token function">use</span><span class="token punctuation">(</span>dest<span class="token punctuation">.</span>Member<span class="token punctuation">.</span>UserName<span class="token punctuation">)</span>
<span class="token keyword">return</span>
<span class="token punctuation">}</span></code></pre>
<p>Get函数中也必须判空</p>
<p><strong>Bad</strong></p>
<pre class="language-go" data-language="go"><code class="language-go"><span class="token keyword">func</span> <span class="token punctuation">(</span>m <span class="token operator">*</span>Member<span class="token punctuation">)</span> <span class="token function">UserName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token builtin">string</span><span class="token punctuation">{</span>
<span class="token keyword">return</span> m<span class="token punctuation">.</span>UserName
<span class="token punctuation">}</span></code></pre>
<p><strong>Good</strong></p>
<pre class="language-go" data-language="go"><code class="language-go"><span class="token keyword">func</span> <span class="token punctuation">(</span>m <span class="token operator">*</span>Member<span class="token punctuation">)</span> <span class="token function">UserName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token builtin">string</span><span class="token punctuation">{</span>
<span class="token keyword">if</span> m <span class="token operator">==</span> <span class="token boolean">nil</span><span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token string">""</span>
<span class="token punctuation">}</span>
<span class="token keyword">return</span> m<span class="token punctuation">.</span>UserName
<span class="token punctuation">}</span></code></pre>
<h4 id="make预分配长度"><strong>make预分配长度</strong></h4>
<p>在进行make内存分配时,尽可能指定容器容量,以便为容器预先分配内存</p>
<p><strong>Bad</strong></p>
<pre class="language-go" data-language="go"><code class="language-go"><span class="token keyword">func</span> <span class="token function">foo</span><span class="token punctuation">(</span>list <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">int</span><span class="token punctuation">)</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">int</span> <span class="token punctuation">{</span>
<span class="token keyword">var</span> res <span class="token operator">=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">int</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span>
<span class="token keyword">for</span> <span class="token boolean">_</span><span class="token punctuation">,</span> item<span class="token operator">:=</span><span class="token keyword">range</span> list<span class="token punctuation">{</span>
<span class="token keyword">if</span> item <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">{</span>
<span class="token keyword">continue</span>
<span class="token punctuation">}</span>
res <span class="token operator">=</span> <span class="token function">append</span><span class="token punctuation">(</span>res<span class="token punctuation">,</span> item<span class="token punctuation">)</span> <span class="token comment">// append 函数会存在大量的内存拷贝</span>
<span class="token punctuation">}</span>
<span class="token keyword">return</span> res
<span class="token punctuation">}</span></code></pre>
<p><strong>Good</strong></p>
<pre class="language-go" data-language="go"><code class="language-go"><span class="token keyword">func</span> <span class="token function">foo</span><span class="token punctuation">(</span>list <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">int</span><span class="token punctuation">)</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">int</span> <span class="token punctuation">{</span>
<span class="token keyword">var</span> res <span class="token operator">=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">int</span><span class="token punctuation">,</span> <span class="token number">0</span> <span class="token punctuation">,</span><span class="token function">len</span><span class="token punctuation">(</span>list<span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token keyword">for</span> <span class="token boolean">_</span><span class="token punctuation">,</span> item<span class="token operator">:=</span><span class="token keyword">range</span> list<span class="token punctuation">{</span>
<span class="token keyword">if</span> item <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">{</span>
<span class="token keyword">continue</span>
<span class="token punctuation">}</span>
res <span class="token operator">=</span> <span class="token function">append</span><span class="token punctuation">(</span>res<span class="token punctuation">,</span> item<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token keyword">return</span> res
<span class="token punctuation">}</span></code></pre>
<h4 id="禁止重复释放channel"><strong>禁止重复释放channel</strong></h4>
<p>重复释放一般存在于异常流程判断中,如果恶意攻击者构造出异常条件使程序重复释放channel,则会触发运行时panic,从而造成DoS攻击。</p>
<p><strong>Bad</strong></p>
<pre class="language-go" data-language="go"><code class="language-go"><span class="token keyword">func</span> <span class="token function">foo</span><span class="token punctuation">(</span>c <span class="token keyword">chan</span> <span class="token builtin">int</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">defer</span> <span class="token function">close</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span>
err <span class="token operator">:=</span> <span class="token function">processBusiness</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
c <span class="token operator"><-</span> <span class="token number">0</span>
<span class="token function">close</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span> <span class="token comment">// 重复释放channel</span>
<span class="token keyword">return</span>
<span class="token punctuation">}</span>
c <span class="token operator"><-</span> <span class="token number">1</span>
<span class="token punctuation">}</span></code></pre>
<p><strong>Good</strong></p>
<pre class="language-go" data-language="go"><code class="language-go"><span class="token keyword">func</span> <span class="token function">foo</span><span class="token punctuation">(</span>c <span class="token keyword">chan</span> <span class="token builtin">int</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">defer</span> <span class="token function">close</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span> <span class="token comment">// 使用defer延迟关闭channel</span>
err <span class="token operator">:=</span> <span class="token function">processBusiness</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
c <span class="token operator"><-</span> <span class="token number">0</span>
<span class="token keyword">return</span>
<span class="token punctuation">}</span>
c <span class="token operator"><-</span> <span class="token number">1</span>
<span class="token punctuation">}</span></code></pre>
<h4 id="确保每个协程都能退出"><strong>确保每个协程都能退出</strong></h4>
<p>启动一个协程就会做一个入栈操作,在系统不退出的情况下,协程也没有设置退出条件,则相当于协程失去了控制,它占用的资源无法回收,可能会导致内存泄露。</p>
<pre class="language-none"><code class="language-none">// bad: 协程没有设置退出条件
func doWaiter(name string, second int) {
for {
time.Sleep(time.Duration(second) * time.Second)
fmt.Println(name, " is ready!")
}
}</code></pre>
<h3 id="推荐-1"><strong>推荐</strong></h3>
<h4 id="不使用unsafe包"><strong>不使用unsafe包</strong></h4>
<p>由于unsafe包绕过了 Golang
的内存安全原则,一般来说使用该库是不安全的,可导致内存破坏,尽量避免使用该包。若必须要使用unsafe操作指针,必须做好安全校验。</p>
<pre class="language-none"><code class="language-none">// bad: 通过unsafe操作原始指针
func unsafePointer() {
b := make([]byte, 1)
foo := (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&b[0])) + uintptr(0xfffffffe)))
fmt.Print(*foo + 1)
}</code></pre>
<h2 id="字符串"><strong>字符串</strong></h2>
<h3 id="强制-2"><strong>强制</strong></h3>
<h4 id="字符串拼接"><strong>字符串拼接</strong></h4>
<p>禁止使用+号进行字符串拼接操作,如果需要使用请使用 strings.Builder</p>
<p><strong>Bad</strong></p>
<pre class="language-go" data-language="go"><code class="language-go"><span class="token keyword">for</span> <span class="token function">foo</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
<span class="token keyword">var</span> str1 <span class="token operator">=</span> <span class="token string">"str1"</span>
<span class="token keyword">var</span> str2 <span class="token operator">=</span> <span class="token string">"str2"</span>
fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>str1<span class="token operator">+</span>str2<span class="token punctuation">)</span>
<span class="token punctuation">}</span></code></pre>
<p><strong>Good</strong></p>
<pre class="language-go" data-language="go"><code class="language-go"><span class="token keyword">for</span> <span class="token function">foo</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
<span class="token keyword">var</span> str1 <span class="token operator">=</span> <span class="token string">"str1"</span>
<span class="token keyword">var</span> str2 <span class="token operator">=</span> <span class="token string">"str2"</span>
<span class="token keyword">var</span> str3 strings<span class="token punctuation">.</span>Builder
str3<span class="token punctuation">.</span><span class="token function">Grow</span><span class="token punctuation">(</span><span class="token function">len</span><span class="token punctuation">(</span>str1<span class="token punctuation">)</span><span class="token operator">+</span><span class="token function">len</span><span class="token punctuation">(</span>str2<span class="token punctuation">)</span><span class="token punctuation">)</span>
str3<span class="token punctuation">.</span><span class="token function">WriteString</span><span class="token punctuation">(</span>str1<span class="token punctuation">)</span>
str3<span class="token punctuation">.</span><span class="token function">WriteString</span><span class="token punctuation">(</span>str2<span class="token punctuation">)</span>
fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>str3<span class="token punctuation">.</span><span class="token function">String</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span></code></pre>
<h4 id="取字符串的某个字符"><strong>取字符串的某个字符</strong></h4>
<p>如果需要取字符串的某个字符,除非非常确定字符串中只含有ANSCII的字符,否则都必须将string转为[]rune类型</p>
<p><strong>Bad</strong></p>
<pre class="language-go" data-language="go"><code class="language-go"><span class="token keyword">var</span> str <span class="token builtin">string</span> <span class="token operator">=</span> <span class="token string">"你好"</span>
<span class="token keyword">var</span> firstChar <span class="token operator">=</span> str<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span></code></pre>
<p><strong>Good</strong></p>
<pre class="language-go" data-language="go"><code class="language-go"><span class="token keyword">var</span> str <span class="token builtin">string</span> <span class="token operator">=</span> <span class="token string">"你好"</span>
<span class="token keyword">var</span> firstChar <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token function">rune</span><span class="token punctuation">(</span>str<span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span></code></pre>
<h4
id="禁止使用fmt进行类型转化"><strong>禁止使用fmt进行类型转化</strong></h4>
<p>如果要将
int64、float等数字类型转化为string的话,禁止使用fmt.Sprintf进行类型转化,它的内部使用反射判断类型,性能相对较差</p>
<p><strong>Bad</strong></p>
<pre class="language-go" data-language="go"><code class="language-go"><span class="token keyword">var</span> str <span class="token builtin">string</span>
<span class="token keyword">var</span> num <span class="token operator">=</span><span class="token number">10</span>
str <span class="token operator">=</span> fmt<span class="token punctuation">.</span><span class="token function">Sprintf</span><span class="token punctuation">(</span><span class="token string">"%v"</span><span class="token punctuation">,</span>num<span class="token punctuation">)</span></code></pre>
<p><strong>Good</strong></p>
<pre class="language-go" data-language="go"><code class="language-go"><span class="token keyword">var</span> str <span class="token builtin">string</span>
<span class="token keyword">var</span> num <span class="token operator">=</span><span class="token number">10</span>
str <span class="token operator">=</span> strconv<span class="token punctuation">.</span><span class="token function">Itoa</span><span class="token punctuation">(</span>num<span class="token punctuation">)</span></code></pre>
<h3 id="推荐-2"><strong>推荐</strong></h3>
<h4 id="cast包"><strong>cast包</strong></h4>
<p>可以尝试使用cast包进行类型的转化</p>
<pre class="language-none"><code class="language-none">cast.ToString()</code></pre>
<h4 id="避免反复类型转化"><strong>避免反复类型转化</strong></h4>
<p>不要反复从固定字符串创建字节切片。</p>
<p><strong>Bad</strong></p>
<pre class="language-go" data-language="go"><code class="language-go"><span class="token keyword">for</span> i <span class="token operator">:=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator"><</span> b<span class="token punctuation">.</span>N<span class="token punctuation">;</span> i<span class="token operator">++</span> <span class="token punctuation">{</span>
w<span class="token punctuation">.</span><span class="token function">Write</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token function">byte</span><span class="token punctuation">(</span><span class="token string">"Hello world"</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span></code></pre>
<p>BenchmarkBad-4 50000000 22.2 ns/op</p>
<p><strong>Good</strong></p>
<pre class="language-go" data-language="go"><code class="language-go">data <span class="token operator">:=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token function">byte</span><span class="token punctuation">(</span><span class="token string">"Hello world"</span><span class="token punctuation">)</span>
<span class="token keyword">for</span> i <span class="token operator">:=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator"><</span> b<span class="token punctuation">.</span>N<span class="token punctuation">;</span> i<span class="token operator">++</span> <span class="token punctuation">{</span>
w<span class="token punctuation">.</span><span class="token function">Write</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span>
<span class="token punctuation">}</span></code></pre>
<p>BenchmarkGood-4 500000000 3.25 ns/op</p>
<h4 id="zero-copy"><strong>zero copy</strong></h4>
<p>对于明确只做使用而不做修改的[]byte转string的功能可以使用
stringx.ZeroCopyBytes2String 函数</p>
<h2 id="异常处理"><strong>异常处理</strong></h2>
<h3 id="强制-3"><strong>强制</strong></h3>
<h4 id="协程中一定要recover"><strong>协程中一定要</strong>recover</h4>
<p>在协程中处理一定要recover掉panic,否则会导致整个进程都退出,并且要尽可能的打印全崩溃信息</p>
<pre class="language-none"><code class="language-none">// good
func foo(f func()){
go func() {
defer func() {
if err := recover(); err != nil {
buf := debug.Stack()
logrus.Errorf("SafeGo.recover :%v stack:%v", err, string(buf))
}
}()
f()
}()
}
// bad
func foo(f func()){
go f()
}</code></pre>
<h4 id="合理使用panic"><strong>合理使用panic</strong></h4>
<p>如果问题可以被屏蔽或者被解决,那么最好让程序继续运行,比如说在某个接口中因为客户端传入了某种特殊值导致崩溃的话最好使用
recover
兜住,但是针对于某些库不能正常工作并且程序就无法继续运行的情况下,最好就Panic出去。比如说数据库连接不上、某些重要配置未配置</p>
<pre class="language-none"><code class="language-none">var stage = os.Getenv("STAGE")
func init(){
if stage == ""{
panic("no value for $STAGE")
}
}</code></pre>
<h3 id="推荐-3"><strong>推荐</strong></h3>
<h4 id="崩溃信息统计"><strong>崩溃信息统计</strong></h4>
<p>崩溃的信息最好能够上报到Prometheus或者直接发到钉钉中</p>
<h4 id="error处理"><strong>error处理</strong></h4>
<p>可以使用 errors 包来处理异常,具体使用方法见</p>
<h2 id="时间日期"><strong>时间日期</strong></h2>
<h3 id="强制-4"><strong>强制</strong></h3>
<ol type="1">
<li>不要在程序中写死一年为365天,一个月为30天,避免在闰年等情况下出现逻辑错误</li>
<li>禁止使用 time.Now()
来获取当前时间,获取的时间为系统默认时区,在Format时间时会出现异常</li>
<li>不要给前端、客户端返回字符串形式的时间类型,也不要接受它们的字符串形式的时间类型,在进行时间类型传输时一律使用秒级时间戳进行通信</li>
<li>必须使用
time.Duration代表某个时间段,禁止直接采用魔法数字来代表时间段。</li>
</ol>
<h3 id="推荐-4"><strong>推荐</strong></h3>
<ol type="1">
<li>建议使用 live_server 的 region 库来代替某些时间函数</li>
<li>对于以周的时间维度划分最好使用ISO周,否则在某周跨年时会出现异常,比如说需要判断某一天是今年的第几周</li>
<li>如果在某些配置文件中需要配置时间段的话,建议统一采用秒数,或者在程序、配置文件中明确时间单位。</li>
</ol>
<h2 id="控制语句">控<strong>制语句</strong></h2>
<h3 id="强制-5"><strong>强制</strong></h3>
<ol type="1">
<li>在高并发场景时,避免使用 “等于”
判断作为中断或者退出的条件。这是因为如果并发处理没有控制好的情况下,可能值会出现值被“击穿”的情况,最好使用大于、小于的区间判断来代替</li>
</ol>
<h3 id="推荐-5"><strong>推荐</strong></h3>
<ol type="1">
<li>表达异常的分支时,少用if-else语句,这种方式可以改为</li>
</ol>
<table>
<colgroup>
<col style="width: 54%" />
<col style="width: 45%" />
</colgroup>
<thead>
<tr class="header">
<th>Bad</th>
<th>Good</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>if condition { return err } else { // 这里写else的代码 }</code></td>
<td><code>if condition { return err } // 这里写else的代码</code></td>
</tr>
</tbody>
</table>
<ol type="1">
<li>尽量避免采用取反逻辑</li>
</ol>
<h2 id="sql操作"><strong>SQL操作</strong></h2>
<h3 id="强制-6"><strong>强制</strong></h3>
<ol type="1">
<li>SQL语句默认使用预编译并绑定变量</li>
<li>禁止拼接SQL语句,对于传入的参数用于order
by或者表名等必须通过校验</li>
</ol>
<h3 id="推荐-6"><strong>推荐</strong></h3>
<ol type="1">
<li>最好使用 gorm.io 库,尽量避免使用自封装的库</li>
<li>在对sql进行加锁时,要注意锁的顺序,避免死锁</li>
<li>最好使用 deleted_at
来做软删除,除非是明确的不重要可删除的数据才真正删除</li>
</ol>
<h2 id="程序结构"><strong>程序结构</strong></h2>
<h3 id="强制-7"><strong>强制</strong></h3>
<ol type="1">
<li>代码的每一层结构都必须有一个地方接受Context,可以放在函数中也可以放在结构体中,但是放在结构体中时务必保证每次使用结构体都初始化。</li>
<li>在引入一个新的代码库依赖时必须保证它的子依赖对现有的的仓库不会有影响。</li>
<li>禁止引入非稳定、未经过测试的代码库。</li>
<li><strong>核心代码、公共common库应该做到测试全复盖</strong></li>
</ol>
<h3 id="推荐-7"><strong>推荐</strong></h3>
<ol type="1">
<li>避免使用init函数。尽可能的使用显式调用</li>
<li>当函数存在可选项时,可以使用Option模式进行优化</li>
<li>尽量减少大括号的嵌套。这会严重影响代码的可读性,以及增加了不必要的复杂度</li>
</ol>
<h2 id="日志"><strong>日志</strong></h2>
<h3 id="强制-8"><strong>强制</strong></h3>
<ol type="1">
<li>禁止使用 fmt、自行书写的logger库来打印日志。日志打印一定要使用
logurs 库。同时在生成logrus.Entry结构时需要携带上ctx参数。</li>
<li>对于敏感操作必须要打印一条日志代表玩家操作过。比如说玩家修改自己的密码。</li>
<li>强制使用日志分级。分为四个等级:debug、info、warn、error</li>
<li>正式环境禁止打印debug日志</li>
<li>logrus强制使用 JSON Format</li>
</ol>
<h3 id="推荐-8"><strong>推荐</strong></h3>
<ol type="1">
<li>谨慎地记录日志。生产环境禁止输出 debug 日志;有选择地输出 info
日志</li>
<li>可以使用warn日志级别来记录用户输入参数错误的情况,如非必要,请不要在此场景打印
error 级别日志</li>
</ol>
<h1 id="服务"><strong>服务</strong></h1>
<h2 id="强制-9"><strong>强制</strong></h2>
<ol type="1">
<li>用户请求传入的任何参数必须做有效性验证</li>
<li>用户输入的SQL参数严格使用参数绑定。防止sql注入、拼接sql字符串访问数据库。对于string类型的参数应该格外注意</li>
<li>对于暴露在外部的接口严格使用<code>${serverName}/${type}/${version}/*</code>
。其中<code>${serverName}</code> 为 项目名。<code>${type}</code>
限定三个类型:app代表暴露在app端的接口,
rpc代表rpc接口,backend代表后台接口。<code>${version}</code>
代表接口版本。</li>
<li>暴露在外的接口要做玩家token验证。</li>
<li>隶属于用户个人的页面或者功能必须进行权限控制校验</li>
<li>用户敏感数据禁止直接展示,必须对数据进行脱敏</li>
</ol>
<h2 id="推荐-9"><strong>推荐</strong></h2>
<ol type="1">
<li>发贴、评论、发送即时消息等用户生成内容的场景需实现防刷、文本内容违禁词过滤等风控策略。</li>
<li>消息队列如果存在很大的吞吐量且允许部分消息丢失则最好使用Kafka。</li>
<li>RabbitMQ适用于重要消息,在接入RabbitMQ时最好</li>
</ol>
]]></content>
<categories>
<category>Golang</category>
</categories>
<tags>
<tag>go</tag>
</tags>
</entry>
<entry>
<title>Logrus打印trace</title>
<url>/2022/06/13/Golang/Logrus%E6%89%93%E5%8D%B0trace/</url>
<content><![CDATA[<blockquote>
<p>看完 <a
href="https://zhouxuwen.github.io/2022/06/08/Go%E6%AF%8F%E5%91%A8%E4%B8%80%E5%BA%93%E4%B9%8BLogrus/#more">Go每周一库之Logrus</a>
由感而发 ,是旭文博客的补充,强烈建议先看旭文的博客</p>
</blockquote>
<span id="more"></span>
<h1 id="logrus打印trace">Logrus打印trace</h1>
<h2 id="logrus-前置知识点"><code>Logrus</code> 前置知识点</h2>
<p>旭文提到 <code>Logrus</code>
的两个重要结构体:<code>Logger Entry</code>,但是对于这两者并没有做详细的介绍,但是这些知识点对于本文下文有重要的意义。</p>
<h3 id="logger"><code>Logger</code></h3>
<p><code>Logger</code>
是一个非常有趣的结构,我们先来看一下它的内部结构,没有写出所有的结构</p>
<pre class="language-go" data-language="go"><code class="language-go"><span class="token keyword">type</span> Logger <span class="token keyword">struct</span> <span class="token punctuation">{</span>
Out io<span class="token punctuation">.</span>Writer <span class="token comment">// 封装流,所有的日志都会调用 Write 方法写.</span>
Hooks LevelHooks <span class="token comment">// 钩子. 在写日志的之前会调用</span>
Formatter Formatter <span class="token comment">// 结构化方法,是一个函数.所有的日志都会调用Format方法把Entry结构化成[]byte数组</span>
Level Level <span class="token comment">// 日志等级</span>
mu MutexWrap <span class="token comment">// 锁.非常重要.</span>
<span class="token comment">// Reusable empty entry</span>
entryPool sync<span class="token punctuation">.</span>Pool <span class="token comment">// entry池。用来保证不会多次分配内存</span>
<span class="token punctuation">}</span></code></pre>
<p>可以看到 <code>Logger</code>
结构体没有依赖其余的组件,都是通过接口的方式暴露出功能,在我们实现插件的时候只需要关注自己需要实现的接口,而完全不需要去修改
<code>logrus</code> 的代码。</p>
<p>Level、Out、Formatter、Hooks这些功能旭文的博客已经写的很明白,此处就不多赘述。</p>
<h3 id="entry"><code>Entry</code></h3>
<p><code>Entry</code> 对应每条日志的实体,他的内部结构如下:</p>
<pre class="language-go" data-language="go"><code class="language-go"><span class="token keyword">type</span> Entry <span class="token keyword">struct</span> <span class="token punctuation">{</span>
Logger <span class="token operator">*</span>Logger
<span class="token comment">// Contains all the fields set by the user.</span>
Data Fields
<span class="token comment">// Time at which the log entry was created</span>
Time time<span class="token punctuation">.</span>Time
<span class="token comment">// Level the log entry was logged at: Trace, Debug, Info, Warn, Error, Fatal or Panic</span>
<span class="token comment">// This field will be set on entry firing and the value will be equal to the one in Logger struct field.</span>
Level Level
<span class="token comment">// Calling method, with package name</span>
Caller <span class="token operator">*</span>runtime<span class="token punctuation">.</span>Frame
<span class="token comment">// Message passed to Trace, Debug, Info, Warn, Error, Fatal or Panic</span>
Message <span class="token builtin">string</span>
<span class="token comment">// When formatter is called in entry.log(), a Buffer may be set to entry</span>
Buffer <span class="token operator">*</span>bytes<span class="token punctuation">.</span>Buffer
Context context<span class="token punctuation">.</span>Context
err <span class="token builtin">string</span>
<span class="token punctuation">}</span></code></pre>
<p>Entry内部包括了Logger结构体指针,这是一个非常常见的编程手法,我们知道Entry都是通过Logger生成的,那么这些Entry就必须要知道是哪个Logger生成了ta,并且Entry会高度依赖Logger(Logger掌握了输出信息),但是需要注意:
Entry没有使用 Logger 的 <code>EnterPool</code>.</p>
<p>Entry最为重要的字段就是Data了,它保存了日志除内容之外的结构化信息,Fields本身就是一个map,没有什么神奇的东西。</p>
<p>Entry也可以生成Entry,主要通过 Data 复制来实现。</p>
<h2 id="logrus实现trace">Logrus实现trace</h2>
<p>trace_id应该是一个微服务的刚需。它由Gateway产生,并在微服务各个节点之中流转,一个trace_id就可以标记出一条完整的链路。</p>
<p>想想有没有这种需求:我需要打印一次请求的完整日志、快速定位服务A请求服务B的日志。需要实现这个功能就需要我们把trace_id给加入到结构化日志中,这可以使用logrus完美实现。</p>
<p>通过刚刚的前置知识点可以知道</p>
<ol type="1">
<li>每条日志的信息都是由 Entry 保存的</li>
<li>Entry 内部由 Data、Ctx字段</li>
<li>Entry 知道 Logger 指针</li>
<li>Logger 在每次输出Entry日志的时候会调用 Hooks</li>
</ol>
<p>接下来我们就可以通过这些来实现我们的功能了。首先书写 <code>Gin</code>
中间件,获取请求头的TraceID信息写入到 <code>gin.Request.Context</code>
中。</p>
<pre class="language-go" data-language="go"><code class="language-go"><span class="token comment">// GinTraceIdMiddleware 生成trade_id</span>
<span class="token keyword">func</span> <span class="token function">GinTraceIdMiddleware</span><span class="token punctuation">(</span>ginCtx <span class="token operator">*</span>gin<span class="token punctuation">.</span>Context<span class="token punctuation">)</span> <span class="token punctuation">{</span>
reqCtx <span class="token operator">:=</span> ginCtx<span class="token punctuation">.</span>Request<span class="token punctuation">.</span><span class="token function">Context</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
header <span class="token operator">:=</span> ginCtx<span class="token punctuation">.</span><span class="token function">GetHeader</span><span class="token punctuation">(</span>HeaderTraceIdKey<span class="token punctuation">)</span>
<span class="token keyword">if</span> header <span class="token operator">!=</span> <span class="token string">""</span> <span class="token punctuation">{</span>
reqCtx <span class="token operator">=</span> <span class="token function">ContextWithTraceId</span><span class="token punctuation">(</span>reqCtx<span class="token punctuation">,</span> header<span class="token punctuation">)</span>
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
reqCtx <span class="token operator">=</span> <span class="token function">ContextWithRandomTraceId</span><span class="token punctuation">(</span>reqCtx<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
ginCtx<span class="token punctuation">.</span>Request <span class="token operator">=</span> ginCtx<span class="token punctuation">.</span>Request<span class="token punctuation">.</span><span class="token function">WithContext</span><span class="token punctuation">(</span>reqCtx<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token comment">// ContextWithRandomTraceId 为Context生成随机的trace_id</span>
<span class="token keyword">func</span> <span class="token function">ContextWithRandomTraceId</span><span class="token punctuation">(</span>ctx context<span class="token punctuation">.</span>Context<span class="token punctuation">)</span> <span class="token punctuation">(</span>newCtx context<span class="token punctuation">.</span>Context<span class="token punctuation">)</span> <span class="token punctuation">{</span>
traceId <span class="token operator">:=</span> <span class="token function">UUID</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token function">ContextWithTraceId</span><span class="token punctuation">(</span>ctx<span class="token punctuation">,</span> traceId<span class="token punctuation">)</span>
<span class="token keyword">return</span>
<span class="token punctuation">}</span>
<span class="token comment">// ContextWithTraceId 为Context设置trace_id. 如果ID已存在则不设置</span>
<span class="token keyword">func</span> <span class="token function">ContextWithTraceId</span><span class="token punctuation">(</span>ctx context<span class="token punctuation">.</span>Context<span class="token punctuation">,</span> traceId <span class="token builtin">string</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>newCtx context<span class="token punctuation">.</span>Context<span class="token punctuation">)</span> <span class="token punctuation">{</span>
oldTraceId<span class="token punctuation">,</span> ok <span class="token operator">:=</span> ctx<span class="token punctuation">.</span><span class="token function">Value</span><span class="token punctuation">(</span>TraceIdKey<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token punctuation">(</span><span class="token builtin">string</span><span class="token punctuation">)</span>
<span class="token keyword">if</span> ok <span class="token operator">&&</span> oldTraceId <span class="token operator">!=</span> <span class="token string">""</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span>
<span class="token punctuation">}</span>
newCtx <span class="token operator">=</span> context<span class="token punctuation">.</span><span class="token function">WithValue</span><span class="token punctuation">(</span>ctx<span class="token punctuation">,</span> TraceIdKey<span class="token punctuation">,</span> traceId<span class="token punctuation">)</span>
<span class="token keyword">return</span>
<span class="token punctuation">}</span></code></pre>
<p>这样我们可以直接书写 <code>router.Use(GinTraceIdMiddleware)</code>
使得每次的请求的ctx都携带 <code>__trace_id</code>
,这样我们只需要让这个ctx在程序各个地方流转即可。</p>
<p>接下来是第二步:让每次打印都携带流转的Ctx。通过上文可以知道每个 Entry
都会携带一个 Ctx
信息。这样Entry也可以设置成这个Ctx。然后在打印日志的时候把Ctx的TraceId信息加入到
Entry 的 Data 中即可。这一步我们可以使用Hook来实现。</p>
<pre class="language-go" data-language="go"><code class="language-go"><span class="token comment">// TraceIdHook 使用该结构体在日志中打印TraceID信息</span>
<span class="token keyword">type</span> TraceIdHook <span class="token keyword">struct</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
<span class="token comment">// Levels 所有等级, 可以书写成可配置的</span>
<span class="token keyword">func</span> <span class="token punctuation">(</span>m <span class="token operator">*</span>TraceIdHook<span class="token punctuation">)</span> <span class="token function">Levels</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>logrus<span class="token punctuation">.</span>Level <span class="token punctuation">{</span>
<span class="token keyword">return</span> logrus<span class="token punctuation">.</span>AllLevels
<span class="token punctuation">}</span>
<span class="token keyword">const</span> <span class="token punctuation">(</span>
TraceIdKey <span class="token operator">=</span> <span class="token string">"__trace_id"</span>
HeaderTraceIdKey <span class="token operator">=</span> <span class="token string">"__trace_id"</span>
<span class="token punctuation">)</span>
<span class="token comment">// Fire implements logrus.Hook.Fire</span>
<span class="token keyword">func</span> <span class="token punctuation">(</span>m <span class="token operator">*</span>TraceIdHook<span class="token punctuation">)</span> <span class="token function">Fire</span><span class="token punctuation">(</span>entry <span class="token operator">*</span>logrus<span class="token punctuation">.</span>Entry<span class="token punctuation">)</span> <span class="token punctuation">(</span>err <span class="token builtin">error</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">var</span> tradeId <span class="token builtin">string</span>
<span class="token keyword">if</span> entry<span class="token punctuation">.</span>Context <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
<span class="token keyword">var</span> ok <span class="token builtin">bool</span>
tradeId<span class="token punctuation">,</span> ok <span class="token operator">=</span> entry<span class="token punctuation">.</span>Context<span class="token punctuation">.</span><span class="token function">Value</span><span class="token punctuation">(</span>TraceIdKey<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token punctuation">(</span><span class="token builtin">string</span><span class="token punctuation">)</span> <span class="token comment">// 判断 entry 中的ctx字段是否存在 traceId 信息</span>
<span class="token keyword">if</span> <span class="token operator">!</span>ok <span class="token punctuation">{</span>
<span class="token keyword">return</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token keyword">if</span> tradeId <span class="token operator">==</span> <span class="token string">""</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span>
<span class="token punctuation">}</span>
entry<span class="token punctuation">.</span>Data<span class="token punctuation">[</span><span class="token string">"trace_id"</span><span class="token punctuation">]</span> <span class="token operator">=</span> tradeId <span class="token comment">// 把 trace_id 信息加入到携带信息中,这样就可以打印出来了</span>
<span class="token keyword">return</span>
<span class="token punctuation">}</span></code></pre>
<h2 id="使用方法">使用方法</h2>
<p>主函数</p>
<pre class="language-go" data-language="go"><code class="language-go"><span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
engine <span class="token operator">:=</span> gin<span class="token punctuation">.</span><span class="token function">Default</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
logrus<span class="token punctuation">.</span><span class="token function">AddHook</span><span class="token punctuation">(</span><span class="token operator">&</span>log<span class="token punctuation">.</span>TraceIdHook<span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span>
engine<span class="token punctuation">.</span><span class="token function">Use</span><span class="token punctuation">(</span>log<span class="token punctuation">.</span>GinTraceIdMiddleware<span class="token punctuation">)</span>
engine<span class="token punctuation">.</span><span class="token function">POST</span><span class="token punctuation">(</span><span class="token string">`/`</span><span class="token punctuation">,</span> <span class="token keyword">func</span><span class="token punctuation">(</span>c <span class="token operator">*</span>gin<span class="token punctuation">.</span>Context<span class="token punctuation">)</span> <span class="token punctuation">{</span>
logrus<span class="token punctuation">.</span><span class="token function">WithContext</span><span class="token punctuation">(</span>c<span class="token punctuation">.</span>Request<span class="token punctuation">.</span><span class="token function">Context</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span>
<span class="token function">WithField</span><span class="token punctuation">(</span><span class="token string">`func`</span><span class="token punctuation">,</span> <span class="token string">`index`</span><span class="token punctuation">)</span><span class="token punctuation">.</span>
<span class="token function">Info</span><span class="token punctuation">(</span><span class="token string">"hello"</span><span class="token punctuation">)</span>
c<span class="token punctuation">.</span><span class="token function">String</span><span class="token punctuation">(</span><span class="token number">200</span><span class="token punctuation">,</span> <span class="token string">"OK!!!"</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token boolean">_</span> <span class="token operator">=</span> engine<span class="token punctuation">.</span><span class="token function">Run</span><span class="token punctuation">(</span><span class="token string">":8080"</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span></code></pre>
<p>log包</p>
<pre class="language-go" data-language="go"><code class="language-go"><span class="token keyword">package</span> log
<span class="token keyword">import</span> <span class="token punctuation">(</span>
<span class="token string">"context"</span>
<span class="token string">"github.com/gin-gonic/gin"</span>
<span class="token string">"github.com/google/uuid"</span>
<span class="token string">"github.com/sirupsen/logrus"</span>
<span class="token string">"strings"</span>
<span class="token punctuation">)</span>
<span class="token keyword">func</span> <span class="token function">InitLog</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
logrus<span class="token punctuation">.</span><span class="token function">AddHook</span><span class="token punctuation">(</span><span class="token operator">&</span>TraceIdHook<span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token keyword">type</span> TraceIdHook <span class="token keyword">struct</span> <span class="token punctuation">{</span>
<span class="token punctuation">}</span>
<span class="token keyword">func</span> <span class="token punctuation">(</span>m <span class="token operator">*</span>TraceIdHook<span class="token punctuation">)</span> <span class="token function">Levels</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>logrus<span class="token punctuation">.</span>Level <span class="token punctuation">{</span>
<span class="token keyword">return</span> logrus<span class="token punctuation">.</span>AllLevels
<span class="token punctuation">}</span>
<span class="token keyword">const</span> <span class="token punctuation">(</span>
TraceIdKey <span class="token operator">=</span> <span class="token string">"__trace_id"</span>
HeaderTraceIdKey <span class="token operator">=</span> <span class="token string">"__trace_id"</span>
<span class="token punctuation">)</span>
<span class="token comment">// Fire implements logrus.Hook.Fire</span>
<span class="token keyword">func</span> <span class="token punctuation">(</span>m <span class="token operator">*</span>TraceIdHook<span class="token punctuation">)</span> <span class="token function">Fire</span><span class="token punctuation">(</span>entry <span class="token operator">*</span>logrus<span class="token punctuation">.</span>Entry<span class="token punctuation">)</span> <span class="token punctuation">(</span>err <span class="token builtin">error</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">var</span> tradeId <span class="token builtin">string</span>
<span class="token keyword">if</span> entry<span class="token punctuation">.</span>Context <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
<span class="token keyword">var</span> ok <span class="token builtin">bool</span>
tradeId<span class="token punctuation">,</span> ok <span class="token operator">=</span> entry<span class="token punctuation">.</span>Context<span class="token punctuation">.</span><span class="token function">Value</span><span class="token punctuation">(</span>TraceIdKey<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token punctuation">(</span><span class="token builtin">string</span><span class="token punctuation">)</span>
<span class="token keyword">if</span> <span class="token operator">!</span>ok <span class="token punctuation">{</span>
<span class="token keyword">return</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token keyword">if</span> tradeId <span class="token operator">==</span> <span class="token string">""</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span>
<span class="token punctuation">}</span>
entry<span class="token punctuation">.</span>Data<span class="token punctuation">[</span><span class="token string">"trace_id"</span><span class="token punctuation">]</span> <span class="token operator">=</span> tradeId
<span class="token keyword">return</span>
<span class="token punctuation">}</span>
<span class="token comment">// GinTraceIdMiddleware 生成trade_id</span>
<span class="token keyword">func</span> <span class="token function">GinTraceIdMiddleware</span><span class="token punctuation">(</span>ginCtx <span class="token operator">*</span>gin<span class="token punctuation">.</span>Context<span class="token punctuation">)</span> <span class="token punctuation">{</span>
reqCtx <span class="token operator">:=</span> ginCtx<span class="token punctuation">.</span>Request<span class="token punctuation">.</span><span class="token function">Context</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
header <span class="token operator">:=</span> ginCtx<span class="token punctuation">.</span><span class="token function">GetHeader</span><span class="token punctuation">(</span>HeaderTraceIdKey<span class="token punctuation">)</span>
<span class="token keyword">if</span> header <span class="token operator">!=</span> <span class="token string">""</span> <span class="token punctuation">{</span>
reqCtx <span class="token operator">=</span> <span class="token function">ContextWithTraceId</span><span class="token punctuation">(</span>reqCtx<span class="token punctuation">,</span> header<span class="token punctuation">)</span>
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
reqCtx <span class="token operator">=</span> <span class="token function">ContextWithRandomTraceId</span><span class="token punctuation">(</span>reqCtx<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
ginCtx<span class="token punctuation">.</span>Request <span class="token operator">=</span> ginCtx<span class="token punctuation">.</span>Request<span class="token punctuation">.</span><span class="token function">WithContext</span><span class="token punctuation">(</span>reqCtx<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token comment">// ContextWithRandomTraceId 为Context生成随机的trace_id</span>
<span class="token keyword">func</span> <span class="token function">ContextWithRandomTraceId</span><span class="token punctuation">(</span>ctx context<span class="token punctuation">.</span>Context<span class="token punctuation">)</span> <span class="token punctuation">(</span>newCtx context<span class="token punctuation">.</span>Context<span class="token punctuation">)</span> <span class="token punctuation">{</span>
traceId <span class="token operator">:=</span> <span class="token function">UUID</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
newCtx <span class="token operator">=</span> <span class="token function">ContextWithTraceId</span><span class="token punctuation">(</span>ctx<span class="token punctuation">,</span> traceId<span class="token punctuation">)</span>
<span class="token keyword">return</span>
<span class="token punctuation">}</span>
<span class="token comment">// ContextWithTraceId 为Context设置trace_id. 如果ID已存在则不设置</span>
<span class="token keyword">func</span> <span class="token function">ContextWithTraceId</span><span class="token punctuation">(</span>ctx context<span class="token punctuation">.</span>Context<span class="token punctuation">,</span> traceId <span class="token builtin">string</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>newCtx context<span class="token punctuation">.</span>Context<span class="token punctuation">)</span> <span class="token punctuation">{</span>
oldTraceId<span class="token punctuation">,</span> ok <span class="token operator">:=</span> ctx<span class="token punctuation">.</span><span class="token function">Value</span><span class="token punctuation">(</span>TraceIdKey<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token punctuation">(</span><span class="token builtin">string</span><span class="token punctuation">)</span>
<span class="token keyword">if</span> ok <span class="token operator">&&</span> oldTraceId <span class="token operator">!=</span> <span class="token string">""</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span>
<span class="token punctuation">}</span>
newCtx <span class="token operator">=</span> context<span class="token punctuation">.</span><span class="token function">WithValue</span><span class="token punctuation">(</span>ctx<span class="token punctuation">,</span> TraceIdKey<span class="token punctuation">,</span> traceId<span class="token punctuation">)</span>
<span class="token keyword">return</span>
<span class="token punctuation">}</span>
<span class="token keyword">func</span> <span class="token function">UUID</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token builtin">string</span> <span class="token punctuation">{</span>
value <span class="token operator">:=</span> uuid<span class="token punctuation">.</span><span class="token function">New</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">return</span> strings<span class="token punctuation">.</span><span class="token function">ToLower</span><span class="token punctuation">(</span>
strings<span class="token punctuation">.</span><span class="token function">Replace</span><span class="token punctuation">(</span>value<span class="token punctuation">.</span><span class="token function">String</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string">"-"</span><span class="token punctuation">,</span> <span class="token string">""</span><span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token keyword">func</span> <span class="token function">BgContextWithCancel</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>ctx context<span class="token punctuation">.</span>Context<span class="token punctuation">,</span> cancel context<span class="token punctuation">.</span>CancelFunc<span class="token punctuation">)</span> <span class="token punctuation">{</span>
ctx<span class="token punctuation">,</span> cancel <span class="token operator">=</span> context<span class="token punctuation">.</span><span class="token function">WithCancel</span><span class="token punctuation">(</span>context<span class="token punctuation">.</span><span class="token function">Background</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
ctx <span class="token operator">=</span> <span class="token function">ContextWithRandomTraceId</span><span class="token punctuation">(</span>ctx<span class="token punctuation">)</span>
<span class="token keyword">return</span>
<span class="token punctuation">}</span>
<span class="token keyword">func</span> <span class="token function">GetTraceId</span><span class="token punctuation">(</span>ctx context<span class="token punctuation">.</span>Context<span class="token punctuation">)</span> <span class="token punctuation">(</span>traceId <span class="token builtin">string</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
traceId<span class="token punctuation">,</span> <span class="token boolean">_</span> <span class="token operator">=</span> ctx<span class="token punctuation">.</span><span class="token function">Value</span><span class="token punctuation">(</span>TraceIdKey<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token punctuation">(</span><span class="token builtin">string</span><span class="token punctuation">)</span>
<span class="token keyword">return</span>
<span class="token punctuation">}</span>
</code></pre>
<p>这样当我们每打一次日志的时候都可以记录到trace_id</p>
<p><img
src="https://img.helloteemo.com.cn/2022/06/13/1655126450.png" /></p>
<h1 id="写在最后">写在最后</h1>
<p>也不知道想写点什么东西在最后,就这样吧</p>
]]></content>
<categories>
<category>Golang</category>
</categories>
<tags>
<tag>go</tag>
</tags>
</entry>
<entry>
<title>SQL运行中间件</title>
<url>/2022/06/19/Golang/SQL%E8%BF%90%E8%A1%8C%E4%B8%AD%E9%97%B4%E4%BB%B6/</url>
<content><![CDATA[<blockquote>
<p>这是一个我很早之前书写的中间件,主要用于监控、收集项目中所运行的所有SQL信息,并对他们进行分析</p>
</blockquote>
<span id="more"></span>
<h1 id="sql运行中间件">SQL运行中间件</h1>
<p>本项目用于公司特定的运行环境,没有做其余的兼容措施,只是想分享一下解决这种问题的思路。</p>
<blockquote>
<p>运行环境:</p>
<p>目标数据库:<code>MySQL</code></p>
<p>SQL框架:<code>database/sql</code> 、<code>gorm.io</code></p>
</blockquote>
<h2 id="基础知识">基础知识</h2>
<p>如果我们需要监控所运行的SQL我们就需要了解Go <code>gorm.io</code>
框架的执行流程,这不做更多的介绍直接说结论,所有的SQL都会由一个叫做
<code>SQLCommon</code> 的对象执行。我们先看一下它的结构</p>
<figure>
<img src="https://img.helloteemo.com.cn/2022/06/19/1655651935.png"
alt="image-20220619231853946" />
<figcaption aria-hidden="true">image-20220619231853946</figcaption>
</figure>
<p>这样的话我们只需要代理这个接口的对象就可以拿到所有的SQL了。</p>
<h2 id="实现">实现</h2>
<p>接下来我们开始代理这个接口</p>
<figure>
<img src="https://img.helloteemo.com.cn/2022/06/19/1655652014.png"
alt="image-20220619232012835" />
<figcaption aria-hidden="true">image-20220619232012835</figcaption>
</figure>
<p>代理接口之后就可以拿到SQL的信息了,为了方便支持更多的监控,我们实现一个观察者模式,只要有SQL运行把消息发送过去就行。</p>
<figure>
<img src="https://img.helloteemo.com.cn/2022/06/19/1655652335.png"
alt="image-20220619232533973" />
<figcaption aria-hidden="true">image-20220619232533973</figcaption>
</figure>
<h1 id="写在最后">写在最后</h1>
<p>更多的就不想写了,就是一个比较基础的功能,主要是实现的思路比较奇特。</p>
<p><del>还可以水一篇博客,嘿嘿</del></p>
]]></content>
<categories>
<category>Golang</category>
</categories>
<tags>
<tag>go</tag>
</tags>
</entry>
<entry>
<title>gRPC在项目中的运用</title>
<url>/2024/07/12/Golang/gRPC%E5%9C%A8%E9%A1%B9%E7%9B%AE%E4%B8%AD%E7%9A%84%E8%BF%90%E7%94%A8/</url>
<content><![CDATA[<blockquote>
<p>gRPC在项目中的运用,本篇文章适用于想要在项目中运用gRPC作为通信框架的技术人员</p>
</blockquote>
<span id="more"></span>
<h1 id="版本说明">版本说明</h1>
<p>本文基于的框架、工具版本</p>
<table>
<colgroup>
<col style="width: 27%" />
<col style="width: 12%" />
<col style="width: 59%" />
</colgroup>
<thead>
<tr class="header">
<th>框架</th>
<th>版本</th>
<th>备注</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>go</td>
<td><code>1.15.17</code></td>
<td></td>
</tr>
<tr class="even">
<td>Protobuf</td>
<td><code>v1.5.2</code></td>
<td><code>github.com/golang/protobuf</code></td>
</tr>
<tr class="odd">
<td>gRPC</td>
<td><code>v1.51.0</code></td>
<td><code>google.golang.org/grpc</code></td>
</tr>
<tr class="even">
<td>Consul</td>
<td><code>v1.12.3</code></td>
<td></td>
</tr>
<tr class="odd">
<td>grpc-gateway</td>
<td><code>v2.11.3</code></td>
<td><code>github.com/grpc-ecosystem/grpc-gateway/v2</code></td>
</tr>
<tr class="even">
<td>grpc-consul-resolver</td>
<td><code>v1.4.4</code></td>
<td><code>github.com/mbobakov/grpc-consul-resolver</code></td>
</tr>
<tr class="odd">
<td>powerproto</td>
<td><code>v0.4.1</code></td>
<td><code>https://github.com/storyicon/powerproto</code></td>
</tr>
</tbody>
</table>
<h1 id="功能点">功能点</h1>
<ul class="task-list">
<li><label><input type="checkbox"
checked="" />Protobuf项目格式、规范</label></li>
<li><label><input type="checkbox"
checked="" />Protobuf编译工具</label></li>
<li><label><input type="checkbox" checked="" />gRPC
服务注册、发现</label></li>
<li><label><input type="checkbox" checked="" />参数校验框架</label></li>
<li><label><input type="checkbox" checked="" />异常恢复</label></li>
<li><label><input type="checkbox" checked="" />链路追踪</label></li>
<li><label><input type="checkbox" checked="" />监控</label></li>
<li><label><input type="checkbox" checked="" />认证</label></li>
<li><label><input type="checkbox" checked="" />异常日志打印</label></li>
<li><label><input type="checkbox" checked="" />grpcGateway</label></li>
<li><label><input type="checkbox" checked="" />RequestID</label></li>
<li><label><input type="checkbox" checked="" />统一响应格式</label></li>
<li><label><input type="checkbox" checked="" />熔断、限流</label></li>
<li><label><input type="checkbox"
checked="" />Protobuf文档生成</label></li>
</ul>
<h1 id="protobuf项目格式规范">Protobuf项目格式、规范</h1>
<h2 id="项目格式">项目格式</h2>
<p>先介绍一下Protobuf的项目格式、规范。所有的Protobuf文件都应该存在在一个统一的仓库、仓库组中,至于具体的存储方案可以看团队的大小,小团队可以无脑使用单仓库存放。</p>
<p>Protobuf的文件目录应该以项目来划分,如果需要新增一个服务,则直接在仓库根目录中新增一个以服务名命名的文件夹,然后在该文件夹中新增一个以服务名命名的proto文件,
例如新增一个名为<code>user</code>的服务,
则在仓库根目录中新增一个<code>user</code>文件夹,
然后在<code>user</code>文件夹中新增一个<code>user.proto</code>文件即可。</p>
<p><code>third_party</code> 存放所有项目引用三方proto文件</p>
<p>Protobuf内容只有包名规范比较重要,具体来说就是 package 和 go_package
的定义</p>
<h2 id="package">package</h2>
<p>包名为应用的表示(APPID). 用于生成gRPC的请求路径, 或者在 Proto
之间进行引用Message</p>
<p>例如:</p>
<pre class="language-none"><code class="language-none">// RequestURL: /grpc_sample.user/${serviceName}/${methodName}
package grpc_sample.user;</code></pre>
<p>其中 <code>grpc_sample</code> 为固定写法, <code>user</code>
为APPID</p>
<blockquote>
<p>目前有两种写法: <code>grpc_sample.${APPID}</code> 和
<code>apis.${APPID}</code>, 两种写法都可以, 但是为了统一, 建议使用
<code>grpc_sample.${APPID}</code></p>
</blockquote>
<h2 id="go_package">go_package</h2>
<p>固定为: <code>${前缀}/${APPID}</code></p>
<p>这里的前缀是 go.mod 的mould的地址</p>
<pre class="language-none"><code class="language-none">// 其中 github.com/helloteemo/pb 为前缀, ${APPID} 为 echo
option go_package = "github.com/helloteemo/pb/echo";</code></pre>
<h1 id="protobuf编译工具">Protobuf编译工具</h1>
<p>经过Protobuf的规范之后,我们拥有了如下格式的代码</p>
<pre class="language-tex" data-language="tex"><code class="language-tex">.
├── echo
│ ├── echo.proto
└── user
├── user.proto
├── go.mod
├── third_party</code></pre>
<p>但是拥有了Protobuf文件还不行,我们需要一个工具来根据IDL来生成Go代码,这个工具就是大名鼎鼎的
<code>protoc</code> ,出名的强大,出名的难配。你可能会遇到如下问题:</p>
<ol type="1">
<li>团队成员的protoc的版本不一致,导致生成的代码不一样,提交的时候就是一大堆的冲突</li>
<li>protoc的配置项太多,团队成员不会使用</li>
</ol>
<p>基于上面两个问题给出一个工具: <code>powerproto</code>
,用来统一团队成员的protoc</p>
<p>具体的安装过程、编译都跳过,这里给出一份配置:
<code>https://github.com/helloteemo/grpc-sample/blob/main/pb/powerproto.yaml</code>
,这份配置已经包括了:grpc代码生成、grpcGateway代码生成、validate生成、openapi稳定生成。应该是足够大部分的团队使用了。</p>
<h1 id="grpc服务注册发现">gRPC服务注册、发现</h1>
<p>这里笔者团队架构的分布式存储为consul,但是思路都是相通的。</p>
<p>服务注册的过程发生在grpc服务能够接受访问的上一时刻(或者完全能够接受访问),由服务主动向consul发起注册,注册信息应该包括服务ip+port、服务名等信息</p>
<p>如果程序接受到SIGTERM等信号时,应该将服务转变为不再接受请求的状态,同时将consul服务取消注册。</p>
<p>服务发现则由gRPC的Resolvr机制实现,</p>
<h1 id="参数校验框架">参数校验框架</h1>
<p>参数校验发生在gRPC服务端,由Protobuf文件生成校验规则之后直接编译成Go代码,在grpc服务中间件中进行校验。</p>
<p>具体规则见框架 <a
href="https://github.com/grpc-ecosystem/go-grpc-middleware/tree/v2">校验框架</a></p>
<h1 id="异常日志打印">异常日志打印</h1>
<p>这里的异常可以理解为是错误,是需要直接被抛出去处理的。</p>
<p>由于众所周知的Go异常处理问题,所以这里我们引入一个框架
<code>github.com/pkg/errors</code> 。它主要的作用是使 err
携带异常Stack、异常信息。在最底层发生异常的时候一层一层上传,在Service层捕获这个err。最后把异常包裹在
<code>google.golang.org/grpc/status</code>
中。然后使用中间件来打印异常。中间件使用函数
<code>status.Convert(err)</code> 来尝试把异常转化回
<code>status.Status</code> 。</p>
<p>这样就做到了统一的异常日志打印</p>
<h1 id="grpcgateway">grpcGateway</h1>
<p>前文我们一直在谈论grpcServer,可以知道grpcServer都是使用Protobuf协议、gRPC框架来进行传送的,这显然对客户端、前端同学不太友好,因此我们需要一个工具来进行:http转grpc。这也就是本章的主角:grpcGateway</p>
<blockquote>
<p>Gateway的主要作用是拥有一个集中式的网关服务,并使用它中转所有的流量。基于这种特性,我们可以得出一下结论:</p>
<ol type="1">
<li>gateway 必须高性能、高吞吐</li>
<li>不能做太多的业务逻辑,保持通用、轻量化</li>
<li>由于中转所有流量,因此它是服务统一的入口,可以按照这个特性来统计服务性能、服务异常等</li>
</ol>
</blockquote>
<p>对于grpcGateway我们在Protobuf中提前见过一面,它长这样</p>
<pre class="language-protobuf" data-language="protobuf"><code class="language-protobuf"><span class="token keyword">rpc</span> <span class="token function">Echo</span><span class="token punctuation">(</span><span class="token class-name">EchoRequest</span><span class="token punctuation">)</span> <span class="token keyword">returns</span> <span class="token punctuation">(</span><span class="token class-name">EchoResponse</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
<span class="token keyword">option</span> <span class="token punctuation">(</span>google<span class="token punctuation">.</span>api<span class="token punctuation">.</span>http<span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token punctuation">{</span>
post<span class="token punctuation">:</span><span class="token string">"/grpc-sample/echo/v1/do"</span>
body<span class="token punctuation">:</span> <span class="token string">"*"</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword">option</span> <span class="token punctuation">(</span>grpc<span class="token punctuation">.</span>gateway<span class="token punctuation">.</span>protoc_gen_openapiv2<span class="token punctuation">.</span>options<span class="token punctuation">.</span>openapiv2_operation<span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token punctuation">{</span>
summary<span class="token punctuation">:</span> <span class="token string">"echo"</span>
description<span class="token punctuation">:</span> <span class="token string">""</span><span class="token punctuation">,</span>
tags<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">"app"</span><span class="token punctuation">]</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>大括号里面的内容就是grpcgateway的定义了。它代表的含义是:我这个rpc接口也可以通过/grpc-sample/echo/v1/do这个http请求进行访问,同时我们在
PowerProto 的定义中也加入了 Gateway 的支持。</p>
<p>ok,知道了上面的知识,我们就可以来写Gateway服务了。</p>
<p>具体的代码可以见服务。</p>
<h1 id="requestid">RequestID</h1>
<p>RequestID的作用在DEBUG的时候再明细不过了。</p>
<p>思路为:gateway中生成RequestID,并携带在上下文中传递到各个service、service之间也需要携带RequestID去调用。</p>
<h1 id="统一响应格式">统一响应格式</h1>
<p>这里有两种思想可供大家参考</p>
<ol type="1">
<li>在每一个rpc中都携带一个Code,然后在Gateway或者前端nodejs中在包一层Code/Message。这样就可以知道服务端的Code业务异常。微信等是这样做的</li>
<li>在rpc不返回Code参数,而是交给<code>status</code>
包。在Gateway中统一从 <code>status</code> 包中拿到code进行Code返回</li>
</ol>
<p>第二种需要在Gateway中需要加一层响应流处理。第一种则不需要</p>
<h1 id="其它">其它</h1>
<p>链路追踪、认证、监控、熔断、限流、异常恢复等均可直接使用框架完成功能</p>
]]></content>
<categories>
<category>Golang</category>
</categories>
<tags>
<tag>go</tag>
</tags>
</entry>
<entry>
<title>gin c.Request.PostForm为空问题原理已经解决方法</title>
<url>/2020/12/22/Golang/gin-c-Request-PostForm%E4%B8%BA%E7%A9%BA%E9%97%AE%E9%A2%98%E5%8E%9F%E7%90%86%E5%B7%B2%E7%BB%8F%E8%A7%A3%E5%86%B3%E6%96%B9%E6%B3%95/</url>
<content><![CDATA[<blockquote>
<p>其实这个还是蛮简单的,初学时犯的错误,当时还是不太理解gin框架的整体流程</p>
</blockquote>
<span id="more"></span>
<p>问题的发现:</p>
<pre class="language-go" data-language="go"><code class="language-go">engine<span class="token punctuation">.</span><span class="token function">POST</span><span class="token punctuation">(</span><span class="token string">"/login"</span><span class="token punctuation">,</span> <span class="token keyword">func</span><span class="token punctuation">(</span>c <span class="token operator">*</span>gin<span class="token punctuation">.</span>Context<span class="token punctuation">)</span> <span class="token punctuation">{</span>
c<span class="token punctuation">.</span><span class="token function">JSON</span><span class="token punctuation">(</span>http<span class="token punctuation">.</span>StatusOK<span class="token punctuation">,</span> c<span class="token punctuation">.</span>Request<span class="token punctuation">.</span>PostForm<span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span></code></pre>
<p>请求、响应报文(报文都略有删减)如下:</p>
<pre class="language-none"><code class="language-none">// 请求报文
POST /login?userid=123 HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 31
Connection: keep-alive
username=user&password=password
// 响应报文
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 5
null</code></pre>
<p>按理来说我们应该接收这样的报文:</p>
<pre class="language-none"><code class="language-none">HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 46
{"password":["password"],"username":["user"]}</code></pre>
<p>我其实挺疑惑的,接着我通过<code>message := c.PostForm("message")</code>获取参数打印出来,代码如下:</p>
<pre class="language-go" data-language="go"><code class="language-go">engine<span class="token punctuation">.</span><span class="token function">POST</span><span class="token punctuation">(</span><span class="token string">"/login"</span><span class="token punctuation">,</span> <span class="token keyword">func</span><span class="token punctuation">(</span>c <span class="token operator">*</span>gin<span class="token punctuation">.</span>Context<span class="token punctuation">)</span> <span class="token punctuation">{</span>
fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>c<span class="token punctuation">.</span><span class="token function">PostForm</span><span class="token punctuation">(</span><span class="token string">"username"</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>c<span class="token punctuation">.</span><span class="token function">PostForm</span><span class="token punctuation">(</span><span class="token string">"password"</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
c<span class="token punctuation">.</span><span class="token function">JSON</span><span class="token punctuation">(</span>http<span class="token punctuation">.</span>StatusOK<span class="token punctuation">,</span> c<span class="token punctuation">.</span>Request<span class="token punctuation">.</span>PostForm<span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span></code></pre>
<p>然后,就成功了??????报文很正常的打印出了我们想要的结果,(黑人问号.jpg)
很自然的我们就想到了<code>c.PostForm("")</code>的副作用"解析"了<code>c.Request.PostForm</code>,使得我们使用时有数据,我们来验证一下:</p>
<pre class="language-go" data-language="go"><code class="language-go">engine<span class="token punctuation">.</span><span class="token function">POST</span><span class="token punctuation">(</span><span class="token string">"/login"</span><span class="token punctuation">,</span> <span class="token keyword">func</span><span class="token punctuation">(</span>c <span class="token operator">*</span>gin<span class="token punctuation">.</span>Context<span class="token punctuation">)</span> <span class="token punctuation">{</span>
fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>c<span class="token punctuation">.</span>Request<span class="token punctuation">.</span>PostForm<span class="token punctuation">)</span>
fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>c<span class="token punctuation">.</span><span class="token function">PostForm</span><span class="token punctuation">(</span><span class="token string">"username"</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>c<span class="token punctuation">.</span>Request<span class="token punctuation">.</span>PostForm<span class="token punctuation">)</span>
c<span class="token punctuation">.</span><span class="token function">JSON</span><span class="token punctuation">(</span>http<span class="token punctuation">.</span>StatusOK<span class="token punctuation">,</span> c<span class="token punctuation">.</span>Request<span class="token punctuation">.</span>PostForm<span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token comment">/*
map[]
user
map[password:[password] username:[user]]
*/</span></code></pre>
<p>结果显示确实如此,这条<code>c.PostForm</code>语句仿佛有魔力一样"解析了"报文,使得<code>c.Request.PostForm</code>里面填充了数据,实际上<code>c.Request.FormValue()</code>也有这种魔力。我们拨开迷雾去寻找真相吧。我们首先分析<code>c.PostForm</code>这个方法</p>
<pre class="language-go" data-language="go"><code class="language-go"><span class="token comment">// PostForm是通过c.GetPostForm拿到数据,忽略第二个返回值</span>
<span class="token keyword">func</span> <span class="token punctuation">(</span>c <span class="token operator">*</span>Context<span class="token punctuation">)</span> <span class="token function">PostForm</span><span class="token punctuation">(</span>key <span class="token builtin">string</span><span class="token punctuation">)</span> <span class="token builtin">string</span> <span class="token punctuation">{</span>
value<span class="token punctuation">,</span> <span class="token boolean">_</span> <span class="token operator">:=</span> c<span class="token punctuation">.</span><span class="token function">GetPostForm</span><span class="token punctuation">(</span>key<span class="token punctuation">)</span>
<span class="token keyword">return</span> value
<span class="token punctuation">}</span>
<span class="token comment">// 可以看到GetPostForm也是通过GetPostFormArray拿到返回值,但是只关注第一个值</span>
<span class="token keyword">func</span> <span class="token punctuation">(</span>c <span class="token operator">*</span>Context<span class="token punctuation">)</span> <span class="token function">GetPostForm</span><span class="token punctuation">(</span>key <span class="token builtin">string</span><span class="token punctuation">)</span> <span class="token punctuation">(</span><span class="token builtin">string</span><span class="token punctuation">,</span> <span class="token builtin">bool</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">if</span> values<span class="token punctuation">,</span> ok <span class="token operator">:=</span> c<span class="token punctuation">.</span><span class="token function">GetPostFormArray</span><span class="token punctuation">(</span>key<span class="token punctuation">)</span><span class="token punctuation">;</span> ok <span class="token punctuation">{</span>
<span class="token keyword">return</span> values<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span> ok
<span class="token punctuation">}</span>
<span class="token keyword">return</span> <span class="token string">""</span><span class="token punctuation">,</span> <span class="token boolean">false</span>
<span class="token punctuation">}</span>
<span class="token comment">// 这个方法作用很明显,获取formCache,从fromCache中拿到key对应的value,如果没有就返回一个空值,那么副作用肯定出现在c.getFormCache()中</span>
<span class="token keyword">func</span> <span class="token punctuation">(</span>c <span class="token operator">*</span>Context<span class="token punctuation">)</span> <span class="token function">GetPostFormArray</span><span class="token punctuation">(</span>key <span class="token builtin">string</span><span class="token punctuation">)</span> <span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">string</span><span class="token punctuation">,</span> <span class="token builtin">bool</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
c<span class="token punctuation">.</span><span class="token function">getFormCache</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">if</span> values <span class="token operator">:=</span> c<span class="token punctuation">.</span>formCache<span class="token punctuation">[</span>key<span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token function">len</span><span class="token punctuation">(</span>values<span class="token punctuation">)</span> <span class="token operator">></span> <span class="token number">0</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> values<span class="token punctuation">,</span> <span class="token boolean">true</span>
<span class="token punctuation">}</span>
<span class="token keyword">return</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">string</span><span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token boolean">false</span>
<span class="token punctuation">}</span>
<span class="token comment">// 这个方法也很简单,只有ParseMultipartForm能产生副作用,其它语句都不可能,就不分析了</span>
<span class="token keyword">func</span> <span class="token punctuation">(</span>c <span class="token operator">*</span>Context<span class="token punctuation">)</span> <span class="token function">getFormCache</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">if</span> c<span class="token punctuation">.</span>formCache <span class="token operator">==</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
c<span class="token punctuation">.</span>formCache <span class="token operator">=</span> <span class="token function">make</span><span class="token punctuation">(</span>url<span class="token punctuation">.</span>Values<span class="token punctuation">)</span>
req <span class="token operator">:=</span> c<span class="token punctuation">.</span>Request
<span class="token keyword">if</span> err <span class="token operator">:=</span> req<span class="token punctuation">.</span><span class="token function">ParseMultipartForm</span><span class="token punctuation">(</span>c<span class="token punctuation">.</span>engine<span class="token punctuation">.</span>MaxMultipartMemory<span class="token punctuation">)</span><span class="token punctuation">;</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
<span class="token keyword">if</span> err <span class="token operator">!=</span> http<span class="token punctuation">.</span>ErrNotMultipart <span class="token punctuation">{</span>
<span class="token function">debugPrint</span><span class="token punctuation">(</span><span class="token string">"error on parse multipart form array: %v"</span><span class="token punctuation">,</span> err<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
c<span class="token punctuation">.</span>formCache <span class="token operator">=</span> req<span class="token punctuation">.</span>PostForm
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token comment">// 代码很长没有贴全,通过命名我们得到了关键的语句:r.ParseForm,可以去测试一下是否是这条语句</span>
<span class="token keyword">func</span> <span class="token punctuation">(</span>r <span class="token operator">*</span>Request<span class="token punctuation">)</span> <span class="token function">ParseMultipartForm</span><span class="token punctuation">(</span>maxMemory <span class="token builtin">int64</span><span class="token punctuation">)</span> <span class="token builtin">error</span> <span class="token punctuation">{</span>
<span class="token keyword">if</span> r<span class="token punctuation">.</span>MultipartForm <span class="token operator">==</span> multipartByReader <span class="token punctuation">{</span>
<span class="token keyword">return</span> errors<span class="token punctuation">.</span><span class="token function">New</span><span class="token punctuation">(</span><span class="token string">"http: multipart handled by MultipartReader"</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token keyword">if</span> r<span class="token punctuation">.</span>Form <span class="token operator">==</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
err <span class="token operator">:=</span> r<span class="token punctuation">.</span><span class="token function">ParseForm</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> err
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token operator">...</span>
<span class="token punctuation">}</span>
<span class="token comment">// c.Request.FormValue()也是通过ParseForm去解析的,同时parseForm的代码就不分析了</span></code></pre>
<p>最后让我们测试一下:</p>
<pre class="language-go" data-language="go"><code class="language-go">engine<span class="token punctuation">.</span><span class="token function">POST</span><span class="token punctuation">(</span><span class="token string">"/login"</span><span class="token punctuation">,</span> <span class="token keyword">func</span><span class="token punctuation">(</span>c <span class="token operator">*</span>gin<span class="token punctuation">.</span>Context<span class="token punctuation">)</span> <span class="token punctuation">{</span>
fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>c<span class="token punctuation">.</span>Request<span class="token punctuation">.</span>PostForm<span class="token punctuation">)</span>
<span class="token boolean">_</span> <span class="token operator">=</span> c<span class="token punctuation">.</span>Request<span class="token punctuation">.</span><span class="token function">ParseForm</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>c<span class="token punctuation">.</span>Request<span class="token punctuation">.</span>PostForm<span class="token punctuation">)</span>
c<span class="token punctuation">.</span><span class="token function">JSON</span><span class="token punctuation">(</span>http<span class="token punctuation">.</span>StatusOK<span class="token punctuation">,</span> c<span class="token punctuation">.</span>Request<span class="token punctuation">.</span>PostForm<span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token comment">/*