-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.xml
3722 lines (3717 loc) · 553 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>Python 3.8+里的asyncio和asyncio.Semaphore</title>
<url>/tech/asyncio-and-asyncio-semaphore-in-python-3-8/</url>
<content><![CDATA[<ul>
<li>Title(EN): <em><strong>asyncio and asyncio.Semaphore in Python 3.8+</strong></em></li>
<li>Author: dog2</li>
</ul>
<hr />
<p>以前的程序放在Python 3.8里跑出错了,原来是由于Python升级3.8后协程库<code>asyncio</code>又双叒叕更新了。 新版本里<code>asyncio.Semaphore</code>的用法改变了,本文简单记录一下新写法。</p>
<a id="more"></a>
<p>代码说明:用<span class="exturl" data-url="aHR0cHM6Ly93d3cucHl0aG9uLWh0dHB4Lm9yZy9hc3luYy8=">支持异步<i class="fa fa-external-link-alt"></i></span>的http库<span class="exturl" data-url="aHR0cHM6Ly93d3cucHl0aG9uLWh0dHB4Lm9yZy8=">httpx<i class="fa fa-external-link-alt"></i></span>简单爬数据,用<code>asyncio.Semaphore</code>控制并发数,而<code>asyncio.Semaphore</code>在Python 3.8中需要配合上下文管理器<code>contextvars.ContextVar</code>使用。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> httpx</span><br><span class="line"><span class="keyword">import</span> asyncio</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> contextvars <span class="keyword">import</span> ContextVar</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">crawl</span><span class="params">(concurrency=<span class="number">3</span>)</span>:</span></span><br><span class="line"> context = ContextVar(<span class="string">"concurrent"</span>) <span class="comment"># 定义全局上下文管理器</span></span><br><span class="line"> URL_BASE = <span class="string">'https://github.com/topics?page='</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">async</span> <span class="function"><span class="keyword">def</span> <span class="title">crawl_one</span><span class="params">(i)</span>:</span></span><br><span class="line"> sem = context.get() <span class="comment"># 获取上下文关键字</span></span><br><span class="line"> <span class="keyword">async</span> <span class="keyword">with</span> sem:</span><br><span class="line"> <span class="keyword">async</span> <span class="keyword">with</span> httpx.AsyncClient() <span class="keyword">as</span> client:</span><br><span class="line"> r = <span class="keyword">await</span> client.get(<span class="string">f"<span class="subst">{URL_BASE}</span><span class="subst">{i}</span>"</span>)</span><br><span class="line"> <span class="keyword">return</span> len(r.text)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">async</span> <span class="function"><span class="keyword">def</span> <span class="title">crawl_all</span><span class="params">()</span>:</span></span><br><span class="line"> context.set(asyncio.Semaphore(concurrency)) <span class="comment">#上下文管理器赋值,concurrency控制并发数</span></span><br><span class="line"> tasks = [asyncio.create_task(crawl_one(i)) <span class="keyword">for</span> i <span class="keyword">in</span> range(<span class="number">1</span>, <span class="number">30</span>)] <span class="comment"># 将协程封装成任务共30个</span></span><br><span class="line"> done, pending = <span class="keyword">await</span> asyncio.wait(tasks) <span class="comment"># 执行所有任务</span></span><br><span class="line"> <span class="keyword">return</span> done</span><br><span class="line"></span><br><span class="line"> tasks_done = asyncio.run(crawl_all())</span><br><span class="line"> <span class="keyword">return</span> [t.result() <span class="keyword">for</span> t <span class="keyword">in</span> tasks_done]</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">'__main__'</span>:</span><br><span class="line"> <span class="keyword">for</span> r <span class="keyword">in</span> crawl(concurrency=<span class="number">1</span>):</span><br><span class="line"> print(r)</span><br></pre></td></tr></table></figure>
<p>参考链接: - <span class="exturl" data-url="aHR0cHM6Ly93d3cuY25ibG9ncy5jb20vYy14LWEvcC8xMDg2ODQ5Ny5odG1s">Python 协程模块 asyncio 使用指南<i class="fa fa-external-link-alt"></i></span></p>
]]></content>
<categories>
<category>Tech</category>
</categories>
<tags>
<tag>Async</tag>
<tag>Coroutine</tag>
<tag>Dev</tag>
<tag>Python</tag>
<tag>Semaphore</tag>
</tags>
</entry>
<entry>
<title>用BasicTeX构建轻量LaTeX写作环境</title>
<url>/tech/building-a-lightweight-latex-environment-with-basictex/</url>
<content><![CDATA[<ul>
<li>Title(EN): <em><strong>Building A Lightweight LaTeX Environment with BasicTeX</strong></em></li>
<li>Author: dog2</li>
</ul>
<hr />
<h1 id="按">按</h1>
<ul>
<li>环境:Max OS 10.15</li>
<li>需求 => 解决方案
<ul>
<li>轻量:TeX Live太重 => 用精简命令行版<code>BasicTeX</code>替代</li>
<li>简单:仅编写公式及特殊情况下的简单写作
<ul>
<li>用<code>Visual Studio Code</code>的插件<code>LaTeX Workshop</code>,它可以实时编辑/编译TeX (推荐)</li>
<li>极简TeX编辑器<code>TeXworks</code></li>
</ul></li>
</ul></li>
</ul>
<a id="more"></a>
<h1 id="环境安装">环境安装</h1>
<h2 id="安装basictex">安装BasicTeX</h2>
<p><span class="exturl" data-url="aHR0cHM6Ly90dWcub3JnL21hY3RleC9tb3JlcGFja2FnZXMuaHRtbA==">BasicTeX<i class="fa fa-external-link-alt"></i></span>是<code>TeX Live</code>的Mac OS版本<code>MacTeX</code>的精简版,前者80MB,后者好几GB。<code>BasicTex</code>只有核心的TeX语法编译的命令行工具,不包含GUI程序。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">brew cask install basictex</span><br></pre></td></tr></table></figure>
<p>安装完成后就可以使用<code>TeX Live</code>的包管理命令<code>tlmgr</code>来管理TeX的包(宏集)了。</p>
<p>使刚刚安装的<code>tlmgr</code>命令在当前shell生效</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">eval</span> <span class="string">"<span class="variable">$(/usr/libexec/path_helper)</span>"</span></span><br></pre></td></tr></table></figure>
<p>将Tex目录永久添加到PATH(默认安装不会添加)</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">sudo tlmgr path add</span><br></pre></td></tr></table></figure>
<p>更新内置所有TeX包</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">sudo tlmgr update --self --all</span><br></pre></td></tr></table></figure>
<h2 id="安装编辑编译环境">安装编辑/编译环境</h2>
<h3 id="准备工作安装ctex-宏集以支持中文">准备工作:安装CTeX 宏集以支持中文</h3>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">sudo tlmgr install ctex</span><br></pre></td></tr></table></figure>
<h3 id="可选编辑器1-visual-studio-code">可选编辑器1: Visual Studio Code</h3>
<h4 id="安装插件">安装插件</h4>
<p>请安装以下插件:</p>
<ul>
<li><code>LaTeX Workshop</code>(必须)</li>
<li><code>LaTeX language support</code>(可选)</li>
<li><code>Useful Snippets</code>(可选)</li>
</ul>
<h4 id="配置latex-workshop插件">配置LaTeX Workshop插件</h4>
<ul>
<li>Code > Preferences > Settings</li>
<li>搜索<code>@ext:james-yu.latex-workshop Recipes</code></li>
<li>点击<code>Edit in settings.json</code></li>
<li>修改<code>"latex-workshop.latex.tools"</code>和<code>"latex-workshop.latex.recipes"</code>为如下配置</li>
<li>重启VSCode窗口</li>
</ul>
<figure class="highlight"><table><tr><td class="code"><pre><span class="line">"latex-workshop.latex.tools": [</span><br><span class="line"> {</span><br><span class="line"> <span class="attr">"name"</span>: <span class="string">"xelatex"</span>,</span><br><span class="line"> <span class="attr">"command"</span>: <span class="string">"xelatex"</span>,</span><br><span class="line"> <span class="attr">"args"</span>: [</span><br><span class="line"> <span class="string">"-synctex=1"</span>,</span><br><span class="line"> <span class="string">"-interaction=nonstopmode"</span>,</span><br><span class="line"> <span class="string">"-file-line-error"</span>,</span><br><span class="line"> <span class="string">"-pdf"</span>,</span><br><span class="line"> <span class="string">"%DOC%"</span></span><br><span class="line"> ]</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> <span class="attr">"name"</span>: <span class="string">"latexmk"</span>,</span><br><span class="line"> <span class="attr">"command"</span>: <span class="string">"latexmk"</span>,</span><br><span class="line"> <span class="attr">"args"</span>: [</span><br><span class="line"> <span class="string">"-synctex=1"</span>,</span><br><span class="line"> <span class="string">"-interaction=nonstopmode"</span>,</span><br><span class="line"> <span class="string">"-file-line-error"</span>,</span><br><span class="line"> <span class="string">"-pdf"</span>,</span><br><span class="line"> <span class="string">"%DOC%"</span></span><br><span class="line"> ]</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> <span class="attr">"name"</span>: <span class="string">"pdflatex"</span>,</span><br><span class="line"> <span class="attr">"command"</span>: <span class="string">"pdflatex"</span>,</span><br><span class="line"> <span class="attr">"args"</span>: [</span><br><span class="line"> <span class="string">"-synctex=1"</span>,</span><br><span class="line"> <span class="string">"-interaction=nonstopmode"</span>,</span><br><span class="line"> <span class="string">"-file-line-error"</span>,</span><br><span class="line"> <span class="string">"%DOC%"</span></span><br><span class="line"> ]</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> <span class="attr">"name"</span>: <span class="string">"bibtex"</span>,</span><br><span class="line"> <span class="attr">"command"</span>: <span class="string">"bibtex"</span>,</span><br><span class="line"> <span class="attr">"args"</span>: [</span><br><span class="line"> <span class="string">"%DOCFILE%"</span></span><br><span class="line"> ]</span><br><span class="line"> }</span><br><span class="line">],</span><br><span class="line">"latex-workshop.latex.recipes": [</span><br><span class="line"> {</span><br><span class="line"> <span class="attr">"name"</span>: <span class="string">"xelatex"</span>,</span><br><span class="line"> <span class="attr">"tools"</span>: [</span><br><span class="line"> <span class="string">"xelatex"</span></span><br><span class="line"> ]</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> <span class="attr">"name"</span>: <span class="string">"pdflatex -> bibtex -> pdflatex*2"</span>,</span><br><span class="line"> <span class="attr">"tools"</span>: [</span><br><span class="line"> <span class="string">"pdflatex"</span>,</span><br><span class="line"> <span class="string">"bibtex"</span>,</span><br><span class="line"> <span class="string">"pdflatex"</span>,</span><br><span class="line"> <span class="string">"pdflatex"</span></span><br><span class="line"> ]</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> <span class="attr">"name"</span>: <span class="string">"xe->bib->xe->xe"</span>,</span><br><span class="line"> <span class="attr">"tools"</span>: [</span><br><span class="line"> <span class="string">"xelatex"</span>,</span><br><span class="line"> <span class="string">"bibtex"</span>,</span><br><span class="line"> <span class="string">"xelatex"</span>,</span><br><span class="line"> <span class="string">"xelatex"</span></span><br><span class="line"> ]</span><br><span class="line"> }</span><br><span class="line">]</span><br></pre></td></tr></table></figure>
<h4 id="使用">使用</h4>
<ol type="1">
<li>用VSCode创建并编写.tex文件</li>
<li>点击VSCode左侧Bar中的TEX</li>
<li>在左侧菜单中,选择 <code>Build LaTeX project</code></li>
<li>若为出现错误,选择 <code>View LaTeX PDF</code>,右侧标签页会弹出pdf预览</li>
<li>至此,修改左侧.tex的文件并保存后,插件会自动帮助我编译.tex文件生成pdf文件,并在右侧更新渲染。</li>
</ol>
<img src="/tech/building-a-lightweight-latex-environment-with-basictex/vscode.gif" class="" title="Visual Stuido Code">
<h3 id="可选编辑器2-texworks">可选编辑器2: TeXworks</h3>
<p><span class="exturl" data-url="aHR0cDovL3d3dy50dWcub3JnL3RleHdvcmtzLw==">TeXworks<i class="fa fa-external-link-alt"></i></span>是一款极简的TeX图形编辑器。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">brew cask install texworks</span><br></pre></td></tr></table></figure>
<h4 id="你好world">你好,world</h4>
<p>打开 TeXworks,选择<code>XeLaTeX</code>作为排版工具,编译执行</p>
<figure class="highlight tex"><table><tr><td class="code"><pre><span class="line"><span class="tag">\<span class="name">documentclass</span><span class="string">[UTF8]</span><span class="string">{ctexart}</span></span></span><br><span class="line"><span class="tag">\<span class="name">begin</span><span class="string">{document}</span></span></span><br><span class="line">你好,world!</span><br><span class="line"><span class="tag">\<span class="name">end</span><span class="string">{document}</span></span></span><br></pre></td></tr></table></figure>
<img src="/tech/building-a-lightweight-latex-environment-with-basictex/1.png" class="" title="用TeXworks编写demo">
<img src="/tech/building-a-lightweight-latex-environment-with-basictex/2.png" class="" title="渲染pdf">
<h1 id="编写公式">编写公式</h1>
<p>编写测试数学公式,代码来源于<span class="exturl" data-url="aHR0cHM6Ly9saWFtLnBhZ2UvMjAxNC8wOS8wOC9sYXRleC1pbnRyb2R1Y3Rpb24v">一份其实很短的 LaTeX 入门文档<i class="fa fa-external-link-alt"></i></span></p>
<details>
<p><summary> 公式测试代码 </summary></p>
<figure class="highlight tex"><table><tr><td class="code"><pre><span class="line"><span class="tag">\<span class="name">documentclass</span><span class="string">[UTF8]</span><span class="string">{ctexart}</span></span></span><br><span class="line"><span class="tag">\<span class="name">usepackage</span><span class="string">{amsmath}</span></span> <span class="comment">% 为了使用 AMS-LaTeX 提供的数学功能,我们需要在导言区加载 amsmath 宏包</span></span><br><span class="line"><span class="tag">\<span class="name">ctexset</span><span class="string">{section/format=\Large\bfseries}</span></span></span><br><span class="line"><span class="tag">\<span class="name">begin</span><span class="string">{document}</span></span></span><br><span class="line"><span class="tag">\<span class="name">section</span><span class="string">{上下标}</span></span></span><br><span class="line">Einstein 's <span class="formula">$E=mc^2$</span>.</span><br><span class="line"><span class="tag">\<span class="name">[</span></span> E=mc^2. <span class="tag">\<span class="name">]</span></span></span><br><span class="line"></span><br><span class="line"><span class="tag">\<span class="name">begin</span><span class="string">{equation}</span></span></span><br><span class="line">E=mc^2.</span><br><span class="line"><span class="tag">\<span class="name">end</span><span class="string">{equation}</span></span></span><br><span class="line"></span><br><span class="line"><span class="tag">\<span class="name">section</span><span class="string">{根式与分式}</span></span></span><br><span class="line"></span><br><span class="line"><span class="formula">$<span class="tag">\<span class="name">sqrt</span><span class="string">{x}</span></span>$</span>, <span class="formula">$<span class="tag">\<span class="name">frac</span><span class="string">{1}</span><span class="string">{2}</span></span>$</span>.</span><br><span class="line"></span><br><span class="line"><span class="tag">\<span class="name">[</span></span> <span class="tag">\<span class="name">sqrt</span><span class="string">{x}</span></span>, <span class="tag">\<span class="name">]</span></span></span><br><span class="line"></span><br><span class="line"><span class="tag">\<span class="name">[</span></span> <span class="tag">\<span class="name">frac</span><span class="string">{1}</span><span class="string">{2}</span></span>. <span class="tag">\<span class="name">]</span></span></span><br><span class="line"></span><br><span class="line"><span class="tag">\<span class="name">[</span></span> z = r<span class="tag">\<span class="name">cdot</span></span> e^{2<span class="tag">\<span class="name">pi</span></span> i}. <span class="tag">\<span class="name">]</span></span></span><br><span class="line"></span><br><span class="line"><span class="tag">\<span class="name">section</span><span class="string">{运算符}</span></span></span><br><span class="line"><span class="tag">\<span class="name">[</span></span> <span class="tag">\<span class="name">pm</span></span><span class="tag">\<span class="name">;</span></span> <span class="tag">\<span class="name">times</span></span> <span class="tag">\<span class="name">;</span></span> <span class="tag">\<span class="name">div</span></span><span class="tag">\<span class="name">;</span></span> <span class="tag">\<span class="name">cdot</span></span><span class="tag">\<span class="name">;</span></span> <span class="tag">\<span class="name">cap</span></span><span class="tag">\<span class="name">;</span></span> <span class="tag">\<span class="name">cup</span></span><span class="tag">\<span class="name">;</span></span></span><br><span class="line"><span class="tag">\<span class="name">geq</span></span><span class="tag">\<span class="name">;</span></span> <span class="tag">\<span class="name">leq</span></span><span class="tag">\<span class="name">;</span></span> <span class="tag">\<span class="name">neq</span></span><span class="tag">\<span class="name">;</span></span> <span class="tag">\<span class="name">approx</span></span> <span class="tag">\<span class="name">;</span></span> <span class="tag">\<span class="name">equiv</span></span> <span class="tag">\<span class="name">]</span></span></span><br><span class="line"></span><br><span class="line"><span class="formula">$ <span class="tag">\<span class="name">sum</span></span>_{i=1}^n i<span class="tag">\<span class="name">quad</span></span> <span class="tag">\<span class="name">prod</span></span>_{i=1}^n $</span></span><br><span class="line"><span class="formula">$ <span class="tag">\<span class="name">sum</span></span><span class="tag">\<span class="name">limits</span></span> _{i=1}^n i<span class="tag">\<span class="name">quad</span></span> <span class="tag">\<span class="name">prod</span></span><span class="tag">\<span class="name">limits</span></span> _{i=1}^n $</span></span><br><span class="line"><span class="tag">\<span class="name">[</span></span> <span class="tag">\<span class="name">lim</span></span>_{x<span class="tag">\<span class="name">to</span></span>0}x^2 <span class="tag">\<span class="name">quad</span></span> <span class="tag">\<span class="name">int</span></span>_a^b x^2 dx <span class="tag">\<span class="name">]</span></span></span><br><span class="line"><span class="tag">\<span class="name">[</span></span> <span class="tag">\<span class="name">lim</span></span><span class="tag">\<span class="name">nolimits</span></span> _{x<span class="tag">\<span class="name">to</span></span>0}x^2<span class="tag">\<span class="name">quad</span></span> <span class="tag">\<span class="name">int</span></span><span class="tag">\<span class="name">nolimits</span></span>_a^b x^2 dx <span class="tag">\<span class="name">]</span></span></span><br><span class="line"></span><br><span class="line"><span class="tag">\<span class="name">[</span></span> <span class="tag">\<span class="name">iint</span></span><span class="tag">\<span class="name">quad</span></span> <span class="tag">\<span class="name">iiint</span></span><span class="tag">\<span class="name">quad</span></span> <span class="tag">\<span class="name">iiiint</span></span><span class="tag">\<span class="name">quad</span></span> <span class="tag">\<span class="name">idotsint</span></span> <span class="tag">\<span class="name">]</span></span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="tag">\<span class="name">section</span><span class="string">{定界符(括号等)}</span></span></span><br><span class="line"><span class="tag">\<span class="name">[</span></span> <span class="tag">\<span class="name">Biggl</span></span>(<span class="tag">\<span class="name">biggl</span></span>(<span class="tag">\<span class="name">Bigl</span></span>(<span class="tag">\<span class="name">bigl</span></span>((x)<span class="tag">\<span class="name">bigr</span></span>)<span class="tag">\<span class="name">Bigr</span></span>)<span class="tag">\<span class="name">biggr</span></span>)<span class="tag">\<span class="name">Biggr</span></span>) <span class="tag">\<span class="name">]</span></span></span><br><span class="line"><span class="tag">\<span class="name">[</span></span> <span class="tag">\<span class="name">Biggl</span><span class="string">[\biggl[\Bigl[\bigl[[x]</span></span><span class="tag">\<span class="name">bigr</span></span>]<span class="tag">\<span class="name">Bigr</span></span>]<span class="tag">\<span class="name">biggr</span></span>]<span class="tag">\<span class="name">Biggr</span></span>] <span class="tag">\<span class="name">]</span></span></span><br><span class="line"><span class="tag">\<span class="name">[</span></span> <span class="tag">\<span class="name">Biggl</span></span> <span class="tag">\<span class="name">{</span></span><span class="tag">\<span class="name">biggl</span></span> <span class="tag">\<span class="name">{</span></span><span class="tag">\<span class="name">Bigl</span></span> <span class="tag">\<span class="name">{</span></span><span class="tag">\<span class="name">bigl</span></span> <span class="tag">\<span class="name">{</span></span><span class="tag">\<span class="name">{</span></span>x<span class="tag">\<span class="name">}</span></span><span class="tag">\<span class="name">bigr</span></span> <span class="tag">\<span class="name">}</span></span><span class="tag">\<span class="name">Bigr</span></span> <span class="tag">\<span class="name">}</span></span><span class="tag">\<span class="name">biggr</span></span> <span class="tag">\<span class="name">}</span></span><span class="tag">\<span class="name">Biggr</span></span><span class="tag">\<span class="name">}</span></span> <span class="tag">\<span class="name">]</span></span></span><br><span class="line"><span class="tag">\<span class="name">[</span></span> <span class="tag">\<span class="name">Biggl</span></span><span class="tag">\<span class="name">langle</span></span><span class="tag">\<span class="name">biggl</span></span><span class="tag">\<span class="name">langle</span></span><span class="tag">\<span class="name">Bigl</span></span><span class="tag">\<span class="name">langle</span></span><span class="tag">\<span class="name">bigl</span></span><span class="tag">\<span class="name">langle</span></span><span class="tag">\<span class="name">langle</span></span> x</span><br><span class="line"><span class="tag">\<span class="name">rangle</span></span><span class="tag">\<span class="name">bigr</span></span><span class="tag">\<span class="name">rangle</span></span><span class="tag">\<span class="name">Bigr</span></span><span class="tag">\<span class="name">rangle</span></span><span class="tag">\<span class="name">biggr</span></span><span class="tag">\<span class="name">rangle</span></span><span class="tag">\<span class="name">Biggr</span></span><span class="tag">\<span class="name">rangle</span></span> <span class="tag">\<span class="name">]</span></span></span><br><span class="line"><span class="tag">\<span class="name">[</span></span> <span class="tag">\<span class="name">Biggl</span></span><span class="tag">\<span class="name">lvert</span></span><span class="tag">\<span class="name">biggl</span></span><span class="tag">\<span class="name">lvert</span></span><span class="tag">\<span class="name">Bigl</span></span><span class="tag">\<span class="name">lvert</span></span><span class="tag">\<span class="name">bigl</span></span><span class="tag">\<span class="name">lvert</span></span><span class="tag">\<span class="name">lvert</span></span> x</span><br><span class="line"><span class="tag">\<span class="name">rvert</span></span><span class="tag">\<span class="name">bigr</span></span><span class="tag">\<span class="name">rvert</span></span><span class="tag">\<span class="name">Bigr</span></span><span class="tag">\<span class="name">rvert</span></span><span class="tag">\<span class="name">biggr</span></span><span class="tag">\<span class="name">rvert</span></span><span class="tag">\<span class="name">Biggr</span></span><span class="tag">\<span class="name">rvert</span></span> <span class="tag">\<span class="name">]</span></span></span><br><span class="line"><span class="tag">\<span class="name">[</span></span> <span class="tag">\<span class="name">Biggl</span></span><span class="tag">\<span class="name">lVert</span></span><span class="tag">\<span class="name">biggl</span></span><span class="tag">\<span class="name">lVert</span></span><span class="tag">\<span class="name">Bigl</span></span><span class="tag">\<span class="name">lVert</span></span><span class="tag">\<span class="name">bigl</span></span><span class="tag">\<span class="name">lVert</span></span><span class="tag">\<span class="name">lVert</span></span> x</span><br><span class="line"><span class="tag">\<span class="name">rVert</span></span><span class="tag">\<span class="name">bigr</span></span><span class="tag">\<span class="name">rVert</span></span><span class="tag">\<span class="name">Bigr</span></span><span class="tag">\<span class="name">rVert</span></span><span class="tag">\<span class="name">biggr</span></span><span class="tag">\<span class="name">rVert</span></span><span class="tag">\<span class="name">Biggr</span></span><span class="tag">\<span class="name">rVert</span></span> <span class="tag">\<span class="name">]</span></span></span><br><span class="line">(x)</span><br><span class="line"><span class="tag">\<span class="name">[</span></span> <span class="tag">\<span class="name">bigl</span></span>((x)<span class="tag">\<span class="name">bigr</span></span>) <span class="tag">\<span class="name">]</span></span></span><br><span class="line"></span><br><span class="line"><span class="tag">\<span class="name">section</span><span class="string">{省略号}</span></span></span><br><span class="line"><span class="tag">\<span class="name">[</span></span> x_1,x_2,<span class="tag">\<span class="name">dots</span></span> ,x_n<span class="tag">\<span class="name">quad</span></span> 1,2,<span class="tag">\<span class="name">cdots</span></span> ,n<span class="tag">\<span class="name">quad</span></span></span><br><span class="line"><span class="tag">\<span class="name">vdots</span></span><span class="tag">\<span class="name">quad</span></span> <span class="tag">\<span class="name">ddots</span></span> <span class="tag">\<span class="name">]</span></span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="tag">\<span class="name">section</span><span class="string">{矩阵}</span></span></span><br><span class="line"><span class="tag">\<span class="name">[</span></span> <span class="tag">\<span class="name">begin</span><span class="string">{pmatrix}</span></span> a&b<span class="tag">\<span class="name">\</span></span>c&d <span class="tag">\<span class="name">end</span><span class="string">{pmatrix}</span></span> <span class="tag">\<span class="name">quad</span></span></span><br><span class="line"><span class="tag">\<span class="name">begin</span><span class="string">{bmatrix}</span></span> a&b<span class="tag">\<span class="name">\</span></span>c&d <span class="tag">\<span class="name">end</span><span class="string">{bmatrix}</span></span> <span class="tag">\<span class="name">quad</span></span></span><br><span class="line"><span class="tag">\<span class="name">begin</span><span class="string">{Bmatrix}</span></span> a&b<span class="tag">\<span class="name">\</span></span>c&d <span class="tag">\<span class="name">end</span><span class="string">{Bmatrix}</span></span> <span class="tag">\<span class="name">quad</span></span></span><br><span class="line"><span class="tag">\<span class="name">begin</span><span class="string">{vmatrix}</span></span> a&b<span class="tag">\<span class="name">\</span></span>c&d <span class="tag">\<span class="name">end</span><span class="string">{vmatrix}</span></span> <span class="tag">\<span class="name">quad</span></span></span><br><span class="line"><span class="tag">\<span class="name">begin</span><span class="string">{Vmatrix}</span></span> a&b<span class="tag">\<span class="name">\</span></span>c&d <span class="tag">\<span class="name">end</span><span class="string">{Vmatrix}</span></span> <span class="tag">\<span class="name">]</span></span></span><br><span class="line"></span><br><span class="line">Marry has a little matrix <span class="formula">$ ( <span class="tag">\<span class="name">begin</span><span class="string">{smallmatrix}</span></span> a&b<span class="tag">\<span class="name">\</span></span>c&d <span class="tag">\<span class="name">end</span><span class="string">{smallmatrix}</span></span> ) $</span>.</span><br><span class="line"></span><br><span class="line"><span class="tag">\<span class="name">section</span></span>(多行公式)</span><br><span class="line"></span><br><span class="line"><span class="tag">\<span class="name">subsection</span><span class="string">{长公式}</span></span></span><br><span class="line"><span class="tag">\<span class="name">subsubsection</span><span class="string">{不对齐}</span></span></span><br><span class="line"><span class="tag">\<span class="name">begin</span><span class="string">{multline}</span></span></span><br><span class="line">x = a+b+c+{} <span class="tag">\<span class="name">\</span></span></span><br><span class="line">d+e+f+g</span><br><span class="line"><span class="tag">\<span class="name">end</span><span class="string">{multline}</span></span></span><br><span class="line"></span><br><span class="line"><span class="tag">\<span class="name">subsubsection</span><span class="string">{对齐}</span></span></span><br><span class="line"><span class="tag">\<span class="name">begin</span><span class="string">{multline*}</span></span></span><br><span class="line">x = a+b+c+{} <span class="tag">\<span class="name">\</span></span></span><br><span class="line">d+e+f+g</span><br><span class="line"><span class="tag">\<span class="name">end</span><span class="string">{multline*}</span></span></span><br><span class="line"></span><br><span class="line"><span class="tag">\<span class="name">subsection</span><span class="string">{公式组}</span></span></span><br><span class="line"><span class="tag">\<span class="name">begin</span><span class="string">{gather}</span></span></span><br><span class="line">a = b+c+d <span class="tag">\<span class="name">\</span></span></span><br><span class="line">x = y+z</span><br><span class="line"><span class="tag">\<span class="name">end</span><span class="string">{gather}</span></span></span><br><span class="line"><span class="tag">\<span class="name">begin</span><span class="string">{align}</span></span></span><br><span class="line">a &= b+c+d <span class="tag">\<span class="name">\</span></span></span><br><span class="line">x &= y+z</span><br><span class="line"><span class="tag">\<span class="name">end</span><span class="string">{align}</span></span></span><br><span class="line"></span><br><span class="line"><span class="tag">\<span class="name">subsection</span><span class="string">{分段函数}</span></span></span><br><span class="line"><span class="tag">\<span class="name">[</span></span> y= <span class="tag">\<span class="name">begin</span><span class="string">{cases}</span></span></span><br><span class="line">-x,<span class="tag">\<span class="name">quad</span></span> x<span class="tag">\<span class="name">leq</span></span> 0 <span class="tag">\<span class="name">\</span></span></span><br><span class="line">x,<span class="tag">\<span class="name">quad</span></span> x>0</span><br><span class="line"><span class="tag">\<span class="name">end</span><span class="string">{cases}</span></span> <span class="tag">\<span class="name">]</span></span></span><br><span class="line"></span><br><span class="line"><span class="tag">\<span class="name">end</span><span class="string">{document}</span></span></span><br></pre></td></tr></table></figure>
</details>
<p>渲染的pdf:</p>
<div class="pdfobject-container" data-target="math_example.pdf" data-height="800px"></div>
<h1 id="latex数学公式编写辅助工具">LaTeX数学公式编写辅助工具</h1>
<p>对LaTeX的数学公式语法还不熟悉,可以使用下列工具辅助:</p>
<ul>
<li><span class="exturl" data-url="aHR0cHM6Ly93d3cubGF0ZXhsaXZlLmNvbS8=">LaTeX公式编辑器<i class="fa fa-external-link-alt"></i></span>
<ul>
<li>强烈推荐,妈咪说出品,详见<span class="exturl" data-url="aHR0cHM6Ly93d3cuYmlsaWJpbGkuY29tL3ZpZGVvL0JWMTRnNHkxcTdwYg==">介绍视频<i class="fa fa-external-link-alt"></i></span></li>
<li><span class="exturl" data-url="aHR0cHM6Ly93d3cubGF0ZXhsaXZlLmNvbS9oZWxw">帮助文档<i class="fa fa-external-link-alt"></i></span>:文档很全面,有详细的 数学符号<=>LaTeX标记 对照表,可以当作cheat sheet查阅</li>
</ul></li>
<li><span class="exturl" data-url="aHR0cHM6Ly9tYXRocGl4LmNvbS8=">Mathpix<i class="fa fa-external-link-alt"></i></span>:截图 => OCR => LaTex语法的数学公式</li>
<li><span class="exturl" data-url="aHR0cDovL2RldGV4aWZ5LmtpcmVsYWJzLm9yZy9jbGFzc2lmeS5odG1s">Detexify<i class="fa fa-external-link-alt"></i></span>:手写数学符号 => 识别 => LaTex语法的数学符号</li>
<li><span class="exturl" data-url="aHR0cHM6Ly93ZWJkZW1vLm15c2NyaXB0LmNvbS92aWV3cy9tYXRoL2luZGV4Lmh0bWw=">Myscript.Math<i class="fa fa-external-link-alt"></i></span>:手写数学公式 => 识别 => LaTex语法的数学公式/绘图</li>
</ul>
<h1 id="参考链接">参考链接</h1>
<ul>
<li><span class="exturl" data-url="aHR0cHM6Ly9saWFtLnBhZ2UvMjAxNC8wOS8wOC9sYXRleC1pbnRyb2R1Y3Rpb24v">一份其实很短的 LaTeX 入门文档<i class="fa fa-external-link-alt"></i></span></li>
<li><span class="exturl" data-url="aHR0cDovL21pcnJvcnMuaWJpYmxpby5vcmcvQ1RBTi9sYW5ndWFnZS9jaGluZXNlL2N0ZXgvY3RleC5wZGY=">CTEX 宏集手册<i class="fa fa-external-link-alt"></i></span></li>
<li><span class="exturl" data-url="aHR0cHM6Ly90aGVtZS1uZXh0LmpzLm9yZy9kb2NzL3RhZy1wbHVnaW5zL3BkZi5odG1s">初心者がBasicTexをmacにインストールする(2019年)<i class="fa fa-external-link-alt"></i></span></li>
<li><span class="exturl" data-url="aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1pYWWhhcHBpbmVzcy9hcnRpY2xlL2RldGFpbHMvOTg3Mzc5NjQ=">(VSCode) LaTeX 所有文件报 no nobo 错误<i class="fa fa-external-link-alt"></i></span></li>
</ul>
]]></content>
<categories>
<category>Tech</category>
</categories>
<tags>
<tag>Tech</tag>
<tag>LaTeX</tag>
<tag>Mac OS</tag>
<tag>Math</tag>
<tag>TeX</tag>
<tag>Visual Studio Code</tag>
</tags>
</entry>
<entry>
<title>「床长人工智能教程」学习笔记 1.1~1.2</title>
<url>/tech/captainbed-s-ai-lessons-learning-notes-1/</url>
<content><![CDATA[<div id="hexo-blog-encrypt" data-wpm="Oh, this is an invalid password. Check and try again, please." data-whm="Oh, these decrypted content cannot be verified, but you can still have a look.">
<div class="hbe-input-container">
<input type="password" id="hbePass" placeholder="" />
<label for="hbePass">由于床长的教程是收费课程,因此本笔记不公开,仅供自己复习查阅用。</label>
<div class="bottom-line"></div>
</div>
<script id="hbeData" type="hbeData" data-hmacdigest="a180c968f0794af80c949f54646fdfde6dc342f9b1db7a241016c4a366c5223d">30e1804eb8fdf5369ca7b036a4d003966067233618b263e2d81f65a7fd5d65a48f2f6169aa25d8c9dd64a7d14ba4813c883bbacde6e327ef4f175a9a237a5422380b3d924de7704c3e03eacd26193ffb794835e4d890f5370cb872356d04cd7ec2a2e8203b51618d8259e47d322e64aaa1836c4dc81c55d3afc8d4d64b95641e80a90c64f2fa08222635a502b2a84be13cc166f7c3c4c48f8642a02061644ce9f475eaaf2d8c87c2de37d1a6d3d8a0103c3446f13b1789e097d5412ddfa1231977556c6af60ac652e7281aa142eed2489e4b02b6eea4a9be1a1f6308a7beab38a1c15a3b77437c693504bb9da9e61d112492bea79274ce61428294a4ddd660d766ad132a609a9411e52f26b134443805cbf44b5e7d9b4df01859aff05a9568710a0532df77ea60ebbeef154d015bca9b4e6c1e065e828723dbec3cfe9ab50c1fcd1f2c678f6007d10b44ec870fa186f9e0ea7c9368c4aea676c88aa7f6e31257fd2717932df2eb5690a48cf47f360ac762ce467a491a7783bfb6a1b8b8042b8d46ffd8a7e0ee656a0e4b5c7ae56b282a4659f4aabf7ff7a344b6db9fdd68391a81ee3d737157efc27f68ccca88057e3fb240f6ad9c53b0d28d9970c4344918f0bb9a1451a0d49907336b8876873b83bee22b6560cb631ee61e0328e249dd682e6949f6512d8cb7eaf259961570ac2ecdd6fd98ab036085d897ae2b21b9627f6bfd502dc7eb211526dbd9edc53db4521a9d42af02145b3239e5f52d189a7b23059281ac34d1e572fbe1f01dad049026e6b99963791b2abc8c0086e74b6ef2fff94b18bfc37e483efc12492b5826ebd70f2123854c393c7e9cc2be5774c459a3fb10744552e385db489945b166a8fd1856be7975d94fdc51812a4155f99aa8e02be0443780e451be40d40df828bae724b563749fe881b413c4dfd4f5a6755590bb75034ed9f21e6a51d52476c8f5377136035cd3c23d5f955793ab402c6d66c0b5ba6f68b7592744ea9c925644fcc7bd465361a9a6e1e265daaea5e3b6d37275f68cdd57a9812070ed27a68a8919a998e2edaefeac04c2ada71d0f6c5f534722357d6005ef730d31da79404c46db38f42d3a2701a356a179b7017a27be0c95267018bafc717bd5da2397e879737bb9235595e060482753d5e45a0b9c88bf441d9834161e51a31b3eb1c9651dd6da9d5b23281013f3bb1f18c437cc47f369bb7e351afaa99c83b43300005134d7e090ceff147644cfd7ce714e302f8279f7012137418efd814a080cfa24a8962c6ec7f4c25aada2648df6bfda256e02faecae23defd30a00d3acf197394098e1d7bf63f08b9967afe64ef3f2fea5905fbf30b418424d2bbabcc977ab5996e9710d8a5e48e517ea180d384c52a4b525fdc3e0f8166c3a10d9bf02aa8680f088b18a73a757802213f3404a20457c7f4a432095bcebc7434641171665d7ab990d027fc233d7e6979adb5220330ad1bf25cde64f8ad451bda563b56dfaa23f2402243fe02452da58a88cf6bc75d2712e552ce96e3667be33dbcd45ede40de43b6c6d8d18dd0a731ad516de9e109c54134cad255d7063b5881b63a17f02edf19ac6311c29f2c0e32f95a627306c000b4507a825a66cf2967397040517628748a19c4ee3d41afbc1282fedec226f301f66256ee03d11fdaea42b53a14dcd64692219ad70782e2c048cce87c8ef741c2ead6109c1a6f546315cb8d0d8565f2dcc822b71736bb952bb4137e9df19f7199096535de7a3e5e5e8246854c1e3b08e61d002c0aa305e5ce7c44c53ef4d91bc0a61d3e4ed7504d139c17f3836824d499fae74246a9986290a9393f27e325e13516dc03f0630805c8bb3f1bb73eeaba35658feebcf76cb97353119668009522e7a18457c80ac0e257bdeea5a9c24ec68f2d27c4d0d305ea7bad96cdad83d5da036f27c30fde05a242b406ca5093815981a0c1f12f4ea7c364ee287305e2463374e2b907db0c2102937a1200ea382344beb058a58c027bea101efbcf2d7a1289d6db0454ca5b0e51662107da34ac3792d9f699cc54a8d7c1d013d092b16ba5b9b1f0452baf71b446c71e6e6c26fa03ada4dbb27c3f65e2621619fd04a0b4a460927e6e4daf566dbe3bd7bf934c2ed7c74fc4854602043363834dcdebeea9f4b05aabd1a28b0426674785d8feea2ec8129c53e242a141d807a9e96203cd7dab5e2f669be089747ed3768845a493c5fee498069e9a88822f5ad64560449171e9251a3a73f54dba96b295a81d3d7fe440bb5adb0a7a37998f37cad16f295e2c34fb9f68112cce5a1c5aea73d85e15620b8139553eb69056e37a0809a90c3b73b1df2bf7a6d653b2ab97e9e0eb868a90bb4e5119e02cd8407e5d2e3bf8ca3ab79c67cab9c7e90989353cb8896ced9c921366103f34109a98198cfe49705f2c82bfd67615ec5399d3ecb41e8a57cb658432040568c5acdc49b46a0585b20458540ea08c4e3e331e540d6e0b7539dea81320c4b4044ef1a871282142db3d87d6abd4b239a470e284b5bd1cc15b33bedd287afaafb4bb8243b1f7031c4f72d7c1f8557cf68ef5f4ee45e2bebbc933873556379832319a4d2eba44c58c7ef1742610edcaf64c082df6feed474d481d14948193ade707502496ee6c54a98de5414357abbab320a03cd370cd0a81af2675d4e91fa0fe89c023c14a91e1021320a6837677ff53e9050bb9a779474fa7c8dfd42d83854b2d13ebc0302928d4263fdb669f758729340eb27c84dcc0897830fb536b991aa2913059260ad602aba3a6642972edd7fd1399cc300838deb5e03658d37952fe060d52e518137614d34aa6f0026eb4c1ff3e9939cb55a436687163ad0d877a55b7bc6572b43d8b51668012c69b565ee12adc1053abdf38ea78cf6c2518427917e0f430f799a42af38f7749229f3e17bbe18dad136a23adec750045da8ebcdf1ff024f250876421b1d930b2c221e553e7a232b251f3d120a2ee5c89340da9421941bd8fdf4ee253fd5029d4770ba1de2dfd8a7e4b18d7d74c0e3eba9b0d31504d27f8d2a0d5be778a7cf886e7f61cae75d5c61fbea911dfac72e6f97e6470011eaca2d5f2aa6a91ac10e382cbff85ca58ad27b1cd317d0bc931ac8c0466505bb1a90bd6937ae8c08f94e3a454ad2510f675d3c251df438b118c7ee3af603458e7da637b11e02525ef630068ae5f2eff0e8b9049d46fd90b6b779b8109af424b29a93531970901ecdc2a955f3774b5e0c037e4f70c89f0010fd5a0e32e012c071dbb29c35b136b1dba65fb3b4209ec0c704f2589245ca8324752e0899701f65ae3cb1c30652b453e2410b2642eda808eeeef3efa7afb983ac945c0e2d27d2c4d09686a4b00b90521d58ee31a326872103eb238c3ba9ad9cb658be4f99111c64b54f9177dff9284af81d63380b69abcad72aa7be1f9205b17ba0f32befb5b729a88573592c04e3f2ea60eb41b3c7aa52ac21cbd763acba7b17ce0197afbc5d2039fd9aaaf1884fc55e29591bfa6b677870ed767835b0d274cef4893f3b6cf39ef5ac06201ca57f820cd435b2c228c4657db5163a0e4ea4e5300039ba02299b731c053bdcfcdaf78f87718b8e663ef90dc0b8a1d6f1781d0fbf4541392d5fcdacaf835053e166158cbb40febd2e6ba174084934dadd6b4fc08e9d73d30ec1e13b0bfc4c064eb21dd6f191fa434974b933227793fb91d9f48d06d64bee83452c4e9d5ebe0d4ee5468f47ba48fdca62ced0ffd39454d2d1d6e2bba3ae218ba1a8a59e4042bbae12931e4b210d786e85296880d0a3d928d31a9256bee5173687971a1a3c1f0f3137628ecd4ddb5e3b0241747d95f427333185358e0beb73966ef289a0c31c917b91dfe73ceb49caf9d08baefb9f6293137a26a08534a2b39089fcf7f03bde8bfd19830050f38293fe58bbbcce6c3d239935c0d20e369121116d977c8db8e0cf790f03e2ad4313be383e7e1f085f91d361438acac9e5169506f9bb016d02f45926e684afc777d4dd3746ff4d2636e6e6350c2186a069a16003dee74d4ff267bdee18961652f958887d4fad9978712f02324f3a193de3478c21b5414288db7964ea2218d85f9a5fbc06e97c5ee19b7e6f42264ac65d5d9cacd59812ad7de27688cbf59e0d16058c8486f9c9cf653b64af3b26e7079dcc80a0e74031d14d055476494b96433a6604ae36bfb5828424e8c07af2f15b5fe6928ced9df80ef54034ed3f7cbec1ff4bbcb203588852015eda9537cee27a5378539c34ef859606cfa073103f9d877275822366c57449c84206076b162db01840dbb8fd8384a2225cfdda436ed2bbc6a4519d02b77458718fdda5cfa3770c22552ed91320c513203db52b35a5bc651325a8d7b96c44c38fe5db8185076413b537dc21f9c59d1f2888883f8b568f4cc2cb5cc8404e8fa48750c2abc2abfcf2c2f4f2b390e883a6eef10395400375e5e799bd1205d22a24b6b9e354898b2463ed4bf25ec42ea47a27997fc016799e9a783d95b907da69495dee5aadf50d330e146f250028bd6a0de3a4acbe7a6e41725e873e7ce0708b53d3e55b4c04c6e330d0c61b8e3a49fad18a42614714beb82b732c7761fd21ec1ca7fb03e1035e3d7f6157341f8c03fd39fbf631c191786fd0a337751f3685261bc3b232ba5ad7b1b8ad2387144328af0b81bc50cb47aa065a4286825ec60bd45188ad5e1ec1c8dbc5e55763d0ac286cb9c6d43997f5f78b14817a1b268f0a17cec521cba8c89baf37e100d17c858439db86e7d0f9e09da5582093343682ef6846c663509f56b144d5004ada3602e2ca607ef9c4f2b9cece55addfd09e41ed1a1bd95eee9751388785e4c6f65516c32f51f6508b97278bf8cb483a063604a7bce4733cbcacf8821adcab6f0d6ad30c064e7086838b378f92fa191c95d80399f4e728c4e06fce441a012f9513710fe605d410704ad8dfff57951206e16c6d5097daf79da9e27938a9263189828f4242d3f677dd86b53be33dd287359c6a6d62be7a5292ae63721ccd4c759fddbb2d594d409d256ad5914698bfcfbf4329b15e434716695579335fcb9b7cea6100c472eb420d8e088c1bb73a6339dacbf46a7748f3d031cf10bcdd231ba35ef5a86fcd81fb7e709bda262c4c932c4e1dec4d107ca2822d6440a9f5367d64e26ec31da90ee66b3667db8fa3f1888d13c944607f107b77e3105b4af71151e792e98f8f04a6a432277b70f787f9b25dc9c1f71a09f373b1770d192c935daab613c7d44caed2280cee226f0b2c916c43cd4b8745be5e9b794b631d0d6ed104b76eaa35d1e0b26539b4e3ed4d5c7364c456c087a534234ade014293ae40a5bd0de024aed51f0013b37f8e05cbd02afde3fb3ae717ded5b09b96c639c320e101d45fa9ee2d18bfb4b234b94355f268d603b1b15e714dbcf3755c4f27cab889ff3d889d2386b8830776c294df84c31b02d90e32ef966d825f4a4c11a97d8d2d3fc298c5baa52473a1b2921ef706e8865dc04c477b78fb92d20a0e096dfd90b7cf90acdad1e84f0475917167d28ad48dfc7de81fa40a87e94b6d61807a428dcfb962dace02dcd7993a47a9359b0b4db9cb35254189aa24bd4990db9ea3dd7fb66acda6bbc1bb2d716026fffcf04a752566ade0ab9ea9dd32235ead2d8ab70b65072544ccf17d8579130966ba2cd50e7d64fd63899135a0f21acbc778e1d6ff26ffa84105213a388c43f1b5b0cc76e8cff365e3b418fd5c7378aca2afb13a6130d3dba2661079ae9a273e6517fb28aee5fd4541c531487ccb0d1aa0a55ac36d99a32d89d75cf289aeb88b86a24338cc042ecb98b7c3705854f3b67cfa754ac2ff8baa9e061e7b5fc5f00eac04d43cc2825cdf6f0e600c0de92e0613b68b7b79e1835d255815dc46d2dc3301aa326085aa25b18baa3590e2870c53757232de62a4d4149a7fc82c5b9d1d1861315d7ac6ebdb0ce75287ad8443bebb8d719215cd28edf538f4193a8f521cc6175e5712eb2be59a0b9bc7bb683c02d5d0afe89eac60b0e056fa976716c3afb2c844e02d6db3edd1bdb593b907d8f32a8e971d10d5a9146a187b6d51e14b727a16dcfbafdfc0813cbd5e6c26bfffcfb073112b0fa0f38c5c50f46121a3dc53faa49a479201ec1b87b79ee8ac34a0e7c6298e920d80ae6da0eabffd5ce92ddfc03476869b718f3b1ca9593b62c7f415b7232afca2b84026ae414c4f8bb0f55a4404ddc4d45b4bcc6ca0951007529d58122d41c53ef87ec8a77247c6aa9d8154caef756eeea17fc10dcddd26df2fe6c36000326770952cf131e472b940719b85c927c61a7f6cc87a0fd58657a57c821eca310aab382631dab12cae0dd57bc7c43d719822a53af9d41fe9bee487a85fb65f0986ec4b918e996e3b433cff39b988f3a7a3502a0d6ace97a758b22ebaebd0a9f235382cde31eb3833bd44832ed789a4f0d2954cae6437eba581869cdb2c11b272ddc8a65226474e531a8c0df82d12993abd4ceae5522b2d20f93c795db84ce20b1d905ae500123fb2d8e9fa3e5819a70accdb2fdd4dc11b88da27ad66cd907d1271c18c7a98c4151cba715c4f7af9416eb689a8a024c7a747dbddbccdbb81621baa32e432a144983e9ae1a9c7b136fbc9cf0d373f20e2bb37218036bd2144943262c9c76262fac21ae942a96674d5413ea29ce35fd2d38d8314ce9af42fcdf6b249e535289a0d24f31727617fd298e1591600b2f9ab8381729bbe11af4bb20b74091b064d9e7f71d179c93a514fc6eed3b6715f45bc5d0e2f8b36f653e3b59689149bf4907a36e67ac7ec12d5416e577c064e741d5a92468ca9600939a113589e74886973bd560b6b3a7a3f9f97c65f9955fed18ac5d731aff602aaea5eba19f61cff596b2ced3f9b03cddc419ff4dd8fb06b32ccc150f6149f5005d6ad0036995a127aee08b9a1b44c998b6778540dd1127ffeadcea346748f38e71d515ab1a404aca3551b10b345fb389fbcd47a8010aca7d5da88447c5dc33167ba51a28237059694c6fa5d8ffafdbb91462c5f74e9edba4f6a2cda616fac2ec5b90ea29905dbe7f0c1f1320616a09289236c1e44b831f12778b86981b56d0e27528b9be3e56438e47ef0de202ea16e2c755a563941e2be8bd79a277449bb67966be1002f8186fface8d4b4fbfbe0040b106057928c2a34490000c585f2e48ddf07532c09e8d848f4435ab771a0b594854cd9acd220ae0cadaf05c245587c41526a4236450664ac99402bf5e953ec6bc0c56e62980021e714e25487b0e6658a6528a345d8a4e325d21e7aae291083e70aaadea9dd7ef71dbe2a7c3b088c6a60e78f30b343f38b54273da93ceb3ce126a18242ed5513aff63830b5f551b679d20062b82f63121d422338df930b7da8f3f9025e558aa70c39890ac5c31ac648fbbf1fba35f4a604408f83a66f992530c7f60a1fe79a205b1423d6db7231dae500d53117c3ce7a8ad9f466eba057bd8a9404e4b6fa24970faaef00fc540745fdb6358052ebaf0f59ebee009c29ab8173c46b2b68334de5fc4aeef18175c80b5a7e63c5069ff2db5c81ac1636ffd2f60ae27b5075f6a96c96c0033aeac40b53a6c9d2fcb1776bbc5e12ea0ddcba5bc6b72aa69f87bc8fe912c4ee3ad99d455f3c793b6cc0fd8aef59569c4349a1dfbc3dd293a1d6ebd2e27a079468cdc8f31870a9b5ac7dba9e6b50099ccc0fafb18379d2871b1aa36a6f11eca75309d3612d6395b3518c10820dd332e6b9443d0e39d4a85dffce61fd754ffb61846eb9720b69178166626cd1bdb029dc41ce93e153ab813e8aba20cc1e106f7654ba2b3457d3508773cace2b89079d6eeb72a6c54fe9b1bb1848aee44b18369ea971e6e6cc74647342d01ac13cfb4afe4b52c3fef4e749ae847cd5c211e8ccf12aed0023d07303ae36904b63f5b6fc02c4d097375aaa2e16505f2e197368f4dd69bdc0a147068a51c4d68bb9f89f947b61a28fc67724c43968b40be3bf4042287ddb5a716b5d21402b469c7531df894adbb26cca02068bacb53b475f8731d3b3833a016202553d6c357ec111e5f7256c6f620c48824792165bb9fd93b7bcdf73b1f1f286a2591f72216e225b36cea900398f7190baca03679638740c01404c5d0cedb53c4a32bacceaffb8e4e45be5aa322fc602b03ca437853c0b6572170bcb2d4563f24c5154686ac74d136ee3942e24da32099a3a71b10d66f3787b96010ebe003e284577673ff508a775a140d0643188e9d9bf431eadfb493b601b51a4b186c2ded6d3b8ffb0ebbaf28c120a69c43af15be5d7cfefdfa50fff6835c025fb8a17df274ac93e1141295654c31e834dd4706262b0552a84482ddbd04f15c04cf4753f5560977bd87c95ded051db483a6aa1af8af236932730a1ce2515b9166ca92d21cab7b2aede92ac6512bf6c9b638f2b73ff81ddabcc5f512246d4768df58a5608fa1078cb46959e2d53b996d51d31d59ab73d3e81447ae6213f0c671a22131388233912096330a9ac3305d1830de1a9f8994d1fcf4833fcc3a1c761808966e1b7f65b2ae7abb6451c96d797d8e9f54c570fedbbeed6a5730b1abd998c877479ea9baec518504d2971c0a41d2c8ea554e0b67e273ae28bc3d1a41f9d3cd700a20a231cedf7cbec3671a4a4fd83d349dd80d40330529659089a1f613ef438d2ce5a39e92aebe7e9efe602ee542856501e90088852bc0cc0ead6bebf9c464992bf10e08f3d21fa3c919ade2f180ac3ec18a502c6d34300325a50c2df1b34499f1282df26b3e09287451886ab71cbcd325784c10c78f19ecf9fb2349a00b0356353e4556bc60b4eec34eb401dcff4390bebb687bd74f4a7998ae10452f2d38a0c3ec8cfe71446c157e2668f604af8fc13878d7a123883bb9f2d2fc55c365bd6af0321362bb9201ce8fb9e5b8041731355faba51438f333c8f4593f9d31452c361f137fd0801b13b5102506d2c7762353a4ee9d211208fea8fad9a9c8e176e9157d8f7b04cff97319ad7f903f298f498c50b0951195cd2fca90db146c3b945cb7900fb45d7639f493a9f4cc432f43e27415796338bbbfe57cbbe0943a12e71da4112f450a390d5d94d1f3ba2dd5c1f1c8796514c2bfdf32c31af497ae93b6445a04dd41d30f5f369f4ae9521f2ae9e47eb3664b6db2f00a98edb1734302cade283ab9ced2abbb48f05f85c4fd8284cd07973f53a09bd2c6a9706e9e28ca8df126d367b4a3fe786b8993965f1cbeff63222e9edf67968ae0c87fa809834e307dbaf5ff422956d21ecfd737174e1a2e5577a8eb2cc0ec031996100997223cdd95c69be28aa847402d2d7c0a234231fe4f681e9f9ebc60c80d5fc0b024201b0b6493115960a53d5a664b684e7a77ad1f5605702a1ccdb73a5fce246a5715a8c220e1a0eb292a41d9dc6e3fdbcb1fb93077d1e5e62e881e6558f36545314b14dfcef89c9120516d201800bb64cc3edf89aae7222e2abc900337f2136eae11a302567bd2958fcbef2bc106c154c29d4b4e9c412af116b1963a80ee998e4eb2b9355e80a8c94a20ee6ac0c6d3f79edb8a2de2e122a3c205bcde330af6e8f23616c18fd64d5dc5de35ce427fedbd6d31bd9e026e3537538d4ba774e8a9379dd34a56eb6bd04548d51fd8a0623e7ccc1c54fc458dec27479cf722a9ead2bf257f935c4bbcdef71e50bdd27e045e31370c4f2d2998a8f0bd95c3a8a0e13cb766809c2b2e00330600e2bf71fff1cb372cf4f894c1718c58cd51690af014c28b7c2fefb50e230c00e168379af88406e429f534bf0ee4bbf6d34c9991bfe53963e6b98d418f75fba61817e6d7fb35f420226dce3aed818892cb4747cbf58628ef62b7ddd5b2977a76c900eff585d0650b33c7fb999991c6f8c95e49b9acfb14b4467ef33020c1886f634e55cc2f5d42be4db77045378be97fa9033d215271fcb47a32adad5fe90787109d526f26fda932df67ade322c9ccefc50be88d0fc77ea295db4cacc9c1b2641784eb85d16774f83d478000f6532ea3b0a4c7787c745896e8f7d41d78e10820b6eeaa992b524bf178b6fed6fc3975890753188c388a8f0a328bcf9806f2efd6898b217c90c527e183de6311417a365ad3946ea96575dbe52c12d2ba25fea0087ff6f1bb01eb991278402735aa37175bcb80560518348d970604fb32c3afeadb35a7f29486ebf7067f050f51340d62a81742d1d1ad971475f55c475ed7f4501f87722c1b9f0a4a86d43e29c422180bb5fe639ddd0af69d8df96c6abcaab5e2c65bedcd565a92beaf4e7fcd723138eddaefd482cd6f193dec97f819b6592107c1518bc9dbf81b0b4ff02b46fc5b68672535c1d4965f393030bd2c9b6eeb58d5cffa78b06c5082a9ffe50b64c9879f7e1c4e9457c55231d45a6f11963266fd3392d7540c320a77f8e059bf2e12e02916e14fcbc3886a917d0f321ce489fbc7ff85318eda90e64ba2aadd1ce42bd9e7c31dbf6a21223e547ea719d5630cb29ac671ea1841fe5f90298067895a121ba373d90b6a4819c97e2868119b12b323a8f6f365eed71623cea49ef9acfe443bcd5f2458d10a433414df572a519f868b5d96c76e8fbb357f2508bf1bb138e073586c6514d45ba681ab481f5a1828c53dbccfd1c96b8610f6f49eae993cd5ee013479e47a90594f78bf1a6f6ffd45be3f22a22a51918784b1e374397d5b5d884f1f09cbf701dd767f7c2d3caf10986d71f4a1f84208d10ee93018f18ae97ed01e6ca5475c2ffe8ff4288404fa4a1f92ac846d04024c839c316b568bce54295a0771d7c4386fde763a3f26a2d6ee52658fe38ef78e6206978a4d60f003849716588d2a121368c2ebe4164d025dd8d5961a3d2a078c9af42fcc8472d2794f63ca67ede6f04e58f678cfe9f6b15bb1dd7b52589c732d3800f797574d06a9ba2049cb99b23b08c40d1fa92c048bb6573d28ce6db712c7d9f997600490b8b9c5691cabdd2094484846ddc6d5df388faf807c9861683a459cdb6496bc6986a1d96f12854af5b83388a113f6b22fc739c57cc8569ce3553a5f033e55f9b27a27cc78e76790ba6b027b27cd56481c53f7cf964f47d4b34018b142b874338281fb4105b292b2168fdbc64621c26f1e20ab6430cd594f2c04327c8fc2c9764edd21421937f206b18deed0bd87a9bdc2f864d7a95b248cdb9ccc28771847188ad677d1be811ffcb26fc618c4b5d7eb5b503acf13f26227700fa0f5a50c85ffc2e52dad10adac0a33e8fbfd2f11d97ccab10792c60e8c8d895dd63fcc0e039de14a108f3e50e90d5499917ae190412113261c720c8b98b85c9d60280bc44c50d04f6c1ab3215b79a719fc48e7ee171fa93be1f877d2d9e563cc3aea70cc8ee6496b7e3755321e6b9ee3376bdd429174e6b06f855d0b7cd0925a702753ac4053f9a6b5ad51239f0e40f67e796529be53d10a4556319d8d772999a8642db47e69547583ed6818ee1239745549d6baf53bf64e866cdf6b7c764924ba5028b33ceb26abdb73a3096dba4f925cc5d59e92af8e2d8c598f0962eb153bb01753f22c6206059193cd5a1163e4a2e2d6364925a94edf5993a54eb95020771903f61c4a62bcca1e0d7f4d1c5acc0422a56cb34512f5833ad91187fc71f962ae1cbc3c9c758ec4a24e46f0968b2b6953f6c4658ab6d586a45f1ffa40c27eb54fe2e821b61cfed74ee0a0339b2d52bbd2fb2a1bb6abce15cecd4fd38c8e918e716d9bf58b8e9bd08a9903ae974a92558db2c5225d480590e595bdccd614f68474e62981de5f6e51c9a61480e7e3426da384bf6cb6b5d6a2d4ad0ddcb99f1f7af2c731064a2d2a2b40841ebcdf68f9ddb070c710e588ad7b48b085b960fff42bc1714ac29d95b96332845e64b7fdc882a3cd0f2b58ca511def1e0468cd41c67a5435a23d409222322cb7cb94a3a089bb582d30cf549d103453042dade0be65369c6ac45a608062bb428aec0c505dd537a4aab1b3e7694381cc7eddb028bb9c6d24fa1ce5701a3ea106654ac5c4346f1e3d5ee79ea3f972b27c7e115594d0414d6fb2997e41603dc1f46213ea0c5f4fd8686d41fa285ae98faaac9b22b3b91c489509a2c3a9cbc0a93da7924d54f8760db5267482d1aee28ea35dd4af4098ee76b77d87dbc880fb2e657d460a53bd1e473566605ff9b687040bfbd1435abd7a788653dd1a8543af6c365a2e23dbf7d5f07d8d44e88b2cc393fc2f730fe081c72728e438be7a47ae809a96758151ff643e850c336c2c005308c19ec5909982be857811d0856ff8452cf0117d7e29104e2aedabb1583ae273de5f20a42b4d9b4fce5519c70529c05e845303bfc17f7db10436a8363cd0c093896f290f57b24d47c2055a71dc8d67f7581d0d54298598d1259688b994899be3ca24197d0623f60af1278c0c35b64befed49f69fd4e395adc28278c953f58b8a5e6beced49bff4d404dfaacc12e965ffb6573f7862b991d067601a82e3b6034fb56e4370d0f95fccd9fb6b982c6f6f7e295b4aadeb74bd7ca23072dab038efd19f39c753759117c8636978b61bc04f4cef0f7df90e9bde9d027ea2c37d315ff883255e9b73c4b33be3e84dcccaca862d743a5af242ab43a42ac52687fba14b309d644b6de4e965e92dc54ee82d4f92ce1d2ac1ffe920412fb44ac0b3c5bfdf2223d81f9027efdb9d6f1789a4bd076b0d2ca996f87999cf76a6cf79e3687106fb4d0ca7d17eba5a6f83ec183a406e6c61aa8cb8a3e9f4adc0b9eb50214e72bda6934e2fd4b419759aa871d1a617215b75bdaa44610ba33fae67addc851938e5dff54242a6d91a0fa850bc766a5fd9aa23316433a52c404f6d18d94431a91cbee124541350a58cea3da8d25af14f4fb20b5403f6584504c5bb0e9c9342ecee8e19ea5defcf60fd6246dbb2b117f1d85d57aefdccfcf5652ece45cbe8b67b1c87956845dadbd6ba76c9fe7d4044af465fda0684737fd847ad886ec27f770aa342937df04d06b65246e7d98844c489c19d8fe7050b47c98dbd5e5d7d31c8c67d1695dc46d21af4be4973a1568f184f2eb6879ebd491f8fdfceacdba9b65ee3c43f999fd27f3b9a82095f1c3591bb2094785cb51d9a65fc204fe4532506977b7ebdb092dc64c4d1625bf4aa91bace291a0e1477fc035ad4df00f189a057911cd4872b70b3544688da86e4ae06812b3cfe133e2fe67238fb6dafbb7776a280c12122a59e83ee6496f0574a43255c80172f0c0dd417bcd9a16683ced7aec657474f7d741eed3ba499e6211fae8511075bf57a5efe48b455526b85537c1cf3c806e2fa4077be2c6163a22a5d8c5c8febe0301de3cdfb62671e3d2a7da78b92402d57dedc43704043438ed61de9a8bbdb6452e874f750159c7f4c2060093a3dabcd20868ef2d93a5b27002415f21e7a88dfb112fdcb2ce69abf0508044acba3c2c7b139f27e22d510fb33c924809797f0bbb81ed7f76b97b015d775d4926b636e2d94c8d6c971fc1abb356c1f0605ec3690c7cf8c8a50f384b78a2019563f01dfbce1462d69b7c9a720705ebeb77db9308b594f70344c610f5ad03bafa6396495f7458983a839584dc544538a1741ad9186f510c7d1c9e082e7d3ed61882fdffa35a4ff41f7108bb6554bd54455f01b016e89a17e82043844bee2f6c915523dd90b019982dd6101d4adf1fa3b54bf13f587a3e0b6d222b10afaa976982fe0cd5de203637a4f7b6c9f64592daaf586ec0eb83b35cbf156882c1d37740f4bd0798641db6d48768cd1bba811f6c63fb8c25c97939030268b82caf661ea1af562fdbfed9ea020ffd629034bef6950c4df24901e4690dcbb7c4bbfcf2def655a845101d9554c194321d3ab6903d5e007d9e7c86c36f0312f686d58360752fd5035b7ce1243ca791e70d1f4bca89efe854b59271fe2581c003197b11f062766975dcfbb544f3db74f7b0fb97bc168d28ee976aee2511303f9ad07cd3aab90aae7b233c5b8c6b9bbf0b34443681701e88346542caae7717259fd51d562f36a8ce504b3b59a08e6f7ed96d5b07ff2380f471b37259eac534e8123b954d1b0e14286e1571addb26fef1835e6f81ed7a635c280999ce7fdf70d7066d953fb674e9ad078f59fe9ad6e549513681a4506f45527b9b3dd5d15aff1c0970f9bad750e00a6f20fd599602c69f635a88a2d1e914e9fbcc23dce00cf5ef15a3ddd48a3261278a6040928b70db75b4d711468297ff428fcdbff59204eeb8e0c8d21a6e5dafe99510e089ec58f41dcacebdb65fc31dd831c74db12c1c7adaae71194fef2bbfa08a2ac4b0e995543f333c2631469a004e0e0f394db2c048a519f6674109028f67952c80fe3f7477d0235f8760b45d246f95b02edf89c032ae5865d1ccd86af95aa50291784da4622dae0e6f42b7b2bf0b22a7627b1dc6eeef05bb8dd588595773d14cfc36e8f7ca1b3df124d3ec6054e29d95c2ced8aeaa7c3aa1b891265d4c5de7d7cd3b89ad5291a53d245e337ad10e10847ce1fc1c5a38c3baf31f0ffee00b5783bedbfd00d7c369b46429163890930515be088ea9dc8aac7ed1f2a42c6f9b0516413f1f26899b81c6a3396d4b89fc862d8435e8278d2f35a57c33ac70ddea73da0055d0c27d4d5b897afafbdc9bc13406becb86af99a92b1d136669944bdb6400b4c9cb05da8a91f213cfc4f016b2fa8db338cb161cfc510ddefc8260fac685429bf6eb5a73990167f2b748c65eefd3132091516f4c0dcd455f84c1866fdd68df12ceabdaed57225ca4b9bf4bacdce1fa77051b0fdca82b1bb842f7ff2bc1ed95784f1a44efd4df54750f3eb097e66156d32528b18d8c24d0f1489fa99e028992468b080d9bdb2e429a48095db7689ce856346b340973c7a3d6a4cf23848409af07c706f5da47194fe74dd85019041aa17abe5bd83dbb00e17b6c5ab0ba02294947c43ef415788a04cf600de489fc040591d00f43c588ed88bbde6356881a881f384d03b5d71b86987d6df7dd8f999438a12708d393f2e1244593dd5b3346ec827cef6ef1d2cee99cd0e3a896cda7f241a257989f9d686a62b76841e15b1bdcfda89b5e639b9a11c0205b9990935f382dac038f701221d5f7c26342b8e1d752bbec6ed023c03065eeb209443454001019f3d2cb23ee648e7b721394dc423fd722180b8c6512054076c55adf82b900b7a663a3ff8fc717070d2c48eefe6199c0506bb761c4af99ab8b5b3dc4d598ca02af73dc36e9b59d5bd2e221015aaefeb9eeeb3d6328f4a9c31a0d7fd27376e33a7542a0e57bb90b42a45770f7c34644d5f4d4e793ea02b77e5dc37eb167dbcd94ecd53655b201b3db3885125ba491c301f037707b310a9f8d770beabe87dff6b8b5093db318b2e88b0f17ca21df4934f0f7389d7cbd8d54bce2397240ca54322ce05af37c38f007f3c8d2a1492f2e231550a90141aab398882ed5c63fdece395b36c557edb77a1da307bf7e8251d2844895cd9decd9a1bc1e9694e928e1c97b5c58e682f0d476bd08a42c8dedbc5d7222b982e83e6060315ef131680c7043e40283c532e1de2155365cd8683c90eebed279a9d5b8ba02d4ae103efc3bd10b6ed6e4ca14c72bc37f6290d32162ae6d5ab15d80e415883fb1129c80659cfd155b6d79eb43a2c34ece1b37eed8cfba08c1ed742dc8427e74618a9205ac6ea5f862663ab179b9db7291c9d95959110233d34f088e83b59b310300ff5d3827295e733c7839bbacb3f798ce36d07ba4daf6249abebbc01d1e04c82ac59fa5ee82341e730c9ac7e0cfb31901df394ce0092193de8303420614e5bd0ec1e18d09b6b0aa1b26811a2c6a39c90fb0eeaf6dfa6a26698d816299b3e5a4154701fdf145f332a077e9e880d0b48ac368aa8f5a50baf43f7a6bdb7f2079b45e766fd6a0e53449a7ef51452a443ad6c63acb53e1bde14944473cf1eda60b9a8879cc6b22179758c816156e8b33ca093e5395582e7ee25d5edbd0d1892f05f3901c1bebdc290144ce33dad4646b97753dd1481b66de5b27511af2cdf6634c48fbb73f14c1dd2b84412a8e18b99fa1fc4f6a734343b0db3d51d74cb5afec597982c1ff56b51c4f5fbae68cceb0cec0a22caaf1bfec45bc2fc1e56885bf1bf0827a44d6da0af2e89c4f8cf104d65585a851de36f8011b020a5b49f4310b19b452749c46f367254b63fccbe2960915839747aea712d85b16f8a350a2bf837c0d767deb2ebc6cc1e729437a814e3d84b3ab483938a764ae603eff8f282e1cd43f245804d1595ef8046f3425c322ad0d08cf7b9bc66cc3d8b5a055b74395832242a5d1c8b32fe2e18cd063a94d166cda2495855d8e2b27c0d13e84416b9a4b01eef1e1dc2b70a2700b78c5f46d279017c97e9e2f651fd78f193aa6ae7fabd77446789ef536af07160e474e3e2f41c4b9935ce0492519b782957957d8f237bd2a20a28ee0b9a6f41b635a65c574ba2d2a4e7dc869a579115487948d7bac267c130e717001b8fbbdd072ec35572641b222de2fb4e8d1c070fb590673cd80aca67c76b3da3b8fd069534094e4fb429ba4ff084b45711e30c3b5860ef2430fb61b1d72ffef90531d7b5a3c5de53dfdb77e2837b5d6dc488dc0de529da9fce08ba28fe66c7ffb22a10c16a6a12d6c0d769a54ecfb5c80d8a2ad66b7a806ecfae20529e774cf2175d725e1fd82f13b3830e49cebbc498a7795da9c87a869afa9c3ec75e64a7b9216e3856def9f9f3ae56dc31ab1d055b3dee8f5dd02075fc0c69899f92da01d016ebcb27e7a99a440aae2699e247c29c45b26e8210cfee3881ebee84a1dc9773156a8a6b1b02d4b431c481eb61fe00cca2a12fd8e5e7bfead37ca28ac434fd8af0e420043f3ce46546c4e8609be925542b1793e78f67efc0783904094fc77d2ca9bbf510ca210a69f8b81049d5e21bc16296603aae19e41bb70d09de885487439de10a34dc3d603e4c584c456185b2e90c61ebb2f51c4b332aa8dfdbb6987d64d14fd6033e5892180d1afb95dcd9d8b5358346453c6b7202885eb637400db890c13b95c183ce87fc478b6ae514b104955c57a2df65a8187dddb2fcc5bf2cc6c81dec5fd22b0eae315909f41ba0f7053a99f120c248d381d381de3602b5d4063e9f1c1c15fd019583cf0fa1483e4bb93b93daa84457f8e1ab8e77b58d4bddac448b49a4a7189eb5b9cbf8b4ced6dbde3943afa6ab95f45cc71e4fb7a9068147d74753ce65e5c2d7e4b4b18f5dc6c30be8207e3db6936b4fc209afdd9214efd39e065952e5b5073371bd8fb0040149ad24e0e5628abfd4e9beb57ba8b47106ea4244a66db1a40099f945e9f1abb33d9ab7a199262e49cdba166703411988a562b3818ad178e9ddce58b89d07d07ba61ae4ac3e6836f002752b5b2915a16060d4b56a8d96e769e1714f3e778a3ed3be0034bd49ad0d74c8468a01234304d29a7d42464b0a2d47f00a47fe2bcba5300014a6ddf5f0312156852ceb1452c4829176adae401ed07c4864f971bd2e0d2f8bae4004fdc99a8ee390624ec443c0d85f05731f8260857f21850e18918844f79660a3ea843fb6670c39e87c5f6653ff7cbd4a8aa0677562265cdf300c96c3ac639d1d9937bef2f7f60d9825e1cdd00d1813247742505b78eda6775ea3d32c44315874af6425d89d0ff254acedb4b6631ae13c507c15585bdff04de39ed0603c9286374323bdca905da5bcc573920ce90e02f648a0d34c867abf24c08a36a567856e92aeeed14233d927fa4e94ab1fd1993110bdbc3308111ec6e37af240197ec61947417acd7b09e3e6312d65933d1a1ffe3b080beaa59bf5798e5c247197ffc663d1969f3ad917be0e8406cf4234f4943202a7334e22e055bac85d493717de353c9cd7e386ee609397fa0c80f0641b7d01d8547ec30f91493ebb3fd731ac5b9db18658489a926f9fc55942d9cd487b8b2ef19f55e85340fbda62038f7abd7b0158939f7388a3cc8986dc8c964981360a9a7a4e5fe0ba044f187e9454b256b9607ae2e2a88eea7e7abe8815f917257c8514b4946aa84b1d5327328384a9033b4fd0781f2ba6ef4bf4aa1aff5ff0452beefe68cf2b51da9bd085d972d8eec0ebe017cee09ed55ba898bd4cc7fd0e189c524944357e30d32b8a577484fb0ec429565a7f6e0245a03d4454f99d0732a4c7a4ab99c1157d603338a2c109dffb7b733c65675cb21f81327da113de1245df2cfbcaea79445155ec4e1f0dd07526425c0368a7dc074a6942b884f097221448220ad6c37e8fbe1d3129843a57f3433bbe6b726003e88ab44d8f1780dc12f6d397e9f1bdde8b19314e8cd25d7f0da57683a3e1a451d6b0033724e93c68f61bebf13422874bfbfecf26bbeeaa916665b85ab0e748c2acddeeea796ff5a43344f58ae40c3aade4cb7f0d43341a94cef430b1f1c9ecd9d7e0ec65d07d85a05f79cd3320f8f0c9dbd42b832086d59b99b4ca3d4569c78c800d16b931592407ebac30ebcec8bca436362b44b5fdcdf02d4ea970b6529e0fd2307442e8a64dc44ae3bb85a8ebeb38b2ee4d9176857907cd69c88f0eb65a1f044ac85b6664d078944f567fc44c3944bda751c2f0265fe2c3ae9bb290e54889be524db35ea9b1582a91814f8e937a9b6f06f78a8894beb2cc4b5baefec2627745a4fa28992d94b93a228eb169273ecf458eedf7ec8b24d33482c16551a749910f029823d26bff298a58b145eacffb4843ed0ba1bb7e100c524c79aafd33a6f8264b3d922bc544919d401ca85d2d97e025fb84a137c3ff633513651d4421da486f9c065eb74c1f912f89ad95c80558337800cb15daa5a19cfd3685971a32a30a01a49d55e9dfef03890e1eb125c3e74dda054ef7f953ac6dc9516bf5d0dc164fce1a56b57047ce9be67f31ae3e8e50531a6c627e39826a66a4aed59d1df790f6832136ba894a985b42ddfd915993554393933182053456d40ae01c2a4ad14997f6011564fae4b7faa4e9c4716f497950cc433dd9778b0f4a2468aba695c1b7b1a7c6335b16230d675e0bc6c563220109ab23bcd8013e07f140e062d0832294d08eadecdc0bf89ecf7895d84bf32675c7039bca32a4c2210bddbabfe0d64969f5747237c9b09ae8f4a83104102cd41db7a6317dc0533c2c996769b88768117bc64397a7ffa44ba5bb59ae3188936cf045d2b76127bb975fac5c929f95bcbb07eb661d47085472f144249682d8f584ac842fde6d5c9b67187ef294177b5b9626824bdb85f74a3bffa2c07cc9866d147fd295ee063b33b3ed7500d6335324824875bc7923355935fe686e27898cfaaa7770fc4454221a888df9a009112a5f4e12dbfc908bc9a68bc43f3e3f15a50fafa62be53915d284db6fa887b3043b7a9e7eeafd83b2b356715f6b75654c7786174f0dde647cceb0d351de6976ecdc5908b6b1c73774da4357c94620971603f9fd23b472372c4f7fdd1d4ad13389540b4914ff15de48836f05c5337c389b3fca111ec7a20583e81350a02bc1f41008838093269f8759b0901baf3c5d7a8ea938f41d82bc57c8cabf9308067228a5e0e4ddbc8d411313ad003eb42c845cc2be0df42b7e8c55c0383767ede5085c22c8753186601c92bdb93700c4529e90d0067046a08ab44f3bb6b19ba889085187dbc7a3a44bc691d9b106bf46c6e753aefa3294acab400e0486c31eecc35b05a271cb1c34806816657cb53bb34b42a8ccc79524e03b26267321da97e5149d8b4cc10c678d1d93e99d3d2f4f589a6492da14ca1c795c634966004e6bad815843b4a90c59970875c2820a7c37c3109e5c322fedc26d95bbfb911d949066ad48071fbe073a2e3a0188acd178cfd5ccdccf07f5161c1db838e2bf54e1cef959b942b55d1526dd27f515023a5a6ede59a4b7c3214570cfb05dd13f741627d5570a057f1d1ffea9538b55d57e138e2c2f948ea0f9278941d31bd8c7b61d3d03fc9cb7e1d3cc347d08c3a7643aed40c63cba5a71ea83fb087dd3235393361dddfb09ec0c41d64cf299e362e420b48247a7797a415d1501e9e33bee39d93867857087e3058d9a1d1ff4119e6a35c004424de3cd8c4506ce678b402d98e6c5c8e932fa0606a41c4cd4bfc73114b062c3adefda9d5c6245bc1aa820f008bcd426e91d4b36f463b673ebe26aa6b5b3893eaf49e4a334558b3fa2ad2c2b57897f97dfff77cd96c6a11a6c9907cd8ea02792fa7e35897a57746fd40e0a2e977cc58cccc699fad49046db6945c0329de0b302b0bd321077b33ba557f96382f68565b319341bd9c40de291199a9d4e6c92ed6ae12406ef18d9dda310254f7019607b2dcc635831c4aa433c016820f0d744b23991c6d8a3ec88cbdfb36980ede390fa8d642e68872981fc01fe0d40a1df2c8224017d8c9f8c7f9a0eadf5b6d217a474c41702642ae1946e677433bb2034fb487630a67e4d4ff04a8eac8c07fd2f33e3cb0c8f1d0a091d9570a1a22b9deee4a1b427e01fb3ba2f9f37f9b166491a445ee1f60d2c703db645dc31db1e97bd819020b4036f33fae5fb7f75dcd6a2f8e316dbeff5c3697751e3786c5285606f0fda05448aacb2b8db6bcc7ca77bef7adb014b9f95983983d08f871787f52dc77a5e7ea9919b1fc752e27f2ed89c0aafb3b4c24569af8ae88717e03851ac010bb7758414bfed7f8776fdf34294217688d9d6545ee9444d8d8e08da242f41435dc80a94c683117ff6d3bbd0d169e5a043441c811f0f95c3cc6d3a62cc8acc73a5861125eea24108151546ae4b710485e43eb982c5d9ca00acf3b9455f6a18835e7d9972f807e345284c49fa14c84aee954760b18aaf8b728af7beddf39656353a7a74285e3d9671ffeb3a8b4ac6285e4db2f36c15de791c03f53f1c98e321b18fb291f6995c9204b39c818233dcd4ce7df643c368b78f9fb9a48203515a14a513a435900ba8e553235809adbd440abfa3d73e9abdf02827e75046e51a5597196ae52739b715b65c5765a860bfc708880818ed7456b3f405b5f8f2e5ecb5a808c53bb04300277623b3850cee361164e666c5120e3a495c87030689c35e7feefda51558c6b42fc2d79991e041ba087d8766c76eb3952e475717fa965e25afe4f6d298958af70259525b2157b53b3ff8b9729021e64430abb43829a6be0ad90e1bb04d68989b7b4ef3db8e75e39053ad15a48d68e1e325c0b858b19f75d298d1288cc8bf935045dfc89237c5e9b90876b1c505b76035a748281e18a5585d263ccc800cb30f2b2e4b95e292152a26600c9c6340039bcd10c3322f05a19b13e857fe9e8e3f9e70cd6a6c5711dd1dd560b56007e25399a268ade2074feaccc8b9855d07500a82cdd0f48e170dcbf6ee2095d0fd52d0c103162ec295e8dbd1474ea0a5e154ebdb5551895e81668d0e76b05e71eca4aa316df5ed3d63a798e94d7ce7c6704e6842acb391dbb1374559ec25785f58202d29404b70f805177b1add6622d84c3a2936df77ee684788390425807844b10df4609b44a84e18d291ed6abf88457c9dc6c2228e4202fab64372e3f76e0539575f297facf8cc3823b68158d0b268c8c2a7300d29f5b46fc905af795bbf2d9653552154e05d15014be4abce42c19920e581f5e762cbb3896c2920dfc6c8cdb3a78d0a9b2e85bcc38b1361d2cff8273d9f64ac91e46abc6e823d8532c1f69c8d2d3a0d8ec60262a9d61e1cd2483ba49363b3aac7253f546a03442c3e0e4c45c11352cde62e35f50329c4425583ec6f725368c0fe7ac930412bfa407e342402edf612889c8dbc7f059dc48bb7c65f3148ce082c9ccc224be21f272d2cec96d8a302f13435c58ec5ec12af820464b4b6a9375fdb2edeb5abe91305955dacedb2ada0257cb979632ea4b2cb4ecee6b829d1da11d1814f4da4b72d191dbd31d03e5ce051da925185f68b6d558d7a533d65b25e0fd5c5d74d3f706a3566da0157d74c6578db73c6912c0d885fbe455601e4684de242b692ea72d5cf5745de9c124d01943e42471d1e010e6e9d5905a336b3ed75962eb246854d776705b4b6aab4788a0149b53e0584f4481e3ba0d6706fead74ef83338655be6cc73d0463b72bb5f7e5632aea1a70e16054e44314f6fbd3613748302708a6dc8b5b211ee054f7f4339f7670882aab30911798973a72f5042ec3fbc519851777fcd2d7d3ea75c908f10fd9b1a31fd7d7cfe532f02aabaaabc3dc97dd5e8daf379b27b69d31b33634a7fe17cadc1cb25be6b4f4459e10e5befa3c3c6e9cd8b99fc9d7536a40d557f00b4f66f43b3a68b0cecbb4db60627cbb4f8c84160e64d67ca43d1f37169f353378a25db06a68c05f957b53b48373ae6693d2fbf80f2800c34c6bc2fa4551ad11f08b779943a9e359b58f5a6d3777e715c0266af2e41aedf5e6ccf07ec03c487763b2da133fcc321c7b5fc196eacf9990f4cf4275eb1ff0e3bdce8c6154fbd49329fb62dd447f0feba7632c553d4cb424af59c05b6802339c5f29db942b20d6a1e4c1f59a860a74edcdebd73b0e59d2a69a0bcb272aae4958d604dc770e61101de159b0fefc01565b02a30673898cc00ab7e0ad0d45d6c596dcd7d672749ade64ae80cb8bf156242921e7210426924cfb1915816a38da41d8538469610f74c0106ccf972caaab0820b31c84e6febbf6aca680f0b1899622df50e085ec32f15fada0265dd9507089d6e79ce8155707a750d45e705b8d8e678fd8064f86266e531e5216932fdfc0c06624fb4de1893d43f44c9661acf7c7fb67bda026cdc755258c172f0101c37eae094a51f71c9ef851797fc7bd1dc83adb6b90c95173a72c06613586551ed55d403bcef186db6add6ca49e6f774218f9b5f7e7ccb03738ae9f595b1ca01c34ffe870e214818c024e9b2dceb0bb0ace22b9610f9d26de60b90b716208cdebf2ab2a9ff7e9fa7dadf6f4b859b45d50a78489994c87213eaaa33b4cee9788fe22b24af121fec08daccbc0d345a2bc0ab44a5ef6bb36ed20ad9dd6abc38120279c6b091a40ecb35eca71d248f165fbd9efb8af3e7e7f8e50ff27f7807d03708e8d55333b9f59b3662dbac88784619c05a12cf7e6303b2a752545e16756118878c0ac57ffc69dda5da36bb1ef4c6771ea72bffef88a9c3c87f79bf129429240b3f47ff2289e2e1b674247fe7f80bcf364b6b59437b45964aa8473bdb957b91c8a50c63d5bc7ea2215ccf58a5c5cfb5b26b9075430c9f857396fa0eefd862f46155c2e33a003633799cf8203d23c08ee99e49fcb323cecb8a28877b5bad45e57dba7d09a63eea8f5495d68c72253db0c06a030647c6f0a54a14894429c987ced9765b7dd132a112905def41a4fa5e86bc3ef6a83a5400d26ae3cde7a48647459ad28a7b912d2e632391728aeb7f6e281f041234f4049a2366994bc9b0393de7cc55775aa2134a50462681986d4a80c10d71fd295e1cec010afbf2221dc7965f16cfe4f6b826d2ee2efc9e6afadce26fe28e364f2f70fd3ae4efbccc099862e3b35694e1fc883d62833ef8c0159f9def7130df47e192dcafc4e958d6305800150887a107d1037a7a9ebb3c77e0446e158f25b90d843912ecae8ffae0e3923d446ea1903c7a15f905fef5ce62ab4fbb1a3f009a0331554d4c738ff4597928c9df20503bbb8f992f5a56714b6038f7abc665a37fdb7dd302a690b785e1dcda55a0e340af6a919d6811a4cc0a955ef71f038a29d3b1028ef7166a90c68bd0137f247d35113d1ea0c00f1aab9b4f9ade4053e895157347e58f4afb4b6f542a9bf44cc2e7f59a9f678cbe47da7282c18893b71a406dc30fce68d15d4c2d8a7193cc79e7826afd8f5ebeb00fd0ffea8f327644fab45d90feefea7df06dd39e13274f0e1dbc594c44dce26d74495849f252ef5e37220808d9d4177a01f6d1af1043cb7c9109dc8b933b39407f157b9940f709f8697abcd144bfb7ae843e6ba1b63a47079b7146fc971a0b5ddad89608f44427bad2f15dfc85fb253d422e6a33fcfd7bba7b14f23453e849b35b4d4bebf141540febdb07254bbd36c85039722c15acde85b01d6882ba913f8b34d416f5987887f9bc00bcd010dee5ae43c3191bdd37981e18fd300659afc029cf2c47f781b491b2cbb6029cef08518fc085635868d70299364e49da965a9300a5a0df70e6c26f875cd74095943f4c82e49ccc744cded83c50fc45f801a4e5c725cfd6414a02366ac32941ed83139733c00884602631fa1ee6264ebeb1931ed5421567bad1904acaa07e39bf424132fc6e1ede84b4dfdfe81fa8d6889258e2dfa41b0285073c29b2a4a568921f3b30156b4555a8d0f47456949ae44b5e9bc6949769fac4b54b9bcbbf6368bafb882d6c9a798a238f03303e036a1f2e7abb04b57617ec13196d132abdda5605367329107f460f9180569971e3d73a8827e28bc0f97efabbb7d129bd474458db2335bcb172aadd32ad7d87403ee602779cfc4f93859167441beb6ed16724267c34872202f480c2c472fd39c2b9a25fc5f80201c098e02caf4725931054bb8ac58a25cf8749bb52c527d27dcc26121104c86d6a9c90c563238603867c2414526c4d838afc3fc9807e757b82f5653140edcf2a47e867c6c3fc6990b9fce1491a987b96066798567ab4a73bb87d2f9c14ae52a1238b9aaff38a58689093ac9cdcc4136625224ffec52c90835eaa02cc216a0708d88f2ff497d419462f6a2df984b07fe5e997ac338d3d7d334f3e9cc107526bd7080a0fa979f1bb61a16afca5bc2a145529cf2d34468330e72091128ed05022a955592a04b9441c956ce333268723c808544f5c4e45a1d9ed2dbc6e66001257d71f05c71d2ae2258c559d12ad27bc8aad6a62359393303d26d5e836f6526def78b9b94ec8857752cc415ead1222b3271171d19bd68cfb9fb180a1302181f49f6f2a5c3f66628d414661db9f33f16e9bf18e0a4c64cddb9020951ecdc6451cf2cb0fb52fdb5c8380f0cd9159073efbfb0b93e4263540610f197db812718fe1e1f39b21a88ac899e15811c58c8b1b1c0a7e4a94735d926f7587808bbfe72efa1af4ba7a57ad54f7b17e1fcf0ee3913e7c43818316dd60e1ad11d6f9e4a757ecfc0371ad2e5cded47f14f9bf5a501925255f81d6eef01e42bc14f3d364f6d3993f695a1d3c3fb42be3eef0772d87414ef30e9a9307393e1f3fe08a1926e18d04aa88f7ef6c674e7124906f7968915b15bdf2df5c4b2eb0d77ff92eb444f0c01482e5d52dcfd15b28218a32690d7cf0af4ae97dba15bbcea2e94e3b9d1a88a5f82aaf9a9b6a22dd3513fd62f8f51422535ecec2939d5c739dd6a242a755e6c460f4de957c94d29c7cb14c6dd3e5097c623164cff366193006179d84958d874b5489160c3164e9deee7e35e70574c5ce24e5e26b0cea813b5f31053444a6b7805556d3c8577cb5a65f92367cdf8def33f8940497eecd2d9dbf6eecfd02309a6227b6a2605fd70cd6ef9f136520bf898b506f5e155363eed94ca7dffdaf726703b48e4a9aa5e275007f9bcf019521ac8f78b0da0860b139fc2c53277ad6489caf15b3bf9b1754ad1e291c7b81f8ea341311f461ff44f18309c236549d989b2f99f3baac08ef5bc7bb2fdbf3d0923dab774de44dd6c263c513653b02fe25160fc0400b00af4a020b1c5cf3c22dcac326167581002e9f12f5796823cf48153c866d7cb9a1f9db1945829abe5a3edb82efd0be2c1fe49e346808fedf82a6132948b8ec6e6e4b4753c45ef29c463d85ef1930b614b1511bb84e80b681d221910ca783aad1c611277914235866e9b9ccfe422a6f69c7d0f0a639d1e919c2d5980e2b3ff2359dfcb30804f82eed129936808d76fbefd1dfce74833191534190404905c97ab763fa6f05f49da5f242ad93fe4da5d8efb706f4341354f9def327981aef0bbee49f02fc53eb1c9d4b918efd8c18e3be37efbe29d42a597615e15a4156f357bfab74727ae4088cfb90b3d0e685de9881edbf59d644afcb22245466f0eeba973856232d6440634f1b457700d190f2bbe95bb7da44de42dbe57ae623e36d170b4a6c2edf95aa7a2cbd4e496078b442f9cb925f49a9bbb2b538e6f3d6460dd5e57f2c45bae3a6985920490c8a1dba0b0a1f4a0e38a0f66b6348f482ef6ebc52ba115a41f310400f6941676bad90d9b21f8438bb633125bb1eebd54d82bdd7fabbd2d3b50d44d2a88e80002e9f23407c33af55b4135c18d95e95f66e9e84cbcfccd7f83b326e7b70120c599acbccc22d4f9b6ed1d20e755a7a180165a37630431925649f432b5d88a85ff56f9608f7cf79f0c287654f8bcd5f8a55e4e73a7b6e718db1df3e1de706821115a4ff9c92905faf948fd38bd08882387f0e7e495ea1ae1e11985574608088ad2aed90d0ca1815aeff68be9f01029e7e7f52a60af312b89e8007ec2d550161a8668a4c43bb310453176ad7e2754046a76ab59e0cc79cb3864f3dbcd51437eb1be97da04f6123985f8091b3f09b64ebed77b32620f05d43300d786c490349b445955ad50c38ebf7ccb43b37e566701d1370415529482b26475381ca7498cfd3577e5769d9cc38a14998d68dde171c784268ef8a81a0bd120cc2700e6bf4e74c5e5926a3e788289022bc1b855edaa5900143d2c59b8cbd996b5d14368cfb9e854dcc5dd65646aa903f5f0101438bc18322b37629a55290e0a00d7dfcba10a98c803e1b3398ce8af875ce046058bacdd967757471017692ce00224ca406e42b67037b4ee56b3090e771fce0a9cce2537b956b4471be78e6f4cbf3c20ed079ee8831b5983bb7c29a57c66d2b652aa700f707b6e02c9d087f4aed9403a968f6c4a806d503bd67bdae4cf69e44746c71f062b8fdc23b5200b27182e2d44c74e54eeec1875a3ba62affb71ad76d2af70bed94c76149506c17947205f510d6cd5c31a9676499799e9c00247aa53b20f79e9445363aa3a43600b226ec4169d08a71fe745373349ae9033f935193805d5d256d62cc35bb006ae6e81a7f7c79e09a92e25d85beba26f0cd5dd73ac2619f574349912a1ef730f348c06bca58ba8318badc3a3b4e43670409ee8a551c3b26627a2e64011716ecc8c24127052e35a80e88a00180c2a2955a328234b26596856467964c85fc60a56b4393d30359c6a3d424d3043aabbb87ec7e590c5fc0dbbe2a954586182965baaa02f9547c2afee2f73a2451d4715f085d4c154596c1e922718eec4fc73247f4cb03f570942cdc9585d35e7177be59f32e4582bc6b84bdc66bf33dc2e7d79e78ca2e970dc76ceb433e0f9df4d4b1164ea625738617fb8257fa62ab303757ae5a6fc4bcac9c3790b58cb256c6780e37dd4f7e60fbde4d9a6fac97bac8efd2b6a720cabb888818745f104fc17b1b0c6fb2527e30260ba63e5073ea616d3bde4a918964b1304b014d0ebe67d6b451cd7c77cc199c43f53523346595ebd4c68dd8a658afed22529626181e923cd1d72f588a3c048a93ff18d38c291542d50a78cecdf884a0899cd9</script>
</div>
<script src="/lib/blog-encrypt.js"></script><link href="/css/blog-encrypt.css" rel="stylesheet" type="text/css">]]></content>
<categories>
<category>Tech</category>
</categories>
<tags>
<tag>Learning Notes</tag>
<tag>Tech</tag>
<tag>AI</tag>
<tag>Machine Learning</tag>
<tag>Neural Network</tag>
</tags>
</entry>
<entry>
<title>二厨笔记 #1: 食材吸盐度小结</title>
<url>/life/dog2s-cooking-notes-1/</url>
<content><![CDATA[<ul>
<li>Title(EN): <em><strong>Dog2's Cooking Notes #1: Salt Absorption Rate of Food Ingredients</strong></em></li>
<li>Author: dog2</li>
</ul>
<hr />
<blockquote>
<p>学习炒菜已两月有余,觉得有些普适性的经验值得总结,便在此记录一下。</p>
<p>本篇总结一下不同食材的吸盐度。</p>
</blockquote>
<h2 id="前言">前言</h2>
<p>一道菜通常由多种食材混合烹制而成,其咸味轻重会直接影响口感。不同食材对盐的吸收能力不尽相同,因此较好地平衡不同食材的咸度是佳作的重要因素之一。但网上绝大多数菜谱或教程很少考虑这一点,大多数菜谱都是在所有食材都已入锅且烹制即将完成前放入盐的,这容易造成一道菜最终由于不同食材咸淡不均而影响口感。</p>
<h2 id="食材吸盐度表">食材吸盐度表</h2>
<ul>
<li>下表记录在炒制或煮制过程中不同食材对盐的吸收难易程度,完全根据个人经验,因此未必准确,如有不妥欢迎指正。</li>
<li>虽然此表反应的是吸盐难易程度,但也可引申为相关食材入味(酸/甜/苦/辣)的难易程度。</li>
<li>可以参考下表调整烹制时食材的形状,或调整盐与各种食材入锅的先后,以平衡不同食材的咸度。</li>
<li>吸盐度0-5分,5分表示极易吸盐,0分表示极难吸盐。</li>
</ul>
<a id="more"></a>
<table>
<thead>
<tr class="header">
<th style="text-align: center;">食材</th>
<th style="text-align: center;">形状</th>
<th style="text-align: center;">吸盐度</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: center;">白菜</td>
<td style="text-align: center;">叶片</td>
<td style="text-align: center;">2.8</td>
</tr>
<tr class="even">
<td style="text-align: center;">白萝卜</td>
<td style="text-align: center;">切片</td>
<td style="text-align: center;">3.5</td>
</tr>
<tr class="odd">
<td style="text-align: center;">包菜</td>
<td style="text-align: center;">手撕片</td>
<td style="text-align: center;">3</td>
</tr>
<tr class="even">
<td style="text-align: center;">大蒜</td>
<td style="text-align: center;">切碎粒</td>
<td style="text-align: center;">2</td>
</tr>
<tr class="odd">
<td style="text-align: center;">黄豆芽</td>
<td style="text-align: center;"></td>
<td style="text-align: center;">3</td>
</tr>
<tr class="even">
<td style="text-align: center;">胡萝卜</td>
<td style="text-align: center;">丁</td>
<td style="text-align: center;">2.5</td>
</tr>
<tr class="odd">
<td style="text-align: center;">茄子</td>
<td style="text-align: center;">丁/条</td>
<td style="text-align: center;">4.5</td>
</tr>
<tr class="even">
<td style="text-align: center;">鸡蛋</td>
<td style="text-align: center;">搅拌后煎块</td>
<td style="text-align: center;">3.6</td>
</tr>
<tr class="odd">
<td style="text-align: center;">鸡蛋</td>
<td style="text-align: center;">搅拌后入汤</td>
<td style="text-align: center;">3.4</td>
</tr>
<tr class="even">
<td style="text-align: center;">金针菇</td>
<td style="text-align: center;"></td>
<td style="text-align: center;">4</td>
</tr>
<tr class="odd">
<td style="text-align: center;">木耳</td>
<td style="text-align: center;">小朵</td>
<td style="text-align: center;">1.5</td>
</tr>
<tr class="even">
<td style="text-align: center;">藕</td>
<td style="text-align: center;">丁</td>
<td style="text-align: center;">3.4</td>
</tr>
<tr class="odd">
<td style="text-align: center;">青椒</td>
<td style="text-align: center;">丝</td>
<td style="text-align: center;">1.5</td>
</tr>
<tr class="even">
<td style="text-align: center;">土豆</td>
<td style="text-align: center;">薄片</td>
<td style="text-align: center;">2.8</td>
</tr>
<tr class="odd">
<td style="text-align: center;">土豆</td>
<td style="text-align: center;">细丝</td>
<td style="text-align: center;">2.8</td>
</tr>
<tr class="even">
<td style="text-align: center;">杏鲍菇</td>
<td style="text-align: center;">切片</td>
<td style="text-align: center;">3</td>
</tr>
<tr class="odd">
<td style="text-align: center;">有机花菜</td>
<td style="text-align: center;">切小朵</td>
<td style="text-align: center;">2</td>
</tr>
<tr class="even">
<td style="text-align: center;">猪肉</td>
<td style="text-align: center;">排骨</td>
<td style="text-align: center;">3</td>
</tr>
<tr class="odd">
<td style="text-align: center;">猪肉</td>
<td style="text-align: center;">丝/丁</td>
<td style="text-align: center;">3.2</td>
</tr>
</tbody>
</table>
<h2 id="后话">后话</h2>
<p>尽信书不如无书,菜谱亦然(,我朝一些书“超然”,比如某几门教科书@_@ 。。)。在看完菜谱并了解了其中不同食材的入锅顺序后,可结合上表总结的食材吸盐度来调整盐的放入方式,如可将菜谱中常见的起锅前一次性放入盐的方式改为分次放入,并合适掌控放入量,甚至可以考虑相应地更改食材的放入顺序,相信有时可以收获超越原菜谱水准的意外惊喜。</p>
<p>接下来的几篇会分享一些基于这种方法改良原有菜谱或者“创造”新菜谱的经验。</p>
<p>祝君烧得一手好菜~</p>
]]></content>
<categories>
<category>Life</category>
</categories>
<tags>
<tag>Cooking</tag>
<tag>Dog2's Cooking Notes</tag>
<tag>Foodie</tag>
</tags>
</entry>
<entry>
<title>二厨笔记 #2: 慢炒三丁「菜谱」</title>
<url>/life/dog2s-cooking-notes-2/</url>
<content><![CDATA[<ul>
<li>Title(EN): <em><strong>Dog2's Cooking Notes #2: Slowly Fried DingDingDing(Cookbook)</strong></em></li>
<li>Author: dog2</li>
</ul>
<hr />
<h2 id="前言">前言</h2>
<p>本菜谱是根据<span class="exturl" data-url="aHR0cDovL3d3dy54aWFjaHVmYW5nLmNvbS9y">「下厨房」<i class="fa fa-external-link-alt"></i></span>菜谱<span class="exturl" data-url="aHR0cDovL3d3dy54aWFjaHVmYW5nLmNvbS9yZWNpcGUvMTAwMjg3NTY2Lw==">「毛豆肉丁炒茄子」<i class="fa fa-external-link-alt"></i></span> 改进而来。</p>
<p>改进后的菜谱在食材中去掉了茄丁,用胡萝卜丁替换,因茄丁按照原菜谱的处理方式仍比较吸盐,且原菜谱中处理茄丁不像烹制肉末茄子那样会使用过量油煎透,这会导致最终烹制的茄丁带有些许生茄特有的腥味。</p>
<p>后续佐料以及烹制方法也有较大改动,具体见新菜谱。</p>
<a id="more"></a>
<h2 id="腌制瘦肉丝丁末">腌制瘦肉(丝/丁/末)</h2>
<p>在介绍正式菜谱之前,本节先介绍一下通用的瘦肉腌制方法。</p>
<p>不少经典菜谱都需要使用瘦肉,比如青椒肉丝、肉末茄子等,在炒制前对瘦肉进行腌制是一道必不可少的工序,腌制其实就是加入佐料拌匀静置片刻的简单过程,但不同菜谱中的腌制佐料不尽相同。因此这里介绍一下个人认为味道较好的腌制方式。</p>
<p>该方法正是从上节中的原菜谱中习得,也可运用到其他需腌制瘦肉的菜谱中,属于一种较好的通用姿势。</p>
<p><strong>腌制方法为:加入生抽、黄酒(料酒)、香油(芝麻油)、胡椒粉、淀粉(嫩肉粉),拌匀并静置10分钟以上。</strong></p>
<p>很多菜谱中介绍的腌制过程是需要加入盐的,但是经过一段时间实践我发现这是不需要且最好不要的,因为正如上一篇文章「食材吸盐度小结」中介绍,瘦肉属于较容易吸盐的食材,若腌制时加入盐,后续在炒制过程中还会再吸盐,最终可能导致炒出的菜唯独瘦肉过咸而导致整道菜的口感不平衡。</p>
<p>为了便于进一步体会,这里用青椒(木耳)肉丝的炒制过程作为示例:</p>
<ol type="1">
<li>腌制完的瘦肉过油后盛起备用。</li>
<li>少量油入锅,爆香葱姜蒜末。</li>
<li>青椒丝、木耳入锅,加入适量生抽、料酒、香油、糖以及三分之二的盐,炒至加入液体挥发殆尽。</li>
<li>瘦肉入锅,加入三分之一的盐及少量水,翻炒至水挥发殆尽后完成。</li>
</ol>
<p>如上过程有如下几点需要注意:</p>
<ul>
<li>这里说各种佐料加入适量而没有具体数量值,是由于适量的值确实需要通过不断实践积累经验才能体会到,也就是“凭感觉”=.=,有一定经验后还需根据当次烹制时食材的量来权衡佐料的量。</li>
<li>这里所说的盐的量,是假设炒至这道菜所需的盐的总量为1,则在炒至青椒和木耳时放入总量的三分之二,加入瘦肉后再放入剩下三分之一。至于实际需盐的量,仍需根据经验判断。</li>
<li>之所以先加入三分之二的盐,是由于青椒和木耳的吸盐度相近,且远不如瘦肉,因此为了平衡最终不同食材的咸度,需要先放入大部分的盐炒制片刻,使部分盐被青椒和木耳吸收,在瘦肉入锅时,再放入剩下的三分之一,就能保证最终不同食材的咸度相近。</li>
<li>在烹制绝大多数菜的时候,都可以加入适量的糖用于提鲜。</li>
<li>最后一次加水,是为了让佐料均匀附着。因为此时锅中较干,几乎无水分,加入的佐料会粘着在食材表面,导致其不均匀分布,而仅用锅铲卜楞(翻炒)几下有时也并不能达到理想效果,因此需用少量水溶解后翻炒,效果最佳。</li>
</ul>
<h2 id="菜谱">菜谱</h2>
<p>言归正传,介绍菜谱——慢炒三丁。</p>
<p>这道菜较为耗时,大概需要40分钟(准备食材约20分钟,烹制约20分钟)。</p>
<h3 id="用料">1. 用料</h3>
<table>
<thead>
<tr class="header">
<th style="text-align: center;">用料</th>
<th style="text-align: center;">说明</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: center;">猪肉</td>
<td style="text-align: center;">肉切小丁腌制10分钟</td>
</tr>
<tr class="even">
<td style="text-align: center;">毛豆</td>
<td style="text-align: center;">去外皮和里皮,里皮一定要去,否则难入味</td>
</tr>
<tr class="odd">
<td style="text-align: center;">胡萝卜</td>
<td style="text-align: center;">切小丁</td>
</tr>
<tr class="even">
<td style="text-align: center;">葱</td>
<td style="text-align: center;">适量切末</td>
</tr>
<tr class="odd">
<td style="text-align: center;">姜</td>
<td style="text-align: center;">适量切片</td>
</tr>
<tr class="even">
<td style="text-align: center;">蒜</td>
<td style="text-align: center;">适量切末</td>
</tr>
<tr class="odd">
<td style="text-align: center;">王守义十三香</td>
<td style="text-align: center;">这道菜的香味主要靠它,绝不可少哦</td>
</tr>
<tr class="even">
<td style="text-align: center;">生抽 料酒 芝麻油 淀粉 胡椒粉 味精(鸡精)</td>
<td style="text-align: center;">适量</td>
</tr>
</tbody>
</table>
<p>至于食材具体用量,依然需要凭感觉<sup>-</sup>,可以参考下图:</p>
<img src="/life/dog2s-cooking-notes-2/1.jpg" class="" title="食材一览">
<h3 id="烹制过程">2. 烹制过程</h3>
<ol type="1">
<li>一定量食用油入锅烧热,肉丁入锅炒至变色盛起待用。</li>
<li>再入少量食用油,葱姜蒜入锅爆香。</li>
<li>毛豆胡萝卜入锅,加入适量生抽、料酒、芝麻油、糖、三分之二的盐以及十三香,十三香可以稍微多一点,炒至加入液体挥发殆尽。糖的多少因人而异,二厨比较喜欢整道菜有淡淡的甜味,因此加得多点。</li>
<li>胡萝卜和毛豆较难熟透,因此需加入大量冷水进行大火煮制,具体的量以稍多于完全淹没锅中食材的量为佳,可以观察上图,此次用量为盛放毛豆的塑料碗一满碗。</li>
<li>在煮制15分钟左右直至加入的水已蒸发大部分而接近收汁时,肉丁入锅,加入适量味精(鸡精)和三分之一的盐,炒至锅中完全收汁。此时再加入少量水(200-300毫升),再炒至完全收汁即可出锅。</li>
</ol>
<h2 id="后话">后话</h2>
<p>本文以平衡不同食材的咸度为中心,坚持慢工细活儿和跟感觉走的两个基本点,介绍了二厨通过乱改菜谱、肆意烹调而妄图成为社会主义好大厨的过程。</p>
<p>这道菜较为耗时,因此不适合工作日烹制,那什么菜能在短时间内完成,以适合日益增多的奔波族呢?下一篇就会搜集一些懒人菜谱,以方便上班族们在回家后能简单快捷地吃上自己亲手打造的黑暗料理~</p>
]]></content>
<categories>
<category>Life</category>
</categories>
<tags>
<tag>Cooking</tag>
<tag>Dog2's Cooking Notes</tag>
<tag>Foodie</tag>
<tag>Cookbook</tag>
</tags>
</entry>
<entry>
<title>Python实现下载FTP服务器的整个目录(文件夹)</title>
<url>/tech/downloading-ftp-directory-recursively-with-python/</url>
<content><![CDATA[<ul>
<li>Title(EN): <em><strong>Downloading FTP Directory Recursively with Python</strong></em></li>
<li>Author: dog2</li>
</ul>
<hr />
<h3 id="需求">需求</h3>
<ul>
<li>快速批量下载多个FTP服务器的上的目录(指定目录或整个目录)。</li>
</ul>
<a id="more"></a>
<h3 id="分析">分析</h3>
<ul>
<li>核心问题:针对一个FTP Server,要能够下载其指定目录。即递归遍历所有目录,针对每个目录中的子目录,创建本地相应目录;针对每个目录中的文件,下载到本地相应目录。</li>
<li>下载所有文件的过程最好也是可并发的,以加快整个下载过程。</li>
</ul>
<h3 id="前车之轮">前车之轮</h3>
<p>需求及实现思路已经清楚,剩下的就是编码测试了。</p>
<p>按照惯例,为避免重复造轮子,在开工前有必要上谷歌百度一下,确定前人是否已经造出了好用的轮子。 找到如下几个:</p>
<ol type="1">
<li><span class="exturl" data-url="aHR0cDovL3d3dy5qYjUxLm5ldC9hcnRpY2xlLzMzOTg2Lmh0bQ==">通过python下载FTP上的文件夹的实现代码<i class="fa fa-external-link-alt"></i></span></li>
<li><span class="exturl" data-url="aHR0cDovL2Nhbmx5bmV0Lml0ZXllLmNvbS9ibG9nLzgzNjk5Ng==">python实现的ftp自动上传下载程序(支持目录递归操作)<i class="fa fa-external-link-alt"></i></span></li>
<li><span class="exturl" data-url="aHR0cDovL3d3dy5qYjUxLm5ldC9hcnRpY2xlLzY3MTk2Lmh0bQ==">python实现支持目录FTP上传下载文件的方法<i class="fa fa-external-link-alt"></i></span></li>
<li><span class="exturl" data-url="aHR0cHM6Ly9weXBpLnB5dGhvbi5vcmcvcHlwaS9mdHB1dGlsLzMuMg==">python第三方库 ftputil<i class="fa fa-external-link-alt"></i></span></li>
<li><span class="exturl" data-url="aHR0cHM6Ly9weXBpLnB5dGhvbi5vcmcvcHlwaS9weWZ0cHN5bmMvMS4wLjM=">python第三方库 pyftpsync<i class="fa fa-external-link-alt"></i></span></li>
</ol>
<p>逐一测试,都不太好用。</p>
<p>1在现实时使用os.chdir切换本地下载目录,批量下载(多线程并发下载多个服务器目录)时会产生目录混乱。</p>
<p>3在下载每个文件都需要执行ls函数确认其存在于远程目录,性能很低。</p>
<p>使用2 4 5没有成功下载过,可能是用法不对,粗略扫了下文档和源码,也没找到正确用法。如果你知道正确用法,还请指点。</p>
<h3 id="实现">实现</h3>
<p>既然前人的轮子都不太好用,只好再造一个。相对其他协议而言,FTP协议还是比较复杂的,因此最好基于已有的FTP库来实现,python的自带FTP库是ftplib,这里就选用它。</p>
<ul>
<li><span class="exturl" data-url="aHR0cHM6Ly9kb2NzLnB5dGhvbi5vcmcvMi9saWJyYXJ5L2Z0cGxpYi5odG1s">文档<i class="fa fa-external-link-alt"></i></span></li>
<li><span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL3B5dGhvbi9jcHl0aG9uL2Jsb2IvMi43L0xpYi9mdHBsaWIucHk=">源码<i class="fa fa-external-link-alt"></i></span></li>
</ul>
<p>大概实现思路及流程已在0x01中指出,这里可能需要用到ftplib中的两个函数</p>
<ul>
<li><strong>ftplib.FTP.dir()</strong> : 用于列举目录信息,其内部实现调用的是FTP协议中的LIST请求,输出格式类似linux下的命令_<strong>ls -alh</strong>_。这里需要注意,默认情况下,该函数是不返回目录信息的字符串的,它在内部实现中调用了ftplib.FTP.retrlines()函数得到返回数据的每一行,并默认使用println函数处理每行数据,即打印至标准输出。当然,处理每行数据的函数是可以被替换的,在dir()函数的参数中指出即可。尽管如此还是难以将目录信息存入到一个字符串并返回,以供我们后续调用。在0x02中提到的3是通过是给dir函数传入自定义类的实例函数,并将目录信息存储在自定义类的实例变量中来得到这个值的。当然,也可以通过自定义函数结合全局变量的方式来得到这个值。这种实现略显蹩脚,因此在这里,我们参照原有dir函数的实现方式,在自定义类中实现一个新的dir函数,它不用再传入处理每行数据的函数,且能够返回目录信息。</li>
<li><strong>ftplib.FTP.retrbinary()</strong> : 用于指定并发送某种FTP请求,并以二进制数据接收响应。这里使用它来执行FTP协议中的RETR命令,以下载FTP服务器上指定路径的文件。</li>
</ul>
<h3 id="代码">代码</h3>
<p>实现代码如下:</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment">#!/usr/bin/env python</span></span><br><span class="line"><span class="comment"># encoding: utf-8</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> os</span><br><span class="line"><span class="keyword">import</span> sys</span><br><span class="line"><span class="keyword">import</span> ftplib</span><br><span class="line"><span class="keyword">import</span> traceback</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">FtpDownloader</span><span class="params">(object)</span>:</span></span><br><span class="line"> PATH_TYPE_UNKNOWN = <span class="number">-1</span></span><br><span class="line"> PATH_TYPE_FILE = <span class="number">0</span></span><br><span class="line"> PATH_TYPE_DIR = <span class="number">1</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">__init__</span><span class="params">(self, host, user=None, passwd=None, port=<span class="number">21</span>, timeout=<span class="number">10</span>)</span>:</span></span><br><span class="line"> self.conn = ftplib.FTP(</span><br><span class="line"> host=host,</span><br><span class="line"> user=user,</span><br><span class="line"> passwd=passwd,</span><br><span class="line"> timeout=timeout</span><br><span class="line"> )</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">dir</span><span class="params">(self, *args)</span>:</span></span><br><span class="line"> <span class="string">'''</span></span><br><span class="line"><span class="string"> by defualt, ftplib.FTP.dir() does not return any value.</span></span><br><span class="line"><span class="string"> Instead, it prints the dir info to the stdout.</span></span><br><span class="line"><span class="string"> So we re-implement it in FtpDownloader, which is able to return the dir info.</span></span><br><span class="line"><span class="string"> '''</span></span><br><span class="line"> info = []</span><br><span class="line"> cmd = <span class="string">'LIST'</span></span><br><span class="line"> <span class="keyword">for</span> arg <span class="keyword">in</span> args:</span><br><span class="line"> <span class="keyword">if</span> arg:</span><br><span class="line"> cmd = cmd + (<span class="string">' '</span> + arg)</span><br><span class="line"> self.conn.retrlines(cmd, <span class="keyword">lambda</span> x: info.append(x.strip().split()))</span><br><span class="line"> <span class="keyword">return</span> info</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">tree</span><span class="params">(self, rdir=None, init=True)</span>:</span></span><br><span class="line"> <span class="string">'''</span></span><br><span class="line"><span class="string"> recursively get the tree structure of a directory on FTP Server.</span></span><br><span class="line"><span class="string"> args:</span></span><br><span class="line"><span class="string"> rdir - remote direcotry path of the FTP Server.</span></span><br><span class="line"><span class="string"> init - flag showing whether in a recursion.</span></span><br><span class="line"><span class="string"> '''</span></span><br><span class="line"> <span class="keyword">if</span> init <span class="keyword">and</span> rdir <span class="keyword">in</span> (<span class="string">'.'</span>, <span class="literal">None</span>):</span><br><span class="line"> rdir = self.conn.pwd()</span><br><span class="line"> tree = []</span><br><span class="line"> tree.append((rdir, self.PATH_TYPE_DIR))</span><br><span class="line"></span><br><span class="line"> dir_info = self.dir(rdir)</span><br><span class="line"> <span class="keyword">for</span> info <span class="keyword">in</span> dir_info:</span><br><span class="line"> attr = info[<span class="number">0</span>] <span class="comment"># attribute</span></span><br><span class="line"> name = info[<span class="number">-1</span>]</span><br><span class="line"> path = os.path.join(rdir, name)</span><br><span class="line"> <span class="keyword">if</span> attr.startswith(<span class="string">'-'</span>):</span><br><span class="line"> tree.append((path, self.PATH_TYPE_FILE))</span><br><span class="line"> <span class="keyword">elif</span> attr.startswith(<span class="string">'d'</span>):</span><br><span class="line"> <span class="keyword">if</span> (name == <span class="string">'.'</span> <span class="keyword">or</span> name == <span class="string">'..'</span>): <span class="comment"># skip . and ..</span></span><br><span class="line"> <span class="keyword">continue</span></span><br><span class="line"> tree.extend(self.tree(rdir=path,init=<span class="literal">False</span>)) <span class="comment"># recurse</span></span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> tree.append(path, self.PATH_TYPE_UNKNOWN)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> tree</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">downloadFile</span><span class="params">(self, rfile, lfile)</span>:</span></span><br><span class="line"> <span class="string">'''</span></span><br><span class="line"><span class="string"> download a file with path %rfile on a FTP Server and save it to locate</span></span><br><span class="line"><span class="string"> path %lfile.</span></span><br><span class="line"><span class="string"> '''</span></span><br><span class="line"> ldir = os.path.dirname(lfile)</span><br><span class="line"> <span class="keyword">if</span> <span class="keyword">not</span> os.path.exists(ldir):</span><br><span class="line"> os.makedirs(ldir)</span><br><span class="line"> f = open(lfile, <span class="string">'wb'</span>)</span><br><span class="line"> self.conn.retrbinary(<span class="string">'RETR %s'</span> % rfile, f.write)</span><br><span class="line"> f.close()</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">True</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">treeStat</span><span class="params">(self, tree)</span>:</span></span><br><span class="line"> numDir = <span class="number">0</span></span><br><span class="line"> numFile = <span class="number">0</span></span><br><span class="line"> numUnknown = <span class="number">0</span></span><br><span class="line"> <span class="keyword">for</span> path, pathType <span class="keyword">in</span> tree:</span><br><span class="line"> <span class="keyword">if</span> pathType == self.PATH_TYPE_DIR:</span><br><span class="line"> numDir += <span class="number">1</span></span><br><span class="line"> <span class="keyword">elif</span> pathType == self.PATH_TYPE_FILE:</span><br><span class="line"> numFile += <span class="number">1</span></span><br><span class="line"> <span class="keyword">elif</span> pathType == self.PATH_TYPE_UNKNOWN:</span><br><span class="line"> numUnknown += <span class="number">1</span></span><br><span class="line"> <span class="keyword">return</span> numDir, numFile, numUnknown</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">downloadDir</span><span class="params">(self, rdir=<span class="string">'.'</span>, ldir=<span class="string">'.'</span>, tree=None,</span></span></span><br><span class="line"><span class="function"><span class="params"> errHandleFunc=None, verbose=True)</span>:</span></span><br><span class="line"> <span class="string">'''</span></span><br><span class="line"><span class="string"> download a direcotry with path %rdir on a FTP Server and save it to</span></span><br><span class="line"><span class="string"> locate path %ldir.</span></span><br><span class="line"><span class="string"> args:</span></span><br><span class="line"><span class="string"> tree - the tree structure return by function FtpDownloader.tree()</span></span><br><span class="line"><span class="string"> errHandleFunc - error handling function when error happens in</span></span><br><span class="line"><span class="string"> downloading one file, such as a function that writes a log.</span></span><br><span class="line"><span class="string"> By default, the error is print to the stdout.</span></span><br><span class="line"><span class="string"> '''</span></span><br><span class="line"> <span class="keyword">if</span> <span class="keyword">not</span> tree:</span><br><span class="line"> tree = self.tree(rdir=rdir, init=<span class="literal">True</span>)</span><br><span class="line"> numDir, numFile, numUnknown = self.treeStat(tree)</span><br><span class="line"> <span class="keyword">if</span> verbose:</span><br><span class="line"> <span class="keyword">print</span> <span class="string">'Host %s tree statistic:'</span> % self.conn.host</span><br><span class="line"> <span class="keyword">print</span> <span class="string">'%d directories, %d files, %d unknown type'</span> % (</span><br><span class="line"> numDir,</span><br><span class="line"> numFile,</span><br><span class="line"> numUnknown</span><br><span class="line"> )</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> <span class="keyword">not</span> os.path.exists(ldir):</span><br><span class="line"> os.makedirs(ldir)</span><br><span class="line"> ldir = os.path.abspath(ldir)</span><br><span class="line"></span><br><span class="line"> numDownOk = <span class="number">0</span></span><br><span class="line"> numDownErr = <span class="number">0</span></span><br><span class="line"> <span class="keyword">for</span> rpath, pathType <span class="keyword">in</span> tree:</span><br><span class="line"> lpath = os.path.join(ldir, rpath.strip(<span class="string">'/'</span>).strip(<span class="string">'\\'</span>))</span><br><span class="line"> <span class="keyword">if</span> pathType == self.PATH_TYPE_DIR:</span><br><span class="line"> <span class="keyword">if</span> <span class="keyword">not</span> os.path.exists(lpath):</span><br><span class="line"> os.makedirs(lpath)</span><br><span class="line"> <span class="keyword">elif</span> pathType == self.PATH_TYPE_FILE:</span><br><span class="line"> <span class="keyword">try</span>:</span><br><span class="line"> self.downloadFile(rpath, lpath)</span><br><span class="line"> numDownOk += <span class="number">1</span></span><br><span class="line"> <span class="keyword">except</span> Exception <span class="keyword">as</span> err:</span><br><span class="line"> numDownErr += <span class="number">1</span></span><br><span class="line"> <span class="keyword">if</span> errHandleFunc:</span><br><span class="line"> errHandleFunc(err, rpath, lpath)</span><br><span class="line"> <span class="keyword">elif</span> verbose:</span><br><span class="line"> <span class="keyword">print</span> <span class="string">'An Error occurred when downloading '</span>\</span><br><span class="line"> <span class="string">'remote file %s'</span> % rpath</span><br><span class="line"> traceback.print_exc()</span><br><span class="line"> <span class="keyword">print</span></span><br><span class="line"> <span class="keyword">if</span> verbose:</span><br><span class="line"> <span class="keyword">print</span> <span class="string">'Host %s: %d/%d/%d(ok/err/total) files downloaded'</span> % (</span><br><span class="line"> self.conn.host,</span><br><span class="line"> numDownOk,</span><br><span class="line"> numDownErr,</span><br><span class="line"> numFile</span><br><span class="line"> )</span><br><span class="line"> <span class="keyword">elif</span> pathType == self.PATH_TYPE_UNKNOWN:</span><br><span class="line"> <span class="keyword">if</span> verbose:</span><br><span class="line"> <span class="keyword">print</span> <span class="string">'Unknown type romote path got: %s'</span> % rpath</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> verbose:</span><br><span class="line"> <span class="keyword">print</span> <span class="string">'Host %s directory %s download finished:'</span> % (</span><br><span class="line"> self.conn.host, rdir</span><br><span class="line"> )</span><br><span class="line"> <span class="keyword">print</span> <span class="string">'%d directories, %d(%d failed) files, %d unknown type.'</span> % (</span><br><span class="line"> numDir,</span><br><span class="line"> numFile,</span><br><span class="line"> numDownErr,</span><br><span class="line"> numUnknown</span><br><span class="line"> )</span><br><span class="line"> <span class="keyword">return</span> numDir, numFile, numUnknown, numDownErr</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">'__main__'</span>:</span><br><span class="line"> <span class="keyword">import</span> sys</span><br><span class="line"> <span class="keyword">import</span> traceback</span><br><span class="line"> <span class="keyword">from</span> pprint <span class="keyword">import</span> pprint <span class="keyword">as</span> pr</span><br><span class="line"></span><br><span class="line"> flog = open(<span class="string">'err.log'</span>, <span class="string">'wb'</span>)</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">run</span><span class="params">(host)</span>:</span></span><br><span class="line"> <span class="keyword">try</span>:</span><br><span class="line"> fd = FtpDownloader(</span><br><span class="line"> host=host,</span><br><span class="line"> user=<span class="string">'test'</span>,</span><br><span class="line"> passwd=<span class="string">'test'</span>,</span><br><span class="line"> port=<span class="number">21</span>,</span><br><span class="line"> timeout=<span class="number">10</span></span><br><span class="line"> )</span><br><span class="line"> numDir, numFile, numUnknown, numDownErr = fd.downloadDir(</span><br><span class="line"> rdir=<span class="string">'.'</span>,</span><br><span class="line"> ldir=<span class="string">'download'</span>,</span><br><span class="line"> tree=<span class="literal">None</span>,</span><br><span class="line"> errHandleFunc=<span class="literal">None</span>,</span><br><span class="line"> verbose=<span class="literal">True</span></span><br><span class="line"> )</span><br><span class="line"> flog.write(</span><br><span class="line"> <span class="string">'%s\nok\n'</span></span><br><span class="line"> <span class="string">'%d directories, %d(%d failed) files, %d unknown type\n\n\n'</span> % (</span><br><span class="line"> host,</span><br><span class="line"> numDir,</span><br><span class="line"> numFile,</span><br><span class="line"> numDownErr,</span><br><span class="line"> numUnknown</span><br><span class="line"> )</span><br><span class="line"> )</span><br><span class="line"> <span class="keyword">except</span> Exception <span class="keyword">as</span> err:</span><br><span class="line"> traceback.print_exc()</span><br><span class="line"> flog.write(</span><br><span class="line"> <span class="string">'%s\nerror\n%s\n\n\n'</span> % (</span><br><span class="line"> host,</span><br><span class="line"> traceback.format_exc()</span><br><span class="line"> )</span><br><span class="line"> )</span><br><span class="line"></span><br><span class="line"> pr(run(sys.argv[<span class="number">1</span>]))</span><br><span class="line"> flog.close()</span><br></pre></td></tr></table></figure>
<p>也可移步至<span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL2RvZy0yL2Z0cGRvd24=">github<i class="fa fa-external-link-alt"></i></span>获取代码,欢迎完善。</p>
<p>这里仅抛砖引玉,实现了下载单个FTP的整个目录的核心功能,可以基于该代码继续实现并发下载多个FTP服务器上的指定文件夹的功能。</p>
<p>值得一提的是,这里针对单个FTP服务器上的多个文件下载还是串行的,如想要实现并发下载单个FTP服务器上的多个文件,则可以先通过tree函数得到FTP服务器的目录树,然后再并发下载相应的文件。但并发下载时若多个下载线程共用一个ftplib.FTP类的实例,并调用该实例的retrbinary函数进行下载,则不同线程之间可能会相互影响,具体可以参考ftplib的源码。</p>
<p>当然,要解决这个问题,可以为每个下载线程创建一个独有的ftplib.FTP类的实例,但这样就加大了FTP服务器处理的并发连接数,最大连接数及下载性能还是会受限于FTP服务器,存在不确定性。</p>
<p>我们将实现的程序与Filezilia进行了对比测试,发现它对多个文件的下载过程也是串行了,而最终下载文件的总数及速度二者相近。</p>
]]></content>
<categories>
<category>Tech</category>
</categories>
<tags>
<tag>Python</tag>
<tag>FTP</tag>
</tags>
</entry>
<entry>
<title>Django REST Framework 学习笔记(一):RESTful API 初探</title>
<url>/tech/drf-learning-notes-1-hello-restful-api/</url>
<content><![CDATA[<ul>
<li>Title(EN): <em><strong>Django REST Framework Learning Notes (1): Hello RESTful API</strong></em></li>
<li>Author: dog2</li>
</ul>
<hr />
<blockquote class="blockquote-center">
<i class="fa fa-quote-left"></i>
<p>前后端分离是大势所趋</p>
<i class="fa fa-quote-right"></i>
</blockquote>
<h1 id="按">按</h1>
<p>最近在学习Django Rest Framework,<span class="exturl" data-url="aHR0cHM6Ly93d3cuZGphbmdvLXJlc3QtZnJhbWV3b3JrLm9yZy90dXRvcmlhbC9xdWlja3N0YXJ0Lw==">官方教程<i class="fa fa-external-link-alt"></i></span>较短,看完感觉并没有学习到最佳实践,对DRF的了解还是不成体系。<span class="exturl" data-url="aHR0cHM6Ly93d3cuZGphbmdvLXJlc3QtZnJhbWV3b3JrLm9yZy9hcGktZ3VpZGUvcmVxdWVzdHMv">官方API文档<i class="fa fa-external-link-alt"></i></span>虽然详细一些,但内容多且零散更适合查阅,对于系统性学习来说还是差了一点。</p>
<p>于是找到了<span class="exturl" data-url="aHR0cHM6Ly93d3cuYmlsaWJpbGkuY29tL3ZpZGVvL0JWMW5FNDExSjdoeA==">一套不错的DRF源码分析视频<i class="fa fa-external-link-alt"></i></span>来学习,并且在这里记下学习笔记。</p>
<p>参考了这两位同学的笔记,大部分是视频讲师在视频里做过的课堂笔记。</p>
<ul>
<li><span class="exturl" data-url="aHR0cHM6Ly93d3cuY25ibG9ncy5jb20vd2FuZ2N1aWNhbi9jYXRlZ29yeS8xNjA3ODIyLmh0bWw=">随笔分类 - Django REST framework笔记<i class="fa fa-external-link-alt"></i></span></li>
<li><span class="exturl" data-url="aHR0cHM6Ly93d3cuY25ibG9ncy5jb20veWFuZ2ppYW9zaG91L2NhdGVnb3J5LzE2NzUwMjEuaHRtbA==">随笔分类 - Django--drf相关<i class="fa fa-external-link-alt"></i></span></li>
</ul>
<p>根据我个人理解对笔记做了部分修改、整理和补充。这里有我的<span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL2RvZy0yL2hlbGxvX2RyZg==">测试代码<i class="fa fa-external-link-alt"></i></span>可供参考,包括django项目、db数据和postman的测试数据包。</p>
<h1 id="基本概念">基本概念</h1>
<ul>
<li><strong><span class="exturl" data-url="aHR0cHM6Ly96aC53aWtpcGVkaWEub3JnL3dpa2kvJUU4JUExJUE4JUU3JThFJUIwJUU1JUIxJTgyJUU3JThBJUI2JUU2JTgwJTgxJUU4JUJEJUFDJUU2JThEJUEy">RESTful<i class="fa fa-external-link-alt"></i></span></strong>: Representational State Transfer 表现层状态转换</li>
<li><strong>ful</strong>: 形容词后缀,表示“(这一)类的、(这种)风格的”</li>
<li><strong><span class="exturl" data-url="aHR0cHM6Ly96aC53aWtpcGVkaWEub3JnL3poLWNuLyVFNSVCQSU5NCVFNyU5NCVBOCVFNyVBOCU4QiVFNSVCQSU4RiVFNiU4RSVBNSVFNSU4RiVBMw==">API<i class="fa fa-external-link-alt"></i></span></strong>: Application Programming Interface 应用程序接口</li>
<li><strong>接口</strong>:联系两个物质的媒介,完成信息交互</li>
<li><strong>Web程序接口</strong>
<ul>
<li>功能:联系前台页面与后台数据库的媒介</li>
<li>组成
<ul>
<li>url: 长得像返回数据的url链接</li>
<li>请求参数: 前台按照指定的key提供数据给后台</li>
<li>响应数据: 后台与数据库交互后将数据反馈给前台</li>
</ul></li>
</ul></li>
</ul>
<a id="more"></a>
<h1 id="restful-api">RESTful API</h1>
<h2 id="接口规范">接口规范</h2>
<ul>
<li>功能:为了采用不同的后台语言,也能使用同样的接口,获取到同样的数据</li>
<li>接口:
<ul>
<li>url</li>
<li>相应数据</li>
</ul></li>
<li>接口文档:
<ul>
<li>url + 请求参数</li>
<li>响应数据</li>
</ul></li>
</ul>
<h2 id="url规范">URL规范</h2>
<p>RESFful API的URL应包含如下部分:</p>
<h3 id="用api关键字标识接口url">用api关键字标识接口url</h3>
<p>如 - api.baidu.com - www.baidu.com/api</p>
<h3 id="优先选择https协议">优先选择https协议</h3>
<p>接口数据安全性考量</p>
<h3 id="版本标识">版本标识</h3>
<p>如</p>
<ul>
<li>api.baidu.com/v1/...</li>
<li>api.baidu.com/v2/...</li>
</ul>
<h3 id="资源">资源</h3>
<p>接口操作的数据对象,在url中一般采用资源(名词)复数形式。一个接口可以包含对该资源的多种操作方式。如</p>
<ul>
<li>api.baidu.com/books</li>
<li>api.baidu.com/books/(pk)</li>
</ul>
<h3 id="请求方法">请求方法</h3>
<p><span class="exturl" data-url="aHR0cHM6Ly9kZXZlbG9wZXIubW96aWxsYS5vcmcvemgtQ04vZG9jcy9XZWIvSFRUUC9NZXRob2Rz">HTTP请求方法<i class="fa fa-external-link-alt"></i></span>,标识操作资源的方式,如</p>
<ul>
<li><code>GET /books/</code> & <code>GET /books/(pk)</code>: 获取所有/获取一个</li>
<li><code>POST /books/</code>: 增加一个(多个)</li>
<li><code>DELETE /books/(pk)</code>: 删除一个</li>
<li><code>PUT /books/(pk)</code>: 整体更新一个</li>
<li><code>PATCH /books/(pk)</code>: 局部更新一个</li>
</ul>
<h3 id="请求参数">请求参数</h3>
<p>往往涉及数据的各种过滤操作及表现形式 - 筛选、排序、限制,如</p>
<ul>
<li>api.baidu.com/books/?search=西&ordering=-price&limit=3</li>
</ul>
<h2 id="响应数据">响应数据</h2>
<p>API返回的数据,一般为Json格式,如</p>
<figure class="highlight json"><table><tr><td class="code"><pre><span class="line">{</span><br><span class="line"></span><br><span class="line"> <span class="attr">"errcode"</span>: <span class="number">0</span>,</span><br><span class="line"> <span class="attr">"msg"</span>: <span class="string">"Query success."</span>,</span><br><span class="line"> <span class="attr">"body"</span>: [</span><br><span class="line"> {</span><br><span class="line"> <span class="attr">"type"</span>: <span class="number">2</span>,</span><br><span class="line"> <span class="attr">"location"</span>: <span class="string">"116.298457,39.848625"</span>,</span><br><span class="line"> <span class="attr">"radius"</span>: <span class="number">41</span>,</span><br><span class="line"> <span class="attr">"country"</span>: <span class="string">"中国"</span>,</span><br><span class="line"> <span class="attr">"province"</span>: <span class="string">"北京市"</span>,</span><br><span class="line"> <span class="attr">"city"</span>: <span class="string">"北京市"</span>,</span><br><span class="line"> <span class="attr">"citycode"</span>: <span class="string">"131"</span>,</span><br><span class="line"> <span class="attr">"district"</span>: <span class="string">"丰台区"</span>,</span><br><span class="line"> <span class="attr">"road"</span>: <span class="string">"丰台南路44号"</span>,</span><br><span class="line"> <span class="attr">"ctime"</span>: <span class="string">"1551178833"</span>,</span><br><span class="line"> <span class="attr">"indoor"</span>: <span class="string">"1"</span>,</span><br><span class="line"> <span class="attr">"error"</span>: <span class="number">0</span></span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<ol type="1">
<li>响应状态码:由开发者(前端、后端、客户)定义,是对资源请求结果的应用层状态码,非HTTP响应状态码。
<ul>
<li>常见字段名示例:
<ul>
<li><code>"status"</code></li>
<li><code>"errcode"</code></li>
</ul></li>
<li>常见值示例:
<ul>
<li><code>0</code>: 表示操作资源成功</li>
<li><code>1</code>: 表示操作资源失败</li>
<li><code>2</code>: 表示操作资源成功,但没匹配结果</li>
</ul></li>
</ul></li>
<li>响应状态码文字说明
<ul>
<li>常见字段名示例
<ul>
<li><code>"msg"</code></li>
<li><code>"message"</code></li>
</ul></li>
</ul></li>
<li>资源本身
<ul>
<li>常见字段名示例
<ul>
<li><code>"data"</code></li>
<li><code>"results"</code></li>
</ul></li>
</ul></li>
</ol>
<p>注意:不能直接返回的资源(子资源、图片、视频等资源),而是返回该资源的url链接</p>
<h1 id="基于restful规范的原生django接口">基于restful规范的原生Django接口</h1>
<p>测试代码及数据请参见 <a href="https://github.com/dog-2/hello_drf/tree/master/no1_my_restapi" target="_blank" rel="noopener">demo项目 <code>no1_my_restapi</code></a></p>
<div class="note warning"><p>原生接口对<span class="exturl" data-url="aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dhbmdqdW41MTU5L2FydGljbGUvZGV0YWlscy80Nzc4MTQ0Mw==">各种格式的POST数据<i class="fa fa-external-link-alt"></i></span>的支持情况如下:</p>
<ul>
<li>[x] form-data</li>
<li>[x] x-www-form-urlencoded</li>
<li>[ ] <del>raw:包括 常见的json、xml等</del></li>
</ul>
<p>也就是说 <strong>原生接口并不支持对POST请求中json数据的自动解析,后端只能拿到整个json字符串</strong></p>
</div>
<h1 id="drf---django-rest-framework">DRF - Django REST Framework</h1>
<p>DRF有如下主要模块:</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">from</span> rest_framework.views <span class="keyword">import</span> APIView, ... <span class="comment"># 视图模块 - 对django原生视图类的封装</span></span><br><span class="line"><span class="keyword">from</span> rest_framework.response <span class="keyword">import</span> Response, ... <span class="comment"># 响应模块 - 对django原生响应类的封装</span></span><br><span class="line"><span class="keyword">from</span> rest_framework.request <span class="keyword">import</span> Request, ... <span class="comment"># 请求模块 - 对django原生请求类的封装</span></span><br><span class="line"><span class="keyword">from</span> rest_framework.serializers <span class="keyword">import</span> Serializer, ... <span class="comment"># 序列化与反序列化模块</span></span><br><span class="line"><span class="keyword">from</span> rest_framework.settings <span class="keyword">import</span> APISettings <span class="comment"># DRF的配置文件</span></span><br><span class="line"><span class="keyword">from</span> rest_framework.filters <span class="keyword">import</span> SearchFilter, ... <span class="comment"># RESTful API 基础功能 - 过滤</span></span><br><span class="line"><span class="keyword">from</span> rest_framework.pagination <span class="keyword">import</span> PageNumberPagination, ... <span class="comment"># RESTful API 基础功能 - 分页</span></span><br><span class="line"><span class="keyword">from</span> rest_framework.authentication <span class="keyword">import</span> TokenAuthentication, ... <span class="comment"># RESTful API 基础功能 - 认证</span></span><br><span class="line"><span class="keyword">from</span> rest_framework.permissions <span class="keyword">import</span> IsAuthenticated, ... <span class="comment"># RESTful API 基础功能 - 权限(是否登录)</span></span><br><span class="line"><span class="keyword">from</span> rest_framework.throttling <span class="keyword">import</span> SimpleRateThrottle <span class="comment"># RESTful API 基础功能 - 频率</span></span><br></pre></td></tr></table></figure>
<p>可以看到 DRF 在 django原有基础上进行类封装,并实现类 RESTful API的各大基础功能。</p>
<p>原生View不同,DRF实现了对json格式的POST请求数据的自动解析:</p>
<ul>
<li>[x] form-data</li>
<li>[x] x-www-form-urlencoded</li>
<li>[x] raw:包括 常见的json、xml等</li>
</ul>
<h1 id="扩展阅读">扩展阅读</h1>
<ul>
<li><strong><span class="exturl" data-url="aHR0cHM6Ly93d3cucmVzdGFwaXR1dG9yaWFsLmNvbS8=">A RESTful Tutorial<i class="fa fa-external-link-alt"></i></span></strong></li>
<li><strong>API Design Cheat Sheet</strong>: <span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL1Jlc3RDaGVhdFNoZWV0L2FwaS1jaGVhdC1zaGVldC9ibG9iL21hc3Rlci9SRUFETUUtemgtSGFucy5tZA==">中文<i class="fa fa-external-link-alt"></i></span> / <span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL1Jlc3RDaGVhdFNoZWV0L2FwaS1jaGVhdC1zaGVldCNhcGktZGVzaWduLWNoZWF0LXNoZWV0">EN<i class="fa fa-external-link-alt"></i></span></li>
<li><span class="exturl" data-url="aHR0cDovL2RyZi5qaXV5b3UuaW5mby8jLw==">DRF API 指南<i class="fa fa-external-link-alt"></i></span></li>
<li><span class="exturl" data-url="aHR0cHM6Ly9xMW1pLmdpdGh1Yi5pby9EamFuZ28tUkVTVC1mcmFtZXdvcmstZG9jdW1lbnRhdGlvbi8=">DRF 官方文档(历史版本)中文翻译<i class="fa fa-external-link-alt"></i></span></li>
<li><span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL2NyaWZhbi9odHRwX3Jlc3RmdWxfYXBp">HTTP后台端:RESTful API接口设计<i class="fa fa-external-link-alt"></i></span></li>
</ul>
]]></content>
<categories>
<category>Tech</category>
</categories>
<tags>
<tag>Learning Notes</tag>
<tag>Tech</tag>
<tag>Dev</tag>
<tag>Backend</tag>
<tag>Django</tag>
<tag>Django REST Framework</tag>
<tag>DRF</tag>
</tags>
</entry>
<entry>
<title>Django REST Framework 学习笔记(十):通用视图、视图工具类、视图集以及路由</title>
<url>/tech/drf-learning-notes-10-generic-views-mixins-viewsets-and-routers/</url>
<content><![CDATA[<ul>
<li>Title(EN): <em><strong>Django REST Framework Learning Notes (10): Generic Views, MixIns, ViewSets and Routers</strong></em></li>
<li>Author: dog2</li>
</ul>
<hr />
<h1 id="基本信息">基本信息</h1>
<ul>
<li>源码
<ul>
<li><span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL2VuY29kZS9kamFuZ28tcmVzdC1mcmFtZXdvcmsvYmxvYi9tYXN0ZXIvcmVzdF9mcmFtZXdvcmsvdmlld3MucHk=">rest_framework.views<i class="fa fa-external-link-alt"></i></span></li>
<li><span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL2VuY29kZS9kamFuZ28tcmVzdC1mcmFtZXdvcmsvYmxvYi9tYXN0ZXIvcmVzdF9mcmFtZXdvcmsvZ2VuZXJpY3MucHk=">rest_framework.generics<i class="fa fa-external-link-alt"></i></span></li>
<li><span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL2VuY29kZS9kamFuZ28tcmVzdC1mcmFtZXdvcmsvYmxvYi9tYXN0ZXIvcmVzdF9mcmFtZXdvcmsvdmlld3NldHMucHk=">rest_framework.viewsets<i class="fa fa-external-link-alt"></i></span></li>
<li><span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL2VuY29kZS9kamFuZ28tcmVzdC1mcmFtZXdvcmsvYmxvYi9tYXN0ZXIvcmVzdF9mcmFtZXdvcmsvcm91dGVycy5weQ==">rest_framework.routers<i class="fa fa-external-link-alt"></i></span></li>
</ul></li>
<li>官方文档
<ul>
<li><span class="exturl" data-url="aHR0cHM6Ly93d3cuZGphbmdvLXJlc3QtZnJhbWV3b3JrLm9yZy9hcGktZ3VpZGUvZ2VuZXJpYy12aWV3cy8=">API Guide - Generic views<i class="fa fa-external-link-alt"></i></span></li>
<li><span class="exturl" data-url="aHR0cHM6Ly93d3cuZGphbmdvLXJlc3QtZnJhbWV3b3JrLm9yZy9hcGktZ3VpZGUvdmlld3NldHMv">API Guide - ViewSets<i class="fa fa-external-link-alt"></i></span></li>
<li><span class="exturl" data-url="aHR0cHM6Ly93d3cuZGphbmdvLXJlc3QtZnJhbWV3b3JrLm9yZy9hcGktZ3VpZGUvcm91dGVycy8=">API Guide - Routers<i class="fa fa-external-link-alt"></i></span></li>
</ul></li>
<li><span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL2RvZy0yL2hlbGxvX2RyZi90cmVlL21hc3Rlci9ubzEwX2RyZl92aWV3cw==">本文demo代码Github<i class="fa fa-external-link-alt"></i></span></li>
</ul>
<a id="more"></a>
<h1 id="aipview---api视图类">AIPView - API视图类</h1>
<p><code>APIView</code>是Django REST Framework提供的所有视图的基类,继承自Django的<code>View</code>父类。</p>
<h2 id="与django-view的不同">与Django <code>View</code>的不同</h2>
<ol type="1">
<li>传入到视图方法中的是REST framework的<code>Request</code>对象,而不是Django的<code>HttpRequeset</code>对象</li>
<li>视图方法可以返回REST framework的<code>Response</code>对象,视图会为响应数据设置(render)符合前端要求的格式</li>
<li>任何<code>APIException</code>异常都会被捕获到,并且处理成合适的响应信息</li>
<li>在进行<code>dispatch()</code>分发前,会对请求进行身份认证、权限检查、流量控制</li>
</ol>
<h2 id="重要类属性">重要类属性</h2>
<p><code>AIPView</code>有如下可设置的重要类属性:</p>
<ul>
<li><code>authentication_classes</code>:列表或元祖,身份认证类</li>
<li><code>permissoin_classes</code>:列表或元祖,权限检查类</li>
<li><code>throttle_classes</code>:列表或元祖,流量控制类</li>
</ul>
<h2 id="示例代码">示例代码</h2>
<p>在<code>APIView</code>中仍有<code>get()</code>,<code>post()</code>等其他请求方式的方法</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">from</span> rest_framework.views <span class="keyword">import</span> APIView</span><br><span class="line"><span class="keyword">from</span> rest_framework.response <span class="keyword">import</span> Response</span><br><span class="line"></span><br><span class="line"><span class="comment"># path('books/', views.BookListView.as_view()),</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">BookListView</span><span class="params">(APIView)</span>:</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">get</span><span class="params">(self, request)</span>:</span></span><br><span class="line"> books = BookInfo.objects.all()</span><br><span class="line"> serializer = BookInfoSerializer(books, many=<span class="literal">True</span>)</span><br><span class="line"> <span class="keyword">return</span> Response(serializer.data)</span><br></pre></td></tr></table></figure>
<h1 id="genericapiview---通用api视图类">GenericAPIView - 通用API视图类</h1>
<p>通用API视图类<code>GenericAPIView</code>继承自<code>APIView</code>,完全兼容<code>APIView</code>,主要增加了操作序列化器和数据库查询的方法,作用是为下面<code>Mixin</code>扩展类的执行提供基础类支持。通常在使用时,可以配合一个或多个<code>Mixin</code>扩展类。</p>
<h2 id="genericapiview比apiview多了什么"><code>GenericAPIView</code>比<code>APIView</code>多了什么</h2>
<ol type="1">
<li><code>get_queryset()</code>:从类属性<code>queryset中</code>获得<code>model</code>的<code>queryset</code>数据。群操作就走<code>get_queryset()</code>方法(包括群查,群增等)。</li>
<li><code>get_object()</code>:从类属性<code>queryset</code>中获得<code>model</code>的<code>queryset</code>数据,再通过有名分组<code>pk</code>确定唯一操作对象。单操作就走<code>get_object()</code>方法(包括单查,单增等)。</li>
<li><code>get_serializer()</code>:从类属性<code>serializer_class</code>中获得<code>serializer</code>的序列化类。</li>
</ol>
<h2 id="重要类属性-1">重要类属性</h2>
<p><code>GenericAPIView</code>有如下可设置的重要类属性:</p>
<ul>
<li>列表视图与详情视图共用
<ul>
<li><code>queryset</code>:指明视图需要的数据(<code>model</code>查询数据)</li>
<li><code>permissoin_classes</code>:指明视图使用的序列化器</li>
</ul></li>
<li>列表视图使用
<ul>
<li><code>pagination_class</code>:指定分页控制类</li>
<li><code>filter_backends</code>:指定过滤控制后端</li>
</ul></li>
<li>详情页视图使用
<ul>
<li><code>lookup_field</code>:自定义主键,有名分组的查询,默认是<code>pk</code></li>
<li><code>lookup_url_kwarg</code>:查询单一数据时url中的参数关键字名称,默认与<code>look_field</code>相同</li>
</ul></li>
</ul>
<h2 id="重要类方法">重要类方法</h2>
<ul>
<li><code>get_queryset()</code>:从类属性<code>queryset</code>中获得<code>model</code>的<code>queryset</code>数据 </li>
<li><code>get_object()</code>:从类属性<code>queryset</code>中获得<code>model</code>的<code>queryset</code>数据,再通过有名分组<code>pk</code>来确定唯一操作对象</li>
<li><code>get_serializer()</code>:从类属性<code>serializer_class</code>中获得<code>serializer</code>的序列化类,主要用来提供给<code>Mixin</code>扩展类使用</li>
</ul>
<h2 id="get_serializer-源码"><code>get_serializer</code> 源码</h2>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">get_serializer</span><span class="params">(self, *args, **kwargs)</span>:</span></span><br><span class="line"> <span class="string">"""</span></span><br><span class="line"><span class="string"> Return the serializer instance that should be used for validating and</span></span><br><span class="line"><span class="string"> deserializing input, and for serializing output.</span></span><br><span class="line"><span class="string"> """</span></span><br><span class="line"> serializer_class = self.get_serializer_class()</span><br><span class="line"> kwargs[<span class="string">'context'</span>] = self.get_serializer_context()</span><br><span class="line"> <span class="keyword">return</span> serializer_class(*args, **kwargs)</span><br></pre></td></tr></table></figure>
<h2 id="示例代码-1">示例代码</h2>
<h3 id="视图层-views.py">视图层 <code>views.py</code></h3>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">BookGenericAPIView</span><span class="params">(GenericAPIView)</span>:</span></span><br><span class="line"> queryset = models.Book.objects.filter(is_delete=<span class="literal">False</span>)</span><br><span class="line"> serializer_class = serializers.BookModelSerializer</span><br><span class="line"> lookup_field = <span class="string">'pk'</span> <span class="comment"># 先定义好,单查可以使用,默认是pk 自定义主键的有名分组,如果路由有名分组不是pk,这个属性就要自己设置了</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment"># 群取</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">get</span><span class="params">(self, request, *args, **kwargs)</span>:</span></span><br><span class="line"> book_query = self.get_queryset() <span class="comment"># 获取queryset数据(model查询数据)</span></span><br><span class="line"> book_ser = self.get_serializer(book_query, many=<span class="literal">True</span>)</span><br><span class="line"> book_data = book_ser.data</span><br><span class="line"> <span class="keyword">return</span> APIResponse(results=book_data)</span><br><span class="line"></span><br><span class="line"> <span class="comment"># # 单取</span></span><br><span class="line"> <span class="comment"># def get(self, request, *args, **kwargs):</span></span><br><span class="line"> <span class="comment"># book_query = self.get_object()</span></span><br><span class="line"> <span class="comment"># book_ser = self.get_serializer(book_query)</span></span><br><span class="line"> <span class="comment"># book_data = book_ser.data</span></span><br><span class="line"> <span class="comment"># return APIResponse(results=book_data)</span></span><br></pre></td></tr></table></figure>
<h3 id="路由层-urls.py">路由层 <code>urls.py</code></h3>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line">urlpatterns = [</span><br><span class="line"> path(<span class="string">'v2/books/'</span>, views.BookGenericAPIView.as_view()),</span><br><span class="line"> path(<span class="string">'v2/books/<pk>/'</span>, views.BookGenericAPIView.as_view()),</span><br><span class="line">]</span><br></pre></td></tr></table></figure>
<h1 id="xxxmodelmixin---视图类的模型工具集"><code>xxxModelMixin</code> - 视图类的模型工具集</h1>
<h2 id="作用">作用</h2>
<ul>
<li>提供了几种后端视图(对数据资源的增删改查)处理流程的实现,如果需要编写的视图属于这五种,则视图可以通过继承相应的扩展类来复用代码,减少自己编写的代码量。</li>
<li><code>mixins</code>有五个工具类文件,一共提供了五个工具类,六个工具方法:单查、群查、单增、单删、单整体改、单局部改</li>
</ul>
<h2 id="使用">使用</h2>
<ul>
<li>继承工具类可以简化请求函数的实现体,但是必须继承<code>GenericAPIView</code>,需要<code>GenericAPIView</code>类提供序列化器与数据库查询的方法(见上方<code>GenericAPIView</code>基类知识点)</li>
<li>工具类的工具方法返回值都是<code>Response</code>类型对象,如果要格式化数据格式再返回给前台,可以通过<code>response.data</code>拿到工具方法返回的<code>Response</code>类型对象的响应数据</li>
</ul>
<h2 id="五大模型工具类">五大模型工具类</h2>
<h3 id="listmodelmixin-群查"><code>ListModelMixin</code> 群查</h3>
<ul>
<li>列表视图扩展类,提供<code>list</code>方法快速实现查询视图</li>
<li>返回<code>200</code>状态码</li>
<li>除了查询,该<code>list</code>方法会对数据进行过滤和分页</li>
</ul>
<h3 id="createmodelmixin-单增"><code>CreateModelMixin</code> 单增</h3>
<ul>
<li>创建视图扩展类,提供<code>create</code>方法快速创建资源的视图,成功返回<code>201</code>的状态码</li>
<li><strong>没有群增的方法,需要自己手动写</strong></li>
</ul>
<h3 id="retrievemodelmixin-单查"><code>RetrieveModelMixin</code> 单查</h3>
<ul>
<li>详情视图扩展类,提供<code>retrieve</code>方法,可以快速实现返回一个存在的数据对象</li>
</ul>
<h3 id="updatemodelmixin-更新修改"><code>UpdateModelMixin</code> 更新/修改</h3>
<ul>
<li>更新视图扩展类,提供<code>update</code>方法,可以快速实现更新一个存在的数据对象,同时也提供<code>partial_update</code>方法,可以实现局部更新</li>
<li><strong>只有单整体改和单局部改,没有群整体改和群局部改</strong></li>
</ul>
<h3 id="destorymodelmixin-删除"><code>DestoryModelMixin</code> 删除</h3>
<ul>
<li>删除视图扩展类,提供<code>destory</code>方法,可以快速实现删除一个存在数据对象</li>
<li>一般不怎么用到,因为实际开发中并不会真的删除数据,而是修改是否可用的标记</li>
</ul>
<h2 id="示例代码-2">示例代码</h2>
<h3 id="视图层-views.py-1">视图层 <code>views.py</code></h3>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">from</span> rest_framework.mixins <span class="keyword">import</span> ListModelMixin, CreateModelMixin, RetrieveModelMixin, UpdateModelMixin</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">BookMixinGenericAPIView</span><span class="params">(ListModelMixin, CreateModelMixin, RetrieveModelMixin, UpdateModelMixin, GenericAPIView)</span>:</span></span><br><span class="line"> <span class="comment"># GenericAPIView提供的序列化器和查询的数据</span></span><br><span class="line"> queryset = models.Book.objects.filter(is_delete=<span class="literal">False</span>)</span><br><span class="line"> serializer_class = serializers.BookModelSerializer</span><br><span class="line"></span><br><span class="line"> <span class="comment"># 单查和群查</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">get</span><span class="params">(self, request, *args, **kwargs)</span>:</span></span><br><span class="line"> <span class="keyword">if</span> <span class="string">'pk'</span> <span class="keyword">in</span> kwargs:</span><br><span class="line"> <span class="comment"># 单查 RetrieveModelMixin方法</span></span><br><span class="line"> response = self.retrieve(request, *args, **kwargs)</span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> <span class="comment"># mixins提供的list方法的响应对象是Response,将该对象格式化为自定义的APIResponse</span></span><br><span class="line"> response = self.list(request, *args, **kwargs) <span class="comment"># 群查 ListModelMixin</span></span><br><span class="line"> <span class="comment"># response的数据都存放在response.data中</span></span><br><span class="line"> <span class="keyword">return</span> APIResponse(results=response.data)</span><br><span class="line"></span><br><span class="line"> <span class="comment"># 单增</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">post</span><span class="params">(self, request, *args, **kwargs)</span>:</span></span><br><span class="line"> response = self.create(request, *args, **kwargs) <span class="comment"># CreateModelMixin方法</span></span><br><span class="line"> <span class="keyword">return</span> APIResponse(results=response.data)</span><br><span class="line"></span><br><span class="line"> <span class="comment"># 单整体修改</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">put</span><span class="params">(self, request, *args, **kwargs)</span>:</span></span><br><span class="line"> response = self.update(request, *args, **kwargs) <span class="comment"># UpdateModelMixin</span></span><br><span class="line"> <span class="keyword">return</span> APIResponse(results=response.data)</span><br><span class="line"></span><br><span class="line"> <span class="comment"># 单局部修改</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">patch</span><span class="params">(self, request, *args, **kwargs)</span>:</span></span><br><span class="line"> response = self.partial_update(request, *args, **kwargs)</span><br><span class="line"> <span class="keyword">return</span> APIResponse(results=response.data)</span><br></pre></td></tr></table></figure>
<h3 id="路由层-urls.py-1">路由层 <code>urls.py</code></h3>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line">urlpatterns = [</span><br><span class="line"> path(<span class="string">'v3/books/'</span>, views.BookMixinGenericAPIView.as_view()),</span><br><span class="line"> path(<span class="string">'v3/books/<pk>/'</span>, views.BookMixinGenericAPIView.as_view()),</span><br><span class="line">]</span><br></pre></td></tr></table></figure>
<h1 id="xxxapiview---功能性子视图类">xxxAPIView - 功能性子视图类</h1>
<p>功能性子视图类继承了<code>GenericAPIView</code>和各种<code>Mixins</code>工具类</p>
<ol type="1">
<li>功能性视图类都是<code>GenericAPIView</code>的子类,且不同的子类继承了不同的工具类</li>
<li>工功能性视图类的功能可以满足需求,只需要继承工具视图,并且提供<code>queryset</code>与<code>serializer_class</code>即可</li>
</ol>
<h2 id="各大功能子视图类">各大功能子视图类</h2>
<p>一表胜千言:</p>
<table>
<colgroup>
<col style="width: 25%" />
<col style="width: 25%" />
<col style="width: 25%" />
<col style="width: 25%" />
</colgroup>
<thead>
<tr class="header">
<th>视图</th>
<th>作用</th>
<th>请求类型</th>
<th>父类</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>ListAPIView</td>
<td>查询多条数据</td>
<td>get</td>
<td>GenericAPIView <br/> ListModelMixin</td>
</tr>
<tr class="even">
<td>CreateAPIView</td>
<td>新增一条数据</td>
<td>post</td>
<td>GenericAPIView <br/> CreateModelMixin</td>
</tr>
<tr class="odd">
<td>RetrieveAPIView</td>
<td>查询一条数据</td>
<td>get</td>
<td>GenericAPIView <br/> RetrieveModelMixin</td>
</tr>
<tr class="even">
<td>UpdateAPIView</td>
<td>修改一条数据</td>
<td>put <br/> patch</td>
<td>GenericAPIView <br/>UpdateModelMixin</td>
</tr>
<tr class="odd">
<td>DestroyAPIView</td>
<td>删除一条数据</td>
<td>delete</td>
<td>GenericAPIView <br/> DestroyModelMixin</td>
</tr>
<tr class="even">
<td>RetrieveUpdateAPIView</td>
<td>单查 <br/> 更新一条数据</td>
<td>get<br/>put<br/>patch</td>
<td>GenericAPIView<br/>RetrieveModelMixin<br/>UpdateModelMixin</td>
</tr>
<tr class="odd">
<td>RetrieveUpdateDestroyAPIView</td>
<td>单查<br/>更新<br/>删除一条数据</td>
<td>get<br/>put<br/>patch<br/>delete</td>
<td>GenericAPIView<br/> RetrieveModelMixin<br/>UpdateModelMixin<br/>DestroyModelMixin</td>
</tr>
<tr class="even">
<td>ListCreateAPIView</td>
<td>群查<br/>更新一条</td>
<td>get<br/>post</td>
<td>GenericAPIView<br/>ListModelMixin<br/>mixins.CreateModelMixin</td>
</tr>
</tbody>
</table>
<h2 id="示例代码-3">示例代码</h2>
<h3 id="视图层-views.py-2">视图层 <code>views.py</code></h3>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">from</span> rest_framework.generics <span class="keyword">import</span> ListCreateAPIView, UpdateAPIView</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">BookListCreatePIView</span><span class="params">(ListCreateAPIView, UpdateAPIView)</span>:</span></span><br><span class="line"> queryset = models.Book.objects.filter(is_delete=<span class="literal">False</span>)</span><br><span class="line"> serializer_class = serializers.BookModelSerializer</span><br></pre></td></tr></table></figure>
<h3 id="路由层-urls.py-2">路由层 <code>urls.py</code></h3>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line">urlpatterns = [</span><br><span class="line"> path(<span class="string">'v4/books/'</span>, views.BookListCreatePIView.as_view()),</span><br><span class="line"> path(<span class="string">'v4/books/<pk>/'</span>, views.BookListCreatePIView.as_view()),</span><br><span class="line">]</span><br></pre></td></tr></table></figure>
<h1 id="xxxviewset---视图集">xxxViewset - 视图集</h1>
<h2 id="常用视图集父类">常用视图集父类</h2>
<h3 id="viewsetmixin">ViewSetMixin</h3>
<p><code>ViewSetMixin</code>主要是自定义了<code>as_view</code>方法,使可以通过其参数指定 <code>HTTP_METHOD</code>与函数的映射关系,如 <code>view = MyViewSet.as_view({'get': 'list', 'post': 'create'})</code></p>
<h3 id="viewset">ViewSet</h3>
<p>继承自<code>APIView</code>和<code>ViewSetMixin</code>,没有提供任何方法,需要自己写</p>
<h3 id="genericviewset">GenericViewSet</h3>
<p>继承<code>GenericAPIView</code>和<code>ViewSetMixin</code>,其中<code>GenericAPIView</code>提供了基础方法,可以直接搭配<code>Mixin</code>扩展类使用,因此比较常用</p>
<h3 id="modelviewset">ModelViewSet </h3>
<p>继承<code>GenericViewset</code>,但同时也包括<code>ListModelMixin</code>、<code>CreateModelMixin</code>等<code>mixin</code>扩展类</p>
<h2 id="源码分析">源码分析</h2>
<ol type="1">
<li>视图集都是默认优先继承<code>ViewSetMixin</code>类,再继承一个视图类(<code>GenericAPIView</code>或<code>APIView</code>)</li>
<li><code>ViewSetMixin</code>提供了重写的<code>as_view()</code>方法,继承视图集的视图类,配置路由时调用<code>as_view()</code>必须传入 请求名-函数名 映射关系字典</li>
</ol>
<p>例如:</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line">path(<span class="string">'v5/books/'</span>, views.BookGenericViewSet.as_view({<span class="string">'get'</span>: <span class="string">'my_get_list'</span>})),</span><br></pre></td></tr></table></figure>
<p>表示<code>get</code>请求会交给<code>my_get_list</code>视图函数处理</p>
<h2 id="genericviewset-示例代码">GenericViewSet 示例代码</h2>
<h3 id="路由层-urls.py-3">路由层 urls.py</h3>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line">urlpatterns = [</span><br><span class="line"> <span class="comment"># View的as_view():将get请求映射到视图类的get方法</span></span><br><span class="line"> <span class="comment"># ViewSet的as_view({'get': 'my_get_list'}):将get请求映射到视图类的my_get_list方法</span></span><br><span class="line"> path(<span class="string">'v5/books/'</span>,</span><br><span class="line"> views.BookGenericViewSet.as_view({<span class="string">'get'</span>: <span class="string">'my_get_list'</span>})),</span><br><span class="line"> path(<span class="string">'v5/books/<pk>/'</span>,</span><br><span class="line"> views.BookGenericViewSet.as_view({<span class="string">'get'</span>: <span class="string">'my_get_obj'</span>})),</span><br><span class="line">]</span><br></pre></td></tr></table></figure>
<h3 id="视图层-views.py-3">视图层 views.py</h3>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">from</span> rest_framework.viewsets <span class="keyword">import</span> GenericViewSet</span><br><span class="line"><span class="keyword">from</span> rest_framework <span class="keyword">import</span> mixins <span class="comment">#工具集 可以使用list,retrieve等方法</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">BookGenericViewSet</span><span class="params">(RetrieveModelMixin, ListModelMixin, GenericViewSet)</span>:</span></span><br><span class="line"> queryset = models.Book.objects.filter(is_delete=<span class="literal">False</span>)</span><br><span class="line"> serializer_class = serializers.BookModelSerializer</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">my_get_list</span><span class="params">(self, request, *args, **kwargs)</span>:</span></span><br><span class="line"> <span class="keyword">return</span> self.list(request, *args, **kwargs)</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">my_get_obj</span><span class="params">(self, request, *args, **kwargs)</span>:</span></span><br><span class="line"> <span class="keyword">return</span> self.retrieve(request, *args, **kwargs)</span><br></pre></td></tr></table></figure>
<h2 id="genericviewset与viewset"><code>GenericViewSet</code>与<code>ViewSet</code></h2>
<h3 id="异同">异同</h3>
<ol type="1">
<li><code>GenericViewSet</code>和<code>ViewSet</code>都继承了<code>ViewSetMixin</code>,<code>as_view</code>都可以配置 请求-函数 映射</li>
<li><code>GenericViewSet</code>继承的是<code>GenericAPIView</code>视图类,用来完成标准的 <code>model</code> 类操作接口</li>
<li><code>ViewSet</code>继承的是<code>APIView</code>视图类,用来完成不需要 <code>model</code> 类参与,或是非标准的 <code>model</code> 类操作接口,如
<ul>
<li><code>post</code>请求在标准的 <code>model</code> 类操作下就是新增接口,登陆的<code>post</code>不满足。登陆的post请求,并不是完成数据的新增,只是用post提交数据,得到的结果也不是登陆的用户信息,而是登陆的认证信息</li>
<li><code>post</code>请求验证码的接口,不需要 <code>model</code> 类的参与</li>
</ul></li>
</ol>
<h3 id="源码">源码</h3>
<ul>
<li>GenericViewSet</li>
</ul>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment"># viewsets.py</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">GenericViewSet</span><span class="params">(ViewSetMixin, generics.GenericAPIView)</span>:</span></span><br><span class="line"> <span class="string">"""</span></span><br><span class="line"><span class="string"> The GenericViewSet class does not provide any actions by default,</span></span><br><span class="line"><span class="string"> but does include the base set of generic view behavior, such as</span></span><br><span class="line"><span class="string"> the `get_object` and `get_queryset` methods.</span></span><br><span class="line"><span class="string"> """</span></span><br><span class="line"> <span class="keyword">pass</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># generics.py</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">GenericAPIView</span><span class="params">(views.APIView)</span>:</span></span><br><span class="line"> <span class="comment"># ...</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># vies.py</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">APIView</span><span class="params">(View)</span>:</span></span><br><span class="line"> <span class="comment"># ...</span></span><br></pre></td></tr></table></figure>
<ul>
<li>ViewSet</li>
</ul>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ViewSet</span><span class="params">(ViewSetMixin, views.APIView)</span>:</span></span><br><span class="line"> <span class="string">"""</span></span><br><span class="line"><span class="string"> The base ViewSet class does not provide any actions by default.</span></span><br><span class="line"><span class="string"> """</span></span><br><span class="line"> <span class="keyword">pass</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># vies.py</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">APIView</span><span class="params">(View)</span>:</span></span><br><span class="line"> <span class="comment"># ...</span></span><br></pre></td></tr></table></figure>
<h2 id="modelviewset-示例代码">ModelViewSet 示例代码</h2>
<h3 id="路由层-urls.py-4">路由层 urls.py</h3>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line">urlpatterns = [</span><br><span class="line"> path(<span class="string">'v6/books/'</span>,</span><br><span class="line"> views.BookModelViewSet.as_view({<span class="string">'get'</span>: <span class="string">'list'</span>, <span class="string">'post'</span>: <span class="string">'create'</span>})),</span><br><span class="line"> path(<span class="string">'v6/books/<pk>/'</span>, </span><br><span class="line"> views.BookModelViewSet.as_view({<span class="string">'get'</span>: <span class="string">'retrieve'</span>, <span class="string">'put'</span>: <span class="string">'update'</span>, <span class="string">'patch'</span>: <span class="string">'partial_update'</span>, <span class="string">'delete'</span>: <span class="string">'destroy'</span>})),</span><br><span class="line">]</span><br></pre></td></tr></table></figure>
<h3 id="视图层-views.py-4">视图层 views.py</h3>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">BookModelViewSet</span><span class="params">(ModelViewSet)</span>:</span></span><br><span class="line"> queryset = models.Book.objects.filter(is_delete=<span class="literal">False</span>)</span><br><span class="line"> serializer_class = serializers.BookModelSerializer</span><br><span class="line"></span><br><span class="line"> <span class="comment"># 删不是数据库,而是该记录中的修改is_delete的值,因此重写默认的destroy函数</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">destroy</span><span class="params">(self, request, *args, **kwargs)</span>:</span></span><br><span class="line"> instance = self.get_object() <span class="comment"># type: models.Book</span></span><br><span class="line"> <span class="keyword">if</span> <span class="keyword">not</span> instance:</span><br><span class="line"> <span class="keyword">return</span> APIResponse(<span class="number">1</span>, <span class="string">'删除失败'</span>) <span class="comment"># 实际操作,在此之前就做了判断</span></span><br><span class="line"> instance.is_delete = <span class="literal">True</span></span><br><span class="line"> instance.save()</span><br><span class="line"> <span class="keyword">return</span> APIResponse(<span class="number">0</span>, <span class="string">'删除成功'</span>)</span><br></pre></td></tr></table></figure>
<h1 id="路由组件">路由组件</h1>
<p>因为具有局限性,所以在开发复杂接口时并不是首选。</p>
<p>示例代码如下:</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">from</span> django.urls <span class="keyword">import</span> path, include</span><br><span class="line"><span class="keyword">from</span> rest_framework.routers <span class="keyword">import</span> SimpleRouter</span><br><span class="line"><span class="keyword">from</span> . <span class="keyword">import</span> views</span><br><span class="line"></span><br><span class="line">router = SimpleRouter()</span><br><span class="line"><span class="comment"># 所有路由与ViewSet视图类的都可以注册,会产生 'v7/books/' 和 'v7/books/<pk>/'</span></span><br><span class="line">router.register(<span class="string">'v7/books'</span>, views.BookModelViewSet)</span><br><span class="line"></span><br><span class="line">urlpatterns = [</span><br><span class="line"> <span class="comment"># router.urls添加方法一</span></span><br><span class="line"> <span class="comment"># path('', include(router.urls)),</span></span><br><span class="line">]</span><br><span class="line"></span><br><span class="line"><span class="comment"># router.urls添加方法二</span></span><br><span class="line"><span class="comment"># urlpatterns += router.urls</span></span><br></pre></td></tr></table></figure>
<h1 id="总结">总结</h1>
<p>一图胜千言,非原创,均来自网络:</p>
<img src="/tech/drf-learning-notes-10-generic-views-mixins-viewsets-and-routers/1.png" class="" title="图1">
<img src="/tech/drf-learning-notes-10-generic-views-mixins-viewsets-and-routers/2.jpg" class="" title="图2">
]]></content>
<categories>
<category>Tech</category>
</categories>
<tags>
<tag>Learning Notes</tag>
<tag>Tech</tag>
<tag>Dev</tag>
<tag>Backend</tag>
<tag>Django</tag>
<tag>Django REST Framework</tag>
<tag>DRF</tag>
</tags>
</entry>
<entry>
<title>Django REST Framework 学习笔记(十一):认证组件</title>
<url>/tech/drf-learning-notes-11-authentication/</url>
<content><![CDATA[<ul>
<li>Title(EN): <em><strong>Django REST Framework Learning Notes (11): Authentication</strong></em></li>
<li>Author: dog2</li>
</ul>
<hr />
<h1 id="基本信息">基本信息</h1>
<ul>
<li>源码 <span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL2VuY29kZS9kamFuZ28tcmVzdC1mcmFtZXdvcmsvYmxvYi9tYXN0ZXIvcmVzdF9mcmFtZXdvcmsvYXV0aGVudGljYXRpb24ucHk=">rest_framework.authentication<i class="fa fa-external-link-alt"></i></span></li>
<li>官方文档 <span class="exturl" data-url="aHR0cHM6Ly93d3cuZGphbmdvLXJlc3QtZnJhbWV3b3JrLm9yZy9hcGktZ3VpZGUvYXV0aGVudGljYXRpb24v">API Guide - Authentication<i class="fa fa-external-link-alt"></i></span></li>
<li>本文demo代码Github
<ul>
<li><span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL2RvZy0yL2hlbGxvX2RyZi90cmVlL21hc3Rlci9ubzExX2RyZl9hdXRoZW50aWNhdGlvbi8=">自定义认证类<i class="fa fa-external-link-alt"></i></span></li>
<li><span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL2RvZy0yL2hlbGxvX2RyZi90cmVlL21hc3Rlci9ubzExX2RyZl9qd3Qv">jwt & simple-jwt<i class="fa fa-external-link-alt"></i></span></li>
</ul></li>
</ul>
<a id="more"></a>
<h1 id="源码分析">源码分析</h1>
<h2 id="入口">入口</h2>
<p>从<code>rest_framework.views.APIView</code>的<code>dispath(self, request, *args, **kwargs)</code>下手 ,<code>dispath</code>方法内 <code>self.initial(request, *args, **kwargs)</code> 进入三大认证</p>
<ul>
<li>认证组件 <code>self.perform_authentication(request)</code>
<ul>
<li>校验用户:游客、合法用户、非法用户</li>
<li>游客:代表校验通过,直接进入下一步校验(权限校验)</li>
<li>合法用户:代表校验通过,将用户存储在<code>request.user</code>中,再进入下一步校验(权限校验)</li>
<li>非法用户:代表校验失败,抛出异常,返回403权限异常结果</li>
</ul></li>
<li>权限组件 <code>self.check_permissions(request)</code>
<ul>
<li>校验用户权限:必须登录、所有用户、登录之后读写,游客只读、自定义用户角色</li>
<li>认证通过:可以进入下一步校验(频率认证)</li>
<li>认证失败:抛出异常,返回403权限异常结果</li>
</ul></li>
<li>频率组件 <code>self.check_throttles(request)</code>
<ul>
<li>限制视图接口被访问的频率次数 - 限制的条件(IP、id、唯一键)、频率周期时间(s、m、h)、频率的次数(3/s)</li>
<li>没有达到限次:正常访问接口</li>
<li>达到限次:限制时间内不能访问,限制时间达到后,可以重新访问</li>
</ul></li>
</ul>
<p>本文介绍认证组件。</p>
<h2 id="rest_framework.views.apiview.initial">rest_framework.views.APIView.initial()</h2>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">initial</span><span class="params">(self, request, *args, **kwargs)</span>:</span></span><br><span class="line"> <span class="string">"""</span></span><br><span class="line"><span class="string"> Runs anything that needs to occur prior to calling the method handler.</span></span><br><span class="line"><span class="string"> """</span></span><br><span class="line"> self.format_kwarg = self.get_format_suffix(**kwargs)</span><br><span class="line"></span><br><span class="line"> <span class="comment"># Perform content negotiation and store the accepted info on the request</span></span><br><span class="line"> neg = self.perform_content_negotiation(request)</span><br><span class="line"> request.accepted_renderer, request.accepted_media_type = neg</span><br><span class="line"></span><br><span class="line"> <span class="comment"># Determine the API version, if versioning is in use.</span></span><br><span class="line"> version, scheme = self.determine_version(request, *args, **kwargs)</span><br><span class="line"> request.version, request.versioning_scheme = version, scheme</span><br><span class="line"></span><br><span class="line"> <span class="comment"># Ensure that the incoming request is permitted</span></span><br><span class="line"> self.perform_authentication(request) <span class="comment">#认证</span></span><br><span class="line"> self.check_permissions(request) <span class="comment">#权限</span></span><br><span class="line"> self.check_throttles(request) <span class="comment">#频率</span></span><br></pre></td></tr></table></figure>
<h2 id="rest_framework.views.apiview.perform_authentication">rest_framework.views.APIView.perform_authentication()</h2>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">perform_authentication</span><span class="params">(self, request)</span>:</span></span><br><span class="line"> <span class="string">"""</span></span><br><span class="line"><span class="string"> Perform authentication on the incoming request.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string"> Note that if you override this and simply 'pass', then authentication</span></span><br><span class="line"><span class="string"> will instead be performed lazily, the first time either</span></span><br><span class="line"><span class="string"> `request.user` or `request.auth` is accessed.</span></span><br><span class="line"><span class="string"> """</span></span><br><span class="line"> request.user</span><br></pre></td></tr></table></figure>
<h2 id="rest_framework.request.request.user">rest_framework.request.Request.user</h2>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="meta">@property</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">user</span><span class="params">(self)</span>:</span></span><br><span class="line"> <span class="string">"""</span></span><br><span class="line"><span class="string"> Returns the user associated with the current request, as authenticated</span></span><br><span class="line"><span class="string"> by the authentication classes provided to the request.</span></span><br><span class="line"><span class="string"> """</span></span><br><span class="line"> <span class="keyword">if</span> <span class="keyword">not</span> hasattr(self, <span class="string">'_user'</span>):</span><br><span class="line"> <span class="keyword">with</span> wrap_attributeerrors():</span><br><span class="line"> self._authenticate()</span><br><span class="line"> <span class="keyword">return</span> self._user</span><br></pre></td></tr></table></figure>
<h2 id="rest_framework.request.request._authenticate">rest_framework.request.Request._authenticate()</h2>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 做认证</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">_authenticate</span><span class="params">(self)</span>:</span> <span class="comment">#这里的self就是request</span></span><br><span class="line"> <span class="comment"># 遍历拿到一个个认证器,进行认证</span></span><br><span class="line"> <span class="comment"># self.authenticators配置的一堆认证类产生的认证类对象组成的 list</span></span><br><span class="line"> <span class="comment"># 即:[auth() for auth in self.authentication_classes]</span></span><br><span class="line"> <span class="keyword">for</span> authenticator <span class="keyword">in</span> self.authenticators:</span><br><span class="line"> <span class="keyword">try</span>:</span><br><span class="line"> <span class="comment"># 认证器(对象)调用认证方法authenticate(认证类对象self, request请求对象)</span></span><br><span class="line"> <span class="comment"># 返回值:登陆的用户与认证的信息组成的 tuple</span></span><br><span class="line"> <span class="comment"># 该方法被try包裹,代表该方法会抛异常,抛异常就代表认证失败</span></span><br><span class="line"> user_auth_tuple = authenticator.authenticate(self)</span><br><span class="line"> <span class="keyword">except</span> exceptions.APIException:</span><br><span class="line"> self._not_authenticated()</span><br><span class="line"> <span class="keyword">raise</span></span><br><span class="line"></span><br><span class="line"> <span class="comment"># 返回值的处理</span></span><br><span class="line"> <span class="keyword">if</span> user_auth_tuple <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span>:</span><br><span class="line"> self._authenticator = authenticator</span><br><span class="line"> <span class="comment"># 如何有返回值,就将 登陆用户 与 登陆认证 分别保存到 request.user、request.auth</span></span><br><span class="line"> self.user, self.auth = user_auth_tuple</span><br><span class="line"> <span class="keyword">return</span></span><br><span class="line"> <span class="comment"># 如果返回值user_auth_tuple为空,代表认证通过,但是没有 登陆用户 与 登陆认证信息,代表游客</span></span><br><span class="line"> self._not_authenticated()</span><br></pre></td></tr></table></figure>
<h1 id="自定义认证类">自定义认证类</h1>
<div class="note warning"><p>认证组件一般都是自定义的,不会使用原始的</p>
</div>
<h2 id="方法">方法</h2>
<p>从源码的<code>settings</code>文件可以看出,认证类需要继承<code>BasicAuthentication</code>(在authentication.py文件)</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line">DEFAULT_AUTHENTICATION_CLASSES<span class="string">': [</span></span><br><span class="line"><span class="string"> '</span>rest_framework.authentication.SessionAuthentication<span class="string">', #会重新开启CSRF认证</span></span><br><span class="line"><span class="string"> '</span>rest_framework.authentication.BasicAuthentication<span class="string">'</span></span><br><span class="line"><span class="string">]</span></span><br></pre></td></tr></table></figure>
<p>具体流程如下:</p>
<ol type="1">
<li>创建继承<code>BaseAuthentication</code>的认证类</li>
<li>重写<code>authenticate</code>方法</li>
<li>实现体根据认证规则 确定游客、非法用户、合法用户 (根据自己的认证规则)
<ul>
<li>没有认证信息返回<code>None</code>(游客)</li>
<li>有认证信息认证失败抛异常(非法用户)</li>
<li>有认证信息认证成功返回用户与认证信息元组(合法用户)</li>
</ul></li>
<li>进行全局或局部配置</li>
</ol>
<h2 id="示例代码">示例代码</h2>
<h3 id="models.py">models.py</h3>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">from</span> django.db <span class="keyword">import</span> models</span><br><span class="line"><span class="keyword">from</span> django.contrib.auth.models <span class="keyword">import</span> AbstractUser</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">User</span><span class="params">(AbstractUser)</span>:</span></span><br><span class="line"> mobile = models.CharField(max_length=<span class="number">11</span>,unique=<span class="literal">True</span>)</span><br><span class="line"></span><br><span class="line"> <span class="class"><span class="keyword">class</span> <span class="title">Meta</span>:</span></span><br><span class="line"> db_table = <span class="string">'user'</span></span><br><span class="line"> verbose_name = <span class="string">'用户表'</span></span><br><span class="line"> verbose_name_plural = verbose_name</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">__str__</span><span class="params">(self)</span>:</span></span><br><span class="line"> <span class="keyword">return</span> self.username</span><br></pre></td></tr></table></figure>
<h3 id="utils.authentications.py">utils.authentications.py</h3>
<details>
<p><summary> <b><i>Source Code</i></b> </summary></p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 1)继承BaseAuthentication类</span></span><br><span class="line"><span class="comment"># 2)重写authenticate(self, request)方法,自定义认证规则</span></span><br><span class="line"><span class="comment"># 3)自定义认证规则基于的条件:</span></span><br><span class="line"><span class="comment"># 没有认证信息返回None(游客)</span></span><br><span class="line"><span class="comment"># 有认证信息认证失败抛异常(非法用户)</span></span><br><span class="line"><span class="comment"># 有认证信息认证成功返回用户与认证信息元组(合法用户)</span></span><br><span class="line"><span class="comment"># 4)完全视图类的全局(settings文件中)或局部(确切的视图类)配置</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> rest_framework.authentication <span class="keyword">import</span> BaseAuthentication</span><br><span class="line"><span class="keyword">from</span> rest_framework.exceptions <span class="keyword">import</span> AuthenticationFailed <span class="comment">#异常接收</span></span><br><span class="line"><span class="keyword">from</span> app01 <span class="keyword">import</span> models</span><br><span class="line"></span><br><span class="line"><span class="comment">#继承BaseAuthentication</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyAuthentication</span><span class="params">(BaseAuthentication)</span>:</span></span><br><span class="line"> <span class="string">"""</span></span><br><span class="line"><span class="string"> 同前台请求头拿认证信息auth(获取认证的字段要与前台约定)</span></span><br><span class="line"><span class="string"> 没有auth是游客,返回None</span></span><br><span class="line"><span class="string"> 有auth进行校验</span></span><br><span class="line"><span class="string"> 失败是非法用户,抛出异常</span></span><br><span class="line"><span class="string"> 成功是合法用户,返回 (用户, 认证信息)</span></span><br><span class="line"><span class="string"> """</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">authenticate</span><span class="params">(self, request)</span>:</span> <span class="comment">#重写authenticate方法</span></span><br><span class="line"> <span class="comment">#前台在请求头携带认证信息,</span></span><br><span class="line"> <span class="comment">#且默认规范用 Authorization 字段携带认证信息,</span></span><br><span class="line"> <span class="comment">#后台固定在请求对象的META字段中 HTTP_AUTHORIZATION 获取</span></span><br><span class="line"> <span class="comment">#认证信息auth</span></span><br><span class="line"> auth = request.META.get(<span class="string">'HTTP_AUTHORIZATION'</span>,<span class="literal">None</span>)<span class="comment">#处理游客</span></span><br><span class="line"> <span class="keyword">if</span> auth <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">None</span></span><br><span class="line"> <span class="comment">#设置认证字段小规则(两段式):"auth 认证字符串" 在BasicAuthentication类中有规则范式</span></span><br><span class="line"> auth_list = auth.split() <span class="comment">#校验是否还是非法用户,不是两段,第一段不是auth就是非法用户</span></span><br><span class="line"> <span class="keyword">if</span> <span class="keyword">not</span> (len(auth_list) == <span class="number">2</span> <span class="keyword">and</span> auth_list[<span class="number">0</span>].lower() == <span class="string">'auth'</span>):</span><br><span class="line"> <span class="keyword">raise</span> AuthenticationFailed(<span class="string">'认证信息有误,非法用户'</span>) <span class="comment">#抛异常</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">#校验认证信息第二段从auth_list[1]中解析出来</span></span><br><span class="line"> <span class="comment"># 注:假设一种情况,信息为abc.123.xyz,就可以解析出admin用户;实际开发,该逻辑一定是校验用户的正常逻辑</span></span><br><span class="line"> <span class="keyword">if</span> auth_list[<span class="number">1</span>] != <span class="string">'abc.123.xyz'</span>: <span class="comment">#校验失败</span></span><br><span class="line"> <span class="keyword">raise</span> AuthenticationFailed(<span class="string">'信息错误,非法用户'</span>)</span><br><span class="line"></span><br><span class="line"> <span class="comment">#最后再去数据库校验是否有此用户</span></span><br><span class="line"> user = models.User.objects.filter(username=<span class="string">'admin'</span>).first()</span><br><span class="line"> <span class="keyword">if</span> <span class="keyword">not</span> user:</span><br><span class="line"> <span class="keyword">raise</span> AuthenticationFailed(<span class="string">'用户数据有误,非法用户'</span>)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> (user,<span class="literal">None</span>)</span><br></pre></td></tr></table></figure>
</details>
<h3 id="settings.py">settings.py</h3>
<p>在<code>settings</code>文件中配置自定义认证组件</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line">REST_FRAMEWORK = {</span><br><span class="line"> <span class="comment"># 认证类配置</span></span><br><span class="line"> <span class="string">'DEFAULT_AUTHENTICATION_CLASSES'</span>: [</span><br><span class="line"> <span class="string">'utils.authentications.MyAuthentication'</span>,</span><br><span class="line"> ],</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="views.py">views.py</h3>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">from</span> rest_framework.views <span class="keyword">import</span> APIView</span><br><span class="line"><span class="keyword">from</span> utils.response <span class="keyword">import</span> APIResponse</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">TestAPIView</span><span class="params">(APIView)</span>:</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">get</span><span class="params">(self, request, *args, **kwargs)</span>:</span></span><br><span class="line"> <span class="comment"># 如果通过了认证组件,request.user就一定有值</span></span><br><span class="line"> <span class="comment"># 游客:AnonymousUser</span></span><br><span class="line"> <span class="comment"># 用户:User表中的具体用户对象</span></span><br><span class="line"> print(request.user)</span><br><span class="line"> <span class="keyword">return</span> APIResponse(<span class="number">0</span>, <span class="string">'test get ok'</span>)</span><br></pre></td></tr></table></figure>
<h3 id="urls.py">urls.py</h3>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">from</span> django.urls <span class="keyword">import</span> path</span><br><span class="line"><span class="keyword">from</span> . <span class="keyword">import</span> views</span><br><span class="line"></span><br><span class="line">urlpatterns = [</span><br><span class="line"> path(<span class="string">'test/'</span>, views.TestAPIView.as_view()),</span><br><span class="line">]</span><br></pre></td></tr></table></figure>
<h2 id="postman测试">Postman测试</h2>
<p>使用Postman的get请求,在自定义认证组件获取用户,在views视图通过<code>request.user</code>能打印出来</p>
<h1 id="token认证">Token认证</h1>
<h2 id="jwt">JWT</h2>
<p>RESTful API里使用最普遍的就是基于json的token认证了,即Json Web Token(JWT)。JWT 是一个开放标准(RFC 7519),它定义了一种用于简洁,自包含的用于通信双方之间以 JSON 对象的形式安全传递信息的方法。JWT 可以使用 HMAC 算法或者是 RSA 的公钥密钥对进行签名。</p>
<h3 id="优点">优点</h3>
<ol type="1">