-
Notifications
You must be signed in to change notification settings - Fork 0
/
search.xml
1808 lines (1552 loc) · 303 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><![CDATA[SQL]]></title>
<url>/2020/02/20/202002200630/</url>
<content type="html"><![CDATA[<p>SQL 是訪問和處理關系數據庫的計算機標準語言。</p>
<h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>應用程序不需要自己管理數據,而是通過數據庫軟件提供的接口來讀寫數據。至於數據本身如何存儲到文件,那是數據庫軟件的事情,應用程序自己並不關心:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">┌──────────────┐</span><br><span class="line">│ application │</span><br><span class="line">└──────────────┘</span><br><span class="line"> ▲│</span><br><span class="line"> ││</span><br><span class="line"> read││write</span><br><span class="line"> ││</span><br><span class="line"> │▼</span><br><span class="line">┌──────────────┐</span><br><span class="line">│ database │</span><br><span class="line">└──────────────┘</span><br></pre></td></tr></table></figure>
<p>這樣一來,編寫應用程序的時候,數據讀寫的功能就被大大地簡化了。</p>
<h3 id="數據模型"><a href="#數據模型" class="headerlink" title="數據模型"></a>數據模型</h3><p>數據庫按照數據結構來組織、存儲和管理數據,實際上,數據庫一共有三種模型:層次模型、網狀模型和關系模型。</p>
<h4 id="層次模型"><a href="#層次模型" class="headerlink" title="層次模型"></a>層次模型</h4><p>層次模型就是以「上下級」的層次關系來組織數據的一種方式,層次模型的數據結構看起來就像一顆樹:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"> ┌─────┐</span><br><span class="line"> │ │</span><br><span class="line"> └─────┘</span><br><span class="line"> │</span><br><span class="line"> ┌───────┴───────┐</span><br><span class="line"> │ │</span><br><span class="line"> ┌─────┐ ┌─────┐</span><br><span class="line"> │ │ │ │</span><br><span class="line"> └─────┘ └─────┘</span><br><span class="line"> │ │</span><br><span class="line"> ┌───┴───┐ ┌───┴───┐</span><br><span class="line"> │ │ │ │</span><br><span class="line">┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐</span><br><span class="line">│ │ │ │ │ │ │ │</span><br><span class="line">└─────┘ └─────┘ └─────┘ └─────┘</span><br></pre></td></tr></table></figure>
<h4 id="網狀模型"><a href="#網狀模型" class="headerlink" title="網狀模型"></a>網狀模型</h4><p>網狀模型把每個數據節點和其它很多節點都連接起來,它的數據結構看起來就像很多城市之間的路網:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"> ┌─────┐ ┌─────┐</span><br><span class="line"> ┌─│ │──────│ │──┐</span><br><span class="line"> │ └─────┘ └─────┘ │</span><br><span class="line"> │ │ │ │</span><br><span class="line"> │ └──────┬─────┘ │</span><br><span class="line"> │ │ │</span><br><span class="line">┌─────┐ ┌─────┐ ┌─────┐</span><br><span class="line">│ │─────│ │─────│ │</span><br><span class="line">└─────┘ └─────┘ └─────┘</span><br><span class="line"> │ │ │</span><br><span class="line"> │ ┌─────┴─────┐ │</span><br><span class="line"> │ │ │ │</span><br><span class="line"> │ ┌─────┐ ┌─────┐ │</span><br><span class="line"> └──│ │─────│ │──┘</span><br><span class="line"> └─────┘ └─────┘</span><br></pre></td></tr></table></figure>
<h4 id="關系模型"><a href="#關系模型" class="headerlink" title="關系模型"></a>關系模型</h4><p>關系模型把數據看作是一個二維表格,任何數據都可以通過行號+列號來唯一確定,它的數據模型看起來就是一個 Excel 表:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">┌─────┬─────┬─────┬─────┬─────┐</span><br><span class="line">│ │ │ │ │ │</span><br><span class="line">├─────┼─────┼─────┼─────┼─────┤</span><br><span class="line">│ │ │ │ │ │</span><br><span class="line">├─────┼─────┼─────┼─────┼─────┤</span><br><span class="line">│ │ │ │ │ │</span><br><span class="line">├─────┼─────┼─────┼─────┼─────┤</span><br><span class="line">│ │ │ │ │ │</span><br><span class="line">└─────┴─────┴─────┴─────┴─────┘</span><br></pre></td></tr></table></figure>
<h3 id="數據類型"><a href="#數據類型" class="headerlink" title="數據類型"></a>數據類型</h3><p>關系數據庫支持的標準數據類型包括數值、字符串、時間等:</p>
<table>
<thead>
<tr>
<th>名稱</th>
<th align="center">類型</th>
<th align="right">說明</th>
</tr>
</thead>
<tbody><tr>
<td>INT</td>
<td align="center">整型</td>
<td align="right">4 字節整數類型,範圍約 +/-21 億</td>
</tr>
<tr>
<td>BIGINT</td>
<td align="center">長整型</td>
<td align="right">8 字節整數類型,範圍約 +/-922 億億</td>
</tr>
<tr>
<td>REAL/FLOAT(24)</td>
<td align="center">浮點型</td>
<td align="right">4 字節浮點數,範圍約 +/-1038</td>
</tr>
<tr>
<td>DOUBLE</td>
<td align="center">浮點型</td>
<td align="right">8 字節浮點數,範圍約 +/-10308</td>
</tr>
<tr>
<td>DECIMAL(M,N)</td>
<td align="center">高精度小數</td>
<td align="right">由用戶指定精度的小數,例如,DECIMAL(20,10) 表示一共 20 位,其中小數 10 位,通常用於財務計算</td>
</tr>
<tr>
<td>CHAR(N)</td>
<td align="center">定長字符串</td>
<td align="right">存儲指定長度的字符串,例如,CHAR(100) 總是存儲 100 個字符的字符串</td>
</tr>
<tr>
<td>VARCHAR(N)</td>
<td align="center">變長字符串</td>
<td align="right">存儲可變長度的字符串,例如,VARCHAR(100) 可以存儲 0~100 個字符的字符串</td>
</tr>
<tr>
<td>BOOLEAN</td>
<td align="center">布爾類型</td>
<td align="right">存儲 True 或者 False</td>
</tr>
<tr>
<td>DATE</td>
<td align="center">日期類型</td>
<td align="right">存儲日期,例如,2016-06-22</td>
</tr>
<tr>
<td>TIME</td>
<td align="center">時間類型</td>
<td align="right">存儲時間,例如,12:20:59</td>
</tr>
<tr>
<td>DATETIME</td>
<td align="center">日期和時間類型</td>
<td align="right">存儲日期+時間,例如,2018-06-22 12:20:59</td>
</tr>
</tbody></table>
<p>選擇數據類型的時候,要根據業務規則選擇合適的類型。通常來說,BIGINT 能滿足整數存儲的需求,VARCHAR(N)能滿足字符串存儲的需求,這兩種類型是使用最廣泛的。</p>
<h3 id="SQL"><a href="#SQL" class="headerlink" title="SQL"></a>SQL</h3><p>SQL 是結構化查詢語言的縮寫,用來訪問和操作數據庫系統。SQL 語句既可以查詢數據庫中的數據,也可以添加、更新和刪除數據庫中的數據,還可以對數據庫進行管理和維護操作。</p>
<p>SQL 語言關鍵字不區分大小寫。但是,針對不同的數據庫,對於表名和列名,有的數據庫區分大小寫,有的數據庫不區分大小寫。同一個數據庫,有的在 Linux 上區分大小寫,有的在 Windows 上不區分大小寫。</p>
<h2 id="MySQL"><a href="#MySQL" class="headerlink" title="MySQL"></a>MySQL</h2><p>MySQL 是目前應用最廣泛的開源關系數據庫。要在 Windows 或 Mac 上安裝 MySQL,首先從 MySQL 官方網站下載最新的 MySQL Community Server 版本:<a href="https://dev.mysql.com/downloads/mysql/" target="_blank" rel="noopener">https://dev.mysql.com/downloads/mysql/</a></p>
<p>MySQL 在安裝過程中會自動創建一個 <strong>root</strong> 用戶,並提示輸入 <strong>root</strong> 口令。要在 Linux 上安裝 MySQL,可以使用發行版的包管理器。例如,Debian 和 Ubuntu 用戶可以簡單地通過命令 <strong>apt-get install mysql-server</strong> 安裝最新的 MySQL 版本。</p>
<p>MySQL 安裝後會自動在後臺運行。為了驗證 MySQL 安裝是否正確,需要通過 <strong>mysql</strong> 這個命令行程序來連接 MySQL 服務器。在命令提示符下輸入 <strong>mysql -u root -p</strong>,然後輸入口令,如果一切正確,就會連接到 MySQL 服務器,同時提示符變為 <strong>mysql></strong>。假設遠程 MySQL Server 的 IP 地址是 10.0.1.99,那麽就使用-h 指定 IP 或域名:<strong>mysql -h 10.0.1.99 -u root -p</strong>。輸入 <strong>exit</strong> 退出 MySQL 命令行。註意,MySQL 服務器仍在後臺運行。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 列出所有數據庫</span></span><br><span class="line">SHOW DATABASES;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 創建一個新數據庫</span></span><br><span class="line">CREATE DATABASE 數據庫名稱;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 刪除一個數據庫</span></span><br><span class="line">DROP DATABASE 數據庫名稱;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 將一個數據庫切換為當前數據庫</span></span><br><span class="line">USE 數據庫名稱</span><br><span class="line"></span><br><span class="line"><span class="comment"># 列出當前數據庫的所有表</span></span><br><span class="line">SHOW TABLES;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看一個表的結構</span></span><br><span class="line">DESC 表名稱;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 創建表</span></span><br><span class="line">CREATE TABLE 表名稱;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 刪除表</span></span><br><span class="line">DROP TABLE 表名稱;</span><br></pre></td></tr></table></figure>
<p>修改表就比較復雜。</p>
<p>要給 <strong>students</strong> 表新增一列 <strong>birth</strong>,使用:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">ALTER</span> <span class="keyword">TABLE</span> students <span class="keyword">ADD</span> <span class="keyword">COLUMN</span> birth <span class="built_in">VARCHAR</span>(<span class="number">10</span>) <span class="keyword">NOT</span> <span class="literal">NULL</span>;</span><br></pre></td></tr></table></figure>
<p>要修改 <strong>birth</strong> 列,例如把列名改為 <strong>birthday</strong>,類型改為 VARCHAR(20):</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">ALTER</span> <span class="keyword">TABLE</span> students <span class="keyword">CHANGE</span> <span class="keyword">COLUMN</span> birth birthday <span class="built_in">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT</span> <span class="literal">NULL</span>;</span><br></pre></td></tr></table></figure>
<p>要刪除列,使用:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">ALTER</span> <span class="keyword">TABLE</span> students <span class="keyword">DROP</span> <span class="keyword">COLUMN</span> birthday;</span><br></pre></td></tr></table></figure>
<h2 id="關系模型-1"><a href="#關系模型-1" class="headerlink" title="關系模型"></a>關系模型</h2><p>關系數據庫是建立在關系模型上的。而關系模型本質上就是若幹個存儲數據的二維表,可以把它們看作很多 Excel 表。表的每一行稱為記錄(Record),記錄是一個邏輯意義上的數據。表的每一列稱為字段(Column),同一個表的每一行記錄都擁有相同的若幹字段。</p>
<p>字段定義了數據類型(整型、浮點型、字符串、日期等),以及是否允許為 <strong>NULL</strong>。註意 <strong>NULL</strong> 表示字段數據不存在。一個整型字段如果為 <strong>NULL</strong> 不表示它的值為 <strong>0</strong>,同樣的,一個字符串型字段為 <strong>NULL</strong> 也不表示它的值為空串 <strong>‘ ‘</strong>。通常情況下,字段應該避免允許為 <strong>NULL</strong>。不允許為 <strong>NULL</strong> 可以簡化查詢條件,加快查詢速度,也利於應用程序讀取數據後無需判斷是否為 <strong>NULL</strong>。</p>
<p>和 Excel 表有所不同的是,關系數據庫的表和表之間需要建立「<strong>一對多</strong>」,「<strong>多對一</strong>」和「<strong>一對一</strong>」的關系,這樣才能夠按照應用程序的邏輯來組織和存儲數據。</p>
<p>在關系數據庫中,關系是通過<strong>主鍵</strong>和<strong>外鍵</strong>來維護的。</p>
<h3 id="主鍵"><a href="#主鍵" class="headerlink" title="主鍵"></a>主鍵</h3><p>在關系數據庫中,一張表中的每一行數據被稱為一條記錄。一條記錄就是由多個字段組成的。每一條記錄都包含若幹定義好的字段。同一個表的所有記錄都有相同的字段定義。</p>
<p>對於關系表,有個很重要的約束,就是任意兩條記錄不能重復。不能重復不是指兩條記錄不完全相同,而是指能夠通過某個字段唯一區分出不同的記錄,這個字段被稱為<strong>主鍵</strong>。</p>
<p>對主鍵的要求,最關鍵的一點是:記錄一旦插入到表中,主鍵最好不要再修改,因為主鍵是用來唯一定位記錄的,修改了主鍵,會造成一系列的影響。</p>
<p>由於主鍵的作用十分重要,如何選取主鍵會對業務開發產生重要影響。所以,選取主鍵的一個基本原則是:不使用任何業務相關的字段作為主鍵。</p>
<h4 id="聯合主鍵"><a href="#聯合主鍵" class="headerlink" title="聯合主鍵"></a>聯合主鍵</h4><p>關系數據庫實際上還允許通過多個字段唯一標識記錄,即兩個或更多的字段都設置為主鍵,這種主鍵被稱為聯合主鍵。對於聯合主鍵,允許一列有重復,只要不是所有主鍵列都重復即可。沒有必要的情況下,盡量不使用聯合主鍵,因為它給關系表帶來了復雜度的上升。</p>
<h3 id="外鍵"><a href="#外鍵" class="headerlink" title="外鍵"></a>外鍵</h3><p>外鍵並不是通過列名實現的,而是通過定義外鍵約束實現的:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">ALTER</span> <span class="keyword">TABLE</span> students <span class="keyword">ADD</span> <span class="keyword">CONSTRAINT</span> fk_class_id <span class="keyword">FOREIGN</span> <span class="keyword">KEY</span> (class_id) <span class="keyword">REFERENCES</span> classes (<span class="keyword">id</span>);</span><br></pre></td></tr></table></figure>
<p>其中,外鍵約束的名稱 <strong>fk_class_id</strong> 可以任意,<strong>FOREIGN KEY (class_id)</strong> 指定了 <strong>class_id</strong> 作為外鍵,<strong>REFERENCES classes (id)</strong> 指定了這個外鍵將關聯到 <strong>classes</strong> 表的 <strong>id</strong> 列(即 classes 表的主鍵)。</p>
<p>通過定義外鍵約束,關系數據庫可以保證無法插入無效的數據。由於外鍵約束會降低數據庫的性能,大部分互聯網應用程序為了追求速度,並不設置外鍵約束,而是僅靠<strong>應用程序</strong>自身來保證邏輯的正確性。這種情況下,<strong>class_id</strong> 僅僅是一個普通的列,只是它起到了外鍵的作用而已。</p>
<p>要刪除一個外鍵約束,也是通過 ALTER TABLE 實現的:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">ALTER</span> <span class="keyword">TABLE</span> students <span class="keyword">DROP</span> <span class="keyword">FOREIGN</span> <span class="keyword">KEY</span> fk_class_id;</span><br></pre></td></tr></table></figure>
<p>註意:刪除外鍵約束並沒有刪除外鍵這一列。</p>
<h3 id="索引"><a href="#索引" class="headerlink" title="索引"></a>索引</h3><p>在關系數據庫中,如果有上萬甚至上億條記錄,在查找記錄的時候,想要獲得非常快的速度,就需要使用索引。索引是關系數據庫中對某一列或多個列的值進行預排序的數據結構。通過使用索引,可以讓數據庫系統不必掃描整個表,而是直接定位到符合條件的記錄,這樣就大大加快了查詢速度。</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">ALTER</span> <span class="keyword">TABLE</span> students <span class="keyword">ADD</span> <span class="keyword">INDEX</span> idx_score (score);</span><br></pre></td></tr></table></figure>
<p>使用 <strong>ADD INDEX idx_score (score)</strong> 就創建了一個名稱為 <strong>idx_score</strong>,使用列 <strong>score</strong> 的索引。索引名稱是任意的,索引如果有多列,可以在括號裏依次寫上,例如:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">ALTER</span> <span class="keyword">TABLE</span> students <span class="keyword">ADD</span> <span class="keyword">INDEX</span> idx_name_score (<span class="keyword">name</span>, score);</span><br></pre></td></tr></table></figure>
<p>索引的效率取決於索引列的值是否散列,即該列的值如果越互不相同,那麽索引效率越高。反過來,如果記錄的列存在大量相同的值,例如 gender 列,大約一半的記錄值是 M,另一半是 F,因此,對該列創建索引就沒有意義。</p>
<p>可以對一張表創建多個索引。索引的優點是提高了查詢效率,缺點是在插入、更新和刪除記錄時,需要同時修改索引,因此,索引越多,插入、更新和刪除記錄的速度就越慢。</p>
<p>對於主鍵,關系數據庫會自動對其創建主鍵索引。使用主鍵索引的效率是最高的,因為主鍵會保證絕對唯一。</p>
<h4 id="唯一索引"><a href="#唯一索引" class="headerlink" title="唯一索引"></a>唯一索引</h4><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">ALTER</span> <span class="keyword">TABLE</span> students <span class="keyword">ADD</span> <span class="keyword">UNIQUE</span> <span class="keyword">INDEX</span> uni_name (<span class="keyword">name</span>);</span><br></pre></td></tr></table></figure>
<p>通過 UNIQUE 關鍵字就添加了一個唯一索引。</p>
<h4 id="強製使用指定索引"><a href="#強製使用指定索引" class="headerlink" title="強製使用指定索引"></a>強製使用指定索引</h4><p>在查詢的時候,數據庫系統會自動分析查詢語句,並選擇一個最合適的索引。但是很多時候,數據庫系統的查詢優化器並不一定總是能使用最優索引。可以使用 FORCE INDEX 強製查詢使用指定的索引。例如:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> * <span class="keyword">FROM</span> students <span class="keyword">FORCE</span> <span class="keyword">INDEX</span> (idx_class_id) <span class="keyword">WHERE</span> class_id = <span class="number">1</span> <span class="keyword">ORDER</span> <span class="keyword">BY</span> <span class="keyword">id</span> <span class="keyword">DESC</span>;</span><br></pre></td></tr></table></figure>
<p>指定索引的前提是索引 <strong>idx_class_id</strong> 必須存在。</p>
<h2 id="查詢數據"><a href="#查詢數據" class="headerlink" title="查詢數據"></a>查詢數據</h2><p>在關系數據庫中,最常用的操作就是查詢。</p>
<p>通過 <strong>mysql -u root -p < init-data.sql</strong> 命令導入初始化數據。</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 如果test數據庫不存在,就創建test數據庫:</span></span><br><span class="line"><span class="keyword">CREATE</span> <span class="keyword">DATABASE</span> <span class="keyword">IF</span> <span class="keyword">NOT</span> <span class="keyword">EXISTS</span> <span class="keyword">test</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 切換到test數據庫</span></span><br><span class="line"><span class="keyword">USE</span> <span class="keyword">test</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 刪除classes表和students表(如果存在):</span></span><br><span class="line"><span class="keyword">DROP</span> <span class="keyword">TABLE</span> <span class="keyword">IF</span> <span class="keyword">EXISTS</span> classes;</span><br><span class="line"><span class="keyword">DROP</span> <span class="keyword">TABLE</span> <span class="keyword">IF</span> <span class="keyword">EXISTS</span> students;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 創建classes表:</span></span><br><span class="line"><span class="keyword">CREATE</span> <span class="keyword">TABLE</span> classes (</span><br><span class="line"> <span class="keyword">id</span> <span class="built_in">BIGINT</span> <span class="keyword">NOT</span> <span class="literal">NULL</span> AUTO_INCREMENT,</span><br><span class="line"> <span class="keyword">name</span> <span class="built_in">VARCHAR</span>(<span class="number">100</span>) <span class="keyword">NOT</span> <span class="literal">NULL</span>,</span><br><span class="line"> PRIMARY <span class="keyword">KEY</span> (<span class="keyword">id</span>)</span><br><span class="line">) <span class="keyword">ENGINE</span>=<span class="keyword">InnoDB</span> <span class="keyword">DEFAULT</span> <span class="keyword">CHARSET</span>=utf8;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 創建students表:</span></span><br><span class="line"><span class="keyword">CREATE</span> <span class="keyword">TABLE</span> students (</span><br><span class="line"> <span class="keyword">id</span> <span class="built_in">BIGINT</span> <span class="keyword">NOT</span> <span class="literal">NULL</span> AUTO_INCREMENT,</span><br><span class="line"> class_id <span class="built_in">BIGINT</span> <span class="keyword">NOT</span> <span class="literal">NULL</span>,</span><br><span class="line"> <span class="keyword">name</span> <span class="built_in">VARCHAR</span>(<span class="number">100</span>) <span class="keyword">NOT</span> <span class="literal">NULL</span>,</span><br><span class="line"> gender <span class="built_in">VARCHAR</span>(<span class="number">1</span>) <span class="keyword">NOT</span> <span class="literal">NULL</span>,</span><br><span class="line"> score <span class="built_in">INT</span> <span class="keyword">NOT</span> <span class="literal">NULL</span>,</span><br><span class="line"> PRIMARY <span class="keyword">KEY</span> (<span class="keyword">id</span>)</span><br><span class="line">) <span class="keyword">ENGINE</span>=<span class="keyword">InnoDB</span> <span class="keyword">DEFAULT</span> <span class="keyword">CHARSET</span>=utf8;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 插入classes記錄:</span></span><br><span class="line"><span class="keyword">INSERT</span> <span class="keyword">INTO</span> classes(<span class="keyword">id</span>, <span class="keyword">name</span>) <span class="keyword">VALUES</span> (<span class="number">1</span>, <span class="string">'一班'</span>);</span><br><span class="line"><span class="keyword">INSERT</span> <span class="keyword">INTO</span> classes(<span class="keyword">id</span>, <span class="keyword">name</span>) <span class="keyword">VALUES</span> (<span class="number">2</span>, <span class="string">'二班'</span>);</span><br><span class="line"><span class="keyword">INSERT</span> <span class="keyword">INTO</span> classes(<span class="keyword">id</span>, <span class="keyword">name</span>) <span class="keyword">VALUES</span> (<span class="number">3</span>, <span class="string">'三班'</span>);</span><br><span class="line"><span class="keyword">INSERT</span> <span class="keyword">INTO</span> classes(<span class="keyword">id</span>, <span class="keyword">name</span>) <span class="keyword">VALUES</span> (<span class="number">4</span>, <span class="string">'四班'</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 插入students記錄:</span></span><br><span class="line"><span class="keyword">INSERT</span> <span class="keyword">INTO</span> students (<span class="keyword">id</span>, class_id, <span class="keyword">name</span>, gender, score) <span class="keyword">VALUES</span> (<span class="number">1</span>, <span class="number">1</span>, <span class="string">'小明'</span>, <span class="string">'M'</span>, <span class="number">90</span>);</span><br><span class="line"><span class="keyword">INSERT</span> <span class="keyword">INTO</span> students (<span class="keyword">id</span>, class_id, <span class="keyword">name</span>, gender, score) <span class="keyword">VALUES</span> (<span class="number">2</span>, <span class="number">1</span>, <span class="string">'小紅'</span>, <span class="string">'F'</span>, <span class="number">95</span>);</span><br><span class="line"><span class="keyword">INSERT</span> <span class="keyword">INTO</span> students (<span class="keyword">id</span>, class_id, <span class="keyword">name</span>, gender, score) <span class="keyword">VALUES</span> (<span class="number">3</span>, <span class="number">1</span>, <span class="string">'小軍'</span>, <span class="string">'M'</span>, <span class="number">88</span>);</span><br><span class="line"><span class="keyword">INSERT</span> <span class="keyword">INTO</span> students (<span class="keyword">id</span>, class_id, <span class="keyword">name</span>, gender, score) <span class="keyword">VALUES</span> (<span class="number">4</span>, <span class="number">1</span>, <span class="string">'小米'</span>, <span class="string">'F'</span>, <span class="number">73</span>);</span><br><span class="line"><span class="keyword">INSERT</span> <span class="keyword">INTO</span> students (<span class="keyword">id</span>, class_id, <span class="keyword">name</span>, gender, score) <span class="keyword">VALUES</span> (<span class="number">5</span>, <span class="number">2</span>, <span class="string">'小白'</span>, <span class="string">'F'</span>, <span class="number">81</span>);</span><br><span class="line"><span class="keyword">INSERT</span> <span class="keyword">INTO</span> students (<span class="keyword">id</span>, class_id, <span class="keyword">name</span>, gender, score) <span class="keyword">VALUES</span> (<span class="number">6</span>, <span class="number">2</span>, <span class="string">'小兵'</span>, <span class="string">'M'</span>, <span class="number">55</span>);</span><br><span class="line"><span class="keyword">INSERT</span> <span class="keyword">INTO</span> students (<span class="keyword">id</span>, class_id, <span class="keyword">name</span>, gender, score) <span class="keyword">VALUES</span> (<span class="number">7</span>, <span class="number">2</span>, <span class="string">'小林'</span>, <span class="string">'M'</span>, <span class="number">85</span>);</span><br><span class="line"><span class="keyword">INSERT</span> <span class="keyword">INTO</span> students (<span class="keyword">id</span>, class_id, <span class="keyword">name</span>, gender, score) <span class="keyword">VALUES</span> (<span class="number">8</span>, <span class="number">3</span>, <span class="string">'小新'</span>, <span class="string">'F'</span>, <span class="number">91</span>);</span><br><span class="line"><span class="keyword">INSERT</span> <span class="keyword">INTO</span> students (<span class="keyword">id</span>, class_id, <span class="keyword">name</span>, gender, score) <span class="keyword">VALUES</span> (<span class="number">9</span>, <span class="number">3</span>, <span class="string">'小王'</span>, <span class="string">'M'</span>, <span class="number">89</span>);</span><br><span class="line"><span class="keyword">INSERT</span> <span class="keyword">INTO</span> students (<span class="keyword">id</span>, class_id, <span class="keyword">name</span>, gender, score) <span class="keyword">VALUES</span> (<span class="number">10</span>, <span class="number">3</span>, <span class="string">'小麗'</span>, <span class="string">'F'</span>, <span class="number">85</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- OK:</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="string">'ok'</span> <span class="keyword">as</span> <span class="string">'result:'</span>;</span><br></pre></td></tr></table></figure>
<h3 id="基本查詢"><a href="#基本查詢" class="headerlink" title="基本查詢"></a>基本查詢</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> * <span class="keyword">FROM</span> <表名></span><br></pre></td></tr></table></figure>
<p>查詢一個表的所有行和所有列的數據。</p>
<h3 id="條件查詢"><a href="#條件查詢" class="headerlink" title="條件查詢"></a>條件查詢</h3><p>SELECT 語句可以通過 <strong>WHERE</strong> 條件來設定查詢條件,查詢結果是滿足查詢條件的記錄。</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> * <span class="keyword">FROM</span> <表名> <span class="keyword">WHERE</span> <條件表達式>;</span><br></pre></td></tr></table></figure>
<p>SELECT 語句可以通過 <strong>WHERE</strong> 條件來設定查詢條件,查詢結果是滿足查詢條件的記錄。第一種條件表達式可以用 <strong><條件 1> AND <條件 2></strong> 表達滿足條件 1 並且滿足條件 2。第二種條件是 <strong><條件 1> OR <條件 2></strong>,表示滿足條件 1 或者滿足條件 2。第三種條件是 <strong>NOT <條件></strong>,表示「不符合該條件」的記錄。<strong>NOT</strong> 條件 <strong><></strong>,因此,<strong>NOT</strong> 查詢不是很常用。</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> * <span class="keyword">FROM</span> students <span class="keyword">WHERE</span> score >= <span class="number">80</span>;</span><br><span class="line"><span class="keyword">SELECT</span> * <span class="keyword">FROM</span> students <span class="keyword">WHERE</span> score >= <span class="number">80</span> <span class="keyword">AND</span> gender = <span class="string">'M'</span>;</span><br><span class="line"><span class="keyword">SELECT</span> * <span class="keyword">FROM</span> students <span class="keyword">WHERE</span> score >= <span class="number">80</span> <span class="keyword">OR</span> gender = <span class="string">'M'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">SELECT</span> * <span class="keyword">FROM</span> students <span class="keyword">WHERE</span> <span class="keyword">NOT</span> class_id = <span class="number">2</span>;</span><br><span class="line"><span class="keyword">SELECT</span> * <span class="keyword">FROM</span> students <span class="keyword">WHERE</span> class_id <> <span class="number">2</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">SELECT</span> * <span class="keyword">FROM</span> students <span class="keyword">WHERE</span> (score < <span class="number">80</span> <span class="keyword">OR</span> score > <span class="number">90</span>) <span class="keyword">AND</span> gender = <span class="string">'M'</span>;</span><br></pre></td></tr></table></figure>
<h3 id="投影查詢"><a href="#投影查詢" class="headerlink" title="投影查詢"></a>投影查詢</h3><p>使用 <strong>SELECT * FROM <表名> WHERE <條件表達式></strong> 可以選出表中的若幹條記錄。返回的二維表結構和原表是相同的,即結果集的所有列與原表的所有列都一一對應。如果只希望返回某些列的數據,而不是所有列的數據,可以用 <strong>SELECT 列 1, 列 2, 列 3 FROM …</strong>,讓結果集僅包含指定列。這種操作稱為投影查詢。</p>
<p>這樣返回的結果集就只包含了指定的列,並且,結果集的列的順序和原表可以不一樣。</p>
<p>使用 <strong>SELECT 列 1, 列 2, 列 3 FROM …</strong> 時,還可以給每一列起個別名,這樣,結果集的列名就可以與原表的列名不同。它的語法是 <strong>SELECT 列 1 別名 1, 列 2 別名 2, 列 3 別名 3 FROM …</strong>。</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="keyword">id</span>, score, <span class="keyword">name</span> <span class="keyword">FROM</span> students;</span><br><span class="line"><span class="keyword">SELECT</span> <span class="keyword">id</span>, score points, <span class="keyword">name</span> <span class="keyword">FROM</span> students;</span><br><span class="line"><span class="keyword">SELECT</span> <span class="keyword">id</span>, score points, <span class="keyword">name</span> <span class="keyword">FROM</span> students <span class="keyword">WHERE</span> gender = <span class="string">'M'</span>;</span><br></pre></td></tr></table></figure>
<h3 id="排序"><a href="#排序" class="headerlink" title="排序"></a>排序</h3><p>使用 SELECT 查詢時,查詢結果集通常是按照 <strong>id</strong> 排序的,也就是根據主鍵排序。可以加上 <strong>ORDER BY</strong> 子句,選擇排序方式。</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- ASC 表示「升序」</span></span><br><span class="line"><span class="keyword">SELECT</span> * <span class="keyword">FROM</span> <表名> <span class="keyword">WHERE</span> <條件表達式> <span class="keyword">ORDER</span> <span class="keyword">BY</span> <屬性名> <span class="keyword">ASC</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- DESC 表示「倒序」</span></span><br><span class="line"><span class="keyword">SELECT</span> * <span class="keyword">FROM</span> <表名> <span class="keyword">WHERE</span> <條件表達式> <span class="keyword">ORDER</span> <span class="keyword">BY</span> <屬性名> <span class="keyword">DESC</span>;</span><br></pre></td></tr></table></figure>
<p>要進一步排序,可以繼續添加列名。</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- DESC 表示「倒序」</span></span><br><span class="line"><span class="keyword">SELECT</span> * <span class="keyword">FROM</span> <表名> <span class="keyword">WHERE</span> <條件表達式> <span class="keyword">ORDER</span> <span class="keyword">BY</span> <屬性名<span class="number">1</span>> <span class="keyword">DESC</span>, <屬性名<span class="number">2</span>> <span class="keyword">DESC</span>;</span><br></pre></td></tr></table></figure>
<p>表示先按<strong>屬性名 1</strong>列倒序,如果有相同的,再按<strong>屬性名 2</strong>列排序:</p>
<p><strong>ORDER BY</strong> 子句要放到 <strong>WHERE</strong> 子句後面</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="keyword">id</span>, <span class="keyword">name</span>, gender, score <span class="keyword">FROM</span> students <span class="keyword">ORDER</span> <span class="keyword">BY</span> score <span class="keyword">ASC</span>;</span><br><span class="line"><span class="keyword">SELECT</span> <span class="keyword">id</span>, <span class="keyword">name</span>, gender, score <span class="keyword">FROM</span> students <span class="keyword">ORDER</span> <span class="keyword">BY</span> score <span class="keyword">DESC</span>;</span><br><span class="line"><span class="keyword">SELECT</span> <span class="keyword">id</span>, <span class="keyword">name</span>, gender, score <span class="keyword">FROM</span> students <span class="keyword">ORDER</span> <span class="keyword">BY</span> score <span class="keyword">DESC</span>, gender <span class="keyword">ASC</span>;</span><br><span class="line"><span class="keyword">SELECT</span> <span class="keyword">id</span>, <span class="keyword">name</span>, gender, score <span class="keyword">FROM</span> students <span class="keyword">WHERE</span> class_id = <span class="number">1</span> <span class="keyword">ORDER</span> <span class="keyword">BY</span> score <span class="keyword">DESC</span>;</span><br></pre></td></tr></table></figure>
<h3 id="分頁查詢"><a href="#分頁查詢" class="headerlink" title="分頁查詢"></a>分頁查詢</h3><p>分頁實際上就是從結果集中「截取」出第 M~N 條記錄。這個查詢可以通過 <strong>LIMIT M OFFSET N</strong> 子句實現。對結果集從 <strong>N</strong> 號記錄開始,最多取 <strong>M</strong> 條。</p>
<p>分頁查詢的關鍵在於,首先要確定每頁需要顯示的結果數量 <strong>pageSize</strong>,然後根據當前頁的索引 <strong>pageIndex</strong>(從 0 開始),確定 <strong>LIMIT</strong> 和 <strong>OFFSET</strong> 應該設定的值:</p>
<ul>
<li><strong>LIMIT</strong> 總是設定為 <strong>pageSize</strong></li>
<li><strong>OFFSET</strong> 計算公式為 <strong>pageSize * pageIndex</strong></li>
</ul>
<p><strong>OFFSET</strong> 超過了查詢的最大數量並不會報錯,而是得到一個空的結果集。</p>
<p>在 MySQL 中,<strong>LIMIT M OFFSET N</strong> 還可以簡寫成 <strong>LIMIT M, N</strong>。</p>
<p>使用 <strong>LIMIT M OFFSET N</strong> 分頁時,隨著 <strong>N</strong> 越來越大,查詢效率也會越來越低。</p>
<h3 id="聚合查詢"><a href="#聚合查詢" class="headerlink" title="聚合查詢"></a>聚合查詢</h3><p>對於統計總數、平均數這類計算,SQL 提供了專門的聚合函數,使用聚合函數進行查詢,就是聚合查詢,它可以快速獲得結果。</p>
<p>使用 SQL 內置的 <strong>COUNT()</strong> 函數查詢記錄數量。</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="keyword">COUNT</span>(*) <span class="keyword">FROM</span> <表名>;</span><br></pre></td></tr></table></figure>
<p><strong>COUNT(*)</strong> 表示查詢所有列的行數,要註意聚合的計算結果雖然是一個數字,但查詢的結果仍然是一個二維表,只是這個二維表只有一行一列,並且列名是 <strong>COUNT(*)</strong>。</p>
<p>通常,使用聚合查詢時,應該給列名設置一個別名,便於處理結果:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="keyword">COUNT</span>(*) 別名 <span class="keyword">FROM</span> <表名>;</span><br></pre></td></tr></table></figure>
<p>除了 <strong>COUNT()</strong> 函數外,SQL 還提供了如下聚合函數:</p>
<table>
<thead>
<tr>
<th>函數</th>
<th align="center">說明</th>
</tr>
</thead>
<tbody><tr>
<td>SUM</td>
<td align="center">計算某一列的合計值,該列必須為數值類型</td>
</tr>
<tr>
<td>AVG</td>
<td align="center">計算某一列的平均值,該列必須為數值類型</td>
</tr>
<tr>
<td>MAX</td>
<td align="center">計算某一列的最大值</td>
</tr>
<tr>
<td>MIN</td>
<td align="center">計算某一列的最小值</td>
</tr>
</tbody></table>
<p>要特別註意:如果聚合查詢的 <strong>WHERE</strong> 條件沒有匹配到任何行,COUNT()會返回 <strong>0</strong>,而 SUM()、AVG()、MAX()和 MIN()會返回 <strong>NULL</strong>:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="keyword">COUNT</span>(*) <span class="keyword">FROM</span> students;</span><br><span class="line"><span class="keyword">SELECT</span> <span class="keyword">COUNT</span>(*) <span class="keyword">num</span> <span class="keyword">FROM</span> students;</span><br><span class="line"><span class="keyword">SELECT</span> <span class="keyword">COUNT</span>(*) boys <span class="keyword">FROM</span> students <span class="keyword">WHERE</span> gender = <span class="string">'M'</span>;</span><br><span class="line"><span class="keyword">SELECT</span> <span class="keyword">AVG</span>(score) average <span class="keyword">FROM</span> students <span class="keyword">WHERE</span> gender = <span class="string">'M'</span>;</span><br></pre></td></tr></table></figure>
<h4 id="分組聚合"><a href="#分組聚合" class="headerlink" title="分組聚合"></a>分組聚合</h4><p>對於聚合查詢,SQL 還提供了「分組聚合」的功能。</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 按屬性名分組</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="keyword">COUNT</span>(*) 別名 <span class="keyword">FROM</span> <表名> <span class="keyword">GROUP</span> <span class="keyword">BY</span> <屬性名>;</span><br></pre></td></tr></table></figure>
<p>執行該 SELECT 語句時,會把<strong>屬性名對應取值</strong>相同的列先分組,再分別計算,因此,得到了<strong>多行</strong>結果。</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="keyword">COUNT</span>(*) <span class="keyword">num</span> <span class="keyword">FROM</span> students <span class="keyword">GROUP</span> <span class="keyword">BY</span> class_id;</span><br><span class="line"><span class="keyword">SELECT</span> class_id, <span class="keyword">COUNT</span>(*) <span class="keyword">num</span> <span class="keyword">FROM</span> students <span class="keyword">GROUP</span> <span class="keyword">BY</span> class_id;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 統計各班的男生和女生人數</span></span><br><span class="line"><span class="keyword">SELECT</span> class_id, gender, <span class="keyword">COUNT</span>(*) <span class="keyword">num</span> <span class="keyword">FROM</span> students <span class="keyword">GROUP</span> <span class="keyword">BY</span> class_id, gender;</span><br></pre></td></tr></table></figure>
<h3 id="多表查詢"><a href="#多表查詢" class="headerlink" title="多表查詢"></a>多表查詢</h3><p>SELECT 查詢不但可以從一張表查詢數據,還可以從多張表同時查詢數據。查詢多張表的語法是:<strong>SELECT * FROM <表 1>, <表 2></strong>。</p>
<p>這種一次查詢兩個表的數據,查詢的結果也是一個二維表,它是<strong>表 1</strong>和<strong>表 2</strong>表的「乘積」,即<strong>表 1</strong>的每一行與<strong>表 2</strong>的每一行都兩兩拼在一起返回。結果集的列數是兩表的列數之和,行數是兩表的行數之積。</p>
<p>這種多表查詢又稱笛卡爾查詢,使用笛卡爾查詢時要非常小心,由於結果集是目標表的行數乘積,對兩個各自有 100 行記錄的表進行笛卡爾查詢將返回 1 萬條記錄,對兩個各自有 1 萬行記錄的表進行笛卡爾查詢將返回 1 億條記錄。</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span></span><br><span class="line"> 表<span class="number">1.</span>屬性<span class="number">1</span> 別名<span class="number">1</span>,</span><br><span class="line"> 表<span class="number">1.</span>屬性<span class="number">2</span>,</span><br><span class="line"> 表<span class="number">1.</span>屬性<span class="number">3</span>,</span><br><span class="line"> 表<span class="number">1.</span>屬性<span class="number">4</span>,</span><br><span class="line"> 表<span class="number">2.</span>屬性<span class="number">1</span> 別名<span class="number">2</span>,</span><br><span class="line"> 表<span class="number">2.</span>屬性<span class="number">2</span> 別名<span class="number">3</span></span><br><span class="line"><span class="keyword">FROM</span> 表<span class="number">1</span>, 表<span class="number">2</span>;</span><br></pre></td></tr></table></figure>
<p>註意,多表查詢時,要使用<strong>表名.列名</strong>這樣的方式來<strong>引用列</strong>和<strong>設置別名</strong>,這樣就避免了結果集的列名重復問題。但是,用<strong>表名.列名</strong>這種方式列舉兩個表的所有列實在是很麻煩,所以 SQL 還允許<strong>給表設置一個別名</strong>,讓投影查詢中引用起來稍微簡潔一點:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span></span><br><span class="line"> 表別名<span class="number">1.</span>屬性<span class="number">1</span> 別名<span class="number">1</span>,</span><br><span class="line"> 表別名<span class="number">1.</span>屬性<span class="number">2</span>,</span><br><span class="line"> 表別名<span class="number">1.</span>屬性<span class="number">3</span>,</span><br><span class="line"> 表別名<span class="number">1.</span>屬性<span class="number">4</span>,</span><br><span class="line"> 表別名<span class="number">2.</span>屬性<span class="number">1</span> 別名<span class="number">2</span>,</span><br><span class="line"> 表別名<span class="number">2.</span>屬性<span class="number">2</span> 別名<span class="number">3</span></span><br><span class="line"><span class="keyword">FROM</span> 表<span class="number">1</span> 表別名<span class="number">1</span>, 表<span class="number">2</span> 表別名<span class="number">2</span>;</span><br></pre></td></tr></table></figure>
<p>註意到 FROM 子句給表設置別名的語法是 <strong>FROM <表名 1> <別名 1>, <表名 2> <別名 2></strong>。</p>
<p>多表查詢也是可以添加 WHERE 條件的:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span></span><br><span class="line"> 表別名<span class="number">1.</span>屬性<span class="number">1</span> 別名<span class="number">1</span>,</span><br><span class="line"> 表別名<span class="number">1.</span>屬性<span class="number">2</span>,</span><br><span class="line"> 表別名<span class="number">1.</span>屬性<span class="number">3</span>,</span><br><span class="line"> 表別名<span class="number">1.</span>屬性<span class="number">4</span>,</span><br><span class="line"> 表別名<span class="number">2.</span>屬性<span class="number">1</span> 別名<span class="number">2</span>,</span><br><span class="line"> 表別名<span class="number">2.</span>屬性<span class="number">2</span> 別名<span class="number">3</span></span><br><span class="line"><span class="keyword">FROM</span> 表<span class="number">1</span> 表別名<span class="number">1</span>, 表<span class="number">2</span> 表別名<span class="number">2</span>;</span><br><span class="line">WHERE 表別名1.屬性1 = 'M' AND 表別名2.屬性1 = 1;</span><br></pre></td></tr></table></figure>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> * <span class="keyword">FROM</span> students, classes;</span><br></pre></td></tr></table></figure>
<p>這種一次查詢兩個表的數據,查詢的結果也是一個二維表,它是 <strong>students</strong> 表和 <strong>classes</strong> 表的「乘積」,即 <strong>students</strong> 表的每一行與 <strong>classes</strong> 表的每一行都兩兩拼在一起返回。結果集的列數是 <strong>students</strong> 表和 <strong>classes</strong> 表的列數之和,行數是 <strong>students</strong> 表和 <strong>classes</strong> 表的行數之積。</p>
<p>上述查詢的結果集有兩列 <strong>id</strong> 和兩列 <strong>name</strong>,兩列 <strong>id</strong> 是因為其中一列是 <strong>students</strong> 表的 <strong>id</strong>,而另一列是 <strong>classes</strong> 表的 <strong>id</strong>,但是在結果集中,不好區分。兩列 <strong>name</strong> 同理。</p>
<p>要解決這個問題,仍然可以利用投影查詢的「設置列的別名」來給兩個表各自的 <strong>id</strong> 和 <strong>name</strong> 列起別名:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span></span><br><span class="line"> students.id <span class="keyword">sid</span>,</span><br><span class="line"> students.name,</span><br><span class="line"> students.gender,</span><br><span class="line"> students.score,</span><br><span class="line"> classes.id cid,</span><br><span class="line"> classes.name cname</span><br><span class="line"><span class="keyword">FROM</span> students, classes;</span><br></pre></td></tr></table></figure>
<p>還可以使用別名:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span></span><br><span class="line"> s.id <span class="keyword">sid</span>,</span><br><span class="line"> s.name,</span><br><span class="line"> s.gender,</span><br><span class="line"> s.score,</span><br><span class="line"> c.id cid,</span><br><span class="line"> c.name cname</span><br><span class="line"><span class="keyword">FROM</span> students s, classes c;</span><br><span class="line"></span><br><span class="line"><span class="keyword">SELECT</span></span><br><span class="line"> s.id <span class="keyword">sid</span>,</span><br><span class="line"> s.name,</span><br><span class="line"> s.gender,</span><br><span class="line"> s.score,</span><br><span class="line"> c.id cid,</span><br><span class="line"> c.name cname</span><br><span class="line"><span class="keyword">FROM</span> students s, classes c</span><br><span class="line"><span class="keyword">WHERE</span> s.gender = <span class="string">'M'</span> <span class="keyword">AND</span> c.id = <span class="number">1</span>;</span><br></pre></td></tr></table></figure>
<h3 id="連接查詢"><a href="#連接查詢" class="headerlink" title="連接查詢"></a>連接查詢</h3><p>連接查詢是另一種類型的多表查詢。連接查詢對多個表進行 JOIN 運算,簡單地說,就是先確定一個主表作為結果集,然後,把其它表的行有選擇性地「連接」在主表結果集上。</p>
<p>例如,想要選出 <strong>students</strong> 表的所有學生信息,可以用一條簡單的 SELECT 語句完成:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> s.id, s.name, s.class_id, s.gender, s.score <span class="keyword">FROM</span> students s;</span><br></pre></td></tr></table></figure>
<p>但是,假設希望結果集同時包含所在班級的名稱,上面的結果集只有 <strong>class_id</strong> 列,缺少對應班級的 <strong>name</strong> 列。</p>
<p>現在問題來了,存放班級名稱的 <strong>name</strong> 列存儲在 <strong>classes</strong> 表中,只有根據 <strong>students</strong> 表的 <strong>class_id</strong>,找到 <strong>classes</strong> 表對應的行,再取出 <strong>name</strong> 列,就可以獲得班級名稱。</p>
<p>這時,連接查詢就派上了用場。先使用最常用的一種內連接 <strong>INNER JOIN</strong> 來實現:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> s.id, s.name, s.class_id, c.name class_name, s.gender, s.score</span><br><span class="line"><span class="keyword">FROM</span> students s</span><br><span class="line"><span class="keyword">INNER</span> <span class="keyword">JOIN</span> classes c</span><br><span class="line"><span class="keyword">ON</span> s.class_id = c.id;</span><br></pre></td></tr></table></figure>
<p>註意 INNER JOIN 查詢的寫法是:</p>
<ul>
<li>先確定主表,仍然使用 <strong>FROM <表 1></strong> 的語法;</li>
<li>再確定需要連接的表,使用 <strong>INNER JOIN <表 2></strong> 的語法;</li>
<li>然後確定連接條件,使用 <strong>ON <條件…></strong>,這裏的條件是 <strong>s.class_id = c.id</strong>,表示 <strong>students</strong> 表的 <strong>class_id</strong> 列與 <strong>classes</strong> 表的 <strong>id</strong> 列相同的行需要連接;</li>
<li>可選:加上 <strong>WHERE</strong> 子句、<strong>ORDER BY</strong> 等子句。</li>
</ul>
<h4 id="內連接(INNER-JOIN)"><a href="#內連接(INNER-JOIN)" class="headerlink" title="內連接(INNER JOIN)"></a>內連接(INNER JOIN)</h4><p>使用 <strong>INNER JOIN</strong> 只返回同時存在於兩張表的行數據。<strong>INNER JOIN</strong> 是最常用的一種 JOIN 查詢,它的語法是 <strong>SELECT … FROM <表 1> INNER JOIN <表 2> ON <條件…>;</strong>。</p>
<figure class="image-box">
<img src="insert.png" alt="" title="" class="">
<p></p>
</figure>
<h4 id="左外連接(LEFT-OUTER-JOIN)"><a href="#左外連接(LEFT-OUTER-JOIN)" class="headerlink" title="左外連接(LEFT OUTER JOIN)"></a>左外連接(LEFT OUTER JOIN)</h4><p>使用 <strong>LEFT OUTER JOIN</strong> 返回左表都存在的行。如果某一行僅在左表存在,那麽結果集就會以 NULL 填充剩下的字段。</p>
<figure class="image-box">
<img src="left.png" alt="" title="" class="">
<p></p>
</figure>
<h4 id="右外連接(RIGHT-OUTER-JOIN)"><a href="#右外連接(RIGHT-OUTER-JOIN)" class="headerlink" title="右外連接(RIGHT OUTER JOIN)"></a>右外連接(RIGHT OUTER JOIN)</h4><p>使用 <strong>RIGHT OUTER JOIN</strong> 返回右表都存在的行。如果某一行僅在右表存在,那麽結果集就會以 NULL 填充剩下的字段。</p>
<figure class="image-box">
<img src="right.png" alt="" title="" class="">
<p></p>
</figure>
<h4 id="全外連接(FULL-OUTER-JOIN)"><a href="#全外連接(FULL-OUTER-JOIN)" class="headerlink" title="全外連接(FULL OUTER JOIN)"></a>全外連接(FULL OUTER JOIN)</h4><p>使用 <strong>FULL OUTER JOIN</strong>,會把兩張表的所有記錄全部選擇出來,並且,自動把對方不存在的列填充為 NULL。</p>
<figure class="image-box">
<img src="full.png" alt="" title="" class="">
<p></p>
</figure>
<h2 id="修改數據"><a href="#修改數據" class="headerlink" title="修改數據"></a>修改數據</h2><p>關系數據庫的基本操作就是增刪改查,即 CRUD:Create、Retrieve、Update、Delete。而對於增、刪、改,對應的 SQL 語句分別是:</p>
<ul>
<li>INSERT:插入新記錄;</li>
<li>UPDATE:更新已有記錄;</li>
<li>DELETE:刪除已有記錄。</li>
</ul>
<h3 id="INSERT"><a href="#INSERT" class="headerlink" title="INSERT"></a>INSERT</h3><p>當需要向數據庫表中插入一條新記錄時,就必須使用 <strong>INSERT</strong> 語句。該語句的基本語法是:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">INSERT</span> <span class="keyword">INTO</span> <表名> (字段<span class="number">1</span>, 字段<span class="number">2</span>, ...) <span class="keyword">VALUES</span> (值<span class="number">1</span>, 值<span class="number">2</span>, ...);</span><br></pre></td></tr></table></figure>
<p>例如,向 <strong>students</strong> 表插入一條新記錄,先列舉出需要插入的字段名稱,然後在 <strong>VALUES</strong> 子句中依次寫出對應字段的值:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">INSERT</span> <span class="keyword">INTO</span> students (class_id, <span class="keyword">name</span>, gender, score) <span class="keyword">VALUES</span> (<span class="number">2</span>, <span class="string">'你猜'</span>, <span class="string">'M'</span>, <span class="number">80</span>);</span><br></pre></td></tr></table></figure>
<p>如果一個字段有默認值,那麽在 <strong>INSERT</strong> 語句中也可以不出現。要註意,字段順序不必和數據庫表的字段順序一致,但值的順序必須和字段順序一致。</p>
<p>還可以一次性添加多條記錄,只需要在 <strong>VALUES</strong> 子句中指定多個記錄值,每個記錄是由 <strong>(…)</strong> 包含的一組值:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">INSERT</span> <span class="keyword">INTO</span> students (class_id, <span class="keyword">name</span>, gender, score) <span class="keyword">VALUES</span></span><br><span class="line"> (<span class="number">1</span>, <span class="string">'大寶'</span>, <span class="string">'M'</span>, <span class="number">87</span>),</span><br><span class="line"> (<span class="number">2</span>, <span class="string">'二寶'</span>, <span class="string">'M'</span>, <span class="number">81</span>);</span><br></pre></td></tr></table></figure>
<h4 id="插入或替換"><a href="#插入或替換" class="headerlink" title="插入或替換"></a>插入或替換</h4><p>如果希望插入一條新記錄(INSERT),但如果記錄已經存在,就先刪除原記錄,再插入新記錄。此時,可以使用 <strong>REPLACE</strong> 語句,這樣就不必先查詢,再決定是否先刪除再插入:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">REPLACE</span> <span class="keyword">INTO</span> students (<span class="keyword">id</span>, class_id, <span class="keyword">name</span>, gender, score) <span class="keyword">VALUES</span> (<span class="number">1</span>, <span class="number">1</span>, <span class="string">'小明'</span>, <span class="string">'F'</span>, <span class="number">99</span>);</span><br></pre></td></tr></table></figure>
<p>若 id=1 的記錄不存在,<strong>REPLACE</strong> 語句將插入新記錄,否則,當前 id=1 的記錄將被刪除,然後再插入新記錄。</p>
<h4 id="插入或更新"><a href="#插入或更新" class="headerlink" title="插入或更新"></a>插入或更新</h4><p>如果希望插入一條新記錄(INSERT),但如果記錄已經存在,就更新該記錄,此時,可以使用 <strong>INSERT INTO … ON DUPLICATE KEY UPDATE …</strong> 語句:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">INSERT</span> <span class="keyword">INTO</span> students (<span class="keyword">id</span>, class_id, <span class="keyword">name</span>, gender, score) <span class="keyword">VALUES</span> (<span class="number">1</span>, <span class="number">1</span>, <span class="string">'小明'</span>, <span class="string">'F'</span>, <span class="number">99</span>) <span class="keyword">ON</span> <span class="keyword">DUPLICATE</span> <span class="keyword">KEY</span> <span class="keyword">UPDATE</span> <span class="keyword">name</span>=<span class="string">'小明'</span>, gender=<span class="string">'F'</span>, score=<span class="number">99</span>;</span><br></pre></td></tr></table></figure>
<p>若 id=1 的記錄不存在,<strong>INSERT</strong> 語句將插入新記錄,否則,當前 id=1 的記錄將被更新,更新的字段由 <strong>UPDATE</strong> 指定。</p>
<h4 id="插入或忽略"><a href="#插入或忽略" class="headerlink" title="插入或忽略"></a>插入或忽略</h4><p>如果希望插入一條新記錄(INSERT),但如果記錄已經存在,就啥事也不幹直接忽略,此時,可以使用 <strong>INSERT IGNORE INTO …</strong> 語句:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">INSERT</span> <span class="keyword">IGNORE</span> <span class="keyword">INTO</span> students (<span class="keyword">id</span>, class_id, <span class="keyword">name</span>, gender, score) <span class="keyword">VALUES</span> (<span class="number">1</span>, <span class="number">1</span>, <span class="string">'小明'</span>, <span class="string">'F'</span>, <span class="number">99</span>);</span><br></pre></td></tr></table></figure>
<p>若 id=1 的記錄不存在,<strong>INSERT</strong> 語句將插入新記錄,否則,不執行任何操作。</p>
<h4 id="寫入查詢結果集"><a href="#寫入查詢結果集" class="headerlink" title="寫入查詢結果集"></a>寫入查詢結果集</h4><p>如果查詢結果集需要寫入到表中,可以結合 <strong>INSERT</strong> 和 <strong>SELECT</strong>,將 <strong>SELECT</strong> 語句的結果集直接插入到指定表中。</p>
<p>例如,創建一個統計成績的表 <strong>statistics</strong>,記錄各班的平均成績:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> <span class="keyword">TABLE</span> <span class="keyword">statistics</span> (</span><br><span class="line"> <span class="keyword">id</span> <span class="built_in">BIGINT</span> <span class="keyword">NOT</span> <span class="literal">NULL</span> AUTO_INCREMENT,</span><br><span class="line"> class_id <span class="built_in">BIGINT</span> <span class="keyword">NOT</span> <span class="literal">NULL</span>,</span><br><span class="line"> average <span class="keyword">DOUBLE</span> <span class="keyword">NOT</span> <span class="literal">NULL</span>,</span><br><span class="line"> PRIMARY <span class="keyword">KEY</span> (<span class="keyword">id</span>)</span><br><span class="line">);</span><br></pre></td></tr></table></figure>
<p>然後就可以用一條語句寫入各班的平均成績:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">INSERT</span> <span class="keyword">INTO</span> <span class="keyword">statistics</span> (class_id, average) <span class="keyword">SELECT</span> class_id, <span class="keyword">AVG</span>(score) <span class="keyword">FROM</span> students <span class="keyword">GROUP</span> <span class="keyword">BY</span> class_id;</span><br></pre></td></tr></table></figure>
<h3 id="UPDATE"><a href="#UPDATE" class="headerlink" title="UPDATE"></a>UPDATE</h3><p>如果要更新數據庫表中的記錄,就必須使用 <strong>UPDATE</strong> 語句。該語句的基本語法是:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">UPDATE</span> <表名> <span class="keyword">SET</span> 字段<span class="number">1</span>=值<span class="number">1</span>, 字段<span class="number">2</span>=值<span class="number">2</span>, ... <span class="keyword">WHERE</span> ...;</span><br></pre></td></tr></table></figure>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">UPDATE</span> students <span class="keyword">SET</span> <span class="keyword">name</span>=<span class="string">'你猜'</span>, score=<span class="number">66</span> <span class="keyword">WHERE</span> <span class="keyword">id</span>=<span class="number">1</span>;</span><br></pre></td></tr></table></figure>
<p>在 <strong>UPDATE</strong> 語句中,更新字段時可以使用表達式。例如,把所有 80 分以下的同學的成績加 10 分:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">UPDATE</span> students <span class="keyword">SET</span> score=score+<span class="number">10</span> <span class="keyword">WHERE</span> score<<span class="number">80</span>;</span><br></pre></td></tr></table></figure>
<p>UPDATE 語句可以沒有 <strong>WHERE</strong> 條件,例如:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">UPDATE</span> students <span class="keyword">SET</span> score=<span class="number">60</span>;</span><br></pre></td></tr></table></figure>
<p>這時,整個表的所有記錄都會被更新。</p>
<h3 id="DELETE"><a href="#DELETE" class="headerlink" title="DELETE"></a>DELETE</h3><p>如果要刪除數據庫表中的記錄,可以使用 <strong>DELETE</strong> 語句。該語句的基本語法是:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">DELETE</span> <span class="keyword">FROM</span> <表名> <span class="keyword">WHERE</span> ...;</span><br></pre></td></tr></table></figure>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">DELETE</span> <span class="keyword">FROM</span> students <span class="keyword">WHERE</span> <span class="keyword">id</span>=<span class="number">1</span>;</span><br><span class="line"><span class="keyword">DELETE</span> <span class="keyword">FROM</span> students <span class="keyword">WHERE</span> <span class="keyword">id</span>>=<span class="number">5</span> <span class="keyword">AND</span> <span class="keyword">id</span><=<span class="number">7</span>;</span><br></pre></td></tr></table></figure>
<h2 id="事務"><a href="#事務" class="headerlink" title="事務"></a>事務</h2><p>在執行 SQL 語句的時候,某些業務要求,一系列操作必須全部執行,而不能僅執行一部分。數據庫事務可以確保該事務範圍內的所有操作都可以全部成功或者全部失敗。如果事務失敗,那麽效果就和沒有執行這些 SQL 一樣,不會對數據庫數據有任何改動。</p>
<p>可見,數據庫事務具有這 4 個特性:</p>
<ul>
<li>Atomic,原子性,將所有 SQL 作為原子工作單元執行,要麽全部執行,要麽全部不執行;</li>
<li>Consistent,一致性,事務完成後,所有數據的狀態都是一致的,比如轉賬, A 賬戶只要減去了 100,B 賬戶則必定加上了 100;</li>
<li>Isolation,隔離性,如果有多個事務並發執行,每個事務作出的修改必須與其它事務隔離;</li>
<li>Duration,持久性,即事務完成後,對數據庫數據的修改被持久化存儲。</li>
</ul>
<p>對於單條 SQL 語句,數據庫系統自動將其作為一個事務執行,這種事務被稱為隱式事務。</p>
<p>要手動把多條 SQL 語句作為一個事務執行,使用 <strong>BEGIN</strong> 開啟一個事務,使用 <strong>COMMIT</strong> 提交一個事務,這種事務被稱為顯式事務,例如,把上述的轉賬操作作為一個顯式事務:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">BEGIN</span>;</span><br><span class="line"><span class="keyword">UPDATE</span> accounts <span class="keyword">SET</span> balance = balance - <span class="number">100</span> <span class="keyword">WHERE</span> <span class="keyword">id</span> = <span class="number">1</span>;</span><br><span class="line"><span class="keyword">UPDATE</span> accounts <span class="keyword">SET</span> balance = balance + <span class="number">100</span> <span class="keyword">WHERE</span> <span class="keyword">id</span> = <span class="number">2</span>;</span><br><span class="line"><span class="keyword">COMMIT</span>;</span><br></pre></td></tr></table></figure>
<p>很顯然多條 SQL 語句要想作為一個事務執行,就必須使用顯式事務。<strong>COMMIT</strong> 是指提交事務,即試圖把事務內的所有 SQL 所做的修改永久保存。如果 <strong>COMMIT</strong> 語句執行失敗了,整個事務也會失敗。</p>
<p>有些時候,希望主動讓事務失敗,這時,可以用 <strong>ROLLBACK</strong> 回滾事務,整個事務會失敗:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">BEGIN</span>;</span><br><span class="line"><span class="keyword">UPDATE</span> accounts <span class="keyword">SET</span> balance = balance - <span class="number">100</span> <span class="keyword">WHERE</span> <span class="keyword">id</span> = <span class="number">1</span>;</span><br><span class="line"><span class="keyword">UPDATE</span> accounts <span class="keyword">SET</span> balance = balance + <span class="number">100</span> <span class="keyword">WHERE</span> <span class="keyword">id</span> = <span class="number">2</span>;</span><br><span class="line"><span class="keyword">ROLLBACK</span>;</span><br></pre></td></tr></table></figure>
<p>數據庫事務是由數據庫系統保證的,開發者只需要根據業務邏輯使用它就可以。</p>
<h3 id="隔離級別"><a href="#隔離級別" class="headerlink" title="隔離級別"></a>隔離級別</h3><p>對於兩個並發執行的事務,如果涉及到操作同一條記錄的時候,可能會發生問題。因為並發操作會帶來數據的不一致性,包括臟讀、不可重復讀、幻讀等。數據庫系統提供了隔離級別來讓開發者有針對性地選擇事務的隔離級別,避免數據不一致的問題。</p>
<p>SQL 標準定義了 4 種隔離級別,分別對應可能出現的數據不一致的情況:</p>
<h4 id="Read-Uncommitted"><a href="#Read-Uncommitted" class="headerlink" title="Read Uncommitted"></a>Read Uncommitted</h4><p><strong>Read Uncommitted</strong> 是隔離級別最低的一種事務級別。在這種隔離級別下,一個事務會讀到另一個事務更新後但未提交的數據,如果另一個事務回滾,那麽當前事務讀到的數據就是臟數據,這就是臟讀(Dirty Read)。</p>
<h4 id="Read-Committed"><a href="#Read-Committed" class="headerlink" title="Read Committed"></a>Read Committed</h4><p>在 <strong>Read Committed</strong> 隔離級別下,一個事務可能會遇到不可重復讀(Non Repeatable Read)的問題。</p>
<p>不可重復讀是指,在一個事務內,多次讀同一數據,在這個事務還沒有結束時,如果另一個事務恰好修改了這個數據,那麽,在第一個事務中,兩次讀取的數據就可能不一致。</p>
<h4 id="Repeatable-Read"><a href="#Repeatable-Read" class="headerlink" title="Repeatable Read"></a>Repeatable Read</h4><p>在 <strong>Repeatable Read</strong> 隔離級別下,一個事務可能會遇到幻讀(Phantom Read)的問題。</p>
<p>幻讀是指,在一個事務中,第一次查詢某條記錄,發現沒有,但是,當試圖更新這條不存在的記錄時,竟然能成功,並且,再次讀取同一條記錄,它就神奇地出現了。</p>
<h4 id="Serializable"><a href="#Serializable" class="headerlink" title="Serializable"></a>Serializable</h4><p><strong>Serializable</strong> 是最嚴格的隔離級別。在 <strong>Serializable</strong> 隔離級別下,所有事務按照次序依次執行,因此,臟讀、不可重復讀、幻讀都不會出現。</p>
<p>雖然 <strong>Serializable</strong> 隔離級別下的事務具有最高的安全性,但是,由於事務是串行執行,所以效率會大大下降,應用程序的性能會急劇降低。如果沒有特別重要的情景,一般都不會使用 Serializable 隔離級別。</p>
<p>(以上內容摘抄於網絡,僅作為個人學習筆記,不適合作為學習教程。)</p>
]]></content>
<categories>
<category> 後端 </category>
<category> 數據庫 </category>
</categories>
<tags>
<tag> 數據庫 </tag>
<tag> MySQL </tag>
</tags>
</entry>
<entry>
<title><![CDATA[使用 Node 和 OAuth2.0 構建一個簡單的 REST API]]></title>
<url>/2018/08/22/201802200830/</url>
<content type="html"><![CDATA[<p>JavaScript 在網絡上無處不在。基本上每個網頁都或多或少包含一些 JavaScript 代碼,即使沒有,你的瀏覽器或許也裝了一些拓展附件,這些拓展附件能夠將一部分 JavaScript 代碼註入網頁中。2018 年依舊如此。</p>
<p>JavaScript 也可以在瀏覽器之外使用,從托管網絡服務器到控製 RC 汽車或者運行一個成熟的操作系統。有些時候你想要兩臺服務器互相通信,無論是本地網絡還是互聯網上。</p>
<p>今天,我會向你展示怎樣使用 node.js 創建一個 REST API,同時使用 OAuth2.0 維護它,以防止未經授權的請求。REST API 遍布整個網絡,但是如果沒有合適的工具那麽就需要大量的模板代碼。我會向你展示怎樣使用兩個神奇的工具,這兩個工具能使工作更輕松,包括用 OKta 實現客戶端憑證流(翻譯「Client Credentials Flow」這個詞組時自己認慫了,只好用百度翻譯了 😂。),它能不使用用戶上下文而將兩臺機器安全地連接在一起。</p>
<h2 id="構建一個-REST-類型的-Node-API-服務器"><a href="#構建一個-REST-類型的-Node-API-服務器" class="headerlink" title="構建一個 REST 類型的 Node API 服務器"></a>構建一個 REST 類型的 Node API 服務器</h2><p>通過使用 <a href="https://expressjs.com/" target="_blank" rel="noopener" title="Express JavaScript library">Express JavaScript library</a> 能夠讓創建 Node 網絡服務器更加簡單。創建一個文件夾用來包含你的服務器項目。</p>
<blockquote>
<p>$ mkdir rest-api</p>
</blockquote>
<p>Node 使用 <strong>package.json</strong> 文件來管理依賴項並且規範(感覺「define」翻譯成「規範」更合適。)你的項目。使用 <strong>npm init</strong> 命令創建一個,(執行這個命令後)會問你一些問題,這些問題是用來幫你初始化這個項目。截止到現在,你能夠使用標準的 JS 來強製規範你的編碼,並且將其用作測試。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line">$ <span class="built_in">cd</span> rest-api</span><br><span class="line"></span><br><span class="line">$ npm init</span><br><span class="line">This utility will walk you through creating a package.json file.</span><br><span class="line">It only covers the most common items, and tries to guess sensible defaults.</span><br><span class="line"></span><br><span class="line">See `npm <span class="built_in">help</span> json` <span class="keyword">for</span> definitive documentation on these fields</span><br><span class="line">and exactly what they <span class="keyword">do</span>.</span><br><span class="line"></span><br><span class="line">Use `npm install <pkg>` afterwards to install a package and</span><br><span class="line">save it as a dependency <span class="keyword">in</span> the package.json file.</span><br><span class="line"></span><br><span class="line">Press ^C at any time to quit.</span><br><span class="line">package name: (rest-api)</span><br><span class="line">version: (1.0.0)</span><br><span class="line">description: A parts catalog</span><br><span class="line">entry point: (index.js)</span><br><span class="line"><span class="built_in">test</span> <span class="built_in">command</span>: standard</span><br><span class="line">git repository:</span><br><span class="line">keywords:</span><br><span class="line">author:</span><br><span class="line">license: (ISC)</span><br><span class="line">About to write to /Users/Braden/code/rest-api/package.json:</span><br><span class="line"></span><br><span class="line">{</span><br><span class="line"> <span class="string">"name"</span>: <span class="string">"rest-api"</span>,</span><br><span class="line"> <span class="string">"version"</span>: <span class="string">"1.0.0"</span>,</span><br><span class="line"> <span class="string">"description"</span>: <span class="string">"A parts catalog"</span>,</span><br><span class="line"> <span class="string">"main"</span>: <span class="string">"index.js"</span>,</span><br><span class="line"> <span class="string">"scripts"</span>: {</span><br><span class="line"> <span class="string">"test"</span>: <span class="string">"standard"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="string">"author"</span>: <span class="string">""</span>,</span><br><span class="line"> <span class="string">"license"</span>: <span class="string">"ISC"</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">Is this OK? (yes)</span><br></pre></td></tr></table></figure>
<p>默認的(文件)入口是 <strong>index.js</strong>,所以你應該用這個名稱創建一個新文件。下面的代碼給你提供了一個相當基礎的服務器(案例),它只是監聽了本機(127.0.0.1)3000 端口,但是沒有做任何其他事情。</p>
<p><strong>index.js 文件內容:</strong></p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> express = <span class="built_in">require</span>(<span class="string">'express'</span>)</span><br><span class="line"><span class="keyword">const</span> bodyParser = <span class="built_in">require</span>(<span class="string">'body-parser'</span>)</span><br><span class="line"><span class="keyword">const</span> { promisify } = <span class="built_in">require</span>(<span class="string">'util'</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> app = express()</span><br><span class="line">app.use(bodyParser.json())</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> startServer = <span class="keyword">async</span> () => {</span><br><span class="line"> <span class="keyword">const</span> port = process.env.SERVER_PORT || <span class="number">3000</span></span><br><span class="line"> <span class="keyword">await</span> promisify(app.listen).bind(app)(port)</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">`Listening on port <span class="subst">${port}</span>`</span>)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">startServer()</span><br></pre></td></tr></table></figure>
<p>(分號是自己添加的,絕大多數情況下沒有分號程序不會報錯,但有一些特殊情況沒了會報錯,還是養成添加分號的習慣吧。)</p>
<p><strong>util</strong> 模塊中的 <strong>promisify</strong> 功能讓你傳入一個具有(傳統)回調功能的函數,而返回給你一個 Promise 式的回調函數,Promise 是異步處理的新標準。這也同樣讓我們使用最近比較新的 <strong>async/await</strong> 語法,能夠使代碼更優雅。</p>
<p>為了使其運行起來,你需要裝上你在文件開頭 <strong>require</strong> 的依賴包。通過使用 <strong>npm intall</strong> 安裝他們。依賴信息將會自動保存到你的 <strong>package.json</strong> 文件,並將依賴包安裝到本地 <strong>node_modules</strong> 文件夾中。</p>
<p><strong>註意:</strong>你不應該將 <strong>node_modules</strong>(文件夾及其內容)提交到資源管理器中,因為它會很快變大(占據空間),(再者)<strong>package.json</strong> 文件(已經)保存了你使用的每一個依賴包的確切版本,如果你在另一臺計算機上安裝它,會得到相同的代碼。</p>
<blockquote>
<p>$ npm install <a href="mailto:express@4.16.3">express@4.16.3</a> util@0.11.0</p>
</blockquote>
<p>對於一些快速更新的依賴包(我猜是這個意思。),安裝 <strong>standard</strong> 作為 <strong>dev</strong> 依賴項,然後運行起來以確保正常。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">$ npm install --save-dev standard@11.0.1</span><br><span class="line">$ npm <span class="built_in">test</span></span><br><span class="line"></span><br><span class="line">> rest-api@1.0.0 <span class="built_in">test</span> /Users/bmk/code/okta/apps/rest-api</span><br><span class="line">> standard</span><br></pre></td></tr></table></figure>
<p>如果一切正常,你不應該看到在 <strong>> standard</strong> 這一行有任何輸出內容。如果報錯,可能是這個樣子:</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">$ npm <span class="built_in">test</span></span><br><span class="line"></span><br><span class="line">> rest-api@1.0.0 <span class="built_in">test</span> /Users/bmk/code/okta/apps/rest-api</span><br><span class="line">> standard</span><br><span class="line"></span><br><span class="line">standard: Use JavaScript Standard Style (https://standardjs.com)</span><br><span class="line">standard: Run `standard --fix` to automatically fix some problems.</span><br><span class="line"> /Users/Braden/code/rest-api/index.js:3:7: Expected consistent spacing</span><br><span class="line"> /Users/Braden/code/rest-api/index.js:3:18: Unexpected trailing comma.</span><br><span class="line"> /Users/Braden/code/rest-api/index.js:3:18: A space is required after <span class="string">','</span>.</span><br><span class="line"> /Users/Braden/code/rest-api/index.js:3:38: Extra semicolon.</span><br><span class="line">npm ERR! Test failed. See above <span class="keyword">for</span> more details.</span><br></pre></td></tr></table></figure>
<p>既然你的代碼已經準備好而且依賴包已經安裝好,你可以通過 <strong>node .</strong>(命令)運行你的服務。(<strong>.</strong> 指的是查看當前文件夾,檢查 <strong>package.json</strong> 文件找到此目錄中要使用的主文件是 <strong>index.js</strong>。):</p>
<blockquote>
<p>$ node .</p>
</blockquote>
<p>為了測試服務是否正在運行,你可以使用 <strong>curl</strong> 命令。(由於)沒有 ENDPOINTS(「ENDPOINTS」只可意會不可言傳,不知如何翻譯。),所以會返回一個錯誤:</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">$ curl localhost:3000 -i HTTP/1.1 404 Not Found X-Powered-By: Express</span><br><span class="line">Content-Security-Policy: default-src 'self' X-Content-Type-Options: nosniff</span><br><span class="line">Content-Type: text/html; charset=utf-8 Content-Length: 139 Date: Thu, 16 Aug</span><br><span class="line">2018 01:34:53 GMT Connection: keep-alive</span><br><span class="line"></span><br><span class="line"><span class="meta"><!DOCTYPE <span class="meta-keyword">html</span>></span></span><br><span class="line"><span class="tag"><<span class="name">html</span> <span class="attr">lang</span>=<span class="string">"en"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">head</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">"utf-8"</span> /></span></span><br><span class="line"> <span class="tag"><<span class="name">title</span>></span>Error<span class="tag"></<span class="name">title</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">head</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">body</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">pre</span>></span>Cannot GET /<span class="tag"></<span class="name">pre</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">body</span>></span></span><br><span class="line"><span class="tag"></<span class="name">html</span>></span></span><br></pre></td></tr></table></figure>
<p>盡管報了一個錯誤,但依舊是好消息。你還沒有設置任何 ENDPOINTS,所以 Express 返回一個 404 錯誤。如果你的服務根本沒有運行,則會得到如下錯誤:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ curl localhost:3000 -i</span><br><span class="line">curl: (7) Failed to connect to localhost port 3000: Connection refused</span><br></pre></td></tr></table></figure>
<h2 id="使用-Node、Express、Sequelize-和-Epilogue-構建-REST-類型的-API"><a href="#使用-Node、Express、Sequelize-和-Epilogue-構建-REST-類型的-API" class="headerlink" title="使用 Node、Express、Sequelize 和 Epilogue 構建 REST 類型的 API"></a>使用 Node、Express、Sequelize 和 Epilogue 構建 REST 類型的 API</h2><p>既然你已經有了一個能夠工作的 Express 服務器,那麽就可以添加一個 REST API。事實上這比你想象的容易得多。使用 <a href="http://docs.sequelizejs.com/" target="_blank" rel="noopener" title="Sequelize">Sequelize</a> 規範數據庫模式是我見過的最簡單的方法,而使用 <a href="https://github.com/dchester/epilogue" target="_blank" rel="noopener" title="Epilogue">Epilogue</a> 創建 REST API ENDPOINTS 幾乎達到了零樣板。</p>
<p>你需要添加這些依賴包到你的項目中。<strong>Sequelize</strong> 需要知道怎樣與數據庫進行通信。現在,使用 <strong>SQLite</strong>,因為它能讓我們快速啟動和運行。</p>
<blockquote>
<p>$ npm install <a href="mailto:sequelize@4.38.0">sequelize@4.38.0</a> epilogue@0.7.1 <a href="mailto:sqlite3@4.0.2">sqlite3@4.0.2</a></p>
</blockquote>
<p>用下面的代碼創建一個 <strong>database.js</strong> 文件。我會接下來更詳細地解釋每一部分。</p>
<p><strong>database.js 文件內容:</strong></p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> Sequelize = <span class="built_in">require</span>(<span class="string">'sequelize'</span>)</span><br><span class="line"><span class="keyword">const</span> epilogue = <span class="built_in">require</span>(<span class="string">'epilogue'</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> database = <span class="keyword">new</span> Sequelize({</span><br><span class="line"> dialect: <span class="string">'sqlite'</span>,</span><br><span class="line"> storage: <span class="string">'./test.sqlite'</span>,</span><br><span class="line"> operatorsAliases: <span class="literal">false</span>,</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> Part = database.define(<span class="string">'parts'</span>, {</span><br><span class="line"> partNumber: Sequelize.STRING,</span><br><span class="line"> modelNumber: Sequelize.STRING,</span><br><span class="line"> name: Sequelize.STRING,</span><br><span class="line"> description: Sequelize.TEXT,</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> initializeDatabase = <span class="keyword">async</span> (app) => {</span><br><span class="line"> epilogue.initialize({ app, <span class="attr">sequelize</span>: database })</span><br><span class="line"></span><br><span class="line"> epilogue.resource({</span><br><span class="line"> model: Part,</span><br><span class="line"> endpoints: [<span class="string">'/parts'</span>, <span class="string">'/parts/:id'</span>],</span><br><span class="line"> })</span><br><span class="line"></span><br><span class="line"> <span class="keyword">await</span> database.sync()</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = initializeDatabase</span><br></pre></td></tr></table></figure>
<p>現在你只需要導入這個文件到你的主應用程序並運行初始化功能。在你的 <strong>index.js</strong> 文件中添加如下內容。</p>
<p><strong>index.js 文件內容:</strong></p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">@@ <span class="number">-2</span>,<span class="number">10</span> +<span class="number">2</span>,<span class="number">14</span> @@ <span class="keyword">const</span> express = <span class="built_in">require</span>(<span class="string">'express'</span>);</span><br><span class="line"><span class="keyword">const</span> bodyParser = <span class="built_in">require</span>(<span class="string">'body-parser'</span>);</span><br><span class="line"><span class="keyword">const</span> { promisify } = <span class="built_in">require</span>(<span class="string">'util'</span>);</span><br><span class="line"></span><br><span class="line">+<span class="keyword">const</span> initializeDatabase = <span class="built_in">require</span>(<span class="string">'./database'</span>);</span><br><span class="line">+</span><br><span class="line"><span class="keyword">const</span> app = express();</span><br><span class="line">app.use(bodyParser.json());</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> startServer = <span class="keyword">async</span> () => {</span><br><span class="line">+ <span class="keyword">await</span> initializeDatabase(app);</span><br><span class="line">+</span><br><span class="line"> <span class="keyword">const</span> port = process.env.SERVER_PORT || <span class="number">3000</span>;</span><br><span class="line"> <span class="keyword">await</span> promisify(app.listen).bind(app)(port);</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">`Listening on port <span class="subst">${port}</span>`</span>);</span><br><span class="line"> ...</span><br></pre></td></tr></table></figure>
<p>你現在能測試語法錯誤,如果一切正常運行這個程序:</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">$ npm <span class="built_in">test</span> && node .</span><br><span class="line"></span><br><span class="line">> rest-api@1.0.0 <span class="built_in">test</span> /Users/bmk/code/okta/apps/rest-api</span><br><span class="line">> standard</span><br><span class="line"></span><br><span class="line">Executing (default): CREATE TABLE IF NOT EXISTS `parts` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `partNumber` VARCHAR(255), `modelNu</span><br><span class="line">mber` VARCHAR(255), `name` VARCHAR(255), `description` TEXT, `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL);</span><br><span class="line">Executing (default): PRAGMA INDEX_LIST(`parts`)</span><br><span class="line">Listening on port 3000</span><br></pre></td></tr></table></figure>
<p>在另一個終端中,你能測試這是否真實有效(我使用 <a href="https://github.com/trentm/json" target="_blank" rel="noopener" title="json CLI">json CLI</a> 來格式化 JSON 響應,用 <strong>npm install –global json</strong> 進行全局安裝。):</p>
<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">$ curl localhost:3000/parts</span><br><span class="line">[]</span><br><span class="line"></span><br><span class="line">$ curl localhost:3000/parts -X POST -d '{</span><br><span class="line"> "partNumber": "abc-123",</span><br><span class="line"> "modelNumber": "xyz-789",</span><br><span class="line"> "name": "Alphabet Soup",</span><br><span class="line"> "description": "Soup with letters and numbers in it"</span><br><span class="line">}' -H 'content-type: application/json' -s0 | json</span><br><span class="line">{</span><br><span class="line"> <span class="attr">"id"</span>: <span class="number">1</span>,</span><br><span class="line"> <span class="attr">"partNumber"</span>: <span class="string">"abc-123"</span>,</span><br><span class="line"> <span class="attr">"modelNumber"</span>: <span class="string">"xyz-789"</span>,</span><br><span class="line"> <span class="attr">"name"</span>: <span class="string">"Alphabet Soup"</span>,</span><br><span class="line"> <span class="attr">"description"</span>: <span class="string">"Soup with letters and numbers in it"</span>,</span><br><span class="line"> <span class="attr">"updatedAt"</span>: <span class="string">"2018-08-16T02:22:09.446Z"</span>,</span><br><span class="line"> <span class="attr">"createdAt"</span>: <span class="string">"2018-08-16T02:22:09.446Z"</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">$ curl localhost:3000/parts -s0 | json</span><br><span class="line">[</span><br><span class="line"> {</span><br><span class="line"> <span class="attr">"id"</span>: <span class="number">1</span>,</span><br><span class="line"> <span class="attr">"partNumber"</span>: <span class="string">"abc-123"</span>,</span><br><span class="line"> <span class="attr">"modelNumber"</span>: <span class="string">"xyz-789"</span>,</span><br><span class="line"> <span class="attr">"name"</span>: <span class="string">"Alphabet Soup"</span>,</span><br><span class="line"> <span class="attr">"description"</span>: <span class="string">"Soup with letters and numbers in it"</span>,</span><br><span class="line"> <span class="attr">"createdAt"</span>: <span class="string">"2018-08-16T02:22:09.446Z"</span>,</span><br><span class="line"> <span class="attr">"updatedAt"</span>: <span class="string">"2018-08-16T02:22:09.446Z"</span></span><br><span class="line"> }</span><br><span class="line">]</span><br></pre></td></tr></table></figure>
<h3 id="Node-API-工作原理"><a href="#Node-API-工作原理" class="headerlink" title="Node API 工作原理"></a>Node API 工作原理</h3><p>如果你著急看後面的可以跳過本部分,但我保證你會有收獲。</p>
<p><strong>Sequelize</strong> 函數會創建一個數據庫。這是你配置詳細信息的地方,例如你要用 SQL 的什麽規範。現在,使用 SQLite 快速啟動和運行。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> database = <span class="keyword">new</span> Sequelize({</span><br><span class="line"> dialect: <span class="string">'sqlite'</span>,</span><br><span class="line"> storage: <span class="string">'./test.sqlite'</span>,</span><br><span class="line"> operatorsAliases: <span class="literal">false</span>,</span><br><span class="line">})</span><br></pre></td></tr></table></figure>
<p>一旦你創建了數據庫,就可以使用 <strong>database.define</strong> 文件為每一個表定義模式。創建一個名為 <strong>parts</strong> 的表,其中包含了一些有用的字段以跟蹤 PARTS。默認情況下,當你創建或者更新一個 row 時,Sequelize 會自動創建更新 <strong>id</strong>、<strong>createdAt</strong> 和 <strong>updatedAt</strong> 字段。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> Part = database.define(<span class="string">'parts'</span>, {</span><br><span class="line"> partNumber: Sequelize.STRING,</span><br><span class="line"> modelNumber: Sequelize.STRING,</span><br><span class="line"> name: Sequelize.STRING,</span><br><span class="line"> description: Sequelize.TEXT,</span><br><span class="line">})</span><br></pre></td></tr></table></figure>
<p>Epilogue 需要連接 Express 中的 <strong>app</strong> 變量才能添加 ENDPOINTS。然而,<strong>app</strong> 變量是在另外一個文件定義的。解決這個問題的一個方法是 export(導出)一個函數,這個函數包含 app 變量並對其進行處理。在另一個(使用 app 變量的)文件中,你可以 import(導入)這個腳本,像 <strong>initializeDatabase(app)</strong> 一樣運行它。</p>
<p>Epilogue 需要用 <strong>app</strong> 和 <strong>database</strong> 進行初始化。然後定義你想要使用的 REST ENDPOINTS。<strong>resource</strong> 函數會包括 <strong>GET</strong>、<strong>POST</strong>、<strong>PUT</strong>、<strong>DELETE</strong> 功能的 ENDPOINTS,主要用來自動執行的。</p>
<p>為了準確創建數據庫,你需要運行 <strong>database.sync()</strong>,這將返回一個 Promise 對象。在啟動服務器之前,你需要等待它結束。</p>
<p><strong>module.exports</strong> 命令表示 <strong>initializeDatabase</strong> 函數可以被其他文件導入。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> initializeDatabase = <span class="keyword">async</span> (app) => {</span><br><span class="line"> epilogue.initialize({ app, <span class="attr">sequelize</span>: database })</span><br><span class="line"></span><br><span class="line"> epilogue.resource({</span><br><span class="line"> model: Part,</span><br><span class="line"> endpoints: [<span class="string">'/parts'</span>, <span class="string">'/parts/:id'</span>],</span><br><span class="line"> })</span><br><span class="line"></span><br><span class="line"> <span class="keyword">await</span> database.sync()</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = initializeDatabase</span><br></pre></td></tr></table></figure>
<h2 id="使用-OAuth2-0-來保護你的-Node-Express-REST-API"><a href="#使用-OAuth2-0-來保護你的-Node-Express-REST-API" class="headerlink" title="使用 OAuth2.0 來保護你的 Node + Express REST API"></a>使用 OAuth2.0 來保護你的 Node + Express REST API</h2><p>現在你已經啟動了一個 REST API 並且運行起來,假設你想要一個特定的應用程序從遠程位置使用它。如果你把它按原樣放在互聯網上,那麽任何人都可以隨意添加、修改或者刪除部件。</p>
<p>為了避免這種情況,你可以使用 OAuth2.0 客戶端憑據流。這是一種讓兩個服務器相互通信而不需要用戶上下文的方法。兩臺服務器必須達成協議才能使用第三方授權服務器。假設有兩個服務器,A 和 B,以及一個授權服務器,服務器 A 托管 REST API,服務器 B 希望訪問這個 API。</p>
<ul>
<li>服務器 B 向授權服務器發送一個密鑰以驗證身份並且同時請求一個臨時令牌(temporary token)。</li>
<li>然後服務器 B 像往常一樣使用(consume)REST API,但隨著請求一起發送令牌。</li>
<li>服務器 A 向授權服務器請求一些可用於驗證(verify)令牌的元數據(metadata)。</li>
<li>服務器 A 驗證服務器 B 的請求。<ul>
<li>如果有效(valid),將會發送成功的響應。</li>
<li>如果令牌無效(invalid),則發送錯誤信息,並且不會泄露(leaked)敏感信息(sensitive information)。</li>
</ul>
</li>
</ul>
<h3 id="創建一個授權服務器"><a href="#創建一個授權服務器" class="headerlink" title="創建一個授權服務器"></a>創建一個授權服務器</h3><p>這裏是開始使用 Okta 的地方。Okta 能夠充當一個授權服務器用來保護你的數據。你可能會問自己「為什麽用 Okta?」。那是因為,使用 Okta(不僅)構建一個 REST 應用程序相當炫酷,(而且)構建一個安全的 REST 應用程序更炫酷。要實現這些,你需要添加身份驗證(authentication ),以便用戶在查看/修改(viewing/modifying)之前必須登錄。Okta 的目標是使<a href="https://developer.okta.com/product/user-management/" target="_blank" rel="noopener" title="身份管理">身份管理</a>(identity management)比你往常使用的更簡單、更安全而且更具有擴展性(scalable)。Okta 是一種雲服務,允許開發者創建、編輯和安全存儲用戶賬戶和用戶賬戶數據,並將它們與一個或者多個應用程序連接。我們的 API 使你能夠:</p>
<ul>
<li><a href="https://developer.okta.com/product/authentication/" target="_blank" rel="noopener" title="驗證">驗證</a>(authenticate)和<a href="https://developer.okta.com/product/authorization/" target="_blank" rel="noopener" title="授權">授權</a>(authorize)用戶;</li>
<li>存儲有關用戶的數據;</li>
<li>執行(perform)基於密碼和<a href="https://developer.okta.com/authentication-guide/social-login/" target="_blank" rel="noopener" title="社交登錄">社交登錄</a>;</li>
<li>通過<a href="https://developer.okta.com/use_cases/mfa/" target="_blank" rel="noopener" title="多因素身份驗證">多因素身份驗證</a>(multi-factor authentication)確保應用程序的安全;</li>
<li>如需了解更多,請查看<a href="https://developer.okta.com/documentation/" target="_blank" rel="noopener" title="產品文檔">產品文檔</a>。</li>
</ul>
<p>如果你還沒有,請<a href="https://developer.okta.com/signup/" target="_blank" rel="noopener" title="註冊一個永久免費的賬戶">註冊一個永久免費的賬戶</a>並且開始吧!</p>
<p>創建賬戶後,登陸開發人員控製臺,導航到 <strong>API</strong>,然後轉到 <strong>Authorization Servers</strong> 選項,點擊指向默認服務器的鏈接。</p>
<p>從 <strong>Settings</strong> 選項中,復製 <strong>Issuer</strong> 字段。你需要將此保存到你的 Node 應用程序可以讀取的地方。在項目中,創建一個名為 <strong>.env</strong> 的文件,如下所示:</p>
<p><strong>.env 文件內容:ISSUER=https://{yourOktaDomain}/oauth2/default</strong></p>
<p><strong>ISSUER</strong> 的值應該是設置頁 <strong>Issuer URI</strong> 字段的值。</p>
<figure class="image-box">
<img src="issuer-afa0da4b4f632196092a4da8f243f3bec37615602dc5b62e8e34546fd1018333.png" alt="" title="" class="">
<p></p>
</figure>
<p><strong>註意:</strong>通常情況下,你不應該把 <strong>.env</strong> 文件存儲在源代碼管理器(source control)中。這允許多個項目使用相同的源代碼(source code),而不需要單獨的分叉(fork)。它可以確保你的安全信息不會被公開(尤其是你將要把源代碼作為開源代碼發布時)。</p>
<p>接下來,導航到 <strong>Scopes</strong> 選項卡,點擊 <strong>Add Scope</strong> 按鈕並為你的 REST API 創建一個作用域(scope)。你需要給它起個名字(比如 <strong>parts_manager</strong>),如果你願意你可以給它一個描述(介紹)。</p>
<figure class="image-box">
<img src="adding-scope-f3ecb3b4eec06d616a130400245843c0de2dd52a54b2fdcff7449a10a2ce75ed.png" alt="" title="" class="">
<p></p>
</figure>
<p>你還應該將作用域名稱添加到 <strong>.env</strong> 文件中,這樣代碼就可以訪問它。</p>
<p><strong>.env 文件內容:</strong></p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">ISSUER=https://{yourOktaDomain}/oauth2/default</span><br><span class="line">SCOPE=parts_manager</span><br></pre></td></tr></table></figure>
<p>現在你需要創建一個客戶端。導航到 <strong>Applications</strong>,然後點擊 <strong>Add Application</strong>,選擇 <strong>Service</strong>,然後點擊 <strong>Next</strong>。輸入你的服務名稱(例如 <strong>Parts Manager</strong>),然後點擊 <strong>Done</strong>。</p>
<p>這將帶你到一個具有你的客戶憑據(credentials)的頁面。這些是服務器 B(將使用 RESTAPI 的服務器)進行身份驗證所需的憑據。對於本例,客戶機和服務器代碼在同一個存儲庫中,因此繼續將此數據添加到 <strong>.env</strong> 文件中。確保用此頁中的值替換 <strong>{yourClientId}</strong> 和 <strong>{yourClientSecret}</strong>。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">CLIENT_ID={yourClientId}</span><br><span class="line">CLIENT_SECRET={yourClientSecret}</span><br></pre></td></tr></table></figure>
<h3 id="創建中間件驗證-Express-中的令牌"><a href="#創建中間件驗證-Express-中的令牌" class="headerlink" title="創建中間件驗證 Express 中的令牌"></a>創建中間件驗證 Express 中的令牌</h3><p>在 Express 中,你可以添加將在每個端點之前運行的中間件。然後,你可以添加元數據、設置頭、記錄一些信息,甚至提前取消請求並發送錯誤消息。在這種情況下,你需要創建一些中間件來驗證客戶機發送的令牌。如果令牌有效,它將繼續到 REST API 並返回適當的響應(appropriate response)。如果令牌無效,它將改為響應一條錯誤消息,以便只有授權的計算機可以訪問。</p>
<p>為了驗證令牌,可以使用 Okta 的中間件。你還需要一個名為 <a href="https://github.com/motdotla/dotenv" target="_blank" rel="noopener" title="dotenv">dotenv</a> 的工具來加載環境變量:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install dotenv@6.0.0 @okta/jwt-verifier@0.0.12</span><br></pre></td></tr></table></figure>
<p>現在創建一個名為 <strong>auth.js</strong> 的文件,該文件將導出中間件:</p>
<p><strong>auth.js 文件內容:</strong></p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> OktaJwtVerifier = <span class="built_in">require</span>(<span class="string">'@okta/jwt-verifier'</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> oktaJwtVerifier = <span class="keyword">new</span> OktaJwtVerifier({ <span class="attr">issuer</span>: process.env.ISSUER })</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = <span class="keyword">async</span> (req, res, next) => {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">const</span> { authorization } = req.headers</span><br><span class="line"> <span class="keyword">if</span> (!authorization) <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">'You must send an Authorization header'</span>)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">const</span> [authType, token] = authorization.trim().split(<span class="string">' '</span>)</span><br><span class="line"> <span class="keyword">if</span> (authType !== <span class="string">'Bearer'</span>) <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">'Expected a Bearer token'</span>)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">const</span> { claims } = <span class="keyword">await</span> oktaJwtVerifier.verifyAccessToken(token)</span><br><span class="line"> <span class="keyword">if</span> (!claims.scp.includes(process.env.SCOPE)) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">'Could not verify the proper scope'</span>)</span><br><span class="line"> }</span><br><span class="line"> next()</span><br><span class="line"> } <span class="keyword">catch</span> (error) {</span><br><span class="line"> next(error.message)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>這個函數首先檢查 <strong>authorization</strong> 是否在請求中,否則拋出錯誤。如果它存在,它應該看起來像 <strong>Bearer {token}</strong>,其中 <strong>{token}</strong> 是 <a href="https://www.jsonwebtoken.io/" target="_blank" rel="noopener" title="JWT">JWT</a> 字符串。如果(請求)頭不是以 <strong>bearer</strong> 開頭將會引發另一個錯誤。然後我們把令牌發送給 <a href="https://www.jsonwebtoken.io/" target="_blank" rel="noopener" title="Okta的JWT驗證器">Okta 的 JWT 驗證器</a> 以驗證令牌。如果令牌無效,JWT 驗證器將拋出一個錯誤。否則,它將返回包含一些信息的對象。然後你可以驗證它是否包含你期望的作用域。</p>
<p>如果一切都成功了,它將調用(call)不帶任何參數(parameters)的 <strong>next()</strong> 函數。它告訴 Express 可以轉到鏈中的<strong>下一個</strong>函數(另一個中間件或最後一個 ENDPOINT)。如果你傳遞一個字符串給下一個函數,Express 會將其視為一個錯誤,該錯誤將傳遞回客戶機,並且不會在鏈中繼續。</p>
<p>你仍然需要導入這個函數並將其作為中間件添加到應用程序中。你也同樣需要在 index 文件頭部加載 <strong>dotenv</strong>,以保證來自 <strong>.env</strong> 的環境變量加載到你的應用程序中。對 <strong>index.js</strong> 做出以下更改。</p>
<p><strong>index.js 文件內容:</strong></p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">@@ <span class="number">-1</span>,<span class="number">11</span> +<span class="number">1</span>,<span class="number">14</span> @@</span><br><span class="line">+<span class="built_in">require</span>(<span class="string">'dotenv'</span>).config();</span><br><span class="line"><span class="keyword">const</span> express = <span class="built_in">require</span>(<span class="string">'express'</span>);</span><br><span class="line"><span class="keyword">const</span> bodyParser = <span class="built_in">require</span>(<span class="string">'body-parser'</span>);</span><br><span class="line"><span class="keyword">const</span> { promisify } = <span class="built_in">require</span>(<span class="string">'util'</span>);</span><br><span class="line"></span><br><span class="line">+<span class="keyword">const</span> authMiddleware = <span class="built_in">require</span>(<span class="string">'./auth'</span>);</span><br><span class="line"><span class="keyword">const</span> initializeDatabase = <span class="built_in">require</span>(<span class="string">'./database'</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> app = express();</span><br><span class="line">app.use(bodyParser.json());</span><br><span class="line">+app.use(authMiddleware);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> startServer = <span class="keyword">async</span> () => {</span><br><span class="line"> <span class="keyword">await</span> initializeDatabase(app);</span><br><span class="line"> ...</span><br></pre></td></tr></table></figure>
<p>要測試請求是否被正確阻止,請嘗試再次運行它…</p>
<blockquote>
<p>$ npm test && node .</p>
</blockquote>
<p>…然後在另一個終端中運行一些 <strong>curl</strong> 命令來測試:</p>
<p>需要授權頭</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">$ curl localhost:3000/parts</span><br><span class="line"><span class="meta"><!DOCTYPE <span class="meta-keyword">html</span>></span></span><br><span class="line"><span class="tag"><<span class="name">html</span> <span class="attr">lang</span>=<span class="string">"en"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">head</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">"utf-8"</span> /></span></span><br><span class="line"> <span class="tag"><<span class="name">title</span>></span>Error<span class="tag"></<span class="name">title</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">head</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">body</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">pre</span>></span>You must send an Authorization header<span class="tag"></<span class="name">pre</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">body</span>></span></span><br><span class="line"><span class="tag"></<span class="name">html</span>></span></span><br></pre></td></tr></table></figure>
<p>授權頭中需要無記名令牌(Bearer token)</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">$ curl localhost:3000/parts -H 'Authorization: Basic asdf:1234'</span><br><span class="line"><span class="meta"><!DOCTYPE <span class="meta-keyword">html</span>></span></span><br><span class="line"><span class="tag"><<span class="name">html</span> <span class="attr">lang</span>=<span class="string">"en"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">head</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">"utf-8"</span> /></span></span><br><span class="line"> <span class="tag"><<span class="name">title</span>></span>Error<span class="tag"></<span class="name">title</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">head</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">body</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">pre</span>></span>Expected a Bearer token<span class="tag"></<span class="name">pre</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">body</span>></span></span><br><span class="line"><span class="tag"></<span class="name">html</span>></span></span><br></pre></td></tr></table></figure>
<p>令牌有效</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">$ curl localhost:3000/parts -H 'Authorization: Bearer asdf'</span><br><span class="line"><span class="meta"><!DOCTYPE <span class="meta-keyword">html</span>></span></span><br><span class="line"><span class="tag"><<span class="name">html</span> <span class="attr">lang</span>=<span class="string">"en"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">head</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">"utf-8"</span> /></span></span><br><span class="line"> <span class="tag"><<span class="name">title</span>></span>Error<span class="tag"></<span class="name">title</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">head</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">body</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">pre</span>></span>Jwt cannot be parsed<span class="tag"></<span class="name">pre</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">body</span>></span></span><br><span class="line"><span class="tag"></<span class="name">html</span>></span></span><br></pre></td></tr></table></figure>
<h3 id="使用-Node-創建測試客戶端"><a href="#使用-Node-創建測試客戶端" class="headerlink" title="使用 Node 創建測試客戶端"></a>使用 Node 創建測試客戶端</h3><p>你現在已經為沒有有效令牌的人禁用了對應用程序的訪問,但是你如何獲取令牌並使用它?我將向你展示如何用 Node 編寫一個簡單的客戶機,這也將幫助你測試有效的令牌是否有效。</p>
<blockquote>
<p>$ npm install <a href="mailto:btoa@1.2.1">btoa@1.2.1</a> request-promise@4.2.2</p>
</blockquote>
<p><strong>client.js 文件內容:</strong></p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">require</span>(<span class="string">'dotenv'</span>).config()</span><br><span class="line"><span class="keyword">const</span> request = <span class="built_in">require</span>(<span class="string">'request-promise'</span>)</span><br><span class="line"><span class="keyword">const</span> btoa = <span class="built_in">require</span>(<span class="string">'btoa'</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> { ISSUER, CLIENT_ID, CLIENT_SECRET, SCOPE } = process.env</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> [, , uri, method, body] = process.argv</span><br><span class="line"><span class="keyword">if</span> (!uri) {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'Usage: node client {url} [{method}] [{jsonData}]'</span>)</span><br><span class="line"> process.exit(<span class="number">1</span>)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> sendAPIRequest = <span class="keyword">async</span> () => {</span><br><span class="line"> <span class="keyword">const</span> token = btoa(<span class="string">`<span class="subst">${CLIENT_ID}</span>:<span class="subst">${CLIENT_SECRET}</span>`</span>)</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">const</span> auth = <span class="keyword">await</span> request({</span><br><span class="line"> uri: <span class="string">`<span class="subst">${ISSUER}</span>/v1/token`</span>,</span><br><span class="line"> json: <span class="literal">true</span>,</span><br><span class="line"> method: <span class="string">'POST'</span>,</span><br><span class="line"> headers: {</span><br><span class="line"> authorization: <span class="string">`Basic <span class="subst">${token}</span>`</span>,</span><br><span class="line"> },</span><br><span class="line"> form: {</span><br><span class="line"> grant_type: <span class="string">'client_credentials'</span>,</span><br><span class="line"> scope: SCOPE,</span><br><span class="line"> },</span><br><span class="line"> })</span><br><span class="line"></span><br><span class="line"> <span class="keyword">const</span> response = <span class="keyword">await</span> request({</span><br><span class="line"> uri,</span><br><span class="line"> method,</span><br><span class="line"> body,</span><br><span class="line"> headers: {</span><br><span class="line"> authorization: <span class="string">`<span class="subst">${auth.token_type}</span> <span class="subst">${auth.access_token}</span>`</span>,</span><br><span class="line"> },</span><br><span class="line"> })</span><br><span class="line"></span><br><span class="line"> <span class="built_in">console</span>.log(response)</span><br><span class="line"> } <span class="keyword">catch</span> (error) {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">`Error: <span class="subst">${error.message}</span>`</span>)</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">sendAPIRequest()</span><br></pre></td></tr></table></figure>
<p>這裏的代碼將來自 <strong>.env</strong> 文件中的變量加載到環境中,然後從 Node 中獲取變量。Node 將環境變量存儲在 <strong>process.env</strong> 文件中(<strong>process</strong> 是一個全局變量,包含一系列有用的變量和函數。)。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">require</span>(<span class="string">'dotenv'</span>).config()</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line"><span class="keyword">const</span> { ISSUER, CLIENT_ID, CLIENT_SECRET, SCOPE } = process.env</span><br><span class="line"><span class="comment">// ...</span></span><br></pre></td></tr></table></figure>
<p>接下來,由於這將從命令行運行,你可以再次使用 <strong>process</strong> 來獲取用 <strong>process.argv</strong> 傳入的參數。這將為你提供一個數組,其中包含傳入的所有參數。前兩個逗號(commas)前面沒有變量名,因為前兩個逗號在本例中並不重要;它們只是通向 <strong>node</strong> 的路徑和腳本的名稱(<strong>client</strong> 或 <strong>client.js</strong>)。</p>
<p>URL 是必需的,它將包括 ENDPOINT,但方法和 JSON 數據是可選的(optional)。默認的方法是 <strong>GET</strong>,所以如果你只是提取數據,你可以忽略掉它。在這種情況下,你也不需要任何有效載荷(payload)。如果參數看起來不正確,那麽這將退出程序,並顯示錯誤消息和退出代碼** 1 **來表示錯誤。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> [, , uri, method, body] = process.argv</span><br><span class="line"><span class="keyword">if</span> (!uri) {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'Usage: node client {url} [{method}] [{jsonData}]'</span>)</span><br><span class="line"> process.exit(<span class="number">1</span>)</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>Node 當前不允許在主線程中(main thread)<strong>await</strong>,因此要使用簡潔的 <strong>async/await</strong> 語法,你不得不創建一個函數,然後調用它。</p>
<p>If an error occurs in any of the awaited functions(如果在任何 <strong>awaited</strong> 函數中發生錯誤), the <strong>try/catch</strong> they will be printed out to the screen.</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> sendAPIRequest = <span class="keyword">async</span> () => {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> } <span class="keyword">catch</span> (error) {</span><br><span class="line"> <span class="built_in">console</span>.error(<span class="string">`Error: <span class="subst">${error.message}</span>`</span>)</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">sendAPIRequest()</span><br></pre></td></tr></table></figure>
<p>這是客戶端向授權服務器發送令牌請求的地方。為了使用授權服務器本身進行授權,你需要使用 Basic Auth。Basic Auth 與瀏覽器在獲得一個要求用戶名和密碼的內置彈出窗口時使用的相同。假設(say)你的用戶名是 <strong>AzureDiamond</strong>,密碼是 <strong>hunter2</strong>。你的瀏覽器將它們與冒號(英文冒號)連接在一起,然後用 base64(這是 btoa 函數所做的)對它們進行編碼(encode),以獲得 <strong>QXp1cmVEaWFtb25kOmh1bnRlcjI=</strong>。然後發送 <strong>Basic QXp1cmVEaWFtb25kOmh1bnRlcjI=</strong> 授權頭。然後,服務器可以使用 base64 解碼(decode)令牌以獲取用戶名和密碼。</p>
<p>傳統的授權(原文是「Basic authorization」。)本質上(inherently )不安全,因為它很容易解碼,這就是為什麽 <strong>https</strong> 對於防止中間人攻擊(a man-in-the-middle attack)很重要的原因。在這裏,客戶機 ID(client ID)和客戶機 Secret(client secret)分別是用戶名和密碼,這也是為什麽你的 <strong>CLIENT_ID</strong> 和 <strong>CLIENT_SECRET</strong> 要保密的原因。</p>
<p>對於 OAuth2.0,你還需要指定(specify)授權類型(grant type),在本例中,它是 <strong>client_credentials</strong>,因為你計劃在兩臺計算機之間進行對話。你還需要指定作用域。這裏可以添加許多其他選項,但這是我們演示所需的全部內容。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> token = btoa(<span class="string">`<span class="subst">${CLIENT_ID}</span>:<span class="subst">${CLIENT_SECRET}</span>`</span>)</span><br><span class="line"><span class="keyword">const</span> auth = <span class="keyword">await</span> request({</span><br><span class="line"> uri: <span class="string">`<span class="subst">${ISSUER}</span>/v1/token`</span>,</span><br><span class="line"> json: <span class="literal">true</span>,</span><br><span class="line"> method: <span class="string">'POST'</span>,</span><br><span class="line"> headers: {</span><br><span class="line"> authorization: <span class="string">`Basic <span class="subst">${token}</span>`</span>,</span><br><span class="line"> },</span><br><span class="line"> form: {</span><br><span class="line"> grant_type: <span class="string">'client_credentials'</span>,</span><br><span class="line"> scope: SCOPE,</span><br><span class="line"> },</span><br><span class="line">})</span><br></pre></td></tr></table></figure>
<p>一旦你通過了身份驗證(authenticated),將獲得一個訪問令牌,你可以將其發送到你的 REST API,它看起來應該類似於 <strong>Bearer eyJra…HboUg</strong>(實際令牌比這個長得多,可能大約有 800 個字符。)。令牌包含了你向 REST API 證明你是誰、令牌何時到期所需的所有信息,以及各種其他信息,如請求的作用域、頒發者(issuer)和用於請求令牌的客戶端 ID。</p>
<p>然後將來自 REST API 的響應打印到屏幕上。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> response = <span class="keyword">await</span> request({</span><br><span class="line"> uri,</span><br><span class="line"> method,</span><br><span class="line"> body,</span><br><span class="line"> headers: {</span><br><span class="line"> authorization: <span class="string">`<span class="subst">${auth.token_type}</span> <span class="subst">${auth.access_token}</span>`</span>,</span><br><span class="line"> },</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(response)</span><br></pre></td></tr></table></figure>
<p>現在去測試一下。再次使用 <strong>npm test && node .</strong> 啟動應用程序,然後嘗試以下命令:</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br></pre></td><td class="code"><pre><span class="line">$ node client http://localhost:3000/parts | json</span><br><span class="line">[</span><br><span class="line"> {</span><br><span class="line"> <span class="string">"id"</span>: 1,</span><br><span class="line"> <span class="string">"partNumber"</span>: <span class="string">"abc-123"</span>,</span><br><span class="line"> <span class="string">"modelNumber"</span>: <span class="string">"xyz-789"</span>,</span><br><span class="line"> <span class="string">"name"</span>: <span class="string">"Alphabet Soup"</span>,</span><br><span class="line"> <span class="string">"description"</span>: <span class="string">"Soup with letters and numbers in it"</span>,</span><br><span class="line"> <span class="string">"createdAt"</span>: <span class="string">"2018-08-16T02:22:09.446Z"</span>,</span><br><span class="line"> <span class="string">"updatedAt"</span>: <span class="string">"2018-08-16T02:22:09.446Z"</span></span><br><span class="line"> }</span><br><span class="line">]</span><br><span class="line"></span><br><span class="line">$ node client http://localhost:3000/parts post <span class="string">'{</span></span><br><span class="line"><span class="string"> "partNumber": "ban-bd",</span></span><br><span class="line"><span class="string"> "modelNumber": 1,</span></span><br><span class="line"><span class="string"> "name": "Banana Bread",</span></span><br><span class="line"><span class="string"> "description": "Bread made from bananas"</span></span><br><span class="line"><span class="string">}'</span> | json</span><br><span class="line">{</span><br><span class="line"> <span class="string">"id"</span>: 2,</span><br><span class="line"> <span class="string">"partNumber"</span>: <span class="string">"ban-bd"</span>,</span><br><span class="line"> <span class="string">"modelNumber"</span>: <span class="string">"1"</span>,</span><br><span class="line"> <span class="string">"name"</span>: <span class="string">"Banana Bread"</span>,</span><br><span class="line"> <span class="string">"description"</span>: <span class="string">"Bread made from bananas"</span>,</span><br><span class="line"> <span class="string">"updatedAt"</span>: <span class="string">"2018-08-17T00:23:23.341Z"</span>,</span><br><span class="line"> <span class="string">"createdAt"</span>: <span class="string">"2018-08-17T00:23:23.341Z"</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">$ node client http://localhost:3000/parts | json</span><br><span class="line">[</span><br><span class="line"> {</span><br><span class="line"> <span class="string">"id"</span>: 1,</span><br><span class="line"> <span class="string">"partNumber"</span>: <span class="string">"abc-123"</span>,</span><br><span class="line"> <span class="string">"modelNumber"</span>: <span class="string">"xyz-789"</span>,</span><br><span class="line"> <span class="string">"name"</span>: <span class="string">"Alphabet Soup"</span>,</span><br><span class="line"> <span class="string">"description"</span>: <span class="string">"Soup with letters and numbers in it"</span>,</span><br><span class="line"> <span class="string">"createdAt"</span>: <span class="string">"2018-08-16T02:22:09.446Z"</span>,</span><br><span class="line"> <span class="string">"updatedAt"</span>: <span class="string">"2018-08-16T02:22:09.446Z"</span></span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> <span class="string">"id"</span>: 2,</span><br><span class="line"> <span class="string">"partNumber"</span>: <span class="string">"ban-bd"</span>,</span><br><span class="line"> <span class="string">"modelNumber"</span>: <span class="string">"1"</span>,</span><br><span class="line"> <span class="string">"name"</span>: <span class="string">"Banana Bread"</span>,</span><br><span class="line"> <span class="string">"description"</span>: <span class="string">"Bread made from bananas"</span>,</span><br><span class="line"> <span class="string">"createdAt"</span>: <span class="string">"2018-08-17T00:23:23.341Z"</span>,</span><br><span class="line"> <span class="string">"updatedAt"</span>: <span class="string">"2018-08-17T00:23:23.341Z"</span></span><br><span class="line"> }</span><br><span class="line">]</span><br><span class="line"></span><br><span class="line">$ node client http://localhost:3000/parts/1 delete | json</span><br><span class="line">{}</span><br><span class="line"></span><br><span class="line">$ node client http://localhost:3000/parts | json</span><br><span class="line">[</span><br><span class="line"> {</span><br><span class="line"> <span class="string">"id"</span>: 2,</span><br><span class="line"> <span class="string">"partNumber"</span>: <span class="string">"ban-bd"</span>,</span><br><span class="line"> <span class="string">"modelNumber"</span>: <span class="string">"1"</span>,</span><br><span class="line"> <span class="string">"name"</span>: <span class="string">"Banana Bread"</span>,</span><br><span class="line"> <span class="string">"description"</span>: <span class="string">"Bread made from bananas"</span>,</span><br><span class="line"> <span class="string">"createdAt"</span>: <span class="string">"2018-08-17T00:23:23.341Z"</span>,</span><br><span class="line"> <span class="string">"updatedAt"</span>: <span class="string">"2018-08-17T00:23:23.341Z"</span></span><br><span class="line"> }</span><br><span class="line">]</span><br></pre></td></tr></table></figure>
<p>(到此翻譯完了,以上翻譯內容結合本人理解,如有問題請留言。)</p>
<p>原文鏈接:<a href="https://developer.okta.com/blog/2018/08/21/build-secure-rest-api-with-node?utm_source=com.alibaba.android.rimet&utm_medium=social&utm_oi=1037865074043592704" target="_blank" rel="noopener">https://developer.okta.com/blog/2018/08/21/build-secure-rest-api-with-node?utm_source=com.alibaba.android.rimet&utm_medium=social&utm_oi=1037865074043592704</a></p>
]]></content>
<categories>
<category> 前端 </category>
<category> Node </category>
</categories>
<tags>
<tag> REST API </tag>
</tags>
</entry>
<entry>
<title><![CDATA[URL 重定向]]></title>
<url>/2016/08/28/201608282022/</url>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>在 Web 開發中,經常會出現重定向的情況,重定向指的是將一個網絡請求重新定個方向,向新的方向發起請求。常見的重定向有網頁重定向,訪問一個網頁時卻跳轉到另外一個網頁;也有域名重定向,向某個域名發起請求卻自動變成請求另外一個域名。</p>
<h2 id="實現原理"><a href="#實現原理" class="headerlink" title="實現原理"></a>實現原理</h2><p>URL 重定向是由客戶端完成的(通常是瀏覽器),客戶端發出請求後,服務器做出響應,如果響應頭的狀態碼為 <strong>301</strong> 或者 <strong>302</strong>,此時響應頭中會多出一個 <strong>Location</strong> 字段,該字段用來表示需要重定向到的新位置。<strong>301</strong> 與 <strong>302</strong> 都是 HTTP 的狀態碼,都表示某個請求地址發生了重定向。<strong>301</strong> 重定向表示的是<strong>永久重定向</strong>(Moved Permanently),表示請求的地址永久性地轉移到另外一個地址,今後任何新的請求都應使用新的地址代替。<strong>302</strong> 重定向表示的是<strong>臨時重定向</strong>(Moved Temporarily),表示請求的地址臨時性地轉移到另外一個地址,不改變後續請求的地址。</p>
<figure class="image-box">
<img src="703023-20160623003845360-1558726736.png" alt="" title="" class="">
<p></p>
</figure>
<p>(圖片來自網絡)</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> http = <span class="built_in">require</span>(<span class="string">'http'</span>)</span><br><span class="line"></span><br><span class="line">http</span><br><span class="line"> .createServer(<span class="function">(<span class="params">req, res</span>) =></span> {</span><br><span class="line"> res.writeHead(<span class="number">301</span>, { <span class="attr">Location</span>: <span class="string">'http://example.com/'</span> })</span><br><span class="line"> <span class="comment">// res.writeHead(302, {'Location': 'http://example.com/'});</span></span><br><span class="line"></span><br><span class="line"> res.end()</span><br><span class="line"> })</span><br><span class="line"> .listen(<span class="number">8080</span>)</span><br></pre></td></tr></table></figure>
<blockquote>
<p>註意:客戶端會先判斷狀態碼,只有當狀態碼是 <strong>301</strong> 或 <strong>302</strong> 時,客戶端才會根據響應頭中 <strong>Location</strong> 字段的值進行重定向。</p>
</blockquote>
<h2 id="區別"><a href="#區別" class="headerlink" title="區別"></a>區別</h2><p><strong>301</strong> 重定向與 <strong>302</strong> 重定向有什麽區別呢?</p>
<p>兩者都表示重定向,其效果都是自動跳轉到一個新的地址。不同在於,<strong>301</strong> 表示原有地址的資源已經被永久地移除了,其資源無法訪問了,搜索引擎在抓取新地址內容的同時會把原有地址的權重等信息轉移到新的地址,也同時把原有地址的信息從引擎索引庫中徹底廢棄。瀏覽器也不會再緩存原有地址的資源。<strong>302</strong> 表示臨時重定向,原有地址的資源依舊存在,只是因為特殊原因臨時進行重定向,搜索引擎在抓取新地址內容的同時不會把原有地址的信息從引擎索引庫中徹底廢棄。</p>
<h2 id="應用"><a href="#應用" class="headerlink" title="應用"></a>應用</h2><p><strong>301</strong> 重定向與 <strong>302</strong> 重定向分別在什麽場景下應用呢?下面給了一些常見的場景。</p>
<h3 id="使用-301-重定向"><a href="#使用-301-重定向" class="headerlink" title="使用 301 重定向"></a>使用 <strong>301</strong> 重定向</h3><ul>
<li>原有域名不再使用。比如訪問 <strong><a href="http://www.xiaomi.com" target="_blank" rel="noopener">www.xiaomi.com</a></strong>(小米官網最早的域名)這個域名,直接重定向到 <strong><a href="http://www.mi.com" target="_blank" rel="noopener">www.mi.com</a></strong>,此時使用 <strong>301</strong> 進行重定向。</li>
</ul>
<figure class="image-box">
<img src="{6D7B9A3E-34DB-48F5-A6C5-E38545AAAB84}_20190622141836.jpg" alt="" title="" class="">
<p></p>
</figure>
<ul>
<li><p>站點升級了協議(由 <strong>http</strong> 升級到 <strong>https</strong>),原協議不再使用(這種情況很少)。</p>
</li>
<li><p>主域名不含 <strong>www</strong>(<a href="https://example.com/)。" target="_blank" rel="noopener">https://example.com/)。</a></p>
</li>
</ul>
<figure class="image-box">
<img src="{15276BFE-0F71-4575-B845-ADA546AE9EF8}_20190622145217.jpg" alt="" title="" class="">
<p></p>
</figure>
<h3 id="使用-302-重定向"><a href="#使用-302-重定向" class="headerlink" title="使用 302 重定向"></a>使用 <strong>302</strong> 重定向</h3><ul>
<li>站點升級了協議(由 <strong>http</strong> 升級到 <strong>https</strong>),原協議依舊使用。</li>
</ul>
<figure class="image-box">
<img src="{0E158DC9-DE68-47EC-9C22-C94E84603E9B}_20190622145046.jpg" alt="" title="" class="">
<p></p>
</figure>
<p>(<strong>307</strong> 與 <strong>302</strong> 類似,都表示臨時重定向,只不過 <strong>307</strong> 為 <strong>GET</strong> 請求方式重定向。)</p>
<ul>
<li>網站切換語言。比如訪問 <strong><a href="https://www.microsoft.com/" target="_blank" rel="noopener">https://www.microsoft.com/</a></strong>(微軟的英文官網)這個域名,服務器判斷用戶當前處於中國大陸地區,直接重定向到 <strong><a href="https://www.microsoft.com/zh-cn" target="_blank" rel="noopener">https://www.microsoft.com/zh-cn</a></strong>(微軟的簡體中文官網),此時使用 <strong>302</strong> 進行重定向。類似的還有根據用戶的設備重定向到 PC 端頁面或者移動端頁面。</li>
</ul>
<figure class="image-box">
<img src="{73F1359C-FC27-449D-AB68-C2850DF21022}_20190622150910.jpg" alt="" title="" class="">
<p></p>
</figure>
<ul>
<li>用戶登錄信息過期,自動跳轉到登陸界面。</li>
</ul>
<figure class="image-box">
<img src="2FEWDIpq0trZFeziy9YSF.png" alt="" title="" class="">
<p></p>
</figure>
<p>綜上所述,只要原有地址的資源今後不再使用一般用 <strong>301</strong> 進行重定向,只要是臨時性重定向一般用 <strong>302</strong> 進行重定向。</p>
]]></content>
<categories>
<category> HTTP </category>
<category> 重定向 </category>
</categories>
<tags>
<tag> HTTP </tag>
<tag> URL </tag>
<tag> 重定向 </tag>
</tags>
</entry>
<entry>
<title><![CDATA[原生 JS 實現圖片懶加載]]></title>
<url>/2016/08/06/201608060654/</url>
<content type="html"><![CDATA[<h2 id="圖片懶加載介紹"><a href="#圖片懶加載介紹" class="headerlink" title="圖片懶加載介紹"></a>圖片懶加載介紹</h2><p>什麽是<strong>懶加載</strong>?懶加載其實就是<strong>按需加載</strong>或者說是<strong>條件加載</strong>,是前端一種優化性能的方式。比如訪問一個頁面,裏面有很多圖片(也可以是視頻等其他資源)。如果這些圖片全部加載出來,不僅要在短時間內發出大量請求而且還要進行渲染,這是很消耗時間的。此外,在渲染過程中會阻塞瀏覽器繼續向下解析,影響用戶體驗。所以,最佳的模式是訪問一個頁面的時候當圖片出現在可視區域內的時候(或者距離可視區域一定距離的時候)再去加載而不是一開始就加載全部圖片。當需要加載的時候再發出圖片請求,避免網頁初始化時請求擁堵以及過多渲染阻塞線程。</p>
<h2 id="圖片懶加載原理"><a href="#圖片懶加載原理" class="headerlink" title="圖片懶加載原理"></a>圖片懶加載原理</h2><p><strong>img</strong> 標簽有一個屬性是 <strong>src</strong>,用來表示圖片的地址,當這個屬性的值不為空時,瀏覽器就會根據這個屬性值發出請求;如果這個屬性值為空,則不會發出請求。根據這個原理,將圖片地址賦值給 <strong>img</strong> 標簽的 <strong>data-src</strong> 屬性,而 <strong>src</strong> 屬性值設為空或者設為一個默認圖片的路徑。</p>
<p>獲取 <strong>img</strong> 標簽到瀏覽器頂部的距離,當這個距離等於某個值或在某個範圍內時(開發者自己決定,一般以瀏覽器窗口的可視區域高度為參考標準。),將 <strong>data-src</strong> 的屬性值賦值給 <strong>src</strong> 屬性。</p>
<h2 id="圖片懶加載實現"><a href="#圖片懶加載實現" class="headerlink" title="圖片懶加載實現"></a>圖片懶加載實現</h2><h3 id="getBoundingClientRect"><a href="#getBoundingClientRect" class="headerlink" title="getBoundingClientRect"></a>getBoundingClientRect</h3><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta"><!DOCTYPE <span class="meta-keyword">html</span>></span></span><br><span class="line"><span class="tag"><<span class="name">html</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">head</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">"utf-8"</span> /></span></span><br><span class="line"> <span class="tag"><<span class="name">title</span>></span>圖片懶加載<span class="tag"></<span class="name">title</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">style</span>></span></span><br><span class="line"> * {</span><br><span class="line"> margin: 0;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> li {</span><br><span class="line"> width: 50%;</span><br><span class="line"> height: 500px;</span><br><span class="line"> }</span><br><span class="line"> <span class="tag"></<span class="name">style</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">head</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">body</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">ul</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">li</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">img</span> <span class="attr">src</span>=<span class="string">""</span> <span class="attr">data-src</span>=<span class="string">"1.jpg"</span> /></span></span><br><span class="line"> <span class="tag"></<span class="name">li</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">li</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">img</span> <span class="attr">src</span>=<span class="string">""</span> <span class="attr">data-src</span>=<span class="string">"2.jpg"</span> /></span></span><br><span class="line"> <span class="tag"></<span class="name">li</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">li</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">img</span> <span class="attr">src</span>=<span class="string">""</span> <span class="attr">data-src</span>=<span class="string">"3.jpg"</span> /></span></span><br><span class="line"> <span class="tag"></<span class="name">li</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">li</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">img</span> <span class="attr">src</span>=<span class="string">""</span> <span class="attr">data-src</span>=<span class="string">"4.jpg"</span> /></span></span><br><span class="line"> <span class="tag"></<span class="name">li</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">li</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">img</span> <span class="attr">src</span>=<span class="string">""</span> <span class="attr">data-src</span>=<span class="string">"5.jpg"</span> /></span></span><br><span class="line"> <span class="tag"></<span class="name">li</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">li</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">img</span> <span class="attr">src</span>=<span class="string">""</span> <span class="attr">data-src</span>=<span class="string">"6.jpg"</span> /></span></span><br><span class="line"> <span class="tag"></<span class="name">li</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">li</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">img</span> <span class="attr">src</span>=<span class="string">""</span> <span class="attr">data-src</span>=<span class="string">"7.jpg"</span> /></span></span><br><span class="line"> <span class="tag"></<span class="name">li</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">li</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">img</span> <span class="attr">src</span>=<span class="string">""</span> <span class="attr">data-src</span>=<span class="string">"8.jpg"</span> /></span></span><br><span class="line"> <span class="tag"></<span class="name">li</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">li</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">img</span> <span class="attr">src</span>=<span class="string">""</span> <span class="attr">data-src</span>=<span class="string">"9.jpg"</span> /></span></span><br><span class="line"> <span class="tag"></<span class="name">li</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">ul</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">body</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">script</span>></span></span><br><span class="line"><span class="javascript"> <span class="keyword">let</span> imgArr = <span class="built_in">document</span>.querySelectorAll(<span class="string">'img'</span>)</span></span><br><span class="line"><span class="javascript"> imgArr = <span class="built_in">Array</span>.from(imgArr) <span class="comment">// 將類數組對象轉化為數組</span></span></span><br><span class="line"></span><br><span class="line"><span class="javascript"> <span class="keyword">const</span> imgLazyload = <span class="function"><span class="params">()</span> =></span> {</span></span><br><span class="line"><span class="actionscript"> <span class="keyword">const</span> lazyDistance = <span class="number">20</span> <span class="comment">// 圖片提前加載的距離</span></span></span><br><span class="line"></span><br><span class="line"><span class="javascript"> <span class="keyword">for</span> (<span class="keyword">let</span> index = <span class="number">0</span>; index < imgArr.length; index++) {</span></span><br><span class="line"><span class="actionscript"> <span class="keyword">const</span> imgDOM = imgArr[index]</span></span><br><span class="line"></span><br><span class="line"><span class="actionscript"> <span class="comment">// 獲取圖片上邊框到瀏覽器可視區域頂部的距離</span></span></span><br><span class="line"><span class="actionscript"> <span class="keyword">const</span> imgDOMtop = imgDOM.getBoundingClientRect().top</span></span><br><span class="line"></span><br><span class="line"><span class="actionscript"> <span class="comment">// 獲取瀏覽器可視區域的高度</span></span></span><br><span class="line"><span class="actionscript"> <span class="keyword">const</span> clientHeight =</span></span><br><span class="line"><span class="javascript"> <span class="built_in">document</span>.documentElement.clientHeight || <span class="built_in">window</span>.innerHeight</span></span><br><span class="line"></span><br><span class="line"> if (imgDOMtop - clientHeight <= lazyDistance) {</span><br><span class="line"> imgDOM.src = imgDOM.dataset.src</span><br><span class="line"></span><br><span class="line"><span class="actionscript"> <span class="comment">// 已經加載的圖片從數組中移除從而避免重復操作</span></span></span><br><span class="line"> imgArr.splice(index, 1)</span><br><span class="line"></span><br><span class="line"> /* </span><br><span class="line"> 數組中移除一個元素後,被移除元素後面的元素向前移動一位。</span><br><span class="line"> 如果「index」不減少一個單位,則會跳過原本在被移除元素後面的那個元素。</span><br><span class="line"> */</span><br><span class="line"> index -= 1</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"><span class="actionscript"> <span class="comment">// 當頁面滾動的時候觸發圖片懶加載邏輯</span></span></span><br><span class="line"><span class="javascript"> <span class="built_in">window</span>.addEventListener(<span class="string">'scroll'</span>, () => {</span></span><br><span class="line"> imgLazyload()</span><br><span class="line"> })</span><br><span class="line"></span><br><span class="line"><span class="actionscript"> <span class="comment">// 頁面加載完成後首屏加載一次</span></span></span><br><span class="line"><span class="javascript"> <span class="built_in">window</span>.addEventListener(<span class="string">'load'</span>, () => {</span></span><br><span class="line"> imgLazyload()</span><br><span class="line"> })</span><br><span class="line"> <span class="tag"></<span class="name">script</span>></span></span><br><span class="line"><span class="tag"></<span class="name">html</span>></span></span><br></pre></td></tr></table></figure>
<p>(以上代碼粘貼到 html 文件中即可運行體驗效果)</p>
<h3 id="IntersectionObserver"><a href="#IntersectionObserver" class="headerlink" title="IntersectionObserver"></a>IntersectionObserver</h3><p>上述方法是通過綁定「<strong>scroll</strong>」事件實現的,當頁面滾動時,判斷被觀察元素上邊框與視口頂部的距離從而判斷被觀察元素是否在視口內。但是 <strong>scroll</strong> 事件觸發過於頻繁,很影響頁面的流暢性,甚至會出現卡頓現象。幸運的是,有一個新的接口 <strong>IntersectionObserver</strong>,既可以觀察元素是否在視口內又完美地解決了上述問題。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> observer = <span class="keyword">new</span> IntersectionObserver(callback, option)</span><br><span class="line"></span><br><span class="line">observer.observe(<span class="built_in">document</span>.querySelector(<span class="string">'#domId'</span>))</span><br><span class="line">observer.unobserve(<span class="built_in">document</span>.querySelector(<span class="string">'#domId'</span>))</span><br><span class="line">observer.disconnect()</span><br></pre></td></tr></table></figure>
<p><strong>IntersectionObserver</strong> 是瀏覽器提供的原生構造函數,接受兩個參數,第一個參數是回調函數,另外一個是可選配置參數。該構造函數的返回值是一個觀察器實例。<strong>observe</strong> 用來指定被觀察的元素,如果要觀察多個元素,需要多次調用這個方法。只要被觀察元素的可見性發生變化,就會執行觀察器的回調函數(回調函數會在被觀察元素剛剛進入視口或者<strong>完全</strong>離開視口時觸發)。<strong>unobserve</strong> 用來取消對元素的觀察,用法與 <strong>observe</strong> 相同。<strong>disconnect</strong> 用來關閉觀察器。</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta"><!DOCTYPE <span class="meta-keyword">html</span>></span></span><br><span class="line"><span class="tag"><<span class="name">html</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">head</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">"utf-8"</span> /></span></span><br><span class="line"> <span class="tag"><<span class="name">title</span>></span>圖片懶加載<span class="tag"></<span class="name">title</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">style</span>></span></span><br><span class="line"> * {</span><br><span class="line"> margin: 0;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> li img {</span><br><span class="line"> width: 50%;</span><br><span class="line"> height: 200px;</span><br><span class="line"> margin-bottom: 2000px;</span><br><span class="line"> }</span><br><span class="line"> <span class="tag"></<span class="name">style</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">head</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">body</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">ul</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">li</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">img</span> <span class="attr">src</span>=<span class="string">""</span> <span class="attr">data-src</span>=<span class="string">"1.jpg"</span> /></span></span><br><span class="line"> <span class="tag"></<span class="name">li</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">li</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">img</span> <span class="attr">src</span>=<span class="string">""</span> <span class="attr">data-src</span>=<span class="string">"2.jpg"</span> /></span></span><br><span class="line"> <span class="tag"></<span class="name">li</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">li</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">img</span> <span class="attr">src</span>=<span class="string">""</span> <span class="attr">data-src</span>=<span class="string">"3.jpg"</span> /></span></span><br><span class="line"> <span class="tag"></<span class="name">li</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">li</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">img</span> <span class="attr">src</span>=<span class="string">""</span> <span class="attr">data-src</span>=<span class="string">"4.jpg"</span> /></span></span><br><span class="line"> <span class="tag"></<span class="name">li</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">li</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">img</span> <span class="attr">src</span>=<span class="string">""</span> <span class="attr">data-src</span>=<span class="string">"5.jpg"</span> /></span></span><br><span class="line"> <span class="tag"></<span class="name">li</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">li</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">img</span> <span class="attr">src</span>=<span class="string">""</span> <span class="attr">data-src</span>=<span class="string">"6.jpg"</span> /></span></span><br><span class="line"> <span class="tag"></<span class="name">li</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">li</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">img</span> <span class="attr">src</span>=<span class="string">""</span> <span class="attr">data-src</span>=<span class="string">"7.jpg"</span> /></span></span><br><span class="line"> <span class="tag"></<span class="name">li</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">li</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">img</span> <span class="attr">src</span>=<span class="string">""</span> <span class="attr">data-src</span>=<span class="string">"8.jpg"</span> /></span></span><br><span class="line"> <span class="tag"></<span class="name">li</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">li</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">img</span> <span class="attr">src</span>=<span class="string">""</span> <span class="attr">data-src</span>=<span class="string">"9.jpg"</span> /></span></span><br><span class="line"> <span class="tag"></<span class="name">li</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">ul</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">body</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">script</span>></span></span><br><span class="line"><span class="javascript"> <span class="keyword">let</span> imgArr = <span class="built_in">document</span>.querySelectorAll(<span class="string">'img'</span>)</span></span><br><span class="line"><span class="javascript"> imgArr = <span class="built_in">Array</span>.from(imgArr) <span class="comment">// 將類數組對象轉化為數組</span></span></span><br><span class="line"></span><br><span class="line"><span class="actionscript"> <span class="comment">// 初始化一個實例</span></span></span><br><span class="line"><span class="javascript"> <span class="keyword">const</span> observer = <span class="keyword">new</span> IntersectionObserver(<span class="function">(<span class="params">changes</span>) =></span> {</span></span><br><span class="line"><span class="actionscript"> <span class="comment">// changes是一個數組,被觀察元素的可見性若是發生變化,該元素的信息就會存入數組中。</span></span></span><br><span class="line"><span class="javascript"> <span class="keyword">for</span> (<span class="keyword">let</span> index = <span class="number">0</span>; index < changes.length; index++) {</span></span><br><span class="line"><span class="actionscript"> <span class="keyword">const</span> changer = changes[index]</span></span><br><span class="line"></span><br><span class="line"><span class="actionscript"> <span class="comment">// console.log(changer.time); // 被觀察元素可見性發生變化時的時間,是一個以毫秒為單位的時間戳。</span></span></span><br><span class="line"><span class="actionscript"> <span class="comment">// console.log(changer.target); // 可見性發生變化的目標元素</span></span></span><br><span class="line"><span class="actionscript"> <span class="comment">// console.log(changer.rootBounds); // 根元素矩形區域的信息,默認根元素是瀏覽器視口。</span></span></span><br><span class="line"><span class="actionscript"> <span class="comment">// console.log(changer.boundingClientRect); // 目標元素矩形區域的信息</span></span></span><br><span class="line"><span class="actionscript"> <span class="comment">// console.log(changer.intersectionRect); // 目標元素與根元素(默認根元素是瀏覽器視口)交叉區域的信息</span></span></span><br><span class="line"><span class="actionscript"> <span class="comment">// console.log(changer.intersectionRatio); // 目標元素的可見比例,即intersectionRect占boundingClientRect的比例。</span></span></span><br><span class="line"></span><br><span class="line"> if (changer.intersectionRatio > 0) {</span><br><span class="line"><span class="actionscript"> <span class="keyword">const</span> targetDOM = changer.target</span></span><br><span class="line"></span><br><span class="line"> targetDOM.src = targetDOM.dataset.src</span><br><span class="line"></span><br><span class="line"><span class="actionscript"> <span class="comment">// 對已經出現在視口的元素取消觀察</span></span></span><br><span class="line"> observer.unobserve(targetDOM)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> })</span><br><span class="line"></span><br><span class="line"><span class="javascript"> <span class="keyword">for</span> (<span class="keyword">let</span> index = <span class="number">0</span>; index < imgArr.length; index++) {</span></span><br><span class="line"><span class="actionscript"> <span class="keyword">const</span> imgDOM = imgArr[index]</span></span><br><span class="line"></span><br><span class="line"> observer.observe(imgDOM)</span><br><span class="line"> }</span><br><span class="line"> <span class="tag"></<span class="name">script</span>></span></span><br><span class="line"><span class="tag"></<span class="name">html</span>></span></span><br></pre></td></tr></table></figure>
<p>(以上代碼粘貼到 html 文件中即可運行體驗效果)</p>
<p><strong>IntersectionObserver</strong> 的第二個參數是一個配置對象。其可以有以下屬性:</p>
<ul>
<li><p><strong>threshold</strong>:默認值為 <strong>[0]</strong>,表示 <strong>intersectionRatio == 0</strong> 時回調函數會被觸發。當然,開發人員也可以設置為 <strong>[0, 0.5, 1]</strong>,表示 <strong>intersectionRatio</strong> 等於 0、0.5、1 時回調函數會被觸發。</p>
</li>
<li><p><strong>root</strong>:默認情況下,被觀察元素的可見性變化是相對於瀏覽器視口的,既以瀏覽器視口為參考系的。該屬性的作用就是修改被觀察元素的參考視口。</p>
</li>
<li><p><strong>rootMargin</strong>:這個屬性用來給 <strong>root</strong> 元素設置 <strong>margin</strong> 值,使用方法與 <strong>CSS</strong> 中的 <strong>margin</strong> 屬性一樣。運行上面的兩個案例會發現,在第一個案例中,圖片標簽將要出現在視口內時就開始加載圖片,另一個案例恰恰相反,圖片標簽出現在視口後才開始加載圖片(被觀察元素只有與 <strong>root</strong> 元素有交叉(交集)時才會觸發回調函數),並未達到預加載效果。給 <strong>root</strong> 元素設置 <strong>rootMargin</strong> 屬性後,被觀察元素只要與 <strong>root</strong> 元素外邊距有交叉(交集)時就會觸發回調函數,達到了未進入視口前就開始加載的效果。</p>
</li>
</ul>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> observer = <span class="keyword">new</span> IntersectionObserver(callback, {</span><br><span class="line"> threshold: [<span class="number">0</span>, <span class="number">0.5</span>, <span class="number">1</span>],</span><br><span class="line"> root: <span class="built_in">document</span>.querySelector(<span class="string">'#domId'</span>),</span><br><span class="line"> rootMargin: <span class="string">'0 0 20px 0'</span>,</span><br><span class="line">})</span><br></pre></td></tr></table></figure>
<blockquote>
<p>註意:如果被觀察元素不是 <strong>root</strong> 元素的子節點,即使被觀察元素出現在瀏覽器視口內,也不會觸發回調函數。</p>
</blockquote>
<p><strong>IntersectionObserver</strong> 是一個<strong>異步</strong>接口,其回調函數只有在瀏覽器線程空閑的時候才會執行,如果瀏覽器當前的任務隊列中有待處理的任務,其回調函數是不會被執行的。</p>
<p>參考鏈接:<a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect" target="_blank" rel="noopener">https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect</a></p>
]]></content>
<categories>
<category> 前端 </category>
<category> JavaScript </category>
</categories>
<tags>
<tag> JavaScript </tag>
<tag> 性能優化 </tag>
<tag> 懶加載 </tag>
</tags>
</entry>
<entry>
<title><![CDATA[常見「同源策略」問題解決方案]]></title>
<url>/2016/08/02/201608020650/</url>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>「同源政策」(same-origin policy)是由 <strong>Netscape</strong> 公司引入瀏覽器的。其使得瀏覽器更加安全,現在所有瀏覽器都支持了這個策略。然而,安全的同時也使得開發過程中出現了一些麻煩。本文列出了一些常見的同源策略問題以及相對應的解決方案。</p>
<h2 id="常見問題"><a href="#常見問題" class="headerlink" title="常見問題"></a>常見問題</h2><p>「同源」指的是兩個地址之間<strong>協議相同</strong>、<strong>域名相同</strong>、<strong>端口相同</strong>,只要有一個不相同就會出現問題。</p>
<p>目前,受到「同源政策」限製的有以下功能:</p>
<ul>
<li>Cookie、LocalStorage、IndexDB 的讀取;</li>
<li>AJAX 請求的響應被瀏覽器拒絕(瀏覽器雖然收到了響應,但是並不使用);</li>
<li>DOM 無法獲取(更不用說操作了)。</li>
</ul>
<h3 id="Cookie"><a href="#Cookie" class="headerlink" title="Cookie"></a>Cookie</h3><p>Cookie 是由服務器寫入瀏覽器的,用來控製客戶端的狀態。雖然受到「同源政策」限製,但是並不苛刻。同源的頁面之間可以共享 Cookie,一級域名相同二級域名不同的頁面之間也可以通過設置 <strong>document.domain</strong> 共享 Cookie。</p>
<blockquote>
<p>註意:該方法僅適用於 Cookie、iframe 窗口,LocalStorage、IndexDB 無法使用該方法解決「同源政策」問題。</p>
</blockquote>
<h3 id="iframe"><a href="#iframe" class="headerlink" title="iframe"></a>iframe</h3><p><strong>iframe</strong> 標簽使得一個頁面中可以嵌入另一個頁面,如果兩個頁面不同源,就無法拿到對方的 DOM。這樣會造成父子頁面無法通信。</p>
<p>對於不同源的父子頁面有以下方法解決通信問題:</p>
<ol>
<li>動態 hash</li>
<li>window.name</li>
<li>window.postMessage</li>
</ol>
<h4 id="動態-hash"><a href="#動態-hash" class="headerlink" title="動態 hash"></a>動態 hash</h4><p>在父頁面中,通過使用<code><iframe src="http://example.com/XXX.html#message"></iframe></code>來嵌入子頁面。其中,<strong><a href="http://example.com/XXX.html" target="_blank" rel="noopener">http://example.com/XXX.html</a></strong> 為子頁面地址,<strong>#</strong> 後面的部分稱之為<strong>片段標識符(Fragment Identifier)</strong>,片段標識符改變頁面不會重新加載,父頁面傳遞給子頁面的數據可以放到此部分內。</p>
<p>其過程是父頁面動態修改 <strong>iframe</strong> 標簽中 <strong>src</strong> 屬性值的片段標識符部分,子頁面通過 <strong>window.onhashchange</strong> 事件監聽片段標識符部分的變化。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 父頁面代碼</span></span><br><span class="line"><span class="built_in">document</span>.querySelector(<span class="string">'iframe'</span>).src = url + <span class="string">'#'</span> + message</span><br><span class="line"><span class="comment">// url為子頁面地址</span></span><br><span class="line"><span class="comment">// message變量存放父頁面傳遞給子頁面的數據</span></span><br></pre></td></tr></table></figure>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 子頁面代碼</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">window</span>.onhashchange = <span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> <span class="keyword">const</span> message = <span class="built_in">window</span>.location.hash</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>同理,該方法也可以逆向使用,即實現子頁面向父頁面傳遞數據。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 父頁面代碼</span></span><br><span class="line"><span class="built_in">window</span>.onhashchange = <span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> <span class="keyword">const</span> message = <span class="built_in">window</span>.location.hash</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 子頁面代碼</span></span><br><span class="line">parent.location.href = url + <span class="string">'#'</span> + message</span><br><span class="line"><span class="comment">// url為父頁面地址</span></span><br><span class="line"><span class="comment">// message變量存放子頁面傳遞給父頁面的數據</span></span><br></pre></td></tr></table></figure>
<h4 id="window-name"><a href="#window-name" class="headerlink" title="window.name"></a>window.name</h4><p><strong>window.name</strong> 是一個特殊的屬性。無論頁面之間是否同源,只要在同一個窗口內,共享該屬性值(前一個頁面設置了該屬性,後一個頁面就可以獲取它。)。</p>
<!-- 其過程是父頁面打開子頁面,子頁面將需要傳遞的數據寫入window.name屬性內,寫入後子頁面跳轉到一個與父頁面同域的頁面,此時父頁面就可以讀取子頁面存在window.name內的數據了。操作繁瑣且必須一直監聽子頁面window.name屬性值的變化,不建議使用。 -->
<h4 id="window-postMessage"><a href="#window-postMessage" class="headerlink" title="window.postMessage"></a>window.postMessage</h4><p>這是 HTML5 新增加的 API,無論兩個窗口是否同源,都可以進行跨窗口通信。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 父頁面向子頁面發送消息</span></span><br><span class="line"><span class="keyword">const</span> popupPage = <span class="built_in">window</span>.open(<span class="string">'http://father.com'</span>)</span><br><span class="line"></span><br><span class="line">popupPage.postMessage(<span class="string">'message'</span>, <span class="string">'http://son.com'</span>)</span><br><span class="line"><span class="comment">// message是向子頁面發送的消息 http://son.com是接收消息的子頁面</span></span><br><span class="line"><span class="comment">// postMessage第二個參數為*時不限製域名,向所有窗口發送</span></span><br></pre></td></tr></table></figure>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 子頁面向父頁面發送消息</span></span><br><span class="line"><span class="built_in">window</span>.opener.postMessage(<span class="string">'message'</span>, <span class="string">'http://father.com'</span>)</span><br></pre></td></tr></table></figure>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 父頁面和子頁面都可以使用message事件監聽對方的消息</span></span><br><span class="line"><span class="built_in">window</span>.addEventListener(<span class="string">'message'</span>, (e) => {</span><br><span class="line"> <span class="built_in">console</span>.log(e.data) <span class="comment">// 接收到的數據內容</span></span><br><span class="line"> <span class="built_in">console</span>.log(e.source) <span class="comment">// 發送消息的頁面地址</span></span><br><span class="line"> <span class="built_in">console</span>.log(e.origin) <span class="comment">// 接收消息的頁面地址</span></span><br><span class="line"></span><br><span class="line"> e.source.postMessage(<span class="string">'message'</span>, <span class="string">'*'</span>)</span><br><span class="line"> <span class="comment">// 子頁面通過e.source屬性引用父頁面並發送消息</span></span><br><span class="line">})</span><br></pre></td></tr></table></figure>
<p><strong>因此,通過 window.postMessage 可以間接讀寫其他頁面的 LocalStorage、IndexDB。</strong></p>
<p>參考鏈接:<a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage" target="_blank" rel="noopener">https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage</a></p>
<h3 id="AJAX"><a href="#AJAX" class="headerlink" title="AJAX"></a>AJAX</h3><p>由於同源政策的影響,AJAX 請求只能發給同源的網址,否則報錯。解決方法有 JSONP、iframe、postMessage、window.name、代理服務器、CORS、WebSocket。</p>
<h4 id="JSONP"><a href="#JSONP" class="headerlink" title="JSONP"></a>JSONP</h4><p>該方法是瀏覽器與服務器跨源通信的常用方法,對於前端簡單且兼容性好,對於後端服務器變動小。其原理是 <strong>script</strong>、<strong>img</strong> 等標簽中的 <strong>src</strong> 屬性請求非同源鏈接不受同源策略影響。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> getData = <span class="function">(<span class="params">data</span>) =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(data)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 動態添加script標簽</span></span><br><span class="line"><span class="comment">// 向跨源網址發出請求</span></span><br><span class="line"><span class="keyword">let</span> script = <span class="built_in">document</span>.createElement(<span class="string">'script'</span>)</span><br><span class="line">script.setAttribute(<span class="string">'type'</span>, <span class="string">'text/javascript'</span>)</span><br><span class="line">script.src = <span class="string">'http://example.com/?callback=getData'</span></span><br><span class="line"><span class="comment">// callback參數用來指定回調函數的名字 服務器收到這個請求以後將數據放在回調函數的參數位置後一起返回</span></span><br><span class="line"><span class="built_in">document</span>.body.appendChild(script)</span><br></pre></td></tr></table></figure>
<h4 id="CORS"><a href="#CORS" class="headerlink" title="CORS"></a>CORS</h4><p>CORS 是跨域資源分享(Cross-Origin Resource Sharing)的縮寫。它使得瀏覽器能夠向非同源服務器發出 AJAX 請求,解決了 AJAX 請求只能向同源服務器發出請求的限製。</p>
<figure class="image-box">
<img src="CORS_principle.png" alt="" title="" class="">
<p></p>
</figure>
<p>(圖片來自網絡)</p>
<p>1.簡單非同源請求:</p>
<p>瀏覽器如果請求非同源服務器,會自動在請求頭中添加 <strong>Origin</strong>(綠圈標註部分)字段,用來說明本次請求來自哪個源。服務器會根據這個字段值,決定是否同意這次非同源請求。如果 <strong>Origin</strong> 指定的源不在許可範圍內,服務器會返回一個常規的 HTTP 響應。瀏覽器檢測響應頭,發現沒有包含 <strong>Access-Control-Allow-Origin</strong>(紅圈標註部分)字段,此時瀏覽器會拋出一個錯誤,請求失敗,<strong>此時狀態碼仍有可能是 200</strong>,響應數據雖然返回給了瀏覽器,但是被瀏覽器攔截了,無法在請求回調函數中獲取。相反,如果 <strong>Origin</strong> 指定的源在許可範圍內,則響應頭中包含 <strong>Access-Control-Allow-Origin</strong> 字段,可以收到數據。</p>
<ul>
<li><strong>Access-Control-Allow-Origin</strong>:該字段取值要麽是請求頭中 <strong>Origin</strong> 字段的值,要麽是一個星號(表示允許來自所有域的跨域請求)。</li>
</ul>
<figure class="image-box">
<img src="simple_req.png" alt="" title="" class="">
<p></p>
</figure>
<p>(圖片來自網絡)</p>
<p>2.含有「預檢請求」的非同源請求:</p>
<p>這種請求會在正式請求發出之前提前發出一次 HTTP 查詢請求(稱之為「<strong>預檢請求</strong>」)。<strong>預檢請求</strong>是瀏覽器預先詢問服務器,當前頁面的域名是否在服務器的允許名單之中,以及可以使用哪些 HTTP <strong>請求方法</strong>和<strong>請求頭字段</strong>。當提出的要求被服務器完全「同意」後,瀏覽器才會發出真正的非同源請求,否則無法進行下一步或者報錯。</p>
<p>「預檢請求」使用的請求方法是 <strong>OPTIONS</strong>(紫圈標註部分),用來告訴服務器該請求是「預檢請求」,不是真正的請求。此外,”預檢請求”的請求頭中還包含 <strong>Access-Control-Request-Method</strong>、<strong>Access-Control-Request-Headers</strong>(黃圈標註部分)兩個特殊字段。</p>
<ul>
<li><strong>Access-Control-Request-Method</strong>:該字段是瀏覽器用來告訴服務器接下來的正式請求將要使用哪些 HTTP 請求方法。</li>
<li><strong>Access-Control-Request-Headers</strong>:該字段取值是一個逗號分隔的字符串。該字段是瀏覽器用來告訴服務器接下來的正式請求在請求頭中將要附加額外的請求頭字段。</li>
</ul>
<p>與此同時,服務器對「預檢請求」做出響應,在響應頭中包含 <strong>Access-Control-Allow-Methods</strong>、<strong>Access-Control-Allow-Headers</strong>(紅圈標註部分)等字段。</p>
<ul>
<li><strong>Access-Control-Allow-Methods</strong>:該字段取值是一個逗號分隔的字符串。表明服務器支持的<strong>所有</strong>跨域請求的方法(避免多次「<strong>預檢請求</strong>」)。</li>
<li><strong>Access-Control-Allow-Headers</strong>:該字段用來指定接下來的正式請求允許攜帶的請求頭字段。</li>
<li><strong>Access-Control-Max-Age</strong>:該字段用來指定本次<strong>預檢請求</strong>的有效期(有效期內不用再發送<strong>預檢請求</strong>),單位為秒。</li>
<li><strong>Access-Control-Expose-Headers</strong>:該字段取值是一個逗號分隔的字符串。在跨域請求時,<strong>XMLHttpRequest</strong> 對象的 <strong>getResponseHeader</strong> 方法只能拿到一些最基本的響應頭字段(Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma)。如果想拿到其他字段,就必須在 <strong>Access-Control-Expose-Headers</strong> 裏面指定。</li>
</ul>
<figure class="image-box">
<img src="prelight.png" alt="" title="" class="">
<p></p>
</figure>
<p>(圖片來自網絡)</p>
<p>滿足以下條件之一的請求在正式請求發出之前會提前發出「預檢請求」:</p>
<ul>
<li>一個請求在請求頭中包含了任何自定義請求頭字段。</li>
<li>使用 HTTP 請求方式是 <strong>GET</strong>、<strong>HEAD</strong>、<strong>POST</strong> 之外的任何一種方式。</li>
<li>請求方式是 <strong>POST</strong>,但請求頭的 <strong>Content-Type</strong> 字段取值是 <strong>application/x-www-form-urlencoded</strong>、<strong>multipart/form-data</strong>、<strong>text/plain</strong> 之外的。</li>
</ul>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> url = <span class="string">'http://example.com/'</span></span><br><span class="line"><span class="keyword">const</span> xhr = <span class="keyword">new</span> XMLHttpRequest()</span><br><span class="line"></span><br><span class="line">xhr.open(<span class="string">'PUT'</span>, url, <span class="literal">true</span>)</span><br><span class="line">xhr.setRequestHeader(<span class="string">'X-Custom-Header'</span>, <span class="string">'value'</span>)</span><br><span class="line">xhr.send()</span><br></pre></td></tr></table></figure>
<p>上面的 <strong>XMLHttpRequest</strong> 請求中,請求方法是 <strong>PUT</strong>,不是常規請求方式。且在生成的請求頭中,有一個用戶自定義的字段 <strong>X-Custom-Header</strong>,瀏覽器發現這是個非正常字段自動發出一個”預檢請求”,詢問服務器是否可以接受這個非正常請求頭字段。如果服務器回絕則返回一個正常的響應頭,此時瀏覽器報錯;如果同意,則返回的響應頭中會有 <strong>Access-Control-Allow-Headers: X-Custom-Header</strong>。</p>
<p>3.附帶身份憑證的非同源請求:</p>
<p>該請求和前兩種請求類似,只不過在發送請求的時候,需要將用戶憑證包含在請求中。</p>
<p>默認情況下,Cookie 不會包含在非同源請求之中,開發者需要在非同源請求中把 <strong>withCredentials</strong> 屬性的值設置為 <strong>true</strong>,而且服務器響應頭中的 <strong>Access-Control-Allow-Credentials</strong> 字段取值也要設置為 <strong>true</strong>,兩者缺一不可。只有這樣,瀏覽器才會把響應內容返回給請求的發起者。對於這種請求,響應頭中的 <strong>Access-Control-Allow-Origin</strong> 字段取值不能為星號。這是因為請求頭中攜帶了 Cookie 信息,如果 <strong>Access-Control-Allow-Origin</strong> 取值為星號請求將會失敗,而將 <strong>Access-Control-Allow-Origin</strong> 字段的值設置為請求發起者所在域名則請求成功執行。此外,這時響應頭中也攜帶了 <strong>Set-Cookie</strong> 字段。</p>
<figure class="image-box">
<img src="cred-req.png" alt="" title="" class="">
<p></p>
</figure>
<p>(圖片來自網絡)</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> url = <span class="string">'http://example.com/'</span></span><br><span class="line"><span class="keyword">const</span> xhr = <span class="keyword">new</span> XMLHttpRequest()</span><br><span class="line"></span><br><span class="line">xhr.open(<span class="string">'GET'</span>, url, <span class="literal">true</span>)</span><br><span class="line">xhr.withCredentials = <span class="literal">true</span></span><br><span class="line"><span class="comment">// XMLHttpRequest的withCredentials設置為true從而向服務器發送Cookie</span></span><br><span class="line">xhr.send()</span><br></pre></td></tr></table></figure>
<blockquote>
<p>註意:有的瀏覽器不用設置 <strong>withCredentials</strong> 為 <strong>true</strong> 也可以向服務器發送 Cookie,可以通過 <strong>xhr.withCredentials = false</strong> 顯式關閉。</p>
</blockquote>
<style>
h4 {
font-size: 14px !important;
}
</style>
]]></content>
<categories>
<category> 前端 </category>
<category> HTTP </category>
<category> 跨域 </category>
</categories>
<tags>
<tag> HTTP </tag>
<tag> 跨域 </tag>
</tags>
</entry>
<entry>
<title><![CDATA[深入淺出 HTTP 協議(上)]]></title>
<url>/2016/07/20/201607200640/</url>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>無論大家是前端程序員還是後端程序員,相信大家對 <strong>HTTP</strong> 這個詞一定不陌生,在程序開發中它無處不在。所以,深入了解 HTTP 協議將會提高自己的編程質量以及提高前後端對接的效率。</p>
<h2 id="HTTP-工作原理"><a href="#HTTP-工作原理" class="headerlink" title="HTTP 工作原理"></a>HTTP 工作原理</h2><h3 id="HTTP-協議簡介"><a href="#HTTP-協議簡介" class="headerlink" title="HTTP 協議簡介"></a>HTTP 協議簡介</h3><p>HTTP 協議是 <strong>Hyper Text Transfer Protocol(超文本傳輸協議)</strong>的縮寫,是用於從萬維網服務器傳輸超文本(例如 HTML 文件、圖片文件、視頻文件等)到本地瀏覽器的傳輸協議(<strong>應用層協議</strong>),也是互聯網上應用最為廣泛的一種網絡協議。</p>
<h3 id="HTTP-架構設計"><a href="#HTTP-架構設計" class="headerlink" title="HTTP 架構設計"></a>HTTP 架構設計</h3><p>HTTP 是基於 <strong>TCP/IP</strong> 通信協議來傳遞數據,工作於客戶端-服務端架構上。瀏覽器作為 HTTP 客戶端通過 <strong>URL</strong> 向 HTTP 服務端發送請求,服務器收到請求後,向客戶端發送響應信息。瀏覽器通過與服務器建立 TCP 連接,之後發送 HTTP 請求與接收 HTTP 響應都是通過訪問 Socket 接口調用 TCP 協議實現。</p>
<figure class="image-box">
<img src="201811235001.jpg" alt="" title="" class="">
<p></p>
</figure>
<p>(圖片來自網絡)</p>
<blockquote>
<p>註意:在這種架構設計下,如果客戶端沒有向服務器發起請求,服務器無法主動向客戶端推送消息。</p>
</blockquote>
<h3 id="URL"><a href="#URL" class="headerlink" title="URL"></a>URL</h3><p>HTTP 使用<strong>統一資源標識符(Uniform Resource Identifiers, URI)</strong>來傳輸數據和建立連接。一旦建立連接後,數據消息就通過類似 Internet 郵件所使用的格式[RFC5322]和多用途 Internet 郵件擴展(MIME)[RFC2045]來傳送。<strong>URL(Uniform Resource Locator)</strong>是一種特殊類型的 URI,包含了用於查找某個資源的足夠的信息。URL 中文叫<strong>統一資源定位符</strong>,是互聯網上用來標識某一處資源的地址。</p>
<p>以 <strong><a href="http://www.munan.wiki:8080/tags/index.html?pageID=5&ID=25618&page=1#title" target="_blank" rel="noopener">http://www.munan.wiki:8080/tags/index.html?pageID=5&ID=25618&page=1#title</a></strong> 為例:</p>
<table>
<thead>
<tr>
<th align="center">content</th>
<th align="center">描述</th>
</tr>
</thead>
<tbody><tr>
<td align="center">http</td>
<td align="center">該部分為協議部分,這代表該 URL 使用的是 HTTP 協議。</td>
</tr>
<tr>
<td align="center"><a href="http://www.munan.wiki" target="_blank" rel="noopener">www.munan.wiki</a></td>
<td align="center">該部分為域名部分。一個 URL 中,也可以使用 IP 地址作為域名。</td>
</tr>
<tr>
<td align="center">8080</td>
<td align="center">該部分為端口部分,域名和端口之間使用「:」作為分隔符。默認不寫為 80 端口。</td>
</tr>
<tr>
<td align="center">tags</td>
<td align="center">該部分為虛擬目錄部分,從域名後的第一個「/」開始到最後一個「/」為止,是虛擬目錄部分。</td>
</tr>
<tr>
<td align="center">index.html</td>
<td align="center">該部分為文件名部分,從域名後的最後一個「/」開始到「?」為止,是文件名部分。</td>
</tr>
<tr>
<td align="center">pageID=5&ID=25618&page=1</td>
<td align="center">該部分為參數部分。參數可以有多個,參數之間用「&」作為分隔符。</td>
</tr>
<tr>
<td align="center">title</td>
<td align="center">該部分為錨部分,從「#」開始到最後,都是錨部分。</td>
</tr>
</tbody></table>
<h3 id="HTTP-請求過程"><a href="#HTTP-請求過程" class="headerlink" title="HTTP 請求過程"></a>HTTP 請求過程</h3><p>平日裏訪問一個網站只需要在瀏覽器搜索框裏輸入網址也就是 URL 就可以了,從用戶輸入 URL 到看到網站內容經歷了以下幾個步驟:</p>
<figure class="image-box">
<img src="856284184-5a6abb497fab2_articlex.jpg" alt="" title="" class="">
<p></p>
</figure>
<p>(圖片來自網絡)</p>
<ol>
<li>域名解析:首先會搜索本地 DNS 緩存,如果沒有就向 DNS 服務器發起域名解析,以獲取該域名所對應的 IP 地址;</li>
<li>建立 TCP 連接:獲取域名對應的 IP 後,跟據 IP 地址和端口號與服務器建立 TCP 連接(也就是 TCP 的 3 次握手連接);</li>
<li>HTTP 請求:TCP 連接成功後,瀏覽器向服務器發起 HTTP 請求報文(報文內容包含請求行、請求頭部、請求主體,該請求報文作為 TCP 三次握手的第三個報文),用來向服務器請求文件;</li>
<li>服務器響應:服務器對瀏覽器請求作出響應,並把對應的 html 文本發送給瀏覽器;</li>
<li>解析代碼:瀏覽器得到響應後,首先解析狀態行,查看狀態碼表明請求是否成功,如果成功讀取響應數據並請求 html 文本中引用的圖片文件、js 文件、css 文件等;</li>
<li>渲染執行頁面:瀏覽器獲取所有資源後,對頁面進行渲染執行呈現給用戶。</li>
</ol>
<h3 id="HTTP-狀態碼"><a href="#HTTP-狀態碼" class="headerlink" title="HTTP 狀態碼"></a>HTTP 狀態碼</h3><p>下面是常見的 HTTP 狀態碼(HTTP Status Code):</p>
<table>
<thead>
<tr>
<th align="center">code</th>
<th align="center">描述</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><strong>1xx</strong></td>
<td align="center"><strong>信息響應類,服務器收到請求,需要請求者繼續執行操作。</strong></td>
</tr>
<tr>
<td align="center">100</td>
<td align="center">(Continue)客戶端應繼續其請求。</td>
</tr>
<tr>
<td align="center">101</td>
<td align="center">(Switching Protocols)服務器根據客戶端的請求切換協議。只能切換到更高級的協議。</td>
</tr>
<tr>
<td align="center"><strong>2xx</strong></td>
<td align="center"><strong>處理成功響應類,操作被成功接收並處理。</strong></td>
</tr>
<tr>
<td align="center">200</td>
<td align="center">(OK)客戶端請求成功,一般用於 GET 與 POST 請求。</td>
</tr>
<tr>
<td align="center">201</td>
<td align="center">(Created)成功請求並創建了新的資源。</td>
</tr>
<tr>
<td align="center">202</td>
<td align="center">(Accepted)接受並處理、但處理未完成。</td>
</tr>
<tr>
<td align="center">203</td>
<td align="center">(Non-Authoritative Information)請求成功,但返回的 meta 信息不在原始的服務器,而是一個副本。</td>
</tr>
<tr>
<td align="center">204</td>
<td align="center">(No Content)服務器成功處理,但未返回內容。</td>
</tr>
<tr>
<td align="center">205</td>
<td align="center">(Reset Content)服務器處理成功,用戶終端應重置文檔視圖,可通過此返回碼清除瀏覽器的表單域(重置內容)。</td>
</tr>
<tr>
<td align="center">206</td>
<td align="center">(Partial Content)服務器成功處理了部分 GET 請求。</td>
</tr>
<tr>
<td align="center"><strong>3xx</strong></td>
<td align="center"><strong>重定向響應類,需要進一步的操作以完成請求。</strong></td>
</tr>
<tr>
<td align="center">300</td>
<td align="center">(Multiple Choices)請求的資源可包括多個位置,相應可返回一個資源特征與地址的列表用於用戶終端選擇。</td>
</tr>
<tr>
<td align="center">301</td>
<td align="center">(Moved Permanently)請求的資源已被永久的移動到新 URI,返回信息會包括新的 URI,瀏覽器會自動定向到新 URI。今後任何新的請求都應使用新的 URI 代替。</td>
</tr>
<tr>
<td align="center">302</td>
<td align="center">(Found)與 301 類似,但資源只是臨時被移動。客戶端應繼續使用原有 URI。</td>
</tr>
<tr>
<td align="center">303</td>
<td align="center">(See Other)與 301 類似,使用 GET 和 POST 請求查看,建議客戶訪問其他 URL 或訪問方式。</td>
</tr>
<tr>
<td align="center">304</td>
<td align="center">(Not Modified)所請求的資源未修改,服務器返回此狀態碼時,不會返回任何資源。客戶端通常會緩存訪問過的資源,通過提供一個頭信息指出客戶端希望只返回在指定日期之後修改的資源。</td>
</tr>